Files
deepseek-harness/packages/client/runtime
imccyu ea8b1178cd feat(web): session list one-list, hover card, row menus, rename, manual ordering
Sidebar session list grows the figma 239-10458 feature set and the
workspace/session browsing region moves wholesale into ui-workspace:

- Group-by menu (WorkSpace / In one list): flat mode lists every session
  top-level, strictly newest-first; the choice persists across reloads.
- Session rows get a 500ms hover detail card (title / relative time /
  status line) and a ... menu (Rename / Fork session / Delete session,
  visual-only for now); workspace headers get ... with Rename (wired) and
  Delete workspace (visual-only).
- workspace.rename RPC: trims, rejects duplicate titles on the create
  chain (workspace-name-conflict), no-op on same title; modal dialog with
  client-side duplicate pre-check.
- workspace.insertSessionBefore RPC (DOM-insertBefore semantics, omitted
  anchor appends): HTML5 drag reorder of root sessions inside a workspace
  group; order truth stays host-side, the view refreshes from the
  response/changed frame.
- Activity pinning removed: the session/event touchSession chain is gone;
  workspace accounts are manually owned (new sessions prepend, explicit
  reordering only). Contracts and tests updated, api catalog regenerated.
- ui-sidebar reduced to the column shell (brand, fold state machine, New
  Session, Settings) exposing one sidebar.workspaces hole with a two-fact
  owner share {wide, expandSidebar}; ui-workspace owns the whole region
  (header, search, grouped/flat lists, dialogs, drag) plus the picker via
  a shared WorkspaceCreateFlow. The old sidebar.workspace picker slot and
  its deferral indirection are gone.
- ui-primitives: Menu gains label entries, danger rows, and
  closeOnPointerLeave; new HoverCard (portaled, open-delay, disabled
  guard). Hover card and row menu never coexist.
2026-07-26 00:55:17 +08:00
..

@deepseek-ai/dsh-client-runtime

Client cordis boot and React-free object services: SlotsService wraps SlotCore and supplies renderer data sources; SessionsService owns Session objects, list/scope/history state, and page-local Session Intent state; WorkspacesService depends on SessionsService and owns Workspace objects, list/actions, page-local Workspace Intent state, default-target derivation, and the cross-object New Session flow. The runtime fans the shared Host stream into both managers. Contract: api-contracts v3 §4.

Workspace and Session lists

Workspace and Session lists have independent monotone pendingready baseline phases and separate refresh activity/error state. Incremental frames arriving during a list request replay over its response. The first successful baseline establishes Host order; later refreshes update rows and membership without changing the relative order of identities already shown. Workspace recency is derived only after both baselines are ready and never changes Workspace list order.

SlotsService gives the renderer separate bare observables for useSessions and useWorkspaces; web-react creates the hooks. Workspace business state does not enter SessionListState or an entry store.

Session creation failures

SessionsService.create accepts an optional caller-preallocated SessionId. It throws SessionCreateError on failure: requestedSessionId remains available after transport uncertainty, while publishedSessionId is set when workspace-attach-failed proves the Host published a real Session before attachment failed. For the New Session flow, the frontend Session object owns its retained prompt and advances it through attachment and send; a partially published Session keeps the same object and prompt while it appears as Ungrouped.

Session title projection

SessionManager retains the latest validated session/title control snapshot independently of list and session-instance arrival. Newer event seqs replace older snapshots, title timestamps contribute to list recency, and a subscription baseline discards any retained title beyond its lastSeq before the optional folded title arrives. Explicit session removal also clears the retained title. The client-facing SessionSummary.title is therefore only the actual durable title; displayTitle is always present and falls back through the cwd basename and session id. A cold persisted session keeps that fallback until opening or resuming it causes the host to fold and project its log-backed title.

Model Experience

None, as the client runtime hosts browser-side services and the session object layer; nothing here reaches a model request.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

  • loader.unload is a stub (throws not-implemented) — the full chain (fiber dispose → registration cascade → style removal) lands with the HMR project.
  • Scope teardown is stage-driven, single-occupant today — the staged session follows list.current exactly (staging is the open signal: the event window opens ⟺ the session is on stage); a removed-while-staged session's scope survives frozen until the stage moves on, not until true observer count reaches zero. Resolution (cell()/binding()/scope()) is pure addressing, render-safe. The staged state can widen to a multi-pane list when concurrent panes land.
  • Value imports of this package from plugin bundles must use the /client subpath — the bare package name is not in the loader externals table and inlines a second module instance, whose private scope-tag Symbol never matches (the empty-state P0 postmortem).