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.
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.
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.
- StateDot ongoing: gradient spin ring replaced by an 8-cell pixel chase
(2px matrix cells, stepped trail, no tweening)
- Chat: streaming pulse block replaced by a turn-level 4-pixel chase at the
flow tail — rides the whole running turn (first-token wait, tools,
streaming) instead of flickering with partial presence
- Tool rows (ToolRow/BashRow): running no longer swaps the icon for a dot;
an animated mask band sweeps the row content, gliding off on exit via
mask-position transition
- Composer: send/stop unified on the blue fill (bigger stop glyph, static
white arrow), textarea box-sizing overflow fix, settling phase hides the
composer while replay decides hero vs docked, workspace-placeholder
fallback disables the bar
- Hero: glow moved behind (z-index) with lower opacity; tool-row hover icon
crossfade at 100ms
Hand fixes for the findings --fix cannot touch, mirroring the fixes
already applied on the fe-docs feature branch (same file, same shape)
so its eventual rebase resolves cleanly:
- restore the return the no-confusing-void-expression autofix ate in
useAbsentSnapshot (typed S | undefined; hook call kept for hook-order
stability, undefined returned explicitly);
- re-type DOM queries the no-unnecessary-type-assertion autofix broke:
getByRole<HTMLButtonElement>(...) generics instead of the removed
as-casts (the eslint program and the client tsconfig aggregate
disagree about these casts; the generic form satisfies both);
- justified eslint-disable for the deliberate legacy paths: keyCode 229
IME-composition detection, execCommand clipboard fallbacks, lib.dom
clipboard optionality, and the any-typed Reflect.get/this probes in
test fakes;
- drop the dead react/no-danger directive (eslint-plugin-react is not
loaded, so the rule never applied) keeping its shiki rationale;
- delete the tautological 'Z' comparison and the renameTarget null
check already implied by renameBlocked;
- css-module non-null assertions replaced by type widening
(Button className, TAG_CLASS Record) per the established pattern;
- misc: max-len comment wraps, void generic drop in the deferred test
helper, unused type imports, floating selectWorkspace promises voided,
member-delimiter newlines in inline type literals.
Mechanical --fix output over the newly linted .tsx files (indent,
arrow-parens, comma-dangle, member-delimiter-style, unnecessary type
assertions), plus the three generic-arrow test hooks converted to
function declarations up front: the comma-dangle fixer strips the
<T,> disambiguation comma and turns them into parse errors otherwise.
Three ds-review-bot round-8 findings on the todo display surfaces.
TodoPanel took the whole `useSession` hook and cast the snapshot to reach
`todos`, which put slot plumbing and an unchecked cast inside the presentation
component. The panel now takes `todos: readonly TodoItem[]`; TodoDock does the
selecting, matching the QueueDock posture the dock slot already establishes.
The todo row carried `onClick` with no keyboard route, so its details panel was
mouse-only. It now takes ToolRow's route verbatim: `role="button"`,
`tabIndex={0}`, and an Enter/Space handler that claims the event. The row stays
a `<div>` because a `<button>` flattens its inline spans.
`session.history`'s tail-only `todos` field was documented at the TS signature
but not in the apiproxy README pair, and the Agent Note claimed the feature
added no new wire vocabulary. Both README sides now state the tail/omission
semantics (an omitted field on a tail response is the empty plan, not unchanged
state), and the note records the one added field instead of denying it.
Tests: TodoPanel specs render the plain list; new TodoDock specs cover selection,
live follow, rollback-to-empty, and the registration shape; a new row spec pins
Enter/Space activation and non-activating keys.
The session/queued frame no longer carries steering — AgentMessage no longer
has the field. The client derives it from the same ordered turn boundaries the
host saw (a frame arriving while a turn is open joined the steering FIFO).
- TodoPanel mounts through a 'conversation.input.dock' list entry
(todoDockEntry, QueueDock posture, order -1 above the queue rows) instead
of a ConversationRoot hardcode; the inner component is unchanged and takes
useSession from the dock entry's standard kit.
- The verify-todo-display.mjs chromium probe is replaced by an assembled
keyless snapshot (apps/web/tests/todo-display.snapshot.ts, the
code-mode-fixture idiom) pinning the TodoRow summary/state, the dock
panel content, and the collapse round-trip over built bundles.
- Fake snapshots across specs gain the todos field; bilingual note/READMEs
updated for the dock mount and the snapshot.
- Keep ConversationSession mounted for blank sessions (chrome-less) so the
draft-persistence mirror stays bound in the hero; hero typing reaches the
chat store again.
- Restore the baselines-ready gate in AppFrame: empty boot snapshots no
longer flash the New Workspace hero before either baseline lands.
- Commit ordinary sends through the machine (send-committed event +
Shell.commitSend): undo can no longer resurrect already-sent content on
the default-sink path.
- Give the production InputMachine a real wall clock so the typing-run
merge window actually expires.
- Coalesce concurrent connectWorkspace creates per workspace: the summary
has no cwd until the host frame lands, so a second New Session inside
that window minted a duplicate hidden blank session.
Test-side catch-up with the session-maybe conversation architecture: the
provide channel's descriptor shape and maybeProvideInfo in fakes, the shared
chat-store handle asserted on conversation.session (the session-maybe shell
carries no store), startSession fakes exposing the workspace list snapshot,
strict session slots declining (not throwing) without a session, AppFrame's
removed empty seat and loading gate, and the hero draft asserted on the
machine (the chat-store mirror binds with ConversationSession). Plus three
lint fixes (max-len split, boolean-compare, arrow-parens/unbound-method).
eslint --fix autofixes plus manual repairs: max-len line splits
(fake-api handlers, notifier/slots JSDoc, spec signatures), charAt over
non-null-asserted indexing in slash detect/menu cores, Array.from for
code-point capping, typeof assertions for unbound-method in specs,
generic getByRole for the send-button cast, effect disposer void-wrap in
command register, and dropped unused type imports.
One highlighter for the client: a synchronous fine-grained shiki core
(JS regex engine, no WASM) in ui-primitives with an explicit grammar
allowlist (typescript, shellscript, json — aliases resolve, unknown
languages take a geometry-identical plain arm). The shared CodeBlock
component owns both arms; markdown fences, the run_code expanded
program body (typescript), and the details panel Input (json) all
route through it. Token colors live in a new ui-theme shiki.css sheet
as --shiki-* custom properties (light/dark blocks), wired through the
shell's base.css chain — tokens-only styling holds; shiki's generated
span tree is the sanctioned innerHTML path (static output, no user
HTML). jsdom specs pin token spans, aliases, both fallbacks, and the
fence route; the built-bundle snapshot asserts the highlighted program
under the code row.