Przeglądaj źródła

refactor(client): remove dead sidebar ordering surfaces

_Kerman 2 miesięcy temu
rodzic
commit
7222a99e33
29 zmienionych plików z 71 dodań i 123 usunięć
  1. 7 10
      packages/client/connection/src/client/fixture.ts
  2. 3 6
      packages/client/connection/tests/fixture.spec.ts
  3. 0 1
      packages/client/runtime/src/client/contract/sessions-port.ts
  4. 0 2
      packages/client/runtime/src/client/sessions/lineage.ts
  5. 6 14
      packages/client/runtime/src/client/sessions/manager.ts
  6. 0 4
      packages/client/runtime/src/client/sessions/service.ts
  7. 1 1
      packages/client/runtime/tests/subagent-lineage.spec.ts
  8. 0 1
      packages/client/test-runtime/src/sessions.ts
  9. 2 2
      packages/client/ui-conversation/tests/skeleton.spec.tsx
  10. 0 2
      packages/client/ui-subagent/tests/conversation-ui.spec.tsx
  11. 1 1
      packages/client/ui-tool/tests/chat-code-subcalls.spec.tsx
  12. 1 1
      packages/client/ui-tool/tests/coverage-tails.spec.tsx
  13. 2 2
      packages/client/ui-tool/tests/diff-card.spec.tsx
  14. 2 2
      packages/client/ui-tool/tests/read-card.spec.tsx
  15. 2 2
      packages/client/ui-tool/tests/terminal-card.spec.tsx
  16. 2 2
      packages/client/ui-workspace/README.i18n.yaml
  17. 1 1
      packages/client/ui-workspace/README.md
  18. 1 1
      packages/client/ui-workspace/README.zh.md
  19. 0 13
      packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css
  20. 9 14
      packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx
  21. 8 18
      packages/client/ui-workspace/src/client/tree.ts
  22. 12 12
      packages/client/ui-workspace/tests/rows.spec.tsx
  23. 1 1
      packages/client/ui-workspace/tests/tree.spec.ts
  24. 10 2
      packages/client/ui-workspace/tests/workspace-browser.spec.tsx
  25. 0 3
      packages/host/apiproxy/src/api-proxy.ts
  26. 0 1
      packages/host/apiproxy/src/api/events.schema.ts
  27. 0 1
      packages/host/apiproxy/src/api/events.ts
  28. 0 1
      packages/host/apiproxy/src/api/sessions.schema.ts
  29. 0 2
      packages/host/apiproxy/src/api/sessions.ts

+ 7 - 10
packages/client/connection/src/client/fixture.ts

