feat(ui-workspace): reorder ungrouped sessions

This commit is contained in:
_Kerman
2026-08-11 19:14:57 +08:00
parent 94897de821
commit 5bb70d7cf2
10 changed files with 156 additions and 43 deletions
@@ -24,8 +24,9 @@ const workspace = (id: string, sessionIds: string[], title = id): WorkspaceView
workspaceId: wid(id), path: `/projects/${id}`, title,
sessionIds: sessionIds.map(sid), createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z',
})
const view = (expandedProjects: readonly string[] = []) => ({
const view = (expandedProjects: readonly string[] = [], ungroupedOrder?: readonly string[]) => ({
expandedProjects,
...(ungroupedOrder === undefined ? {} : { ungroupedOrder }),
})
const noArchive: readonly SessionId[] = []
const archived = (...ids: string[]): readonly SessionId[] => ids.map(sid)
@@ -54,6 +55,19 @@ describe('deriveGroups', () => {
expect(groups[1]!.sessions.map(session => session.id)).toEqual([sid('loose')])
})
it('applies stored Ungrouped order and appends new loose Sessions by recency', () => {
const sessions = list(summary('one', 3), summary('two', 2), summary('new', 4))
const groups = deriveGroups(
sessions,
[],
noArchive,
view([UNGROUPED_KEY], ['two', 'stale', 'two']),
)
expect(groups[0]!.sessions.map(session => session.id)).toEqual([
sid('two'), sid('new'), sid('one'),
])
})
it('shows only the current blank session in its Workspace count and tree', () => {
const currentBlank = { ...summary('current-blank', 5), blank: true }
const staleBlank = { ...summary('stale-blank', 4), blank: true }
@@ -9,6 +9,7 @@ import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
import type { WorkspaceBrowserProps } from '../src/client/contract/slots.ts'
import { createWorkspaceViewStore } from '../src/client/stores.ts'
import { UNGROUPED_KEY } from '../src/client/tree.ts'
import { WorkspaceBrowser } from '../src/client/WorkspaceBrowser.tsx'
import { zh } from '../src/client/locales.ts'
@@ -111,8 +112,8 @@ describe('WorkspaceBrowser', () => {
rerender(b, { useWorkspaces: hook(workspaceState([])) })
await waitFor(() => {
expect(b.store.getSnapshot().workspaceExpansion).toEqual({})
expect(b.store.getSnapshot().recentSessionOrder).toEqual({})
expect(b.store.getSnapshot().recentSessionUpdatedAt).toEqual({})
expect(b.store.getSnapshot().recentSessionOrder).toEqual({ [UNGROUPED_KEY]: [] })
expect(b.store.getSnapshot().recentSessionUpdatedAt).toEqual({ [UNGROUPED_KEY]: {} })
})
})
@@ -776,6 +777,55 @@ describe('WorkspaceBrowser', () => {
expect(insertSessionBefore).toHaveBeenCalledTimes(1)
})
it('persists Ungrouped drag order in both modes without writing a Host Workspace account', async () => {
const insertSessionBefore = vi.fn(async () => {})
const sessions = sessionState([summary('one', 3), summary('two', 2), summary('three', 1)])
const b = mount({
useSessions: hook(sessions),
useWorkspaces: hook(workspaceState([])),
insertSessionBefore,
})
fireEvent.click(screen.getByText('未分组'))
const dragAfter = (sourceTitle: string, targetTitle: string): void => {
const source = screen.getByText(sourceTitle).closest('[role="treeitem"]') as HTMLElement
const target = screen.getByText(targetTitle).closest('[role="treeitem"]') as HTMLElement
target.getBoundingClientRect = () => ({
top: 150, bottom: 184, left: 0, right: 200, width: 200, height: 34, x: 0, y: 150, toJSON: () => ({}),
})
fireEvent.dragStart(source, { dataTransfer: dragData() })
fireDrag(target, 'drop', 180)
}
dragAfter('one', 'three')
expect(b.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['two', 'three', 'one'])
dragAfter('two', 'one')
expect(b.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['three', 'one', 'two'])
expect(insertSessionBefore).not.toHaveBeenCalled()
fireEvent.click(screen.getByRole('button', { name: '视图选项' }))
fireEvent.click(screen.getByRole('menuitem', { name: '最近更新' }))
await waitFor(() => {
expect(b.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['one', 'two', 'three'])
})
dragAfter('one', 'three')
expect(b.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['two', 'three', 'one'])
expect(insertSessionBefore).not.toHaveBeenCalled()
b.view.unmount()
const restored = mount({
useSessions: hook(sessions),
useWorkspaces: hook(workspaceState([])),
insertSessionBefore,
})
expect(restored.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['two', 'three', 'one'])
expect(screen.getAllByRole('treeitem').slice(1).map(row => row.textContent)).toEqual([
expect.stringContaining('two'),
expect.stringContaining('three'),
expect.stringContaining('one'),
])
})
it('still sends the reorder when the dragged row left the group mid-drag', () => {
const insertSessionBefore = vi.fn(async () => {})
const sessions = sessionState([summary('one', 2), summary('two', 1)])