Commit Graph
297 Commits
Author SHA1 Message Date
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 c5e54ee0ac chore(plan): close T-439 (login-shell PATH verified via Linux menu launch)
Acceptance gap confirmed closed: a make-install build launched from the
Linux application menu (not a terminal) resolves the login-shell PATH —
pql/git/claude/PTY children find user-installed tools. macOS Homebrew
path remains code-correct; a separate bug will be filed if it surfaces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:03:19 +02:00
jpmschweitzerandClaude Opus 4.8 02950cd853 chore(plan): close T-437 (/clear fix verified live in GUI)
Live-confirmed on CLI 2.1.177: /clear clears the primary pane to an
empty conversation without exiting code 1. The await-death teardown fix
(081678e) holds in the running app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:59:36 +02:00
jpmschweitzerandClaude Opus 4.8 ffcf17a136 chore(plan): close T-438 (web fence done); file T-440 (Playwright e2e follow-up)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:57:49 +02:00
jpmschweitzerandClaude Opus 4.8 03d053274e fix(env): one login-shell-derived PATH for every spawned tool (T-439)
Desktop/dock-launched clide inherits a sparse PATH (no ~/.local/bin, brew,
nvm, …), so pql/git/claude and PTY tools went missing. T-347 fixed only the
toolchain/pql path on Linux; env.dart's expander was still macOS-only and
claude/PTY/git used the raw PATH — the breakage recurred per spawn site
because there were three divergent expanders.

Consolidate into one resolver (lib/src/env/shell_env.dart):
- primeLoginShellPath(): probe the user's real login shell once at startup
  (`$SHELL -l -c`, sentinel-framed, bounded timeout, graceful fallback to the
  process PATH). Captures the user's actual PATH, not a hardcoded guess.
- expandToolPath(): the canonical merge (moved from toolchain_paths, which
  re-exports it for its tests) — unions the well-known user/local bin dirs.
- resolvedToolPath(): currentSearchPath() + expandToolPath, the single call
  every spawn site uses.

Routed through it: PTY children (registry.dart now overrides PATH), git
(env.dart → operations.dart), the toolchain probe (toolchain_paths), and
hosted claude (agent_bootstrap). Primed in main.dart's !kIsWeb boot. Deleted
the macOS-only env.dart copy and the cli_install copy.

Tests: new shell_env_test (probe + every fallback + merge); env_test and
cli_install_test updated to the consolidated surface. analyze clean, web wasm
build still green, make test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:46:11 +02:00
jpmschweitzerandClaude Opus 4.8 ca08c2a17d feat(web): fence dart:ffi behind web stubs so the WASM build compiles (T-438, D-100)
`flutter build web --wasm` had been broken since the tree-sitter/PTY dart:ffi
pivot. Per D-100 (resolving Q-50: keep the web "happy accident" alive), every
native binding now sits behind a `dart.library.ffi` conditional import with a
graceful web stub. Desktop builds are unchanged — no fidelity loss; the web
target degrades (no terminal, native git, or syntax highlighting).

Discriminator is `dart.library.ffi`, not `dart.library.io` — dart2wasm provides
dart:io, so FFI is the only blocker.

Fences:
- PTY: pty_session → pty_backend_io / pty_backend_web (stub throws).
- tree-sitter: pure types → syntax_result.dart; tree_sitter_service is now a
  facade over _ffi/_stub; tree_sitter_boot_io/stub fences TreeSitterLib.init().
- watchdog: watchdog_windows_stub (all -1 sampler).
- claude ABI probe: native_abi_io/stub (was `dart:ffi show Abi`).
- testmode fd-check: fd_check_io/stub.

Also dart2js-safe: the 64-bit FNV literals in session_naming.dart + paths.dart
(the dual JS fallback rejected them) — split into 32-bit halves, dropped a
no-op 64-bit mask. Desktop/wasm hash values unchanged.

CI: added a `web-wasm` job (flutter build web --wasm) so the fence can't rot.
Two FFI-constructing tree-sitter tests import _ffi.dart directly (the analyzer
resolves the conditional facade to the stub branch).

Verified: `flutter build web --wasm` → built; `flutter analyze` clean;
`make test` green. Full Playwright e2e harness wiring is the tracked follow-on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:31:24 +02:00
jpmschweitzerandClaude Opus 4.8 967db2f8d9 chore(governance): sync DQR index for D-100 / D-32 / Q-50
Regenerated governance/README.md index (pql decisions sync): D-32
retitled, D-100 added, Q-50 moved to Resolved. Belongs with 4bba2ef.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:36:53 +02:00
jpmschweitzerandClaude Opus 4.8 4bba2ef2f0 D-100 (fence dart:ffi) + amend D-32 (GitHub Actions); close T-384
- D-32 amended: Gitea-primary/not-activated → GitHub Actions, active
  (Linux test + Windows ConPTY/soak + release; web-WASM e2e withheld).
  The staged Gitea pipeline was never activated and is gone.
