Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it
prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`,
`verify-translation-pairing --write` for the touched bilingual pairs,
`gen-doc-graphs`, and one typert snapshot whose ids embed character offsets.
`pnpm run rescope-vendor --check` verifies the result.
Renames nine vendored packages (cordis, cosmokit, schemastery and the six
@cordisjs plugins) and every reference that resolves them: manifest names and
dependency keys, module specifiers including declare-module merges, cordis.yml
plugin names, tsconfig paths, every Markdown fence, and `docs/` prose.
Directory names, upstream versions, and dependency ranges are unchanged, so
vendor/README.md still reads as an upstream snapshot; its manifest table gains
an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed
at each fork's origin.
The tutorial tier follows the rename end to end: its yaml fences named plugins
the Loader can no longer resolve, its `ts ignore-check` fences disagreed with
the compiled fences beside them, and its prose quoted both. The contracts that
told readers to keep upstream names — the root convention and the vendoring
cookbook's tree comment and manifest invariant — now say to rescope instead.
Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle
purity gate now names the vendored libraries a browser bundle inlines, and the
files where a bare `cordis` is an agent-preset id keep that product data.
Delete design-session citations (decision/audit/plan ordinals, stack
positions), change narration, review choreography, and reviewer-addressed
justification from comments, JSDoc, docs, READMEs, Agent Notes, tests, and
generator templates; restate every affected fact as current-state contract
prose. Fix generated docs at their sources and regenerate the catalogs and
cordis-surface regions; re-paste type-equiv blocks; update every bilingual
counterpart and re-record the pairs. Record the citation rule in the
committed-artifact-citations Agent Note.
The task registry has run every background bash, pwsh, pty-send, and
one-shot subagent since it landed, but only the model could read it: a
human at the Web client could not see that a build was running, tell a
finished task from a stuck one, or find its outcome anywhere but the
`run_in_background` tool card that printed an id and never updated.
Task state now reaches the browser as one whole-snapshot `session/tasks`
mux frame per session, pushed at every registry commit that changes what
that session can see. `TaskService` gains `onTasksChanged`, which is
owner-granular because owner-disposal removal is a change no per-task
record can express. The carrier reads the exact owner the listener hands
it, so a push stays correct while that scope tears down, and reads the
baseline through the non-resuming `ctx.agents.get` so listing never
revives a cold session. The client keeps a last-wins mirror on
`SessionListState`, and a new `dsh-client-ui-task` package renders it
beside the subagent catalog — rendering nothing at all until the session
has a task, so an ordinary conversation grows no new chrome.
Streamed per-task output and human-initiated cancellation are separate
phases; the note records why neither has to undo this channel, and why
no Web path may call the consuming `ctx.tasks.read()`.
A session that stops running while it is not the selected session arms a
green 'done' reminder dot on its sidebar row, so the operator notices a
finished background session and returns to it; opening the session clears
the dot, and a re-run re-arms it on completion.
SessionManager owns the reminder set (a sibling of the waiting-approval
bit): a running->idle edge of a non-selected session arms it, select()
consumes it, removal prunes it, and it survives connection generations.
The bit rides SessionListEntry/SessionSummary into the workspace browser
rows, which render the existing StateDot done state (running keeps the
spinner) and label the hover card '已完成/Completed'.
- lifecycle-chrome's second scaffold staged its workspace under the OUTER
scaffold's temp root, coupling two supposedly independent worlds and
leaving the aria scrub root wrong; it now uses its own workspaceCwd.
- The direct-open path now carries the same `flowBusy` gate that disables
the equivalent menu entry, so an occupant re-registering mid-adoption
cannot raise a second flow.
- A composition with no directory-picker no longer opens a zero-entry
popover on the hero anchor: with nothing to pick and nothing to add,
the gesture shows nothing. Both behaviors gain a unit test.
- Brought three partially superseded Agent Notes current (the native
picker, the workspace UI product flow, and the sidebar browsing split),
cross-linked to this decision, both languages re-recorded.
- Corrected this Note's own Testing section: the shared e2e helper stages
and adopts its directory, it does not create one in-dialog — only
workspace-management does. Named the client-seam and CLI-README residue
in the follow-up TODO alongside the wire branch.
Node >= 21 exposes a global `navigator` reporting the machine's own language,
so gating detection on `navigator` let a non-browser boot of the client tree
resolve to `en` instead of the documented fallback; `window` is the browser
test. `navigator.languages` is spec-required but absent on some embedders and
older WebViews, where spreading it would throw at boot, so the walk tolerates
its absence and `navigator.language` covers that host.
The per-spec pin boilerplate collapses into one suite-level
`usePinnedBrowserLanguages('zh-CN')`, which owns the rationale in
dsh-client-test-runtime, and the English-browser e2e scenario now clears the
console warnings channel too — its page has no closing inventory spec.
Both Workspace surfaces offered "Open local folder…" and "Create a new
workspace" for one outcome. The browse occupant already carries its own
New folder affordance, so picking a directory covered creating one; the
name dialog only added a second vocabulary and a create target the
operator could neither see nor choose.
The surviving entry is named after the outcome — "Add workspace…" — and a
menu now appears only where there is something to choose between: with no
Workspace listed (the add-only sidebar header, or an empty hero list) the
anchor gesture raises the directory flow directly instead of a one-row
popover. An empty list counts as final only after the list baseline lands,
and a composition with no directory-flow occupant hides the sidebar button
rather than offering a dead one.
WorkspaceCreateFlow becomes WorkspacePickFlow (createOnly -> addOnly) and
the injected createWorkspace narrows to { path }. The host's
workspace.create({ name }) branch and `dsh web --workspace-root` lost their
last product consumer; both are marked at the call site for a follow-up.
A first visit resolved to Chinese regardless of the browser: LocaleService
read `dsh.locale` and fell straight back to `zh` when nothing was stored,
ignoring the languages the browser already states it reads.
The initial locale now resolves through three ordered sources — the persisted
preference, then `navigator` (first entry of the ordered language list whose
primary subtag names a shipped locale, so `zh-Hans-CN` -> zh and `en-GB` ->
en), then `FALLBACK_LOCALE`. An explicit choice still wins and nothing writes
the detected locale back to storage, so "has the user chosen?" stays a
question only the stored value answers.
Specs asserting the shipped Chinese copy now state the browser they assume:
the web e2e scenarios open their page with `locale: ZH_BROWSER_LOCALE`, and
package specs pin it through the new `pinBrowserLanguages` test helper.
`settings-chrome.e2e.ts` gains an English-browser scenario as the
assembled-app proof.
A blank row is a provisional placeholder: nothing has happened in it,
so the row verbs (rename/fork/archive) and a 'now' stamp would act on
content that does not exist. The trailing cells and the hover card's
time line stay off until the first prompt lands.
Public snapshot state stays in the store engine's plain-data vocabulary
(immer drafts reject Sets without the MapSet plugin, which stays off):
manager/service/contract carry readonly SessionId[] in Host order, and
the tree derivations build their own transient Set — the
expandedProjects pattern. Membership-unchanged installs still keep the
array reference for Object.is short-circuits.
The visual-only Delete session placeholder becomes a wired Archive
session action: no confirmation dialog (non-destructive), failures stay
console diagnostics. tree.ts hides archived sessions in every
derivation (workspace groups, Ungrouped, search, flat list) through the
sessionVisible predicate. The workspace-management e2e pins the archive
round trip across reload.
Conflict resolutions:
- `session.list`: master's projection columns fold into the PR's cancellable,
batched `listVisibleSessionSummaries`, which `session.search` shares as its
visibility baseline; master's goal helpers stay beside it.
- Client sessions face: master narrowed `ctx.sessions` to `ISessions`, so the
search verb and its protocol-constant bound are declared there and the
test-runtime double implements them (recorded, empty page unless a scenario
stubs hits).
- `WorkspaceBrowser`: master's per-row Rename wiring rides the PR's search
results view; the tree keeps the PR's query-free derivations.
- `dsh web` bin: the PR's shutdown-handlers-before-readiness order with
master's boot-time LAN address snapshot.
- `session-query-sqlite`: master's `SCHEMA_VERSION` 7 stands; the PR's bump
carried no schema change.
- Specs: master wraps assistant/steering message payloads and requires an
`application/json` carrier request, so the search fixtures and tests follow.
- Web aria goldens keep master's recording plus the PR's search placeholder;
the navigation-panes inventory keeps master's terminal-card golden next to
the PR's search-results golden.
Sink the behavior previously held only by the apps/web jsdom snapshots
into the owning packages, each bench mounting the real apply on the
production slot machinery with fixture-fed data:
- ui-conversation/assembly-surfaces: the todo_write turn reaches both
product surfaces (keyed toolview row + dock plan strip via the todos
projection) and the strip follows projection retirement; the keyed
bash row carries its resident terminal card while the fallback row
reaches one through expand; the locked no-session view state; the
composer textarea surviving the blank→active conversion as the same
DOM node; the promptError alert strip with the machine-restored
draft; one summary update re-labeling the breadcrumb.
- ui-workspace/rename-assembly: the session-rename chain (row menu →
dialog → the injected renameSession hop → ISession.rename with the
edge-trimmed draft → dialog close and row re-label from the list),
plus the rejected arm keeping the dialog open with the error.
- runtime/workspaces-service: startInitialSelection — connects the
recent Workspace once both baselines are ready and opens the session,
stays idle with a current session or no recent target (double start
fails loud), and a failed connect returns to waiting and retries on
the next list change.
Component-level arms stay in the existing package suites; these files
prove only the assembled wiring.
The row menu's Rename item opens the same dialog pattern as workspace rename
(no client-side conflict rule; the host normalizes) and resolves through the
session face via ctx.sessions.binding. Assembled-app snapshot covers the
row-menu -> dialog -> unary-settle flow.
The browse level now streams through opendir into a name-sorted window of
maxEntries + 1 candidates (boundedInsert), so memory stays O(maxEntries)
no matter how many children a directory holds and enterability probing
touches only windowed candidates; a windowed broken symlink is not
backfilled since the eviction already marks the level truncated.
schemastery joins the package's runtime dependencies (the source launcher
and isolated installs failed to resolve the value import). The
folder-error dialog's Choose again goes inert while the flow hole is
empty, and the withdrawal effect also keys on the open transition, so a
flow can never open over a hole nobody serves.
- Directory-flow occupancy moves onto the inject face's reserved hooks
compartment: apply publishes a stable observable per surface and the
renderer binds useDirectoryFlow — no hand-rolled component subscriptions
(the client contract's channel for registrant-private reactive facts).
- The native flow's alive guard re-arms in effect setup: StrictMode's
development replay ran the cleanup once and every later outcome was
discarded.
- NativeDirectoryFlow moves to a package-internal module; ./client exports
only the Loader surface, tests import the internal module directly.
- The composition swap comment no longer advertises -browse as a complete
swap before its dialog lands (stacked follow-up).
hasDirectoryFlow was a plain per-render read: a flow plugin unloading (HMR)
while its dialog was open left flowOpen stuck with nobody to cancel,
permanently disabling the workspace actions. Occupancy now rides
useSyncExternalStore over the hole's registration subscription, and an
empty hole withdraws an active flow; the menu entry also reacts to
activation without a reopen (ds-review-bot).
- While a picking flow is open (native chooser pending, browse dialog up) or
its pick is being adopted, every other menu action disables: a late outcome
must not race a concurrent selection or creation (ds-review-bot warning).
- ctx.directoryPicker joins the architecture Capability Services map (both
languages); neighboring rows condensed to keep the doc inside its ceiling.
- directory-picker-browse documents that its client half lands in the next
stacked PR: a -browse composition today hides the picking affordance (the
documented empty-hole default) rather than misbehaving (ds-review-bot
critical; the dialog itself ships in #821).