fix(client): refine workspace view controls

This commit is contained in:
_Kerman
2026-08-11 16:46:52 +08:00
parent 34e90dc3fe
commit 5b1da441d5
7 changed files with 77 additions and 42 deletions

View File

@@ -118,11 +118,11 @@ function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }: {
// be cut off at the header's bounds.
portal
anchor={(
<Tooltip label={t('groupBy.label')} side="bottom" delayMs={500}>
<Tooltip label={t('viewOptions.label')} side="bottom" delayMs={500}>
<button
type="button"
className={clsx(css.iconButton, css.wide)}
aria-label={t('groupBy.label')}
aria-label={t('viewOptions.label')}
onClick={() => { setOpen(v => !v) }}
>
<IconPersonalizationOutline16 />
@@ -163,13 +163,13 @@ type SessionTreeProps = Pick<
workspaceExpansion: Readonly<Record<string, boolean>>
/** Persist one Workspace group's zero-or-five-session state. */
setWorkspaceExpanded: (key: string, expanded: boolean) => void
/** Editable orders used by recent-update mode. */
/** Shared editable orders used by both Session order modes. */
recentSessionOrder: Readonly<Record<string, readonly string[]>>
/** Last update timestamps observed by recent-update mode. */
/** Last update timestamps observed for one-time recent-update promotions. */
recentSessionUpdatedAt: Readonly<Record<string, Readonly<Record<string, number>>>>
/** Replace one recent-mode order and its observed timestamps. */
/** Replace one shared order and its observed timestamps. */
syncRecentSessions: (workspaceKey: string, order: string[], updatedAt: Record<string, number>) => void
/** Apply a manual drag inside one recent-mode order. */
/** Apply a drag to one shared order. */
setRecentSessionOrder: (workspaceKey: string, order: string[]) => void
/** Registry-global archive set (hidden rows). */
archivedSessionIds: readonly SessionNode['id'][]
@@ -181,7 +181,7 @@ type SessionTreeProps = Pick<
onSessionRename: (sessionId: SessionNode['id'], currentTitle: string) => void
/** Archive a session (row menu action; the row disappears on the state echo). */
onSessionArchive: (sessionId: SessionNode['id']) => void
/** Session visual order; manual mode drags durable order, updated mode drags its view order. */
/** Session order behavior: fixed after edits, or additionally promoted by user activity. */
orderBy: SessionOrderBy
}
@@ -201,6 +201,7 @@ function SessionTree({
const sessionDropCommitted = useRef(false)
const [workspaceDrag, setWorkspaceDrag] = useState<WorkspaceDragState | null>(null)
const workspaceDropCommitted = useRef(false)
const previousOrderBy = useRef(orderBy)
const nativeDragActive = drag !== null || workspaceDrag !== null
useEffect(() => {
if (!nativeDragActive) return
@@ -232,16 +233,18 @@ function SessionTree({
[workspaceExpansion],
)
useEffect(() => {
if (orderBy !== 'updated' || list.phase !== 'ready') return
if (list.phase !== 'ready') return
const switchedToUpdated = previousOrderBy.current !== 'updated' && orderBy === 'updated'
previousOrderBy.current = orderBy
for (const workspace of workspaces) {
const key = workspace.workspaceId as string
const sessionIds = workspace.sessionIds.filter(id => list.byId[id] !== undefined)
const previousOrder = recentSessionOrder[key]
const previousUpdatedAt = recentSessionUpdatedAt[key] ?? {}
let nextOrder = reconciledSessionOrder(sessionIds, previousOrder)
if (previousOrder === undefined) {
if (orderBy === 'updated' && (previousOrder === undefined || switchedToUpdated)) {
nextOrder.sort((a, b) => compareSessionRecency(a, b, list.byId))
} else {
} else if (orderBy === 'updated') {
const promoted = sessionIds
.filter((id) => {
const session = list.byId[id]
@@ -270,14 +273,12 @@ function SessionTree({
}
}, [list, orderBy, recentSessionOrder, recentSessionUpdatedAt, syncRecentSessions, workspaces])
const orderedWorkspaces = useMemo(() => {
if (orderBy !== 'updated') return workspaces
return workspaces.map((workspace) => {
const stored = recentSessionOrder[workspace.workspaceId as string]
const sessionIds = reconciledSessionOrder(workspace.sessionIds, stored)
if (stored === undefined) sessionIds.sort((a, b) => compareSessionRecency(a, b, list.byId))
return { ...workspace, sessionIds }
})
}, [list.byId, orderBy, recentSessionOrder, workspaces])
}, [recentSessionOrder, workspaces])
const groups = useMemo(
() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, { expandedProjects }, 'manual'),
[list, orderedWorkspaces, archivedSessionIds, expandedProjects],
@@ -846,7 +847,7 @@ export function WorkspaceBrowser({
searchInput.current?.focus()
}}
>
<Tooltip label={t('search')} disabled={searchExpanded}>
<Tooltip label={t('search')} side="bottom" delayMs={500} disabled={searchExpanded}>
<button
type="button"
className={css.searchButton}

View File

@@ -10,6 +10,7 @@ export const zh = {
'session.new': '新会话',
'section.workspaces': '工作区',
'section.sessions': '会话',
'viewOptions.label': '视图选项',
'groupBy.label': '分组方式',
'groupBy.workspace': '按工作区',
'groupBy.flat': '单列表',
@@ -22,7 +23,7 @@ export const zh = {
'empty.noMatches': '无匹配结果',
'workspace.add': '添加工作区',
'search.sessions.aria': '搜索会话',
'search.placeholder': '搜索名称、关键词…',
'search.placeholder': '搜索会话…',
'search.clear': '清除搜索',
'search.results.aria': '搜索结果',
'search.pending': '正在搜索会话历史…',
@@ -78,6 +79,7 @@ export const en = {
'session.new': 'New Session',
'section.workspaces': 'Workspaces',
'section.sessions': 'Sessions',
'viewOptions.label': 'View options',
'groupBy.label': 'Group by',
'groupBy.workspace': 'WorkSpace',
'groupBy.flat': 'In one list',
@@ -90,7 +92,7 @@ export const en = {
'empty.noMatches': 'No matches',
'workspace.add': 'Add workspace',
'search.sessions.aria': 'Search sessions',
'search.placeholder': 'Search name, keywords...',
'search.placeholder': 'Search sessions...',
'search.clear': 'Clear search',
'search.results.aria': 'Search results',
'search.pending': 'Searching session history…',

View File

@@ -9,7 +9,7 @@ import { defineStore, type EngineStoreHandle } from '@deepseek-ai/dsh-client-run
/** Session-list grouping mode: workspace sections or one flat recency list. */
export type WorkspaceGroupBy = 'workspace' | 'flat'
/** Session order: durable Workspace order or an activity-promoted editable order. */
/** Session order: user-arranged only, or user-arranged plus activity promotion. */
export type WorkspaceOrderBy = 'manual' | 'updated'
/** Workspace browser viewing state persisted across surface remounts and reloads. */
@@ -18,9 +18,9 @@ type WorkspaceViewState = {
orderBy: WorkspaceOrderBy
/** Explicit zero-or-five-session state keyed by Workspace group identity. */
workspaceExpansion: Record<string, boolean>
/** Editable per-Workspace order used by recent-update mode. */
/** Shared editable per-Workspace order; recent-update mode may promote rows within it. */
recentSessionOrder: Record<string, string[]>
/** Last observed update timestamps used to detect promotion events. */
/** Last observed update timestamps used to detect one-time promotion events. */
recentSessionUpdatedAt: Record<string, Record<string, number>>
}