fix(review): wire Session Manager to /api/sessions/unified contract, stop Ctrl+K PTY leak, finish shortcut registry (PR #146)

- Session Manager (COD-121/192): align _loadSessionManagerList() with the
  merged #139 endpoint — map UnifiedSessionItem fields (lastActivityAt
  epoch-ms → lastModified, optional sizeBytes/firstPrompt/name) to the
  history-record shape _buildHistoryItem renders; surface non-2xx /
  error-envelope responses as a visible message instead of a silent
  "No sessions found"; route clicks by liveness (live row → selectSession,
  history row → resumeHistorySession by conversation UUID) via a new
  onActivate option so a live session is never duplicate-resumed
- Ctrl+K double-dispatch: gate the palette chord in
  attachCustomKeyEventHandler (return false on keydown) so xterm never
  writes 0x0b kill-line into the PTY while the palette opens; gate is
  registry-aware so a rebound/disabled palette shortcut restores normal
  terminal Ctrl+K
- Shortcut registry (COD-157) finished per maintainer decision: document
  keydown now dispatches through getShortcutRegistry() +
  matchesShortcutEvent() (legacy SHORTCUTS table removed), honoring
  per-shortcut disable and rebinds incl. the palette chord; overrides
  persist via saveAppSettingsToStorage() (correct device key + cache
  coherence, was orphaned 'codeman:settings'); Shortcuts tab renders on
  open via switchSettingsTab hook; capture uses a persistent listener that
  ignores bare modifier keydowns (combos now capturable) and requires a
  Ctrl/Cmd/Alt chord; settings rows use delegated listeners instead of
  inline onclick (JS-string injection sink) and overrides can no longer
  clobber id/label/action; added the missing row + overlay CSS
- matchesShortcutEvent: reject undeclared extra modifiers (Ctrl+Shift+K
  no longer hijacked from Firefox devtools) while keeping Ctrl/Cmd
  interchangeable; match physical code OR produced key for layout parity
- Registry/dispatch gaps: added restore-terminal-size entry, documented
  Ctrl+Shift+R again in the help modal (test flipped to assert presence),
  Ctrl+?/Alt+? now really open the registry-driven shortcut overlay, and
  Escape closes it
- Palette new-session pick routes through selectQuickStartCase() so the
  searchable combobox, dir display, and lastUsedCase stay in sync
- Removed fork cherry-pick debris: dead _onSessionListMaybeChanged(),
  orphaned .session-row-menu CSS, nonexistent closeMobileHeaderUtilities
  calls
- Tests: functional vm-harness coverage for the unified-list field
  mapping + error state + liveness routing, palette chord shift/disable/
  rebind handling, override persistence round-trip, capture flow, tab
  render hook, and source guards for the PTY gate + registry dispatch

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-07-12 19:06:12 +02:00
parent 8a133d083b
commit 88f47754ad
10 changed files with 645 additions and 140 deletions
+18 -2
View File
@@ -134,6 +134,17 @@ Object.assign(CodemanApp.prototype, {
return false;
}
// Command palette chord (COD-153): keep it out of the PTY. The document
// CAPTURE handler has already opened the palette by the time xterm sees
// this keydown, but its preventDefault() does NOT stop xterm — without
// this gate Ctrl+K would ALSO write 0x0b (readline kill-line) into the
// live session behind the palette, truncating whatever the user had
// typed. Route through the registry-aware checker so a rebound or
// disabled palette shortcut restores normal terminal Ctrl+K.
if (ev.type === 'keydown' && this.shouldOpenCommandPaletteFromShortcut?.(ev)) {
return false;
}
// Ctrl+V / Cmd+V: intercept before xterm sends ^V to PTY.
// Route through our paste trap which handles both images and text.
if ((ev.ctrlKey || ev.metaKey) && ev.key === 'v' && ev.type === 'keydown') {
@@ -1074,6 +1085,7 @@ Object.assign(CodemanApp.prototype, {
* @param {Array} cases linked cases (for #caseName label)
* @param {object} [options]
* @param {boolean} [options.showViewAll=true] show "View all in folder" button in detail panel
* @param {Function} [options.onActivate] main-row click handler override (default: resume the conversation)
*/
_buildHistoryItem(s, cases, options) {
const showViewAll = options?.showViewAll !== false;
@@ -1095,10 +1107,14 @@ Object.assign(CodemanApp.prototype, {
item.className = 'history-item';
item.title = s.workingDir;
// Main row: clickable surface that triggers resume
// Main row: clickable surface that triggers resume (or a caller-supplied
// activation — the Session Manager switches to live sessions instead)
const mainRow = document.createElement('div');
mainRow.className = 'history-item-main';
mainRow.addEventListener('click', () => this.resumeHistorySession(s.sessionId, s.workingDir));
mainRow.addEventListener(
'click',
options?.onActivate || (() => this.resumeHistorySession(s.sessionId, s.workingDir))
);
const textCol = document.createElement('div');
textCol.className = 'history-item-text';