Commit Graph
289 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 09d1a6370a add clide ui open to drive GUI readers from the CLI (T-231)
The drive-half complement to clide status (observe): an agent can now
open a doc in a GUI reader from the CLI so it can show the user what
it's looking at. 'clide ui open tickets T-48' (decisions by id,
markdown by path) publishes a 'selection' to the kernel MessageBus that
the reader's ReaderNav picks up.

The verb (ui_command.dart) is decoupled from the kernel via a publish
callback, wired in main.dart to the post-boot-captured MessageBus, so it
stays Flutter-free under dart test. Reads CLI positionals or named args.

Also files T-232 (CLI argv args don't reach typed handlers -- the gating
drive-the-IDE bug) and T-233 (diff-panel ui.open follow-up).

Closes T-231 (under T-208).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:30:36 +02:00
jpmschweitzerandClaude Opus 4.8 8682564903 add clide status orientation snapshot; close Epic C (T-221)
clide status returns a one-shot snapshot for an orienting agent: the
workspace root, a git summary (branch/ahead/behind/clean/counts), the
active editor buffer + selection, the read-only readers' viewed docs
(new ReaderNavRegistry.currentByReader, the T-220 fold), focusedFile,
the live view-pane list (T-219), and the layout (slots + visibility +
focus mode). Previously 'status' was an unknown command (exit 3).

The verb handler (status_command.dart) is a thin Flutter-free wrapper;
the snapshot is assembled in main.dart where the live kernel + subsystem
state is in scope, with readerNav captured post-boot. Composite shape is
verified live; the pieces are unit-tested.

Closes T-221 and T-218 (Epic C) under T-208 'Give Claude hands' --
the observe half of D-6 parity is now in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:51:15 +02:00
jpmschweitzerandClaude Opus 4.8 6c4c48bfc2 add a permission-mode cycler to the primary Claude pane
T-226. The primary pane showed the permission mode but had no way to
change it (only the cockpit roster did, T-181). Add three affordances,
all cycling the safe trio default -> acceptEdits -> plan over the
stream-json control channel:

- Ctrl/Cmd+M while the composer is focused, intercepted at the composer
  so it targets that pane's session. Shift+Tab (the CLI chord) is
  deliberately NOT used — Tab/Shift+Tab are real a11y focus-traversal
  intents since T-204.
- The status-line mode label is now an interactive badge (ClideTappable):
  click, or focus + Enter/Space, cycles it.
- A "Claude: Cycle permission mode" palette command targeting the primary
  session.

bypassPermissions stays out of every cycle path here — it's reachable
only via the cockpit's explicit confirm (T-181). Shared helpers
(nextSafePermissionMode, statusSegmentsAroundMode) live in claude_status;
the cockpit's existing copy is left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:45:52 +02:00
jpmschweitzerandClaude Opus 4.8 be06b94754 register live GUI panes into clide pane list (T-219)
Per D-6 parity / D-83, make the kernel tabs the user sees (Claude,
Files, Editor, viewers) visible to the CLI. The PTY-backed PaneRegistry
can't model widget panes, so rather than mirror state (and risk drift),
pane.list snapshots the kernel PanelRegistry + LayoutArrangement at
request time via an injected view-pane source.

New Flutter-free ViewPane value type + snapshotViewPanes kernel bridge;
the daemonClientFactory now passes the PanelRegistry through so the
dispatcher can read it. pane.list merges PTY panes (source absent) with
UI tabs (source: ui, with slot/title/active/visible).

Acceptance (GUI-open: pane list enumerates live panes) needs a running
app to confirm end-to-end; unit-tested at the snapshot + dispatch level.

Closes T-219 (under T-218 / T-208 'Give Claude hands').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:49:57 +02:00
jpmschweitzerandClaude Opus 4.8 4508be76ac focus the Claude composer on a background tap
T-227. A tap on empty conversation area now lands the cursor in the
composer. The conversation area is wrapped in a translucent
GestureDetector whose onTap focuses a pane-owned composer FocusNode, so
message links, copy buttons, and the SelectableRegion's selection drags
keep winning their own gestures — only an unclaimed tap reaches us. It's
a no-op while a prompt holds the interaction zone (D-78), so a tap never
pulls focus over an open prompt.

