13 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 2e4f87455a feat(vim): gt / gT cycle workspace tabs (T-405 part 2)
Closes out the T-403 cross-pane vim layer. gt/gT bind to the existing
workspace.tab.next/previous commands (also on ctrl+pagedown/up for every
preset), resolved by the focused editor's matcher or a pane's PaneKeyNav —
bare-g sequences stay editor/pane-local (T-404's global matcher only engages
on modified-chord prefixes), so no global-matcher surgery.

- vim.yaml: g t -> command:workspace.tab.next, g shift+t -> .previous
  (vim.normal); shares the `g` prefix with `g g` (docStart / nav.top),
  distinguished by the final chord.
- PaneKeyNav now EXECUTES non-editor.vim.* command intents (e.g.
  workspace.tab.*) instead of swallowing all command intents, so gt/gT work
  from a focused pane; editor.vim.* buffer edits stay blocked in panes.

Tests: vim-preset resolution (gt/gT bind, gg intact), PaneKeyNav executes
the command from a pane, and editor.vim.* is never run from a pane. make
test green; analyze + format clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 12:08:07 +02:00
jpmschweitzerandClaude Opus 4.8 e54d5263e0 feat(vim): ex command-line overlay (:w :q :wq :x :e :N, ZZ) (T-407)
Under the Vim preset, `:` opens a transient one-line ex overlay running a
fixed v1 table; ZZ runs :wq directly. Completes the last built child of the
T-403 cross-pane vim layer (T-405 part 2 gt/gT still open).

- ExLineController + parseExCommand grammar + editor-targeted executors
  (lib/kernel/src/ex_line.dart); the overlay (lib/widgets/src/ex_line_overlay
  .dart) reuses the quick-open chrome, mounts in the root_shell Stack, and
  publishes the exline.open scope flag. Unknown commands flash + stay open;
  with no active buffer every command no-ops (2026-06-13 decision).
- :q closes the active tab via editor.close on its id — the registry promotes
  the next buffer and the split self-collapses on the last (2026-06-12
  decision); :w/:wq/:x/ZZ save (+close) the active buffer.
- :e <path> seeds quick-open (new QuickOpenController.open(seed:)); :N adds the
  editor.goto-line IPC/CLI verb (reuses _offsetForLine). Goto needs caret sync:
  EditorController now handles editor.selection-changed and the editor view
  moves the caret on a selection-only change.
- `:` and ZZ are typed intents; the editor matcher and PaneKeyNav now bubble
  unhandled typed intents to the app-root Actions, so they fire from any focus.
  vim.yaml binds `:`, ZZ (shift+z shift+z), and Esc-dismiss.

Tests: parser/controller/executors, editor.goto-line daemon tests,
selection-changed (controller + view), full overlay widget test. make test
green; analyze + format clean.

Also files T-441 (drop bold from the ticket-id card label) and T-442
(sub-agent renders as 3 cards instead of one bundle) under the T-276 UI epic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:44:48 +02:00
jpmschweitzerandClaude Opus 4.8 cbe76938a8 vim ctrl+w window-command family + global multi-chord matcher (T-404)
The structural piece: a global SequenceMatcher in root_shell, at the
HardwareKeyboard level so a focused editor/pane can't swallow the second
chord. It only STARTS on a modified chord that prefixes a sequence (ctrl+w),
so bare-key sequences (gg, dd) stay editor/pane-local and single-chord presets
are untouched; bare ctrl+w still fires editor.close after the D-82 timeout.

vim.yaml binds the window family under vim.normal||vim.visual: ctrl+w h/l →
panel.focus.left/right, j → dock.toggle, w / ctrl+w → focus.nextPanel,
shift+w → focus.previousPanel, o → panel.focusMode, q/c → editor.close.

Tests: ctrl+w sequence resolution at the keymap layer, plus app-level
integration (ctrl+w o toggles focus mode; bare ctrl+w closes the editor after
the timeout; a bare g is not grabbed globally).

