diff --git a/apps/web/tests/expected/session-archive-active/dialog.expected.md b/apps/web/tests/expected/session-archive-active/dialog.expected.md
index a9faf5c4dc..2a6f4d638f 100644
--- a/apps/web/tests/expected/session-archive-active/dialog.expected.md
+++ b/apps/web/tests/expected/session-archive-active/dialog.expected.md
@@ -1,7 +1,7 @@
- dialog "Stop and archive this session?":
- heading "Stop and archive this session?" [level=2]
- button "Close"
- - paragraph: “ARCHIVE_ACTIVE run the long command” still has work in progress. Archiving stops it first; you can restore the session later from “Show archived” in the sidebar, and the stopped work will not resume on its own.
+ - paragraph: “ARCHIVE_ACTIVE run the long command” still has work in progress. Archiving stops it first; you can restore the session later from the “All conversations (show archived)” filter in the sidebar, and the stopped work will not resume on its own.
- list "Work that will be stopped":
- listitem: The turn in progress
- listitem: "1 background job: node -e \"const fs=require('node:fs'); fs.writeFileSync('started.txt', 'started'); setInterval(() => { fs.appendFileSync('heartbeat.txt', '.') }, 200); setTimeout(() => { fs.writeFileSync('finished.txt', 'finished') }, 60000)\""
diff --git a/apps/web/tests/expected/workspace-management/grouping-options.expected.md b/apps/web/tests/expected/workspace-management/grouping-options.expected.md
index 2a91c7813d..d669656e3b 100644
--- a/apps/web/tests/expected/workspace-management/grouping-options.expected.md
+++ b/apps/web/tests/expected/workspace-management/grouping-options.expected.md
@@ -9,5 +9,6 @@
- menuitem "Last updated"
- separator
- text: Filter sessions
- - menuitem "Show archived"
+ - menuitem "Hide archived"
+ - menuitem "All conversations (show archived)"
- menuitem "Archived only"
diff --git a/apps/web/tests/session-archive-active.e2e.ts b/apps/web/tests/session-archive-active.e2e.ts
index 5f3dbaf380..d192d3d207 100644
--- a/apps/web/tests/session-archive-active.e2e.ts
+++ b/apps/web/tests/session-archive-active.e2e.ts
@@ -218,7 +218,7 @@ describe.skipIf(MODE === 'record')('web e2e: archiving a running Session stops i
it('the restored Session continues the conversation from a regular log', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-session-archive-active-restore'))
await page.getByRole('button', { name: 'View options' }).click()
- await page.getByRole('menuitem', { name: 'Show archived', exact: true }).click()
+ await page.getByRole('menuitem', { name: 'All conversations (show archived)', exact: true }).click()
await expect.poll(() => sessionRow().count(), { timeout: 10_000 }).toBe(1)
await clickHoverAction(sessionRow(), 'Session actions for ')
await page.getByRole('menuitem', { name: 'Unarchive session' }).click()
diff --git a/apps/web/tests/session-unarchive.e2e.ts b/apps/web/tests/session-unarchive.e2e.ts
index 068f5c0b6b..4db30e55a7 100644
--- a/apps/web/tests/session-unarchive.e2e.ts
+++ b/apps/web/tests/session-unarchive.e2e.ts
@@ -102,7 +102,7 @@ describe('web e2e: archived sessions are restored from the sidebar filter', () =
.toContain(SessionId(SEED_ID))
await page.getByRole('button', { name: 'View options' }).click()
- await page.getByRole('menuitem', { name: 'Show archived', exact: true }).click()
+ await page.getByRole('menuitem', { name: 'All conversations (show archived)', exact: true }).click()
await ungroupedSection()
await expect.poll(() => sessionRow.count(), { timeout: 10_000 }).toBe(1)
await clickHoverAction(sessionRow, `Session actions for ${title}`)
@@ -111,8 +111,10 @@ describe('web e2e: archived sessions are restored from the sidebar filter', () =
() => [...scaffold.ctx.workspaceRegistry.archivedSessionIds],
{ timeout: 10_000 },
).toEqual([])
+ // Back to the default filter: the restored row must be an ordinary row
+ // again, visible without any archived rows in the view.
await page.getByRole('button', { name: 'View options' }).click()
- await page.getByRole('menuitem', { name: 'Show archived', exact: true }).click()
+ await page.getByRole('menuitem', { name: 'Hide archived', exact: true }).click()
await ungroupedSection()
await expect.poll(() => sessionRow.count(), { timeout: 15_000 }).toBe(1)
diff --git a/packages/client/ui-primitives/src/ReferenceIcon.tsx b/packages/client/ui-primitives/src/ReferenceIcon.tsx
index 0f3baad8b3..ca566c40f6 100644
--- a/packages/client/ui-primitives/src/ReferenceIcon.tsx
+++ b/packages/client/ui-primitives/src/ReferenceIcon.tsx
@@ -2,7 +2,7 @@ import type { ReactNode } from 'react'
import { ICON_MEDIUM_STROKE, ICON_REGULAR_STROKE } from './icons/index.tsx'
import type { IconProps } from './icons/props.ts'
import {
- BrowseOutlineArtwork, FolderCloseArtwork, NewChatOutlineArtwork,
+ BrowseOutlineArtwork, ChatLinesOutlineArtwork, FolderCloseArtwork,
} from './icons/shared-artwork.tsx'
/** Reference domains with distinct composer and transcript glyphs. */
@@ -24,7 +24,7 @@ interface WeightedReferenceIconProps extends ReferenceIconProps {
*/
function ReferenceIconArtwork({ kind, size = 16, className, strokeWidth }: WeightedReferenceIconProps): ReactNode {
switch (kind) {
- case 'session': return
+ case 'session': return
case 'file': return
case 'folder': return
}
diff --git a/packages/client/ui-primitives/src/icons/index.tsx b/packages/client/ui-primitives/src/icons/index.tsx
index 56b7fac3a4..ce7e15f198 100644
--- a/packages/client/ui-primitives/src/icons/index.tsx
+++ b/packages/client/ui-primitives/src/icons/index.tsx
@@ -2,10 +2,11 @@
* Shared current-color product icons. Names identify the glyph and weight;
* rendered size remains a prop instead of part of the component name.
*/
+import { useId } from 'react'
import type { IconProps } from './props.ts'
import {
- BrowseOutlineArtwork, CodeBracketsArtwork, FolderCloseArtwork, GlobeOutlineArtwork,
- NewChatOutlineArtwork,
+ BrowseOutlineArtwork, ChatLinesOutlineArtwork, CodeBracketsArtwork, FolderCloseArtwork,
+ GlobeOutlineArtwork, NewChatOutlineArtwork,
} from './shared-artwork.tsx'
export type { IconProps } from './props.ts'
@@ -1109,12 +1110,8 @@ export const IconSendOutlineMedium = (props: IconProps) => (
)
-const IconQueueOutlineArtwork = ({ size = 14, className, strokeWidth }: WeightedIconProps) => (
-
+const IconQueueOutlineArtwork = ({ size = 14, ...rest }: WeightedIconProps) => (
+
)
/** Regular one-pixel IconQueueOutline artwork. */
@@ -1574,6 +1571,36 @@ export const IconChevronsUpDownOutlineMedium = (props: IconProps) => (
)
+const IconArchiveOffOutlineArtwork = ({ size = 16, className, strokeWidth }: WeightedIconProps) => {
+ // The mask carves whitespace along the slash out of the box strokes, so the
+ // id must stay unique per rendered instance.
+ const maskId = `dsh-archive-off-${useId().replaceAll(':', '')}`
+ return (
+
+ )
+}
+
+/** Regular one-pixel IconArchiveOffOutline artwork. */
+export const IconArchiveOffOutlineRegular = (props: IconProps) => (
+
+)
+
+/** Medium IconArchiveOffOutline artwork with a 1.3px stroke. */
+export const IconArchiveOffOutlineMedium = (props: IconProps) => (
+
+)
+
const IconArchiveCheckOutlineArtwork = ({ size = 16, className, strokeWidth }: WeightedIconProps) => (
)
+/**
+ * Render shared conversation geometry — the chat bubble around two text
+ * lines — for the queue product icon and session reference icons.
+ * @param props - Size, optional CSS class, and inherited stroke width.
+ * @returns The decorative SVG artwork.
+ */
+export const ChatLinesOutlineArtwork = ({ size = 16, className, strokeWidth }: WeightedArtworkProps) => (
+
+)
+
/**
* Render shared globe geometry for product and link icons.
* @param props - Size, optional CSS class, and inherited stroke width.
diff --git a/packages/client/ui-primitives/tests/icons.client.spec.tsx b/packages/client/ui-primitives/tests/icons.client.spec.tsx
index 279a644abc..5c59ff3593 100644
--- a/packages/client/ui-primitives/tests/icons.client.spec.tsx
+++ b/packages/client/ui-primitives/tests/icons.client.spec.tsx
@@ -17,8 +17,8 @@ const icons = Object.fromEntries(
const iconNames = Object.keys(icons)
describe('product icon set', () => {
- it('exports regular and medium variants for all 93 public glyphs', () => {
- expect(iconNames.length).toBe(186)
+ it('exports regular and medium variants for all 94 public glyphs', () => {
+ expect(iconNames.length).toBe(188)
expect(iconNames.some(name => /\d+$/.test(name))).toBe(false)
const regular = iconNames.filter(name => name.endsWith('Regular')).map(name => name.slice(0, -'Regular'.length))
const medium = iconNames.filter(name => name.endsWith('Medium')).map(name => name.slice(0, -'Medium'.length))
diff --git a/packages/client/ui-workspace/README.i18n.yaml b/packages/client/ui-workspace/README.i18n.yaml
index 373ff6d361..c67b624877 100644
--- a/packages/client/ui-workspace/README.i18n.yaml
+++ b/packages/client/ui-workspace/README.i18n.yaml
@@ -27,8 +27,8 @@
en: 9eab3bfbaf98c987
zh: 54e7e141dae5c9e8
/deepseek-ai-dsh-client-ui-workspace/use-this-package/managing-sessions:
- en: 745c8b9cb3858789
- zh: fb7fdf5856dfba89
+ en: d49eb4c9b3158cfe
+ zh: 251be2fe3d1ac857
/deepseek-ai-dsh-client-ui-workspace/use-this-package/pending-interactions:
en: 9fb88260380bf093
zh: 3bace2b96ecd4a82
diff --git a/packages/client/ui-workspace/README.md b/packages/client/ui-workspace/README.md
index d4d4e0df8f..6ae901dfa4 100644
--- a/packages/client/ui-workspace/README.md
+++ b/packages/client/ui-workspace/README.md
@@ -49,7 +49,7 @@ Collapsed search is one header action beside the view and add actions: activatin
The Session row's Rename action opens a dialog prefilled with the row's display title; confirming an unchanged title is deliberately allowed — it pins the current automatic title against regeneration. Double-clicking a title also opens Rename; for an unarchived Session, the preceding clicks open its conversation. Rename uses a temporary `workspaceOperation` reference and awaits its initial history opening. The row's Fork action forks at the source's last completed turn and increments the inherited persisted title through Session Controller without retaining the child, opening its history, or changing selection. Workspace Delete opens a confirmation that states the retention boundary; success removes the group while its Sessions remain under Ungrouped. Pin, Rename, Fork, and Archive are themselves entries of the `sidebar.workspaces.session.menu.item` list (pin and archive also of `sidebar.workspaces.session.row.action`), so a client plugin's action takes whatever position its `order` gives it.
-Archive commits without a confirmation dialog for a quiet Session and retains the Session's account position. A Session with running work is the one case that asks first: the Host refuses the plain archive and names that work, and the sidebar opens a stop-and-archive dialog listing it by family — the turn in progress, running subagents, background jobs, scheduled reminders, each with its labels — with the restore path stated; confirming asks the Host to stop the work the way the stop button does and archives once the archive set is durable, while the stops settle in the background, and cancelling leaves the Session running and visible. View options control visibility: the default hides archived Sessions, Show archived includes them, and Archived only hides ordinary Sessions. Visible archived rows are grayed and carry an accessible explanation that they cannot be opened until restored; Rename, Fork, and Unarchive remain available. A successful archive shows a notice with an undo action and a "show archived" action that switches the filter directly; a stop-and-archive shows the same notice under its own wording, and undo restores the Session without resuming the stopped work. Unarchive removes the archive mark without restoring a pin or changing the saved position.
+Archive commits without a confirmation dialog for a quiet Session and retains the Session's account position. A Session with running work is the one case that asks first: the Host refuses the plain archive and names that work, and the sidebar opens a stop-and-archive dialog listing it by family — the turn in progress, running subagents, background jobs, scheduled reminders, each with its labels — with the restore path stated; confirming asks the Host to stop the work the way the stop button does and archives once the archive set is durable, while the stops settle in the background, and cancelling leaves the Session running and visible. View options control visibility through one explicit three-way choice: Hide archived (the default) hides archived Sessions, All conversations (show archived) includes them, and Archived only hides ordinary Sessions and drops Workspaces without an archived Session; in tree grouping, children of a dropped Workspace nest under the nearest shown ancestor. Visible archived rows are grayed and carry an accessible explanation that they cannot be opened until restored; Rename, Fork, and Unarchive remain available. A successful archive shows a notice with an undo action and — while archived rows are hidden — a "filter archived sessions" action that switches the filter to All conversations (show archived); a stop-and-archive shows the same notice under its own wording, and undo restores the Session without resuming the stopped work. Unarchive removes the archive mark without restoring a pin or changing the saved position. An empty list shows a centered glyph-over-text placeholder; the Archived-only view names its own emptiness (No archived sessions yet) and adds a View other sessions text action that switches the filter back to Hide archived.
A Session title wider than its row is clipped with an ellipsis at rest. Hovering the row scrolls the title to its far edge — the incremented title of a fork, for example — and reveals it without the ellipsis; leaving the row returns the title to its start.
diff --git a/packages/client/ui-workspace/README.zh.md b/packages/client/ui-workspace/README.zh.md
index cf84f9e703..eaa28029fa 100644
--- a/packages/client/ui-workspace/README.zh.md
+++ b/packages/client/ui-workspace/README.zh.md
@@ -49,7 +49,7 @@ kind: "package-reference"
Session 行内的 Rename 操作打开一个以该行显示标题预填的对话框;确认未修改的标题是有意允许的——这正是把当前自动标题钉住、不再被重新生成覆盖的手势。双击标题也会打开 Rename;对于未归档 Session,先发生的点击会打开其对话。Rename 使用临时 `workspaceOperation` reference,并等待首次历史打开。行内 Fork 在源会话最后一个已完成轮次处 fork,通过 Session Controller 递增继承的持久化标题,不 retain 子会话、不打开其历史,也不改变选择。Workspace 行内的 Delete 操作会打开确认框,说明保留边界;成功后该分组被移除,其 Session 则留在 Ungrouped 下。Pin、Rename、Fork、Archive 本身就是 `sidebar.workspaces.session.menu.item` 列表的条目(pin 与 archive 同时也是 `sidebar.workspaces.session.row.action` 的条目),因此客户端插件的 action 由其 `order` 决定落在哪个位置。
-对静止的 Session,Archive 不经确认对话框直接提交,并保留 Session 的记账位置。仍有工作在跑的 Session 是唯一会先询问的情形:Host 拒绝普通归档并列出这些工作,侧栏随即打开"停止并归档"对话框,按族列出——进行中的回合、运行中的子代理、后台任务、定时提醒,各带名称——并写明恢复路径;确认后请 Host 按停止按钮同样的方式停止这些工作,归档集合持久化后即完成归档,停止在后台收敛;取消则让 Session 继续运行并保持可见。视图选项控制显隐:默认隐藏已归档 Session,显示已归档会将其纳入列表,仅显示已归档则隐藏普通 Session。可见的归档行置灰,并提供无障碍说明,告知取消归档后才能打开;Rename、Fork 与取消归档仍然可用。归档成功后的提示提供"撤销"和"筛选已归档会话"两个动作,后者直接把筛选切到显示已归档;停止并归档显示同样的提示但措辞不同,撤销只恢复 Session,不会让被停止的工作继续。取消归档移除归档标记,但不恢复置顶,也不改变保存的位置。
+对静止的 Session,Archive 不经确认对话框直接提交,并保留 Session 的记账位置。仍有工作在跑的 Session 是唯一会先询问的情形:Host 拒绝普通归档并列出这些工作,侧栏随即打开"停止并归档"对话框,按族列出——进行中的回合、运行中的子代理、后台任务、定时提醒,各带名称——并写明恢复路径;确认后请 Host 按停止按钮同样的方式停止这些工作,归档集合持久化后即完成归档,停止在后台收敛;取消则让 Session 继续运行并保持可见。视图选项以一组显式三选一控制显隐:隐藏已归档(默认项)隐藏已归档 Session,全部对话(显示已归档)将其纳入列表,仅显示已归档则隐藏普通 Session,并丢弃没有归档 Session 的 Workspace;树形分组下,被丢弃 Workspace 的子级挂到最近一个仍显示的祖先下。可见的归档行置灰,并提供无障碍说明,告知取消归档后才能打开;Rename、Fork 与取消归档仍然可用。归档成功后的提示提供"撤销"动作,并在归档行仍被隐藏时附带"筛选已归档会话"动作,后者直接把筛选切到全部对话(显示已归档);停止并归档显示同样的提示但措辞不同,撤销只恢复 Session,不会让被停止的工作继续。取消归档移除归档标记,但不恢复置顶,也不改变保存的位置。列表为空时显示居中的"图标在上、文字在下"占位;仅显示已归档视图用自己的文案(暂无已归档会话),并附"查看其他会话"文字按钮,点击把筛选切回隐藏已归档。
标题宽于所在行时,静止状态以省略号裁切。把指针停在行上,标题会滚动到远端——例如 fork 递增后的标题——并在揭示时不显示省略号;指针离开后标题回到开头。
diff --git a/packages/client/ui-workspace/src/client/contract/slots.ts b/packages/client/ui-workspace/src/client/contract/slots.ts
index de4d9099d4..830cc3570a 100644
--- a/packages/client/ui-workspace/src/client/contract/slots.ts
+++ b/packages/client/ui-workspace/src/client/contract/slots.ts
@@ -438,10 +438,15 @@ export type SessionArchiveConfirmProps =
& Omit
& PropsHooks
-/** Props of the row toast entry in `shell.overlay`. */
+/**
+ * Props of the row toast entry in `shell.overlay`. The declared viewing store
+ * carries the archived filter; the archived notice omits its filter action
+ * when archived rows are already visible.
+ */
export type RowToastProps =
PropsRuntime<'shell.overlay'>
& PropsLocale<'workspace'>
+ & PropsStore
& Omit
& PropsHooks
diff --git a/packages/client/ui-workspace/src/client/index.ts b/packages/client/ui-workspace/src/client/index.ts
index 61ca586a8f..98ff858e35 100644
--- a/packages/client/ui-workspace/src/client/index.ts
+++ b/packages/client/ui-workspace/src/client/index.ts
@@ -298,8 +298,10 @@ export function apply(ctx: Context): void {
yield ctx.slots.register({
name: 'shell.overlay', id: 'workspace.session-archive', locale: NS, inject: archiveConfirmInjected,
}, SessionArchiveConfirmDialog)
+ // The toast shares the browser's viewing store: it reads the archived
+ // filter to drop the archived notice's filter action once rows are visible.
yield ctx.slots.register({
- name: 'shell.overlay', id: 'workspace.row-toast', locale: NS, inject: rowToastInjected,
+ name: 'shell.overlay', id: 'workspace.row-toast', locale: NS, store: viewStore, inject: rowToastInjected,
}, RowActionToast)
})
ctx.slots.inject('conversation.hero.workspace', () => ctx.slots.register(
diff --git a/packages/client/ui-workspace/src/client/locales.ts b/packages/client/ui-workspace/src/client/locales.ts
index 0cbb0efd5d..b94557b924 100644
--- a/packages/client/ui-workspace/src/client/locales.ts
+++ b/packages/client/ui-workspace/src/client/locales.ts
@@ -27,11 +27,14 @@ export const zh = {
'orderBy.manual': '手动排序',
'orderBy.updated': '最近更新',
'filterBy.label': '筛选会话',
- 'viewOptions.showArchived': '显示已归档',
+ 'viewOptions.hideArchived': '隐藏已归档',
+ 'viewOptions.showArchived': '全部对话(显示已归档)',
'viewOptions.onlyArchived': '仅显示已归档',
'sessions.expand': '展开其余 {n} 个会话',
'sessions.collapse': '收起',
'empty.none': '暂无会话',
+ 'empty.noneArchived': '暂无已归档会话',
+ 'empty.viewOthers': '查看其他会话',
'empty.noMatches': '无匹配结果',
'workspace.add': '添加工作区',
'search.sessions.aria': '搜索会话',
@@ -65,7 +68,7 @@ export const zh = {
'toast.archived': '会话已归档,可',
'toast.stoppedAndArchived': '已停止并归档,可',
'archive.confirm.title': '停止并归档此会话?',
- 'archive.confirm.desc': '“{title}”仍有正在进行的工作。归档会先停止这些工作;之后可在侧栏“显示已归档”中恢复会话,被停止的工作不会自动继续。',
+ 'archive.confirm.desc': '“{title}”仍有正在进行的工作。归档会先停止这些工作;之后可在侧栏筛选“全部对话(显示已归档)”中恢复会话,被停止的工作不会自动继续。',
'archive.confirm.activity': '将被停止的工作',
'archive.confirm.turn': '进行中的回合',
'archive.confirm.subagents.one': '{n} 个运行中的子智能体:{names}',
@@ -144,11 +147,14 @@ export const en = {
'orderBy.manual': 'Manual',
'orderBy.updated': 'Last updated',
'filterBy.label': 'Filter sessions',
- 'viewOptions.showArchived': 'Show archived',
+ 'viewOptions.hideArchived': 'Hide archived',
+ 'viewOptions.showArchived': 'All conversations (show archived)',
'viewOptions.onlyArchived': 'Archived only',
'sessions.expand': 'Show {n} more sessions',
'sessions.collapse': 'Show less',
'empty.none': 'No sessions yet',
+ 'empty.noneArchived': 'No archived sessions yet',
+ 'empty.viewOthers': 'View other sessions',
'empty.noMatches': 'No matches',
'workspace.add': 'Add workspace',
'search.sessions.aria': 'Search sessions',
@@ -182,7 +188,7 @@ export const en = {
'toast.archived': 'Session archived. You can ',
'toast.stoppedAndArchived': 'Session stopped and archived. You can ',
'archive.confirm.title': 'Stop and archive this session?',
- 'archive.confirm.desc': '“{title}” still has work in progress. Archiving stops it first; you can restore the session later from “Show archived” in the sidebar, and the stopped work will not resume on its own.',
+ 'archive.confirm.desc': '“{title}” still has work in progress. Archiving stops it first; you can restore the session later from the “All conversations (show archived)” filter in the sidebar, and the stopped work will not resume on its own.',
'archive.confirm.activity': 'Work that will be stopped',
'archive.confirm.turn': 'The turn in progress',
'archive.confirm.subagents.one': '{n} running subagent: {names}',
diff --git a/packages/client/ui-workspace/src/client/rows/Rows.module.css b/packages/client/ui-workspace/src/client/rows/Rows.module.css
index 2e0ff9f469..49b3a59274 100644
--- a/packages/client/ui-workspace/src/client/rows/Rows.module.css
+++ b/packages/client/ui-workspace/src/client/rows/Rows.module.css
@@ -84,6 +84,11 @@
color: var(--dsw-alias-label-tertiary);
}
+/* The 40% share only protects a snippet; a snippetless match keeps the row. */
+.searchResultWorkspace:only-child {
+ max-width: 100%;
+}
+
.searchResultSnippet {
flex: 1;
min-width: 0;
diff --git a/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css b/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css
index c85844415f..df1ec40315 100644
--- a/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css
+++ b/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css
@@ -519,6 +519,33 @@
font-size: 13px;
}
+.emptyState {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ margin-top: 80px;
+ padding: 0 12px;
+ color: var(--dsw-alias-label-tertiary);
+ font-size: 13px;
+ line-height: 20px;
+}
+
+.emptyState > svg {
+ margin-bottom: 4px;
+ color: var(--dsw-alias-label-caption);
+}
+
+.emptyAction {
+ padding: 0;
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-size: 13px;
+ line-height: 20px;
+ color: var(--dsw-alias-link);
+}
+
/* Rename dialog form (same figma dialog family as the create modals). */
.renameInput {
box-sizing: border-box;
diff --git a/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx b/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx
index 7de46dc0e9..ea3e1b06ea 100644
--- a/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx
+++ b/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx
@@ -17,10 +17,10 @@
import { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react'
import clsx from 'clsx'
import {
- Button, IconArchiveCheckOutlineRegular, IconArchiveOutlineRegular,
+ Button, IconArchiveCheckOutlineRegular, IconArchiveOffOutlineRegular, IconArchiveOutlineRegular,
IconChevronsUpDownOutlineRegular, IconClockOutlineRegular, IconCloseFillRegular,
IconFlatListOutlineRegular, IconFolderCloseRegular, IconProjectAddOutlineRegular,
- IconSearchOutlineRegular, IconSlidersTwoOutlineRegular,
+ IconQueueOutlineRegular, IconSearchOutlineRegular, IconSlidersTwoOutlineRegular,
IconWorkspaceTreeOutlineRegular, Menu, Modal, Toast, Tooltip,
} from '@deepseek-ai/dsh-client-ui-primitives'
import type {
@@ -127,22 +127,21 @@ function ViewOptionsMenu({ groupBy, orderBy, archivedFilter, onGroupPick, onOrde
{ id: 'updated', label: t('orderBy.updated'), icon: },
{ type: 'separator' as const, id: 'archived-filter-separator' },
{ type: 'label' as const, id: 'filter-by', text: t('filterBy.label') },
- { id: 'show-archived', label: t('viewOptions.showArchived'), icon: },
+ { id: 'hide-archived', label: t('viewOptions.hideArchived'), icon: },
+ { id: 'show-archived', label: t('viewOptions.showArchived'), icon: },
{ id: 'only-archived', label: t('viewOptions.onlyArchived'), icon: },
]}
selectedIds={[
groupBy,
orderBy,
- ...archivedFilter === 'show' ? ['show-archived'] : [],
- ...archivedFilter === 'only' ? ['only-archived'] : [],
+ { default: 'hide-archived', show: 'show-archived', only: 'only-archived' }[archivedFilter],
]}
onSelect={(id) => {
if (id === 'workspace' || id === 'workspace-tree' || id === 'flat') onGroupPick(id)
else if (id === 'manual' || id === 'updated') onOrderPick(id)
- // The two archived items are mutually exclusive; re-picking the
- // selected one returns to the default hide-archived view.
- else if (id === 'show-archived') onArchivedFilterPick(archivedFilter === 'show' ? 'default' : 'show')
- else if (id === 'only-archived') onArchivedFilterPick(archivedFilter === 'only' ? 'default' : 'only')
+ else if (id === 'hide-archived') onArchivedFilterPick('default')
+ else if (id === 'show-archived') onArchivedFilterPick('show')
+ else if (id === 'only-archived') onArchivedFilterPick('only')
setOpen(false)
}}
align="end"
@@ -247,6 +246,8 @@ type SessionTreeProps = Pick<
setSessionOrder: (accountKey: string, order: readonly string[]) => void
/** Registry-global pin and archive sets plus the archived-visibility choice. */
rowState: SessionRowState
+ /** Switch the archived filter back to the default hide-archived view. */
+ onLeaveArchivedOnly: () => void
/** Open the browser-owned rename dialog for a real Workspace group. */
onRenameRequest: (workspaceId: WorkspaceId, currentTitle: string) => void
/** Open the browser-owned delete-confirmation dialog for a real Workspace group. */
@@ -259,10 +260,26 @@ type SessionTreeProps = Pick<
onSessionRevealed: (sessionId: SessionId) => void
}
+/** The list-empty placeholder — a glyph over the text; the archived-only view names its filter and offers the way back. */
+function EmptySessions({ rowState, onLeaveArchivedOnly, t }: Pick) {
+ const archivedOnly = rowState.archivedFilter === 'only'
+ return (
+