221 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 fb0179d493 align card font sizes across the conversation stream (T-344)
ClideCollapserCard and ConversationCard sat adjacent but used different
tokens for the same roles, so labels/summaries rendered 1-2px apart.
Standardise both on label = clideFontCaption (14), collapsed summary =
clideFontMeta (13): bump ConversationCard's label up from clideFontSmall,
bring the collapser's summary down from clideFontCaption. Goldens
regenerated for the affected card images.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 18:15:56 +02:00
jpmschweitzerandClaude Opus 4.8 75cf14f1cb tickets panel: per-type filter chips (T-343)
A row of toggle chips below the filter box — one per pql ticket type,
ordered large→small (Initiative, Epic, Story, Task, Bug), each with its
TicketTypeColors dot + border. Single-click toggles a type; double-click
isolates it (chart-legend solo, fully reversible); disabling the last
enabled type snaps all back on so the list is never blank. ANDed with the
text filter; all on by default, nothing persisted.

One GestureDetector owns both onTap + onDoubleTap so Flutter disambiguates
single vs double. Wireframe updated + approved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 18:13:04 +02:00
jpmschweitzerandClaude Opus 4.8 0a6d5a5ef8 format conversation_view after the resolver extraction
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:52:30 +02:00
jpmschweitzerandClaude Opus 4.8 9b02a976ec extract pick-up + file-resolver helpers for coverage
The T-339 pick-up test was the only importer of the ~450-line claude
extension.dart, pulling its (mostly UI-wiring, untestable) lines into
the coverage denominator and dropping the suite below the 95% floor.

Move applyTicketPickUp into its own ticket_pick_up.dart and the T-300
path resolver into a pure resolveWorkspaceFilePath() — both small, fully
covered, and imported by the tests instead of the whole extension. No
behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:51:47 +02:00
jpmschweitzerandClaude Opus 4.8 3134a0ac52 tickets: backlog relevance sweep (sub-T-100)
Swept the 19 backlog tickets with ids below T-100 for relevance against
the current codebase + governance.

Cancelled (obsolete):
- T-26 web-tree-sitter — contradicts the desktop-first guardrail
- T-60 workspace trust prompt — premature; third-party ext loading unshipped
- T-40 PRs tab — unscoped, no extension, data path undecided

Annotated (stale text / mixed state, work still valid):
- T-25, T-27 — app/ path prefix stale post-D-56
- T-8 — mixed Tier-6 completion (theme-picker done, settings-ui stub)
- T-55 — split: tmux persistence done, OS tray still a stub
- T-41 — likely superseded by canvas epic (T-317/D-91)
- T-67 — reframe from one-time audit to coverage ratchet
- T-81 — T-79/T-80 done, #21 obsolete, ~7 items remain

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:18:11 +02:00
jpmschweitzerandClaude Opus 4.8 3b23da052e fold the Deny & simplify denial instead of a red error (T-340)
A user-initiated denial (Deny & simplify) came back as an isError
tool_result and rendered as a prominent expanded-red "Bash · error"
block — pure noise, since the user chose it. It now folds to a muted,
collapsed "denied" card.

Built as a reusable filter rather than string-matching the note: DenyTool
carries a `quiet` flag, the session collects quiet denials' tool_use_ids,
and ConversationView renders any error whose id is in that set folded +
muted. Genuine tool failures (ids not in the set) keep the expanded-red
treatment. Adding future "expected error" cases is just adding ids.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:29:30 +02:00
jpmschweitzerandClaude Opus 4.8 a948bc8a1f pick-up sets the ticket to in_progress on accept (T-339)
When a ticket is handed to a live Claude pane (T-327), advance it to
in_progress on the receiving side of the bus — gated on acceptance, so a
pick-up with no live session stays a quiet no-op and never mutates state.
Only a not-yet-started ticket (backlog/ready) transitions, so re-picking
up a review/done ticket doesn't drag it backwards. On success it publishes
(builtin.tickets, changed) so the sidebar refreshes.

The handler logic moves into a testable applyTicketPickUp() seam; the
sidebar button now carries the current status in the pick-up payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:18:16 +02:00
jpmschweitzerandClaude Opus 4.8 dea3e70e49 fold sub-agent prompts via parent_tool_use_id (T-338)
In live stream-json sessions a sub-agent's spawning prompt is tagged
with parent_tool_use_id, not the transcript JSONL's isSidechain +
parentUuid. The parser ignored that field, so the prompt parsed as a
main-thread user turn and rendered as a blue "you" card above the
Activity Agent card instead of folding into it.

Carry parent_tool_use_id onto ConversationItem; its presence now marks
the item as a sidechain message. The sidechain fold resolves ownership
directly by tool-use id (no transcript-only uuid chain to walk), so the
prompt folds into its Agent card and the run nests under it as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 16:03:50 +02:00
jpmschweitzerandClaude Opus 4.8 026e331146 clickable file references in the Claude conversation (T-300)
Workspace file paths mentioned by Claude now linkify and open in the
editor: bare (lib/app.dart), with a line (lib/app.dart:42), backticked,
or as markdown links. Only paths that exist in the repo linkify — the
resolver gates on existence so prose (version numbers, "e.g.") stays
literal. Clicking maps to the editor.open verb, jumping to the line when
a :line suffix is present (D-6 parity).

ClideMarkdown gains resolveFileRef + onOpenFile hooks; conversation_view
resolves against the open project root + existsSync and dispatches over
IPC. Detection covers running prose, whole-content code spans, and link
hrefs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:55:53 +02:00
jpmschweitzerandClaude Opus 4.8 ec88944896 Deny & simplify note: tell Claude not to narrate the retry (T-328)
Add a clause to the preformatted deny-simplify note so Claude proceeds
silently with the simpler version instead of narrating the change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:19:15 +02:00
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