- Q-50 resolved → D-100: FENCE the web/WASM target. Every dart:ffi
  importer goes behind a conditional-import facade with a web stub so
  `flutter build web --wasm` compiles; desktop fidelity untouched. Keeps
  the web "happy accident" alive as a hopeful future target per user.
- T-438 filed for the fence implementation (12 ffi importers + CI wasm
  compile gate + re-enable e2e/ui targets).
- T-384 closed (done): Gitea premise OBE, scripts repointed (2026-06-12),
  D-32 reconciled; the dead e2e targets delegated to T-438.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:29:11 +02:00
jpmschweitzerandClaude Opus 4.8 081678e2f7 fix(claude): /clear no longer kills the session on CLI 2.1.177 (T-437)
/clear tore the session down and respawned on the same deterministic
--session-id BEFORE the old claude process had actually exited. The
orchestrator's close() ran conversation.dispose() unawaited and kill()
only sent SIGTERM without awaiting exitCode, so the respawn raced a
still-alive holder of the id — claude 2.1.177 rejects it as "Session ID
… is already in use" and exits 1.

Root cause confirmed from clide's own crash log + isolated probes against
2.1.177: the id frees the instant the holder dies (SIGTERM cleans the new
~/.claude/sessions/<pid>.json registry), so awaiting real death is the
fix — and it preserves T-268's deterministic-id continuity (chosen over
minting a fresh id, which would change the continuity model).

- stream_json_session: kill() awaits exitCode (SIGTERM → 2s → SIGKILL →
  await); dispose() idempotent (shared cached future); new
  SessionEnd.reason getter (last non-empty stderr line, capped).
- session_orchestrator: close() awaits session.dispose() so teardown
  returns only once the process is truly dead, before clear + respawn.
- claude_pane: surface end.reason in the status line — no more opaque
  "code 1".
- session_naming: correct the stale clearSessionTranscript doc (real
  sidecar is the shared memory/ dir) + the await-death precondition.
- tests: close() blocks until process exit; SessionEnd.reason.

CLI 2.1.177 re-probe (folded-in scope): sessions/ registry characterized
(PID-keyed, cleaned on exit); init cache auto-refreshes; advertised
slash_commands show no routing-table drift. No further code change needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 15:32:02 +02:00
jpmschweitzerandClaude Opus 4.8 708d4c6f95 T-437: fold the CLI 2.1.177 re-probe into scope + acceptance
The init-cache/routing-table refresh and sessions/ registry
characterization are now explicit deliverables of T-437, not a
separate follow-up, with updated acceptance criteria.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 13:31:55 +02:00
jpmschweitzerandClaude Opus 4.8 8f962226a0 file T-437: /clear kills the Claude session (exit 1) on CLI 2.1.177
Regression of T-268's /clear mechanism. /clear deletes the transcript and
respawns with the same deterministic --session-id, but claude 2.1.177 now
tracks session ids in ~/.claude/sessions/<pid>.json (+history.jsonl) beyond
the per-project transcript clide purges, so the id reads as in-use and
claude exits 1 at startup validation. Codebase only probed <=2.1.175.

Hypothesis is strong but unconfirmed: the pane shows an opaque "code 1" and
swallows claude's stderr — capturing it is fix step 1. Filed high with
ranked fixes (mint fresh id on clear; or clean the registry; surface stderr).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 13:23:09 +02:00
jpmschweitzerandClaude Opus 4.8 d42fdea23a chore(plan): close T-433 + epic T-425; cancel duplicate breakdown T-426-430
T-425 had a pre-existing breakdown (T-426-430) I didn't check for and
re-filed as T-432-436, which is what got implemented. Cancelled T-426-430 as
duplicates (each notes its implemented twin) and closed the epic — the
crash-survivable logging / observability work is complete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:34:44 +02:00
jpmschweitzerandClaude Opus 4.8 514feefccf chore(plan): mark T-436 (CI crash-evidence artifacts) done
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:59:05 +02:00
jpmschweitzerandClaude Opus 4.8 f40be4f758 feat(ci): capture crash evidence as artifacts when a run wedges (T-436)
Closes the observability loop: the log + breadcrumb + watchdog files are now
collected by CI so a wedged run leaves downloadable evidence instead of
nothing.

- logDirectory(): CLIDE_LOG_DIR overrides the per-platform default, so CI can
  point the logs at an uploadable workspace dir (and tests at a temp dir).
  Now takes an injectable env map; tested.
- test_app.dart: when CLIDE_LOG_DIR is set, the testmode harness tees its
  logger to a FileLogSink + spawns the watchdog (off by default — normal
  run-testmode keeps the stderr-only path, no isolate). _say breadcrumbs each
  test into the file.
- conpty_orphan_probe.dart: with CLIDE_LOG_DIR set it passes a verbose PtyLog,
  so when soak-conpty-kill.ps1 force-kills the parent, the reader/waiter
  isolates' LAST crumb is fsynced to disk — naming what the wedged isolate was
  doing at the instant of death.
- bundle-smoke job: runs the real release app with CLIDE_LOG=debug +
  CLIDE_LOG_DIR, uploads clide-logs (watchdog heartbeat/sample + FileLogSink)
  in an always() step.
- windows-soak kill-probe job: sets CLIDE_LOG_DIR, uploads the FFI crumbs.

Coverage gate 95.08%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:58:48 +02:00
jpmschweitzerandClaude Opus 4.8 16953653eb chore(plan): mark T-435 (crash-diagnostic watchdog) done
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:16:01 +02:00
jpmschweitzerandClaude Opus 4.8 b7d6d6f6f2 chore(plan): mark T-434 (PTY FFI breadcrumbs) done
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:57:12 +02:00
jpmschweitzerandClaude Opus 4.8 1faa047393 release: sync licenses.yaml self.version to 2.5.0
gen-build-info auto-syncs assets/licenses.yaml `self.version` from pubspec on
every build/run/test; this is the generated catch-up to the 2.5.0 cut (it ran
during the coverage build). Also persists the T-432 done status.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:30:24 +02:00
jpmschweitzerandClaude Opus 4.8 85cc34e09c feat(log): crash-survivable FileLogSink + dev/prod verbosity toggle (T-432)
First increment of the observability epic (T-425), the productive pivot after
the ConPTY freeze refused to reproduce on CI: if we can't reproduce it, make
the next occurrence leave evidence.

- FileLogSink (lib/kernel/src/file_log_sink.dart): synchronous, crash-survivable
  LogSink. Appends each record as one JSON line to a size-rotated file; fsyncs
  warn/error + risky-source (pty/ffi/conpty/watchdog) records immediately so the
  last breadcrumb is on disk before a hard death, batches the rest on a timer.
  Never throws. Flutter-free → unit-tested under dart test against a temp dir.
- logDirectory() (paths.dart): persistent per-platform log dir (LOCALAPPDATA /
  ~/Library/Logs / $XDG_STATE_HOME) — durable across reboot, unlike the
  ephemeral socketDirectory.
- resolveLogLevel() (log.dart): the requested dev/prod toggle. CLIDE_LOG
  dart-define → CLIDE_LOG env → app.log.level setting → warn(release)/info(debug).
  Lenient parse; an invalid source falls through.
- Boot wiring (facade.boot + main.dart): FileLogSink leads the sink chain (so a
  crash records before the volatile stderr/ring sinks) and the resolved level
  sets Logger.minLevel.

Tests: FileLogSink (JSON shape, error/stack, rotation cap, append-across-restart,
timer-cancel), resolveLogLevel precedence + fall-through, logDirectory per-OS.
Coverage gate 95.11%.

Follow-ups under T-425: live toggle CLI/command/chip (T-433), FFI breadcrumbs
(T-434), watchdog isolate (T-435), CI artifact wiring (T-436).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:29:41 +02:00
jpmschweitzerandClaude Opus 4.8 ebea0dd846 docs(pty): correct the coverage-ignore rationale; note T-431 + windows.yml gap
A testability audit of the ignore span found the original comment overclaimed
("none of it can execute on Linux") and credited the wrong test file. Reality,
verified fragment by fragment:

- The span is excluded at FILE granularity but is not 100% syscall — _Coord /
  _StartupInfoExW struct packing and write()'s empty-guard are pure transforms
  that could be unit-tested on Linux if extracted from the binding-touching
  methods. Tracked in T-431 (also covers the mirror gap in native_pty.dart's
  POSIX marshalling).
- The pure helpers are tested by windows_pty_args_test.dart (not _test.dart).
- The FFI path's BEHAVIOUR is validated on windows-latest (real ConPTY spawn),
  but windows.yml collects no coverage — so there is intentionally no line-
  coverage metric for this span anywhere; correctness rests on that functional
  suite + the VM soak, not on coverage.

Comment-only; no code or coverage change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 00:38:29 +02:00
jpmschweitzer faacb732bc T-424: note pre-VM progress (clamp + timeout landed; FFI deferred to VM) 2026-06-14 20:55:29 +02:00
jpmschweitzerandClaude Opus 4.8 154c2c8e43 file T-424 (ConPTY Job-Object reaping) + T-425 logging epic
Follow-ups from the Windows test-freeze analysis:
- T-424 (bug, high): place each WindowsPty child in a kill-on-close Windows
  Job Object so the child AND its conhost.exe are reaped on session/test-
  process exit (rank-1 freeze culprit). Sibling ConPTY-teardown fixes noted
  in the description.
- T-425 (epic, high): crash-survivable logging & observability, so the next
  freeze leaves on-disk evidence. Children: T-426 FileLogSink, T-427 FFI
  breadcrumbs, T-428 watchdog isolate, T-429 dev/prod verbosity toggle,
  T-430 testmode/CI wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:16:55 +02:00
jpmschweitzerandClaude Opus 4.8 bb0d78f8de Q-51 + T-422 epic: unify workspace lifecycle on one fenced primitive
The status-bar branch bleed (T-421) is a symptom of a deeper gap: there
is no single "open workspace X" primitive — only project.open() (in-place,
same process, shared daemonBus) and newWindow() (blank detached process,
no repo arg, no env scrub). T-367 and T-269 are the same root.

- Q-51 (architecture): unify on WorkspaceService.open(root, target);
  open question of whether in-place switching survives at all vs a
  strict workspace⇒window⇒process⇒socket⇒bus⇒session-id 1:1 mapping.
- T-422 epic owns the unification; T-421 reparented under it; T-423
  builds the primitive and routes all entry points through it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:47:33 +02:00
jpmschweitzerandClaude Opus 4.8 07b5133e99 file T-421: status-bar git branch bleeds across parallel windows
Bug: the status-bar branch sometimes shows another open window's
branch. Filed high-priority with investigation notes — contradicts the
T-269 cross-window isolation invariant. Two candidate root causes
captured (shared in-memory DaemonBus vs inherited CLIDE_SOCK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:31:26 +02:00
jpmschweitzerandClaude Opus 4.8 5a21ea949a workspace tab cycle commands + ctrl+pagedown/up (T-405 part 1)
Add workspace.tab.next / workspace.tab.previous commands that cycle the
Slots.workspace tab strip with wraparound (no-op under two tabs), bound
ctrl+pagedown / ctrl+pageup across every preset via defaultBindings. Single-
chord, so no global matcher needed. Activating a tab also focuses the
workspace slot.

Part 2 (vim gt/gT) is deferred — it needs the global multi-chord matcher
T-404 introduces. T-405 stays open for that follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:06:36 +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 9a44001fbf sync pql changelog (T-407 decisions, T-406 export)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:43:48 +02:00
jpmschweitzerandClaude Opus 4.8 b3136cab79 resolve T-407 no-active-buffer behavior: :q no-ops when no editor
When no editor buffer is active (tree/conversation focused, split closed),
:q / :w / :wq / :x / ZZ no-op for v1 — no other pane touched. Closes the
last open question on the ticket; v1 stays strictly editor-targeted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:41:30 +02:00
jpmschweitzerandClaude Opus 4.8 0e715f871d record :q close-active-tab decision on T-407
:q closes the active editor tab and focuses the next; the last :q
collapses the split for free via the existing editor.active-changed{id:null}
→ arrangement.closeEditor() path, so :q never dispatches command:editor.close.
The one gap is registry close() re-focusing first-not-next; recommend the
UI-side next-tab activate-then-close.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:39:53 +02:00
jpmschweitzerandClaude Opus 4.8 68acb89e83 refine T-403 vim cross-pane tickets + file workflow-card polish (T-419)
Ran a two-agent parallel workflow to refine the four T-403 children against
the actual code, appending a sharpened scope / acceptance-criteria / files /
dependencies / open-questions block to each:
- T-404 (ctrl+w window family), T-405 (tab cycle + gt/gT),
  T-406 (normal-mode list/scroll nav), T-407 (ex `:` overlay).

Both agents independently surfaced the shared structural blocker — no global
multi-chord SequenceMatcher exists today (the global key path is single-chord;
only the editor has a matcher) — and a recommended sequencing, now recorded as
a coordination note on the parent T-403 (build the matcher once; T-406 first).

Also files T-419 under the UI tracker (T-276): keep the workflow run card's
agent rows + usage visible while collapsed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 22:07:13 +02:00
jpmschweitzerandClaude Opus 4.8 b399acedbf surface Claude Code Workflow runs in convo + sidebar (T-416)
A `Workflow` tool-use launches its multi-agent run in the background and
returns immediately; the real fan-out arrives out-of-band on stream-json
`type:"system"` task_* events (task_started / task_progress / task_updated /
task_notification) keyed by the launching tool-use id — which clide was
dropping. (Wire shape captured by two live stream-json probes; recorded on
the ticket.)

- workflow_run.dart: a pure, Flutter-free WorkflowRun/WorkflowAgent model
  that folds those events (phases, per-agent start→progress→done deltas,
  usage) into a snapshot.
- StreamJsonSession recognises the events, accumulates a
  Map<toolUseId, WorkflowRun>, and exposes `workflows` + `workflowsStream`.
- A `Workflow` tool-use with a live run renders a dedicated run card —
  phase groups, per-agent rows with spinner/check status, usage, and the
  script — falling back to the generic tool card pre-progress or on reload.
  The run breaks the activity cluster so it's always first-class (like T-342).
- The sidebar Activity tab adds a WORKFLOWS section: one row per run with its
  done/total agent count, tinted by running/done state.

Closes T-416 and the T-410 epic (all children done).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 21:44:41 +02:00
jpmschweitzerandClaude Fable 5 ac21f5cb69 adjust composer typeahead test for the grown owned-command set
T-413 added 'mcp' (among others) to kClideOwnedCommands, which the
composer unions onto the suggestion list (T-162) — '/m' now yields
[mcp, memory, model], so reaching 'model' takes two arrow-downs. The
test's intent (selection moves; Enter completes, never submits) is
unchanged.

Board: T-158 annotated — /usage is answerable headless on 2.1.175,
unblocking its upstream blocker (see T-415).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:11:28 +02:00
jpmschweitzerandClaude Fable 5 0fd8a241db Activity tab: session controls + live usage block (T-415)
The Activity tab gains the power-panel's session strip and a usage block:

- SESSION controls (clear / compact / fork / resume + refresh-usage)
  publish their slash command on builtin.claude/command — the same path
  as typing it, so /clear semantics (and any future confirm behavior)
  live in exactly one place.
- The usage block revisits T-158's "blocked on upstream": probed against
  claude 2.1.175, a forwarded /usage IS answered headless, free
  (num_turns 0), as parseable text. parseUsageText() extracts session /
  week / week-Sonnet percentages (timezone parentheticals stripped); the
  sidebar watches the primary session's synthetic output for
  usage-shaped responses and renders them as a USAGE section. Refresh is
  user-initiated (the control sends /usage) — no polling, no background
  calls (D-64).
- The runtime row gains the session's effort level (T-412's status
  field).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:04:55 +02:00
