Tests bring the changed sources to full per-file coverage, the READMEs and
docs describe the uplink, the invocation context, and the Peer registry in
both languages, and the generated catalogs are refreshed.
Every Typert Remote stream accepts client-to-Host items on the same
logical stream. A method declares the uplink item type in its return
type, RemoteStream<Out, In>, and reads the items through
this.ctx.invocation.uplink(); the invocation also carries the calling
Peer. Client generated methods return a handle that iterates the
downlink and exposes send/end/dispose for the uplink.
The composer's plan-mode chip rendered its label followed by a 12px
one-pixel cross, out of step with the sibling access-mode trigger's
glyph-then-label layout. The chip now leads with the 14px plan glyph
(the /plan command's own icon) and swaps it for a new circled close —
IconCloseCircleFill, a filled disc with the cross knocked out — while
the enabled chip is hovered or keyboard-focused, so the pending click
reads as turning plan mode off.
The pill's corners move from a 999px circular capsule to the compact
Button geometry (14px on the 28px row) under the theme's superellipse
curvature, matching every neighbouring control; the lifecycle-chrome
probe now pins that radius and the shared corner-shape.
* fix(web): bind the right Sidebar before same-commit mount effects
A plan review arriving while a global panel was active crashed its opener when the user returned: the frame mounts the Conversation column ahead of the right Sidebar seat, so PlanReviewOpen's mount effect called ctx.sidebarRight.openResource before the seat's passive bindService effect had published the binding. The slot runtime retired the crashed entry for the rest of the page, removing the View full plan link and every later automatic open.
Publish the seat binding from a layout effect, which runs before any passive effect of the same commit regardless of tree order. The seat spec mounts a Conversation-column opener ahead of the seat and returns from a global panel; the plan-review Web scenario delivers a review while the Plugins panel is open and expects the preview and the link after the return. Both fail before the fix.
* fix(web): open the plan review once the Sidebar seat is mounted
Replace the layout-effect binding with an observable: ctx.sidebarRight.mounted publishes the session whose seat is on screen, undefined while a global panel replaces the Conversation. The seat keeps binding from its passive effect. PlanReviewOpen reads the source through a bound hook and opens once a seat is defined, so a review mounting ahead of the seat in the same commit no longer assumes a bound seat and never crashes its entry.
The seat spec's opener stand-in now reads `mounted` the way a slot component does; the service spec pins that only real transitions publish, and the PlanReviewOpen spec covers a review that mounts before any seat and opens once the seat binds.
* test(web): align the plan-review console tripwires and the seat term
The off-screen review case now asserts the reconnect warnings like its siblings and filters crash logs by error level as the other e2e files do. The zh README keeps 席位 for the seat throughout.
* test(plan): keep the review props cast at its baseline fingerprint
verify-no-unknown-casts counts a modified assertion as new. The two PlanReviewOpen specs now share the unchanged baseline cast through one helper and supply the seat hook as a typed value beside it.
The shared rule now states why the collapsed identity and the expanded value keep two label tiers, and the detailName comment names the row identity it also renders.
The occupying badge chooses its own side, and the update badge keeps a right-opening bubble that the Windows caption menu can cover; ui-settings-general records that as a known limitation.
Web and Desktop now share one command pair: start:* launches the artifacts
of a prior complete build and dev:* builds first, then launches. start:web
is new and spells out the same source launch vector as `pnpm dsh web`;
dev:web runs `pnpm run build`, the three rebuild watchers, and `dsh web` in
one terminal, with --skip-build and --no-serve for the previous two-terminal
flow. The launcher gate pins both scripts to the dsh CLI, and a root
Makefile names the same commands as make web|dev-web|desktop|dev-desktop|build.
The dev-web stage supervisor waits for stages the terminal already
interrupted, escalates only survivors, forwards SIGTERM once, and reports a
stage that exits on its own. Signal listeners are persistent because the
tsdown watchers bundle signal-exit, which re-raises a signal when it finds
no other listener.
The previous sentence claimed every caption icon bubble opens below the caption, but only the sidebar toggle and New Session derive side=bottom; an occupying sidebar.toggle.badge keeps the default side.
The Client Slot catalog serves `cordis-client-runner` closures, which cannot
`require` anything, so the `sidebar.workspaces.session.menu.item` example
now renders a `role="menuitem"` `<button>` and the doc names
`MenuItemButton` as what the shipped rows use rather than what an entry must
render; the README separates the Module Loader lane, which does reach the
primitive through `require`, from the runner lane. The Agent Note and the
contract module doc say the entries read the pin and archive sets through
hooks their own faces inject, that the owner draws no separator, and that
the strip keeps clicks from opening the row. `RowActionToast` gets its
contract sentence and the rename-assembly spec points at the e2e that
exists.
The Session row's action strip stops click propagation itself, so a
`sidebar.workspaces.session.row.action` entry that renders a plain button no
longer opens the row as a side effect; the shipped buttons drop their own
handling. `Menu` decides submenu exclusivity on the list's own bubbles, by
DOM: reaching a top-level row that is not a submenu parent, by pointer or by
focus, closes the open card for data rows and component rows alike, which
the per-row handlers only did for data rows. A group start on the list's
first row draws no hairline.
The first live-fixture e2e case registers the scaffold's cleanup right after
launch, as the file's other cases do. The three pin scenarios the browser
spec lost when the order write moved into `UiWorkspace.pinSession` — newer
saved orders during a pending pin, a Workspace disappearing during one, and
members whose summaries are temporarily missing — return as service cases.
The typert Cordis catalog drops any exported name that two declarations
share, so ui-workspace's `SessionRenameRequest` made the generator remove
Session Controller's request type from the model-facing API catalog. The
row-side pending rename is `SessionRenameTarget`: the Session and the title
the dialog seeds from.
A Session row's "..." menu and its hover buttons become two list slots the
WorkspaceBrowser entry declares, `sidebar.workspaces.session.menu.item` and
`sidebar.workspaces.session.row.action`. The shipped actions register from
apply the way a client plugin does: pin (menu 100, button 200), rename (200),
fork (300), archive (menu 400, button 100). An entry receives only the row
identity, reads the pin and archive sets through Set hooks its own face
injects, decides its own visibility, renders `MenuItemButton` or one icon
button, and dismisses the menu through the slot-level `useMenuOpenState`
hook bound from the row's render occurrence.
Each action's behavior lives in its injected callbacks. `UiWorkspace` gains
`pinSession` and `unpinSession`; the pin write into the saved orders happens
there, through the viewing-store instance the apply creates and hands the
browser's registration as its handle (the ui-layout pattern), reading the
memberships current at completion. The rename dialog and the row-action
notice are `shell.overlay` entries fed by apply-closure observables through
the faces' `hooks`; the archived notice switches the archived filter
directly. WorkspaceBrowser threads no action callbacks, renders no toast,
and hosts no rename dialog; its title double-click raises the same rename
request.
In ui-primitives, `MenuItemButton` is the plain `role="menuitem"` row for
menus whose rows are components: it shares a data row's markup and styling
and needs no shared state, because `Menu` decides the keyboard walk from the
DOM and now runs the post-selection focus return once on the list's click
bubble for every row kind. `Menu` accepts such rows as `children` beside
`items`, which becomes optional together with `onSelect`; `separatorBefore`
lets a row start a group with the same hairline a separator entry draws. The
context-based `MenuAction` goes away with its extension group.
Review feedback on this PR: keep one identity colour rather than raising the
card subtitle to label-secondary. The detail-page package name moves from
label-caption to label-tertiary; the settings card subtitle keeps its own
colour and shares only the 12px size with the row it expands into.
master's session row menu now leads with Pin session and SessionNode
carries pinned/archived, so the slot-action specs and the Web golden take
the row as it is after the rebase.