diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index 0796ae8a..46782e00 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -4229,3 +4229,53 @@ Done when: probe runs as one exec round-trip, RemoteToolset drives open-failure INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DQEMTDHF8SV27AKAB8JHW', 'description', 'PLACEHOLDER — exact tickets crystallize after the Phase-0 spike (T-330) picks the footprint model. Both designs below so the choice is a swap, not a redesign. IF auto-pushed agent: execution layer is mostly transport + provisioning — subsystems (GitClient, PqlClient, FilesService, SearchService, NativePty) run UNCHANGED inside the remote agent; buildDispatcher (main.dart ~:176) split into UI-coupled vs headless-safe registrations; new bin/clide_agent.dart hosts the headless set + an IpcServer; RemoteTransport = SSH-tunneled agent socket (stdio bridge); watching is native inotify in the agent (watcher.dart unchanged); plus a provisioning module lib/src/remote/ (push, version-name, launch, GC). IF no-install ssh-exec: introduce a RemoteExecutionContext that each subsystem uses instead of bare Process.run/File/Directory — ssh -tt for PTYs, multiplexed ControlMaster command channels for git/pql/file ops, and a polling watcher (debounced git-status/mtime, or inotifywait if present) emitting the same FileChange events so the UI is unaware; heavier subsystem surface, zero remote footprint. Either way: pql runs where its .pql/ index lives (remote), git runs where .git/ lives (remote), clide still only wraps pql (D-3 preserved). Blocks Phases 3 and 4. Depends on Phase 0 (T-330) and Phase 1 (T-331).', 'PLACEHOLDER — exact tickets crystallize after the Phase-0 spike (T-330) picks the footprint model. Both designs below so the choice is a swap, not a redesign. IF auto-pushed agent: execution layer is mostly transport + provisioning — subsystems (GitClient, PqlClient, FilesService, SearchService, NativePty) run UNCHANGED inside the remote agent; buildDispatcher (main.dart ~:176) split into UI-coupled vs headless-safe registrations; new bin/clide_agent.dart hosts the headless set + an IpcServer; RemoteTransport = SSH-tunneled agent socket (stdio bridge); watching is native inotify in the agent (watcher.dart unchanged); plus a provisioning module lib/src/remote/ (push, version-name, launch, GC). IF no-install ssh-exec: introduce a RemoteExecutionContext that each subsystem uses instead of bare Process.run/File/Directory — ssh -tt for PTYs, multiplexed ControlMaster command channels for git/pql/file ops, and a polling watcher (debounced git-status/mtime, or inotifywait if present) emitting the same FileChange events so the UI is unaware; heavier subsystem surface, zero remote footprint. Either way: pql runs where its .pql/ index lives (remote), git runs where .git/ lives (remote), clide still only wraps pql (D-3 preserved). Blocks Phases 3 and 4. Depends on Phase 0 (T-330) and Phase 1 (T-331). 2026-06-12: footprint decided — no-install ssh-exec (D-96, user pick). This story is now the EXECUTION-LAYER UMBRELLA for that model; the agent-model branch in the description above is dead. Expanded into: T-398 (SSH connection manager — ControlMaster + exec channel, the foundation), T-399 (ExecutionContext seam sweep across git/pql/files/search/editor), T-400 (remote PTY via ssh -tt through the existing NativePty), T-401 (polling watcher), T-402 (D-98 preflight probe). Blocker graph: 399/400/402 by 398; 401 by 399. Close this story when those five are done; T-333/T-334 unblock then.', NULL, '2026-06-12 03:16:59', '2026-06-12 03:16:59', '2026-06-12 03:16:59', NULL, 'ee6546888baa7789804fc49c278a6e60', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKP67X1Y1FEE9T5R0E5DA9C', 'description', NULL, 'From the 2026-06-12 vim keybind review (user: "we are leaving opportunities on the table" for cross-pane vim interactions). Findings: + +TODAY the vim layer (T-65) is editor-only. vim.normal/insert/visual scope flags are global (VimModeService), but every binding in vim.yaml either targets editor.vim.* (applied by the focused editor''s key handler, editor_view.dart _dispatchVim) or is a copy of the default preset''s app chords. Outside the editor, the vim preset offers nothing vim-shaped: no ctrl+w window family, no gt/gT, no j/k in the file tree / ticket list / git panel / conversation (those panes have NO key handling at all — mouse-only), no ex command line (vim_mode_service.dart explicitly defers it as "a transient overlay"). + +EXISTING primitives to map onto: focus.nextPanel/previousPanel (F6/shift+F6), panel.focus.left/middle/right (ctrl+1/2/3), panel.focusMode (ctrl+. — semantically EXACTLY vim''s ctrl+w o "only"), editor.open/close (ctrl+e/ctrl+w), dock.toggle (ctrl+j), sidebar.collapse/context.collapse, quickOpen, alt+1..5 sidebar sections. The D-82 sequence matcher already resolves exact-vs-longer ambiguity with a pending-exact + timeout (sequence_matcher.dart _pendingExact), so chord-prefixed sequences like "ctrl+w h" are expressible in preset YAML today. + +GAP also found: no workspace tab next/prev cycling command exists for ANY preset (only direct alt+N for sidebar sections) — child ticket adds the commands, vim binds gt/gT to them. + +Children: T-404 (ctrl+w window-command family), T-405 (tab cycle commands + gt/gT), T-406 (normal-mode list/scroll nav intents for non-editor panes), T-407 (ex command-line overlay). 404/405 are YAML+small-command work; 406 is the structural one; 407 is the most visible.', NULL, '2026-06-12 03:21:06', '2026-06-12 03:21:06', '2026-06-12 03:21:06', NULL, 'bf4b66250410bce443e0114635bb2401', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKP8KFAF526ZNXBQS98DPPG', 'description', NULL, 'Bind vim''s window-command prefix in assets/keymaps/vim.yaml, guarded `when: vim.normal` (and probably `|| vim.visual`), mapping onto the existing panel commands — no new services: + +- `ctrl+w h` → command:panel.focus.left; `ctrl+w l` → command:panel.focus.right (clide''s three-column layout has no vertical pane stack, so j/k map to the dock: `ctrl+w j` → command:dock.toggle — document the approximation in the YAML comment) +- `ctrl+w w` and `ctrl+w ctrl+w` → focus.nextPanel; `ctrl+w shift+w` → focus.previousPanel +- `ctrl+w o` → command:panel.focusMode (vim "only" — exact semantic match) +- `ctrl+w q` and `ctrl+w c` → command:editor.close + +Conflict to resolve (the real work): editor.close carries defaultBinding ''ctrl+w'' globally. Verify how preset bindings + defaultBindings merge in KeymapService, and that the sequence matcher''s pending-exact path (sequence_matcher.dart, _pendingExact + timeout flush) makes bare ctrl+w wait for a possible second chord under the vim preset — bare ctrl+w should still close the editor after the ambiguity timeout, prefix completions should win immediately. Add matcher tests for chord-prefixed sequences (existing tests cover `d d` letter sequences; `ctrl+w h` adds a modified first chord). + +Done when: all bindings above work under the vim preset with editor focused AND with tree/conversation focused (they''re global commands, not editor.vim.*); bare ctrl+w still closes the editor after the timeout; no behavior change under default/vscode/jetbrains presets; keymap loader + matcher tests cover the new shapes.', NULL, '2026-06-12 03:21:26', '2026-06-12 03:21:26', '2026-06-12 03:21:26', NULL, '263c482620fb5598ea49e292dc7d573a', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPAZR4XEV8YW3PVR2XJBFC', 'description', NULL, 'Two halves; the first benefits every preset (the review found NO tab-cycling command exists anywhere — only alt+1..5 direct sidebar-section picks): + +1. New commands in the default-layout extension (or panels host): workspace.tab.next / workspace.tab.previous — cycle the workspace slot''s tab strip (PanelRegistry/MultitabPane activate-next/previous with wraparound). Give them defaultBindings ctrl+pagedown / ctrl+pageup (the GTK/VS Code convention) so default/vscode/jetbrains presets gain tab cycling for free. Check lib/kernel/src/panels/registry.dart for the activation API; add one if only direct activateTab(id) exists. + +2. vim.yaml: `g t` → command:workspace.tab.next, `g shift+t` → command:workspace.tab.previous, when vim.normal. Watch the existing `g g` (docStart) prefix — the matcher already buffers `g`, so `g t` slots in beside it; add a matcher/loader test for two sequences sharing the `g` prefix with different finals. + +Done when: ctrl+pagedown/up cycle workspace tabs under every preset; gt/gT cycle under vim; shared-prefix sequence test green; alt+N behavior unchanged.', NULL, '2026-06-12 03:21:43', '2026-06-12 03:21:43', '2026-06-12 03:21:43', NULL, '73451f38a7c81a487fa3d539e9d7daa5', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPD85PFBPJJTQ0WS3PWWXR', 'description', NULL, 'The structural piece: make vim NORMAL mode mean something in panes that aren''t the editor. Today the file tree, ticket board, git panel, and conversation view have no keyboard handling at all (mouse-only — verified 2026-06-12); under the vim preset, j/k outside the editor are dead keys. + +Mechanism (follow the ActivateIntent pattern from default.yaml — intents dispatched via Actions.maybeInvoke against the FOCUSED context, so only opted-in widgets respond and there''s no global-flag confusion): + +1. New typed intents in kernel/src/keymap/intents.dart: nav.down / nav.up / nav.pageDown / nav.pageUp / nav.top / nav.bottom / nav.expandOrRight / nav.collapseOrLeft / nav.activate (ids in builtinIntents). +2. vim.yaml binds them when "vim.normal && !editor.focused": j/k, ctrl+d/ctrl+u, "g g"/shift+g, l/h, [o, enter]. Needs an editor.focused scope flag if none exists — check what the editor publishes today; the editor''s own key handler consumes j/k first when focused, so the guard may even be unnecessary — verify dispatch order and document it. +3. Panes opt in with Actions handlers: + - file tree (lib/builtin/files/src/file_tree_view.dart): selection cursor + j/k move, h/l collapse/expand-or-step-into, o/enter open (the NERDTree idiom) + - conversation view (lib/builtin/claude/src/conversation_view.dart): j/k line scroll, ctrl+d/u half page, G jump-to-bottom AND re-arm follow-tail (_atBottom), gg top + - ticket board + git panel lists: selection cursor + activate +4. default/vscode/jetbrains presets can bind the same intents to arrows/page keys later — the intents are preset-neutral; this ticket only wires vim. + +Scope guard: this is keyboard NAVIGATION only — no editing semantics outside the editor. Start with tree + conversation (highest value), lists can trail in a follow-up commit on the same ticket. + +Done when: with the vim preset active and the tree/conversation focused, j/k/ctrl+d/ctrl+u/gg/G work as above; widget tests per pane; zero behavior change under other presets and in insert mode.', NULL, '2026-06-12 03:22:05', '2026-06-12 03:22:05', '2026-06-12 03:22:05', NULL, '78c20e5b2c1549926cecc559a95426ff', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPFTC7H5NY0XHBTEGF8XQ4', 'description', NULL, 'The deferred piece vim_mode_service.dart already names: "command-line is surfaced separately as a transient overlay rather than a persistent mode." Minimal ex line, not a vimscript interpreter: + +- `:` (shift+semicolon) when vim.normal opens a one-line overlay (reuse the quick-open overlay chrome/widgets; it is NOT a mode — Esc dismisses back to normal, no scope-flag churn beyond an exline.open flag for its own enter/escape bindings). +- v1 grammar, one table, no parsing cleverness: + :w → editor save (find the editor''s save command id; check editor_commands.dart _save), :q → command:editor.close, :wq / :x → save then close, :e → quickOpen.open pre-seeded with (check QuickOpenIntent for a seed param; add one if absent), : → editor goto-line (editor has a goto? if not, smallest possible addition to editor.vim ops), : → shake/flash + stay open. +- ZZ ("shift+z shift+z" sequence) → save-close, riding the same plumbing — include it here, it''s one YAML line once :wq exists. +- Cross-pane angle: the ex line is GLOBAL under vim.normal (works with tree/conversation focused — :q closes the focused tab via editor.close fallback to active workspace tab; keep v1 simple: editor-targeted only, document it). + +Done when: : opens the overlay from any pane under the vim preset; the v1 table works with widget tests; unknown commands don''t execute anything; ZZ saves+closes.', NULL, '2026-06-12 03:22:28', '2026-06-12 03:22:28', '2026-06-12 03:22:28', NULL, '267348e926541d1c7b5e55c3c1ef6219', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index 63de95d1..2b731694 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -4517,3 +4517,58 @@ Done when: probe runs as one exec round-trip, RemoteToolset drives open-failure INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DQEMTDHF8SV27AKAB8JHW', 'story', '06FB3DHCTP001YCHFP39XER0ZM', 'Fork — execution layer (expand after Phase-0 spike decides footprint)', 'PLACEHOLDER — exact tickets crystallize after the Phase-0 spike (T-330) picks the footprint model. Both designs below so the choice is a swap, not a redesign. IF auto-pushed agent: execution layer is mostly transport + provisioning — subsystems (GitClient, PqlClient, FilesService, SearchService, NativePty) run UNCHANGED inside the remote agent; buildDispatcher (main.dart ~:176) split into UI-coupled vs headless-safe registrations; new bin/clide_agent.dart hosts the headless set + an IpcServer; RemoteTransport = SSH-tunneled agent socket (stdio bridge); watching is native inotify in the agent (watcher.dart unchanged); plus a provisioning module lib/src/remote/ (push, version-name, launch, GC). IF no-install ssh-exec: introduce a RemoteExecutionContext that each subsystem uses instead of bare Process.run/File/Directory — ssh -tt for PTYs, multiplexed ControlMaster command channels for git/pql/file ops, and a polling watcher (debounced git-status/mtime, or inotifywait if present) emitting the same FileChange events so the UI is unaware; heavier subsystem surface, zero remote footprint. Either way: pql runs where its .pql/ index lives (remote), git runs where .git/ lives (remote), clide still only wraps pql (D-3 preserved). Blocks Phases 3 and 4. Depends on Phase 0 (T-330) and Phase 1 (T-331). 2026-06-12: footprint decided — no-install ssh-exec (D-96, user pick). This story is now the EXECUTION-LAYER UMBRELLA for that model; the agent-model branch in the description above is dead. Expanded into: T-398 (SSH connection manager — ControlMaster + exec channel, the foundation), T-399 (ExecutionContext seam sweep across git/pql/files/search/editor), T-400 (remote PTY via ssh -tt through the existing NativePty), T-401 (polling watcher), T-402 (D-98 preflight probe). Blocker graph: 399/400/402 by 398; 401 by 399. Close this story when those five are done; T-333/T-334 unblock then.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-10 13:26:56', '2026-06-12 03:16:59', NULL, '4fc2f8e6f5436799502a571f7bc8cce6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKP67X1Y1FEE9T5R0E5DA9C', 'story', NULL, 'vim cross-pane interaction layer — window commands, tab motions, pane-local normal-mode nav', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-12 03:20:52', '2026-06-12 03:20:52', NULL, 'bcb56a3fee5350d82a45f846bb500f83', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKP67X1Y1FEE9T5R0E5DA9C', 'story', NULL, 'vim cross-pane interaction layer — window commands, tab motions, pane-local normal-mode nav', 'From the 2026-06-12 vim keybind review (user: "we are leaving opportunities on the table" for cross-pane vim interactions). Findings: + +TODAY the vim layer (T-65) is editor-only. vim.normal/insert/visual scope flags are global (VimModeService), but every binding in vim.yaml either targets editor.vim.* (applied by the focused editor''s key handler, editor_view.dart _dispatchVim) or is a copy of the default preset''s app chords. Outside the editor, the vim preset offers nothing vim-shaped: no ctrl+w window family, no gt/gT, no j/k in the file tree / ticket list / git panel / conversation (those panes have NO key handling at all — mouse-only), no ex command line (vim_mode_service.dart explicitly defers it as "a transient overlay"). + +EXISTING primitives to map onto: focus.nextPanel/previousPanel (F6/shift+F6), panel.focus.left/middle/right (ctrl+1/2/3), panel.focusMode (ctrl+. — semantically EXACTLY vim''s ctrl+w o "only"), editor.open/close (ctrl+e/ctrl+w), dock.toggle (ctrl+j), sidebar.collapse/context.collapse, quickOpen, alt+1..5 sidebar sections. The D-82 sequence matcher already resolves exact-vs-longer ambiguity with a pending-exact + timeout (sequence_matcher.dart _pendingExact), so chord-prefixed sequences like "ctrl+w h" are expressible in preset YAML today. + +GAP also found: no workspace tab next/prev cycling command exists for ANY preset (only direct alt+N for sidebar sections) — child ticket adds the commands, vim binds gt/gT to them. + +Children: T-404 (ctrl+w window-command family), T-405 (tab cycle commands + gt/gT), T-406 (normal-mode list/scroll nav intents for non-editor panes), T-407 (ex command-line overlay). 404/405 are YAML+small-command work; 406 is the structural one; 407 is the most visible.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-12 03:20:52', '2026-06-12 03:21:06', NULL, 'c010f4263ca39c5852ed7615dda1e94b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKP8KFAF526ZNXBQS98DPPG', 'task', '06FBKP67X1Y1FEE9T5R0E5DA9C', 'vim ctrl+w window-command family', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-12 03:21:11', '2026-06-12 03:21:11', NULL, 'f7e0c94b671a3da751bf2a1359bb9eca', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKP8KFAF526ZNXBQS98DPPG', 'task', '06FBKP67X1Y1FEE9T5R0E5DA9C', 'vim ctrl+w window-command family', 'Bind vim''s window-command prefix in assets/keymaps/vim.yaml, guarded `when: vim.normal` (and probably `|| vim.visual`), mapping onto the existing panel commands — no new services: + +- `ctrl+w h` → command:panel.focus.left; `ctrl+w l` → command:panel.focus.right (clide''s three-column layout has no vertical pane stack, so j/k map to the dock: `ctrl+w j` → command:dock.toggle — document the approximation in the YAML comment) +- `ctrl+w w` and `ctrl+w ctrl+w` → focus.nextPanel; `ctrl+w shift+w` → focus.previousPanel +- `ctrl+w o` → command:panel.focusMode (vim "only" — exact semantic match) +- `ctrl+w q` and `ctrl+w c` → command:editor.close + +Conflict to resolve (the real work): editor.close carries defaultBinding ''ctrl+w'' globally. Verify how preset bindings + defaultBindings merge in KeymapService, and that the sequence matcher''s pending-exact path (sequence_matcher.dart, _pendingExact + timeout flush) makes bare ctrl+w wait for a possible second chord under the vim preset — bare ctrl+w should still close the editor after the ambiguity timeout, prefix completions should win immediately. Add matcher tests for chord-prefixed sequences (existing tests cover `d d` letter sequences; `ctrl+w h` adds a modified first chord). + +Done when: all bindings above work under the vim preset with editor focused AND with tree/conversation focused (they''re global commands, not editor.vim.*); bare ctrl+w still closes the editor after the timeout; no behavior change under default/vscode/jetbrains presets; keymap loader + matcher tests cover the new shapes.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-12 03:21:11', '2026-06-12 03:21:26', NULL, '5d1048dc893b51f245a8dbbda5f96f8c', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPAZR4XEV8YW3PVR2XJBFC', 'task', '06FBKP67X1Y1FEE9T5R0E5DA9C', 'workspace tab cycle commands + vim gt/gT', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-12 03:21:31', '2026-06-12 03:21:31', NULL, 'db7e75ad6a5f5854fb349cf68899a8c8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPAZR4XEV8YW3PVR2XJBFC', 'task', '06FBKP67X1Y1FEE9T5R0E5DA9C', 'workspace tab cycle commands + vim gt/gT', 'Two halves; the first benefits every preset (the review found NO tab-cycling command exists anywhere — only alt+1..5 direct sidebar-section picks): + +1. New commands in the default-layout extension (or panels host): workspace.tab.next / workspace.tab.previous — cycle the workspace slot''s tab strip (PanelRegistry/MultitabPane activate-next/previous with wraparound). Give them defaultBindings ctrl+pagedown / ctrl+pageup (the GTK/VS Code convention) so default/vscode/jetbrains presets gain tab cycling for free. Check lib/kernel/src/panels/registry.dart for the activation API; add one if only direct activateTab(id) exists. + +2. vim.yaml: `g t` → command:workspace.tab.next, `g shift+t` → command:workspace.tab.previous, when vim.normal. Watch the existing `g g` (docStart) prefix — the matcher already buffers `g`, so `g t` slots in beside it; add a matcher/loader test for two sequences sharing the `g` prefix with different finals. + +Done when: ctrl+pagedown/up cycle workspace tabs under every preset; gt/gT cycle under vim; shared-prefix sequence test green; alt+N behavior unchanged.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-12 03:21:31', '2026-06-12 03:21:43', NULL, '1a8fc46b24bcdb47326b1a1ae23ae785', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPD85PFBPJJTQ0WS3PWWXR', 'task', '06FBKP67X1Y1FEE9T5R0E5DA9C', 'normal-mode list/scroll navigation intents for non-editor panes', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-12 03:21:49', '2026-06-12 03:21:49', NULL, '083c644a38176907300739c60ac621e1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPD85PFBPJJTQ0WS3PWWXR', 'task', '06FBKP67X1Y1FEE9T5R0E5DA9C', 'normal-mode list/scroll navigation intents for non-editor panes', 'The structural piece: make vim NORMAL mode mean something in panes that aren''t the editor. Today the file tree, ticket board, git panel, and conversation view have no keyboard handling at all (mouse-only — verified 2026-06-12); under the vim preset, j/k outside the editor are dead keys. + +Mechanism (follow the ActivateIntent pattern from default.yaml — intents dispatched via Actions.maybeInvoke against the FOCUSED context, so only opted-in widgets respond and there''s no global-flag confusion): + +1. New typed intents in kernel/src/keymap/intents.dart: nav.down / nav.up / nav.pageDown / nav.pageUp / nav.top / nav.bottom / nav.expandOrRight / nav.collapseOrLeft / nav.activate (ids in builtinIntents). +2. vim.yaml binds them when "vim.normal && !editor.focused": j/k, ctrl+d/ctrl+u, "g g"/shift+g, l/h, [o, enter]. Needs an editor.focused scope flag if none exists — check what the editor publishes today; the editor''s own key handler consumes j/k first when focused, so the guard may even be unnecessary — verify dispatch order and document it. +3. Panes opt in with Actions handlers: + - file tree (lib/builtin/files/src/file_tree_view.dart): selection cursor + j/k move, h/l collapse/expand-or-step-into, o/enter open (the NERDTree idiom) + - conversation view (lib/builtin/claude/src/conversation_view.dart): j/k line scroll, ctrl+d/u half page, G jump-to-bottom AND re-arm follow-tail (_atBottom), gg top + - ticket board + git panel lists: selection cursor + activate +4. default/vscode/jetbrains presets can bind the same intents to arrows/page keys later — the intents are preset-neutral; this ticket only wires vim. + +Scope guard: this is keyboard NAVIGATION only — no editing semantics outside the editor. Start with tree + conversation (highest value), lists can trail in a follow-up commit on the same ticket. + +Done when: with the vim preset active and the tree/conversation focused, j/k/ctrl+d/ctrl+u/gg/G work as above; widget tests per pane; zero behavior change under other presets and in insert mode.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-12 03:21:49', '2026-06-12 03:22:05', NULL, 'ed32bc2d1104dd21559d5432808327d5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPFTC7H5NY0XHBTEGF8XQ4', 'task', '06FBKP67X1Y1FEE9T5R0E5DA9C', 'vim ex command-line overlay (:w :q :e :N)', NULL, 'backlog', 'low', NULL, NULL, NULL, '2026-06-12 03:22:10', '2026-06-12 03:22:10', NULL, '349cf7a92c07e61b94e4881d80f3b3b1', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBKPFTC7H5NY0XHBTEGF8XQ4', 'task', '06FBKP67X1Y1FEE9T5R0E5DA9C', 'vim ex command-line overlay (:w :q :e :N)', 'The deferred piece vim_mode_service.dart already names: "command-line is surfaced separately as a transient overlay rather than a persistent mode." Minimal ex line, not a vimscript interpreter: + +- `:` (shift+semicolon) when vim.normal opens a one-line overlay (reuse the quick-open overlay chrome/widgets; it is NOT a mode — Esc dismisses back to normal, no scope-flag churn beyond an exline.open flag for its own enter/escape bindings). +- v1 grammar, one table, no parsing cleverness: + :w → editor save (find the editor''s save command id; check editor_commands.dart _save), :q → command:editor.close, :wq / :x → save then close, :e → quickOpen.open pre-seeded with (check QuickOpenIntent for a seed param; add one if absent), : → editor goto-line (editor has a goto? if not, smallest possible addition to editor.vim ops), : → shake/flash + stay open. +- ZZ ("shift+z shift+z" sequence) → save-close, riding the same plumbing — include it here, it''s one YAML line once :wq exists. +- Cross-pane angle: the ex line is GLOBAL under vim.normal (works with tree/conversation focused — :q closes the focused tab via editor.close fallback to active workspace tab; keep v1 simple: editor-targeted only, document it). + +Done when: : opens the overlay from any pane under the vim preset; the v1 table works with widget tests; unknown commands don''t execute anything; ZZ saves+closes.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-12 03:22:10', '2026-06-12 03:22:28', NULL, 'f5eeef51f090bf48c73133940d5348f9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/governance/README.md b/governance/README.md index 66de51c0..0881e1b9 100644 --- a/governance/README.md +++ b/governance/README.md @@ -137,6 +137,10 @@ You might also want, project-permitting: - [D-93: clide writes no directories of its own into the workspace](decisions/architecture.md#d-93-clide-writes-no-directories-of-its-own-into-the-workspace) — _architecture_ - [D-94: Workspace mode is a first-class, extensible declared capability](decisions/architecture.md#d-94-workspace-mode-is-a-first-class-extensible-declared-capability) — _architecture_ - [D-95: Workspace validity and onboarding flow](decisions/architecture.md#d-95-workspace-validity-and-onboarding-flow) — _architecture_ +- [D-96: Remote-execution footprint — no-install ssh-exec](decisions/architecture.md#d-96-remote-execution-footprint--no-install-ssh-exec) — _architecture_ +- [D-97: ssh:// workspace URI + system-ssh auth](decisions/architecture.md#d-97-ssh-workspace-uri--system-ssh-auth) — _architecture_ +- [D-98: Remote-tool contract + connect preflight](decisions/architecture.md#d-98-remote-tool-contract--connect-preflight) — _architecture_ +- [D-99: Remote session identity keyed on (host, workspace)](decisions/architecture.md#d-99-remote-session-identity-keyed-on-host-workspace) — _architecture_ ## Open questions @@ -156,7 +160,6 @@ You might also want, project-permitting: - [Q-17: Icon set growth](questions/process.md#q-17-icon-set-growth) — _process_ - [Q-18: Theme hot-reload in release builds](questions/process.md#q-18-theme-hot-reload-in-release-builds) — _process_ - [Q-20: Kernel DB service — namespaced SQL access?](questions/process.md#q-20-kernel-db-service--namespaced-sql-access) — _process_ -- [Q-23: SSH-remote development — run clide against a remote workspace](questions/architecture.md#q-23-ssh-remote-development--run-clide-against-a-remote-workspace) — _architecture_ - [Q-25: Body text face — mono everywhere vs Josefin Sans UI + mono code](questions/architecture.md#q-25-body-text-face--mono-everywhere-vs-josefin-sans-ui--mono-code) — _architecture_ - [Q-26: Small screen layout (< 1000px)](questions/architecture.md#q-26-small-screen-layout--1000px) — _architecture_ - [Q-27: Two-editor split](questions/architecture.md#q-27-two-editor-split) — _architecture_ @@ -189,6 +192,7 @@ You might also want, project-permitting: - [Q-19: (withdrawn)](questions/process.md#q-19-withdrawn) — _process_ - [Q-21: Pql absorbs planning vs keeps separate](questions/architecture.md#q-21-pql-absorbs-planning-vs-keeps-separate) — _architecture_ - [Q-22: Ticket persistence strategy](questions/architecture.md#q-22-ticket-persistence-strategy) — _architecture_ +- [Q-23: SSH-remote development — run clide against a remote workspace](questions/architecture.md#q-23-ssh-remote-development--run-clide-against-a-remote-workspace) — _architecture_ - [Q-28: Terminal strip scope — shell only or logs/errors/tests](questions/architecture.md#q-28-terminal-strip-scope--shell-only-or-logserrorstests) — _architecture_ - [Q-32: MCP tool surface — minimum slash-ide or extended clide tools?](questions/architecture.md#q-32-mcp-tool-surface--minimum-slash-ide-or-extended-clide-tools) — _architecture_ - [Q-33: MCP transport — SSE, WebSocket, stdio, or all?](questions/architecture.md#q-33-mcp-transport--sse-websocket-stdio-or-all) — _architecture_