Commit Graph
211 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 3097d5f63b hand a ticket to Claude from the sidebar (T-327)
Hovering a ticket card reveals a person-simple-run icon; clicking it
fetches the full ticket (pql.tickets.show withContext), builds a "pick
this up and start" prompt, and publishes ('builtin.tickets','pick-up',
{id,prompt}) on the message bus. The Claude builtin subscribes and
injects it into the active session (primary, else first visible) as a
user turn — a quiet no-op when no session is live. Sidebar stays
decoupled from the orchestrator (bus-only). Prompt-builder test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:52:27 +02:00
jpmschweitzerandClaude Opus 4.8 4f632d3cdd dock Claude's task list above the composer (T-308)
Claude's TodoWrite checklist was invisible. Add a TaskItem/TaskStatus
model + a latest-wins parser (taskListFrom) that reads the most recent
TodoWrite tool call (it replaces the whole list each time), and a compact
display-only ClaudeTaskDock pinned between the conversation and the
composer: collapsed to "N tasks · M done" + the current in-progress item,
expandable to the full checklist with per-item status glyphs + a11y
labels. Hidden when there are no tasks. Parser + widget tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:31:35 +02:00
jpmschweitzerandClaude Opus 4.8 2c8e9f2821 add 'Deny & simplify' permission option (T-311)
A fourth button on the permission card that denies the action with a
preformatted note: it's too complex for the permission system, retry in a
simpler/more granular form, and explicitly do NOT add a memory or change
permission settings (so Claude reformulates instead of fiddling with the
permission surface). A typed note is appended rather than discarded.
Addressable by number key (4 with remember, else 3); tooltip explains it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:14:00 +02:00
jpmschweitzerandClaude Opus 4.8 a879238102 fix image stale-render on in-place overwrite (T-312)
Image cards, thumbnails, the lightbox, and `clide image show` rendered
via Image.file, whose FileImage keys Flutter's imageCache by (path,
scale) only — so overwriting a file at the same path handed back the
previously decoded frame (hit live re-exporting a wireframe PNG). Add
ClideFileImage, a FileImage that folds mtime + size into ==/hashCode so an
in-place change is a fresh cache key (miss → re-decode), and route the
five Image.file sites through it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:25:58 +02:00
jpmschweitzerandClaude Opus 4.8 7a98cbae4b frame the context / thinking / agent-prompt meta cards (T-306)
The injected context block and the thinking / agent-prompt blocks
rendered as frameless `bare` cards, reading as unfinished `> context …`
rows next to the framed tool cards. Switch them to the bordered variant —
same panel border + left chevron + label as the surrounding cards — while
keeping the D-78 de-emphasis (muted accent, collapsed by default,
first-line summary; thinking gains a summary for parity). Adds a
conversation_card_meta golden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:16:41 +02:00
jpmschweitzerandClaude Opus 4.8 3833a41a9e Phosphor icons: resolve by name via a generated map (T-314)
Replace the 49 hand-maintained named consts with one generated
label→codepoint map (phosphor_glyphs.g.dart, 1512 glyphs from the glyph
table via tool/gen_phosphor_glyphs.dart). Feature code now references
glyphs by their exact kebab-case name — PhosphorIcons.byName('folder') —
with no raw codepoints; this also lets a Lua extension name an icon
without crossing the FFI boundary with a codepoint.

