feat(client-runtime): project the archive set and the archiveSession action

WorkspaceListState gains archivedSessionIds (ReadonlySet, replaced only
on membership change), installed as full snapshots from the list
baseline, the unary echo, and the changed frame. Archiving the current
session clears the selection into the New Session view state. Test
doubles (test-runtime, fake APIs, fixture client) follow the widened
IWorkspaces/IApiClient faces.
This commit is contained in:
imccyu
2026-07-31 02:40:14 +08:00
committed by imccyu
parent 53ceab53b8
commit c764ed7e64
24 changed files with 177 additions and 21 deletions

View File

@@ -73,6 +73,7 @@ export function conversationSnapshot(sessionId: SessionId): ConversationSnapshot
export function workspaceListState(): WorkspaceListState {
return {
items: [],
archivedSessionIds: new Set(),
state: 'idle',
phase: 'ready',
error: null,

View File

@@ -186,4 +186,21 @@ export class TestWorkspaces implements IWorkspaces {
if (stub !== undefined) return await (stub(workspaceId, sessionId, beforeSessionId) as Promise<WorkspaceView>)
return { workspaceId, title: '', path: '', sessionIds: [sessionId] } as unknown as WorkspaceView
}
/**
* Archive a session (recorded). The default mirrors the production face's
* observable effect: the id joins the list state's archive set.
* @param sessionId - session to archive.
*/
async archiveSession(sessionId: SessionId): Promise<void> {
this.calls.push({ method: 'archiveSession', args: [sessionId] })
const stub = this.stubs.get('archiveSession')
if (stub !== undefined) {
await (stub(sessionId) as Promise<void>)
return
}
await this.update((draft) => {
draft.archivedSessionIds = new Set([...draft.archivedSessionIds, sessionId])
})
}
}