@@ -1387,11 +1387,10 @@ interface FixtureWorld {
 /** Build the fixture's legacy API and Remote RPC faces over one state graph. */
 /** Build the fixture's legacy API and Remote RPC faces over one state graph. */
 function createFixtureWorld(options: FixtureOptions): FixtureWorld {
 function createFixtureWorld(options: FixtureOptions): FixtureWorld {
   // The resident fixture sessions all carry history, so none of them is blank.
   // The resident fixture sessions all carry history, so none of them is blank.
-  const fixtureSessionsNow = Date.now()
   const sessions: SessionSummary[] = options.empty ? [] : [
   const sessions: SessionSummary[] = options.empty ? [] : [
-    { sessionId: sid('fx-alpha'), createdAt: fixtureSessionsNow - 180_000, updatedAt: fixtureSessionsNow, running: true, blank: false, cwd: '/tmp/fixture' },
-    { sessionId: sid('fx-beta'), createdAt: fixtureSessionsNow - 120_000, updatedAt: fixtureSessionsNow - 60_000, running: false, blank: false, parentSessionId: sid('fx-alpha'), cwd: '/tmp/fixture' },
-    { sessionId: sid('fx-gamma'), createdAt: fixtureSessionsNow - 60_000, updatedAt: fixtureSessionsNow - 120_000, running: false, blank: false, cwd: '/tmp/fixture' },
+    { sessionId: sid('fx-alpha'), updatedAt: Date.now(), running: true, blank: false, cwd: '/tmp/fixture' },
+    { sessionId: sid('fx-beta'), updatedAt: Date.now() - 60_000, running: false, blank: false, parentSessionId: sid('fx-alpha'), cwd: '/tmp/fixture' },
+    { sessionId: sid('fx-gamma'), updatedAt: Date.now() - 120_000, running: false, blank: false, cwd: '/tmp/fixture' },
   ]
   ]
   const logs = new Map<SessionId, SessionEvent[]>([[sid('fx-alpha'), buildAlphaLog()]])
   const logs = new Map<SessionId, SessionEvent[]>([[sid('fx-alpha'), buildAlphaLog()]])
   const modelSelections = new Map<SessionId, ModelSelection>(sessions.map(session => [
   const modelSelections = new Map<SessionId, ModelSelection>(sessions.map(session => [
@@ -2047,16 +2046,15 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
             return ok(request, { sessionId: requestedId })
             return ok(request, { sessionId: requestedId })
           }
           }
         }
         }
-        const createdAt = Date.now()
         const created: SessionSummary = {
         const created: SessionSummary = {
-          sessionId: requestedId ?? sid(`fx-${nextSession++}`), createdAt, updatedAt: createdAt, running: false, blank: true, cwd,
+          sessionId: requestedId ?? sid(`fx-${nextSession++}`), updatedAt: Date.now(), running: false, blank: true, cwd,
         }
         }
         sessions.push(created)
         sessions.push(created)
         modelSelections.set(created.sessionId, { provider: 'deepseek-official', model: 'deepseek-v4-flash' })
         modelSelections.set(created.sessionId, { provider: 'deepseek-official', model: 'deepseek-v4-flash' })
         attachedSessions += 1
         attachedSessions += 1
         const emitSession = (): void => {
         const emitSession = (): void => {
           // Mirrors the host: the frame fires at creation, so blank is constantly true.
           // Mirrors the host: the frame fires at creation, so blank is constantly true.
-          emitHost({ type: 'host/session-added', sessionId: created.sessionId, createdAt, blank: true, cwd })
+          emitHost({ type: 'host/session-added', sessionId: created.sessionId, blank: true, cwd })
         }
         }
         if (workspace !== undefined && options.failWorkspaceAttach) {
         if (workspace !== undefined && options.failWorkspaceAttach) {
           emitSession()
           emitSession()
@@ -2123,16 +2121,15 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
         }
         }
         let cut = boundary.seq + 1
         let cut = boundary.seq + 1
         while (cut < log.length && log[cut]?.type !== 'turn/start') cut++
         while (cut < log.length && log[cut]?.type !== 'turn/start') cut++
-        const createdAt = Date.now()
         const child: SessionSummary = {
         const child: SessionSummary = {
-          sessionId: sid(`fx-${nextSession++}`), createdAt, updatedAt: createdAt, running: false, blank: false,
+          sessionId: sid(`fx-${nextSession++}`), updatedAt: Date.now(), running: false, blank: false,
           parentSessionId: sessionId,
           parentSessionId: sessionId,
           ...source.cwd === undefined ? {} : { cwd: source.cwd },
           ...source.cwd === undefined ? {} : { cwd: source.cwd },
         }
         }
         logs.set(child.sessionId, log.slice(0, cut))
         logs.set(child.sessionId, log.slice(0, cut))
         sessions.push(child)
         sessions.push(child)
         emitHost({
         emitHost({
-          type: 'host/session-added', sessionId: child.sessionId, createdAt, blank: false,
+          type: 'host/session-added', sessionId: child.sessionId, blank: false,
           parentSessionId: sessionId,
           parentSessionId: sessionId,
           ...source.cwd === undefined ? {} : { cwd: source.cwd },
           ...source.cwd === undefined ? {} : { cwd: source.cwd },
         })
         })

+ 3 - 6
packages/client/connection/tests/fixture.spec.ts

@@ -267,9 +267,8 @@ describe('createFixtureApi', () => {
     expect(seen).toHaveLength(1)
     expect(seen).toHaveLength(1)
     const added = seen[0]
     const added = seen[0]
     if (added?.type !== 'host/session-added') throw new Error('session-added frame missing')
     if (added?.type !== 'host/session-added') throw new Error('session-added frame missing')
-    expect(Number.isFinite(added.createdAt)).toBe(true)
     expect(added).toEqual({
     expect(added).toEqual({
-      type: 'host/session-added', sessionId: createdId, createdAt: added.createdAt, blank: true, cwd: '/tmp/fixture',
+      type: 'host/session-added', sessionId: createdId, blank: true, cwd: '/tmp/fixture',
     })
     })
     const list = await api.sessions.list(req({}))
     const list = await api.sessions.list(req({}))
     if (!list.result.ok) throw new Error('list failed')
     if (!list.result.ok) throw new Error('list failed')
@@ -707,9 +706,8 @@ describe('createFixtureApi', () => {
     // write pushes the fresh workspace snapshot after session-added.
     // write pushes the fresh workspace snapshot after session-added.
     const added = seen[0]
     const added = seen[0]
     if (added?.type !== 'host/session-added') throw new Error('session-added frame missing')
     if (added?.type !== 'host/session-added') throw new Error('session-added frame missing')
-    expect(Number.isFinite(added.createdAt)).toBe(true)
     expect(added).toEqual({
     expect(added).toEqual({
-      type: 'host/session-added', sessionId: id, createdAt: added.createdAt, blank: true, cwd: '/tmp/fixture',
+      type: 'host/session-added', sessionId: id, blank: true, cwd: '/tmp/fixture',
     })
     })
     expect(seen[1]).toMatchObject({
     expect(seen[1]).toMatchObject({
       type: 'host/workspace-changed',
       type: 'host/workspace-changed',
@@ -741,9 +739,8 @@ describe('createFixtureApi', () => {
     })
     })
     const added = frames[1]
     const added = frames[1]
     if (added?.type !== 'host/session-added') throw new Error('session-added frame missing')
     if (added?.type !== 'host/session-added') throw new Error('session-added frame missing')
-    expect(Number.isFinite(added.createdAt)).toBe(true)
     expect(added).toEqual({
     expect(added).toEqual({
-      type: 'host/session-added', sessionId: preallocated, createdAt: added.createdAt, blank: true,
+      type: 'host/session-added', sessionId: preallocated, blank: true,
       cwd: made.result.value.workspace.path,
       cwd: made.result.value.workspace.path,
     })
     })
 
 

+ 0 - 1
packages/client/runtime/src/client/contract/sessions-port.ts

@@ -16,7 +16,6 @@ export interface SessionsPortSummary {
   /** Empty-log bit (blank sessions are reused by New Session instead of minting another). */
   /** Empty-log bit (blank sessions are reused by New Session instead of minting another). */
   blank: boolean
   blank: boolean
   cwd?: string
   cwd?: string
-  createdAt: number
   updatedAt: number
   updatedAt: number
 }
 }
 
 

+ 0 - 2
packages/client/runtime/src/client/sessions/lineage.ts

@@ -17,7 +17,6 @@ export interface TitledSessionSummary extends SessionSummary {
 export interface SessionListEntry {
 export interface SessionListEntry {
   sessionId: SessionId
   sessionId: SessionId
   title?: string
   title?: string
-  createdAt: number
   updatedAt: number
   updatedAt: number
   running: boolean
   running: boolean
   /** Empty-log bit mirrored from the summary; lists hide blank sessions (filtering stays with the consumer). */
   /** Empty-log bit mirrored from the summary; lists hide blank sessions (filtering stays with the consumer). */
@@ -78,7 +77,6 @@ export function flattenLineage(
     const pendingInteraction = pendingInteractions?.get(s.sessionId)
     const pendingInteraction = pendingInteractions?.get(s.sessionId)
     out.push({
     out.push({
       ...s,
       ...s,
-      createdAt: s.createdAt ?? s.updatedAt,
       ...(pendingInteraction === undefined ? {} : { pendingInteraction }),
       ...(pendingInteraction === undefined ? {} : { pendingInteraction }),
       completed: completed?.has(s.sessionId) ?? false,
       completed: completed?.has(s.sessionId) ?? false,
       depth,
       depth,

+ 6 - 14
packages/client/runtime/src/client/sessions/manager.ts

@@ -541,9 +541,8 @@ export class SessionManager {
         : { ...(opts.cwd === undefined ? {} : { cwd: opts.cwd }), ...shared }
         : { ...(opts.cwd === undefined ? {} : { cwd: opts.cwd }), ...shared }
       const { result } = await this.api.sessions.create(payload)
       const { result } = await this.api.sessions.create(payload)
       if (result.ok) {
       if (result.ok) {
-        const createdAt = Date.now()
         this.recordMutation({ kind: 'upsert', summary: {
         this.recordMutation({ kind: 'upsert', summary: {
-          sessionId: result.value.sessionId, createdAt, updatedAt: createdAt, running: false, blank: true,
+          sessionId: result.value.sessionId, updatedAt: Date.now(), running: false, blank: true,
           ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
           ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
           ...(result.value.agentPreset !== undefined ? { agentPreset: result.value.agentPreset } : {}),
           ...(result.value.agentPreset !== undefined ? { agentPreset: result.value.agentPreset } : {}),
         } })
         } })
@@ -553,11 +552,9 @@ export class SessionManager {
         // so expose it immediately as Ungrouped while the caller keeps the
         // so expose it immediately as Ungrouped while the caller keeps the
         // prompt buffer and decides whether to retry attachment.
         // prompt buffer and decides whether to retry attachment.
         if (publishedSessionId !== undefined) {
         if (publishedSessionId !== undefined) {
-          const createdAt = Date.now()
           this.recordMutation({ kind: 'upsert', summary: {
           this.recordMutation({ kind: 'upsert', summary: {
             sessionId: publishedSessionId,
             sessionId: publishedSessionId,
-            createdAt,
-            updatedAt: createdAt,
+            updatedAt: Date.now(),
             running: false,
             running: false,
             blank: true,
             blank: true,
           } })
           } })
@@ -591,9 +588,8 @@ export class SessionManager {
         ? result.value.sessionId
         ? result.value.sessionId
         : workspaceAttachSessionId(result.error)
         : workspaceAttachSessionId(result.error)
       if (childId !== undefined) {
       if (childId !== undefined) {
-        const createdAt = Date.now()
         this.recordMutation({ kind: 'upsert', summary: {
         this.recordMutation({ kind: 'upsert', summary: {
-          sessionId: childId, createdAt, updatedAt: createdAt, running: false, blank: false,
+          sessionId: childId, updatedAt: Date.now(), running: false, blank: false,
           parentSessionId: opts.sessionId,
           parentSessionId: opts.sessionId,
           ...(source?.cwd !== undefined ? { cwd: source.cwd } : {}),
           ...(source?.cwd !== undefined ? { cwd: source.cwd } : {}),
         } })
         } })
@@ -620,9 +616,8 @@ export class SessionManager {
    * @param agentPreset - the preset id the host confirmed.
    * @param agentPreset - the preset id the host confirmed.
    */
    */
   noteAgentPreset(sessionId: SessionId, agentPreset: string): void {
   noteAgentPreset(sessionId: SessionId, agentPreset: string): void {
-    const createdAt = Date.now()
     this.recordMutation({ kind: 'upsert', summary: {
     this.recordMutation({ kind: 'upsert', summary: {
-      sessionId, createdAt, updatedAt: createdAt, running: false, blank: true, agentPreset,
+      sessionId, updatedAt: Date.now(), running: false, blank: true, agentPreset,
     } })
     } })
   }
   }
 
 
@@ -799,10 +794,8 @@ export class SessionManager {
     const frame = envelope.payload
     const frame = envelope.payload
     switch (frame.type) {
     switch (frame.type) {
       case 'host/session-added': {
       case 'host/session-added': {
-        const createdAt = frame.createdAt ?? Date.now()
         this.mergeSummary({
         this.mergeSummary({
-          sessionId: frame.sessionId, createdAt, updatedAt: createdAt,
-          running: false, blank: frame.blank,
+          sessionId: frame.sessionId, updatedAt: Date.now(), running: false, blank: frame.blank,
           ...(frame.parentSessionId !== undefined ? { parentSessionId: frame.parentSessionId } : {}),
           ...(frame.parentSessionId !== undefined ? { parentSessionId: frame.parentSessionId } : {}),
           ...(frame.origin !== undefined ? { origin: frame.origin } : {}),
           ...(frame.origin !== undefined ? { origin: frame.origin } : {}),
           ...(frame.cwd !== undefined ? { cwd: frame.cwd } : {}),
           ...(frame.cwd !== undefined ? { cwd: frame.cwd } : {}),
@@ -1055,8 +1048,7 @@ export class SessionManager {
     const items = fresh.map((entry) => {
     const items = fresh.map((entry) => {
       const prev = this.entryCache.get(entry.sessionId)
       const prev = this.entryCache.get(entry.sessionId)
       if (
       if (
-        prev !== undefined && prev.createdAt === entry.createdAt
-        && prev.updatedAt === entry.updatedAt && prev.running === entry.running
+        prev !== undefined && prev.updatedAt === entry.updatedAt && prev.running === entry.running
         && prev.blank === entry.blank && prev.agentPreset === entry.agentPreset
         && prev.blank === entry.blank && prev.agentPreset === entry.agentPreset
         && prev.parentSessionId === entry.parentSessionId && prev.cwd === entry.cwd
         && prev.parentSessionId === entry.parentSessionId && prev.cwd === entry.cwd
         && prev.origin === entry.origin && prev.title === entry.title && prev.depth === entry.depth
         && prev.origin === entry.origin && prev.title === entry.title && prev.depth === entry.depth

+ 0 - 4
packages/client/runtime/src/client/sessions/service.ts

@@ -66,8 +66,6 @@ export interface SessionSummary {
    * selected blank entry.
    * selected blank entry.
    */
    */
   blank: boolean
   blank: boolean
-  /** Durable session creation time. */
-  createdAt: number
   updatedAt: number
   updatedAt: number
   /** Current host-computed projection values retained by the object layer. */
   /** Current host-computed projection values retained by the object layer. */
   projectionValues?: Readonly<Partial<SessionProjectionMap>>
   projectionValues?: Readonly<Partial<SessionProjectionMap>>
@@ -669,7 +667,6 @@ export class SessionsService implements ISessions {
         running: entry.running,
         running: entry.running,
         ...(entry.completed ? { completed: true } : {}),
         ...(entry.completed ? { completed: true } : {}),
         blank: entry.blank,
         blank: entry.blank,
-        createdAt: entry.createdAt,
         updatedAt: entry.updatedAt,
         updatedAt: entry.updatedAt,
         ...(entry.pendingInteraction === undefined
         ...(entry.pendingInteraction === undefined
           ? {}
           ? {}
@@ -703,7 +700,6 @@ export class SessionsService implements ISessions {
             origin: 'subagent',
             origin: 'subagent',
             running: child.activity === 'running',
             running: child.activity === 'running',
             blank: false,
             blank: false,
-            createdAt: 0,
             updatedAt: 0,
             updatedAt: 0,
           }
           }
         } else if (summary.displayTitle !== displayTitle) {
         } else if (summary.displayTitle !== displayTitle) {

+ 1 - 1
packages/client/runtime/tests/subagent-lineage.spec.ts

@@ -11,7 +11,7 @@ function summary(
   running = false,
   running = false,
 ): SessionSummary {
 ): SessionSummary {
   return {
   return {
-    id: sid(id), displayTitle: id, running, blank: false, createdAt: 0, updatedAt: 0,
+    id: sid(id), displayTitle: id, running, blank: false, updatedAt: 0,
     ...(parentId === undefined ? {} : { parentId }),
     ...(parentId === undefined ? {} : { parentId }),
     ...(origin === undefined ? {} : { origin }),
     ...(origin === undefined ? {} : { origin }),
   }
   }

+ 0 - 1
packages/client/test-runtime/src/sessions.ts

@@ -233,7 +233,6 @@ export class TestSessions implements ISessions {
       displayTitle: fixture.id,
       displayTitle: fixture.id,
       running: false,
       running: false,
       blank: false,
       blank: false,
-      createdAt: this.records.size + 1,
       updatedAt: this.records.size + 1,
       updatedAt: this.records.size + 1,
       ...fixture.summary,
       ...fixture.summary,
     }
     }

+ 2 - 2
packages/client/ui-conversation/tests/skeleton.spec.tsx

@@ -99,10 +99,10 @@ function mount(
   } = {},
   } = {},
 ) {
 ) {
   const root = sid('root')
   const root = sid('root')
-  const rootRow = { id: root, displayTitle: 'Root', running: false, blank: false, createdAt: 1, updatedAt: 1 }
+  const rootRow = { id: root, displayTitle: 'Root', running: false, blank: false, updatedAt: 1 }
   const childRow = {
   const childRow = {
     id: SID, displayTitle: 'Child', parentId: root, cwd: '/projects/one',
     id: SID, displayTitle: 'Child', parentId: root, cwd: '/projects/one',
-    running: false, blank: options.summaryBlank ?? false, createdAt: 2, updatedAt: 2,
+    running: false, blank: options.summaryBlank ?? false, updatedAt: 2,
     ...(options.summaryOrigin === undefined ? {} : { origin: options.summaryOrigin }),
     ...(options.summaryOrigin === undefined ? {} : { origin: options.summaryOrigin }),
   }
   }
   const listed = options.omitSummaryRow !== true
   const listed = options.omitSummaryRow !== true

+ 0 - 2
packages/client/ui-subagent/tests/conversation-ui.spec.tsx

@@ -56,7 +56,6 @@ function props(
         displayTitle: 'worker',
         displayTitle: 'worker',
         running: true,
         running: true,
         blank: false,
         blank: false,
-        createdAt: Date.now(),
         updatedAt: Date.now(),
         updatedAt: Date.now(),
       },
       },
     },
     },
@@ -84,7 +83,6 @@ function summary(id: SessionId, updatedAt: number): SessionSummary {
     displayTitle: id,
     displayTitle: id,
     running: false,
     running: false,
     blank: false,
     blank: false,
-    createdAt: updatedAt,
     updatedAt,
     updatedAt,
   }
   }
 }
 }

+ 1 - 1
packages/client/ui-tool/tests/chat-code-subcalls.spec.tsx

@@ -110,7 +110,7 @@ async function bench(snapshot: ConversationSnapshot) {
   const session = createSnapshotStore<ConversationSnapshot>(snapshot)
   const session = createSnapshotStore<ConversationSnapshot>(snapshot)
   const list = createSnapshotStore<SessionListState>({
   const list = createSnapshotStore<SessionListState>({
     ids: [SID],
     ids: [SID],
-    byId: { [SID]: { id: SID, title: 'S', displayTitle: 'S', running: false, blank: false, createdAt: 1, updatedAt: 1 } },
+    byId: { [SID]: { id: SID, title: 'S', displayTitle: 'S', running: false, blank: false, updatedAt: 1 } },
     current: SID,
     current: SID,
     phase: 'ready', subagentsByParent: {}, tasksBySession: {}, currentAddress: undefined,
     phase: 'ready', subagentsByParent: {}, tasksBySession: {}, currentAddress: undefined,
   })
   })

+ 1 - 1
packages/client/ui-tool/tests/coverage-tails.spec.tsx

@@ -26,7 +26,7 @@ function listStore() {
   return createSnapshotStore<SessionListState>({
   return createSnapshotStore<SessionListState>({
     ids: [SID],
     ids: [SID],
     byId: {
     byId: {
-      [SID]: { id: SID, title: 'r', displayTitle: 'r', running: false, blank: false, createdAt: 0, updatedAt: 0 },
+      [SID]: { id: SID, title: 'r', displayTitle: 'r', running: false, blank: false, updatedAt: 0 },
     },
     },
     current: undefined,
     current: undefined,
     phase: 'ready',
     phase: 'ready',

+ 2 - 2
packages/client/ui-tool/tests/diff-card.spec.tsx

@@ -156,7 +156,7 @@ describe('chat row diff body', () => {
 describe('FileMutationRow diff card', () => {
 describe('FileMutationRow diff card', () => {
   const list = () => createSnapshotStore<SessionListState>({
   const list = () => createSnapshotStore<SessionListState>({
     ids: [SID],
     ids: [SID],
-    byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, createdAt: 0, updatedAt: 0, cwd: '/w/app' } },
+    byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, updatedAt: 0, cwd: '/w/app' } },
     current: SID,
     current: SID,
     phase: 'ready',
     phase: 'ready',
     subagentsByParent: {}, tasksBySession: {},
     subagentsByParent: {}, tasksBySession: {},
@@ -314,7 +314,7 @@ describe('DetailsPanel diff Output section', () => {
       ? { ids: [], byId: {}, current: undefined, phase: 'ready', subagentsByParent: {}, tasksBySession: {}, currentAddress: undefined }
       ? { ids: [], byId: {}, current: undefined, phase: 'ready', subagentsByParent: {}, tasksBySession: {}, currentAddress: undefined }
       : {
       : {
         ids: [SID],
         ids: [SID],
-        byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, createdAt: 0, updatedAt: 0, cwd } },
+        byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, updatedAt: 0, cwd } },
         current: SID,
         current: SID,
         phase: 'ready',
         phase: 'ready',
         subagentsByParent: {}, tasksBySession: {},
         subagentsByParent: {}, tasksBySession: {},

+ 2 - 2
packages/client/ui-tool/tests/read-card.spec.tsx

@@ -170,7 +170,7 @@ describe('GenericToolCard read body', () => {
 describe('ReadRow keyed toolview', () => {
 describe('ReadRow keyed toolview', () => {
   const list = () => createSnapshotStore<SessionListState>({
   const list = () => createSnapshotStore<SessionListState>({
     ids: [SID],
     ids: [SID],
-    byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, createdAt: 0, updatedAt: 0, cwd: '/w/app' } },
+    byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, updatedAt: 0, cwd: '/w/app' } },
     current: SID,
     current: SID,
     phase: 'ready',
     phase: 'ready',
     subagentsByParent: {}, tasksBySession: {},
     subagentsByParent: {}, tasksBySession: {},
@@ -260,7 +260,7 @@ describe('DetailsPanel Output section (read)', () => {
       ? { ids: [], byId: {}, current: undefined, phase: 'ready', subagentsByParent: {}, tasksBySession: {}, currentAddress: undefined }
       ? { ids: [], byId: {}, current: undefined, phase: 'ready', subagentsByParent: {}, tasksBySession: {}, currentAddress: undefined }
       : {
       : {
         ids: [SID],
         ids: [SID],
-        byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, createdAt: 0, updatedAt: 0, cwd } },
+        byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, updatedAt: 0, cwd } },
         current: SID,
         current: SID,
         phase: 'ready',
         phase: 'ready',
         subagentsByParent: {}, tasksBySession: {},
         subagentsByParent: {}, tasksBySession: {},

+ 2 - 2
packages/client/ui-tool/tests/terminal-card.spec.tsx

@@ -345,7 +345,7 @@ describe('chat row terminal body', () => {
 describe('BashRow terminal card', () => {
 describe('BashRow terminal card', () => {
   const list = () => createSnapshotStore<SessionListState>({
   const list = () => createSnapshotStore<SessionListState>({
     ids: [SID],
     ids: [SID],
-    byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, createdAt: 0, updatedAt: 0 } },
+    byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, updatedAt: 0 } },
     current: undefined,
     current: undefined,
     phase: 'ready',
     phase: 'ready',
     subagentsByParent: {}, tasksBySession: {},
     subagentsByParent: {}, tasksBySession: {},
@@ -451,7 +451,7 @@ describe('DetailsPanel Output section', () => {
       ? { ids: [], byId: {}, current: undefined, phase: 'ready', subagentsByParent: {}, tasksBySession: {}, currentAddress: undefined }
       ? { ids: [], byId: {}, current: undefined, phase: 'ready', subagentsByParent: {}, tasksBySession: {}, currentAddress: undefined }
       : {
       : {
         ids: [SID],
         ids: [SID],
-        byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, createdAt: 0, updatedAt: 0, cwd } },
+        byId: { [SID]: { id: SID, displayTitle: 'r', running: false, blank: false, updatedAt: 0, cwd } },
         current: SID,
         current: SID,
         phase: 'ready',
         phase: 'ready',
         subagentsByParent: {}, tasksBySession: {},
         subagentsByParent: {}, tasksBySession: {},

+ 2 - 2
packages/client/ui-workspace/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/client/ui-workspace/README.md
 #   pnpm run verify-translation-pairing --write packages/client/ui-workspace/README.md
-README.md: 24d73beba56f527eb57276accb2693255bad9020
-README.zh.md: 85a5ce9210dd084e6e6e0746eb19aac12d98d54d
+README.md: 359979fa26b0e12c7e28b929029fb7d4e9fca324
+README.zh.md: 2139ea3cf7ec3ae84aaa84e181d2f36a60c9ba60

Plik diff jest za duży
+ 1 - 1
packages/client/ui-workspace/README.md


Plik diff jest za duży
+ 1 - 1
packages/client/ui-workspace/README.zh.md


+ 0 - 13
packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css

@@ -38,19 +38,6 @@
   background: var(--dsw-alias-interactive-bg-hover);
   background: var(--dsw-alias-interactive-bg-hover);
 }
 }
 
 
-.viewOptionLabel {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 8px;
-  width: 100%;
-}
-
-.viewOptionCheck {
-  flex: none;
-  color: var(--dsw-alias-label-primary);
-}
-
 /* Section header: title, an inline search control, and the two trailing
 /* Section header: title, an inline search control, and the two trailing
    actions. Expanding search collapses the action cluster and takes its room. */
    actions. Expanding search collapses the action cluster and takes its room. */
 .sectionHeader {
 .sectionHeader {

+ 9 - 14
packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx

@@ -12,7 +12,7 @@
 import { useEffect, useMemo, useRef, useState } from 'react'
 import { useEffect, useMemo, useRef, useState } from 'react'
 import clsx from 'clsx'
 import clsx from 'clsx'
 import {
 import {
-  Button, IconCheckOutline16, IconCloseFill14, IconPersonalizationOutline16,
+  Button, IconCloseFill14, IconPersonalizationOutline16,
   IconProjectAddOutline16, IconSearchOutline16, Menu, Modal, Tooltip,
   IconProjectAddOutline16, IconSearchOutline16, Menu, Modal, Tooltip,
 } from '@deepseek-ai/dsh-client-ui-primitives'
 } from '@deepseek-ai/dsh-client-ui-primitives'
 import type {
 import type {
@@ -89,24 +89,19 @@ function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }: {
   t: WorkspaceBrowserProps['t']
   t: WorkspaceBrowserProps['t']
 }) {
 }) {
   const [open, setOpen] = useState(false)
   const [open, setOpen] = useState(false)
-  const optionLabel = (label: string, selected: boolean) => (
-    <span className={css.viewOptionLabel}>
-      <span>{label}</span>
-      {selected && <IconCheckOutline16 className={css.viewOptionCheck} />}
-    </span>
-  )
   return (
   return (
     <Menu
     <Menu
       open={open}
       open={open}
       onClose={() => { setOpen(false) }}
       onClose={() => { setOpen(false) }}
       items={[
       items={[
         { type: 'label' as const, id: 'group-by', text: t('groupBy.label') },
         { type: 'label' as const, id: 'group-by', text: t('groupBy.label') },
-        { id: 'workspace', label: optionLabel(t('groupBy.workspace'), groupBy === 'workspace') },
-        { id: 'flat', label: optionLabel(t('groupBy.flat'), groupBy === 'flat') },
+        { id: 'workspace', label: t('groupBy.workspace') },
+        { id: 'flat', label: t('groupBy.flat') },
         { type: 'label' as const, id: 'order-by', text: t('orderBy.label') },
         { type: 'label' as const, id: 'order-by', text: t('orderBy.label') },
-        { id: 'manual', label: optionLabel(t('orderBy.manual'), orderBy === 'manual'), disabled: groupBy !== 'workspace' },
-        { id: 'updated', label: optionLabel(t('orderBy.updated'), orderBy === 'updated') },
+        { id: 'manual', label: t('orderBy.manual'), disabled: groupBy !== 'workspace' },
+        { id: 'updated', label: t('orderBy.updated') },
       ]}
       ]}
+      selectedIds={[groupBy, orderBy]}
       onSelect={(id) => {
       onSelect={(id) => {
         if (id === 'workspace' || id === 'flat') onGroupPick(id)
         if (id === 'workspace' || id === 'flat') onGroupPick(id)
         else if (id === 'manual' || id === 'updated') onOrderPick(id)
         else if (id === 'manual' || id === 'updated') onOrderPick(id)
@@ -280,7 +275,7 @@ function SessionTree({
     })
     })
   }, [recentSessionOrder, workspaces])
   }, [recentSessionOrder, workspaces])
   const groups = useMemo(
   const groups = useMemo(
-    () => deriveGroups(list, orderedWorkspaces, archivedSessionIds, { expandedProjects }, 'manual'),
+    () => deriveGroups(list, orderedWorkspaces, archivedSessionIds, { expandedProjects }),
     [list, orderedWorkspaces, archivedSessionIds, expandedProjects],
     [list, orderedWorkspaces, archivedSessionIds, expandedProjects],
   )
   )
   const now = Date.now()
   const now = Date.now()
@@ -684,12 +679,12 @@ export function WorkspaceBrowser({
     const onClick = (event: MouseEvent): void => {
     const onClick = (event: MouseEvent): void => {
       if (!(event.target instanceof Node) || searchRoot.current?.contains(event.target) === true) return
       if (!(event.target instanceof Node) || searchRoot.current?.contains(event.target) === true) return
       searchInput.current?.blur()
       searchInput.current?.blur()
-      if (query !== '') return
+      if (normalizedQuery !== '') return
       setSearchExpanded(false)
       setSearchExpanded(false)
     }
     }
     document.addEventListener('click', onClick)
     document.addEventListener('click', onClick)
     return () => { document.removeEventListener('click', onClick) }
     return () => { document.removeEventListener('click', onClick) }
-  }, [query, wide, searchExpanded])
+  }, [normalizedQuery, wide, searchExpanded])
 
 
   useEffect(() => {
   useEffect(() => {
     if (normalizedQuery === '') {
     if (normalizedQuery === '') {

+ 8 - 18
packages/client/ui-workspace/src/client/tree.ts

@@ -29,7 +29,6 @@ export interface SessionNode {
   runningSubagentCount: number
   runningSubagentCount: number
   /** Finished running while not selected and not yet opened (the green "done" reminder dot). */
   /** Finished running while not selected and not yet opened (the green "done" reminder dot). */
   completed: boolean
   completed: boolean
-  createdAt: number
   updatedAt: number
   updatedAt: number
 }
 }
 
 
@@ -108,10 +107,6 @@ function byRecency(a: SessionSummary, b: SessionSummary): number {
   return a.id < b.id ? -1 : 1
   return a.id < b.id ? -1 : 1
 }
 }
 
 
-function sortSessions(sessions: SessionSummary[]): void {
-  sessions.sort(byRecency)
-}
-
 /**
 /**
  * Ordinary sessions are visible; among blank sessions, only the current one
  * Ordinary sessions are visible; among blank sessions, only the current one
  * is visible. Subagent children use their parent header catalog; archived
  * is visible. Subagent children use their parent header catalog; archived
@@ -141,10 +136,12 @@ function buildGroup(
   createdAt: number | undefined,
   createdAt: number | undefined,
   label: string,
   label: string,
   members: readonly SessionSummary[],
   members: readonly SessionSummary[],
-  orderBy: SessionOrderBy,
+  order: 'account' | 'recency',
 ): Group {
 ): Group {
   const sessions = [...members]
   const sessions = [...members]
-  if (orderBy !== 'manual') sortSessions(sessions)
+  // Workspace order is the caller-selected sessionIds; only Ungrouped lacks
+  // an account order and therefore falls back to recency.
+  if (order === 'recency') sessions.sort(byRecency)
   return { key, workspaceId, cwd, createdAt, label, sessions }
   return { key, workspaceId, cwd, createdAt, label, sessions }
 }
 }
 
 
@@ -157,7 +154,6 @@ function groupByWorkspace(
   list: SessionListState,
   list: SessionListState,
   workspaces: readonly WorkspaceView[],
   workspaces: readonly WorkspaceView[],
   archived: ReadonlySet<SessionId>,
   archived: ReadonlySet<SessionId>,
-  orderBy: SessionOrderBy,
 ): Group[] {
 ): Group[] {
   const groups: Group[] = []
   const groups: Group[] = []
   const accounted = new Set<SessionId>()
   const accounted = new Set<SessionId>()
@@ -172,7 +168,7 @@ function groupByWorkspace(
     }
     }
     groups.push(buildGroup(
     groups.push(buildGroup(
       workspace.workspaceId, workspace.workspaceId, workspace.path,
       workspace.workspaceId, workspace.workspaceId, workspace.path,
-      Date.parse(workspace.createdAt), workspace.title, members, orderBy,
+      Date.parse(workspace.createdAt), workspace.title, members, 'account',
     ))
     ))
   }
   }
   const stray = list.ids
   const stray = list.ids
@@ -180,10 +176,7 @@ function groupByWorkspace(
     .filter((s): s is SessionSummary =>
     .filter((s): s is SessionSummary =>
       s !== undefined && !accounted.has(s.id) && sessionVisible(s, list.current, archived))
       s !== undefined && !accounted.has(s.id) && sessionVisible(s, list.current, archived))
   if (stray.length > 0) {
   if (stray.length > 0) {
-    groups.push(buildGroup(
-      UNGROUPED_KEY, undefined, undefined, undefined, UNGROUPED_LABEL, stray,
-      orderBy === 'manual' ? 'updated' : orderBy,
-    ))
+    groups.push(buildGroup(UNGROUPED_KEY, undefined, undefined, undefined, UNGROUPED_LABEL, stray, 'recency'))
   }
   }
   return groups
   return groups
 }
 }
@@ -199,7 +192,6 @@ function sessionNode(
     running: s.running,
     running: s.running,
     runningSubagentCount: descendants.get(s.id)?.runningCount ?? 0,
     runningSubagentCount: descendants.get(s.id)?.runningCount ?? 0,
     completed: s.completed === true,
     completed: s.completed === true,
-    createdAt: s.createdAt,
     updatedAt: s.updatedAt,
     updatedAt: s.updatedAt,
     ...(s.pendingInteraction === undefined ? {} : { pendingInteraction: s.pendingInteraction }),
     ...(s.pendingInteraction === undefined ? {} : { pendingInteraction: s.pendingInteraction }),
   }
   }
@@ -217,7 +209,6 @@ function sessionNode(
  * @param workspaces - real workspaces in stable Host order.
  * @param workspaces - real workspaces in stable Host order.
  * @param archivedSessionIds - registry-global archive set.
  * @param archivedSessionIds - registry-global archive set.
  * @param view - local expansion arrays.
  * @param view - local expansion arrays.
- * @param orderBy - local session ordering mode.
  * @returns group sections in render order.
  * @returns group sections in render order.
  */
  */
 export function deriveGroups(
 export function deriveGroups(
@@ -225,7 +216,6 @@ export function deriveGroups(
   workspaces: readonly WorkspaceView[],
   workspaces: readonly WorkspaceView[],
   archivedSessionIds: readonly SessionId[],
   archivedSessionIds: readonly SessionId[],
   view: TreeView,
   view: TreeView,
-  orderBy: SessionOrderBy = 'manual',
 ): GroupNode[] {
 ): GroupNode[] {
   const archived = new Set(archivedSessionIds)
   const archived = new Set(archivedSessionIds)
   const expandedProjects = new Set(view.expandedProjects)
   const expandedProjects = new Set(view.expandedProjects)
@@ -235,7 +225,7 @@ export function deriveGroups(
     : (workspaces.find(w => w.sessionIds.includes(list.current as SessionId))?.workspaceId as string | undefined)
     : (workspaces.find(w => w.sessionIds.includes(list.current as SessionId))?.workspaceId as string | undefined)
         ?? UNGROUPED_KEY
         ?? UNGROUPED_KEY
   const groups: GroupNode[] = []
   const groups: GroupNode[] = []
-  for (const g of groupByWorkspace(list, workspaces, archived, orderBy)) {
+  for (const g of groupByWorkspace(list, workspaces, archived)) {
     const expanded = expandedProjects.has(g.key)
     const expanded = expandedProjects.has(g.key)
     groups.push({
     groups.push({
       key: g.key,
       key: g.key,
@@ -273,7 +263,7 @@ export function deriveFlat(
     if (s === undefined || !sessionVisible(s, list.current, archived)) continue
     if (s === undefined || !sessionVisible(s, list.current, archived)) continue
     rows.push(s)
     rows.push(s)
   }
   }
-  sortSessions(rows)
+  rows.sort(byRecency)
   return rows.map(session => sessionNode(session, descendants))
   return rows.map(session => sessionNode(session, descendants))
 }
 }
 
 

+ 12 - 12
packages/client/ui-workspace/tests/rows.spec.tsx

@@ -116,7 +116,7 @@ describe('workspace browser rows', () => {
   it('renders and opens a selected running Session row', () => {
   it('renders and opens a selected running Session row', () => {
     const node: SessionNode = {
     const node: SessionNode = {
       id: sid('session'), title: 'Session', blank: false, running: true,
       id: sid('session'), title: 'Session', blank: false, running: true,
-      runningSubagentCount: 0, completed: false, createdAt: 0, updatedAt: 0,
+      runningSubagentCount: 0, completed: false, updatedAt: 0,
     }
     }
     const onOpen = vi.fn()
     const onOpen = vi.fn()
     render(
     render(
@@ -137,7 +137,7 @@ describe('workspace browser rows', () => {
       <SessionNodeItem
       <SessionNodeItem
         node={{
         node={{
           id: sid('s1'), title: 'One', blank: false, running: false,
           id: sid('s1'), title: 'One', blank: false, running: false,
-          runningSubagentCount: 0, completed: false, createdAt: 0, updatedAt: 0, ...over,
+          runningSubagentCount: 0, completed: false, updatedAt: 0, ...over,
         }}
         }}
         currentId={undefined} now={0} onOpen={vi.fn()}
         currentId={undefined} now={0} onOpen={vi.fn()}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t}
@@ -169,7 +169,7 @@ describe('workspace browser rows', () => {
     try {
     try {
       const node: SessionNode = {
       const node: SessionNode = {
         id: sid('owner'), title: 'Delegating', blank: false, running: false,
         id: sid('owner'), title: 'Delegating', blank: false, running: false,
-        runningSubagentCount: 2, completed: false, createdAt: 0, updatedAt: 0,
+        runningSubagentCount: 2, completed: false, updatedAt: 0,
       }
       }
       render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
       render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
@@ -191,7 +191,7 @@ describe('workspace browser rows', () => {
     try {
     try {
       const node: SessionNode = {
       const node: SessionNode = {
         id: sid('owner'), title: 'Delegating', blank: false, running: true,
         id: sid('owner'), title: 'Delegating', blank: false, running: true,
-        runningSubagentCount: 1, completed: false, createdAt: 0, updatedAt: 0,
+        runningSubagentCount: 1, completed: false, updatedAt: 0,
       }
       }
       render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
       render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
@@ -212,7 +212,7 @@ describe('workspace browser rows', () => {
   it('keeps child activity as a secondary status while user attention is primary', () => {
   it('keeps child activity as a secondary status while user attention is primary', () => {
     const node: SessionNode = {
     const node: SessionNode = {
       id: sid('owner'), title: 'Needs input', blank: false, pendingInteraction: 'question',
       id: sid('owner'), title: 'Needs input', blank: false, pendingInteraction: 'question',
-      running: false, runningSubagentCount: 1, completed: false, createdAt: 0, updatedAt: 0,
+      running: false, runningSubagentCount: 1, completed: false, updatedAt: 0,
     }
     }
     render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
     render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
       onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
       onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
@@ -303,7 +303,7 @@ describe('workspace browser rows', () => {
     try {
     try {
       const node: SessionNode = {
       const node: SessionNode = {
         id: sid('s-blank'), title: 'ignored', blank: true, running: false,
         id: sid('s-blank'), title: 'ignored', blank: true, running: false,
-        runningSubagentCount: 0, completed: false, createdAt: 0, updatedAt: 0,
+        runningSubagentCount: 0, completed: false, updatedAt: 0,
       }
       }
       render(<SessionNodeItem node={node} currentId={node.id} now={0} onOpen={vi.fn()}
       render(<SessionNodeItem node={node} currentId={node.id} now={0} onOpen={vi.fn()}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
@@ -330,7 +330,7 @@ describe('workspace browser rows', () => {
     const onArchive = vi.fn()
     const onArchive = vi.fn()
     const node: SessionNode = {
     const node: SessionNode = {
       id: sid('s1'), title: 'One', blank: false, running: false,
       id: sid('s1'), title: 'One', blank: false, running: false,
-      runningSubagentCount: 0, completed: false, createdAt: 0, updatedAt: 0,
+      runningSubagentCount: 0, completed: false, updatedAt: 0,
     }
     }
     render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={onOpen}
     render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={onOpen}
       onRename={onRename} onFork={onFork} onArchive={onArchive} t={t} />)
       onRename={onRename} onFork={onFork} onArchive={onArchive} t={t} />)
@@ -364,7 +364,7 @@ describe('workspace browser rows', () => {
     try {
     try {
       const node: SessionNode = {
       const node: SessionNode = {
         id: sid('s1'), title: 'Hovered', blank: false, running: true,
         id: sid('s1'), title: 'Hovered', blank: false, running: true,
-        runningSubagentCount: 0, completed: false, createdAt: 0, updatedAt: 0,
+        runningSubagentCount: 0, completed: false, updatedAt: 0,
       }
       }
       render(<SessionNodeItem node={node} currentId={undefined} now={60_000} onOpen={vi.fn()}
       render(<SessionNodeItem node={node} currentId={undefined} now={60_000} onOpen={vi.fn()}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
@@ -395,7 +395,7 @@ describe('workspace browser rows', () => {
     try {
     try {
       const node: SessionNode = {
       const node: SessionNode = {
         id: sid(pendingInteraction), title: 'Needs input', blank: false,
         id: sid(pendingInteraction), title: 'Needs input', blank: false,
-        pendingInteraction, running: true, runningSubagentCount: 0, completed: false, createdAt: 0, updatedAt: 0,
+        pendingInteraction, running: true, runningSubagentCount: 0, completed: false, updatedAt: 0,
       }
       }
       const view = render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
       const view = render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
@@ -422,7 +422,7 @@ describe('workspace browser rows', () => {
     try {
     try {
       const node: SessionNode = {
       const node: SessionNode = {
         id: sid('s1'), title: 'Quiet', blank: false, running: false,
         id: sid('s1'), title: 'Quiet', blank: false, running: false,
-        runningSubagentCount: 0, completed: false, createdAt: 0, updatedAt: 0,
+        runningSubagentCount: 0, completed: false, updatedAt: 0,
       }
       }
       render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
       render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
@@ -440,7 +440,7 @@ describe('workspace browser rows', () => {
     try {
     try {
       const node: SessionNode = {
       const node: SessionNode = {
         id: sid('s1'), title: 'Done', blank: false, running: false,
         id: sid('s1'), title: 'Done', blank: false, running: false,
-        runningSubagentCount: 0, completed: true, createdAt: 0, updatedAt: 0,
+        runningSubagentCount: 0, completed: true, updatedAt: 0,
       }
       }
       render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
       render(<SessionNodeItem node={node} currentId={undefined} now={0} onOpen={vi.fn()}
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
         onRename={vi.fn()} onFork={vi.fn()} onArchive={vi.fn()} t={t} />)
@@ -456,7 +456,7 @@ describe('workspace browser rows', () => {
   it('draggable row wires start/end and gates hover/drop on an active same-group drag', () => {
   it('draggable row wires start/end and gates hover/drop on an active same-group drag', () => {
     const node: SessionNode = {
     const node: SessionNode = {
       id: sid('s1'), title: 'Drag me', blank: false, running: false,
       id: sid('s1'), title: 'Drag me', blank: false, running: false,
-      runningSubagentCount: 0, completed: false, createdAt: 0, updatedAt: 0,
+      runningSubagentCount: 0, completed: false, updatedAt: 0,
     }
     }
     const inactive = dragProps()
     const inactive = dragProps()
     const { rerender } = render(
     const { rerender } = render(

+ 1 - 1
packages/client/ui-workspace/tests/tree.spec.ts

@@ -12,7 +12,7 @@ const sid = (id: string) => id as SessionId
 const wid = (id: string) => id as WorkspaceId
 const wid = (id: string) => id as WorkspaceId
 const summary = (id: string, updatedAt: number, cwd?: string): SessionSummary => ({
 const summary = (id: string, updatedAt: number, cwd?: string): SessionSummary => ({
   id: sid(id), displayTitle: id, running: false, blank: false,
   id: sid(id), displayTitle: id, running: false, blank: false,
-  createdAt: updatedAt, updatedAt, ...(cwd === undefined ? {} : { cwd }),
+  updatedAt, ...(cwd === undefined ? {} : { cwd }),
 })
 })
 const list = (...items: SessionSummary[]): SessionListState => ({
 const list = (...items: SessionSummary[]): SessionListState => ({
   ids: items.map(item => item.id),
   ids: items.map(item => item.id),

+ 10 - 2
packages/client/ui-workspace/tests/workspace-browser.spec.tsx

@@ -22,7 +22,7 @@ const t: WorkspaceBrowserProps['t'] = makeTranslate(zh, commonZh)
 const sid = (id: string) => id as SessionId
 const sid = (id: string) => id as SessionId
 const wid = (id: string) => id as WorkspaceId
 const wid = (id: string) => id as WorkspaceId
 const summary = (id: string, updatedAt: number, overrides: Partial<SessionSummary> = {}): SessionSummary => ({
 const summary = (id: string, updatedAt: number, overrides: Partial<SessionSummary> = {}): SessionSummary => ({
-  id: sid(id), displayTitle: id, running: false, blank: false, createdAt: updatedAt, updatedAt, ...overrides,
+  id: sid(id), displayTitle: id, running: false, blank: false, updatedAt, ...overrides,
 })
 })
 const sessionState = (items: readonly SessionSummary[], overrides: Partial<SessionListState> = {}): SessionListState => ({
 const sessionState = (items: readonly SessionSummary[], overrides: Partial<SessionListState> = {}): SessionListState => ({
   ids: items.map(item => item.id),
   ids: items.map(item => item.id),
@@ -107,8 +107,11 @@ describe('WorkspaceBrowser', () => {
 
 
     fireEvent.click(screen.getByRole('button', { name: '视图选项' }))
     fireEvent.click(screen.getByRole('button', { name: '视图选项' }))
     expect(screen.getByText('分组方式')).toBeTruthy() // the menu heading label
     expect(screen.getByText('分组方式')).toBeTruthy() // the menu heading label
+    expect(screen.getAllByRole('menuitem').map(item => item.textContent)).toEqual([
+      '按工作区', '单列表', '手动排序', '最近更新',
+    ])
+    expect(screen.getByRole('menuitem', { name: '按工作区' }).querySelector('svg')).toBeTruthy()
     expect(screen.getByRole('menuitem', { name: '手动排序' }).querySelector('svg')).toBeTruthy()
     expect(screen.getByRole('menuitem', { name: '手动排序' }).querySelector('svg')).toBeTruthy()
-    expect(screen.queryByText('创建时间')).toBeNull()
     fireEvent.click(screen.getByRole('menuitem', { name: '单列表' }))
     fireEvent.click(screen.getByRole('menuitem', { name: '单列表' }))
     // Store-driven flip: title changes, rows flatten newest-first, headers gone.
     // Store-driven flip: title changes, rows flatten newest-first, headers gone.
     expect(b.store.getSnapshot().groupBy).toBe('flat')
     expect(b.store.getSnapshot().groupBy).toBe('flat')
@@ -405,6 +408,11 @@ describe('WorkspaceBrowser', () => {
 
 
     fireEvent.click(search)
     fireEvent.click(search)
     const input = screen.getByPlaceholderText<HTMLInputElement>('搜索会话…')
     const input = screen.getByPlaceholderText<HTMLInputElement>('搜索会话…')
+    fireEvent.change(input, { target: { value: '   ' } })
+    fireEvent.click(document.body)
+    expect(search.getAttribute('aria-expanded')).toBe('false')
+
+    fireEvent.click(search)
     fireEvent.change(input, { target: { value: 'kept' } })
     fireEvent.change(input, { target: { value: 'kept' } })
     fireEvent.click(document.body)
     fireEvent.click(document.body)
     expect(search.getAttribute('aria-expanded')).toBe('true')
     expect(search.getAttribute('aria-expanded')).toBe('true')

+ 0 - 3
packages/host/apiproxy/src/api-proxy.ts

@@ -466,7 +466,6 @@ function sessionListFields(header: SessionHeader, events: readonly SessionEvent[
 function summarize(session: Session, running: boolean): SessionSummary {
 function summarize(session: Session, running: boolean): SessionSummary {
   return {
   return {
     sessionId: session.id,
     sessionId: session.id,
-    createdAt: session.header.createdAt,
     // Excludes end-seed: a resumed-but-untouched session
     // Excludes end-seed: a resumed-but-untouched session
     // must not sort as freshly worked in.
     // must not sort as freshly worked in.
     updatedAt: lastActivityTime(session.events) ?? session.header.createdAt,
     updatedAt: lastActivityTime(session.events) ?? session.header.createdAt,
@@ -500,7 +499,6 @@ async function summarizeCold(
   }
   }
   return {
   return {
     sessionId: meta.id,
     sessionId: meta.id,
-    createdAt: meta.createdAt,
     updatedAt,
     updatedAt,
     running: false,
     running: false,
     // Lazy persistence keeps never-appended sessions out of list(); reading
     // Lazy persistence keeps never-appended sessions out of list(); reading
@@ -3394,7 +3392,6 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
             queue.push(frame({
             queue.push(frame({
               type: 'host/session-added',
               type: 'host/session-added',
               sessionId: session.id,
               sessionId: session.id,
-              createdAt: session.header.createdAt,
               // Derived at frame time like summarize(); a just-created session
               // Derived at frame time like summarize(); a just-created session
               // has run no turn yet, so this is constantly true in practice.
               // has run no turn yet, so this is constantly true in practice.
               blank: sessionBlank(session),
               blank: sessionBlank(session),

+ 0 - 1
packages/host/apiproxy/src/api/events.schema.ts

@@ -71,7 +71,6 @@ export const hostFrameSchema = z.discriminatedUnion('type', [
   z.object({
   z.object({
     type: z.literal('host/session-added'),
     type: z.literal('host/session-added'),
     sessionId: sessionIdSchema,
     sessionId: sessionIdSchema,
-    createdAt: z.number().optional(),
     blank: z.boolean(),
     blank: z.boolean(),
     parentSessionId: sessionIdSchema.optional(),
     parentSessionId: sessionIdSchema.optional(),
     origin: z.literal('subagent').optional(),
     origin: z.literal('subagent').optional(),

+ 0 - 1
packages/host/apiproxy/src/api/events.ts

@@ -128,7 +128,6 @@ export type HostFrame =
   | {
   | {
     type: 'host/session-added'
     type: 'host/session-added'
     sessionId: SessionId
     sessionId: SessionId
-    createdAt?: number
     blank: boolean
     blank: boolean
     parentSessionId?: SessionId
     parentSessionId?: SessionId
     origin?: 'subagent'
     origin?: 'subagent'

+ 0 - 1
packages/host/apiproxy/src/api/sessions.schema.ts

@@ -51,7 +51,6 @@ export const sessionEventSchema = z.object({
 /** SessionSummary row of session.list (`projections` reuses the history block's shape and schema). */
 /** SessionSummary row of session.list (`projections` reuses the history block's shape and schema). */
 export const sessionSummarySchema = z.object({
 export const sessionSummarySchema = z.object({
   sessionId: sessionIdSchema,
   sessionId: sessionIdSchema,
-  createdAt: z.number().optional(),
   updatedAt: z.number(),
   updatedAt: z.number(),
   running: z.boolean(),
   running: z.boolean(),
   blank: z.boolean(),
   blank: z.boolean(),

+ 0 - 2
packages/host/apiproxy/src/api/sessions.ts

@@ -147,8 +147,6 @@ export type QueueAction =
 /** Session list entry (v1 builds no index: list does readdir+stat). */
 /** Session list entry (v1 builds no index: list does readdir+stat). */
 export interface SessionSummary {
 export interface SessionSummary {
   sessionId: SessionId
   sessionId: SessionId
-  /** Session creation time from the durable session header when supplied by the Host. */
-  createdAt?: number
   /**
   /**
    * Last activity. Attached: the last non-`session/end-seed` event, since a
    * Last activity. Attached: the last non-`session/end-seed` event, since a
    * pickup is not activity. Cold: the log's mtime, or `createdAt` for a backend
    * pickup is not activity. Cold: the log's mtime, or `createdAt` for a backend

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików