Commit Graph
898 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Fable 5 f378be4084 persist the ticket dep-graph export
The pre-commit hook staged the ticket/history tables but left the
deps + idmap exports (the T-398..T-402 and T-403..T-407 blocker
links) unstaged on the previous commit; sweep them in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 05:23:47 +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 6817abaf96 add WorkspaceRef + remote identity on RecentProject (T-332)
The model-independent half of the ssh:// open scheme. WorkspaceRef is
the value type for "where a workspace lives" — a local path or
ssh://[user@]host[:port]/abs/path, with parse/uri round-tripping and a
host:path display form. RecentProject carries host/port/user
(back-compatible JSON: absent keys deserialize as local) so remote
recents survive restarts and render with their host badge.

The remaining T-332 scope — ProjectManager.current off bare Directory,
open() branching, remote resolveProject — is gated on the execution
layer (T-336), which is itself blocked on the T-330 footprint pick;
the epic's blocker graph now encodes that gating.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 03:11:57 +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 05584eb9e5 drop the unused mocktail dep; amend D-25 (T-385)
mocktail was pinned and documented as the IO-mocking strategy, but
after the T-91 coverage drive it had zero imports — every IO seam
ended up with an injected hand-rolled fake instead. D-25 is amended
to record that the hand-rolled-fakes rule covers IO seams too;
licenses.yaml and the lockfile follow. The ptyc binary removal noted
in this sweep landed with the git-API commit (it was already staged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:18:14 +02:00
jpmschweitzerandClaude Fable 5 37f3ad0796 remove the tmux-era TranscriptPublisher class (T-385)
No production code constructed it since the stream-json pivot (D-77)
— only its own test did. The ClaudeConversation bus-addressing
constants stay; the meta sidebar and team panel host still consume
them for member-status messages. The companion finding — the team
roster surfaces listening to TeamMemberJoined events nothing emits —
is real rewiring work, split out as T-396 (drive the roster from
TeamBroker membership, then delete the ghost event types).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:17:37 +02:00
jpmschweitzerandClaude Fable 5 06c2e76be4 remove the dead ColumnHat widget; keep hatHeight (T-385)
ColumnHat was superseded by the hat bar in app.dart and survived only
through a zero-coverage smoke test. Its file also carried the live
hatHeight constant (D-57's 24px hats) consumed by the hat bar and the
menu bar — that moves to widgets/src/chrome_metrics.dart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:17:15 +02:00
jpmschweitzerandClaude Fable 5 21ddecffef trim libc.dart to the symbols the PTY layer uses (T-385)
The fd-passing-era surface — recvmsg with the msghdr/cmsghdr/iovec
struct family, raw read/write, ioctl/winsize, the fcntl non-blocking
helpers — had no callers since the daemon dissolution (D-56);
NativePty binds its own symbols. What remains is what's actually
consumed: socketpair + close (the ClideTestApp harness), errno, the
poll event bits, and the two signal numbers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:16:55 +02:00
jpmschweitzerandClaude Fable 5 401b1e1ce5 delete ToolCheck and the GraphView placeholder (T-385)
ToolCheck had zero callers. GraphView was unreachable — the graph
builtin contributes nothing, so no surface ever built it; the flat
pql-connections ListView it held was never the owned-canvas graph
anyway (T-7 cancelled). The Governance Graph idea (Q-46/Q-49) starts
fresh if it lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 02:16:33 +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
jpmschweitzerandClaude Fable 5 99dc52052a dispose-safe teardown for KeymapService + ClaudeConfig; cover the
claude extension lifecycle

make test-integration failed at widget-tree finalization: the
palette's dispose() clears its scope flag, which during app teardown
runs AFTER KernelServices.dispose() has disposed the KeymapService —
notifyListeners asserted. Scope-flag mutations now use the same
fire-and-forget guard SettingsStore established. Same family in
ClaudeConfig: activation's unawaited load() could notify (and start
watchers on) a disposed notifier when a teardown raced it.

The claude extension's activation lifecycle and command success paths
are now exercised end-to-end through the kernel fixture — the file
entered the coverage denominator with the T-391 failure-path tests,
so per the ratchet discipline the rest of it gets covered too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:55:56 +02:00
jpmschweitzerandClaude Fable 5 4443e1c834 run dart format over the scorpion-fix files
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:40:09 +02:00
jpmschweitzerandClaude Fable 5 41af83f024 repoint the UI harness at the repo root; CI via make targets (T-384)
tools/ui/build.sh and serve.sh still cd'd into the app/ directory the
flattening removed, so make test-e2e / ui-dev / ui-smoke died at the
first line. The staged Gitea workflow had the same stale cd in every
job, plus a coverage gate with no coverage run before it — it now
goes through the make targets (tooling discipline: the make layer owns
env setup) with make test-coverage feeding make coverage-gate.

Fixing the paths exposed the real break: flutter build web --wasm
cannot compile the tree since the dart:ffi pivot (tree-sitter, native
PTY) — dart:ffi does not exist on the wasm target. Fence vs park vs
drop is filed as Q-50; the workflow's e2e job is withheld with a
pointer there, and T-384 sits in review until Q-50 resolves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:39:24 +02:00
jpmschweitzerandClaude Fable 5 bc3c47ee81 make extension activation transactional (T-377)
Three lifecycle gaps, benign among curated builtins but hazardous the
day Tier-6 Lua extensions land: a throw mid-contribution left earlier
contributions mounted while the extension recorded as failed (a retry
then double-applied them); deactivate ignored active dependents; and
the panel/command registries silently clobbered on id collision.

Activation now tracks what it mounted and unwinds it all on failure
(including the extension's own deactivate when its activate had
succeeded); deactivate refuses with a logged warning while active
dependents exist — disable the dependents first; duplicate
contribution/command ids throw, which the transactional path turns
into a clean failed activation with first-wins semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:35:24 +02:00
jpmschweitzerandClaude Fable 5 c31f5bfb14 add Terminal.writeBytes with a persistent UTF-8 decoder (T-373)
The terminal's only ingestion API was write(String), so both byte
consumers decoded per chunk — a multi-byte rune split across PTY
reads (or a tail window starting mid-character, which FileTailFollower
does by construction) rendered as U+FFFD garbage. writeBytes feeds a
per-instance chunked Utf8Decoder that carries partial-rune state
across calls; the terminal pane and the Bash live-tail follower now
use it, and write(String) stays for tests and programmatic writes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:32:13 +02:00
jpmschweitzerandClaude Fable 5 928dede847 return error envelopes from failed claude commands (T-391)
Sixteen claude.* handlers reported ok with an `error` field buried in
the payload — `clide claude.agent.set-permission-mode bogus` exited 0,
so scripts could not detect failure, drifting from the D-6 exit-code
contract every other subsystem honors. Missing/invalid args are now
userError, missing sessions notFound, a missing orchestrator
toolError, and a failed task reassign no longer reports ok:false as a
success. No UI consumer read the old payloads. Table-driven test
walks every failure path asserting non-zero codes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:28:50 +02:00
jpmschweitzerandClaude Fable 5 638869621e remove dead welcome tiles; advertise only real shortcuts (T-383)
Clone-from-git and Start-a-Claude-session were inert onTap: () {}
stubs whose printed shortcuts were never registered — dead UI on the
first screen a new user sees. No advertised dead ends: the tiles are
removed until their flows exist. The tips card was also fiction
(four of six shortcuts unregistered, ⌘ glyphs for a ctrl-based
default keymap) — it now lists six bindings that exist in the shipped
default preset / contributed commands, and the Open-folder glyph
matches the real ctrl+o binding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:24:58 +02:00
jpmschweitzerandClaude Fable 5 d9ae2d7585 surface kernel notifications as toasts (T-382)
The Notifications service had zero widget consumers — anything pushed
through ctx.notify (cli_install's dogfood warnings, install results)
accumulated in a list nothing rendered. The service now takes the
kernel MessageBus and publishes each notification to the toast
channel with mapped severity, so the existing ToastOverlay renders
them; the active list stays for API compatibility. Chose routing over
building a notifications tray nobody asked for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:21:16 +02:00
jpmschweitzerandClaude Fable 5 01c3de37e1 render markdown hard breaks and image placeholders (T-379)
Both node types fell through the inline-span switch to an empty
textContent span: words on either side of a hard break glued
together, and images vanished with no trace. A br now emits a
newline; an img renders a muted italic "[image: alt]" placeholder
(falling back to the src) — no inline network loading in the owned
renderer; live-pane images keep going through clide image show.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:18:41 +02:00
jpmschweitzerandClaude Fable 5 e413380ea9 make settings persistence safe for nested data and crashes (T-376)
Three failure modes in the YAML store: maps nested inside lists (the
documented keymap-overlay shape) fell through _emitScalar to
toString() and corrupted on the next read; writes went straight to
the live file, so a crash mid-write truncated every setting; and a
parse failure silently returned an empty map that the next set()
wrote over the user's file. Maps in lists now emit as YAML flow
mappings, writes are temp-file + rename, and an unparseable file is
preserved as .broken with a warning through the kernel Logger (new
onError hook, wired in the facade).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:16:48 +02:00
jpmschweitzerandClaude Fable 5 5d52694889 serve IPC clients with one await-for read loop (T-372)
The async onData handler never paused its subscription, so pipelined
requests interleaved mid-handler — violating D-72's serial-dispatch
contract — while the shared StringBuffer could re-frame underneath an
in-flight await and the per-chunk utf8.decode corrupted runes split
across reads. One `await for` over a persistent Utf8Decoder +
LineSplitter fixes framing, decoding, and serialization at once.
Tests: two frames pipelined in one write dispatch strictly in order;
a frame split mid-rune across writes decodes intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:13:43 +02:00
jpmschweitzerandClaude Fable 5 bbc6899df2 consume the fork source on first bind (T-375)
widget.forkSourceId took precedence over the fresh/resume logic on
EVERY (re)bind, so /clear in a fork pane re-forked the original
conversation instead of clearing, and /resume re-forked the same way.
The source is now copied into one-shot pane state and cleared after
the first successful fork spawn; later respawns operate on the pane's
own session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:11:17 +02:00
jpmschweitzerandClaude Fable 5 51957eb0ac coalesce concurrent session spawns onto one future (T-374)
Orchestrator.spawn() check-then-acts on the session registry across
two awaits (transcript-tail read, process start) — two racing callers
for the same id both passed the check and the loser's live claude
process was orphaned, never killed, never observed. The first caller
now installs the spawn future synchronously; later callers await the
same future, and a failed spawn clears the entry so a retry proceeds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:08:33 +02:00
jpmschweitzerandClaude Fable 5 5d9443d9f7 spawn terminal panes in the open workspace root (T-381)
The shell spawned with Directory.current — $HOME for desktop-entry
launches, and stale after a project switch since the process CWD
never moves. Use the kernel project root, falling back to the
process CWD only when no project is open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:06:45 +02:00
jpmschweitzerandClaude Fable 5 5f9c054420 replay-latest ValueStream for session state streams (T-386, T-274)
Broadcast streams drop the current value for late subscribers — the
shape behind T-274: the init event fires while spawn() is still
awaiting the transcript-tail read, before the pane subscribes, so the
status bar stayed blank. New pure-Dart ValueStream<T> (no rxdart —
prefer-zero-deps) replays the latest value to each new subscriber;
statusStream, busyStream, and pendingPromptStream in the claude
builtin now use it. busyStream subscribers see the current state
first (seeded false), which the busy test now asserts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:05:08 +02:00
jpmschweitzerandClaude Fable 5 0e7353bf9c run dart format over the dragon-fix files
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:58:37 +02:00
jpmschweitzerandClaude Fable 5 9889afdc35 require a bearer token on the MCP HTTP server (T-362)
D-71's threat model — another user on the same host must not drive my
IDE — was enforced with 0600 on the unix socket and then bypassed
wholesale by the unauthenticated localhost SSE port, which since D-86
serves every clide verb as a tool. The server now mints 32 bytes of
CSPRNG token per start, publishes it via the /ide discovery lock
file's authToken slot (the field Claude Code's client reads), chmods
the lock to 0600, and rejects any request that doesn't present the
token in x-claude-code-ide-authorization with 401.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:58:01 +02:00
jpmschweitzerandClaude Fable 5 a919d79ce1 watch the claude process: drain stderr, surface exit (T-361)
The session observed its child only via stdout. Two failure modes:
with --verbose the CLI chats on stderr, and an undrained 64KB pipe
blocks the child mid-turn with zero diagnostics; and nothing watched
the exit code, so a crashed process just looked thoughtful forever.

ClaudeStreamJsonProcess now drains stderr from construction into a
bounded tail buffer, and StreamJsonSession watches exitCode: on death
it flips busy off, clears any unanswerable pending prompt, and emits
a SessionEnd (exit code + stderr tail) — replayed via session.end for
late binders. The pane reports the exit in its status line and logs
the stderr tail; a deliberate dispose suppresses the watch so /clear
and teardown don't read as crashes. Test fakes extend the process
base instead of implementing it, so its defaults carry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:54:12 +02:00
jpmschweitzerandClaude Fable 5 77c4341318 scope collapser-card semantics exclusion to the header (T-370)
The summarized button semantics (label, expanded/collapsed state)
wrapped the entire card with excludeSemantics, so every expanded
child vanished from the a11y tree — a screen-reader user could expand
a run and hear nothing inside it. The exclusion now wraps only the
header (ticker when collapsed, header row when expanded); inner item
cards stay readable, and the redundant background-toggle tappable is
explicitly excluded so the header stays the single AT stop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:47:06 +02:00
jpmschweitzerandClaude Fable 5 664a8da72e tear down the previous workspace's services on project switch (T-367)
buildDispatcher composed a fresh PaneRegistry, FilesService,
SearchService, and EditorRegistry per workspace, but their shutdown()
methods had zero callers — every project switch left the old set's
file watcher emitting into the new workspace's bus and its PTYs
alive. The dispatcher now pairs with a teardown closure that the
serialized swap invokes after the old server stops; the same-path
reuse fast-path drops the unused new set without teardown since its
services are inert until a command starts them. SearchService gains
the shutdown() it was missing (cancels in-flight searches).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:44:24 +02:00
jpmschweitzerandClaude Fable 5 ba6ab51118 confine editor.open/editor.save to the workspace (T-363)
The editor registry resolved buffer paths with a string join that
passed absolute paths through verbatim and never normalized `..` —
an unconfined read and write primitive over IPC while files.read was
carefully guarded. Buffer paths now resolve through
resolveUnderRootFollowingSymlinks: traversal, absolute escapes, and
symlinks-out are rejected at open, and re-checked at save so a
symlink swapped in under an open buffer's path can't redirect the
write. D-80's extra read roots deliberately do not apply — a buffer
is a write surface. Handlers map PathOutsideRoot to the same error
files.read uses. Also merges a duplicate Added heading that had crept
into the Unreleased changelog section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:39:23 +02:00
jpmschweitzerandClaude Fable 5 88d72789f4 apply include/exclude globs in search.replace (T-364)
computeReplacements accepted the query's glob filters and silently
dropped them — replace could rewrite files the equivalent search
would never have matched. The grep engine's glob helpers are now
public and shared, so search and replace can't disagree on scope;
both the preview and the apply path go through the filtered list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:34:47 +02:00
jpmschweitzerandClaude Fable 5 8477e302ef detect symlinks from the lister entity, never descend them (T-365)
stat() follows links, so `stat.type == link` was always false: every
FileEntry reported isSymlink=false and walkFiles happily descended
symlinked directories — an escape hatch out of the workspace and a
cycle risk for the search engine built on the walk. The lister already
runs with followLinks: false, so the Link entity itself is the signal.
listDir keeps reporting the target type for the UI; walkFiles skips
descent into symlinked dirs and still emits file symlinks as entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:32:24 +02:00
jpmschweitzerandClaude Fable 5 34821fbc39 guard SGR 38/48 lookahead; parse colon sub-parameters (T-369)
printf '\e[38m' was a RangeError inside Terminal.write — the
extended-color branches indexed params[i+1..i+4] unguarded. An
emulator must never throw on hostile bytes. Both branches now share a
bounds-checked helper that ignores truncated sequences.

Colons were silently dropped mid-CSI, fusing 38:2:255:0:0 into one
bogus parameter; the consumer now records ECMA-48 sub-parameter
links, so ITU T.416 colon-form truecolor/256-color (with or without
the colorspace slot) parses identically to the semicolon form, and a
malformed colon group is dropped whole instead of bleeding into
neighbouring SGR codes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:30:41 +02:00
jpmschweitzerandClaude Fable 5 390ab2b64e gate conversation auto-scroll on the bottom pin (T-368)
New items arrive on every streamed token, and _onChanged jumped to
maxScrollExtent unconditionally — so a reader who scrolled up was
dragged back to the tail continuously for the whole reply. The
_atBottom pin already existed for viewport resizes (T-297); apply it
to the new-item path too, re-checking after layout since the user can
scroll during the frame. Twin tests added beside the T-297 pair:
pinned view keeps following, scrolled-up view stays put.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:26:32 +02:00
jpmschweitzerandClaude Fable 5 75fc2719a0 cache the kernel ref so dispose() can actually clean up (T-366)
ClideKernel.of(context) is an illegal ancestor lookup inside
dispose(); both panes wrapped it in catch (_) and silently did
nothing. The terminal pane therefore never sent pane.close (backend
PTY + daemon pane leaked per closed pane) and the Claude pane never
removed its settings listener. Both now cache KernelServices in
didChangeDependencies and the swallow-everything helpers are gone.
New terminal_pane_test covers the close-on-dispose path; note in it
why the whole tree must unmount (harness Overlay keeps
initialEntries across rebuilds).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:24:27 +02:00
jpmschweitzerandClaude Fable 5 466383671d close the PTY master fd when the child exits naturally (T-360)
_reap() flipped _dead without releasing the master fd, and close()
short-circuits on _dead — so every naturally-exited child leaked its
fd and pty device for the life of the app. The reader isolate sends
EOF only after leaving its poll loop, so releasing the fd inside
_reap() cannot race the reader. Regression test counts /dev/ptmx
entries in /proc/self/fd across a natural exit; verified to fail
against the unfixed code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:18:47 +02:00
jpmschweitzerandClaude Fable 5 647c22d32a file Q-35 through Q-49 for the Fable review feature proposals
New governance/questions/design.md holds one open question per Part IV
feature proposal — Tier 1 (agent blame, context x-ray, trust ledger,
activity HUD, active-ticket chip), Tier 2 (twin-timeline rewind, visual
dialog, immortal terminals, cost ledger, ticket dispatch), Tier 3
(semantic terminal, codebase map, live mixed documents, sealed
workspace) — plus one batch record for the honorable mentions, so each
can resolve into a D-record + initiative or an R-record. Remote Claude
over SSH got a context append on existing Q-23 instead of a duplicate
record. README index regenerated by pql decisions sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:14:21 +02:00
jpmschweitzerandClaude Fable 5 3ed90fc318 file the 2026-06-11 Fable review under epic T-359
Commit fable-ous.md (13-reviewer multi-agent assessment of the whole
tree) and the ticket tree it produced: epic T-359 with 26 children
covering the dragon bugs (PTY fd leak, undrained claude stderr,
unauthenticated MCP HTTP, path-confinement gaps, dispose-path leaks,
SGR crash, a11y semantics), the medium scorpions, a dead-code sweep,
the systemic-pattern work, and split plans for app.dart and the claude
meta sidebar. Root causes appended to existing T-274, T-283 context,
and the parser split plan to T-123. One review claim (ColumnHat
duplicated in app.dart) was refuted during verification and is
annotated on T-385/T-394.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:13:52 +02:00
jpmschweitzerandClaude Opus 4.8 662325d5db trim CHANGELOG bullets to the 60-word cap
The T-325 and T-342 entries leaked commit-body detail into the changelog;
shorten to user-facing impact per the changelog-gate cap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 19:17:50 +02:00
jpmschweitzerandClaude Opus 4.8 1e24f0022d close out T-325 in the ticket board
Persist the live-tail story's done transition (ticket-DB sweep only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 19:17:09 +02:00
jpmschweitzerandClaude Opus 4.8 d43377ac89 claude: live-tail terminal sub-card in expanded Bash cards (T-325, UI)
Wire the detection + follower core into the Bash tool card. A Bash card
with a follow intent (`tail -f …`) gains a "live tail" segment below the
result: an embedded read-only TerminalView fed by FileTailFollower on the
file the command follows, resolved against the open workspace.

Lazy lifecycle for free: the collapser builds its children only when
expanded (clide_collapser_card.dart), so _BashLiveTail starts the follower
in didChangeDependencies on expand and stops it in dispose on collapse —
no follower runs until the card is expanded. No resolvable file-backed
source → a muted "no independent source to follow" note, never an empty
terminal. The workspace root comes from kernel.project.current, so no new
plumbing through the conversation widget tree.

Tests: a tail Bash card surfaces the segment (+ the muted note when no
project/source); an ordinary `ls` card gets no segment; the segment only
builds on expand.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 19:09:01 +02:00
jpmschweitzerandClaude Opus 4.8 898a0316e5 claude: Bash live-tail detection + read-only file follower (T-325, core)
The detection/follow core for the live-tail sub-card, with the UI wiring
to follow. Claude Code runs every Bash tool itself and clide only sees the
final tool_result block — we can't mirror the running process, so instead
we detect a file-backed source the command follows and open our own
read-only follower on the same file.

- bash_tail_source.dart: detectBashTailSource() parses a Bash command for a
  single, safe, file-backed source (tail/cat/less with one file arg, inside
  the workspace via resolveUnderRoot). Returns null for a pipe-into-tail, a
  redirect, two files, or a path outside the repo — the caller then shows a
  "nothing to follow" note. bashHasTailIntent() gates WHEN the segment
  appears: v1 triggers on `tail`/follow-flags only, so ordinary cat/ls/git
  cards stay clean (cat/less remain detectable for later).
- file_tail_follower.dart: a polling, read-only `tail -f`-style follower
  (no subprocess, no touching Claude's command) that emits the trailing
  window then appended deltas, and re-reads from the top on truncation.

Tested: 19 parser cases (incl. the `git push | tail -25` and outside-
workspace null cases), the intent predicate, and the follower (initial
window / appended delta / missing file / rotation / start / stop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 17:12:53 +02:00
jpmschweitzerandClaude Opus 4.8 d43ddfed9c pql: refine T-47 (clide self-update) and return it to the backlog
Fleshed out the self-update story with grounded constraints, a blocking
prerequisite, decisions, and a phased breakdown:
- D-64 ("no auto-update checks without user action") is stricter than the
  original "opt-in or gated" wording → the check must be explicitly
  user-initiated every time (palette / About button), not a startup poll.
- POLICY.md grudging-allowance criteria apply to the explicit fetch.
- Hard prereq: no release channel exists (2 stale tags, no CI, no signed
  artifacts) → recommended splitting a "release channel" sibling under T-46.
- Phases P0 prereq → P1 check+notify → P2 download+verify → P3 apply+relaunch
  (tmux sessions survive, D-41) → P4 macOS/deltas.

Moved back to backlog pending the release-channel prerequisite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:33:36 +02:00