A pasted-image @<path> token now renders as an inline, keyboard-activatable
thumbnail in the Claude conversation that opens the full image in the lightbox;
the composer's attachment chips use the same (larger, 44px) thumbnail. New
ImageThumbnail + openImageLightbox in the Claude layer; ClideMarkdown gains an
onImageToken builder seam (mirroring onRecordTap) that drops a WidgetSpan into
the text flow — it owns no Image.file/lightbox, staying generic. Missing files
degrade to a placeholder; render-only (the sent text + copyText are unchanged).
Resolves the conflicting T-236 (inline thumbnail) / T-254 (image card) designs
into the hybrid the user chose; recorded as D-89.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swap the composer's hand-rolled LayerLink/OverlayEntry slash popover for the
shared ClideTypeahead, driven by a ClideMenuListController for arrow/Enter nav
while the EditableText keeps focus. The key pipeline (Esc-fallthrough,
Tab-complete, history) stays in the composer.
ClideTypeahead now bridges its live suggestions through a ValueNotifier so the
popover narrows as you type — the OverlayEntry is a separate subtree that does
not rebuild with the host, so a captured list would go stale. The notifier and
open/close run post-frame to avoid rebuilding widgets during the parent's build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both the sidebar and the full-pane chat composers hand-rolled the same
LayerLink + OverlayEntry + _showOverlay/_removeOverlay + _AtOverlay. Replace
both with ClideTypeahead driven by the suggestion list; delete _AtOverlay and
the per-copy overlay plumbing. The text parsing/completion (activeAtQuery,
filterAtNames, completeAt, parseAtTag) and the Esc handler stay in the hosts.
Behaviour is unchanged (the popover now uses the shared dropdown styling);
team_chat_sidebar_test stays green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The collapsed activity / agent-run cards (ClideHolderCard) wrapped themselves
in 3px vertical margin, while the prose ConversationCards use 14px bottom /
0 top. So a folded card floated ~17px below the previous card but hugged the
next one at 3px — the uneven gap the earlier bordered-padding tweak didn't
address. Give the holder the same bottom-14 / top-0 margin.
The holder-card golden is regenerated for the taller frame. The copy-button
holder test parks its hover and advances past the tooltip show-delay so the
(exit-uncancellable) Future.delayed timer fires instead of leaking.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build the T-275 picker on the new popover primitive (D-88): an icon-only,
per-mode-coloured button trailing the composer text box opens a ClideMenu of
the safe trio (default/acceptEdits/plan, active marked) plus a divided, disabled
bypass row (the footgun stays behind the cockpit guard, T-181). The label lives
in the tooltip, the menu, and the status bar — the resting button is the glyph
alone. Coexists with the composer's Stop row when busy.
- new permission_mode_control.dart (PermissionModeControl + per-mode
icon/colour helpers); shieldCheck/shieldWarning glyphs added to PhosphorIcons.
- claude_composer.dart: permissionMode + onSetPermissionMode props; control
trails the text box (bottom-aligned), shown only when wired.
- claude_pane.dart: pass the current mode + a setter; demote the status-bar
_ModeBadge to a passive, per-mode-coloured text indicator (no click). Ctrl/Cmd+M
still cycles (onCycleMode unchanged).
Regenerated the phosphor-glyphs reference (47 defined). Tests: menu opens with
the trio + disabled bypass, select sets the mode, helpers map colours/icons,
control coexists with Stop, hidden when no mode.
Note: claude_pane.dart also carries the earlier T-274 resume diagnostic log line
(uncommitted in the working tree, reviewed as benign) — it rides along here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bordered cards (tool / Agent calls) used 10px vertical interior padding while
stripe cards (you / claude) used 8, so a collapsed tool/Agent card read
chunkier — taller box and more trailing space — than its neighbours in the
conversation log. Match the bordered variant's vertical padding to the stripe
variant (8) so boxed cards share one rhythm. Box-to-box inter-card margin is
unchanged (a uniform 14). Regenerated the merged-card golden.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The conversation ListView.builder built its items (_ConversationTurn,
_ActivityCard) with no keys, so Flutter matched the stateful subtrees inside
them (ConversationCard collapse/hover/focus; ClideHolderCard expand state) to
widgets by POSITION. The visible list reshapes exactly when a tool result
lands — T-262 folds a success result into its call card and suppresses the
standalone result, errors append a sticky card, clusters re-fold — so after a
read/write completed, a card's collapse/hover state (or a cluster's identity)
could reattach to the wrong card.
Give each list item a stable ValueKey from its identity: sticky item by
item.uuid, folded cluster by its first item's uuid (namespaced turn./cluster./
run./step. so the four call sites can't collide), plus super.key on the
_ConversationTurn/_ActivityCard constructors.
Tests: unfolded cards carry per-item keys; a folded cluster carries its
first-item key.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Linkify bare ticket/governance refs (T-281, D-77, Q-5, R-2) in rendered
conversation messages so clicking one opens the record in its context-pane
reader — T- in the tickets reader, D/Q/R in the decisions reader — reusing
the existing `selection` MessageBus addressing (the same path clide ui open
and the panels use; D-6 parity already satisfied by `clide ui open`).
ClideMarkdown now linkifies bare refs in running text (paragraphs, lists,
headings, bold/italic), not just record-shaped markdown links. Matching is
word-boundary anchored so "T-shirt" (no digits) and "PT-281" (mid-word) stay
literal; `code` spans and `pre` blocks render verbatim and never reach the
linkifier, so refs inside code stay plain. The clickable span is shared
between bare refs and record-shaped links so both look and behave alike.
Tests: ClideMarkdown linkifier cases (bare T/D/Q/R tap fires onRecordTap,
T-shirt + inline-code refs stay plain, no-callback stays plain); conversation
view integration (clicking a bare ref publishes the reader-open selection to
the tickets/decisions reader).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The in-flight "Pondering…" turn indicator used muted grey; switch it to
the existing claudeAccent (#d97757, Anthropic's brand coral) — the
indicator is main-thread Claude running, which the accent is reserved for.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A sub-agent's prose rendered as "claude" and its thinking as "thinking",
identical to the main assistant, because the label logic ignored
isSidechain — presenting sub-agent output as if the main Claude said it.
Now a sidechain AssistantTextMessage is labelled "agent" with a muted
stripe (never the coral claudeAccent brand), and sidechain thinking is
"agent thinking". Main-thread items are unchanged.
Tests: sidechain prose/thinking relabel, main-thread unchanged; golden
contrasting the muted agent stripe with the coral claude stripe.
This completes epic T-267 (conversation rendering streamlining): fold
success result (T-262), fold agent prompt (T-263), nest the agent run
(T-264), the shared holder primitive (T-266), and this attribution fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A sub-agent's sidechain run used to spill loose into the main chain,
indistinguishable from main-thread items. Now:
- _sidechainFold routes every sidechain item to its owning Agent/Task
tool-use by walking the parentUuid chain up to the Agent message it
branches off (nearest-preceding Agent as fallback) — correct even for
parallel agents.
- The run (prose / thinking / tool cards) nests in an "agent run"
ClideHolderCard UNDER the Agent card, suppressed from the top level. The
prompt still folds into the call (T-263); a successful sidechain tool
result folds into its own tool card inside the run, so it isn't a
separate step.
- When a run is shown, the Agent card's returned-result segment is dropped
(it duplicates the run's final output, note E) — but kept when no run
was captured, so output is never lost.
Tests: run nesting, returned-result dedup, parallel-run attachment (would
fail under nearest-preceding), and folded-result-not-double-counted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extracts a shared ClideHolderCard primitive (consumed next by T-264) that
renders a folded run as one framed container wrapping its sub-cards:
- The whole holder background is the collapse toggle — a gesture target
behind the children that only fires for hits the children don't consume.
Each sub-card opaquely absorbs its own bounds, so a card tap (and its
copy button) interacts with the card, never the holder; selection drags
pass through. This ends the scroll race: while a run tail-follows, a
click on whatever background is in view collapses it, no top header to
reach.
- A focusable caret keeps the control keyboard/AT reachable (D-78); the
collapsed ticker + step count are preserved.
- _ActivityCard becomes a thin stateless adopter of the primitive.
Tests: ticker/expand, background-toggle, child-tap-not-hijacked, copy
still works, keyboard Activate path; golden for collapsed + expanded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A sidechain sub-agent prompt was rendered with the blue "you" label,
falsely implying the user typed it. Now:
- transcript_reader parses parentUuid (was dropped) onto every
ConversationItem.
- conversation_view resolves each sidechain prompt to its spawning
Agent/Task card via parentUuid (nearest-preceding Agent as fallback),
folds the prompt into that card as a collapsed "prompt" segment, and
suppresses the standalone block. Layered order when expanded:
call → prompt → result (note E).
- A sidechain UserMessage never gets the "you" treatment: folded into its
card, or — when orphaned — rendered as a muted standalone "agent prompt".
Tests: parentUuid parse, fold + suppression, parallel-agent attachment
(would fail under a nearest-preceding heuristic), orphan relabel, and a
golden for the Agent card's call → prompt → result layering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A successful tool call now renders as a single merged card instead of a
separate call card + result card:
- ConversationCard gains a trailing header status slot (success check /
error cross, semantics-labelled) and optional labelled extra body
segments (P1 + P2 — reused by T-263's Agent card).
- conversation_view builds a reverse toolUseId→result lookup, folds a
successful result into its call card as a colorized ClideCodeBlock
(Read → file grammar, Bash → shell, else text), and suppresses the
standalone success result in _visibleItems. Errors stay a separate
prominent red card, now with a matching red header mark (note C).
- Suppression happens before grouping, so a merged success result is no
longer a separate activity-cluster step (note D).
Tests: card status-mark + segment coverage, merged-fold + language
inference + in-flight/orphan/error cases, cluster non-double-count, and a
golden for the merged card (collapsed + expanded + error).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude_pane.dart had no widget-test harness, so the T-269 wiring (primary
rebind on workspace switch, host tab-reset) shipped uncovered — and the
moment any test mounts ClaudePane its 230 lines enter the coverage
denominator, which is what sank the gate earlier.
Add a real harness: a fake ClaudeSessionOrchestrator (no real `claude`
process) plus a connected fake IPC answering files.root, with spawn/respawn
driven inside tester.runAsync so the transcript-probe File I/O resolves.
Covers spawn-binds-to-repo, in-place rebind, same-repo no-op, send, /clear
(in place), /fork, /resume (picker open + cancel), mode cycle, draft
round-trip, init-event status, can_use_tool prompt swap, secondary spawn +
dispose-close, tap-to-focus, and the disconnected-daemon error path.
claude_pane.dart 25% -> 84%; session-host tab-reset covered too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Separate clide windows are isolated (own process, per-root IPC socket,
per-repo deterministic session id), so parallel repos in separate windows
were already fine. But switching the workspace in place (Open Project /
Open Folder) only emitted ProjectOpened — nothing rebound the Claude
session, so the primary pane kept the PREVIOUS repo's conversation.
Two compounding causes, fixed in layers:
- ClaudeSessionOrchestrator.spawn() was idempotent on the literal key
'primary' without checking cwd, so it handed the old repo's session to
the new repo. It now reuses a cached session only when its cwd matches
the spec; a mismatch tears the stale one down and spawns fresh.
- The primary ClaudePane is built once behind a GlobalKey and spawns once,
so it never re-resolved. It now listens for ProjectOpened and rebinds:
close its orchestrator entry, drop the cached session id + repo root, and
respawn against the now-active repo. Secondaries don't self-rebind.
- ClaudeSessionHost drops the old repo's secondary/fork tabs on a switch,
so a switched workspace starts like a fresh launch (lone primary).
- The extension closes any remaining sessions whose cwd != the new root,
catching team/non-pane sessions no pane owns.
Tested at the orchestrator: cwd-aware idempotency (reuse on cwd match,
teardown + respawn on mismatch). Pane/host widget coverage is intentionally
deferred — claude_pane.dart has no widget-test harness yet and pulling it
into coverage piecemeal would drop the gate; tracked separately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The primary Claude pane is anchored to a deterministic session id derived
from the repo path so it resumes the same transcript across restarts
(D-77/T-146). But /clear (T-156) respawned on a fresh RANDOM id, so the
next launch re-resolved to the deterministic id, found its old transcript
on disk, and resumed the PRE-clear conversation — the cleared session was
orphaned and the clear silently didn't stick.
/clear in the primary pane now deletes the deterministic session's
transcript (and its sidecar dir) and respawns on the SAME id, so
`--session-id` re-creates it empty and a cleared primary stays cleared.
Secondary panes are throwaway and keep the fresh-random behaviour.
Factor the duplicated transcript-path construction out of claude_pane into
session_naming helpers (claudeProjectDir / claudeTranscriptPath /
clearSessionTranscript) so the clear logic is DRY and unit-tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each RunningIndicator shuffles a copy of the verb list on creation, so a turn
doesn't always start with the same word. A `shuffle` flag (default true) is
turned off in tests for deterministic assertions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mincing, Boiling, Humming, Buzzing, Magicking, Cliding, Zooming, Bouncing —
appended to the rotation (20 verbs, ~80s cycle). Pondering stays first, so
the reduced-motion default is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The static gray `running…` gave no sense anything was happening. Replace it
with a RunningIndicator: an animated ellipsis (Pondering → . → .. → ...) and a
curated, on-brand verb that rotates every few seconds (Pondering, Conjuring,
Brewing, …).
The verbs are clide-owned, not the Claude Code CLI's — that list is a TUI
cosmetic the stream-json protocol doesn't surface, and reusing the bundled
strings is a licensing gray area, so a curated list keeps us self-contained
(own-the-rendering-stack, D-75). Animation is driven off a single
AnimationController's value (no timers) so tests advance it with bounded
pumps; reduced-motion shows a static verb and the a11y label stays stable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The inline image cards (T-249) are often too small to read. Clicking one now
opens a full-screen lightbox: zoom (scroll wheel / pinch), pan when zoomed,
double-click to reset to fit, Esc / close button / backdrop click to dismiss.
ClideLightbox is a reusable primitive (lib/widgets/) over Flutter's
InteractiveViewer with clide-owned zoom gestures, shown via the DialogRouter
(dimmed backdrop, single modal at a time, D-78). The card stays display-only;
the click is a navigation gesture, not an inline control.
CLI parity (D-6): `clide image show <path> --fullscreen` opens straight into
the lightbox instead of injecting a card.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drives an image inline into the Claude conversation log over the same
bus-publish path as ui.toast/ui.open, keeping the dispatcher handler
Flutter-free. The card is display-only per D-78; the verb registers a
CommandSchema so it surfaces in clide capabilities for T-248 discovery.
Closes T-249.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
setPermissionMode only wrote the control_request; the mode was never
folded back into the session status, so the badge and Ctrl/Cmd+M looked
dead. Optimistically merge + emit the new mode immediately; the next
system/init still reconciles.
Co-Authored-By: Claude <noreply@anthropic.com>
A heavy agent turn buried user/Claude prose under a wall of tool-call/
result rows. A pure grouping pass (activity_cluster.dart) folds runs of
consecutive meta items into clusters; the conversation view renders each
cluster as one collapsible activity card — collapsed by default with a
live one-line ticker of the latest step + a step count, click/Enter to
expand the steps in order. Sticky items (user messages, Claude prose,
and FAILED results) render first-class and seal the cluster.
Fold level is switchable (FoldLevel none/tools/thinking/everything);
default L1 folds tool calls+results while keeping diffs and thinking
first-class. The grouping logic is fully unit-tested; the card is
keyboard + screen-reader accessible. Persisting the level via a user
setting + control is the tracked follow-up T-235.
Closes T-230 (under T-132).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
_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>
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>
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>
Bash, Write, and Edit/MultiEdit each get a tool-appropriate body in
the permission card instead of the indented-JSON dump: Bash renders
the command as a shell code block with a background/timeout footer,
Write shows path + content syntax-highlighted from the extension, and
Edit shows path + before/after blocks. Unknown tools keep the JSON
fallback.
Co-Authored-By: Claude <noreply@anthropic.com>
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 34s
The sidebar had outgrown one scroll (stats + roster + config don't fit). A
sub-tab strip now switches between three surfaces: Activity (usage stats +
the primary session's live runtime), Team (the member roster, auto-fronted
when a team spawns and otherwise quiet), and Config (the Claude-environment
settings table over ClaudeConfig).
Activity and Config render their key→value rows through one shared table
(same label column + row pitch + header style) so toggling tabs doesn't move
anything. The expandable skills/agents/commands/permissions/MCP browser on
the Config tab is the follow-up (T-183). Exposes StreamJsonSession.status so
the runtime row can seed from the session's current state.
T-182, D-77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude's tmux team mode let teammates message each other and share a task
list; that mode is undocumented and unavailable headless. clide rebuilds
the same behavior over its own managed sessions, as the broker.
Verified live against claude 2.1.150 that a spawner can host an in-process
("SDK") MCP server entirely over the stream-json control channel — no
subprocess, no --mcp-config, no socket: declare the server name in the
initialize handshake's sdkMcpServers, answer the mcp_message JSON-RPC
round-trips (initialize / tools/list / tools/call) under
response.response.mcp_response. SDK tool calls are permission-gated through
the existing can_use_tool path. Documented in the 2.1.150 spike §6.
StreamJsonSession gains an McpServer hosting seam; TeamBroker + TeamMcpServer
expose send_message / broadcast / list_teammates / inbox / claim_task /
task_status, all routed through one shared broker. The orchestrator owns the
broker, registers each team session, delivers a message into the target's
next turn on its stdin, and injects roster + role via --append-system-prompt.
Solo sessions are unchanged (no MCP server, no initialize handshake).
T-170, D-77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>