tickets: backlog relevance sweep (sub-T-100)

Swept the 19 backlog tickets with ids below T-100 for relevance against
the current codebase + governance.

Cancelled (obsolete):
- T-26 web-tree-sitter — contradicts the desktop-first guardrail
- T-60 workspace trust prompt — premature; third-party ext loading unshipped
- T-40 PRs tab — unscoped, no extension, data path undecided

Annotated (stale text / mixed state, work still valid):
- T-25, T-27 — app/ path prefix stale post-D-56
- T-8 — mixed Tier-6 completion (theme-picker done, settings-ui stub)
- T-55 — split: tmux persistence done, OS tray still a stub
- T-41 — likely superseded by canvas epic (T-317/D-91)
- T-67 — reframe from one-time audit to coverage ratchet
- T-81 — T-79/T-80 done, #21 obsolete, ~7 items remain

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 17:18:11 +02:00
co-authored by Claude Opus 4.8
parent 66d3d024ca
commit 3134a0ac52
7 changed files with 647 additions and 2 deletions
+147
View File
@@ -3233,3 +3233,150 @@ Disposition (2026-06-10): mostly already done before pickup.
- #22 (logger): DONE prior. lib/src/ipc/server.dart imports the kernel Logger, holds a ''final Logger log'', and logs via log.error/warn/info(''ipc'', ). No stderr.writeln/print anywhere in lib/src/ipc, lib/src/pty, or lib/src/daemon. Folded in by the D-56 daemon dissolution + PTY FFI pivot.
- #26 (cmd correlation): the only live remnant the catch-all ''dispatch threw'' log omitted the request cmd. Fixed: it now logs ''dispatch threw for "<cmd>"''. Internal logging only; no changelog.', NULL, '2026-06-10 14:58:32', '2026-06-10 14:58:32', '2026-06-10 14:58:32', NULL, '067666253769388e306dd99c4f976df9', 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 ('06FB0TNQM734YZ060Q63H40EYG', 'status', 'in_progress', 'done', NULL, '2026-06-10 14:58:43', '2026-06-10 14:58:43', '2026-06-10 14:58:43', NULL, '192064fbd5628b282512128ddc3d2688', 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 ('06FB0TNQM42M9RK4399B4F4WSG', 'status', 'backlog', 'ready', NULL, '2026-06-10 15:01:43', '2026-06-10 15:01:43', '2026-06-10 15:01:43', NULL, 'a002c3ce8b3efa35c18d9708ed37c9c3', 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 ('06FB0TNQM60QRRNEEWG84VWKXC', 'status', 'backlog', 'ready', NULL, '2026-06-10 15:01:52', '2026-06-10 15:01:52', '2026-06-10 15:01:52', NULL, 'dee252ba6fbb07189c947101968e1743', 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 ('06FB0TNQM42M9RK4399B4F4WSG', 'status', 'ready', 'in_progress', NULL, '2026-06-10 15:01:54', '2026-06-10 15:01:54', '2026-06-10 15:01:54', NULL, '1c3ed11e37579ea9a3a21e85e3ba110a', 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 ('06FB0TNQM60QRRNEEWG84VWKXC', 'status', 'ready', 'in_progress', NULL, '2026-06-10 15:02:14', '2026-06-10 15:02:14', '2026-06-10 15:02:14', NULL, 'c5844c06118422cb09538405b12fe478', 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 ('06FB0TNQM42M9RK4399B4F4WSG', 'description', 'Ship a VS Code-compatible keybinding preset that maps standard VS Code shortcuts to clide commands. Users select it in settings. Covers file navigation, editor actions, panel toggles, search, and terminal.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:<id>` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today.
**Acceptance:**
1. `assets/keymaps/vscode.yaml` ships covering the documented VS Code default keybindings.
2. `KeymapService.setPreset("vscode")` activates the preset and all asserted bindings resolve as expected.
3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, ).
4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p quick-open command, ctrl+shift+p palette).
**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).', 'Ship a VS Code-compatible keybinding preset that maps standard VS Code shortcuts to clide commands. Users select it in settings. Covers file navigation, editor actions, panel toggles, search, and terminal.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:<id>` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today.
**Acceptance:**
1. `assets/keymaps/vscode.yaml` ships covering the documented VS Code default keybindings.
2. `KeymapService.setPreset("vscode")` activates the preset and all asserted bindings resolve as expected.
3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, ).
4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p quick-open command, ctrl+shift+p palette).
**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).
Correction (2026-06-10): this ticket''s ''see Q-9'' reference is stale Q-9 is ''Lua runtime vendoring'', unrelated. The search-everywhere / double-tap-modifier gap is now tracked by T-341.', NULL, '2026-06-10 15:07:52', '2026-06-10 15:07:52', '2026-06-10 15:07:52', NULL, 'f5caba1a25d32651bbd7503c906633cf', 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 ('06FB0TNQM60QRRNEEWG84VWKXC', 'description', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:<id>` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.).
**Acceptance:**
1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings.
2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve.
3. A regression test exercises a representative subset (e.g. shift+shift quick-open command see Q-9 if the search-everywhere overlay needs its own intent).', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:<id>` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.).
**Acceptance:**
1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings.
2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve.
3. A regression test exercises a representative subset (e.g. shift+shift quick-open command see Q-9 if the search-everywhere overlay needs its own intent).
Correction (2026-06-10): ''see Q-9'' is stale (Q-9 is Lua runtime vendoring). The double-Shift ''Search Everywhere'' chord is NOT expressible by the current matcher (bare/double modifiers unsupported) tracked in T-341. This preset maps quick-open to Ctrl+Shift+N and the palette to Ctrl+Shift+A as the expressible IntelliJ equivalents.', NULL, '2026-06-10 15:07:52', '2026-06-10 15:07:52', '2026-06-10 15:07:52', NULL, '18bc4f20a98f7528f3a9a976e34d9377', 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 ('06FB0TNQM5YG22EV7BFTX5RTPR', 'description', 'Conditional import behind TreeSitterService: native impl uses dart:ffi to libtree-sitter.so, web impl uses dart:js_interop to web-tree-sitter (official emscripten build from tree-sitter org). Same grammar .wasm files on both platforms. Vendor web-tree-sitter .wasm + JS glue as Flutter web assets, pinned version, added to licenses.yaml.', 'Conditional import behind TreeSitterService: native impl uses dart:ffi to libtree-sitter.so, web impl uses dart:js_interop to web-tree-sitter (official emscripten build from tree-sitter org). Same grammar .wasm files on both platforms. Vendor web-tree-sitter .wasm + JS glue as Flutter web assets, pinned version, added to licenses.yaml.
Cancelled 2026-06-10 (backlog relevance sweep): contradicts the desktop-first guardrail (CLAUDE.md) - web is an explicit non-goal / happy-accident only. TreeSitterService is FFI-only and there is no shipped web product, so a web-tree-sitter dual-path is not wanted. Reopen only if web ever becomes a real target.', NULL, '2026-06-10 15:13:50', '2026-06-10 15:13:50', '2026-06-10 15:13:50', NULL, 'ad42a5b7d9d3cc5c5e0e1ed97b9858aa', 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 ('06FB0TNQM50X0DX8XTVZEA5GN8', 'description', 'Prompt before running extensions or loading project settings in untrusted repositories. Trust decision persisted per repo path. Untrusted mode disables third-party extensions and restricts IPC commands.', 'Prompt before running extensions or loading project settings in untrusted repositories. Trust decision persisted per repo path. Untrusted mode disables third-party extensions and restricts IPC commands.
Cancelled 2026-06-10 (relevance sweep): premature. Third-party (Lua) extension loading is not shipped - ExtensionScanner.discover is test-only and the Lua runtime is a Tier-6 skeleton. Nothing to trust-gate yet; revisit at Tier 6 when external extension loading lands (the trust surface will likely be Lua sandboxing per D-19, not a per-repo prompt).', NULL, '2026-06-10 15:13:52', '2026-06-10 15:13:52', '2026-06-10 15:13:52', NULL, 'c4e57efc161c7afbc5e8a3c3d08b8d74', 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 ('06FB0TNQM6RDM2EKZX132GPV7M', 'description', 'Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.', 'Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.
Cancelled 2026-06-10 (relevance sweep): spec''d in D-47 but unscoped, no extension exists, and the data path (git host API vs local metadata) is undecided. Closing to clear the backlog; file a fresh scoped story if a PRs surface is wanted.', NULL, '2026-06-10 15:13:52', '2026-06-10 15:13:52', '2026-06-10 15:13:52', NULL, '99b1fceb9134d0e6bead70da08a51710', 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 ('06FB0TNQM4W194B2421P2SF83R', 'description', 'BUILD.md at app/native/linux-x64/ has TODO checklist: build from pinned source SHA in CI, record SHA-256, cross-compile for macOS (aarch64, x86_64) and Windows (x86_64). Currently built on contributor machine.', 'BUILD.md at app/native/linux-x64/ has TODO checklist: build from pinned source SHA in CI, record SHA-256, cross-compile for macOS (aarch64, x86_64) and Windows (x86_64). Currently built on contributor machine.
Path fix (2026-06-10 sweep): ticket says app/native/linux-x64/ - the app/ prefix is stale (D-56 dissolved the two-package layout). Correct path is native/linux-x64/BUILD.md. Work remains valid: native/linux-x64/libtree-sitter.so is committed but there is still no CI build/cross-compile job.', NULL, '2026-06-10 15:13:54', '2026-06-10 15:13:54', '2026-06-10 15:13:54', NULL, '3ddd8faeeaabb70a137165ec736e8a07', 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 ('06FB0TNQM48MDE8ZZ82VWNY994', 'description', 'D-46 defines the boundary: content extensions (editor, claude, claude-control, markdown, diff, git-ui, pql, canvas, graph, decisions, tickets, todos, problems) move from app/lib/builtin/ to app/lib/extensions/. Incremental — one at a time, each behind a working build. Extension contract must support bundled Dart extension as a first-class category.', 'D-46 defines the boundary: content extensions (editor, claude, claude-control, markdown, diff, git-ui, pql, canvas, graph, decisions, tickets, todos, problems) move from app/lib/builtin/ to app/lib/extensions/. Incremental — one at a time, each behind a working build. Extension contract must support bundled Dart extension as a first-class category.
Path fix (2026-06-10 sweep): app/lib/builtin/ -> lib/builtin/ (app/ prefix stale per D-56). D-46 still confirmed/active. lib/extensions/ does not exist yet and the shipped extensions (editor, claude, markdown, diff, git-ui, pql, canvas, graph, decisions, tickets, todos, problems) are still under lib/builtin/. Migration unstarted, still valid.', NULL, '2026-06-10 15:13:56', '2026-06-10 15:13:56', '2026-06-10 15:13:56', NULL, '81d27a7cf777fc0290574d807938ea36', 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 ('06FB0TNQM6T6580D8ABDVTMNZW', 'description', 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype.
**Extension API (third-party Lua):**
- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc.
- Manifest schema, capability gating, sandboxed FS/IPC access.
- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15).
- Marketplace / distribution story is OUT OF SCOPE for Tier 6 local-install only.
**Settings UI (`builtin.settings-ui`):**
- Schema-driven settings panel reading from the kernel SettingsStore.
- Render strategy: form fields keyed off the schema each subsystem registers.
- Edits write back to `.clide/settings.yaml`.
**Theming UI (`builtin.theme-picker` extends):**
- Live preview of the four bundled themes (D-44).
- Custom theme: import YAML, validate against schema, register at runtime.
- Per-component override surface (long horizon).
**Distributable builds:**
- AppImage / Flatpak for Linux, .dmg for macOS see T-46.
- Self-update mechanism see T-47.
- License manifest auto-regen as part of the release build.
Big epic children land incrementally. Most concrete child tickets already exist; this is the umbrella.', 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype.
**Extension API (third-party Lua):**
- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc.
- Manifest schema, capability gating, sandboxed FS/IPC access.
- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15).
- Marketplace / distribution story is OUT OF SCOPE for Tier 6 local-install only.
**Settings UI (`builtin.settings-ui`):**
- Schema-driven settings panel reading from the kernel SettingsStore.
- Render strategy: form fields keyed off the schema each subsystem registers.
- Edits write back to `.clide/settings.yaml`.
**Theming UI (`builtin.theme-picker` extends):**
- Live preview of the four bundled themes (D-44).
- Custom theme: import YAML, validate against schema, register at runtime.
- Per-component override surface (long horizon).
**Distributable builds:**
- AppImage / Flatpak for Linux, .dmg for macOS see T-46.
- Self-update mechanism see T-47.
- License manifest auto-regen as part of the release build.
Big epic children land incrementally. Most concrete child tickets already exist; this is the umbrella.
Status note (2026-06-10 sweep): mixed completion. theme-picker is substantially implemented; settings-ui is a stub; the Lua runtime is skeleton-only (lib/lua/); distributable builds (T-46/T-47) remain deferred Tier-6 work. Epic stays open as the umbrella.', NULL, '2026-06-10 15:13:57', '2026-06-10 15:13:57', '2026-06-10 15:13:57', NULL, '15d4ca47570b23a19b16c22e40a8d73e', 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 ('06FB0TNQM66FTCTWHH9AQTNFKR', 'description', 'Minimize to system tray on Linux (AppIndicator) or Dock on macOS. Reopening from tray restores the window without cold boot. tmux sessions stay alive in background regardless.', 'Minimize to system tray on Linux (AppIndicator) or Dock on macOS. Reopening from tray restores the window without cold boot. tmux sessions stay alive in background regardless.
Scope split (2026-06-10 sweep): the session-persistence half is effectively done - tmux keeps Claude/terminal sessions alive across restart (D-41). The OS-tray/AppIndicator + dock half is a stub only (lib/kernel/src/tray.dart - TrayRegistry has no platform-channel wiring) and is Tier-6+. Remaining work = the tray integration.', NULL, '2026-06-10 15:13:59', '2026-06-10 15:13:59', '2026-06-10 15:13:59', NULL, '8a2142b2ae85644bdbe22627ce3bde77', 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 ('06FB0TNQM67JSC5RKS6M9182KG', 'description', 'Spec lists images as a right-panel section. No extension exists yet.', 'Spec lists images as a right-panel section. No extension exists yet.
Relevance note (2026-06-10 sweep): likely superseded. The image card + full-screen lightbox shipped (T-249/T-252) and the canvas epic (T-317, D-91) folds image display into the unified drawing-card renderer rather than a separate context-panel tab. Confirm whether a distinct images rail section is still wanted; otherwise close in favor of the canvas path.', NULL, '2026-06-10 15:14:01', '2026-06-10 15:14:01', '2026-06-10 15:14:01', NULL, 'faf96dd64f003093273f6a331c56bfa8', 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 ('06FB0TNQM7P6Q0DG4RG4CBHFJG', 'description', 'Audit all interactive widgets for Semantics coverage (labels, roles, states). Verify flutter test can locate and interact with every panel, button, and input via find.bySemanticsLabel. Run the existing a11y test suite and document gaps. Target: every user-facing action is testable without widget keys.', 'Audit all interactive widgets for Semantics coverage (labels, roles, states). Verify flutter test can locate and interact with every panel, button, and input via find.bySemanticsLabel. Run the existing a11y test suite and document gaps. Target: every user-facing action is testable without widget keys.
Reframe (2026-06-10 sweep): the original ''audit Semantics coverage'' framing is stale - test/a11y/ (semantic_coverage, contrast, keyboard_traversal, i18n) is now a mature per-PR gate per D-20. Re-scope to forward work: ratchet the semantic-coverage floor and deepen per-extension Semantics assertions, rather than a one-time review.', NULL, '2026-06-10 15:14:02', '2026-06-10 15:14:02', '2026-06-10 15:14:02', NULL, 'a83b56d7fee3b9a20bc0d78f6125532a', 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 ('06FB0TNQM79CNBXJ2S3CFQR7VM', 'description', 'Catch-all for the medium-priority items from the PTY/IPC error-handling audit (T-18, see docs/audits/pty-ipc-error-handling-2026-05-05.md) that didn`t earn dedicated tickets:
- **#17** `files.read` `readAsStringSync` is unguarded; UTF-8 errors / permissions / mid-read deletion become 500-style dispatch errors. Wrap in try/catch and emit a clean `IpcResponse.err`.
- **#19** `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed.
- **#20** Reader isolate treats every negative `read()` return that isn`t EINTR as EOF. Distinguish EBADF/EIO (real EOF) from transient EAGAIN (recoverable) and log the latter.
- **#21** `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd.
- **#25** `_gitError` in `lib/src/daemon/git_commands.dart` always reports `tool_error`; push rejections / merge conflicts should map to `IpcExitCode.conflict` when stderr matches known patterns.
- **#27** `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure.
- **#28** `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade.
- **#29** PATH resolution in `native_pty.dart` uses the first existing match without `X_OK` check; non-executable files shadow valid binaries further along PATH.
Land each as a small focused commit; ticket closes when all items above are merged.', 'Catch-all for the medium-priority items from the PTY/IPC error-handling audit (T-18, see docs/audits/pty-ipc-error-handling-2026-05-05.md) that didn`t earn dedicated tickets:
- **#17** `files.read` `readAsStringSync` is unguarded; UTF-8 errors / permissions / mid-read deletion become 500-style dispatch errors. Wrap in try/catch and emit a clean `IpcResponse.err`.
- **#19** `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed.
- **#20** Reader isolate treats every negative `read()` return that isn`t EINTR as EOF. Distinguish EBADF/EIO (real EOF) from transient EAGAIN (recoverable) and log the latter.
- **#21** `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd.
- **#25** `_gitError` in `lib/src/daemon/git_commands.dart` always reports `tool_error`; push rejections / merge conflicts should map to `IpcExitCode.conflict` when stderr matches known patterns.
- **#27** `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure.
- **#28** `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade.
- **#29** PATH resolution in `native_pty.dart` uses the first existing match without `X_OK` check; non-executable files shadow valid binaries further along PATH.
Land each as a small focused commit; ticket closes when all items above are merged.
Item status (2026-06-10 sweep): from the T-18 audit, #16 (git error kinds) landed via T-79 and #22 (logging) via T-80. #21 (scm_rights.dart bounds check) is OBSOLETE - fd-passing/recvmsg was removed, the file no longer exists; drop it. Spot-checked still-open: #17 files.read unguarded readAsStringSync (files_commands.dart), #28 IpcResponse.fromJson TypeError (envelope.dart), #29 PATH X_OK check (native_pty.dart). ~7 items remain.', NULL, '2026-06-10 15:14:04', '2026-06-10 15:14:04', '2026-06-10 15:14:04', NULL, '94ea0fc1d09ea5eb98acf8ff624ac984', 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 ('06FB0TNQM5YG22EV7BFTX5RTPR', 'status', 'backlog', 'cancelled', NULL, '2026-06-10 15:14:17', '2026-06-10 15:14:17', '2026-06-10 15:14:17', NULL, 'b459f350a8c1a86865579cde7f7b02b1', 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 ('06FB0TNQM50X0DX8XTVZEA5GN8', 'status', 'backlog', 'cancelled', NULL, '2026-06-10 15:14:17', '2026-06-10 15:14:17', '2026-06-10 15:14:17', NULL, '351f1eede59364f6bb95653147b98ee2', 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 ('06FB0TNQM6RDM2EKZX132GPV7M', 'status', 'backlog', 'cancelled', NULL, '2026-06-10 15:14:17', '2026-06-10 15:14:17', '2026-06-10 15:14:17', NULL, '992ba3fca9faf99417aaa1a7afa9e6be', 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 ('06FB0TNQM42M9RK4399B4F4WSG', 'status', 'in_progress', 'done', NULL, '2026-06-10 15:17:59', '2026-06-10 15:17:59', '2026-06-10 15:17:59', NULL, '61000cdb8bb01686c6001f57d16ae717', 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 ('06FB0TNQM60QRRNEEWG84VWKXC', 'status', 'in_progress', 'done', NULL, '2026-06-10 15:17:59', '2026-06-10 15:17:59', '2026-06-10 15:17:59', NULL, '81990604a08fbedc28075b95538625d6', 2) ON CONFLICT(hash) DO NOTHING;
+162
View File
@@ -2699,3 +2699,165 @@ Disposition (2026-06-10): mostly already done before pickup.
- #23 (errno constants): DONE prior. Magic numbers are centralized errno values in lib/src/ipc/errno_mapping.dart (PosixErrno: eintr=4, ebadf=9, epipe=32, ), signals in lib/src/pty/ffi/libc.dart (sighup=1, sigwinch=28). native_pty.dart uses PosixErrno.* and libc.* throughout; no inline magic numbers remain. The ticket''s lib/src/pty/session.dart never existed at that path.
- #22 (logger): DONE prior. lib/src/ipc/server.dart imports the kernel Logger, holds a ''final Logger log'', and logs via log.error/warn/info(''ipc'', ). No stderr.writeln/print anywhere in lib/src/ipc, lib/src/pty, or lib/src/daemon. Folded in by the D-56 daemon dissolution + PTY FFI pivot.
- #26 (cmd correlation): the only live remnant the catch-all ''dispatch threw'' log omitted the request cmd. Fixed: it now logs ''dispatch threw for "<cmd>"''. Internal logging only; no changelog.', 'done', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-06-10 14:58:43', NULL, 'd7ba06c0e311a1945f8ccead0b132570', 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 ('06FB0TNQM42M9RK4399B4F4WSG', 'story', NULL, 'VS Code keybinding preset', 'Ship a VS Code-compatible keybinding preset that maps standard VS Code shortcuts to clide commands. Users select it in settings. Covers file navigation, editor actions, panel toggles, search, and terminal.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:<id>` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today.
**Acceptance:**
1. `assets/keymaps/vscode.yaml` ships covering the documented VS Code default keybindings.
2. `KeymapService.setPreset("vscode")` activates the preset and all asserted bindings resolve as expected.
3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, ).
4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p quick-open command, ctrl+shift+p palette).
**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).', 'ready', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:01:43', NULL, 'b853e77093d133d712104ef3a900762a', 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 ('06FB0TNQM60QRRNEEWG84VWKXC', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:<id>` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.).
**Acceptance:**
1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings.
2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve.
3. A regression test exercises a representative subset (e.g. shift+shift quick-open command see Q-9 if the search-everywhere overlay needs its own intent).', 'ready', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:01:52', NULL, '21b160e1724fea9e39aec2884e50904b', 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 ('06FB0TNQM42M9RK4399B4F4WSG', 'story', NULL, 'VS Code keybinding preset', 'Ship a VS Code-compatible keybinding preset that maps standard VS Code shortcuts to clide commands. Users select it in settings. Covers file navigation, editor actions, panel toggles, search, and terminal.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:<id>` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today.
**Acceptance:**
1. `assets/keymaps/vscode.yaml` ships covering the documented VS Code default keybindings.
2. `KeymapService.setPreset("vscode")` activates the preset and all asserted bindings resolve as expected.
3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, ).
4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p quick-open command, ctrl+shift+p palette).
**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:01:54', NULL, 'e714ae4f4d0fc2f6a4f35e1c84b53ee9', 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 ('06FB0TNQM60QRRNEEWG84VWKXC', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:<id>` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.).
**Acceptance:**
1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings.
2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve.
3. A regression test exercises a representative subset (e.g. shift+shift quick-open command see Q-9 if the search-everywhere overlay needs its own intent).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:02:14', NULL, 'a08e16020c92d6e38b94158c42b0e877', 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 ('06FB44SKPKTHFMV6WD28GZYPXM', 'task', NULL, 'Support double-tap / bare-modifier chords (JetBrains Search Everywhere)', 'The KeyChord matcher can''t represent a bare or double-tapped modifier: KeyChord.fromKeyEvent returns null for bare modifier presses (key_chord.dart _isBareModifier), and KeyChord.parse requires a base key after the last ''+'', so ''shift+shift'' / ''shift shift'' both fail. Consequence: JetBrains ''Search Everywhere'' (double-Shift) and any double-tap-modifier shortcut can''t be bound.
Decide (design): is search-everywhere its own intent + overlay, or an alias of quick-open? Then extend the chord/sequence matcher to represent a bare-modifier ''key'' (and a double-tap as a sequence) so a preset can bind it.
Surfaced by T-64/T-66 (VS Code / JetBrains keymap presets). Those tickets'' ''see Q-9'' reference is STALE Q-9 is ''Lua runtime vendoring'', unrelated. The JetBrains preset currently maps quick-open to Ctrl+Shift+N (Go to File) and the palette to Ctrl+Shift+A (Find Action) as the expressible IntelliJ equivalents.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-10 15:07:43', '2026-06-10 15:07:43', NULL, 'e4a9db7e1d1759ebd6a8ad97ab4e9aca', 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 ('06FB0TNQM42M9RK4399B4F4WSG', 'story', NULL, 'VS Code keybinding preset', 'Ship a VS Code-compatible keybinding preset that maps standard VS Code shortcuts to clide commands. Users select it in settings. Covers file navigation, editor actions, panel toggles, search, and terminal.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:<id>` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today.
**Acceptance:**
1. `assets/keymaps/vscode.yaml` ships covering the documented VS Code default keybindings.
2. `KeymapService.setPreset("vscode")` activates the preset and all asserted bindings resolve as expected.
3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, ).
4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p quick-open command, ctrl+shift+p palette).
**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).
Correction (2026-06-10): this ticket''s ''see Q-9'' reference is stale Q-9 is ''Lua runtime vendoring'', unrelated. The search-everywhere / double-tap-modifier gap is now tracked by T-341.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:07:52', NULL, '4a85b3c69805401cba51e521e9ba7d5d', 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 ('06FB0TNQM60QRRNEEWG84VWKXC', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:<id>` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.).
**Acceptance:**
1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings.
2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve.
3. A regression test exercises a representative subset (e.g. shift+shift quick-open command see Q-9 if the search-everywhere overlay needs its own intent).
Correction (2026-06-10): ''see Q-9'' is stale (Q-9 is Lua runtime vendoring). The double-Shift ''Search Everywhere'' chord is NOT expressible by the current matcher (bare/double modifiers unsupported) tracked in T-341. This preset maps quick-open to Ctrl+Shift+N and the palette to Ctrl+Shift+A as the expressible IntelliJ equivalents.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:07:52', NULL, '067b925b777ecce5c0c25239193989f6', 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 ('06FB0TNQM5YG22EV7BFTX5RTPR', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'web-tree-sitter via JS interop for browser builds', 'Conditional import behind TreeSitterService: native impl uses dart:ffi to libtree-sitter.so, web impl uses dart:js_interop to web-tree-sitter (official emscripten build from tree-sitter org). Same grammar .wasm files on both platforms. Vendor web-tree-sitter .wasm + JS glue as Flutter web assets, pinned version, added to licenses.yaml.
Cancelled 2026-06-10 (backlog relevance sweep): contradicts the desktop-first guardrail (CLAUDE.md) - web is an explicit non-goal / happy-accident only. TreeSitterService is FFI-only and there is no shipped web product, so a web-tree-sitter dual-path is not wanted. Reopen only if web ever becomes a real target.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:33', '2026-06-10 15:13:50', NULL, 'dd1bb8fd534cfa37647528f4ed68da52', 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 ('06FB0TNQM50X0DX8XTVZEA5GN8', 'story', NULL, 'workspace trust prompt', 'Prompt before running extensions or loading project settings in untrusted repositories. Trust decision persisted per repo path. Untrusted mode disables third-party extensions and restricts IPC commands.
Cancelled 2026-06-10 (relevance sweep): premature. Third-party (Lua) extension loading is not shipped - ExtensionScanner.discover is test-only and the Lua runtime is a Tier-6 skeleton. Nothing to trust-gate yet; revisit at Tier 6 when external extension loading lands (the trust surface will likely be Lua sandboxing per D-19, not a per-repo prompt).', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-10 15:13:52', NULL, '0edf2435dec17eca4a3fd8a605cc2afe', 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 ('06FB0TNQM6RDM2EKZX132GPV7M', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'Add PRs tab to sidebar', 'Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.
Cancelled 2026-06-10 (relevance sweep): spec''d in D-47 but unscoped, no extension exists, and the data path (git host API vs local metadata) is undecided. Closing to clear the backlog; file a fresh scoped story if a PRs surface is wanted.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-06-10 15:13:52', NULL, '8fa9643461ef70f261fc895639d15811', 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 ('06FB0TNQM4W194B2421P2SF83R', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'CI reproducible build for libtree-sitter.so', 'BUILD.md at app/native/linux-x64/ has TODO checklist: build from pinned source SHA in CI, record SHA-256, cross-compile for macOS (aarch64, x86_64) and Windows (x86_64). Currently built on contributor machine.
Path fix (2026-06-10 sweep): ticket says app/native/linux-x64/ - the app/ prefix is stale (D-56 dissolved the two-package layout). Correct path is native/linux-x64/BUILD.md. Work remains valid: native/linux-x64/libtree-sitter.so is committed but there is still no CI build/cross-compile job.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:24', '2026-06-10 15:13:54', NULL, 'c52de8ef9eb80fae7cda60ffb23f8117', 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 ('06FB0TNQM48MDE8ZZ82VWNY994', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'migrate shipped extensions from builtin/ to extensions/ per D-46', 'D-46 defines the boundary: content extensions (editor, claude, claude-control, markdown, diff, git-ui, pql, canvas, graph, decisions, tickets, todos, problems) move from app/lib/builtin/ to app/lib/extensions/. Incremental — one at a time, each behind a working build. Extension contract must support bundled Dart extension as a first-class category.
Path fix (2026-06-10 sweep): app/lib/builtin/ -> lib/builtin/ (app/ prefix stale per D-56). D-46 still confirmed/active. lib/extensions/ does not exist yet and the shipped extensions (editor, claude, markdown, diff, git-ui, pql, canvas, graph, decisions, tickets, todos, problems) are still under lib/builtin/. Migration unstarted, still valid.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:45', '2026-06-10 15:13:56', NULL, '8b66e304cc2695c4850917dad1e53de1', 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 ('06FB0TNQM6T6580D8ABDVTMNZW', 'epic', NULL, 'Tier 6 — extension API, settings, theming, distributable builds', 'Tier 6 of the build plan: the things that make clide a real product instead of a working prototype.
**Extension API (third-party Lua):**
- The Lua runtime supporter tool (D-19) lands as a peer of pql/ptyc.
- Manifest schema, capability gating, sandboxed FS/IPC access.
- Same TabContribution / CommandContribution / etc. surface as built-in Dart extensions (D-15).
- Marketplace / distribution story is OUT OF SCOPE for Tier 6 local-install only.
**Settings UI (`builtin.settings-ui`):**
- Schema-driven settings panel reading from the kernel SettingsStore.
- Render strategy: form fields keyed off the schema each subsystem registers.
- Edits write back to `.clide/settings.yaml`.
**Theming UI (`builtin.theme-picker` extends):**
- Live preview of the four bundled themes (D-44).
- Custom theme: import YAML, validate against schema, register at runtime.
- Per-component override surface (long horizon).
**Distributable builds:**
- AppImage / Flatpak for Linux, .dmg for macOS see T-46.
- Self-update mechanism see T-47.
- License manifest auto-regen as part of the release build.
Big epic children land incrementally. Most concrete child tickets already exist; this is the umbrella.
Status note (2026-06-10 sweep): mixed completion. theme-picker is substantially implemented; settings-ui is a stub; the Lua runtime is skeleton-only (lib/lua/); distributable builds (T-46/T-47) remain deferred Tier-6 work. Epic stays open as the umbrella.', 'backlog', 'medium', NULL, NULL, 'D-15', '2026-04-22 11:45:41', '2026-06-10 15:13:57', NULL, 'bcccbd9609f6aba3fcfd3117f61af377', 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 ('06FB0TNQM66FTCTWHH9AQTNFKR', 'story', NULL, 'system tray and dock persistence', 'Minimize to system tray on Linux (AppIndicator) or Dock on macOS. Reopening from tray restores the window without cold boot. tmux sessions stay alive in background regardless.
Scope split (2026-06-10 sweep): the session-persistence half is effectively done - tmux keeps Claude/terminal sessions alive across restart (D-41). The OS-tray/AppIndicator + dock half is a stub only (lib/kernel/src/tray.dart - TrayRegistry has no platform-channel wiring) and is Tier-6+. Remaining work = the tray integration.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-10 15:13:59', NULL, 'ab5f60da82d9e23bf6104099b2fa9af0', 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 ('06FB0TNQM67JSC5RKS6M9182KG', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'Add images tab to context panel', 'Spec lists images as a right-panel section. No extension exists yet.
Relevance note (2026-06-10 sweep): likely superseded. The image card + full-screen lightbox shipped (T-249/T-252) and the canvas epic (T-317, D-91) folds image display into the unified drawing-card renderer rather than a separate context-panel tab. Confirm whether a distinct images rail section is still wanted; otherwise close in favor of the canvas path.', 'backlog', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-06-10 15:14:01', NULL, 'f81afd3f2563491ccd7325ced11e5e25', 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 ('06FB0TNQM7P6Q0DG4RG4CBHFJG', 'story', NULL, 'review Semantics widget coverage and UI testability', 'Audit all interactive widgets for Semantics coverage (labels, roles, states). Verify flutter test can locate and interact with every panel, button, and input via find.bySemanticsLabel. Run the existing a11y test suite and document gaps. Target: every user-facing action is testable without widget keys.
Reframe (2026-06-10 sweep): the original ''audit Semantics coverage'' framing is stale - test/a11y/ (semantic_coverage, contrast, keyboard_traversal, i18n) is now a mature per-PR gate per D-20. Re-scope to forward work: ratchet the semantic-coverage floor and deepen per-extension Semantics assertions, rather than a one-time review.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:14:02', NULL, '6d8c5bcd384a18ffa8f34f642c4f680e', 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 ('06FB0TNQM79CNBXJ2S3CFQR7VM', 'task', NULL, 'Misc PTY/IPC/git polish (audit medium-priority items)', 'Catch-all for the medium-priority items from the PTY/IPC error-handling audit (T-18, see docs/audits/pty-ipc-error-handling-2026-05-05.md) that didn`t earn dedicated tickets:
- **#17** `files.read` `readAsStringSync` is unguarded; UTF-8 errors / permissions / mid-read deletion become 500-style dispatch errors. Wrap in try/catch and emit a clean `IpcResponse.err`.
- **#19** `PtySession.close` swallows the 500ms timeout silently (`onTimeout: () {}`). Log when the timeout fires so we know SIGKILL was needed.
- **#20** Reader isolate treats every negative `read()` return that isn`t EINTR as EOF. Distinguish EBADF/EIO (real EOF) from transient EAGAIN (recoverable) and log the latter.
- **#21** `scm_rights.dart` reads cmsg-data fd without verifying `dataOffset + 4 <= msgControllen`. Bounds check before deref so a malformed peer can`t feed garbage as an fd.
- **#25** `_gitError` in `lib/src/daemon/git_commands.dart` always reports `tool_error`; push rejections / merge conflicts should map to `IpcExitCode.conflict` when stderr matches known patterns.
- **#27** `pane.spawn` returns `ok` even when `registry.write(id, bytes)` returned `n == -1`. Distinguish the failure.
- **#28** `IpcResponse.fromJson` throws `TypeError` on a malformed peer response missing `error`. Graceful degrade.
- **#29** PATH resolution in `native_pty.dart` uses the first existing match without `X_OK` check; non-executable files shadow valid binaries further along PATH.
Land each as a small focused commit; ticket closes when all items above are merged.
Item status (2026-06-10 sweep): from the T-18 audit, #16 (git error kinds) landed via T-79 and #22 (logging) via T-80. #21 (scm_rights.dart bounds check) is OBSOLETE - fd-passing/recvmsg was removed, the file no longer exists; drop it. Spot-checked still-open: #17 files.read unguarded readAsStringSync (files_commands.dart), #28 IpcResponse.fromJson TypeError (envelope.dart), #29 PATH X_OK check (native_pty.dart). ~7 items remain.', 'backlog', 'low', NULL, NULL, NULL, '2026-05-05 12:58:59', '2026-06-10 15:14:04', NULL, 'd98147f71017f0d6b3b8e0688c2760c9', 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 ('06FB0TNQM5YG22EV7BFTX5RTPR', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'web-tree-sitter via JS interop for browser builds', 'Conditional import behind TreeSitterService: native impl uses dart:ffi to libtree-sitter.so, web impl uses dart:js_interop to web-tree-sitter (official emscripten build from tree-sitter org). Same grammar .wasm files on both platforms. Vendor web-tree-sitter .wasm + JS glue as Flutter web assets, pinned version, added to licenses.yaml.
Cancelled 2026-06-10 (backlog relevance sweep): contradicts the desktop-first guardrail (CLAUDE.md) - web is an explicit non-goal / happy-accident only. TreeSitterService is FFI-only and there is no shipped web product, so a web-tree-sitter dual-path is not wanted. Reopen only if web ever becomes a real target.', 'cancelled', 'medium', NULL, NULL, NULL, '2026-04-22 20:14:33', '2026-06-10 15:14:17', NULL, '366c0ac563b3f001d6e783dda9cc60f2', 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 ('06FB0TNQM50X0DX8XTVZEA5GN8', 'story', NULL, 'workspace trust prompt', 'Prompt before running extensions or loading project settings in untrusted repositories. Trust decision persisted per repo path. Untrusted mode disables third-party extensions and restricts IPC commands.
Cancelled 2026-06-10 (relevance sweep): premature. Third-party (Lua) extension loading is not shipped - ExtensionScanner.discover is test-only and the Lua runtime is a Tier-6 skeleton. Nothing to trust-gate yet; revisit at Tier 6 when external extension loading lands (the trust surface will likely be Lua sandboxing per D-19, not a per-repo prompt).', 'cancelled', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-06-10 15:14:17', NULL, '34d12878273efad542a07a4e96eef3ac', 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 ('06FB0TNQM6RDM2EKZX132GPV7M', 'task', '06FB0TNQM6T6580D8ABDVTMNZW', 'Add PRs tab to sidebar', 'Spec lists PRs as a left-panel section (icon rail position 5). No extension exists yet.
Cancelled 2026-06-10 (relevance sweep): spec''d in D-47 but unscoped, no extension exists, and the data path (git host API vs local metadata) is undecided. Closing to clear the backlog; file a fresh scoped story if a PRs surface is wanted.', 'cancelled', 'low', NULL, NULL, 'D-47', '2026-04-22 21:03:40', '2026-06-10 15:14:17', NULL, '2d35befb6eebdc0a5778c26887622e85', 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 ('06FB0TNQM42M9RK4399B4F4WSG', 'story', NULL, 'VS Code keybinding preset', 'Ship a VS Code-compatible keybinding preset that maps standard VS Code shortcuts to clide commands. Users select it in settings. Covers file navigation, editor actions, panel toggles, search, and terminal.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is now in place. Implementation is now just authoring `assets/keymaps/vscode.yaml` against the typed Intents in `lib/kernel/src/keymap/intents.dart` (plus `command:<id>` bindings for VS-Code-specific commands the preset wants to bind to clide commands). Users will switch presets via `app.keymap.preset = vscode` once a settings UI exists, or directly via the setting today.
**Acceptance:**
1. `assets/keymaps/vscode.yaml` ships covering the documented VS Code default keybindings.
2. `KeymapService.setPreset("vscode")` activates the preset and all asserted bindings resolve as expected.
3. The preset uses when-clauses where VS Code does (`editor.focused`, `inputFocused`, `palette.open`, ).
4. A regression test loads the preset and asserts a representative subset (e.g. ctrl+p quick-open command, ctrl+shift+p palette).
**Out of scope:** clide commands that have no VS Code analogue (those keep their default-preset bindings).
Correction (2026-06-10): this ticket''s ''see Q-9'' reference is stale Q-9 is ''Lua runtime vendoring'', unrelated. The search-everywhere / double-tap-modifier gap is now tracked by T-341.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:17:59', NULL, 'f4c92b7fb7bbf4e1a2afb9b02f223822', 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 ('06FB0TNQM60QRRNEEWG84VWKXC', 'story', NULL, 'JetBrains keybinding preset', 'Ship a JetBrains/IntelliJ-compatible keybinding preset mapping standard JetBrains shortcuts to clide commands. Covers navigation, refactoring, search, run/debug, and tool windows.
**Unblocked by T-117 (2026-05-17):** the keystroke mapper layer is in place. Implementation is authoring `assets/keymaps/jetbrains.yaml` against the typed Intents + `command:<id>` bindings, plus when-clauses for the contexts JetBrains presets typically scope to (`editor.focused`, `inputFocused`, etc.).
**Acceptance:**
1. `assets/keymaps/jetbrains.yaml` ships covering the documented IntelliJ default keybindings.
2. `KeymapService.setPreset("jetbrains")` activates the preset and all asserted bindings resolve.
3. A regression test exercises a representative subset (e.g. shift+shift quick-open command see Q-9 if the search-everywhere overlay needs its own intent).
Correction (2026-06-10): ''see Q-9'' is stale (Q-9 is Lua runtime vendoring). The double-Shift ''Search Everywhere'' chord is NOT expressible by the current matcher (bare/double modifiers unsupported) tracked in T-341. This preset maps quick-open to Ctrl+Shift+N and the palette to Ctrl+Shift+A as the expressible IntelliJ equivalents.', 'done', 'medium', NULL, NULL, NULL, '2026-04-24 06:34:16', '2026-06-10 15:17:59', NULL, '58c6bc49c1ec261dfffc3d5d60fc44a5', 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);
+7
View File
@@ -31,6 +31,13 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
### Added
- **VS Code keybinding preset.** A `vscode` keymap mapping VS Code's default
shortcuts (Ctrl+P, Ctrl+Shift+P, Ctrl+B, Ctrl+J, Ctrl+`, zoom, …) to clide.
Activate via the "Keymap: VS Code" command or `app.keymap.preset = vscode`. (T-64)
- **JetBrains keybinding preset.** A `jetbrains` keymap mapping IntelliJ's
defaults (Find Action, Go to File, tool windows, …). Double-Shift "Search
Everywhere" isn't expressible by the chord matcher yet (T-341), so Go to File
stands in for quick-open. (T-66)
- **Picking up a ticket now starts it.** Handing a ticket to a live Claude pane
(sidebar pick-up) also moves it to `in_progress` and refreshes the sidebar —
but only on acceptance and only from a not-yet-started status, so a pick-up
+95
View File
@@ -0,0 +1,95 @@
# clide JetBrains / IntelliJ keymap preset (T-66).
#
# Maps IntelliJ's default keybindings to clide intents + commands. A preset
# fully REPLACES the active layer, so this file is self-contained.
#
# Notation (D-82): `+` joins a chord, a space sequences, a YAML list
# alternates. IntelliJ's mac and Linux/Windows defaults diverge for several
# actions (Go to File is Cmd+Shift+O on mac but Ctrl+Shift+N on win/linux),
# so both spellings are bound.
#
# Scope flags: only `palette.open` / `quickOpen.open` are published today;
# IntelliJ's editor-scoped contexts have no producer yet, so global chords
# stay ungated (they're global in IntelliJ too).
#
# Not bound — no clide command analogue (kept out of scope per the ticket):
# Run (Shift+F10), Debug (Shift+F9), Rename/Refactor (Shift+F6), Settings
# (Ctrl+Alt+S). And "Search Everywhere" (double-Shift) is not expressible by
# the current chord matcher (bare/double modifiers unsupported) — tracked by
# T-341; Go to File / Find Action below are the practical stand-ins.
name: jetbrains
bindings:
# -- Activation / focus -----------------------------------------------
# No F6 panel cycling here: IntelliJ uses F6 (Move) / Shift+F6 (Rename),
# so binding them to panel focus would fight muscle memory.
- intent: activate
keys: [enter, space]
- intent: dismiss
keys: escape
- intent: focus.next
keys: tab
- intent: focus.previous
keys: shift+tab
# -- Find Action (≈ command palette): Ctrl+Shift+A --------------------
- intent: palette.open
keys: [ctrl+shift+a, meta+shift+a]
- intent: palette.selectNext
keys: down
when: palette.open
- intent: palette.selectPrevious
keys: up
when: palette.open
- intent: palette.accept
keys: enter
when: palette.open
- intent: dismiss
keys: escape
when: palette.open
# -- Quick open: Go to File / Go to Class / Recent Files --------------
# win/linux: Ctrl+Shift+N, Ctrl+N, Ctrl+E. mac: Cmd+Shift+O, Cmd+O,
# Cmd+E. clide has one fuzzy file finder, so all land on quick-open.
- intent: quickOpen.open
keys: [ctrl+shift+n, ctrl+n, ctrl+e, meta+shift+o, meta+o, meta+e]
when: "!palette.open"
- intent: quickOpen.selectNext
keys: down
when: quickOpen.open
- intent: quickOpen.selectPrevious
keys: up
when: quickOpen.open
- intent: quickOpen.accept
keys: enter
when: quickOpen.open
- intent: dismiss
keys: escape
when: quickOpen.open
# -- Find in Path (search): Ctrl+Shift+F ------------------------------
- intent: findInFiles.open
keys: [ctrl+shift+f, meta+shift+f]
# -- Tool windows -----------------------------------------------------
# Project (Alt+1) → sidebar; Terminal (Alt+F12) → bottom dock; Hide All
# Windows / distraction-free (Ctrl+Shift+F12) → focus (zen) mode.
- intent: command:sidebar.collapse
keys: [alt+1, meta+1]
- intent: command:dock.toggle
keys: alt+f12
- intent: command:panel.focusMode
keys: [ctrl+shift+f12, meta+shift+f12]
# -- Editor -----------------------------------------------------------
# Close active tab: Ctrl+F4 (win/linux) / Cmd+W (mac).
- intent: command:editor.close
keys: [ctrl+f4, meta+w]
# Zoom is a clide convenience — IntelliJ has no default zoom keys.
- intent: command:view.zoomIn
keys: [ctrl+equal, ctrl+shift+equal, meta+equal, meta+shift+equal]
- intent: command:view.zoomOut
keys: [ctrl+minus, meta+minus]
- intent: command:view.zoomReset
keys: [ctrl+0, meta+0]
+114
View File
@@ -0,0 +1,114 @@
# clide VS Code keymap preset (T-64).
#
# Maps VS Code's default keybindings to clide intents + commands so users
# with VS Code muscle memory feel at home. A preset fully REPLACES the
# active layer, so this file is self-contained (activation, focus, palette
# and quick-open navigation are all repeated from `default.yaml`).
#
# Notation (D-82): `+` joins a chord, a space sequences (`ctrl+k z`), a
# YAML list alternates (`[ctrl+b, meta+b]`). Both Ctrl (Linux/Windows) and
# Meta/Cmd (macOS) variants are bound so one preset serves every platform.
#
# Scope flags: only `palette.open` and `quickOpen.open` are published today
# (by the palette / quick-open overlays). VS Code's editor-scoped contexts
# (`editor.focused`, `inputFocused`) have no producer yet, so the global
# chords below stay ungated — which matches VS Code, where these commands
# (palette, quick-open, sidebar toggle, …) are global anyway. Editor-text-
# scoped gating lands when those scope producers do.
name: vscode
bindings:
# -- Activation / focus -----------------------------------------------
- intent: activate
keys: [enter, space]
- intent: dismiss
keys: escape
- intent: focus.next
keys: tab
- intent: focus.previous
keys: shift+tab
# Cycle the editor "parts" / panels (VS Code F6).
- intent: focus.nextPanel
keys: f6
- intent: focus.previousPanel
keys: shift+f6
# -- Command palette (Ctrl+Shift+P, F1) -------------------------------
- intent: palette.open
keys: [ctrl+shift+p, meta+shift+p, f1]
- intent: palette.selectNext
keys: [down, ctrl+n]
when: palette.open
- intent: palette.selectPrevious
keys: [up, ctrl+p]
when: palette.open
- intent: palette.accept
keys: enter
when: palette.open
- intent: dismiss
keys: escape
when: palette.open
# -- Quick open / Go to File (Ctrl+P) ---------------------------------
- intent: quickOpen.open
keys: [ctrl+p, meta+p]
when: "!palette.open"
- intent: quickOpen.selectNext
keys: [down, ctrl+n]
when: quickOpen.open
- intent: quickOpen.selectPrevious
keys: up
when: quickOpen.open
- intent: quickOpen.accept
keys: enter
when: quickOpen.open
- intent: dismiss
keys: escape
when: quickOpen.open
# -- Search (Ctrl+Shift+F) --------------------------------------------
- intent: findInFiles.open
keys: [ctrl+shift+f, meta+shift+f]
# -- Panel toggles ----------------------------------------------------
# Toggle Side Bar (Ctrl+B), Panel (Ctrl+J), Secondary Side Bar
# (Ctrl+Alt+B). clide's left sidebar / bottom dock / right context bar.
- intent: command:sidebar.collapse
keys: [ctrl+b, meta+b]
- intent: command:dock.toggle
keys: [ctrl+j, meta+j]
- intent: command:context.collapse
keys: [ctrl+alt+b, meta+alt+b]
# Toggle Terminal (Ctrl+`). clide has one bottom dock where shell/output
# live, so the terminal chord targets the same dock as Ctrl+J.
- intent: command:dock.toggle
keys: [ctrl+backquote, meta+backquote]
# Zen Mode (Ctrl+K Z).
- intent: command:panel.focusMode
keys: ["ctrl+k z", "meta+k z"]
# -- Editor actions ---------------------------------------------------
# Close Editor (Ctrl+W).
- intent: command:editor.close
keys: [ctrl+w, meta+w]
# Window zoom (VS Code View: Zoom In/Out/Reset). `+` is shift+equal on
# most layouts, so bind both equal and shift+equal.
- intent: command:view.zoomIn
keys: [ctrl+equal, ctrl+shift+equal, meta+equal, meta+shift+equal]
- intent: command:view.zoomOut
keys: [ctrl+minus, meta+minus]
- intent: command:view.zoomReset
keys: [ctrl+0, meta+0]
# -- File / workspace -------------------------------------------------
# Open Folder (Ctrl+K Ctrl+O), New Window (Ctrl+Shift+N), Close Folder
# (Ctrl+K F), Color Theme (Ctrl+K Ctrl+T).
- intent: command:file.openFolder
keys: ["ctrl+k ctrl+o", "meta+k meta+o"]
- intent: command:file.newWindow
keys: [ctrl+shift+n, meta+shift+n]
- intent: command:file.closeWorkspace
keys: ["ctrl+k f", "meta+k f"]
- intent: command:theme.pick
keys: ["ctrl+k ctrl+t", "meta+k meta+t"]
@@ -26,11 +26,13 @@ class KeybindingsUiExtension extends ClideExtension {
@override
Future<void> deactivate() async => _keymap = null;
/// Presets that ship today. VS Code / JetBrains (T-64/T-66) join here
/// once their YAMLs land.
/// Presets that ship today, each exposed as a `keymap.preset.<name>`
/// command that activates it.
static const _presets = <String, String>{
'default': 'Keymap: Default',
'vim': 'Keymap: Vim',
'vscode': 'Keymap: VS Code',
'jetbrains': 'Keymap: JetBrains',
};
@override
@@ -0,0 +1,118 @@
/// Regression tests for the VS Code (T-64) and JetBrains (T-66) keymap
/// presets: activating each via `KeymapService.setPreset` resolves a
/// representative subset of its bindings as expected. The real shipped
/// `assets/keymaps/*.yaml` files are read from disk and fed through the
/// loader, so a typo in the preset fails here.
library;
import 'dart:convert';
import 'dart:io';
import 'package:clide/kernel/src/keymap/intents.dart';
import 'package:clide/kernel/src/keymap/key_chord.dart';
import 'package:clide/kernel/src/keymap/keymap_service.dart';
import 'package:clide/kernel/src/settings.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart' show Intent;
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late Directory appDir;
late SettingsStore settings;
setUp(() async {
appDir = await Directory.systemTemp.createTemp('clide_preset_test_');
settings = SettingsStore(appDir: appDir);
await settings.load();
});
tearDown(() async {
settings.dispose();
if (await appDir.exists()) await appDir.delete(recursive: true);
});
// Build a service whose bundle serves the real shipped preset content.
Future<KeymapService> activate(String preset) async {
final src = File('assets/keymaps/$preset.yaml').readAsStringSync();
final svc = KeymapService(
settings: settings,
appDir: appDir,
bundle: _bundle({'assets/keymaps/$preset.yaml': src}),
);
await svc.setPreset(preset);
return svc;
}
bool isCommand(Intent? i, String id) => i is InvokeCommandIntent && i.commandId == id;
group('vscode preset (T-64)', () {
test('setPreset activates it and the representative subset resolves', () async {
final svc = await activate('vscode');
expect(settings.get<String>(kKeymapPresetSetting), 'vscode');
// Acceptance #4: the two canonical chords.
expect(svc.keymap!.resolve(KeyChord.parse('ctrl+p'), const {}), isA<QuickOpenIntent>());
expect(svc.keymap!.resolve(KeyChord.parse('ctrl+shift+p'), const {}), isA<PaletteOpenIntent>());
// F1 also opens the palette in VS Code.
expect(svc.keymap!.resolve(KeyChord.parse('f1'), const {}), isA<PaletteOpenIntent>());
// Search + panel toggles.
expect(svc.keymap!.resolve(KeyChord.parse('ctrl+shift+f'), const {}), isA<FindInFilesIntent>());
expect(isCommand(svc.keymap!.resolve(KeyChord.parse('ctrl+b'), const {}), 'sidebar.collapse'), isTrue);
expect(isCommand(svc.keymap!.resolve(KeyChord.parse('ctrl+j'), const {}), 'dock.toggle'), isTrue);
expect(isCommand(svc.keymap!.resolve(KeyChord.parse('ctrl+w'), const {}), 'editor.close'), isTrue);
});
test('ctrl+p is quick-open only while the palette is closed (when-clause)', () async {
final svc = await activate('vscode');
// palette.open → ctrl+p flips to palette.selectPrevious, not quick-open.
expect(svc.keymap!.resolve(KeyChord.parse('ctrl+p'), const {'palette.open': true}), isA<PaletteSelectPreviousIntent>());
});
test('the Ctrl+K Ctrl+T sequence binds the theme picker', () async {
final svc = await activate('vscode');
final hit = svc.keymap!.effectiveBindings.any((b) =>
b.sequence.length == 2 &&
b.sequence[0] == KeyChord.parse('ctrl+k') &&
b.sequence[1] == KeyChord.parse('ctrl+t') &&
isCommand(b.intent, 'theme.pick'));
expect(hit, isTrue);
});
});
group('jetbrains preset (T-66)', () {
test('setPreset activates it and the representative subset resolves', () async {
final svc = await activate('jetbrains');
expect(settings.get<String>(kKeymapPresetSetting), 'jetbrains');
// Double-Shift "Search Everywhere" is not expressible (T-341); Go to
// File (Ctrl+Shift+N) is the stand-in for quick-open.
expect(svc.keymap!.resolve(KeyChord.parse('ctrl+shift+n'), const {}), isA<QuickOpenIntent>());
expect(svc.keymap!.resolve(KeyChord.parse('ctrl+e'), const {}), isA<QuickOpenIntent>());
// Find Action → command palette.
expect(svc.keymap!.resolve(KeyChord.parse('ctrl+shift+a'), const {}), isA<PaletteOpenIntent>());
// Find in Path; tool windows.
expect(svc.keymap!.resolve(KeyChord.parse('ctrl+shift+f'), const {}), isA<FindInFilesIntent>());
expect(isCommand(svc.keymap!.resolve(KeyChord.parse('alt+1'), const {}), 'sidebar.collapse'), isTrue);
expect(isCommand(svc.keymap!.resolve(KeyChord.parse('alt+f12'), const {}), 'dock.toggle'), isTrue);
});
});
}
/// In-memory AssetBundle that serves whatever the constructor map says.
AssetBundle _bundle(Map<String, String> files) => _MapBundle(files);
class _MapBundle extends CachingAssetBundle {
_MapBundle(this._files);
final Map<String, String> _files;
@override
Future<ByteData> load(String key) async {
final s = _files[key];
if (s == null) throw Exception('asset not in fake bundle: $key');
// utf8 (not codeUnits) so non-ASCII in preset comments survives — the real
// rootBundle serves utf8 bytes.
return ByteData.view(Uint8List.fromList(utf8.encode(s)).buffer);
}
}