This is the global matcher T-405 part 2 (gt/gT) was waiting on — though bare-g
sequences need more thought (g is editor-local), noted for that follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:19:32 +02:00
jpmschweitzerandClaude Opus 4.8 25db19fc0e vim normal-mode navigation in non-editor panes (T-406)
The structural T-403 child: make vim normal mode mean navigation in panes
that were mouse-only. The passive global key path can't run multi-chord
sequences (D-82), so each pane hosts its own SequenceMatcher — factored into
a reusable PaneKeyNav that resolves the live keymap and dispatches nav.*
intents while a pane holds focus under the vim preset.

- nav.* intents (down/up/pageDown/pageUp/top/bottom/expandOrRight/
  collapseOrLeft/activate) — preset-neutral; vim.yaml binds j/k/ctrl+d/ctrl+u/
  gg/G/l/h/[o,enter] under `vim.normal && !editor.focused`.
- The editor publishes an `editor.focused` scope flag from its focus node, so
  the same keys stay buffer motions while the editor is focused and become nav
  when a pane is — resolved by file order + the guard (no change to the editor
  motion bindings).
- File tree: a flattened visible-index selection cursor in FileTreeController
  (j/k move, h collapse-or-out, l expand-or-into, o/enter open), with a focus
  ring + scroll-into-view.
- Conversation: j/k line-scroll, ctrl+d/u half-page, gg top, G bottom — G
  re-arms follow-tail.

Foundation for T-404/T-405/T-407, which build on the per-pane matcher and the
editor.focused guard. Git panel + ticket board list nav deferred to a
follow-up (the ticket says lists can trail). Tests: keymap resolution under
both scopes, PaneKeyNav dispatch, the controller selection model, and
end-to-end key-driven nav in both panes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 14:46:04 +02:00
jpmschweitzerandClaude Opus 4.8 430189d714 keymap: support bare-modifier double-tap chords; double-Shift → quick-open (T-341)
The chord matcher couldn't represent a bare or double-tapped modifier:
KeyChord.parse required a base key, so `shift shift` failed, and JetBrains
"Search Everywhere" (double-Shift) was unbindable.

Design decision: search-everywhere aliases clide's existing quick-open
finder (not a new overlay) — bound across all four presets per the user.

Changes:
- KeyChord: a bare modifier name (`shift`, `ctrl`, `cmd`, …) parses as a
  modifier-free chord on that modifier's logical key, so parseSequence(
  'shift shift') yields a two-chord double-tap. Adds KeyChord.bareModifier
  and modifierForLogicalKey.
- ModifierTapTracker: headless, clock-injected double-tap detector. A bare
  modifier never forms a single chord; an intervening key breaks the gesture.
- app.dart global handler feeds bare-modifier KeyDowns to the tracker and,
  on a double-tap, resolves the 2-chord sequence via the new
  KeymapService.resolveSequence. The existing single-chord path is untouched
  (zero behavioural risk to normal keys).
- Presets: default/vim/vscode/jetbrains add `shift shift` → quickOpen.open.
  jetbrains header updated (the gesture is now expressible).

Tests: bare-modifier parse/equality/round-trip; tracker window/reset/
different-modifier/consume; each shipped preset resolves double-Shift to
QuickOpenIntent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 13:26:54 +02:00
jpmschweitzerandClaude Opus 4.8 3134a0ac52 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>
2026-06-10 17:18:11 +02:00
jpmschweitzerandClaude Opus 4.8 e0fba4a3bc add Zed-style application menu bar: File / View / Help (T-48)
A custom in-window menu bar in the hat (no native menu, D-7), built from
the command registry so it stays in sync and satisfies D-6 parity.

- Menu model + hybrid resolver (menu_model.dart): a curated File/View/Help
  tree where a MenuAutoFill node sweeps in unplaced view.* commands; titles
  + keybindings come from the registry/keymap; unregistered or
  enabledWhen-false items render disabled (greyed), never hidden.
- Widgets: MenuBar row in the hat (chrome tokens), anchored MenuDropdown
  overlay (dropdown tokens), two-column MenuItemRow with inline keybinding.
- Full keyboard: Alt+mnemonic opens (hook in _RootShell._onKey), arrows
  navigate, Enter activates, Esc closes, Left/Right switch menus.
- Commands: file.openFolder / file.newWindow / file.closeWorkspace /
  help.about, registered by MenuBarExtension(services:). File logic lifted
  out of the project switcher into FileActions (one source of truth; the
  switcher now dispatches the commands). Ctrl+O / Ctrl+Shift+N are now real
  keybindings in default.yaml.
- Help → About: version/commit/date/repo from build-info + the bundled
  dependency licenses parsed from assets/licenses.yaml.

Edit/Selection menus are deferred to T-271/T-272 (need focused-surface
command routing).

Tests: resolver + controller + licenses parse (pure); menu-bar widget
(open/close/execute/disabled/Esc/arrow/Enter/Left-Right); FileActions +
Open dialog; app-level Alt+F, non-repo dialog, and closeWorkspace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:39:56 +02:00
jpmschweitzerandClaude Opus 4.8 bcb57bc75f ship the Vim keymap preset
T-65 — the capstone of the Vim epic. assets/keymaps/vim.yaml binds the
muscle-memory set guarded on vim.normal/insert/visual: hjkl/w/b/e/0/$/^/
gg/G motions, x/dd/dw/D/yy/p/P/cc/cw edits, i/a/I/A/o/O insert entries,
v + d/y/c in visual, Esc back to normal, and counts via the matcher. App
shortcuts (palette, find, zoom) are carried in the preset so they survive
under Vim.

The editor now feeds Shift chords to the matcher (Vim's capitals: G, D,
A, P, $) while still bubbling Ctrl/Alt/Meta to the global handler. The
keybindings-ui stub gains palette commands (`Keymap: Vim` / `Keymap:
Default`) to switch presets — the user-facing way to turn Vim on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:32:40 +02:00
jpmschweitzerandClaude Opus 4.8 f96c565acd add find-in-files sidebar panel + Ctrl/Cmd+Shift+F
The find-in-files UI on top of the search.grep engine. A
FindInFilesController drives search.grep, accumulates streamed
search.match events (scoped to the active searchId, stale ids
ignored) grouped by file, and opens a match in the editor at its line.
The SearchPanelView contributes a sidebar tab: a debounced query box,
regex + case toggles, include/exclude glob fields, and a grouped
results list with the matched span highlighted.

findInFiles.open (Ctrl/Cmd+Shift+F) reveals and activates the search
tab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:53:50 +02:00
jpmschweitzerandClaude Opus 4.8 0c7a6e86d5 add quick-open fuzzy file finder (Ctrl/Cmd+P)
A file picker overlay over the whole workspace, distinct from the
command palette. QuickOpenController holds the file list + a
subsequence fuzzy filter; the overlay loads the list via files.walk on
open, shows RecentFilesService entries on an empty query, and opens the
selection through a shared openWorkspaceFile helper (.md → markdown
reader bus, else editor.open) that the files panel now also routes
through, so recents stay in sync from every open site.

Bound to ctrl+p / meta+p with `when: !palette.open` so it never
collides with the palette's ctrl+p navigation; in-overlay arrows/enter/
escape reuse the palette's keymap-driven model via quickOpen.* intents.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:19:21 +02:00
jpmschweitzerandClaude Opus 4.7 7937da1734 panel-to-panel focus traversal via F6 / Shift+F6 (T-105)
Each SlotHost now owns a FocusScopeNode and registers it with
FocusTracker on mount. The render is wrapped in
FocusScope + FocusTraversalGroup so Tab stays within a panel and
slot-level focus is observable.

When a slot's scope gains focus, SlotHost pushes
(slot, activeContributionId) to FocusTracker — this collapses the
parallel-tracker model the consultant flagged. FocusTracker keeps
its setActive surface for explicit callers (palette, etc.) but
slot-scoped tab activation feeds it automatically.

Two new intents, two new bindings:
  FocusNextPanelIntent     → F6
  FocusPreviousPanelIntent → Shift+F6
(VS Code convention; preset YAML.)

The cycle skips slots without a registered scope, so a layout that
hides the context panel doesn't strand focus on a missing target.
Fewer than two registered → no-op.

SlotHost split into a stateful outer (scope + registry) and a
stateless `_SlotBody` (the existing slot-specific rendering),
keeping the build straightforward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:56:35 +02:00
jpmschweitzerandClaude Opus 4.7 12e0509fa3 keyboard-operable ClideTappable + palette nav (T-100)
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m5s
Plug widgets into the keymap layer landed in T-117.

ClideTappable:
  - Wrap in `Actions(ActivateIntent → onTap)` outside a `Focus` so
    dispatch from the focused context walks up and hits the action.
  - Add a focus ring via `tokens.globalFocus` (DecoratedBox foreground
    overlay, transparent border when unfocused, no layout shift).
  - Disabled (`onTap == null`) skips focus traversal and shows the
    forbidden cursor.

ClidePalette:
  - Register Actions for the four palette intents
    (selectNext / selectPrev / accept / dismiss).
  - Publish `palette.open` scope flag via `KeymapService.setScopeFlag`
    so when-clauses can scope future bindings to "palette only".
  - Highlight the selected row with `listItemSelectedBackground`;
    scroll it into view on nav.
  - `PaletteController` grows `selectedIndex` + `selectNext` /
    `selectPrevious` / `acceptSelected`; index resets on open /
    filter change.

Intents.dart drops the `ClideIntent` base — `ActivateIntent` and
`DismissIntent` come from Flutter; clide owns the palette and text-
scale and command-bridge subclasses. `parseIntentId('activate')` →
Flutter's class; same for dismiss. Widget code uses the canonical
Flutter Intent types where they fit.

App root grows a PaletteOpenIntent action that calls
`services.palette.open()`, completing the ctrl/cmd+shift+p path
end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:48:03 +02:00
jpmschweitzerandClaude Opus 4.7 798ba524f1 keystroke mapper layer — intents, presets, when-clauses (T-117)
Build the upstream of every keyboard-driven feature: widgets bind
to typed Intents, the keymap resolves chord+context to an Intent,
and Flutter's Actions dispatches. The widget never touches a key.

Layers (low → high precedence):
  1. preset YAML in assets/keymaps/<preset>.yaml
  2. extension-registered command bindings (via
     KeymapService.registerCommandBinding from ExtensionManager)
  3. user file at <appDir>/keybindings.yaml
  4. settings JSON overlay at app.keymap.overrides

The when-clause grammar is a tiny recursive-descent parser over
boolean expressions on a named context bag — VS-Code style
`palette.open && !textInputFocused`. Producing services publish
scope flags via setScopeFlag.

Keys reference LogicalKeyboardKey.keyId (stable across keyboard
layouts), not the locale-aware keyLabel the consultant flagged.

Ships:
  - lib/kernel/src/keymap/{key_chord, when_clause, intents, keymap,
    keymap_service}.dart
  - assets/keymaps/default.yaml (the baseline preset)
  - 90+ unit tests covering parser precedence, layering precedence,
    scope evaluation, register/unregister, settings overlay,
    malformed-input tolerance
  - app.dart root handler routes through KeymapService → Actions
  - ExtensionManager mirrors every legacy defaultBinding into the
    keymap as a contribution layer

KeybindingResolver kept temporarily as a back-compat shim for
callers we haven't migrated yet; safe to delete once the last
caller goes through Actions.

Closes T-110 (consultant: scoped Shortcuts/Actions; off keyLabel).
Annotates T-23 with what's left for T-100. Unblocks T-64 / T-65 /
T-66 (preset data tickets).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:40:02 +02:00