A `command` carrying two shell commands on two lines rendered as one row:
`.command` had `white-space: nowrap`, so the two collapsed into a single
ellipsized line that read as one command with stray arguments.
Render one prompt row per command line, and move the run-state dot out of
flow into a gutter reserved to the left of the card surface, so it neither
indents its command nor depends on the command's text metrics to line up.
The dot stays exactly one per card, on the first row. The exit status the
view carries is the whole call's and bash reports no per-command status, so
a dot per line would assert, of a line that succeeded inside a failing call,
that the line itself failed. The single visually hidden label keeps the same
scope, since one label per row would read to assistive technology as several
distinct outcomes.
Fixture turn 60's command becomes two lines, so the built-bundle snapshot
pins the layout and its dot distribution (`dotsPerPromptRow: [1, 0]`), and
the e2e adds that the dot starts left of the card surface — geometry jsdom
cannot compute. Both READMEs now also record that this package's
user-facing copy is inline Chinese, since zero-cordis atoms have no route to
`ctx.locale`; extracting it belongs to the repo-wide localization work.
The check added last commit derived its elevated set from the sheets that
already rebind, which cannot catch the omission it targets: such a set only
confirms what someone already remembered, and a surface nobody has rebound
yet defines itself as unelevated. Review found the case that proves it —
TodoPanel scrolls in .list on a --dsw-specific-tip card, the same dark rung
as the menu surface, unrebound and with the derived check green.
Resolves the set from the palette's own dark elevation ladder instead: the
surface tokens whose dark value lands on bg-layer-2 or bg-layer-3, which is
the step the l1/l2 split encodes. A new palette token on an elevated rung is
in scope the moment it is defined.
Scope is by token family rather than geometry: only --dsw-alias-bg-* and
--dsw-specific-* name a surface. The button, interactive, and markdown
families reach the same rungs while naming a control or an inline span that
no scroll container renders a bar against, and shape cannot separate them
since a floating button carries a radius, a shadow, and a fixed size —
ChatView's .toBottom pill was the false positive that showed this.
Adds the missing TodoPanel rebind. Mutation-checked all four rebinds in
turn: each is named with its surface. The palette anchoring has its own
control — narrowing the family pattern turns it red on --dsw-specific-menu.
Review found three scroll containers sitting on surfaces the rebinding
contract covers, none of which rebound: ui-primitives' shared Menu card
on --dsw-specific-menu (the surface PopupSelectView already rebinds for),
and the composer input and question composer cards, both on
--dsw-specific-input-major. Each rendered the l1 thumb, which differs
from l2 only in the dark palette and only on that surface, so a
light-palette screenshot and a code read both look correct.
Adds the mechanical check that would have caught them instead of leaving
it to inspection: a sheet that scrolls somewhere and paints a known
elevated surface somewhere must rebind. The elevated set is derived from
the sheets that already rebind, since a rebinding rule paints the surface
whose elevation it declares, so a new elevated surface joins the set by
rebinding rather than by anyone updating a list. Surface-level rather than
element-level because the card and the descendant that scrolls are
separate rules and CSS text does not say which contains which. Verified by
reverting each of the three fixes in turn: the check names the sheet and
the surface every time.
Also commits snapshots/sidebar-scrollbar/geometry.expected.md, the
resolved scrollbar style and geometry in both palettes. The aria goldens
the other web scenarios commit cannot carry a CSS-only change, since it
alters no DOM and no accessible name and leaves their trees
byte-identical. Absolute coordinates stay out: they track font metrics and
the laid-out sidebar width, so committing them would document the platform
and force a per-platform re-record.
Master replaced StateDot's ongoing ring with a pixel-art chase, so the
prompt line's run-state description named an indicator that no longer
exists. Same fix in the note, both READMEs, and the test name.
The terminal card showed no run state: a running command and a settled
command that produced no output rendered the same prompt line, so whether
a command was still running had to be inferred from the absence of output.
Lead the prompt line with a StateDot in three of its states — the spinning
ring while running, red for the same exit status that renders the status
pill, green for a clean settle. That is the same indicator a tool row's
leading icon carries, so a row and its own card cannot disagree about one
command; the row/card agreement is pinned in the ui-conversation spec.
StateDot is aria-hidden, so a visually hidden text label rides beside it,
which is what the refreshed aria goldens now record.
The e2e adds what jsdom cannot compute: the dot's color resolves to the
green success token through the real theme stylesheet, and the dot precedes
the prompt label in document order.
A non-`auto` `scrollbar-width` or `scrollbar-color` makes Chromium and Safari
discard every `::-webkit-scrollbar*` rule for that element, including
`::-webkit-scrollbar-thumb:hover`. Declaring both unconditionally left the
hover tokens rendering nowhere: the engines implementing the hover
pseudo-element are exactly the ones the standard properties silence, and
Firefox has no hover pseudo-element to fall back on. Both hover tokens and all
four elevated surfaces' hover rebinds were therefore dead code.
Measured in chromium on probe elements with `scrollbar-gutter: stable`: an 8px
`::-webkit-scrollbar` alone reserved a 30px band, and adding
`scrollbar-width: thin` dropped it to the 10px `thin` reserves.
The standard properties now sit inside `@supports not
selector(::-webkit-scrollbar)`, so Firefox takes them and WebKit-based engines
take the pseudo-elements. The WebKit rules stay ungated: an engine without
those pseudo-elements drops them as unknown selectors, and gating them would
hide them from an engine that implements them without `selector()` — the
pre-16.4 Safari the ungated form serves correctly.
Three unit assertions pin the split by source offset, which the existing
at-rule-flattening parser cannot see. The web e2e now reads the path chromium
actually takes: the `auto` standard properties as the gate's signature, the
pseudo-element sizing and track, the indirection variables resolved per
throwaway probe, and the hover declaration as cascade rule text — chromium
folds the `:hover` rule into `getComputedStyle(el,
'::-webkit-scrollbar-thumb')`, so no computed query separates the states.
The bash tool already declares the `card: 'terminal'` render intent for
both its call and its result, and host/connection/runtime already deliver
it to the browser as callView/resultView. The Web client ignored it:
rows derived from raw args, and the details panel flattened every tool's
content into one soft-wrapping `<pre>`. Column-aligned output folded into
a paragraph and a long listing stretched the panel without bound.
`TerminalBlock` (ui-primitives) renders a command as a terminal surface:
a shortened-cwd prompt line, output at `white-space: pre` in a
horizontally scrolling box, a head/tail height cap with an expand
control, an exit-code/signal status pill, and a copy control for the raw
output. ANSI SGR runs are parsed with `anser` and resolved onto `--dsw-*`
theme tokens, with literal rgb kept for values the design system has no
token for. Geometry and fonts mirror CodeBlock; the clipboard write both
need moved into a package-internal `clipboard.ts`.
Both Web render sites for a bash call consume the intent through one
derivation (`terminal-card-model.ts`), so they cannot disagree about a
command, its cwd, or its exit status: the keyed BashRow carries the card
resident below its summary row, and the render-site fallback row keeps it
behind its existing expand control. Rows cap at 8 lines against the
panel's 16.
Inline output in the chat row reverses this package's stated
no-inline-output convention, on the owner's explicit decision; the Agent
Note records the reversal and its bound.
Tests: TerminalBlock/ansi/clipboard unit specs, ui-conversation wiring
specs at every render site, a built-client-graph snapshot covering both
chat-row shapes, and a real-browser e2e asserting the no-wrap layout and
the page's own Clipboard API.
Review follow-ups: the three new notify loops (currentProvideInfo
subscribers, ui-skill lexicon listeners, late-registration controller
setup) now contain per-callback failures so one faulty consumer cannot
starve the rest, abort the list projection pass, or poison the source
roster with no disposer; controller lexicon polling drops a throwing
source with a console record like the candidate path. The ui-slash
README (both languages) now states the late-registration warm and the
subscribeLexicon contract, and the scenario suite drives a typed /name
token gaining its decoration when the roll settles with no further
input.
provideInfo(id)/maybeProvideInfo(id) lost their last external caller
when the renderer host switched to the currentProvideInfo observable;
both become private (tests assert through the public projection). The
reprojection method's name now says what it does — re-derive and
publish on change — and matches the field family it maintains.
Registrant-private reactive facts previously reached components as raw
observables that each component subscribed by hand (InputBar notices/
lexicon via uSES, SettingsRoot via a version/subscribe/getter triple).
The inject face now carries a reserved hooks compartment of bare
sources; the renderer binds each into a use<Name> selector hook through
the same machinery as the provide channel, so components consume
useNotices/useLexicon/useSections and never see a subscription
primitive. InputBar and SettingsRoot are the first two consumers.
The decoration scan read a mutable lexicon() aggregation during render
with no subscription, so a catalog settling or a child spawning after
prewarm left drafted tokens undecorated until an unrelated re-render.
The controller now publishes the aggregation as a snapshot store fed by
a new optional SlashSource.subscribeLexicon hook (ui-skill notifies on
settle/invalidate, ui-subagent forwards the session-list feed), the
composer keyboard face exposes it as an observable, and InputBar
subscribes through uSES. Sources registered after scope birth now warm
and join live controllers via a service broadcast.
The manual useState+useEffect subscription could miss a registration
landing between render and effect commit; uSES closes that window and
keeps the same version-dedupe behavior.
A provider roster change under a stable current id rematerialized every
scope's bundle but nothing notified React: SessionProvider resolved the
bundle from a current-id subscription only, so mounted entries kept the
obsolete hook/prop schema until an unrelated re-render. The sessions
service now owns an atomic currentProvide observable fed by both current
writes and roster changes; the renderer host exposes it as
sessions.provide, replacing the current/provideInfo/maybeProvideInfo
trio, and both providers subscribe to it.
Tool row summaries strip the session workspace root; the running sweep
becomes a glare-band overlay (deepsuite ShimmerText pattern); assistant
nodes that render nothing no longer split tool-row groups; block and
tool-row spacing collapse to one 16px rhythm.