A composer draft past the 14-line cap could not be scrolled: the caret and
the selection moved, but the words stayed frozen at line 1, so the tail of
anything longer than the cap was unreachable while writing it.
The composer paints its text in two stacked layers. The textarea owns the
value, the selection and the caret but renders its own glyphs transparent;
every visible character is painted by the decoration backdrop beneath it,
which also carries the claim-token highlight, the chips and the ghost hint.
The backdrop is `inset: 0; overflow: hidden` — clipped, not scrolled — and
nothing linked its offset to the textarea's. Below the cap both layers rest
at 0, which is why the defect hid behind every short-draft screenshot and
fixture.
InputBar now mirrors the textarea's scrollTop onto the backdrop, from a
`scroll` listener (every gesture and every caret-driven scroll) and from a
layout effect keyed on the committed draft (an edit reflows both layers
without necessarily firing a scroll event).
Scrolling is layout, so jsdom cannot show this: the unit spec stubs both
offsets and proves the mirroring paths run, while a new browser scenario
measures the user-visible fact against the built client with a DOM Range
over the backdrop's own text — after a wheel gesture over a 40-line draft
the last line is on screen and the first has scrolled out. Confirmed both
directions: with the mirroring reverted and the packages rebuilt, the
golden reads `last draft line is on screen: false` while `textarea moved:
true`.
master's locale rollout renders the details Output section title through
t('details.output') (输出 under the zh seat the test uses). The spec still
asserted the English 'Output' literal and failed. Match the sibling
terminal-card suite's localized assertion.
master's locale rollout made `t` a required prop on GenericToolCardProps and
the details slot; the web-card spec predates it. Supply the makeTranslate seat
the sibling suites use.
The base (feat/search-presenter) renamed the search result view's discriminant
from `kind` to `shape` and removed the view's `content` field (a UI without a
card now falls back to the raw tool/result content). Adapt the web consumer:
- searchCardModel switches on `result.shape`; SearchBlock's own `kind` prop is
mapped from it.
- The truncation recovery footer reads the block's raw `content` (where the
`Full … stored at …` locator now lives) instead of the removed view content.
- Fixture grep/glob views use `shape` and drop `content`; the recovery footer
rides the raw tool/result text.
- Tests and the bilingual Agent Note follow the rename and the recovery source.
The base's ReadResultView now requires `offset` (persisted so an empty window
still knows its start). The connection fixture's read sample and read-card.spec's
resultRead helper supply it (the window's first line, 41).
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.
The coverage lane caught a regression in the prior commit: FileMutationRow now
passes the tool's raw path to the injected openFile (which resolves against cwd
in apply.ts), but diff-card.spec still asserted the row pre-resolved. Assert on
the raw path instead. Also fix the built-boot diff assertion to match on the
line body and footer text — the `+ ` prefix is a CSS ::before, absent from
textContent.
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.
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.