mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
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.
This commit is contained in:
@@ -19,6 +19,8 @@ export interface RpcMethodMap {
|
||||
'host.describe': HostApi['describe']
|
||||
'workspace.list': WorkspaceApi['list']
|
||||
'workspace.create': WorkspaceApi['create']
|
||||
'workspace.rename': WorkspaceApi['rename']
|
||||
'workspace.insertSessionBefore': WorkspaceApi['insertSessionBefore']
|
||||
}
|
||||
|
||||
/** Business request payload of method K (reaches through the RpcRequest narrow form to payload). */
|
||||
|
||||
@@ -40,6 +40,7 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
|
||||
z.object({ code: z.literal('workspace-not-found'), message: z.string(), details: z.object({ workspaceId: z.string() }) }),
|
||||
z.object({ code: z.literal('workspace-invalid-path'), message: z.string(), details: z.object({ path: z.string() }) }),
|
||||
z.object({ code: z.literal('workspace-name-conflict'), message: z.string(), details: z.object({ name: z.string() }) }),
|
||||
z.object({ code: z.literal('workspace-move-invalid'), message: z.string(), details: z.object({ workspaceId: z.string(), sessionId: z.string(), beforeSessionId: z.string().optional() }) }),
|
||||
z.object({ code: z.literal('agent-busy'), message: z.string(), details: z.object({ reason: z.string() }) }),
|
||||
z.object({ code: z.literal('internal'), message: z.string(), details: z.object({}) }),
|
||||
]) as unknown as z.ZodType<RpcError>
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface RpcErrorDetailsMap {
|
||||
'workspace-not-found': { workspaceId: string }
|
||||
'workspace-invalid-path': { path: string }
|
||||
'workspace-name-conflict': { name: string }
|
||||
'workspace-move-invalid': { workspaceId: string; sessionId: SessionId; beforeSessionId?: SessionId }
|
||||
'agent-busy': { reason: string }
|
||||
'internal': {}
|
||||
}
|
||||
|
||||
@@ -44,3 +44,29 @@ export const workspaceCreateValueSchema = z.object({
|
||||
workspace: workspaceViewSchema,
|
||||
created: z.boolean(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'workspace.create'>>>
|
||||
|
||||
/** workspace.rename request payload: the new title must be non-blank. */
|
||||
export const workspaceRenameRequestSchema = z.object({
|
||||
workspaceId: workspaceIdSchema,
|
||||
title: z.string(),
|
||||
}).refine(
|
||||
payload => payload.title.trim() !== '',
|
||||
{ message: 'workspace.rename requires a non-blank title' },
|
||||
) satisfies z.ZodType<Wire<RequestPayload<'workspace.rename'>>>
|
||||
|
||||
/** workspace.rename response value. */
|
||||
export const workspaceRenameValueSchema = z.object({
|
||||
workspace: workspaceViewSchema,
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'workspace.rename'>>>
|
||||
|
||||
/** workspace.insertSessionBefore request payload (anchor omitted = append to end). */
|
||||
export const workspaceInsertSessionBeforeRequestSchema = z.object({
|
||||
workspaceId: workspaceIdSchema,
|
||||
sessionId: sessionIdSchema,
|
||||
beforeSessionId: sessionIdSchema.optional(),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'workspace.insertSessionBefore'>>>
|
||||
|
||||
/** workspace.insertSessionBefore response value. */
|
||||
export const workspaceInsertSessionBeforeValueSchema = z.object({
|
||||
workspace: workspaceViewSchema,
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'workspace.insertSessionBefore'>>>
|
||||
|
||||
@@ -24,7 +24,10 @@ export interface WorkspaceView {
|
||||
path: string
|
||||
/** Unique display title (defaults to the path basename at create). */
|
||||
title: string
|
||||
/** Sessions accounted under this workspace, newest-first for display. */
|
||||
/**
|
||||
* Sessions accounted under this workspace, in manually owned order
|
||||
* (attach prepends, insertSessionBefore reorders; activity never does).
|
||||
*/
|
||||
sessionIds: SessionId[]
|
||||
/** ISO-8601 creation instant. */
|
||||
createdAt: string
|
||||
@@ -52,4 +55,27 @@ export interface WorkspaceApi {
|
||||
*/
|
||||
create(request: RpcRequest<{ path?: string; name?: string }>):
|
||||
Promise<RpcResponse<{ workspace: WorkspaceView; created: boolean }>>
|
||||
|
||||
/**
|
||||
* Renames a workspace. `title` is trimmed and must be non-empty
|
||||
* (schema-enforced). An unknown id fails with `workspace-not-found`; a
|
||||
* title equal to another workspace's fails with `workspace-name-conflict`.
|
||||
* Renaming to the current title is a no-op success (no durable write).
|
||||
*/
|
||||
rename(request: RpcRequest<{ workspaceId: WorkspaceId; title: string }>):
|
||||
Promise<RpcResponse<{ workspace: WorkspaceView }>>
|
||||
|
||||
/**
|
||||
* Moves an accounted session within its workspace's manual order,
|
||||
* DOM-insertBefore-like: with `beforeSessionId` the session is inserted
|
||||
* before that anchor; omitted appends to the end. An unknown workspace
|
||||
* fails with `workspace-not-found`; a session or anchor not accounted by
|
||||
* the workspace fails with `workspace-move-invalid`. A move to the current
|
||||
* position is a no-op success.
|
||||
*/
|
||||
insertSessionBefore(request: RpcRequest<{
|
||||
workspaceId: WorkspaceId
|
||||
sessionId: SessionId
|
||||
beforeSessionId?: SessionId
|
||||
}>): Promise<RpcResponse<{ workspace: WorkspaceView }>>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user