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'.
The session row menu's Archive action carried ic_ds_download_outline_16, a
download tray that reads as "save to disk" rather than "put away". Swap it
for the design's archive glyph — a lidded box with a label slot — added to
the ic_ds_* set as IconArchiveOutline20.
The glyph is native 20, the first of that size in the set, so the menu's
16px icon slot asks for it explicitly. The figma export's 0.11px stroke ring
around the box contour is dropped: it restates the same contour in the same
ink, which currentColor already carries.
- 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.
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 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.
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.
The fixture route shares the requireSession guard, reads the accepted seq off
the appended event, and gains the error-arm spec the workspace.rename case
set as precedent; WorkspaceBrowser drops the unchanged-title block — confirming
the current automatic title IS the pin gesture — and both touched client
READMEs document the new dialog and the unary-settle rule bilingually.
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).
ui-workspace's two trigger surfaces each declare a single-kind directory-flow
hole (conversation.hero.workspace.directoryFlow / sidebar.workspaces.directoryFlow,
same owner contract) and keep only the trigger and the adoption: the Open-local-
folder entry renders while the surface's hole is occupied, and the occupant
reports one picked path per open through the hole's owner conversation
(open/busy/onPicked/onCancel/onError).
directory-picker-native becomes dual-face: its browser half fills both holes
with a renderless occupant driving host.pickDirectory, so the cordis.yml row
that mounts the backend also composes the client interaction — a mismatch is
impossible and a second flow package fails at client load.
With composition wiring both sides, the host.describe.directoryPicker
advertisement and the client's kind branching lose their last consumer:
the field, WorkspacesService.directoryPickerKind(), the DirectoryPickerKind
wire type, and the picker's per-open describe read are deleted. The connection
fixture now serves a deterministic pickDirectory path so the keyless snapshot
drives the full pick-then-adopt flow. ui-workspace's hand-rolled declaration
deferral is replaced by the deferRegistration helper it duplicated.
The browse interaction also presents a dialog (the in-app modal), so 'dialog'
failed to discriminate the two capability kinds; 'native' names where the
chooser runs. Package directory-picker-dialog -> directory-picker-native, kind
'dialog' -> 'native', with every seam/gateway/client/doc reference updated and
the seam Agent Note's naming rationale rewritten to match.
ds-review-bot round 4. On Windows, isAbsolute admits rooted drive-less
forms (\foo, /foo) that resolve() then rebases onto the process's current
drive; both browse primitives now gate on a fullyQualified check (drive
letter or UNC on win32, POSIX-absolute elsewhere) with a platform test
seam, per-platform unit cases, and the contract wording updated on the
seam, the backend README pair, and the error messages.
The picker-kind effect also kept a resolved 'dialog' across close, so a
backend swapped while the menu was closed could paint the stale entry for
one frame on reopen; the close arm now clears the state, pinned by a
reopen-under-pending-read race test.
ds-review-bot round 3. The seam package broke the service-package export
contract (named export only), so the config catalog filed it under Other
libraries and default imports failed; it now default-exports DirectoryPicker
like every abstract seam, and the regenerated catalog lists it as one.
The picker-kind effect also let a settlement from a superseded flow open
leak into the current one (close/reopen mid-describe, or a reconnect that
swaps the backend): the read now resets the affordance on every open and a
cleanup-toggled flag discards obsolete settlements, both directions pinned
by jsdom races.