jpmschweitzerandClaude Fable 5 1674d3021a tint the Claude sidebar tab icon with the Claude accent (T-418)
TabContribution gains an optional iconColor honoured by the icon rail:
full-strength when active/hovered, dimmed (70%) when idle, so the tint
reads as identity without outshouting the active-state border. The
Claude Activity tab sets claudeAccent (#D97757) — nominative use per the
licenses.yaml trademark note (it marks Claude's own panel).

Filed and closed as a try-it-out (user request); trivially revertible if
the accent doesn't land visually.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:30:22 +02:00
jpmschweitzerandClaude 7c4ac182f6 file status-bar styling cohesion bug (T-417)
Ticket-only commit so the pql changelog write-through persists. T-417
captures the font/size/token/vertical-alignment drift across the five
bottom status bar items, filed under the UI tracker epic T-276.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-12 13:25:04 +02:00
jpmschweitzerandClaude Fable 5 52d90be730 own the open-in-clide command family (T-413)
/permissions, /status, /config, /mcp, /agents, /hooks, /memory, and
/help move from the TUI-only notice catalog to clide-owned commands
with real behavior:

- /permissions <mode> sets the mode over set_permission_mode; bare
  /permissions opens a picker in the interaction zone — the same card
  /model and /effort use (kPermissionModes, bypass last and explicit
  per T-181).
- /status → Claude sidebar Activity tab; /config, /mcp, /agents,
  /hooks → Config tab. The pane activates the claude.meta sidebar tab
  and publishes a meta.tab message; the sidebar subscribes and switches
  its sub-tab — the same MessageBus addressing `clide ui open` uses
  (D-6), so the CLI can drive it too.
- /memory opens the workspace CLAUDE.md via editor.open.
- /help renders a local summary card (clide-owned + advertised
  commands) — the CLI's TUI help doesn't exist headless.

The catalog keeps empty-hint entries for these tokens as safety nets if
they're ever removed from owned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:06:10 +02:00
jpmschweitzerandClaude Fable 5 1bdd88f4ab own /effort: respawn-with-resume carrying --effort, picker UX (T-412)
Spike result (probed claude 2.1.175 over stream-json): there is NO
set_effort/set_thinking_effort control subtype — both are rejected. The
lever is the `--effort <level>` spawn flag (low/medium/high/xhigh/max;
settings.json effortLevel is the persisted default). So changing effort
restarts the process: respawn-with-resume keeps the conversation and
carries the flag — the same continuity /clear and /resume already rely on.

- SpawnSpec.effort → orchestrator appends `--effort <level>`.
- claude_pane: /effort <level> validates and respawns (toast explains the
  restart); bare /effort opens a picker; the pane re-applies its effort on
  every later respawn. Invalid level → local notice listing levels.
- ModelPickerCard generalised minimally (title + isCurrent predicate) so
  the effort picker reuses it; effort needs exact matching because `high`
  is a substring of `xhigh` and alias-containment would mis-mark it.
- SessionStatus.effort + StreamJsonSession.noteEffort: the wire never
  reports effort, so the spawner records what it set; status/sidebar read
  it from the normal status stream.
- Routing: effort moves from the TUI-only catalog to kClideOwnedCommands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:56:09 +02:00
jpmschweitzerandClaude Fable 5 02c6dd4cf0 route slash commands: TUI-only builtins become local notices (T-411)
clide forwards composer input to a headless (stream-json) CLI, where the
TUI's interactive commands don't exist. A known-but-TUI-only command
errored raw ("/x isn't available in this environment", rendered as fake
claude prose); an un-advertised one (e.g. /effort on 2.1.175) was worse —
bracket-pasted to the model as literal text, burning a real turn.

Probed claude 2.1.175 for ground truth: the initialize handshake's
slash_commands advertises skills + the headless builtins only; forwarded
local-command output comes back as an assistant message with model
"<synthetic>"; set_effort is not a control subtype; /usage works headless.

- slash_commands.dart: SlashRoute routing table (owned > advertised >
  TUI-only catalog > forward) + kTuiOnlyCommands with clide-native hints
  + tuiOnlyNotice(). One source of truth replacing ad-hoc checks.
- claude_pane._send routes 'unavailable' to a local notice card; nothing
  reaches the session.
- transcript_reader: AssistantTextMessage.synthetic ("<synthetic>" model)
  so CLI-local output is distinguishable; "<synthetic>" no longer
  clobbers the tracked model in SessionStatus (latent /usage bug).
- conversation_view: synthetic output renders as a muted framed "clide"
  card (T-306 styling), never coral Claude prose.
- kFallbackSlashCommands trimmed to the genuinely-headless builtin set —
  it doubles as the router's advertised fallback, and the old list's
  TUI-only entries would have routed to a raw CLI error.

Board (rides this commit): T-414 gains the user's sidebar styling-pass
note; T-416 filed — surface Claude Code Workflow runs in convo/status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:25:19 +02:00
jpmschweitzerandClaude Fable 5 91c5501cf3 file the Claude power-control-panel epic (T-410 + five stories)
Design: capture every TUI-only harness slash command properly instead of
leaking the CLI's raw "isn't available in this environment" error, and
grow the Claude sidebar into an interactive control panel.

Grounded in the version-keyed initialize probes (~/.config/clide/claude):
the advertised slash_commands list is the authoritative "forwards safely"
set; TUI-only builtins are absent from it. Three layers: a declarative
routing table (forward/owned/unavailable) replacing kClideOwnedCommands,
a reactive catch-all that renders unknown TUI-command errors as hint
cards, and D-6 parity controls in the sidebar Config/Activity tabs
(model/effort/permission pickers, session controls, usage block).

T-411 routing+capture, T-412 /effort spike, T-413 open-in-clide family,
T-414 Config-tab controls, T-415 Activity controls + usage (revisits
T-158).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:59:42 +02:00
jpmschweitzerandClaude Fable 5 1d6682df18 intercept /model: arg sets the model, bare opens a picker (T-408)
Typed into the conversation view, /model was forwarded to the session's
stdin as message text — the CLI's interactive picker only exists in its
own TUI, so nothing happened. clide now owns it like /clear//resume//fork
(T-156).

/model <name> sends a set_model control_request (verified against
claude 2.1.175: subtype accepted alongside set_permission_mode;
"default" resets to the CLI's configured model) with an optimistic
status merge, rolled back with a toast if the CLI rejects the name.
Bare /model swaps a picker card into the interaction zone (D-78) —
numbers / arrows + Enter / Esc, mirroring the prompt card's shortcuts.

The model list comes from the `initialize` handshake response, which
the session now always sends — the spike verified it is side-effect-
free, and it previously went out only when MCP servers were hosted.
Until the response lands the picker falls back to the stable aliases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:04:29 +02:00
jpmschweitzerandClaude Fable 5 073c0893b5 release v2.4.1
Patch release: double-Shift quick-open (new in 2.4.0) no longer fires
on chorded Shift, so Shift+; types a colon again in the editor and the
Claude composer (T-409).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:49:54 +02:00
jpmschweitzerandClaude Fable 5 8966a159db require a clean release for double-tap modifier detection (T-409)
Typing Shift+; opened quick-open instead of a colon. Two flaws in the
T-341 detector: it counted a tap on the Shift keydown (so a chorded
press could complete the gesture before the chord key arrived), and it
relied on the chorded key bubbling to the root KeyboardListener to
break the gesture — but a focused editor or text field consumes that
event, so the tracker never saw it.

The tracker now models press/release: a tap is a press with no other
key going down while the modifier is held, and the gesture fires on
the second clean release. The root shell feeds it from a
HardwareKeyboard handler, which observes every event before focus
dispatch regardless of who consumes it, and treats a modifier pressed
while a non-modifier is already held as a chord.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:49:20 +02:00
jpmschweitzerandClaude Fable 5 395a125241 file the vim cross-pane interaction tickets (T-403..T-407)
Review outcome: the vim layer (T-65) is editor-only today — the mode
flags are global but every binding drives the focused editor, and the
tree/board/git/conversation panes have no keyboard handling at all.
T-403 carries the findings; children map vim idioms onto existing
panel commands (ctrl+w family incl. ctrl+w o → focus mode), add the
missing workspace tab cycling (gt/gT + ctrl+pgup/pgdn for every
preset), introduce nav intents for pane-local j/k navigation, and the
minimal ex command line the mode service already reserved space for.

Also sweeps in the regenerated governance index (D-96..D-99 listed,
Q-23 moved to resolved) from the decisions sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 05:23:15 +02:00
jpmschweitzerandClaude Fable 5 b9c0ec4dea resolve Q-23: no-install ssh-exec remote model (D-96..D-99, T-330)
The user picked the zero-footprint model for SSH-remote workspaces:
stock OpenSSH only, nothing clide-specific installed on the remote.

- D-96 — footprint: ssh -tt PTYs, ControlMaster exec channels, polling
  watcher, RemoteExecutionContext seam; D-56's single-process rule is
  strengthened (no clide process anywhere but the local app).
- D-97 — ssh://[user@]host[:port]/path naming; auth delegates wholly
  to system ssh in BatchMode; Windows is a known v1 gap.
- D-98 — remote-tool contract: shell+git required, pql/claude degrade
  behind banners, one batched connect preflight.
- D-99 — session + per-workspace state identity re-keys on
  (host, repo), amending D-41/D-77; local keeps its identity.

T-330 closes; T-336 expands into T-398..T-402 (connection manager,
ExecutionContext sweep, remote PTY, polling watcher, preflight) with
the blocker graph encoded in the board.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 05:17:26 +02:00
jpmschweitzerandClaude Fable 5 be28bdef82 record the SSH-remote footprint decision menu in Q-23 (T-330)
The Phase-0 spike's core pick — no-install ssh-exec vs auto-pushed
remote agent — is the user's call (they've said they don't want to
manage remote installs; the agent model buys inotify + a stateful
backend). Q-23 now carries the 2026-06-12 triage block with both
options, the agent-model sub-questions, the remote-tool-contract
D-record need, and the latency-probe evidence gap (no sshd reachable
from the dev box). T-330 is annotated blocked-on-user; the
model-independent backbone phases proceed meanwhile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 03:05:56 +02:00
jpmschweitzerandClaude Fable 5 051ceea3b2 route DaemonClient through a DaemonTransport seam (T-331)
The UI's backend client connected straight to the workspace unix
socket, hard-coding the local shape. It now talks JSON-lines through a
DaemonTransport (new lib/src/ipc/transport.dart, Flutter-free), with
LocalSocketTransport reproducing today's connect byte-for-byte — zero
behavior change, proven by the untouched client test suite plus new
seam tests driving the client over an in-memory transport.

This is the slot the SSH-remote backend (T-329/Q-23) plugs into:
request correlation, reconnect/backoff, and event forwarding live
above the seam and won't change when the endpoint is remote.
main.dart's swapIpcServer becomes swapBackend per the same plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 03:04:58 +02:00
jpmschweitzerandClaude Fable 5 c3dd1d3e3f capture CSI intermediate bytes; stop bare-final mis-dispatch (T-123)
_consumeCsi silently discarded intermediate bytes (0x20-0x2f), so an
intermediate-bearing sequence dispatched on its bare final byte —
`CSI 5 SP @` (VT420 scroll-left) ran as "insert 5 blank characters",
and `CSI Ps SP q` (DECSCUSR) could collide with any future bare-q
handler. The parser now records intermediates on the CSI scratch
object and routes any sequence carrying them to unknownCSI, since no
intermediate form is implemented yet.

Implementing DECSCUSR itself (cursor shape + renderer support) is
filed as T-397.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:55:58 +02:00
jpmschweitzerandClaude Fable 5 f8958fd372 split escape parser handlers into part files (T-123)
parser.dart (1139 LOC) now keeps only the core — byte queue, dispatch
tables, ESC/CSI consumers — at 373 LOC. The handler groups move to
mixins in part files of the same library, so every private member
stays library-scoped and no public surface is added:

- csi_handlers.dart — cursor/erase/scroll ops, DA/DSR, margins, tab
  clear, repeat, window manipulation
- sgr_handlers.dart — SGR incl. the guarded 38/48 extended-color path
  (T-369)
- mode_handlers.dart — ANSI + DEC private mode set/reset
- osc_handlers.dart — OSC string parsing + dispatch

An abstract _EscapeParserBase carries the shared state (handler sink,
queue, token bookkeeping, the reusable _Csi scratch) the mixins are
`on`. All 76 parser tests (and the rest of the terminal suite) pass
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:50:17 +02:00
jpmschweitzerandClaude Fable 5 31df537770 split claude_meta_sidebar.dart into meta_sidebar/ (T-395)
The 1192-LOC sidebar monolith now keeps only its lifecycle — stats
polling, team membership streams, primary-session binding, broker
subscription, inject + accordion state — and switches between
stateless, props-driven tab views under meta_sidebar/:

- models.dart — SidebarTab/ConfigSection/ConfigPermKind enums, the
  MetaSection/MetaRow models, and the shared table geometry both
  Activity and Config render on
- activity_tab.dart / team_tab.dart / config_tab.dart — the three
  bodies; accordion expansion stays in the parent (survives tab
  switches) and arrives as prop + callback
- roster_row.dart, permission_badge.dart, task_row.dart,
  tab_strip.dart, icon_button.dart, inject_field.dart — the widgets

Public API unchanged: ClaudeMetaSidebar stays put and SidebarTab is
re-exported from the root file, so all 44 sidebar tests (and
extension.dart) pass without a single edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:38:57 +02:00
jpmschweitzerandClaude Fable 5 7f16016bc3 split lib/app.dart into lib/src/shell/ (T-394)
app.dart was 1187 LOC mixing five concerns. It now keeps ClideApp +
the WidgetsApp root (~60 LOC); the shell moved to lib/src/shell/:

- root_shell.dart — keyboard/intent routing (keymap resolution,
  double-tap modifiers, menu mnemonics), the overlay stack, and the
  welcome overlay
- hat_bar.dart + project_switcher.dart — the window-chrome bar and
  its recents/file-actions dropdown (now in src/shell, not builtin/ —
  they're app chrome, not extension-shaped contributions)
- slot_host.dart — slot mounting, focus-scope integration, the
  per-slot bodies incl. the workspace split + editor drag handle;
  _SlotBody's static title resolver became the shared resolveTabTitle
- layout.dart — the three-column grid, status bar, collapse toggles,
  bottom icon rails

app.dart re-exports RootLayout, SlotHost, StatusbarHost, and
StatusbarCollapseToggle, so every existing import (incl. the three
app-level test files) is unchanged. Pure move + minimal publics
(RootShell, HatBar, ProjectSwitcherButton); full suite green with no
test edits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:29:46 +02:00
jpmschweitzerandClaude Fable 5 a59c3658a9 remove the legacy free-function git API (T-385)
operations.dart carried a full second git operation surface
(gitStage/gitCommit/gitStash/gitPush/...) that duplicated GitClient
verb-for-verb, was kept alive only by its own tests, and hid a latent
pipe deadlock in _applyPatch (stdin written without draining stderr).
The file keeps the genuinely shared plumbing — gitBin resolution,
GitException, validateGitRef, GitLogEntry — which GitClient, the
status/diff readers, and the git command handlers consume.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:16:06 +02:00