The composer learned to accept an external focus node (the pane owns it,
so it survives composer remounts) and attaches its key handling to
whichever node it's given.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:09:17 +02:00
jpmschweitzerandClaude Opus 4.8 ab5d63debd add Up/Down prompt history to the Claude composer
T-163. Up/Down walk previously-sent prompts, Claude-CLI-style: Up steps
to older entries only once the caret is on the first line (so multi-line
edits move line-by-line first), Down returns toward newer ones and, past
the newest, restores the in-progress draft that was stashed on entry.

History is per-session, owned by the pane (appended on submit, deduping
immediate repeats) and read by the composer. Previewing entries is
suppressed from the draft report, so browsing history never overwrites
the persisted draft (T-228) — and a remount mid-browse restores the real
draft, not a preview. History nav only applies when the slash typeahead
is closed; Up/Down still drive the typeahead when it's open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:04:59 +02:00
jpmschweitzerandClaude Opus 4.8 b4a02dc533 persist the Claude composer draft across UI swaps
T-228. Typing in the composer was lost whenever the input was torn down
and rebuilt — most visibly when a permission prompt takes the composer's
place (D-78), since the prompt card replaces the composer widget and its
TextEditingController went with it.

Hoist the draft out of the widget: ClaudeComposer gains initialValue +
onDraftChanged, and the pane holds a per-session draft map, seeding the
composer on (re)mount and clearing the entry on submit. Keying the
composer by session id means switching sessions in a pane swaps to that
session's own draft. Text and caret both survive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:00:48 +02:00
jpmschweitzerandClaude Opus 4.8 f6552435f4 bootstrap clide-hosted Claude sessions (Epic B)
Per D-83 the clide-hosted stream-json session is the primary dogfood
target — the process clide spawns, so the one clide can fully equip.
Adds a Flutter-free agent_bootstrap module and injects it centrally in
ClaudeSessionOrchestrator.spawn so every hosted session (primary,
secondary, fork, teammate) is handed:

- CLIDE_SOCK (the per-workspace socket, D-70) + CLIDE_WORKSPACE in its
  env, and `clide` prepended to PATH when not already resolvable, so
  `clide …` works with zero manual socket discovery (T-215);
- a system-prompt context note (--append-system-prompt) telling it it is
  inside clide and how to drive the IDE via `clide …`, plus the D-6
  parity contract; the note merges with the team preamble into one
  --append-system-prompt (T-216);
- a Bash(clide:*) allow rule (--allowedTools) so clide calls aren't
  prompted (T-217).

The PATH resolver no-ops when clide is already on PATH (installed case,
T-211) and falls back to ~/.local/bin and the dev native/<os>-<arch>
tree; it degrades gracefully when none is found. Pure helpers are
unit-tested; the fork-args test is made position-independent.

Closes T-214, T-215, T-216, T-217 (under T-208 "Give Claude hands").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:21:34 +02:00
Jeroen SchweitzerandClaude Opus 4.8 da4bc267cb filter files inside ignored directories in the watcher
isIgnored only matched a directory path itself, not files beneath it, so
a recursive watch still surfaced changes inside .dart_tool/, build/, etc.
Linux usually hid this because inotify drops the nested creates; macOS
FSEvents delivers them, so the tree reacted to churn it should ignore.
Check each ancestor segment as a directory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:00:29 +02:00
Jeroen SchweitzerandClaude Opus 4.8 6337bec152 make Claude sidebar sub-tabs keyboard-activatable
The Activity / Team / Config sub-tabs were built on a raw GestureDetector,
so Tab traversal skipped them and Enter/Space did nothing — a gap against
the repo's a11y contract. Switch to ClideTappable (focusable, Enter/Space
activates) wrapped in button + selected semantics, and add a test that
drives the switch via ActivateIntent rather than a pointer tap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:19:35 +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 caca816233 make the editor modal with Vim motions and edits
T-206. A pure motion/edit engine (vim_edit_ops.dart) operates on
(text, selection, register) and returns the new value plus an
insert-mode request — hjkl/w/b/e/0/^/$/gg/G motions, x/dd/D/dw/yy/p/P/
cc/cw/o/O edits, i/a/I/A insert entries, and d/y/c over a visual range.
It's headless, so the whole grammar is unit-tested in isolation.

