Two overlong lines wrap (fixture permissionSelectOf signature, apply.ts
type-import list) and three test-side unnecessary assertions drop
(eslint --fix).
The five ui-conversation machinery specs (apply-inject, chat-apply,
chat-toolview-slot, service-orchestration, selection-survival — now .tsx)
and the web app/app-shell specs assemble through the runtime instead of
hand-built Context + SlotsService + fake-session scaffolding per suite.
Session behavior mocks are typed against ISession, so incomplete fakes
fail at compile time.
Plan mode is entered through /plan only — the select control is retired.
The conversation.input.plan seat (now right of the access-mode control)
renders a read-only Plan chip while the projection's effective target is
plan mode; its hover x executes /plan off, and the chip follows the folded
target (appears on /plan immediately, disappears on /plan off) with frames
correcting either way. While plan mode is targeted the composer textarea's
placeholder switches to the plan-task wording — InputBar reads the same
projection through the standard-kit useProjection (the TodoDock posture:
a type-only key merge, no domain service edge), and owner placeholders
still win.
PermissionSelect returns as the Access seat's wired occupant: options and
the current value read from the 'permissions' projection through the
standard-kit useProjection (no fetch, no mount timing — the resident
composer's mount-once fetch bug dies with the fetch), key absence renders
nothing (permission-less host, or a Draft with no session), and a pick
submits the '/permission <preset>' command line through the new
ComposerBarInjected.command callback (Session.command = command.execute
admission; the pushed projection frame lands the confirmed value). The
READONLY_OPTIONS placeholder and its local state leave InputBar.
The connection fixture mirrors the host: a permissions unit fold (three
knob events over the fixture preset table), the projections block +
baseline/push frames carry the key, and /permission joins the command
catalog with the same switch-through-knob-events handler shape.
The session.permissions/setPermission unary pair, the PermissionOption wire
DTO, the client Session wrappers, and the fixture/fake mirrors all leave the
wire: the read side moves to the 'permissions' session projection and the
write side moves to the /permission command in follow-up commits, so the
web protocol gains no permission methods at all.
The pendingSwitches + prompt-submit flush + hasOpenTurn move also goes.
Knob events no longer need turn enclosure: the persistence scanner keeps
standalone events after the last turn/end as part of the preserved prefix
(remove-synthetic-log-only-turns), none of the three knob invariants demand
an open turn, and the setters append bare events. An idle switch commits
immediately; hasOpenTurn stays a user-approval private fold (its audit pair
is the one contract that still requires enclosure).
The old PermissionSelect chip and its mount-time fetch die with the RPCs
(the resident composer broke the mount-once assumption); the projection-fed
replacement lands with the Access seat swap.
39 conflicts resolved per the reattachment plan (missions worktree-projbiz
0728-1859): baseline wins for deleted packages (host/runtime, old ui/acp,
ui-sidebar Rows/tree) and retired specs; unions for wire-layer exports and
client summary fields; the approval takeover, waitingApprovals tracking, and
PendingApproval domain face carry over onto the master structure. The two
new host specs follow the runtime->apiproxy rename. Dead PR-side wiring
(ConversationInjected permissions/setPermission spread, InputBar controls
prop, boot.ts sandbox composition) resolves to master and its replacement
lands in follow-up commits.
plan-mode contributes the RFC's complete double-event example: command/run
records named plan set the wanted target (off -> false, else true),
plan/mode commits and clears it, and view derives {active, pending}.
Pending is thereby a pure replay quantity recovered from the log alone.
The plan key merges into SessionProjectionMap from src/types.ts, served
through the new ./types and ./client outlets (session-title template);
compositions without the registry are unaffected.
Sweep the silently auto-merged leftovers of the superseded #587 wire layer:
the PlanModeState interface and planMode/setPlanMode entries in the sessions
API and rpc map, the fixture plan RPC spec, the composer.controls README
paragraph and spec assertions, the old-format plan-mode snapshot, and the
two Agent Notes written against the RPC design (to be rewritten for the
projection reattachment). ChatView/PendingCard keep the approval-only
pending rendering; QuestionComposer improvements ride along unchanged.
Resolution follows the reattachment plan: the #587 wire layer (planMode/
setPlanMode RPC, prompt.planMode, client Session projection fences) is
dropped in favor of the session-projection base now on master; master
structure wins in all shared files. Kept from the PR side: the approval-only
pending filter in ChatView (questions render solely through the composer
takeover) and the auto-merged QuestionComposer improvements. The deleted
host/runtime package and retired test carriers are removed with master.
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 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.
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.
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.
Lint: eslint --fix over the merge-crossed projection/command files (arrow
parens, trailing commas, unnecessary assertions), Extract<> replaces the
keyof-map & string intersections no-redundant-type-constituents rejects,
the fold-adapter's merge loop drops its non-null assertions for a
bounds-carrying cursor, one JSDoc line wrapped under max-len (api-catalog
regenerated). Snapshots: the four TUI goldens re-recorded for the merged
event-count shift (the durable command lifecycle adds one event to the
seeded diagnostics log). The headless advanced-toolchain snapshot passes
on CI and fails locally in this sandbox both with and without these
changes (30s child timeout — environment-bound, tracked in the ledger).
- 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
commandId crosses three boundaries (session log, wire admission response,
client flow pairing), so per the branded-id rule it becomes
Branded<'CommandId'>, declared in a new pure @deepseek-ai/dsh-commands/brand
outlet (the dsh-llm/brand shape: type + constructor, no Context merges, so
wire and client programs can name it without loading the host plugin). The
event payloads, CommandExecution, and the executor mint carry the brand; the
wire schema gains commandIdSchema as the domain's single brand-cast point
(the approvals precedent); CommandNode and the fixture's fabrication cast
follow type-only.