byName is total: an unknown name degrades to the `placeholder` box so the
bug is visible (it's a real error), while phosphor_glyphs_test asserts
every byName('...') literal in lib/ resolves — recovering the typo check a
const gave. Migrated the 89 call sites. Adds EmptyIconPainter for an
intentional blank that still reserves the icon box; ClideFilterBox gains
showIcon to keep the slot aligned when blank.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:46:51 +02:00
jpmschweitzerandClaude Opus 4.8 b680df2b24 T-310: numpad digits select prompt-card options
The prompt card's number-key shortcuts matched only digit1..digit9
(the number row); numpad 1-9 fell through to ignored. Add a
parallel _numpadKeys list and check it in _onKey so the keypad
maps to the same 1-9 selection for Allow/Deny and question
options. numpadEnter was already handled. The hasPrimaryFocus
guard still lets digits type into a focused note field.

Adds four widget tests (numpad Allow/Deny, question option,
focused-note swallow). Closes T-310 (under UI epic T-276).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:07:35 +02:00
jpmschweitzerandClaude Opus 4.8 a73f0485c3 route every tool use through the collapser (T-305 P3)
Every tool use now renders as a ClideCollapserCard over a one-item list
(a single tool is a list of one) — no separate single-card path. The
collapser carries the echoed last line, the count, and the aggregate
status (spinner while in-flight, check/cross once resolved); the inner
content card holds the call body + folded CALL/PROMPT/RESULT segments and
its own per-item mark. Inside a run (activity/edits/agent), tools render
as the bare inner content card so collapsers don't nest.

ConversationCard gains a `margin` param so inner cards carry no stream
margin; the collapser pads its inner canvas evenly on all sides (the
inner card no longer jams under the header). Rewrote the conversation_view
tests for the new structure and added a single-tool golden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:39:38 +02:00
jpmschweitzerandClaude Opus 4.8 cf36eca4e5 migrate group cards onto ClideCollapserCard; retire ClideHolderCard (T-305)
The activity run, edit run, and sub-agent run cards now render through the
shared ClideCollapserCard primitive instead of ClideHolderCard. The
collapsed ticker now leads with the card label, the count sits in a
fixed-width slot, and the status tick is pinned to the right edge.

ClideHolderCard (T-266) is fully superseded — removed along with its test
and golden; the deeper-control-passthrough coverage moved to the
ClideCollapserCard widget test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:57:01 +02:00
jpmschweitzerandClaude Opus 4.8 7171a09fc2 gate clide:// deep links: paranoid allowlist + confirmation prompt (T-56, D-90)
A clide:// link is an untrusted external vector (any webpage can fire one), so it
no longer translates to a command in parseArgv. It routes the raw URL to a new
builtin.deeplink handler that is doubly defensive: a default-deny allowlist
(kDeepLinkSafeActions — only the read-only 'open' verb; run/git/write/passthrough
rejected) AND a mandatory 'an external link wants to: … allow?' confirmation
before anything runs. Records the security boundary as D-90.

The earlier silent editor.open passthrough is replaced; open still works, now
behind the prompt. Tests cover the allowlist (the boundary) + the gating.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:57:43 +02:00
jpmschweitzerandClaude Opus 4.8 7d8f58690f number-key + Enter shortcuts for interactive prompt buttons (T-240)
In a ToolPromptCard, number keys pick the matching button/option (labels are
prefixed 1./2./3.…) and Enter confirms the primary action — matching the Claude
CLI. Permission: 1=Allow, 2=Allow&remember (when offered) else Deny, 3=Deny.
AskUserQuestion: 1..N select/toggle the current question's options + Other.

The card autofocuses and the key handler self-guards on hasPrimaryFocus, so once
the user clicks into a note field the digits type normally and never fire a
button. Shared the permission/option actions between the buttons and the keys.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:27:08 +02:00
jpmschweitzerandClaude Opus 4.8 1e8e37c6f9 clickable http links in the Claude conversation; bundle markdown hooks (T-253)
http(s) links (typed or autolinked) in the conversation now open via the OS URL
handler (OsBridge.openURL) on click, with a hover underline + pointer; non-http
schemes stay inert. Works across prose, lists, tables, and headings.

Refactor: ClideMarkdown's growing set of inline-interaction callbacks
(onRecordTap, onImageToken, onLinkTap) is bundled into one ClideMarkdownHooks
value threaded as a single param — no more per-callback threading, and the hooks
now reach every context uniformly (links/images previously only worked in some).
The public widget API is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:16:57 +02:00
jpmschweitzerandClaude Opus 4.8 86a52cd731 finish T-235 (persisted activity fold level); close T-132 via Q-34
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>
2026-06-09 19:24:19 +02:00
jpmschweitzerandClaude Opus 4.8 407ed25ab0 group consecutive same-file edits into one collapsed card (T-296)
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>
2026-06-09 18:34:27 +02:00
jpmschweitzerandClaude Opus 4.8 6efb3b5d2b re-anchor the conversation tail when the bottom input zone resizes (T-297)
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>
2026-06-09 18:22:14 +02:00
jpmschweitzerandClaude Opus 4.8 529f40996d render pasted images inline with lightbox expansion (T-236, T-254, D-89)
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>
2026-06-09 17:54:55 +02:00
jpmschweitzerandClaude Opus 4.8 2577a1220e editor honours EditorSettings: Tab/Shift+Tab indent + max_line_length ruler (T-29)
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>
2026-06-09 09:16:39 +02:00
jpmschweitzerandClaude Opus 4.8 9f249f6dcc migrate the theme switcher onto ClideAnchoredOverlay + ClideMenu (T-286, D-88)
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>
2026-06-08 23:24:00 +02:00
jpmschweitzerandClaude Opus 4.8 1c20bf128a migrate the slash typeahead onto ClideTypeahead (T-286, D-88)
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>
2026-06-08 23:02:07 +02:00
jpmschweitzerandClaude Opus 4.8 60312eff3b migrate the @-mention typeahead onto ClideTypeahead (T-288)
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>
2026-06-08 22:31:47 +02:00
jpmschweitzerandClaude Opus 4.8 e1d51eba7d migrate the menu bar onto ClideAnchoredOverlay + ClideMenu (T-286)
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>
2026-06-08 19:37:10 +02:00
jpmschweitzerandClaude Opus 4.8 cb4598b048 match folded activity-card spacing to the conversation cards (T-282)
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>
2026-06-08 19:33:37 +02:00
jpmschweitzerandClaude Opus 4.8 410252d121 keep the welcome screen from overflowing on small windows
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>
2026-06-08 18:54:24 +02:00
jpmschweitzerandClaude Opus 4.8 a95b332925 permission-mode control beside the Claude composer (T-275)
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>
2026-06-08 18:52:29 +02:00
jpmschweitzerandClaude Opus 4.8 a2437b0720 unify boxed conversation-card interior padding (T-282)
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>
2026-06-08 17:42:01 +02:00
jpmschweitzerandClaude Opus 4.8 1ae5cc609c key conversation list items so card state survives result reshapes (T-285)
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>
2026-06-08 17:37:38 +02:00
jpmschweitzerandClaude Opus 4.8 5241876871 clickable T/D/Q/R cross-refs in the Claude conversation (T-279)
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>
2026-06-08 16:13:01 +02:00
jpmschweitzerandClaude Opus 4.8 a62003a833 tickets panel: parent/child as a tree, bold subject ticket (T-281)
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>
2026-06-08 13:49:36 +02:00
jpmschweitzerandClaude Opus 4.8 e8cc1f1ceb turn indicator → Claude coral-orange brand accent (T-273)
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>
2026-06-08 13:44:34 +02:00
jpmschweitzerandClaude Opus 4.8 069879620f attribute sidechain prose/thinking to the agent, not claude (T-265); close T-267
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>
2026-06-08 12:09:47 +02:00
jpmschweitzerandClaude Opus 4.8 db6ecbd5df nest the whole sub-agent run under its Agent card (T-264)
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>
2026-06-08 11:26:09 +02:00
jpmschweitzerandClaude Opus 4.8 75f7d1ca12 restyle activity card as a holder container; background-toggle collapse (T-266)
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>
2026-06-08 11:04:38 +02:00
jpmschweitzerandClaude Opus 4.8 a7a7b04a05 fold sub-agent prompt into the Agent card; relabel you → agent prompt (T-263)
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>
2026-06-08 10:45:48 +02:00
jpmschweitzerandClaude Opus 4.8 9a8f8641f7 merge successful tool call + result into one card (T-262)
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>
2026-06-07 19:41:28 +02:00
jpmschweitzerandClaude Opus 4.8 e0fba4a3bc add Zed-style application menu bar: File / View / Help (T-48)
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>
2026-06-07 17:39:56 +02:00
jpmschweitzerandClaude Opus 4.8 c28d3d31d3 add ClaudePane + session-host widget-test harness (T-269 coverage)
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>
2026-06-07 13:45:24 +02:00
jpmschweitzerandClaude Opus 4.8 826395481c make sidebar filter boxes CLI-addressable via the MessageBus (T-270)
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>
2026-06-07 12:44:40 +02:00
jpmschweitzerandClaude Opus 4.8 f813fd4d5a rebind the Claude pane on an in-place workspace switch (T-269)
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>
2026-06-07 12:37:53 +02:00
jpmschweitzerandClaude Opus 4.8 892240b3a6 primary /clear empties the deterministic session in place (T-268)
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>
2026-06-07 11:39:26 +02:00
jpmschweitzerandClaude Opus 4.8 9905854fca shuffle the running-verb order per turn (T-255)
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>
2026-06-07 11:08:51 +02:00
jpmschweitzerandClaude Opus 4.8 3af5ddcbf1 add 8 more running verbs (T-255)
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>
2026-06-07 11:00:49 +02:00
jpmschweitzerandClaude Opus 4.8 ed97867a60 animate the running turn indicator with rotating verbs (T-255)
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>
2026-06-07 10:51:02 +02:00
jpmschweitzerandClaude Opus 4.8 076e66db80 add ClideLightbox — click image cards to enlarge (T-252)
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>
2026-06-07 10:32:16 +02:00
jpmschweitzerandClaude Opus 4.8 49f480481a wire the output dock into the layout — toggle, tabs, persistence (T-54)
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>
2026-06-07 09:58:29 +02:00
jpmschweitzerandClaude Opus 4.8 b0c9e9fccd add Output dock tab — filterable, auto-scrolling log view (T-54)
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>
2026-06-06 23:24:51 +02:00
jpmschweitzerandClaude Opus 4.8 51cfa15c78 distinguish a dev-tree clide from a packaged install (T-256)
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>
2026-06-06 21:53:01 +02:00
jpmschweitzerandClaude Opus 4.8 635ae04dae fix Esc closing the editor instead of returning to Vim normal mode
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>
2026-06-06 20:58:14 +02:00
jpmschweitzerandClaude Opus 4.8 562c17c06d add in-app "Install clide command in PATH" affordance (T-212)
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>
2026-06-06 12:05:41 +02:00
jpmschweitzerandClaude Opus 4.8 870b773381 open a file in the diff panel via clide ui open diff
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>
2026-06-06 10:50:12 +02:00
jpmschweitzerandClaude Opus 4.8 b446bd024d add image-viewer card + clide image show verb
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>
2026-06-06 10:22:38 +02:00