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.
unbound-method flags destructuring a method-style member (method
signatures are bivariant and exempt from the this-context check).
These contract members are all plain callbacks — declare them as
property-style function types so consumers can destructure them
without a false this-binding hazard. Type-level only.
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.
Both todo one-line surfaces truncate the active hint with overflow: hidden
and text-overflow: ellipsis. A "+N" appended to the first active task's name
therefore sat at the far end of the truncatable text, so a long task name or a
narrow viewport clipped exactly the part that reports the other running tasks,
leaving a parallel plan indistinguishable from a sequential one.
planSummary now returns activeContent and activeExtra as separate fields
instead of one joined activeHint, and each surface renders the count in its own
flex: none span beside the ellipsized name: .activeExtra in the collapsed plan
strip header, .extra in the todo_write row. Putting the count in front of the
name was rejected — the task name is what the reader looks for first.
The parallel-plan cases in todo-panel.spec.tsx now assert the count is a
separate element from the name, and both fail if the two are rejoined. The
assembled web snapshot re-records: the flex gap supplies the visual space, so
the transcript reads "实现 fixture 样本+1" with no space in the text nodes.
Lifting the single-in_progress cap makes a list shape reachable that the
web surfaces never received. Two sites derived their one-line summary with
todos.find(t => t.status === 'in_progress') — the collapsed TodoPanel header
and the todo_write row — which was total under the old cap and silently
dropped every active item but the first once several could match: a plan
with three running tasks collapsed to the name of one. The expanded list
was always correct, so neither PR's tests covered it.
Both sites now take planSummary in contract/todo-plan-model.ts, the
domain-shared face both the skeleton and toolviews domains may import; the
duplicated derivation was why one find could be fixed while the other
stayed wrong. The hint names the first active item and suffixes +<n> for
the rest, so the collapsed line reports how many tasks are running.
The web fixture's todo sample now runs two items in_progress, so the
assembled web transcript replays a parallel plan: the row reads
'1/4 已完成 · 实现 fixture 样本 +1' over the built bundles.
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 automation-only ACP bridge deliberately omits todo presentation (its
edge test asserts plan updates are omitted; the todo-write tool note records
the mapping's retirement). Chinese counterpart updated, pair re-recorded;
the TodoPanel header comment drops the same claim.
- 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.
- Extract the shared New Session action into WorkspacesService.startSession
(sidebar button and workspace browser both delegate; recent-Workspace
targeting and the no-workspace clear live in one place).
- Fold the chip-insertion transaction shared by insert-ref and paste-upgrade
into one InputMachine helper.
- Share the fixture's session-not-found guard across the sessionId-addressed
catalog routes.
- Drop the AppFrame baselines-ready loading gate (user ruling: the bare
status line reads worse than the shell's own pending rendering); both
column occupants mount from first paint.
- 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.
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.
ds-review-bot finding: a growing fence retokenized on every chunk
(quadratic main-thread work). MarkdownText gains a streaming flag —
the streaming partial renders fences through the plain arm and the
finalize swap highlights once; AssistantMarkdown threads its existing
flag. (The zh Agent Note pair the review also flagged landed earlier
on this branch.) New spec pins plain-while-streaming and
highlighted-after-finalize.
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.
The bridge replaces its serialization queue with a pool that reuses the
native concurrency contract: submissions classify through
registry.executionMode (fail-closed isConcurrencySafe), start strictly in
submission order, overlap up to the validated maxParallelSubCalls config
(default 10; 1 restores serial), and exclusive calls drain the pool, run
alone, and bar later calls. Each started sub-call logs a
tool/code-dispatch-start event at pool entry; the existing
tool/code-dispatch settles the pair (started ⇔ settles exactly once;
abandoned queued calls log neither). SDK prompt guidance now states the
true Promise.all contract — re-recorded across every code/both-mode
snapshot (plus the stale cordis-dynamic-toolchain fixture gaining the
required description arg).
Client: CodeSubCall widens to RunningToolCall | ToolResultNode — starts
land the running shape (rows wear the native running ring), settles
replace in place preserving start order, callTime pairs to the start
time. Fixture emits start/settle pairs; jsdom pins the running sub-row;
runtime specs pin in-place settlement and out-of-order completion.
The client indexes tool/code-dispatch events into
ConversationSnapshot.codeDispatches (parent callId -> ToolResultNode-shaped
sub-calls; live mux and history replay build the identical index). ChatView
renders each run_code parent as the new code variant (description summary,
program as the expanded monospace body) with its sub-dispatches as
always-visible indented rows — every sub-row dispatches through the SAME
keyed conversation.chat.toolview hole with the same GenericToolCard
fallback, so custom registrations (bash sample) take over sub-rows exactly
as top-level rows. The details panel resolves sub-callIds to full logged
args and complete output through the native path.
Evidence: fixture turn 64 + built-bundle jsdom snapshot, real-machinery
jsdom suites (nesting, error state, details, running parent, reference
stability), and a recorded code-mode browser e2e round (keyless replay +
aria golden). Scaffold gains a toolsMode patch knob.