docs(web): finalize workspace UI product flow

This commit is contained in:
imccyu
2026-07-25 16:15:29 +08:00
parent 9eb9c70a8a
commit 08ce02da2b
14 changed files with 265 additions and 254 deletions

View File

@@ -1,6 +1,6 @@
# @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 owns Workspace objects, list/actions, page-local Workspace Intent state, and default-target derivation. The runtime fans the shared Host stream into both managers. Contract: api-contracts v3 §4.
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

View File

@@ -149,12 +149,18 @@ export class SessionManager {
return session
}
/** @returns the active frontend Session, if one remains selected. */
/**
* Resolve the active frontend Session Intent.
* @returns the active frontend Session, if one remains selected.
*/
getIntent(): Session | undefined {
return this.intentSessionId === undefined ? undefined : this.sessions.get(this.intentSessionId)
}
/** @param text - exact controlled-input value for the active frontend Session. */
/**
* Update the retained prompt of the active frontend Session.
* @param text - exact controlled-input value for the active frontend Session.
*/
updateIntent(text: string): void {
this.getIntent()?.updatePendingPrompt(text)
}

View File

@@ -209,17 +209,24 @@ export class SessionsService {
* Start or retarget the sole client-local Session intent.
* @param target - resolved real or frontend-only Workspace target.
* @param prompt - optional prompt retained across retargeting.
* @returns the frontend Session object that owns the Intent.
*/
startIntent(target: SessionIntentTarget, prompt = ''): Session {
return this.manager.startIntent(target, prompt)
}
/** @returns the active frontend Session object, if one exists. */
/**
* Resolve the active frontend Session Intent.
* @returns the active frontend Session object, if one exists.
*/
intent(): Session | undefined {
return this.manager.getIntent()
}
/** @param text - exact controlled-input value for the current Session Intent. */
/**
* Update the retained prompt of the active frontend Session.
* @param text - exact controlled-input value for the current Session Intent.
*/
updateIntent(text: string): void {
this.manager.updateIntent(text)
}

View File

@@ -141,7 +141,10 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
return result
}
/** @param text - exact controlled value of this Session's retained prompt. */
/**
* Update this Session's retained prompt while it remains editable.
* @param text - exact controlled value of this Session's retained prompt.
*/
updatePendingPrompt(text: string): void {
const pending = this.pendingPrompt
if (pending === null || pending.phase === 'sending') return

View File

@@ -2,7 +2,7 @@
Conversation domain: skeleton (header/tabs/composer/empty state), chat view (grouped step-summary flow, streaming tail isolation, stats line, per-tool row slot with a bash sample registrant), minimal details panel, scope-addressed ConversationService. Contract: api-contracts v3 §7 plus the slot terminal design (store seat / props shares).
The no-session hero renders the frontend Session Intent from the Session list projection, including its frontend Workspace Intent when no real Workspace exists. It declares `conversation.empty.workspace`, where ui-workspace registers the same picker used by the sidebar. WorkspacesService starts and publishes the two intents. The Session object keeps its identity across publication and retains any prompt that still needs connection or delivery; ConversationRoot reads that `pendingPrompt` from `useSession` and edits or retries it through the scoped ConversationService.
The no-session hero renders the frontend Session Intent from the Session list projection, including its frontend Workspace Intent when no real Workspace exists. It declares `conversation.empty.workspace`, where ui-workspace registers the same picker used by the sidebar. WorkspacesService starts the cross-object flow; each Workspace or Session object owns its own materialization. The Session keeps its identity across publication and retains any prompt that still needs connection or delivery; ConversationRoot reads that `pendingPrompt` from `useSession` and edits or retries it through the scoped ConversationService.
The view ring IS a slot: the conversation registration declares the `'conversation.view'` list slot (session scope) in its `children` table, ConversationRoot renders the active entry through its renderSlot share (`only: <active id>`), and view tabs project from the ring ledger's registration options (`id`/`order`/`label`). The chat view is this package's own ring entry; other plugins (ui-trajectory) contribute tabs through plain `ctx.slots.register` — the former package-local view registry (`registerView`/`ViewEntry`/`ConversationViewMap` and the chrome attachment table) is retired, with per-view chrome dissolved into the view components themselves.

View File

@@ -49,7 +49,10 @@ export class ConversationService extends Service {
await this.scopedSession('loadOlder').loadOlder()
}
/** Update the scoped Session's retained pending prompt. */
/**
* Update the scoped Session's retained pending prompt.
* @param text - exact controlled-input value to retain.
*/
updatePendingPrompt(text: string): void {
this.scopedSession('updatePendingPrompt').updatePendingPrompt(text)
}