A pql rebuild reset planning state to the committed changelog baseline (max T-207), dropping the T-208–T-225 ticket tree sourced from self-analysis.md. Re-created it deterministically (same order → same IDs) and ran `pql plan export` so the changelog now carries the full tree: - T-208 initiative "Give Claude hands" - epics T-209 (PATH delivery), T-214 (bootstrap), T-218 (observability), T-222 (events) + tasks T-224/T-225, with blockers and decision refs. Committing the regenerated changelog makes the work durable: the next `pql plan rebuild` replays it instead of resetting to T-207. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
69 KiB
SQL
82 lines
69 KiB
SQL
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-194', 'bug', 'T-132', 'Markdown reader 404s on absolute paths handed to files.read', 'The sidebar markdown reader (and any files.read caller) was handed an absolute path (e.g. the Claude Config tab publishing a skill''s absolute SKILL.md manifestPath). resolveUnderRoot joined the absolute path onto the workspace root (/repo + /repo/x = /repo/repo/x), which does not exist, so the reader showed ''file not found'' for a file that exists. Fix: resolveUnderRoot normalizes an absolute input as-is and relies on the existing containment check (so absolute-under-root resolves, absolute-outside still throws PathOutsideRoot). Found live opening .claude/skills/frame0-wireframe/SKILL.md.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 06:49:25', '2026-06-01 06:49:47', NULL, '01a3a891c9786f7afb7a1b95e8ab0b2f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-195', 'story', 'T-132', 'Reader can''t open user-scope Claude config files (~/.claude skills/agents/commands)', 'Clicking a user-scope skill in the Claude Config tab (e.g. /home/<user>/.claude/skills/peon-ping-toggle/SKILL.md) shows ''path outside workspace''. The markdown reader loads via files.read, which is workspace-confined for security (T-102); but user-scope Claude config files legitimately live under ~/.claude, outside the repo. Per D-76, Claude''s config surface (skills/agents/commands under ~/.claude AND the repo .claude) is clide-managed and safe to surface. Proposed: a scoped trusted-read — a new IPC verb (e.g. claude.read-config-file) that permits reads ONLY under the resolved Claude config roots (~/.claude + <repo>/.claude), still rejecting arbitrary paths. Route the Config tab''s skill/agent/command markdown clicks through it, or have the markdown reader fall back to it when the path is under a Claude config root but outside the workspace. Alternative: the Config tab already reads SKILL.md during scan (claude_config.dart) and could publish content directly via a load-by-content channel rather than a path (the reader currently only reads by path). Repo-scope skills already work after T-194; this covers the user-scope half.', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 06:56:05', '2026-06-01 07:08:16', NULL, '7b46d0491c01bcc056c430a7af48f734', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-197', 'bug', NULL, 'Editor pane never reveals on editor.open (edit button, file-tree open)', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 08:58:19', '2026-06-01 10:45:14', NULL, '05ae54e31d0c9befdbfef2fe096737c0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-196', 'bug', 'T-186', 'Decision reader: first click switches tab but doesn''t load (lost on tab mount)', 'Clicking a D-record activates the decisions.detail context-panel tab but does not load the record; only the SECOND click (any record) works. Root cause: DecisionDetailView subscribes to the ''selection'' MessageBus channel in didChangeDependencies, which runs only AFTER the extension''s activateTab mounts the view. The bus is broadcast (no replay), so the ''selection'' message that triggered the reveal is already gone — the view subscribes too late and misses it. The markdown extension already solved this: activateTab, then addPostFrameCallback -> re-publish on a separate ''load'' channel the viewer subscribes to, so the load fires after the viewer mounts+subscribes. Fix: mirror that — DecisionDetailView subscribes to ''load''; DecisionsExtension forwards ''selection'' -> post-frame ''load'' after activateTab.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 08:58:10', '2026-06-01 10:45:14', NULL, 'c87a8726edd727831291bd1055e39ae1', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-198', 'task', 'T-186', 'Reader pin UX: pin icon, toggle, left-toggle/right-navigate', 'Polish on the reader action bar (follows T-190). (1) push-pin glyph instead of the chain/link icon; (2) the pin button TOGGLES pinned state (tap to pin current, tap again to unpin) rather than set/replace; (3) layout split — pin/unpin toggle on the LEFT, jump-to-pin moved into the navigator (back/forward) on the right. Added ReaderNav.togglePin(), PhosphorIcons.pushPin (0xe3e2) + arrowUUpLeft (0xe08a), and an active state on the action button.', 'done', 'low', NULL, NULL, NULL, '2026-06-01 11:11:32', '2026-06-01 11:31:39', NULL, 'd2392d2e56ffc552c8f4f8993350d939', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-199', 'task', 'T-186', 'Tickets detail: adopt retained ReaderNav (back/forward + pin) + static tab', 'Bring the tickets detail reader in line with markdown/decisions (T-196/D-81). Adopt the retained ReaderNav(''builtin.tickets'', dataKey ''id''): the nav records selections + emits ''load''; the controller loads on ''load'' (not ''selection''); the view grabs nav.current on mount and wraps in ClidePaneChrome with a ReaderActionBar (pin toggle left; back/forward + jump-to-pin right; NO edit pencil since tickets are pql records, not files). Also drop the per-click uncontribute/contribute churn in TicketsExtension (the T-188 anti-pattern) — use the static tickets.detail tab + reveal on selection. TicketDetailController loses its panels dependency (reveal moves to the extension).', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 11:38:32', '2026-06-01 12:35:04', NULL, '82fe1aefd26d3f53872cc6983d1ae52d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-200', 'bug', NULL, 'Sidebar icon rail overflows (RenderFlex) when tab count exceeds width', 'Adding the Search sidebar tab (T-52) pushed the bottom icon rail past its width: ''A RenderFlex overflowed by 54 pixels'' from clide_icon_rail.dart — a fixed Row(center, max) with one _RailButton per tab. Fix: ClideIconRail centers when the icons fit and scrolls horizontally when they don''t (LayoutBuilder + SingleChildScrollView + ConstrainedBox(minWidth: maxWidth)). Robust to any tab count. (Related: T-201 will reduce tab count by merging the pql + find-in-files search surfaces.)', 'done', 'high', NULL, NULL, NULL, '2026-06-01 14:04:00', '2026-06-01 14:04:24', NULL, '59e5ac635342f5994afcc76c0fb3cd1e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-201', 'task', 'T-186', 'Merge pql search into the find-in-files Search tab (one search surface)', 'The pql sidebar panel (ranked text search + PQL DSL query + synced markdown-file listing) and the find-in-files Search tab (content grep) are duplicate search surfaces. Consolidate into ONE Search tab with a mode switch (option B): Find (content grep, existing), Vault (pql ranked search), Query (PQL DSL), Markdown (synced md-file listing, keeps focus-highlight + live refresh). Remove the builtin.pql SIDEBAR tab; keep the pql extension''s Backlinks context-panel tab + PqlController. Implementation: SearchPanelView gains a 4-mode switcher and holds both FindInFilesController + PqlController; extract the pql body/result rows into a reusable widget (they''re private in pql_panel_view.dart today); move the md-listing focus-sync + files.changed refresh into the merged panel; delete PqlPanelView. Test/coverage note: pql_panel_view + pql_controller are currently untested (not in the coverage denominator); folding them into the tested Search panel pulls them in, so the merge must cover the Vault/Query/Markdown modes too (same dynamic that bit T-199). Also reduces sidebar tab count (helps the rail, T-200). No D-79 conflict: grep vs ranked-search remain distinct backends; this is UI consolidation only.', 'done', 'medium', NULL, NULL, NULL, '2026-06-01 14:25:59', '2026-06-01 14:45:39', NULL, '4101a253d4893d6ea9b994332645dfdb', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-197', 'bug', NULL, 'Editor pane never reveals on editor.open (edit button, file-tree open)', 'Opening a file in the editor (the reader''s edit pencil, a non-.md file-tree click, a decision''s edit) calls editor.open and the daemon opens the buffer, but the editor pane never appears in the workspace slot above the Claude pane. Root cause: EditorExtension contributes a workspace tab (editor.active, priority 80) but has no activate() that reveals/activates it. editor.opened is emitted on the DaemonBus, but the EditorController that handles it only exists once EditorView is mounted — and nothing ever activates the editor tab to mount it. Fix: add EditorExtension.activate() subscribing to the editor.opened / editor.active-changed DaemonEvents and calling panels.activateTab(Slots.workspace, ''editor.active''). EditorView.hydrate() already pulls the active buffer on mount, so reveal-then-hydrate avoids any publish/subscribe race.
|
|
|
|
Reopened fix (2026-06-01): the original fix called panels.activateTab(Slots.workspace, ''editor.active''), but _WorkspaceSlot renders its editor split off arrangement.editorOpen, NOT the active tab — so the editor never appeared. Real fix: EditorExtension.activate now calls arrangement.openEditor() on editor.opened / active-changed(non-null) and closeEditor() on active-changed(null). Test asserts arrangement.editorOpen.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 08:58:19', '2026-06-01 16:36:38', NULL, '01039a9e83c3112ca93e33c228763b50', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-202', 'bug', NULL, 'rate_limit_event with numeric resetsAt crashes the Claude session', 'stream_json_session._statusFromEvent cast info[''resetsAt''] as String?, but claude sends resetsAt as a unix-epoch NUMBER → ''int is not a subtype of String?'' unhandled in _onLine, killing the session''s line parsing. Fix: accept num (epoch seconds → DateTime.fromMillisecondsSinceEpoch) or String (ISO). Surfaced in a live session log.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 16:48:31', '2026-06-01 16:48:56', NULL, '589320cb1ad6a97a3d91c090ce4f2112', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-203', 'bug', 'T-197', 'Editor split throws semantics + SelectableRegion exceptions on open', 'Opening the editor split (now that T-197 works) flooded two exceptions. (1) The _EditorDragHandle Semantics had slider:true + value + onIncrease/onDecrease but no increasedValue/decreasedValue → ''SemanticsNode with action increase needs both value and increasedValue'' on every flush; added increasedValue/decreasedValue. (2) Reparenting the Claude pane (direct child → Column/Expanded) when the split opens disposed its ClideSelectionArea/SelectableRegion subtree mid selection-update microtask → ''selectable not in this registrar'' + ''renderObject of inactive element''; fixed with a stable GlobalKey on the workspace primary so Flutter moves the element instead of disposing it.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 16:48:31', '2026-06-01 16:48:56', NULL, '77c284e1d35358783bda17955abba6b8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'story', 'T-65', 'Key-sequence resolution in the keymap layer (Vim motions, counts)', 'The resolver is single-chord: KeyChord.fromKeyEvent -> Keymap.resolve fires and forgets one keystroke. Real Vim needs multi-key sequences (dd, gg, dw, ciw, yy) and count prefixes (5j, 3dd). Add a pending-sequence model to KeymapService: accumulate chords into a buffer, match against sequence bindings by prefix, fire on full match, reset on no-prefix-match or timeout. Add a KeySequence type (ordered list of KeyChord) alongside KeyChord. Extend the YAML schema to express sequences distinctly from alias-lists: keys:[d,d] today means ''either d or d'' (alias) — need a ''sequence:'' key or a chord-string notation (e.g. ''d d'' space-separated) for ordered sequences. Add count-prefix capture so a leading digit run is parsed as a repeat count passed to the intent. Keep single-chord resolution unchanged (fast path). This also unblocks JetBrains shift+shift (T-66) via a double-tap special-case. Decision-worthy: the sequence/notation choice may warrant a D-record.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-01 18:48:27', '2026-06-01 18:48:27', NULL, '50a4ef00ac4ea54e4d83d0991fc9905b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-206', 'story', 'T-65', 'Modal editor: Vim motion & edit intents', 'The editor (lib/builtin/editor/src/editor_view.dart) is a raw Flutter EditableText that only handles Cmd/Ctrl+S; in normal mode, letter keys must NOT insert text and must instead drive motions/edits. Add typed editor Intents + Action handlers wired into the editor that operate on the SyntaxTextController/TextEditingValue: cursor motions (cursorLeft/Down/Up/Right for h/j/k/l), word motions (wordForward/wordBackward/wordEnd for w/b/e), line motions (lineStart/lineEnd/firstNonBlank for 0/$/^), document motions (documentStart/End for gg/G), edits (deleteChar x, deleteLine dd, deleteWord dw, changeLine cc, changeWord cw, yankLine yy, paste p/P, openLineBelow/Above o/O), and insert-entry (enterInsert i/a/I/A/o). Motions+edits must compose with the count prefix (T-205) and operator+motion (dw, cw). The editor must read the active Vim mode (T-206 service) to decide whether a bare key inserts or commands — key interception must win over EditableText''s text input in normal mode. Acceptance: each intent has an Action that mutates the buffer correctly; regression tests cover j (cursor down), x (delete char), dd (delete line), i (enter insert).', 'backlog', 'high', NULL, NULL, NULL, '2026-06-01 18:48:40', '2026-06-01 18:48:40', NULL, '752546ab98b527cd6abcb1232f578aca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'story', 'T-65', 'Vim mode service + status-bar mode indicator', 'A small mode-tracking service (ChangeNotifier) that owns the current Vim mode (normal/insert/visual), publishes vim.normal / vim.insert / vim.visual scope flags via KeymapService.setScopeFlag, and exposes the mode to the editor (T-206) and a status-bar indicator (-- NORMAL -- / -- INSERT -- / -- VISUAL --). Handles transitions: Esc -> normal (from any mode); i/a/o/I/A -> insert (from normal); v -> visual (from normal); colon -> command-line indicator (stretch). Resets to normal on editor focus by default (configurable). The when-clause grammar already supports dotted flags (vim.normal && editor.focused), confirmed in when_clause.dart. Service is a pure ChangeNotifier with no deps; fits in lib/kernel/src/keymap/ or as an editor builtin piece. Acceptance: setPreset(''vim'') + this service produce correct mode transitions; a regression test asserts i->insert and Esc->normal flip the scope flags; the status bar reflects mode.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-01 18:48:47', '2026-06-01 18:48:47', NULL, '337aed65495c6d858c5ce205b54d5b06', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', 'Ship a Vim-compatible keybinding preset with modal editing support (normal/insert/visual modes). Maps Vim motions and commands to clide editor and navigation actions. Users select it in settings.
|
|
|
|
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place; modal Vim presets are more involved than the VS Code preset (T-64) because modes need to be expressed as scope flags (`vim.normal`, `vim.insert`, `vim.visual`) that the when-clause grammar can branch on. Implementation work:
|
|
|
|
1. Author `assets/keymaps/vim.yaml` using the typed Intents + `command:<id>` bindings.
|
|
2. Add a small mode-tracking service that publishes `vim.<mode>` scope flags via `KeymapService.setScopeFlag`.
|
|
3. Bind `Esc` to mode-reset → normal; `i` (when `vim.normal`) → enter insert; etc.
|
|
|
|
**Acceptance:**
|
|
1. `assets/keymaps/vim.yaml` ships covering the documented Vim default keybindings for editor / navigation / panes.
|
|
2. `KeymapService.setPreset("vim")` + the mode-tracking service together produce correct mode transitions.
|
|
3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert).
|
|
|
|
Refinement (2026-06-01): scoped Vim-first for a Vim-power-user demo this weekend. ''Author a YAML file'' was wrong — T-117 shipped single-chord resolution + scope flags + when-clauses only. Decomposed into children: T-204 (fix dead default preset — live bug), T-205 (key-sequence + count resolution), T-206 (modal editor motion/edit intents), T-207 (Vim mode service + status indicator). T-65 itself becomes assets/keymaps/vim.yaml + regression tests once children land. T-64/T-66 deferred (single-chord, easy; T-205 hands JetBrains shift+shift later). Foundation is shared, not Vim-only.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-01 18:49:02', NULL, '8a7f2eee186a7824893e7f2d22604156', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-204', 'task', 'T-65', 'Fix dead default keymap: focus.next/previous undefined drops whole preset', 'The keymap loader catches FormatException from preset parsing and silently sets _preset=null (keymap_service.dart load()). default.yaml binds tab->focus.next and shift+tab->focus.previous, but neither id exists in builtinIntents (only focus.nextPanel/previousPanel). parseIntentId returns null -> KeymapLayer.fromYaml throws -> the ENTIRE default preset is dropped at boot. So palette (ctrl+shift+p), quick-open (ctrl+p), find-in-files, and text-scale bindings are all dead at runtime. Uncaught because every keymap_service_test injects a synthetic bundle; the real asset is never parsed. Fix: add focus.next->NextFocusIntent and focus.previous->PreviousFocusIntent (Flutter-provided, like activate/dismiss) to builtinIntents so Tab does correct widget focus traversal; add a test that loads the REAL assets/keymaps/default.yaml and asserts it parses with the expected binding count, as a regression guard for every shipped preset.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-01 18:48:19', '2026-06-01 18:49:17', NULL, '01fc224dd1bef50e6c42340ec3a3d81f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'story', 'T-65', 'Vim mode service + status-bar mode indicator', 'A small mode-tracking service (ChangeNotifier) that owns the current Vim mode (normal/insert/visual), publishes vim.normal / vim.insert / vim.visual scope flags via KeymapService.setScopeFlag, and exposes the mode to the editor (T-206) and a status-bar indicator (-- NORMAL -- / -- INSERT -- / -- VISUAL --). Handles transitions: Esc -> normal (from any mode); i/a/o/I/A -> insert (from normal); v -> visual (from normal); colon -> command-line indicator (stretch). Resets to normal on editor focus by default (configurable). The when-clause grammar already supports dotted flags (vim.normal && editor.focused), confirmed in when_clause.dart. Service is a pure ChangeNotifier with no deps; fits in lib/kernel/src/keymap/ or as an editor builtin piece. Acceptance: setPreset(''vim'') + this service produce correct mode transitions; a regression test asserts i->insert and Esc->normal flip the scope flags; the status bar reflects mode.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-01 18:48:47', '2026-06-01 18:51:08', NULL, '387db5a29a081da46a96dcb8672ccb79', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-204', 'task', 'T-65', 'Fix dead default keymap: focus.next/previous undefined drops whole preset', 'The keymap loader catches FormatException from preset parsing and silently sets _preset=null (keymap_service.dart load()). default.yaml binds tab->focus.next and shift+tab->focus.previous, but neither id exists in builtinIntents (only focus.nextPanel/previousPanel). parseIntentId returns null -> KeymapLayer.fromYaml throws -> the ENTIRE default preset is dropped at boot. So palette (ctrl+shift+p), quick-open (ctrl+p), find-in-files, and text-scale bindings are all dead at runtime. Uncaught because every keymap_service_test injects a synthetic bundle; the real asset is never parsed. Fix: add focus.next->NextFocusIntent and focus.previous->PreviousFocusIntent (Flutter-provided, like activate/dismiss) to builtinIntents so Tab does correct widget focus traversal; add a test that loads the REAL assets/keymaps/default.yaml and asserts it parses with the expected binding count, as a regression guard for every shipped preset.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:19', '2026-06-01 18:51:08', NULL, 'b335e35e5d0f4c3cb267d1b9573c1434', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-207', 'story', 'T-65', 'Vim mode service + status-bar mode indicator', 'A small mode-tracking service (ChangeNotifier) that owns the current Vim mode (normal/insert/visual), publishes vim.normal / vim.insert / vim.visual scope flags via KeymapService.setScopeFlag, and exposes the mode to the editor (T-206) and a status-bar indicator (-- NORMAL -- / -- INSERT -- / -- VISUAL --). Handles transitions: Esc -> normal (from any mode); i/a/o/I/A -> insert (from normal); v -> visual (from normal); colon -> command-line indicator (stretch). Resets to normal on editor focus by default (configurable). The when-clause grammar already supports dotted flags (vim.normal && editor.focused), confirmed in when_clause.dart. Service is a pure ChangeNotifier with no deps; fits in lib/kernel/src/keymap/ or as an editor builtin piece. Acceptance: setPreset(''vim'') + this service produce correct mode transitions; a regression test asserts i->insert and Esc->normal flip the scope flags; the status bar reflects mode.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:47', '2026-06-01 18:59:19', NULL, 'bb6b26be440d7850e55bf9ab138263bd', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'story', 'T-65', 'Key-sequence resolution in the keymap layer (Vim motions, counts)', 'The resolver is single-chord: KeyChord.fromKeyEvent -> Keymap.resolve fires and forgets one keystroke. Real Vim needs multi-key sequences (dd, gg, dw, ciw, yy) and count prefixes (5j, 3dd). Add a pending-sequence model to KeymapService: accumulate chords into a buffer, match against sequence bindings by prefix, fire on full match, reset on no-prefix-match or timeout. Add a KeySequence type (ordered list of KeyChord) alongside KeyChord. Extend the YAML schema to express sequences distinctly from alias-lists: keys:[d,d] today means ''either d or d'' (alias) — need a ''sequence:'' key or a chord-string notation (e.g. ''d d'' space-separated) for ordered sequences. Add count-prefix capture so a leading digit run is parsed as a repeat count passed to the intent. Keep single-chord resolution unchanged (fast path). This also unblocks JetBrains shift+shift (T-66) via a double-tap special-case. Decision-worthy: the sequence/notation choice may warrant a D-record.', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-01 18:48:27', '2026-06-01 19:07:16', NULL, '63396c8b4d6ae568bde2b7465001c3ad', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-205', 'story', 'T-65', 'Key-sequence resolution in the keymap layer (Vim motions, counts)', 'The resolver is single-chord: KeyChord.fromKeyEvent -> Keymap.resolve fires and forgets one keystroke. Real Vim needs multi-key sequences (dd, gg, dw, ciw, yy) and count prefixes (5j, 3dd). Add a pending-sequence model to KeymapService: accumulate chords into a buffer, match against sequence bindings by prefix, fire on full match, reset on no-prefix-match or timeout. Add a KeySequence type (ordered list of KeyChord) alongside KeyChord. Extend the YAML schema to express sequences distinctly from alias-lists: keys:[d,d] today means ''either d or d'' (alias) — need a ''sequence:'' key or a chord-string notation (e.g. ''d d'' space-separated) for ordered sequences. Add count-prefix capture so a leading digit run is parsed as a repeat count passed to the intent. Keep single-chord resolution unchanged (fast path). This also unblocks JetBrains shift+shift (T-66) via a double-tap special-case. Decision-worthy: the sequence/notation choice may warrant a D-record.', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:27', '2026-06-01 19:12:01', NULL, '7cc1e3c9a5249e5d352b0107b1936327', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-206', 'story', 'T-65', 'Modal editor: Vim motion & edit intents', 'The editor (lib/builtin/editor/src/editor_view.dart) is a raw Flutter EditableText that only handles Cmd/Ctrl+S; in normal mode, letter keys must NOT insert text and must instead drive motions/edits. Add typed editor Intents + Action handlers wired into the editor that operate on the SyntaxTextController/TextEditingValue: cursor motions (cursorLeft/Down/Up/Right for h/j/k/l), word motions (wordForward/wordBackward/wordEnd for w/b/e), line motions (lineStart/lineEnd/firstNonBlank for 0/$/^), document motions (documentStart/End for gg/G), edits (deleteChar x, deleteLine dd, deleteWord dw, changeLine cc, changeWord cw, yankLine yy, paste p/P, openLineBelow/Above o/O), and insert-entry (enterInsert i/a/I/A/o). Motions+edits must compose with the count prefix (T-205) and operator+motion (dw, cw). The editor must read the active Vim mode (T-206 service) to decide whether a bare key inserts or commands — key interception must win over EditableText''s text input in normal mode. Acceptance: each intent has an Action that mutates the buffer correctly; regression tests cover j (cursor down), x (delete char), dd (delete line), i (enter insert).', 'in_progress', 'high', NULL, NULL, NULL, '2026-06-01 18:48:40', '2026-06-01 19:16:58', NULL, '4627c4a8eb9257df10d0b3e82c7c1cc6', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-206', 'story', 'T-65', 'Modal editor: Vim motion & edit intents', 'The editor (lib/builtin/editor/src/editor_view.dart) is a raw Flutter EditableText that only handles Cmd/Ctrl+S; in normal mode, letter keys must NOT insert text and must instead drive motions/edits. Add typed editor Intents + Action handlers wired into the editor that operate on the SyntaxTextController/TextEditingValue: cursor motions (cursorLeft/Down/Up/Right for h/j/k/l), word motions (wordForward/wordBackward/wordEnd for w/b/e), line motions (lineStart/lineEnd/firstNonBlank for 0/$/^), document motions (documentStart/End for gg/G), edits (deleteChar x, deleteLine dd, deleteWord dw, changeLine cc, changeWord cw, yankLine yy, paste p/P, openLineBelow/Above o/O), and insert-entry (enterInsert i/a/I/A/o). Motions+edits must compose with the count prefix (T-205) and operator+motion (dw, cw). The editor must read the active Vim mode (T-206 service) to decide whether a bare key inserts or commands — key interception must win over EditableText''s text input in normal mode. Acceptance: each intent has an Action that mutates the buffer correctly; regression tests cover j (cursor down), x (delete char), dd (delete line), i (enter insert).', 'done', 'high', NULL, NULL, NULL, '2026-06-01 18:48:40', '2026-06-01 19:27:18', NULL, '2810354fde44fa08199b38acb9c017a4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-65', 'story', NULL, 'Vim keybinding preset', 'Ship a Vim-compatible keybinding preset with modal editing support (normal/insert/visual modes). Maps Vim motions and commands to clide editor and navigation actions. Users select it in settings.
|
|
|
|
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place; modal Vim presets are more involved than the VS Code preset (T-64) because modes need to be expressed as scope flags (`vim.normal`, `vim.insert`, `vim.visual`) that the when-clause grammar can branch on. Implementation work:
|
|
|
|
1. Author `assets/keymaps/vim.yaml` using the typed Intents + `command:<id>` bindings.
|
|
2. Add a small mode-tracking service that publishes `vim.<mode>` scope flags via `KeymapService.setScopeFlag`.
|
|
3. Bind `Esc` to mode-reset → normal; `i` (when `vim.normal`) → enter insert; etc.
|
|
|
|
**Acceptance:**
|
|
1. `assets/keymaps/vim.yaml` ships covering the documented Vim default keybindings for editor / navigation / panes.
|
|
2. `KeymapService.setPreset("vim")` + the mode-tracking service together produce correct mode transitions.
|
|
3. A regression test exercises a representative motion (`j` → cursor down) and a mode change (`i` → insert).
|
|
|
|
Refinement (2026-06-01): scoped Vim-first for a Vim-power-user demo this weekend. ''Author a YAML file'' was wrong — T-117 shipped single-chord resolution + scope flags + when-clauses only. Decomposed into children: T-204 (fix dead default preset — live bug), T-205 (key-sequence + count resolution), T-206 (modal editor motion/edit intents), T-207 (Vim mode service + status indicator). T-65 itself becomes assets/keymaps/vim.yaml + regression tests once children land. T-64/T-66 deferred (single-chord, easy; T-205 hands JetBrains shift+shift later). Foundation is shared, not Vim-only.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-01 19:29:15', NULL, 'efc76aa91cfc889968d5bda60f897afa', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-220', 'task', 'T-218', 'Reflect active editor buffer + focused file + selection into EditorRegistry', 'For UI-driven (non-IPC) opens and focus changes, mirror the active file + selection into EditorRegistry (lib/src/editor/registry.dart, read by editor_commands.dart:120/136) so `editor active` / `editor list` track what the user is actually looking at, not only IPC-opened buffers. Acceptance: focusing/opening a file in the GUI is reflected in `clide editor active`.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '024c50b98b31cb66da1a7361f068415d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-216', 'task', 'T-214', 'Inject an agent context note describing the CLI surface + parity contract', 'Give the hosted agent a short note (CLAUDE.md fragment / system reminder) stating it is inside clide and how to drive the editor / git / file tree / panes via `clide ...`, plus the D-6 parity contract. Acceptance: a hosted agent is told, without the user having to explain it.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '032f132431556cd5b9e478ce052abfff', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-208', 'initiative', NULL, 'Give Claude hands', 'Make a Claude agent able to actually WORK inside clide: reach the CLI, know clide is hosting it, and observe the same workspace the user sees. Source: self-analysis.md (2026-06-02), a live dogfood probe by a Claude (Opus 4.8) running against a live instance (socket + MCP SSE up, version 2.1.0).
|
|
|
|
Finding: the IPC/CLI foundation (D-1, D-6, D-56; built under T-99 incl. the C client T-126 and the `clide tail --events` stream T-129) works end-to-end at the socket level. But a fresh agent cannot REACH or OBSERVE it. Three blockers:
|
|
1. No `clide` binary on PATH, and no install path that delivers one (Epic A — blocker).
|
|
2. No bootstrap: nothing tells a fresh agent clide is running or where its socket is (Epic B).
|
|
3. The live UI panes / active file are not reflected in the CLI registries, so the D-6 parity premise (agent sees what the user sees) breaks (Epic C).
|
|
|
|
Plus: a governance decision on WHICH agent model is the dogfood target (hosted stream-json session vs external CLI driver vs both) and an MCP follow-up. The pleasant surprise per the probe: the hard part (a live, correct, single-process IPC contract with proper 0/1/3 exit codes) is done; what''s missing is the last mile that puts the tool in the agent''s hands. NOTE: the report says ''daemon'' but D-56 dissolved it — this is the in-process IPC server; the socket contract is unchanged.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '0c8826a602ec3d1f5fd9cb24d57cb4df', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-218', 'epic', 'T-208', 'Make the live workspace observable to the CLI (D-6 parity)', 'Gap 3 from self-analysis.md, verified in code. PaneRegistry (lib/src/panes/registry.dart) is written ONLY by the pane.spawn IPC path, so the built-in TabContribution panes — Claude (builtin/claude/src/extension.dart:42), Files (builtin/files/.../extension.dart:22), Editor (builtin/editor/.../extension.dart:52) — render in the kernel layout tree but never appear in `pane list` (read at pane_commands.dart:121). EditorRegistry is written by editor.open IPC (the Files tree uses it) but the active Claude pane / focused file / selection are not reflected. Result while the UI is in use: `clide pane list` -> {"panes":[]}, `clide editor active` -> {"active":null}. The ''agent sees what the user sees'' half of D-6 is missing.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '1abeb011393b813cc9810eab72c56d15', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-222', 'epic', 'T-208', 'Agent-friendly event observation (pull-based)', 'Gap 4 from self-analysis.md. `clide tail --events` (T-129) is a never-returning stream — awkward for a request/response agent loop; the agent must background it and poll a file or it misses events between polls. Provide a pull-based form that fits an agent loop natively.', 'backlog', 'medium', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '52bfa2fc2cd53bf08cd94573643cdf15', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-221', 'task', 'T-218', 'Add a clide status one-shot snapshot command', 'Gap 6. Add a `status` command to the dispatcher (currently unknown -> exit 3) returning a single snapshot: active pane, focused file + selection, git summary, layout — the natural first call an agent makes to orient. Acceptance: `clide status` returns a structured snapshot with exit 0. Depends on the pane/editor registries (C1/C2) for its pane and file fields.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '5ba39937538d85c2475c0e706445bd07', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-209', 'epic', 'T-208', 'Ship the clide CLI on PATH', 'Gap 1 (BLOCKER) from self-analysis.md. The C client native/clide-cli/clide.c (T-126, done) is the real CLI, but there is no supported way for `clide` to land on an agent''s PATH:
|
|
- `make clide-cli` (Makefile:284) is a non-default target; `build` (Makefile:169) does not depend on it, so the binary is never produced by a normal build (native/macos-arm64/clide had never been built).
|
|
- `make install` (Makefile:195) copies the GUI bundle and `ln -sf $(INSTALL_PREFIX)/clide/clide $(INSTALL_DIR)/clide` (Makefile:200) symlinks the FLUTTER GUI runner — not the C client — on BOTH macOS and Linux (on macOS that path doesn''t even resolve inside clide.app).
|
|
|
|
Net: `which clide` -> not found, `clide ping` -> exit 127. The CLI-first contract (D-1) has no delivery mechanism. Related to the broader installer epic T-46, but that is the GUI/runtime installer; this epic is specifically C-client-on-PATH delivery.', 'backlog', 'high', NULL, NULL, 'D-1', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '7b9a66f2daa4a692c7eb08cad919eb71', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-223', 'task', 'T-222', 'Add cursor-based pull events: clide events --since <cursor>', 'Return all events since a cursor plus a new cursor, alongside the existing stream (T-129). Acceptance: `clide events --since <c>` returns events + next-cursor with exit 0; repeated calls neither drop nor duplicate. Coordinate the cursor/persistence design with open questions Q-2 (event back-pressure) and Q-3 (event persistence + audit) — this dovetails with both.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '9c1c0c333670cd884794f5e8ccd521e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-212', 'task', 'T-209', 'In-app ''Install clide command in PATH'' affordance', 'VS Code-style: the GUI offers to install the `clide` shell command on PATH, for users who run the .app without `make install`. A command + command-palette entry that copies/symlinks the bundled C client to a PATH dir and reports success/failure. Acceptance: invoking it makes `clide` resolve on PATH from a fresh shell.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, '9cd1982fd89036a7fd9c8aa181c73cca', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-219', 'task', 'T-218', 'Register built-in panes into the PaneRegistry the CLI reads', 'Make the kernel layout''s TabContribution panes (Claude / Files / Editor and any viewer) visible to `pane list` / `pane focus` by registering or mirroring them into PaneRegistry (lib/src/panes/registry.dart, read by pane_commands.dart:121). Acceptance: with the GUI open, `clide pane list` enumerates the live panes the user sees, with stable ids and focus state.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'a81479a52653c4e644baa58121c17f01', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-213', 'task', 'T-209', 'Regression test: clideVersion matches pubspec version', 'Gap 6 version-drift guard. clideVersion in lib/src/build_info.g.dart (generated by gen-build-info) currently matches pubspec.yaml `version:` (both 2.1.0). Add a test asserting they stay equal so an agent keying off `clide version` is never misled.', 'backlog', 'low', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'af3253c0d2ad462a32f5ff31ba688d77', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-211', 'task', 'T-209', 'install delivers the C client to PATH on macOS and Linux', 'Fix `make install` so $(INSTALL_DIR)/clide (default ~/.local/bin/clide) IS the compiled C client, not the Flutter GUI binary. Today Makefile:200 symlinks the GUI bundle runner on both platforms. Acceptance: after `make install`, `which clide` resolves and `clide ping` returns {"pong":true} against a running instance on macOS and Linux. Blocked by ''Build the C client by default''.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'b9771b279b7249b8efe59eb702c5c05f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-217', 'task', 'T-214', 'Pre-seed a Bash(clide *) permission allow rule for hosted agents', 'So the hosted agent is not prompted on every `clide ...` call. Acceptance: clide-hosted sessions get a Bash(clide *) allow rule in their effective settings.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'bdcb7506945369bb6a84bd68583274b4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-224', 'task', 'T-208', 'Decide the dogfood agent model (governance Q/D record)', 'Gap 5 from self-analysis.md — foundational. Two distinct ''Claude inside clide'' stories with different gaps: (A) the clide-HOSTED stream-json session (D-77/D-78) — the user''s primary Claude pane, which itself needs `clide` on PATH to drive the surrounding IDE; (B) an EXTERNAL agent (e.g. a Claude Code harness, TERM_PROGRAM=zed as observed) driving via `clide ...` IPC, which clide cannot fully observe because plain file reads / `make test` / `git` bypass clide. Decide the intended model — hosted, external, or both — and record it under governance/ (claim via `pql decisions claim`). Gates how Epic B bootstraps and how Epic C frames observability.', 'backlog', 'high', NULL, NULL, 'D-77', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'c220bfde67438d19824eef10006a8c1a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-214', 'epic', 'T-208', 'Bootstrap the clide-hosted/launched agent', 'Gap 2 from self-analysis.md. Even with the binary installed, a fresh session has no signal it is hosted by clide. There IS an MCP discovery lock (~/.claude/ide/<pid>.lock pointing at workspace + SSE URL) but NOTHING for the Bash/CLI path: no CLIDE_SOCK/CLIDE_WORKSPACE env var, no injected context note, no pre-seeded Bash(clide *) allow rule. Discovery is the difference between a capability existing and getting used — the probing agent had to be told ''you''re inside clide'' and then hunt for the socket.', 'backlog', 'high', NULL, NULL, 'D-6', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'd95247263694e8a70fbeae3570b9579c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-210', 'task', 'T-209', 'Build the C clide client by default', 'Make the `clide-cli` target (Makefile:284) a dependency of `build` (Makefile:169) and `install` (Makefile:195) so the C client is always produced. Today it builds only via the explicit, non-default `make clide-cli`. Acceptance: a plain `make build` / `make install` produces the C client binary for the current OS.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'e2bead8a722cf0375adf79d22050b30e', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-225', 'task', 'T-208', 'MCP transport reachable by an external agent', 'Gap 6 from self-analysis.md. The MCP SSE server (T-130, D-68) is live but mcp__ide__* tools are not exposed to an external agent out of the box, and getDiagnostics/executeCode were noted as stubs. The CLI path is the priority (Epics A-C); MCP follows. Decide + wire the minimal reachable MCP surface, or explicitly defer with a note. Relates to open question Q-32 (minimum MCP tool surface).', 'backlog', 'low', NULL, NULL, 'D-68', '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'ebc32a0ac23296877246ac17f50d15b5', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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 (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-215', 'task', 'T-214', 'Export CLIDE_SOCK/CLIDE_WORKSPACE + ensure clide on the child PATH', 'When clide spawns/hosts an agent (the stream-json session per D-77, and any launched agent), inject CLIDE_SOCK (the per-workspace deterministic socket, D-70) and CLIDE_WORKSPACE into the child env, and guarantee `clide` is on the child PATH. Acceptance: a hosted agent''s shell has both vars and `clide ping` works with zero manual socket discovery. Blocked by Epic A delivery.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-02 18:13:52', '2026-06-02 18:13:52', NULL, 'fed9697da84ca9013659b9a882475c3a', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_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);
|