The parenthetical listed 14/16 and this branch added 20, but the set never
held to it: IconRightUpOutline14 defaults to 8, IconTreeCorner8x10 to 10,
and IconWarningOutline16 to 14. Drop the list rather than maintain one that
drifts, and retitle the icons test that carried the same generalization.
The session row menu's Archive action carried ic_ds_download_outline_16, a
download tray that reads as "save to disk" rather than "put away". Swap it
for the design's archive glyph — a lidded box with a label slot — added to
the ic_ds_* set as IconArchiveOutline20.
The glyph is native 20, the first of that size in the set, so the menu's
16px icon slot asks for it explicitly. The figma export's 0.11px stroke ring
around the box contour is dropped: it restates the same contour in the same
ink, which currentColor already carries.
Address review of the ToolRow card refactor:
- ToolRow renders a visually-hidden run-state label (row.running/failed/stopped
locale keys) so a running/failed/interrupted row is not a colour-only signal;
the StateDot and the sweep are both aria-hidden. Restores the stopped-state
text assertion in diff-card.spec at the ToolRow layer.
- The file-link button stops Enter/Space from bubbling to DisclosureRow's
whole-row keydown, which would preventDefault the key and toggle expand
instead of opening the file (the keyboard analogue of the click stopPropagation).
- search-row passes output={model.output} unconditionally, matching the other
three card rows (ToolRow renders the card over the output when present).
- Assert locale: 'conversation' in the search/web/diff registration tests.
- Add the read render-intent paragraph to the ui-conversation README and a
ReadBlock atom entry + Read section to ui-primitives (both languages).
- Update the grep fixture sample lines to the post-refactor SearchRow shape.
Refresh the code-mode-round, steering, and search-card goldens for the added
status text and updated sample lines.
Rewrite the read/search/web/diff card spec suites for the collapsed-by-default
ToolRow composition: each row now needs the conversation `t`, and the card is
absent from the DOM until the row's [data-expandable] toggle is clicked. Refresh
the built-boot and search-card assembled snapshots (they expand the row before
shaping the card) and the code-mode-round golden. Fix a fixture turn-number
collision the 985/986 merge introduced (grep reused turn 67, already the
multi-hunk edit's, so both shared one callId) by renumbering search/web/todo to
68-72. Document the refactor's behavior in the ui-conversation README (both
languages) and add the Agent Note.
The card is a React child of the wrapper, so capture-phase presses on it
reached the wrapper's dismissal handler: the first pointerdown of a text
selection closed the card, contradicting its JSDoc contract. Restrict the
immediate close to presses outside the card, keeping it mounted under a
held press (and the browser click with it), and align onPointerLeave's
grace arming with Menu (only while open).
Pin both new behaviors in hover-card.spec and update the bilingual Agent
Note.
The tail-header cap fix pushed SearchBlock's head/tail slicing arithmetic and
its copy-feedback hook over the duplication gate's threshold against the
byte-identical logic in TerminalBlock. Extract both into head-tail-cap.ts
(headTailCap) and use-copy-feedback.ts (useCopyFeedback) and consume them from
both blocks, deleting the clone rather than nudging it under the limit.
Address the ds-review-bot findings on the search card:
- searchCardModel dropped the result view's `content`, so a capped search's
`Full … stored at: <locator>` recovery footer vanished from the UI (the card
replaces the raw text). Thread it through as `SearchCardModel.recovery` and
render it below the card at all three sites, only when truncated.
- SearchRow's fallback body was gated on `state === 'error'`, so a settled
non-error call with no card (a successful nested run_code sub-dispatch, a
legacy generic result) showed only its summary with content lost. Widen it to
any settled call with `search === null`.
- searchCardModel trusted the `files`/`paths` shape the host wire schema only
string-checks; a malformed known-kind frame would crash SearchBlock. Validate
the full shape and fall to the generic path on mismatch.
- SearchBlock's restored tail file header added a row without consuming a tail
slot, exceeding maxLines by one and overstating the hidden count. Make it
consume a slot so the visible count holds at maxLines and `hidden` stays exact.
Correct the fixture JSDoc (now genuinely exceeds the row cap) and the Agent Note
recovery-text claim, sync the ui-conversation bilingual README with the search
row, and add an assembled keyless snapshot (apps/web/tests/search-card.snapshot.ts)
that pins the grep card's shape from the built bundles.
- built-boot smoke asserts [data-variant=web][data-tool=web_search/fetch]
(the keyed WebRow) instead of [data-web] (which WebBlock draws even on the
GenericToolCard fallback, so a silent keyed-registration failure passed).
- WebBlock renders an explicit empty-state note when a search returns no
answer and no sources, instead of a blank <ol>; the chat row does not
surface the raw result content, so the backend's 'No results found.' was
otherwise invisible.
- README (ui-conversation, ui-primitives) and the frontend Agent Note record
the unknown-web-kind null arm, the details-panel flattened body, and the
empty-search copy; pairings re-recorded.
Top-level read samples in read-card.spec use the read tool's real file_path
schema key (web_fetch keeps its own path key); the Input-JSON assertion follows.
The LANGS/LANG_ALIASES JSDoc no longer claims the TypeScript grammar embeds
JSX/TSX — shiki's TS grammar tokenizes JSX approximately, an accepted trade to
keep one JS-family boot grammar.
Bring the TUI diff footer onto the same terminator rule and distinct-path
count the Web DiffBlock uses (a trailing newline terminates its line; two
hunks in one file read as 1 file), so the two front ends' `+A -R · N file(s)`
footers agree. Reserve space in the diff path header for the floating copy
button so a long path no longer scrolls under it. Pass the tool's raw path to
the injected openFile (which already resolves against cwd) instead of resolving
twice. Rename the shared block-body CSS class to a card-neutral cardBody so a
terminal-spacing tweak cannot silently move the diff card. Add a same-file
two-hunk TUI unit test and an assembled built-boot assertion that the write
turn renders +1 -0 · 1 file end to end.
Only TypeScript, shell, and JSON grammars load at Web boot; the read card's
wider langFromPath extension set loads through dynamic imports on first use,
so a session that never opens a read card in one of those languages avoids
~1.6 MB of grammar modules and their synchronous init. ReadBlock/CodeBlock
re-render on grammar-load via useSyncExternalStore, picking up highlighting
once the grammar registers. ReadBlock hides the copy control on an empty
window (a successful read of an empty file settles to lines: [] with
card:'read'), matching TerminalBlock so it cannot wipe the clipboard.
Every expandable tool row shares one interaction (whole-row toggle,
icon-to-chevron hover preview) and one expanded body: an IN/OUT
gutter-labeled card with per-section 150px scroll caps and sticky labels.
toolRowModel derives result output and the error first line, terminalFailed
surfaces a failing exit as the collapsed row's red dot, a hover Inspect
pill jumps to the call's trajectory record through a one-shot store
handoff, and the chat view keeps its scroll offset across view switches.
- webCardModel returns null for an unknown web `kind` (wire from a newer host)
instead of drawing it as a malformed fetch, matching the unknown-`card` and
terminal-model wire-boundary default.
- Fixture WEB_SEARCH_RESULT/WEB_FETCH_RESULT and the source type derive from the
contract's ToolResultView via Extract, so a new contract field fails at the
type level rather than drifting silently.
- built-boot smoke asserts the web_search/web_fetch turns render their keyed
WebRow cards, giving the registration and wire projection an assembled check.
- DetailsPanel comment no longer claims the card omits content for search.
- ui-primitives README inline-Chinese limitation now lists WebBlock's controls.
Complete the stalled review pass: drop the unused useMemo import (rows flatten
inline), add SearchBlock to the ui-primitives README (both languages) with a
Search results section, and re-record the doc pairings. Search card behavior
and tests unchanged (313 pass).
The workspace browser's two hover-raised popups both died on the way to
them. HoverCard closed on the first pointerleave and rendered its card
pointer-events:none, but the card sits 8px off the anchor, so every path
to it crossed ground belonging to neither. The row action menus put
closeOnPointerLeave's handler on the portaled list, so aiming back at the
... trigger that opened it, or overshooting a list edge, closed it with no
window to come back.
usePointerGrace owns one cancelable delayed close (200ms) shared by both
atoms: leaving arms it, returning cancels it. The hover card becomes
hit-testable so resting on it holds it open, and Menu moves pointer-leave
dismissal to the wrapper span, where React's enter/leave traversal makes
trigger and portaled list one region.
Both gestures are pinned in the real browser lane; each fails without the
corresponding fix.
Register the full grammar set the read tool's langFromPath emits (python,
go, rust, yaml, markdown, html, and the rest) so a read card highlights the
same extensions the backend recognizes instead of returning undefined for
them. Rewrite highlightLines' terminator-line check to the explicit
last !== undefined form to keep a single branch for per-file coverage.
Add the running-state sweep animation to ReadRow, matching BashRow/ToolRow,
so a running read row shows executing feedback.
Use file_path (the real read tool schema field) in the turn 66 read fixture
sample, its presentCall branch, and the turn 64 run_code read sub-dispatches,
so the built-boot snapshot replays a production-shaped call and the details
panel shows the correct Input JSON.
Document why ReadBlock omits TerminalBlock's empty-window copy guard, and
correct the read-card-model {@link} and the turn 66 fixture comment.