The editor wires it in: in normal/visual mode bare keys feed the
SequenceMatcher (modified chords bubble to the global handler for the
palette etc.), a fired editor.vim.* intent applies the op count times
and persists through the existing edit path, and vim.mode.* intents go
to the registry. Crucially the EditableText is read-only in command
mode — on desktop printable keys arrive over the TextInput channel
separately from KeyEvents, so swallowing the key event alone wouldn't
stop them typing; read-only does, while our edits still drive the
controller directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:27:15 +02:00
jpmschweitzerandClaude Opus 4.8 ba304a95e6 add keymap multi-chord sequences and a stateful matcher
T-205, the resolver foundation for Vim motions (dd, gg, dw, ciw) and
repeat counts (5j). KeymapBinding now holds an ordered chord sequence
(length 1 for the common single-chord case); `keys:` parses a space-
separated spec into that sequence (D-82). Keymap.resolve keeps the
single-chord fast path; a new stateless Keymap.match answers
exact/prefix/none for a pending buffer.

SequenceMatcher wraps that query with a pending buffer, a repeat-count
prefix (leading digits, 0 excluded since it's the line-start motion),
the d-vs-dd timeout case (flush fires the buffered exact), and broken-
sequence recovery (discard, restart on the last chord). It is headless —
no keyboard reads, no event swallowing — so the editor (T-206) can drive
it from Focus.onKeyEvent and act on the result.

Also drops a stray unused import in the Vim indicator test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:11:59 +02:00
jpmschweitzerandClaude Opus 4.8 0c0a6087c7 normalize quick-open overlay formatting
dart format reflow of a single-statement if; clears a latent
format-gate violation left in the tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:11:38 +02:00
jpmschweitzerandClaude Opus 4.8 61b969d010 add Vim mode service, mode commands, and status indicator
T-207, first foundation piece of the Vim layer (T-65 epic). A
VimModeService (ChangeNotifier) owns the normal/insert/visual mode and
mirrors it into the keymap as mutually-exclusive vim.normal/vim.insert/
vim.visual scope flags. Those flags are the public mode interface: the
editor (T-206) will read them to decide insert-vs-command, and vim.yaml
(T-65) guards bindings with `when: vim.*`. Nothing reaches across the
builtin boundary into the service object.

The layer is gated on the active preset — the builtin.vim extension
ties VimModeService.enabled to app.keymap.preset and re-checks on every
keymap reload, so i/v/Esc never hijack input under non-Vim presets. Mode
commands (vim.mode.{normal,insert,visual}) carry no default binding for
the same reason; only vim.yaml binds keys to them. A status-bar item
shows `-- NORMAL --` etc. while enabled.

Exposes KeymapService on the extension context so the layer can publish
scope flags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 20:59:01 +02:00
jpmschweitzerandClaude Opus 4.8 4aa24a9898 fix dead default keymap from undefined focus intents
default.yaml bound tab/shift+tab to focus.next/focus.previous, intent
ids absent from builtinIntents. parseIntentId returns null, so
KeymapLayer.fromYaml throws — and KeymapService.load catches that and
sets _preset = null. The whole default preset was silently dropped at
boot: palette, quick-open, find-in-files, and zoom bindings never fired.

It went unnoticed because every keymap_service_test injects a synthetic
bundle; the shipped asset was never parsed in a test. Add focus.next ->
NextFocusIntent and focus.previous -> PreviousFocusIntent (Flutter-
provided, for correct Tab widget traversal), and a test that reads every
real assets/keymaps/*.yaml through the loader so a future typo fails CI
instead of disabling the keymap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 20:50:49 +02:00
jpmschweitzerandClaude Opus 4.8 2e323990df fix runtime exceptions surfaced when the editor opens
Now that the editor split actually opens (T-197), it exposed latent
issues, plus a coincidental Claude-session crash in the same log:

- T-203: the _EditorDragHandle's slider Semantics had value +
  onIncrease/onDecrease but no increased/decreasedValue, so Flutter
  asserted on every semantics flush — add them. And opening the split
  reparented the Claude pane (direct child → Column/Expanded), tearing
  down its SelectableRegion mid selection-update ('selectable not in
  this registrar' / 'inactive element'); a stable GlobalKey on the
  workspace primary makes Flutter move the element instead.
- T-202: rate_limit_event.resetsAt arrives as a unix-epoch number but
  was cast `as String?`, throwing in the stream-json line parser. Accept
  a num (epoch) or an ISO string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 18:49:09 +02:00
jpmschweitzerandClaude Opus 4.8 740cc7bb7b actually reveal the editor split on file open (T-197)
The first T-197 fix flipped the wrong lever: it called
activateTab(Slots.workspace, 'editor.active'), but _WorkspaceSlot
renders its editor split off arrangement.editorOpen — not the active
tab — so clicking a file still showed nothing. Call arrangement
.openEditor() on editor.opened / active-changed(non-null), and
closeEditor() on active-changed(null) so the split collapses when the
last buffer closes. Test now asserts arrangement.editorOpen, the lever
the UI actually reads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 18:36:48 +02:00
jpmschweitzerandClaude Opus 4.8 c6a8da1b3a show filter-box placeholders + make the search icon optional
The Search tab's Find mode stacked four ClideFilterBoxes (search,
replace, include, exclude) that all looked identical: every box drew the
magnifying glass and the hint was only a semantics label, never visible
text — so they read as four blank search boxes. Render the hint as
placeholder text while empty, and make the leading icon optional (the
replace + glob fields pass icon: null). General win — every filter box
now shows its placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 17:20:09 +02:00
jpmschweitzerandClaude Opus 4.8 a02fc754a8 merge the pql search panel into the unified Search tab
The pql sidebar panel and the find-in-files tab were duplicate search
surfaces. Consolidate into one Search tab with a mode switch: Find
(content grep), Vault (pql ranked search), Query (PQL DSL), Markdown
(the synced markdown-file listing, keeping focus-highlight + live
refresh). SearchPanelView holds both FindInFilesController and
PqlController; the pql body + result rows move into a reusable
PqlSearchBody. The standalone builtin.pql sidebar tab is removed (one
fewer tab — eases the rail); the pql extension keeps the Backlinks
context panel. No D-79 conflict — grep vs ranked search remain distinct
backends, this is UI consolidation.

Adds the pql builtin's first widget/controller tests (it was untested,
so folding it into the tested Search panel required covering the
Vault/Query/Markdown modes + PqlController).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:45:54 +02:00
jpmschweitzerandClaude Opus 4.8 eb90ba14bc fix sidebar icon rail overflow
The rail was a fixed Row(center, max) — one button per tab — so adding
the Search tab pushed it 54px past its width and threw a RenderFlex
overflow. Center the icons when they fit and scroll horizontally when
they don't (LayoutBuilder + SingleChildScrollView + a minWidth floor),
so the rail stays correct at any tab count. (T-200)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:04:36 +02:00
jpmschweitzerandClaude Opus 4.8 5b3fbb994f move the pin toggle to the pane leading slot
The pin/unpin toggle is a mode control, not navigation — grouping it
with back/forward/jump implied they work alike. Pull it out of
ReaderActionBar into a standalone ReaderPinButton placed before the
title (ClidePaneChrome.leading), leaving the right-hand navigator to
back/forward/jump-to-pin/edit. Applies to all three readers. (T-198)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 15:26:23 +02:00
jpmschweitzerandClaude Opus 4.8 e2b43b2a86 tickets reader: adopt retained ReaderNav + static tab
Bring the tickets detail in line with markdown/decisions (D-81). The
controller loads on 'load' (the channel the retained ReaderNav emits),
the extension reveals the static tickets.detail tab on selection instead
of the per-click uncontribute/contribute churn (the T-188 anti-pattern),
and the view grabs nav.current on mount and wraps in ClidePaneChrome
with a ReaderActionBar — pin toggle left, back/forward + jump-to-pin
right, no edit pencil (tickets are pql records, not files). The
controller drops its now-unused panels dependency.

Also adds the tickets builtin's first tests — the sidebar list
(load/sections/filter/select/empty/error/refresh) and the detail reader
(load, nav, pin, parents/decisions/status) — covering a pre-existing gap
exposed by bringing these files under test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:35:18 +02:00
jpmschweitzerandClaude Opus 4.8 f83e52b818 reader pin UX: push-pin icon, toggle, left/right split
Per user feedback on the reader action bar: use the push-pin glyph (not
the chain/link), make the pin button toggle the pinned state (tap to pin
current, tap again to unpin) via ReaderNav.togglePin, and split the
layout so the pin/unpin toggle sits on the left while jump-to-pin joins
the navigator (back/forward) on the right — left toggles, right
navigates. The action button gains an active (accent) state for the
pinned indicator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:31:51 +02:00
jpmschweitzerandClaude Opus 4.8 0eb7b0df2f fix decision first-click + editor reveal via retained reader nav
Two reveal-on-open bugs:

Decisions opened only on the second click (T-196): the detail view
subscribed in didChangeDependencies, which runs after the tab is
revealed, so the broadcast 'selection' that triggered the reveal was
already gone. Hoist the back/forward history out of per-view State into
a retained per-reader ReaderNav (kernel ChangeNotifier in a
ReaderNavRegistry, D-81). The nav records selections, emits 'load' (the
single channel readers display from), and survives mount/unmount — the
reader grabs nav.current on mount, so the first selection lands. Both
the markdown and decisions readers move to this model; the per-view
ReaderHistoryMixin and the markdown post-frame forward hack are gone.

The editor pane never opened (T-197): EditorExtension contributed a
workspace tab but nothing activated it on editor.open. Add an activate()
that reveals the tab on editor.opened / editor.active-changed; the
view's hydrate() pulls the active buffer on mount.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:45:48 +02:00
jpmschweitzerandClaude Opus 4.8 0bb89a2e58 read user-scope Claude config files via a read allow-list (D-80)
The reader opened repo-local .claude markdown but rejected user-scope
files under ~/.claude with "path outside workspace" — that dir is
global, outside the repo, and files.read was repo-confined (T-102).

Per D-76 the Claude config surface is clide-managed, so files.read now
resolves a path under an allow-list: the workspace root plus trusted
extra read roots (FilesService.extraReadRoots), wired in main.dart to
~/.claude when present. Reads widen; writes stay repo-confined, and the
symlink re-check still refuses a config-root symlink that escapes. Off-
root paths and `..` traversal are rejected as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:08:32 +02:00
jpmschweitzerandClaude Opus 4.8 ade8a88b75 fix markdown reader 404 on absolute paths
resolveUnderRoot joined an absolute input onto the workspace root
(/repo + /repo/x → /repo/repo/x), so files.read 404'd on a file that
exists. The Claude Config tab hands the reader a skill's absolute
SKILL.md path, which hit this. Normalize an absolute input as-is; the
existing containment check still rejects absolute paths outside the
root, so the T-102 boundary is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 08:50:00 +02:00
jpmschweitzerandClaude Opus 4.8 4aed6c12a5 add search-and-replace across files
Builds on the find-in-files engine. A replace engine applies the
query's replacement to each matching file — literal or regex with
capture-group expansion ($1, $&, $$) — and reports per-file, per-line
before/after edits computed with the same logic the apply uses, so
preview and apply never disagree.

The search.replace command previews (no disk writes) or applies
(writing each changed file through the workspace path-safety guard).
The panel gains a Replace field: each match row previews its rewritten
line, and Replace all is gated on a clean git working tree (git is the
undo) plus a confirmation before it writes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 21:41:09 +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 399a4d3a3f add workspace grep engine + search.grep/cancel, editor.open --line
The pure-Dart content-search engine behind find-in-files (D-79): walks
the ignore-pruned workspace, fans files across worker isolates
(Isolate.run) for parallelism, matches each line with a literal
indexOf fast-path or a RegExp, and streams match batches with
cooperative cancellation. No ripgrep dependency; the search.grep IPC
contract is engine-agnostic so an rg accelerator can slot in later.

search.grep returns a searchId and streams search.match / search.done
(or search.error) events, mirroring files.watch; search.cancel stops
an in-flight search. The service reuses the files service's resolved
ignore set so both honour the same ignore_files: layering.

editor.open gains an optional 1-based line argument: it converts the
line to a byte offset and sets the initial selection, enabling
click-to-line from search results.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:31:18 +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.8 d7be5535d5 drive workspace ignore from ignore_files:, add files.walk
Replace the hardcoded .gitignore + .clideignore read with the ordered
ignore_files: chain from .pql/config.yaml (D-4) — the single ignore
knob clide owns (D-3). readIgnoreFiles defaults to .gitignore (plus
.clideignore when present) when the config is absent or malformed, and
honours an explicit [] as "no file-based exclusions".

Add walkFiles + the files.walk command: a recursive, ignore-pruned,
capped flat file listing reused by quick-open (T-51) and the search
engine (T-52). Closes the never-filed ignore-layering placeholder in
files_commands.dart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:08:34 +02:00
jpmschweitzerandClaude 6d0f5f244c add a reader chrome: back/forward, pin, and edit-pencil
The markdown and decision sidebar readers gain a shared action bar. A new
lib/builtin/shared/reader_chrome.dart provides ReaderHistory (browser-style
back/forward stack — push truncates forward), a ReaderHistoryMixin that also
holds a single pin slot, and a ReaderActionBar widget. Both readers push to
history only on external selection; back/forward and jump-to-pin reload
in-place without re-publishing a selection (no bus churn / no decision-tab
re-trigger). The edit pencil opens the current doc in the editor
(editor.open) — the markdown path, or the decision's file_path.

T-189, T-190, T-191.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-31 18:38:39 +02:00
jpmschweitzerandClaude 3c9cc4cbae surface clide-owned commands in the slash typeahead
The composer sourced its slash suggestions only from the CLI probe
(activeClaudeConfig.slashCommands), which never advertises clide-owned
commands, so /resume and /fork were missing from the typeahead. Union
kClideOwnedCommands onto the command source unconditionally — whether a
caller supplies a resolver or the default probe is used — de-duped via a
Set so /clear (in both) appears once.

T-162.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-31 16:17:14 +02:00
jpmschweitzerandClaude d0e169eba2 open the decision reader without re-contributing its tab
The decisions extension tore down and re-contributed the decisions.detail
context-panel tab on every selection, racing the view's own subscription and
leaving the panel unrevealed — so clicking a decision often did nothing. Match
the working ticket panel: contribute the tab once (static), and on selection
just reveal the context panel and activateTab; DecisionDetailView loads via its
existing subscription.

T-188.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-31 16:16:55 +02:00
jpmschweitzerandClaude c56b60622b open markdown files in the reader, not the editor
The files panel and the Claude Config tab called ipc.request('editor.open')
for every file, which targets the editor — so a .md click never reached the
right-side markdown reader (it opens only when something publishes
('builtin.markdown','selection')). Route .md clicks from the files panel
(tree + filtered rows), the Config tab's file-backed rows, and .md wiki links
in the viewer to that channel; non-.md files still open in the editor. Also
remove the dead DaemonEvent fallback that listened for 'editor.buffer_activated'
(the registry emits 'editor.active-changed').

T-187.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-31 16:16:34 +02:00
jpmschweitzerandClaude e866da9fd8 capture a forked session's real id from its init event
A --fork-session branch is spawned without --session-id, so claude mints
a new session id that only arrives in the init event; the ManagedSession
was left holding its placeholder. StreamJsonSession now captures
session_id from the first event that carries it and exposes it via
claudeSessionId / sessionIdResolved; the orchestrator folds that back into
ManagedSession.sessionId (idempotent for normal sessions). A fork can now
itself be resumed or forked.

T-185.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-31 12:21:56 +02:00
jpmschweitzerandClaude d0ad8ed4aa stream Claude replies token-by-token from stream_event deltas
A live capture against claude 2.1.150 (both --print and the interactive
stream-json transport clide uses) confirms --include-partial-messages
emits the in-progress reply as stream_event envelopes wrapping Anthropic
streaming deltas — NOT assistant events with partial:true, which is what
T-168 assumed, so that handler never fired and streaming was inert.

Replace it: accumulate content_block_delta text per message id (tracked
from message_start, since deltas carry no id) and emit a placeholder under
a stable partial-<id> uuid the controller upserts in place; the matching
single-text-block assistant event reuses that uuid to finalize, while
tool_use / thinking blocks keep their own uuids and append in order. Tests
rewritten against the captured shape; spike doc records it.

T-184.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-31 12:16:52 +02:00
jpmschweitzerandClaude 494a6f13a5 build the Config sidebar tab
The Config sub-tab grows from a static settings table into a browser of
the Claude environment: the pinned settings table stays, and below it
expandable accordions list the full (never-truncated) sets of skills,
agents, commands, hooks, permissions (grouped + colour-coded by allow/
ask/deny), and MCP servers. File-backed entries are clickable and open
their .md via editor.open. ClaudeConfig gains agents, hooks, and
mcpServers loaders plus path fields on skills/commands, kept live by the
existing .claude file watcher.

T-183.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-31 11:30:02 +02:00
jpmschweitzerandClaude a0636c2e5a add the team chat inbox over the broker
Renders broker traffic as a chat timeline and makes the user a first-class
participant. The broker grows a Stream<TeamMessage> and a recipient field,
auto-registers a virtual `user` member, and gains sendAsUser. A Flutter-free
TeamChatModel (owned by the orchestrator) accumulates the feed and exposes
postAsUser with @-routing (a new at_commands helper mirroring slash) and an
optional interrupt that cancels the target's turn before delivery. One model
backs two surfaces: a compact cockpit widget that pops out into a full
workspace chat pane. CLI parity via clide.team-chat.open / .post.

T-180.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-31 10:49:04 +02:00
jpmschweitzerandClaude e3c0b0146b fork a Claude conversation into a new pane
Adds fork-into-a-pane: /fork in the composer, a roster Fork button, and a
clide.agent.fork command all branch a session via
--resume <source> --fork-session, so the branch gets its own claude
session id and diverges without touching the original. SpawnSpec/
ManagedSession gain forkSourceSessionId; the orchestrator selects the
fork argv via a new forkSessionArgs helper; the session host opens the
fork as a new secondary pane.

The branch's real claude session-id (assigned by --fork-session, arriving
in the init event) is not yet captured back — tracked as T-185.

T-172.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 23:34:50 +02:00
jpmschweitzerandClaude 2888efbe5f add per-agent permission-mode badge to the cockpit roster
Each roster row shows a D/A/P mode badge reflecting the session's live
permission mode. Click cycles the safe trio default -> acceptEdits ->
plan and sends a set_permission_mode control_request to that session
(mirrors interrupt(); fire-and-forget). bypassPermissions is a footgun,
so it is reachable only on Shift-click and behind an inline confirm. A
clide.agent.set-permission-mode command gives the CLI parity.

T-181.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 23:25:07 +02:00
jpmschweitzerandClaude 4220d97a49 add the Claude team cockpit: roster controls + live task list
The meta sidebar's Team tab becomes a control surface for clide-managed
agents instead of a read-only roster. Each row gains show/hide, mute,
close, and inject-a-message; a live task list renders from the broker
with reassign. The broker grows a Dart change-stream (kept Flutter-free
for dart test) plus tasks/reassign; the orchestrator gains mute/unmute,
injectMessage, and member-name session resolution. Every new UI action
has a matching clide command (D-6 parity).

T-171.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 23:11:22 +02:00
jpmschweitzerandClaude 80401a3228 make conversation card actions keyboard-reachable
The copy button and custom message actions only rendered on hover, so
they were unreachable by keyboard or assistive tech. Keep them in the
tree always — revealed via opacity on hover OR focus — and route each
through ClideTappable (Tab traversal + Enter/Space activation) with a
Semantics button label and onTap so AT can discover and invoke them.
alwaysIncludeSemantics keeps them in the semantics tree while hidden.

T-174.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 23:09:57 +02:00
jpmschweitzerandClaude c5e58733a2 render typed tool cards and live session status from stream-json
The conversation pane now exploits the structured stream instead of
dumping tool input as JSON. ConversationController indexes tool_use by id
so a tool_result pairs back to its call and renders the Edit/Write diff or
is_error failure in place; per-tool bodies (Bash command+output, Read/Grep
file/query) reuse the shared renderers factored out of the permission
card. SessionStatus gains cost + contextWindow + rate-limit, read straight
off the init/result/rate_limit_event events, so the in-pane status line
reflects live state without the config probe.

Partial-message streaming is wired behind --include-partial-messages but
its event shape is unverified against the live binary and degrades to a
no-op if it differs — see T-184.

T-168.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 13:44:19 +02:00
jpmschweitzerandClaude f6b88f503f retire tmux for Claude sessions
Session lifecycle now runs entirely on the stream-json model: argv
selection picks --resume <id> for an existing transcript and
--session-id <uuid> for a fresh one, and the managed-session orchestrator
owns spawn/close. With the transport off tmux, remove the tmux session
lifecycle (reaping, kill-all-for-repo) and the tmux-polling team observer;
kill-all-sessions now closes sessions through the orchestrator. Team
membership is orchestrator-driven since the coordination broker landed.

Amends D-41 (tmux persistence -> --resume). T-167.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 13:05:41 +02:00
jpmschweitzerandClaude 7b9861a097 give the status-bar context slot a flexible share
StatusbarHost laid every item at intrinsic width, so once the focused-pane
context line grew long (a model/mode/context/skills summary) the row's
content exceeded the bar width and overflowed instead of letting the slot
shrink. Add an opt-in flex factor to StatusItemContribution; the host wraps
flex>0 items in Flexible(loose) so they yield width when the bar is tight,
and the marquee then receives a bounded viewport and scrolls. Drops the
fixed maxWidth cap on the pane-context item.

T-160.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 13:04:59 +02:00
jpmschweitzerandClaude 4761b5c12d escape the NUL in the Other-choice sentinel
_kOther carried a literal NUL byte so a real option labelled "other"
could never collide with the sentinel. The raw byte made the source
read as binary: git showed a binary diff and grep/file treated it as
data. Write it as a unicode escape instead — identical runtime value,
plain-text source again.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 12:47:45 +02:00
jpmschweitzerandClaude d5bf831e1a suppress duplicate file_path on Write/Edit permission cards
Claude often sends the file path itself as the tool description for
Write/Edit. The card body already renders that path, so printing the
description line above it showed the same path twice. Suppress the
description when it just repeats file_path.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-30 12:46:48 +02:00
Jeroen SchweitzerandClaude 6e4c3c4bf4 seed resumed Claude session from the transcript tail
claude --resume keeps the model's prior context but emits no past
turns over stream-json, so the pane was visually empty until the
user sent a new prompt. The orchestrator now reads the last 256 KB
of the on-disk transcript JSONL when SpawnSpec.resume is true and
seeds the ConversationController with the parsed items before the
stream subscription starts. Best-effort: missing or unreadable file
just falls back to the previous empty-pane behaviour.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-28 10:17:32 +02:00