T-235: app-scoped kActivityFoldLevelKey + a claude.activity.fold-level command
that cycles none→tools→thinking→everything; ClaudePane + team_panel_host read it
and re-fold live via the settings notifier. Unit tests for the helpers.
T-132 cleanup: the one blocked item (account/team token budget) is detached
(T-158), reframed as Q-34 'how + when to surface the budget given upstream
doesn't expose it', with T-158 as its backlog resolver. T-132 closed — all
doable work delivered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A run of 2+ consecutive edits to the same file now folds into one ClideHolderCard
labelled '# edits' (coalesceEditRuns, run after groupConversation) instead of a
stack of cards; a different file or an interleaving step splits the run. Every
edit stays reachable on expand.
The holder gained an optional aggregate status. New owned primitives: ClideSpinner
(the logo mark, monochrome, 3D Y-axis rotation, reduced-motion-aware) and
ClideStatusIndicator (running→spinner / success→check / error→cross, with an
AnimatedSwitcher seam for a richer transition later — kept self-contained, not
built on ConversationCard's mark). The activity card shares the same indicator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the interaction zone grows/shrinks (composer ↔ permission prompt /
AskUserQuestion, D-78) the conversation viewport changed height but the scroll
offset didn't follow, leaving the last card hidden behind the taller box. Track
whether the view is pinned to the tail; a LayoutBuilder around the list detects
the viewport-height change and re-jumps to the bottom only when pinned, so a
scrolled-up reader is undisturbed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
EditorController exposes the active buffer's EditorSettings (parsed from the
buffer payload, refreshed on editor.settings-changed). EditorView takes over Tab
to insert the configured indent (spaces or a tab) and Shift+Tab to dedent — only
when a source has an opinion, otherwise Flutter's focus traversal stands. A
max_line_length draws a 1px wrap-guide ruler painted behind the text.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the status item's hand-rolled OverlayEntry + bespoke popover rows for the
shared anchored-overlay (above/end, autoFlip) and ClideMenu. The High-contrast
toggle becomes a keepOpenOnSelect item; theme rows mark the active one. Nav,
focus capture, barrier and Esc now come from the primitives.
The popover tests move to anchoredHarness (a real sized overlay) so the
above-anchored panel hit-tests on-screen, which the shared canSizeOverlay
harness does not.
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>
Replace the per-button hand-rolled LayerLink/OverlayEntry/barrier and the
bespoke MenuDropdown + MenuItemRow with the shared popover primitive (D-88).
A small _MenuOverlayAdapter bridges the single-open MenuBarController to each
button's ClideOverlayController, so one source of truth drives open/close,
hover-switch, Alt mnemonics, and Left/Right menu switching (onArrowLeft/Right).
menu_dropdown.dart and menu_item_row.dart are deleted (absorbed by ClideMenu).
Behaviour and a11y are unchanged; menu_bar_test + app_test stay 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>
A short or narrow viewport (many recents, small window) overflowed the
centred Column. Make the content scrollable with a minHeight so it still
centres when there's room, and let a long recent-project branch name
ellipsise instead of forcing the row wider. T-273 follow-up.
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>
Replace the ambiguous inline "T-1 ← T-9" id row on ticket cards with a
small tree: the parent renders as a muted, clickable breadcrumb above, and
the card's own ticket sits below it under a "└" connector in bold — so it's
clear which id is the subject and which is its parent. Tapping the parent
selects it.
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>
A custom in-window menu bar in the hat (no native menu, D-7), built from
the command registry so it stays in sync and satisfies D-6 parity.
- Menu model + hybrid resolver (menu_model.dart): a curated File/View/Help
tree where a MenuAutoFill node sweeps in unplaced view.* commands; titles
+ keybindings come from the registry/keymap; unregistered or
enabledWhen-false items render disabled (greyed), never hidden.
- Widgets: MenuBar row in the hat (chrome tokens), anchored MenuDropdown
overlay (dropdown tokens), two-column MenuItemRow with inline keybinding.
- Full keyboard: Alt+mnemonic opens (hook in _RootShell._onKey), arrows
navigate, Enter activates, Esc closes, Left/Right switch menus.
- Commands: file.openFolder / file.newWindow / file.closeWorkspace /
help.about, registered by MenuBarExtension(services:). File logic lifted
out of the project switcher into FileActions (one source of truth; the
switcher now dispatches the commands). Ctrl+O / Ctrl+Shift+N are now real
keybindings in default.yaml.
- Help → About: version/commit/date/repo from build-info + the bundled
dependency licenses parsed from assets/licenses.yaml.
Edit/Selection menus are deferred to T-271/T-272 (need focused-surface
command routing).
Tests: resolver + controller + licenses parse (pure); menu-bar widget
(open/close/execute/disabled/Esc/arrow/Enter/Left-Right); FileActions +
Open dialog; app-level Alt+F, non-repo dialog, and closeWorkspace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
The sidebar/dock filter fields (the shared ClideFilterBox) had no CLI
peer — a one-way, UI-only affordance that broke D-6 parity. Add the
drive+observe verb `clide ui filter <address> [<text>]`, routed entirely
through the kernel MessageBus pub/sub so a box reacts to a published
message identically whether the trigger was a UI keystroke or the CLI —
keeping extensions first-class (no dispatcher→widget wiring).
- ClideFilterBox gains an `address`; when set it listens on `filter.set`
for its address and republishes its value on `filter.state`. Null
address keeps the box a kernel-free UI widget.
- FilterStateCache (new kernel service) caches the latest `filter.state`
per address — the bus has no retention, so this backs the observe-half.
- ui.filter: with text → publishes `filter.set` (drive); without →
reads the cache (observe). Honest toolError when there is no live UI.
- Address every box: decisions/tickets/files/git/output/problems panes,
the four search boxes, and the pql search/query/markdown inputs.
Addresses are the ids from `clide pane list` (e.g. decisions.panel).
settings.json: allow the `clide` CLI + relevant skills.
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>
The D-87 bottom dock, end to end. New Slots.dock in the classic preset
(hidden by default); RootLayout renders it full-width above the status bar
when open, capped at half the window so Claude stays largest (the D-47
amendment). LogRing now lives on KernelServices (boot tees the kernel logger
into it; main.dart also tees the IPC/MCP logger), so the dock shows logs from
every subsystem.
OutputExtension contributes the Output tab, the merged health/toggle
status-bar widget (green check when clean, warn/error counts otherwise) that
replaces the old ipc-status item, and the dock.toggle command (Ctrl+J).
Problems moves out of the sidebar into the dock. open/height persist per
workspace via the default-layout extension.
Drag-resize of the dock height is deferred (DragResizeHandle needs a dock
sign case); height is the persisted default for now. Boot verified via
testmode; full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OutputController wraps a LogRing with source/level/text filter state;
OutputView renders the filtered rows (time · level · source · message,
severity-colored), follows the tail with a jump-to-latest pill when scrolled
up, and offers source/level cycle chips + Clear. Second slice of the D-87
dock — the component is standalone + tested; the dock shell, the merged
status-bar toggle widget, and moving Problems in are the next slices, where
this gets wired to a bottom slot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CliInstaller.inspect() gains a devTree state: when `clide` on PATH resolves
to a dev-tree build artifact (native/<plat>/clide, the Makefile's
CLIDE_CLI_BIN output) it's classified separately from a packaged install
rather than silently treated as "installed". needsInstall stays false for a
dev build (it's intentional on a checkout, not a reinstall prompt), and the
launch-time check surfaces it as an info note. install() flags fromDevTree +
notes it in the result message when the copied source is a dev build.
Closes the last open child of the T-208 "give Claude hands" initiative.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
panel.focusMode.exit is bound to Escape in the contributions layer, which
outranks the active preset. In Vim insert/visual mode that shadowed the
vim preset's `escape → vim.mode.normal` binding, so Esc closed the editor
instead of returning to normal mode.
Add an optional when-clause to a CommandContribution's defaultBinding
(plumbed through to KeymapService.registerCommandBinding, which already
accepts one) and guard focusMode.exit's escape with
`!vim.insert && !vim.visual` — symmetric with vim.yaml's vim.mode.normal.
Esc still exits focus / closes the editor in normal and non-Vim modes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A command-palette / `clide` CLI verb (`clide.installCli`) copies the
bundled C client into ~/.local/bin, VS Code "Install code command" style,
so a user who runs the app without `make install` can still reach the CLI
from a shell. On launch the app detects when `clide` is missing from PATH
or resolves to a stale symlink into the Flutter GUI bundle (the dogfood
footgun: a bare `clide` launched a second app instead of querying the
socket) and notifies with a pointer to the command. `make build` now ships
the C client inside the app bundle so the affordance can self-install from
it; `make run` points it at the dev-tree client via CLIDE_CLI_BIN.
Detection and the copy live in the Flutter-free CliInstaller
(kernel/src/cli_install.dart) so they run under unit tests without a real
install.
Closes epic T-209 (ship the clide CLI on PATH).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds diff as a fourth ui.open target. The diff extension now retains an
app-scoped DiffController and subscribes to its builtin.diff/selection
channel: a selection reveals the diff tab and focuses the file, which
the view scrolls into view and highlights. Retaining the controller in
the extension (not the view) lets a focus survive the tab being
revealed/remounted, mirroring the ReaderNav viewers.
Closes T-233.
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>
The status-bar popover already handles quick theme switching, so a
separate theme-only modal was redundant. Repurpose it as a general
Settings surface whose first (currently only) section is Appearance:
base themes, sorted, with a High-contrast toggle for -hc siblings,
reusing the theme_families helpers shared with the popover.
Command id stays theme.pick (the welcome theme-link and tests
reference it); only the title and the view it opens change.
Co-Authored-By: Claude <noreply@anthropic.com>
Non-modal operation-feedback toasts, bottom-right: a ClideToast card per
severity (success/warning/error/info), auto-dismiss (errors linger), queue
with a visible cap, slide+fade in, manual dismiss, live-region a11y.
ToastService is a MessageBus consumer — components raise a toast by publishing
to the 'toast' channel (publishToast helper), so emitters stay decoupled from
the UI. GitController's push/pull are the first emitters. ToastOverlay mounts
in the app-root Stack.
Also adds comprehensive GitController coverage: importing it for the toast
emitter test first pulled the whole file into the coverage denominator, so the
controller is now tested end to end (status/stage/commit/stash/push/pull).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per demo feedback on the T-234 status-bar popover: collapse the -hc
theme rows into a single 'High contrast' toggle at the top (applies the
chosen base theme's -hc sibling live, falling back to the base when none
exists); list base themes only, sorted by display name; widen 240->280
and ellipsize rows so 'Catppuccin Mocha' no longer wraps; swap the swatch
dot for the Phosphor palette icon; lowercase the bar label to match the
all-lowercase status bar (proper case kept in the a11y label).
New pure theme_families helpers (base/sibling/resolve), unit-tested.
Modal picker_view consistency + the status-bar right-alignment remain on
T-237.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
A far-right status-bar control shows the current theme and opens an
anchored popover (not the full-screen modal) to switch live: click or
keyboard (arrow/Enter to pick, Esc/tap-away to dismiss without change).
Reuses ThemeController.available/select; the theme.pick palette command
is unchanged (D-6 parity). Registered at priority 110 so it sits in the
status bar's right group.
Closes T-234 (under no parent; demo polish).
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>