Frame0 wireframe for the app settings UI (design step under T-8). A
centered modal with a left sidebar of sections (Claude is one), inline
per-field scope (no header toggle): each field shows its EFFECTIVE value
+ a source tag, edits default to Project, and a field's tag promotes it
to Always (global, all clide) or resets. Editor category fully rendered
(number/select/toggle/external-file + override shadow).
Scope tag becomes an icon: folder (project) / globe (always) /
circle-dashed (default), recorded on T-302. Filed T-313 (native Phosphor
glyph preview card — the vehicle to pick icons in-pane) and T-312 (image
card caches by path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Filed under UI epic T-276. Adds a fourth permission-card option:
a deny carrying a preformatted note asking Claude to retry the
action in a simpler format, with an explicit clause not to add a
memory or rewrite permission config (avoids continuous fiddling
with the permission surface). Working label 'Deny & simplify',
tooltip via ClideButton's existing tooltip param. Captures the
escalation-in-context design note. Persisting the changelog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Core Workflow ran straight through and tended to fan out a
whole set of screens unprompted. Reframe it as a design
conversation: author one screen, show it, then STOP for explicit
user approval before building the next (loop back to step 2 per
screen). Multi-screen requests become a one-at-a-time queue.
Closes T-307 (under UI epic T-276).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capture the three card categories (dialog / simple / collapsible), the
ClideCollapserCard rules, and the shared card-spacing constants so future
conversation-panel work follows the settled model. Closes T-305.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Filed under UI epic T-276. Prompt-card number shortcuts only
match the top number row (_digitKeys = digit1..digit9); numpad
1-9 fall through. numpadEnter is already handled, so numpad
parity is partial. Persisting the ticket changelog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Filed under UI epic T-276. Clicking the dimmed area beside a
smaller-than-94% image doesn't close the lightbox today — the
host backdrop only reaches the 6% margin. Persisting the ticket
changelog (no code change to piggyback on).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude's TodoWrite/TaskCreate task list is currently neither parsed
(transcript_reader skips the event) nor rendered — invisible to the user.
Ticket covers modelling it from the transcript and docking a compact,
collapsible, display-only list directly above the composer in the Claude
conversation view. Filed under the UI epic T-276.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bookkeeping only — these all had DONE/FIXED completion notes (and landed
commits) but were never transitioned off in_progress/ready:
T-29 editor respects .editorconfig (d5da614)
T-292 pty session_test flake — serial pty pass (0231cb4)
T-236 pasted-image @path thumbnails (7fb1a10)
T-254 pasted-image inline render — closed with T-236 (7fb1a10)
T-241 ultrawide widget-test coverage (1411e19)
T-253 clickable conversation links (1e8e37c)
T-293 per-repo theme persistence (e7cbdcd)
Review queue is now empty; no open work left in active/ready.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
First slice of the conversation-panel card unification. Adds the
category-3 collapser primitive (lib/widgets/): a list of 1..N inner item
cards, collapsed ticker <-> expanded framed inner canvas, with the agreed
chrome — color drives the border + chevron/label tint, a fixed-width
counter slot, the status icon hard against the right edge, chevron hard
against the left edge, background + caret toggle (D-78 tail-follow). The
aggregate status/count/echoed-title are caller-computed, so the widget
stays free of conversation semantics; inner items keep their own per-item
status.
Also adds clide_card_metrics.dart — shared spacing constants (gap, radius,
header padding, counter slot width) for all three card categories.
Not yet wired into the stream (no user-visible change); migration of the
group/tool cards follows. Widget test + golden included.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T-306: card the injected "context" conversation block to match the
collapser-card frame the rest of the tool/group cards use, keeping the
D-78 muted/collapsed de-emphasis.
T-307: gate the frame0-wireframe skill on user approval after each shown
screen — wireframing is a design dialogue, not a batch generation run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two collapser-card families (ClideHolderCard group container, T-266; the
ConversationCard merged tool card, T-262) attach collapse differently and
render inconsistent header chrome — status mark placement, no fixed
counter slot, color knob on one but not the other. File T-305 (under the
T-276 UI tracker) to extract one ClideCollapserCard primitive both grab:
a `color` for border/text fidelity, a fixed-width counter slot, and the
status icon hard against the card edge.
Adds the Frame0 wireframe (collapsed color variants + expanded nested
cards) at docs/design/wireframes/cards/collapser-card.{json,png}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pql integration tests (test/pql/client_test.dart) and pql.* daemon
handler tests (test/daemon/pql_commands_test.dart) each spawn a real
`pql` process against the shared on-disk .pql/pql.db. In the parallel
test pool, concurrent invocations contend for the SQLite lock and flake
with PqlException(69) (db busy) — surfaced reliably by the pql 1.10
record_id migration. They pass one-at-a-time, so isolation is the fix.
Tag both files @Tags(['serial']) and add a --concurrency=1 serial pass
to ci/test_core.sh (pty + serial together), mirroring ci/test.sh's
existing serial handling. The error-path companion uses a fake binary,
so it stays parallel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrites the committed .pql/changelog/ from the old T-NNN-keyed format to
the record_id identity model (pql 1.10.0, D-26): tickets keyed by a ULID
record_id, the friendly T-NNN in ticket_idmap, references and hashes
recomputed. Produced by the one-off cmd/migrate-ids in the pql repo.
Other clones: install pql >= 1.10.0, then rm .pql/pql.db && pql plan
rebuild to regenerate the local DB from this changelog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Open Folder on a non-repo path" widget test wedged the runner ~10
minutes on a _RawReceivePort teardown hang. Root cause: project
validation shelled out to `git rev-parse` via Process.run, whose exit
ReceivePort leaks under the fake-async widget-test harness.
The KernelFixture now injects a pure-Dart `.git`-walk validator
(synchronous existsSync/typeSync, no native port), so the open-folder
flow is subprocess-free. Un-skipped the test and scoped the switcher
tap to the hat-bar ClideTappable to disambiguate it from the welcome
overlay's "clide" wordmark.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The composer (and any bottom-most pane content) ran flush into the
window's bottom resize-drag strip when the status bar was hidden, since
the bar normally covers that edge. Reserve ClideResizeBorder.edgeThickness
as a bottom inset in RootLayout whenever the status bar is not visible, so
the interaction zone bottom-anchors consistently either way.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause (confirmed by standalone reproducer): the Open Workspace
folder picker. GTK's GtkPlacesSidebar enumeration inside
gtk_file_chooser_dialog_new(SELECT_FOLDER) builds GFileInfo objects
without standard::size, then calls g_file_info_get_size() — a
GTK-internal bug that emits a GLib-GIO-CRITICAL pair on every pick.
Ruled out the T-138 clipboard channel.
Two-part fix:
- Switch pickDirectory to GtkFileChooserNative (portal-backed; runs
out-of-process and silently in sandboxed/Flatpak builds).
- Install a narrowly-scoped GLib-GIO log handler that drops only the
"g_file_info_get_size / without standard::size" message and forwards
every other GLib-GIO critical untouched — the universal fix for the
non-sandbox fallback path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
clide://open?path=&line= translates (in parseArgv) to editor.open, so an OS
scheme invocation routes through the existing CLI→IPC path into the running
window — single-instance for free, no new code path. Registered the scheme:
linux/clide.desktop MimeType x-scheme-handler/clide (Exec already passes %U) and
macOS Info.plist CFBundleURLTypes. Parser validates the action/path/line.
Linux works end to end (the OS passes the URL as argv). macOS URL DELIVERY (the
AppDelegate openURLs callback → forward into this path) is a follow-up — the
scheme is registered, but the native hook needs a real macOS machine to verify,
so it's not shipped blind. Also drops a stray import in clide_markdown_test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add setSurfaceSize(tester, width) to the widget harness — the reusable way to
test width-sensitive layout at an ultrawide surface (a wide SizedBox under the
default 800px surface is clamped, so the view's physicalSize must be set), the
foundation T-241 asked for.
Ultrawide cases on the surfaces most prone to width-proportional bugs (the T-239
class): ClideMarquee stays static when a line fits a 3440 slot (it was only
tested narrow); the quick-open palette stays width-capped, not stretched edge to
edge. The status bar already covers 600+3440 (T-239).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
Both under the UI tracker epic T-276. T-300: linkify file-path
references in the conversation so they open in the editor via
`clide editor open`. T-301: show in-repo paths relative to the repo
root — design open, needs discussion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Two fixes to the just-shipped collapse toggles:
- They lived inside the centre StatusbarHost, so they shifted when a pane
collapsed and the bar resized. Move them to the OUTER status-bar row ends
(screen edges) so they stay put. StatusbarHost reverts to its prior layout.
- The toggle was a const widget that read isCollapsed in its own build; Flutter
skips identical const children on rebuild, so the chevron never flipped. Pass
collapsed/visible in from the parent's arrangement listener so it rebuilds and
the chevron tracks the action direction. A 'flips live' test now guards it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Salvage the report's only actionable content — automate Dart osv-scanner
and native SHA256 verification in CI — into T-299, then drop the stray
root-level analysis doc (the rest was praise + an exploration trace;
recoverable from history at bf4b6be).
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>
Fixed ~24px caret-line cells bookend the status bar (left=sidebar, right=context);
chevron flips per arrangement.isCollapsed (inward=collapse, outward=expand) and
fires the existing sidebar.collapse / context.collapse commands — the mouse
affordance for the already keyboard/CLI-addressable action (D-6). Visual only;
no new collapse logic. Status items shift 28px inward for the reserved cells.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Composer/prompt box alignment depends on the status bar being present;
with it hidden the box sits weird. Should bottom-anchor independently.
Filed under T-276.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
set() doesn't await its file write, so a write in flight when the store is
disposed (app shutdown, or a closing test) would assert on a disposed
ChangeNotifier. Skip the post-write notify once disposed via a _disposed guard +
_safeNotify. Surfaced by the T-293 theme-persistence test flaking under the
loaded parallel run; also the correct behaviour for graceful shutdown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the interaction zone (D-78) grows for a permission / AskUserQuestion
prompt, the conversation viewport shrinks but the scroll offset isn't
adjusted, so tail content hides behind the box. Filed under T-276.
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>
Bundle subsequent edits to the same file into a single ClideHolderCard
(reusing the T-230/T-266 folding), labelled "# edits" instead of
"# steps", with every per-edit report preserved on expand. Under T-276.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ThemeController.select() applied live but never persisted, so every restart reset
to the first bundled theme. Add wireThemePersistence (facade): on theme change
write app.theme (global default) + project.theme (the open repo's
.clide/settings.yaml); on settings change — notably when a repo opens and its
project values load — restore the most specific saved theme (project then app).
The name encodes the -hc variant so high-contrast persists; an unknown/removed
theme is ignored so a stale value can't wedge startup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revise the mock — toggles no longer float on the pane edges; they live in
the bottom status bar, pinned to the center pane's left/right edge so each
slides to the far end when its pane collapses.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug found while showing a wireframe during T-294 — image-show renders into
the primary tab regardless of the active tab. Filed under T-276 UI epic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the wireframe (states A/B) for edge-anchored collapse toggles on the
center pane. Records that collapse logic already exists (sidebar.collapse /
context.collapse commands) so the ticket is scoped to the mouse affordance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settled placement — one button per center-pane edge, fixed regardless of
adjacent pane state, so the same control collapses and re-opens.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Set up a Frame0 mock to talk through collapse/expand affordances for
the left sidebar and right context pane. Filed under the T-276 UI epic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ci/test_core.sh ran 'dart test test/ipc test/pty ... test/pql' in the default
parallel pool, so the real-PTY tests in test/pty competed for fds + CPU with the
other suites and the reader isolate was starved — intermittently failing 'write
sends keystrokes to child' under load. Split into a serial --tags pty pass and a
parallel --exclude-tags pty pass, mirroring ci/test.sh. Same 571 tests; pty pass
verified stable across repeated runs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both T-278 issues — typing '-' breaking the list, and flaky Tab-to-complete —
were resolved by the D-88 ClideTypeahead migration (T-286): the popover now
narrows live as you type (ValueNotifier-bridged suggestions) and the field keeps
focus (captureFocus:false), so '-' filters normally and Tab reliably accepts the
highlight. Add regression coverage: hyphenated queries in slash_commands_test and
a composer widget test that types through a hyphen then Tab-completes.
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>
Introduce EditorSettings as the effective, source-agnostic editor configuration
the editor and save path obey. .editorconfig is demoted to one *source*
(editorconfig.dart now returns EditorSettings); editor_settings_resolver.dart is
the single composition seam where future sources — a settings panel, a clide
settings file — layer in via merge() without the editor changing.
The registry resolves settings on buffer load and, when a .editorconfig is saved
in-app, re-resolves every open buffer and emits editor.settings-changed (a hook
in save, not a filesystem watcher — the realistic case, cheaply). Buffer JSON
carries editorSettings. 100% line coverage on the new model + resolver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New lib/src/editor/editorconfig.dart: a dependency-free INI parser, an
EditorConfig-flavour glob matcher (*, **, ?, [seq], {a,b}, {m..n}), and
directory-walk resolution honouring root=true with nearest-file / last-section
precedence. EditorRegistry resolves the config when a buffer opens (exposed on
the buffer JSON for the UI) and applies end_of_line / trim_trailing_whitespace /
insert_final_newline on save, reconciling the in-memory buffer when the text
changes. 100% line coverage on the new file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record the closing amendment on D-88: base blockers fixed, every anchored
surface migrated except quick-open (deliberately left bespoke — a persistent
centred widget that shares neither ClideMenu nor anchoring). Mark T-286 and
T-288 done.
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>
External read-only analysis of clide's architecture, quality gates, and security posture. Notes one improvement area: automate dependency CVE scanning in CI (today it's manual review).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swap the composer's hand-rolled LayerLink/OverlayEntry slash popover for the
shared ClideTypeahead, driven by a ClideMenuListController for arrow/Enter nav
while the EditableText keeps focus. The key pipeline (Esc-fallthrough,
Tab-complete, history) stays in the composer.
ClideTypeahead now bridges its live suggestions through a ValueNotifier so the
popover narrows as you type — the OverlayEntry is a separate subtree that does
not rebuild with the host, so a captured list would go stale. The notifier and
open/close run post-frame to avoid rebuilding widgets during the parent's build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both the sidebar and the full-pane chat composers hand-rolled the same
LayerLink + OverlayEntry + _showOverlay/_removeOverlay + _AtOverlay. Replace
both with ClideTypeahead driven by the suggestion list; delete _AtOverlay and
the per-copy overlay plumbing. The text parsing/completion (activeAtQuery,
filterAtNames, completeAt, parseAtTag) and the Esc handler stay in the hosts.
Behaviour is unchanged (the popover now uses the shared dropdown styling);
team_chat_sidebar_test stays green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The slash and @ typeaheads are near-duplicate caret-anchored completion
surfaces. Per the amended D-88 they share ClideTypeahead (not ClideMenu): the
host owns text parsing + completion; ClideTypeahead owns the anchored overlay +
suggestion list, driven by a suggestions list. Unlike a menu it does not
capture focus or install a barrier — the text field keeps focus — and an
optional nav controller drives the highlight from the field's key handler.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root-cause of the theme-picker friction: the primitive's focus model is fine
(keyboard nav reaches a ClideMenu through the overlay), but the shared harness()
uses Overlay(canSizeOverlay) + a zero-size MediaQuery, which mispositions an
anchored follower off-screen and defeats autoFlip.
- autoFlip now reads the real view size (View.physicalSize) instead of
MediaQuery.size, so it flips correctly even when MediaQuery is overridden.
- Drop the inner Align in the follower — it pegged the panel to a corner of the
full-screen follower box and broke hit-testing for non-top-left anchors, so
end-aligned menu items weren't mouse-tappable.
- Add anchoredHarness() — a properly-sized Overlay tree for testing popover
content (the remaining migrations will use it).
Tests: keyboard nav through the overlay, an end-aligned item is mouse-tappable,
and autoFlip flips below->above near the bottom edge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rolling UI epic T-276 child. Secondary-tap context menu built on the
ClideAnchoredOverlay + ClideMenu primitives (D-88): a baseline of
copy/cut/paste/select-all driven by surface focus/selection, plus
context-aware items injected per surface (file-navigator duplicate/rename,
git-pane stage/unstage/revert). Injected actions map to clide verbs for
user/Claude parity (D-6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record the decomposition: ClideAnchoredOverlay is the shared base every
anchored surface adopts; content matches the surface — ClideMenu for menus,
a new ClideTypeahead for the slash/@ typeaheads, bespoke for quick-open and
the theme picker. Re-scopes T-288 accordingly. The base blockers (focus race,
follower untappable in the test harness) are what to fix first.
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>
The T-282 bordered-card vertical padding change (10 to 8) shrinks the
agent-run sub-cards the holder-card golden renders; the merged-card golden
was regenerated then but this one was missed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The T-275 and T-279 Added entries exceeded the changelog gate's per-bullet
word cap. Move the detail to the commit bodies; keep the entries to a line.
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>
Nine surfaces hand-rolled the same anchored-overlay + row-list + barrier +
keyboard-nav pattern. Extract one owned primitive (no Material):
- ClideAnchoredOverlay (clide_anchored.dart): positioning + lifecycle —
LayerLink/CompositedTransformFollower or centred Positioned, side/align +
auto-flip on viewport bounds, full-screen tap-away barrier, OverlayEntry
bookkeeping, focus capture, Esc-to-close. Driven by a ClideOverlayController.
- ClideMenu + ClideMenuListController (clide_menu.dart): a dropdown-token row
surface (items + separators) with arrow/enter/escape nav, skip-disabled,
active mark, per-item colour/leading glyph, keepOpenOnSelect (live-apply),
and onArrowLeft/Right hooks. The nav controller is reusable by surfaces that
keep bespoke rows (typeaheads, quick-open).
Additive — no call sites changed yet. D-88 records the convention (new `design`
domain): anchored pickers build on these; modal pickers stay on DialogRouter.
Tests: clide_anchored_test (open/close, barrier, Esc, centred, clean dispose)
and clide_menu_test (list-nav skip/wrap, select + onClose, disabled, Esc,
keepOpenOnSelect; pure ClideMenuListController cases).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror assets/fonts/phosphor/codepoints.csv into a readable, greppable
markdown table at references/phosphor-glyphs.md — codepoint · kebab · Pascal,
with an "In clide" column flagging the ~45 glyphs already wired into
PhosphorIcons (reach for those first). Generated by scripts/gen-phosphor-
glyphs.py (regenerate after a font bump). Point icons.md + SKILL.md at it.
Supports glyph-picking for T-275 (composer permission-mode icons) and any
future icon work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The static hover-tooltip bubble beside the accept-edits icon read like a label
on the button. Remove it; the resting controls are now unambiguously icon-only,
with the label appearing only in the open dropdown, the status-bar indicator,
and the (real, on-hover) tooltip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Incorporate review feedback:
- Resting control is icon-only; the label moves to the hover tooltip, the
status-bar indicator, and the open dropdown rows.
- Status-bar mode becomes a passive, colour-coded text indicator (no
click-to-cycle) — switching lives in the composer control + Ctrl/Cmd+M.
- Add the ACTIVE (turn running) state: the existing Stop row sits above the
text box while the mode icon stays trailing it — both visible, no conflict.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frame0 wireframe driving T-275: a persistent permission-mode icon-button
trailing the composer text box, with per-mode resting variants (default /
accept-edits / plan), the open-dropdown state (active marked, bypass divided
off + guarded), and the D-78 interaction-zone state (prompt replaces the
composer; status-bar badge persists as the mirror). Embedded design notes
capture the prompt-mode interaction and the keep-the-status-badge decision.
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>
The status-bar footer marquee drove a raw Ticker whenever its content
overflowed, ignoring MediaQuery.disableAnimations — unlike the turn indicator
(T-273), which stops on the same flag. Two costs: reduced-motion users still
got the scrolling footer, and the perpetual ticker was the historical
pumpAndSettle-hang culprit (its tests cope by only ever pump()-ing).
Unify on the one mechanism: read disableAnimations in didChangeDependencies
(as running_indicator does) and gate the ticker on it via _syncTicker(). Under
reduced motion the marquee never starts the ticker (stops + resets if running)
and renders the child statically (clipped, no looped copy). Toggling the flag
at runtime starts/stops the scroll.
Tests: reduced-motion marquee does not scroll and pumpAndSettle completes (no
hang); flipping the flag off lets an overflowing marquee scroll again.
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>
`test/app_test.dart` › "Open Folder on a non-repo path surfaces the 'no
git repo' dialog" wedges the runner ~10 minutes — teardown hangs on
`_RawReceivePort._handleMessage`. It's pre-existing (reproduces at the
base commit, predates the T-267 epic) and not a one-line fix: bisection
shows it persists even with project validation stubbed to a pure-Dart
`.git` walk and `runAsync` removed, so the booted-app + open-folder path
holds a native port teardown never drains.
Skipped so the suite/gate stays green; T-280 tracks the real fix with the
full bisection.
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>