@qilin-agent/client-ui-workspace 3.1.7 → 3.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -27,8 +27,8 @@
27
27
  en: 5e049b90c55eddb3
28
28
  zh: e06dc8d0d25c013f
29
29
  /qilin-agent-client-ui-workspace/use-this-package/search:
30
- en: faf570bcad825439
31
- zh: a65300b0b40a8550
30
+ en: 77fd4ac670f676cf
31
+ zh: 89277e09cafa0af4
32
32
  /qilin-agent-client-ui-workspace/use-this-package/managing-sessions:
33
33
  en: 36a910e6ad337e1c
34
34
  zh: 9607173765bac0c7
@@ -48,8 +48,8 @@
48
48
  en: 745e917ae00da6ab
49
49
  zh: 32c745f9078829b8
50
50
  /qilin-agent-client-ui-workspace/understand-the-implementation/view-state:
51
- en: dc93d10e80153f62
52
- zh: 5caa3399dbf84f62
51
+ en: 4b7f2f65d6259d33
52
+ zh: 7bbccd66050725f4
53
53
  /qilin-agent-client-ui-workspace/understand-the-implementation/hover-cards:
54
54
  en: f236cadca4c98088
55
55
  zh: 8b36850c1d5f73d2
package/README.md CHANGED
@@ -43,7 +43,7 @@ Hierarchy uses registered canonical paths only. It does not scan for projects or
43
43
 
44
44
  ### Search
45
45
 
46
- Collapsed search is one header action beside the view and add actions: activating it expands the field across the header. A non-blank query replaces either browsing mode with one flat result list — case-insensitive title and Workspace substring matches appear immediately, while a 250 ms debounced Host request adds ranked current-conversation content matches and snippets. Each new query aborts the preceding request; a failed content search leaves metadata matches visible with a warning. The list is capped at 20. Choosing a result clears and collapses search, opens the Session, and scrolls its row into view in the configured browsing mode; grouped browsing also expands its Workspace and the full Session list when required.
46
+ Collapsed search is one header action beside the view and add actions: activating it expands the field across the header. A non-blank query replaces either browsing mode with one flat result list — case-insensitive title and Workspace substring matches appear immediately, while a 250 ms debounced Host request adds ranked current-conversation content matches and snippets. Each new query aborts the preceding request; a failed content search leaves metadata matches visible with a warning. The list is capped at 20. Choosing a result clears and collapses search, opens the Session, and scrolls its row into view in the configured browsing mode; grouped browsing also expands its Workspace and the full Session list when required. Opening or closing search in the expanded sidebar animates the header's trailing inset, inline padding, and gap together with the field over 180 ms, and a reduced-motion preference settles them at once.
47
47
 
48
48
  ### Managing sessions
49
49
 
@@ -89,8 +89,12 @@ The chip occupies the conversation hero's `conversation.hero.agentPreset` hole a
89
89
 
90
90
  ### View state
91
91
 
92
+ Workspace and Session rows take their metrics from the properties the sidebar root inherits to this region. The expanded-row padding minus the paint inset sets the leading cell, the label gap spaces the cells, and the inset itself rides a transparent border so the hover fill stays inset without moving content; a Session row shows the same gap on its title margin instead of the row. Trailing actions sit 13 px apart on 16 px control cells, and each button carries a 24 px click target centred by a -4 px margin. The time label pulls 1 px so its edge matches the action strip, the section label keeps a 1 px left offset, and Session insertion markers compensate the row's transparent border. On a coarse pointer the hover-only verb strip is always drawn, the archive verb yields its cell to the menu and pin verbs (it stays in the row menu), a tapped row keeps no hover fill, and a Workspace row toggles from Enter or Space.
93
+
92
94
  Once the Workspace list baseline is ready, browser-persisted expansion and manual Session-order records retain only current Workspace ids plus Ungrouped and the flat-list account. `WorkspaceView.sessionIds` supplies real-Workspace membership, not Session display order. View actions require the current account orders explicitly. Flat-list membership and ordering use Session ids; row rendering adds status indicators once. Entering Manual snapshots every active account from the current display; reconciliation retains saved members that still belong to the account, removes departed members, and appends newly known members by recency. A new membership entry without a Session summary is omitted until that summary arrives, while an already saved slot survives a temporarily missing summary. During Workspace reconnection, Manual records an observed blank Session at the front of its saved flat and known group orders without removing other saved members; full membership reconciliation waits for the Workspace baseline. This reconciliation remains mounted while the sidebar is a rail or search replaces its body. Last updated derives directly from each current list snapshot without reading or writing saved positions; equal timestamps use Session ids as a stable tie-break. The shared sidebar projection hides rows whose durable Session summary has `origin: 'subagent'`, and each visible ordinary row inherits the blue activity indicator while any descendant reached through uninterrupted subagent-origin lineage is running. The same pure derivation reads the Schedule key from list projection values for grouped, flat, and search nodes; the package uses only the type-only `@qilin-agent/schedule/client` dependency and does not import the Schedule runtime or `ui-schedule`.
93
95
 
96
+ Row motion belongs to [AnimatedRows](../ui-primitives/src/AnimatedRows.tsx). The grouped tree and the flat list render through it and key every row with `data-row-key` (`workspace:<key>`, `session:<id>`, `overflow:<group>`, `empty`); it measures keyed rows before and after commits that change membership or order, glides and fades them with native animations, and paints removed rows as inert copies outside the scrolling list. Initial loading, drag commits, overflow expansion, and view-option changes settle immediately, and a reduced-motion preference skips the animation.
97
+
94
98
  ### Hover cards
95
99
 
96
100
  Workspace and Session hover cards copy the value their row clips: activating a Workspace card writes its full directory path, while activating a non-blank Session card writes its full display title. A provisional blank New Session card remains read-only because its localized label is a placeholder rather than session content.
package/README.zh.md CHANGED
@@ -43,7 +43,7 @@ kind: "package-reference"
43
43
 
44
44
  ### 搜索
45
45
 
46
- 折叠搜索是视图和添加操作旁的一枚区头按钮:激活后输入框会扩展并占据区头。非空白查询会以单一扁平结果列表替代任一浏览模式——不区分大小写的标题和 Workspace 子串匹配项会立即显示,经 250 ms 防抖的 Host 请求则会加入经过排序的当前对话内容匹配项及其摘要片段。每次新查询都会中止前一个请求;内容搜索失败时,元数据匹配项仍会显示,同时给出警告。列表最多显示 20 条结果。选择结果会清空并收起搜索、打开 Session,并在当前浏览模式中将其行滚动到可见区域;分组浏览还会按需展开所属 Workspace 和完整 Session 列表。
46
+ 折叠搜索是视图和添加操作旁的一枚区头按钮:激活后输入框会扩展并占据区头。非空白查询会以单一扁平结果列表替代任一浏览模式——不区分大小写的标题和 Workspace 子串匹配项会立即显示,经 250 ms 防抖的 Host 请求则会加入经过排序的当前对话内容匹配项及其摘要片段。每次新查询都会中止前一个请求;内容搜索失败时,元数据匹配项仍会显示,同时给出警告。列表最多显示 20 条结果。选择结果会清空并收起搜索、打开 Session,并在当前浏览模式中将其行滚动到可见区域;分组浏览还会按需展开所属 Workspace 和完整 Session 列表。在展开的侧边栏中,打开或收起搜索会以 180 ms 动画一并改变区头的尾部内缩、行内内边距和间距,开启减少动态效果时则立即落位。
47
47
 
48
48
  ### 管理会话
49
49
 
@@ -89,8 +89,12 @@ chip 占据会话英雄区的 `conversation.hero.agentPreset` 槽位,把工作
89
89
 
90
90
  ### 视图状态
91
91
 
92
+ Workspace 与 Session 行采用侧边栏根节点继承给本区域的属性:展开态行内边距减去底色内缩决定前置单元格,标签间距决定单元格间隔,内缩本身由一条透明边框承载,因此悬停底色保持内缩而内容不移动;Session 行不在行上留该间距,而是在标题外边距上留同样的值。行尾操作按钮以 13 px 间隔排在 16 px 控制格上,每个按钮以 -4 px 补偿外边距承载居中的 24 px 点击热区。时间标签左收 1 px 以与操作条边缘对齐,区头标签保留 1 px 左偏移,Session 插入标记补偿该透明边框。粗指针(触屏)上仅悬停可见的操作条始终显示,归档动词把位置让给菜单与置顶动词(仍保留在行菜单里),被点按的行不会残留悬停底色,Workspace 行可用 Enter 或空格切换折叠。
93
+
92
94
  Workspace 列表基线就绪后,浏览器持久化的展开状态和手动 Session 顺序记录只保留当前 Workspace id、Ungrouped 和单列表记账。`WorkspaceView.sessionIds` 提供真实 Workspace 的成员关系,而不提供 Session 显示顺序。视图操作要求显式传入当前各记账的顺序。单列表的成员筛选和排序使用 Session id,行渲染只计算一次状态指示。进入手动排序会从当前显示结果一次性记录每个有效记账;对账会保留仍属于该记账的已保存成员、移除已经离开的成员,并按最近更新时间追加新发现的成员。尚无 Session 摘要的新成员会等摘要到达后再加入,而已经保存的位置在摘要暂时缺失时仍会保留。Workspace 重连期间,手动排序会将已观察到的空白 Session 记录到已保存的单列表及已知分组顺序首位,不移除其他已保存成员;完整成员对账等待 Workspace 基线到齐。即使侧边栏收成窄栏或搜索替代列表主体,这项对账也保持挂载。最近更新直接从每份当前列表快照派生,不读取或写入已保存位置;时间相同时按 Session id 稳定排序。共享侧边栏投影会隐藏持久化 Session 摘要中带有 `origin: 'subagent'` 的行;每个可见普通行都会在经不间断的 subagent 谱系可达的任一后代运行时继承蓝色活动指示器。同一项纯派生逻辑还会为分组、平铺与搜索节点读取列表 projection value 中的 Schedule key;本包只使用纯类型依赖 `@qilin-agent/schedule/client`,不会导入 Schedule 运行时或 `ui-schedule`。
93
95
 
96
+ 行动效由 [AnimatedRows](../ui-primitives/src/AnimatedRows.tsx) 负责。分组树与平铺列表都经它渲染,并为每一行写上 `data-row-key`(`workspace:<key>`、`session:<id>`、`overflow:<group>`、`empty`);它在改变成员关系或顺序的提交前后测量带键行,以原生动画平移与渐隐,并把离场行作为惰性副本绘制在滚动列表之外。首次加载、拖拽提交、展开溢出和视图选项切换都立即落位,开启减少动态效果时完全跳过动画。
97
+
94
98
  ### 悬浮卡片
95
99
 
96
100
  Workspace 与 Session 悬浮卡片会复制对应行被截断的值:激活 Workspace 卡片会写入其完整目录路径,激活非空白 Session 卡片则会写入其完整显示标题。临时的空白「新会话」卡片保持只读,因为其本地化标签是占位文案,并非会话内容。
package/lib/client.js CHANGED
@@ -9,6 +9,120 @@ window.__ModuleLoader__.load({
9
9
  let _qilin_agent_kylin = require("@qilin-agent/kylin");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
11
  let react = require("react");
12
+ //#region lib/types/client/context-menus.js
13
+ /**
14
+ * Register the built-in Session and Workspace actions on the shared menu.
15
+ * @param ctx - workspace plugin context owning menus, membership, and locale.
16
+ * @param actions - shared row callbacks; execution always uses the captured object.
17
+ */
18
+ function registerWorkspaceMenus(ctx, actions) {
19
+ const t = ctx.locale.bind("workspace");
20
+ const state = () => ctx.workspaces.list.getSnapshot();
21
+ ctx.effect(() => ctx.contextMenus.register({
22
+ id: "workspace.session.pin",
23
+ target: "session",
24
+ order: 100,
25
+ observe: () => [ctx.workspaces.list, ctx.locale],
26
+ resolve: ({ sessionId }) => {
27
+ const pinned = state().pinnedSessionIds.includes(sessionId);
28
+ return [{
29
+ id: "pin",
30
+ label: t(pinned ? "menu.unpinSession" : "menu.pinSession"),
31
+ icon: { key: pinned ? "workspace.unpin" : "workspace.pin" },
32
+ run: () => {
33
+ (pinned ? actions.unpin : actions.pin)(sessionId);
34
+ }
35
+ }];
36
+ }
37
+ }), "ui-workspace: pin menu");
38
+ ctx.effect(() => ctx.contextMenus.register({
39
+ id: "workspace.session.rename",
40
+ target: "session",
41
+ order: 200,
42
+ resolve: ({ sessionId, displayTitle }) => [{
43
+ id: "rename",
44
+ label: t("rename"),
45
+ icon: { key: "workspace.rename" },
46
+ commandId: "session.rename",
47
+ run: () => {
48
+ actions.requestSessionRename(sessionId, displayTitle);
49
+ }
50
+ }]
51
+ }), "ui-workspace: rename menu");
52
+ ctx.effect(() => ctx.contextMenus.register({
53
+ id: "workspace.session.fork",
54
+ target: "session",
55
+ order: 300,
56
+ resolve: ({ sessionId }) => [{
57
+ id: "fork",
58
+ label: t("menu.fork"),
59
+ icon: { key: "workspace.fork" },
60
+ commandId: "session.fork",
61
+ run: () => {
62
+ actions.fork(sessionId);
63
+ }
64
+ }]
65
+ }), "ui-workspace: fork menu");
66
+ ctx.effect(() => ctx.contextMenus.register({
67
+ id: "workspace.session.id",
68
+ target: "session",
69
+ order: 450,
70
+ resolve: ({ sessionId }) => [{
71
+ id: "copy",
72
+ label: t("menu.sessionId"),
73
+ icon: { key: "workspace.copy" },
74
+ separatorBefore: true,
75
+ run: () => {
76
+ actions.copySessionId(sessionId);
77
+ }
78
+ }]
79
+ }), "ui-workspace: Session id menu");
80
+ ctx.effect(() => ctx.contextMenus.register({
81
+ id: "workspace.session.archive",
82
+ target: "session",
83
+ order: 600,
84
+ resolve: ({ sessionId }) => [{
85
+ id: "archive",
86
+ label: t("menu.archiveSession"),
87
+ icon: { key: "workspace.archive" },
88
+ separatorBefore: true,
89
+ afterContent: true,
90
+ commandId: "session.archive",
91
+ run: () => {
92
+ actions.archive(sessionId);
93
+ }
94
+ }]
95
+ }), "ui-workspace: archive menu");
96
+ ctx.effect(() => ctx.contextMenus.register({
97
+ id: "workspace.header.rename",
98
+ target: "workspace",
99
+ order: 100,
100
+ resolve: ({ workspaceId, displayTitle }) => [{
101
+ id: "rename",
102
+ label: t("rename"),
103
+ icon: { key: "workspace.rename" },
104
+ run: () => {
105
+ actions.requestWorkspaceAction(workspaceId, displayTitle, "rename");
106
+ }
107
+ }]
108
+ }), "ui-workspace: Workspace rename menu");
109
+ ctx.effect(() => ctx.contextMenus.register({
110
+ id: "workspace.header.delete",
111
+ target: "workspace",
112
+ order: 300,
113
+ resolve: ({ workspaceId, displayTitle }) => [{
114
+ id: "delete",
115
+ label: t("delete.workspace"),
116
+ icon: { key: "workspace.delete" },
117
+ danger: true,
118
+ separatorBefore: true,
119
+ run: () => {
120
+ actions.requestWorkspaceAction(workspaceId, displayTitle, "delete");
121
+ }
122
+ }]
123
+ }), "ui-workspace: Workspace delete menu");
124
+ }
125
+ //#endregion
12
126
  //#region lib/types/client/shortcuts.js
13
127
  /**
14
128
  * Create the private browser request source shared by commands and controls.
@@ -977,7 +1091,7 @@ window.__ModuleLoader__.load({
977
1091
  }
978
1092
  //#endregion
979
1093
  //#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
980
- const css$2 = ".RrwzeW_projectRow,.RrwzeW_sessionRow{padding:0 8px;cursor:pointer;user-select:none;color:var(--qilin-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.RrwzeW_projectRow:hover,.RrwzeW_sessionRow:hover,.RrwzeW_sessionRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--qilin-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.RrwzeW_searchResultRow:hover,.RrwzeW_searchResultRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultHeading{align-items:center;min-width:0;display:flex}.RrwzeW_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.RrwzeW_searchResultWorkspace,.RrwzeW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.RrwzeW_searchResultWorkspace{max-width:40%;color:var(--qilin-alias-label-tertiary);flex:none}.RrwzeW_searchResultSnippet{min-width:0;color:var(--qilin-alias-label-secondary);flex:1}.RrwzeW_projectRow{box-sizing:border-box;align-items:center;height:34px}.RrwzeW_projectRow .RrwzeW_rowActions{height:20px}.RrwzeW_sessionRow{height:32px;animation:RrwzeW_row-in .15s var(--ds-ease-in-out);gap:0}.RrwzeW_sessionRow .RrwzeW_title{margin:0 6px 0 4px}@keyframes RrwzeW_row-in{0%{opacity:0}}.RrwzeW_slot{width:16px;height:20px;color:var(--qilin-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.RrwzeW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.RrwzeW_folderActive{color:var(--qilin-alias-state-business-primary)}.RrwzeW_projectRow .RrwzeW_chevron{display:none}.RrwzeW_projectRow:hover .RrwzeW_chevron{display:inline-flex}.RrwzeW_projectRow:hover .RrwzeW_folder{display:none}.RrwzeW_arrow{transition:transform .15s var(--ds-ease-in-out)}.RrwzeW_arrowOpen{transform:rotate(90deg)}.RrwzeW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.RrwzeW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_renameInput{border:.5px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.RrwzeW_sessionRow .RrwzeW_title{flex:1}.RrwzeW_sessionRow .RrwzeW_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}.RrwzeW_sessionRow .RrwzeW_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}.RrwzeW_sessionRow .RrwzeW_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}@media (hover:hover){.RrwzeW_sessionRow:hover .RrwzeW_title,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_title{text-overflow:clip}}.RrwzeW_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--qilin-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.RrwzeW_time{color:var(--qilin-alias-label-tertiary);flex:none;font-size:12px;line-height:20px}.RrwzeW_dot{flex:none}.RrwzeW_pinIndicator{width:16px;height:20px;color:var(--qilin-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_pinIndicator,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_pinIndicator{display:none}.RrwzeW_rowActions{flex:none;align-items:center;gap:15px;display:none}.RrwzeW_projectRow:hover .RrwzeW_rowActions,.RrwzeW_sessionRow:hover .RrwzeW_rowActions,.RrwzeW_projectRow.RrwzeW_menuOpen .RrwzeW_rowActions,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_rowActions{display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_time,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_time{display:none}.RrwzeW_projectRow.RrwzeW_menuOpen,.RrwzeW_sessionRow.RrwzeW_menuOpen{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_sessionRow.RrwzeW_dropBefore,.RrwzeW_sessionRow.RrwzeW_dropAfter{position:relative}.RrwzeW_sessionRow.RrwzeW_dropBefore:before,.RrwzeW_sessionRow.RrwzeW_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--qilin-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}.RrwzeW_sessionRow.RrwzeW_dropBefore:before{top:-7px}.RrwzeW_sessionRow.RrwzeW_dropAfter:after{bottom:-7px}.RrwzeW_hoverContent{flex-direction:column;gap:8px;display:flex}.RrwzeW_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.RrwzeW_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.RrwzeW_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.RrwzeW_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.RrwzeW_iconButton{cursor:pointer;width:16px;height:16px;color:var(--qilin-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.RrwzeW_iconButton:hover{color:var(--qilin-alias-label-primary)}.RrwzeW_chevron{color:var(--qilin-alias-label-caption)}@media (prefers-reduced-motion:reduce){.RrwzeW_sessionRow,.RrwzeW_arrow{transition:none;animation:none}}";
1094
+ const css$2 = ".RrwzeW_projectRow,.RrwzeW_sessionRow{align-items:center;gap:var(--qilin-sidebar-label-gap);border-inline:var(--qilin-sidebar-paint-inset) solid transparent;box-sizing:border-box;padding:0 8px;cursor:pointer;user-select:none;color:var(--qilin-alias-label-primary);background-clip:padding-box;border-radius:8px;padding-inline-start:calc(var(--qilin-sidebar-row-padding) - var(--qilin-sidebar-paint-inset) + var(--dsh-workspace-indent,0px));display:flex}@media (hover:hover){.RrwzeW_projectRow:hover,.RrwzeW_sessionRow:hover{background-color:var(--qilin-alias-interactive-bg-hover)}}.RrwzeW_sessionRow.RrwzeW_selected{background-color:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultRow{box-sizing:border-box;width:100%;min-height:48px;padding:4px calc(var(--qilin-sidebar-row-padding) - var(--qilin-sidebar-paint-inset));border:none;border-inline:var(--qilin-sidebar-paint-inset) solid transparent;cursor:pointer;text-align:left;color:var(--qilin-alias-label-primary);background:padding-box padding-box;border-radius:8px;flex-direction:column;align-items:stretch;display:flex}@media (hover:hover){.RrwzeW_searchResultRow:hover{background-color:var(--qilin-alias-interactive-bg-hover)}}.RrwzeW_searchResultRow.RrwzeW_selected{background-color:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultHeading{align-items:center;min-width:0;display:flex}.RrwzeW_searchResultTitle{min-width:0;margin:0 var(--qilin-sidebar-label-gap);text-overflow:ellipsis;white-space:nowrap;flex:0 auto;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_searchResultMeta{min-width:0;margin-left:calc(16px + var(--qilin-sidebar-label-gap));align-items:center;gap:6px;display:flex}.RrwzeW_searchResultWorkspace,.RrwzeW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.RrwzeW_searchResultWorkspace{max-width:40%;color:var(--qilin-alias-label-tertiary);flex:none}.RrwzeW_searchResultSnippet{min-width:0;color:var(--qilin-alias-label-secondary);flex:1}.RrwzeW_projectRow{box-sizing:border-box;align-items:center;height:34px}.RrwzeW_projectRow .RrwzeW_rowActions{height:20px}.RrwzeW_sessionRow{gap:0;height:32px}.RrwzeW_sessionRow .RrwzeW_title{margin:0 6px 0 var(--qilin-sidebar-label-gap)}.RrwzeW_slot{width:16px;height:20px;color:var(--qilin-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.RrwzeW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.RrwzeW_folderActive{color:var(--qilin-alias-state-business-primary)}.RrwzeW_projectRow .RrwzeW_chevron{display:none}@media (hover:hover){.RrwzeW_projectRow:hover .RrwzeW_chevron{display:inline-flex}.RrwzeW_projectRow:hover .RrwzeW_folder{display:none}}.RrwzeW_arrow{transition:transform .15s var(--ds-ease-in-out)}.RrwzeW_arrowOpen{transform:rotate(90deg)}.RrwzeW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.RrwzeW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_renameInput{border:1px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.RrwzeW_sessionRow .RrwzeW_title{flex:1}.RrwzeW_sessionRow .RrwzeW_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}.RrwzeW_sessionRow .RrwzeW_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}.RrwzeW_sessionRow .RrwzeW_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}@media (hover:hover){.RrwzeW_sessionRow:hover .RrwzeW_title,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_title{text-overflow:clip}}.RrwzeW_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--qilin-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.RrwzeW_time{color:var(--qilin-alias-label-tertiary);flex:none;margin-inline-end:-1px;font-size:12px;line-height:20px}.RrwzeW_dot{flex:none}.RrwzeW_pinIndicator{width:16px;height:20px;color:var(--qilin-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_pinIndicator{display:none}@media (hover:hover){.RrwzeW_sessionRow:hover .RrwzeW_pinIndicator{display:none}}@media (hover:none){.RrwzeW_sessionRow .RrwzeW_pinIndicator{display:none}}.RrwzeW_rowActions{flex:none;align-items:center;gap:13px;display:none}.RrwzeW_projectRow.RrwzeW_menuOpen .RrwzeW_rowActions,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_rowActions,.RrwzeW_projectRow:focus-within .RrwzeW_rowActions,.RrwzeW_sessionRow:focus-within .RrwzeW_rowActions{display:inline-flex}.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_time{display:none}@media (hover:hover){.RrwzeW_projectRow:hover .RrwzeW_rowActions,.RrwzeW_sessionRow:hover .RrwzeW_rowActions{display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_time{display:none}}@media (hover:none){.RrwzeW_projectRow .RrwzeW_rowActions,.RrwzeW_sessionRow .RrwzeW_rowActions{display:inline-flex}.RrwzeW_projectRow[aria-expanded=false] .RrwzeW_rowActions,.RrwzeW_sessionRow .RrwzeW_time,.RrwzeW_secondaryAction{display:none}}.RrwzeW_projectRow.RrwzeW_menuOpen,.RrwzeW_sessionRow.RrwzeW_menuOpen{background-color:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_sessionRow.RrwzeW_dropBefore,.RrwzeW_sessionRow.RrwzeW_dropAfter{position:relative}.RrwzeW_sessionRow.RrwzeW_dropBefore:before,.RrwzeW_sessionRow.RrwzeW_dropAfter:after{content:\"\";z-index:1;left:calc(-1 * var(--qilin-sidebar-paint-inset));right:calc(4px - var(--qilin-sidebar-paint-inset));background:linear-gradient(55deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--qilin-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute}.RrwzeW_sessionRow.RrwzeW_dropBefore:before{top:-7px}.RrwzeW_sessionRow.RrwzeW_dropAfter:after{bottom:-7px}.RrwzeW_hoverContent{flex-direction:column;gap:8px;display:flex}.RrwzeW_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.RrwzeW_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.RrwzeW_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.RrwzeW_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.RrwzeW_iconButton{cursor:pointer;width:24px;height:24px;color:var(--qilin-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;margin:-4px;padding:0;display:inline-flex}.RrwzeW_iconButton:focus-visible{outline-offset:-2px}.RrwzeW_iconButton:hover{color:var(--qilin-alias-label-primary)}.RrwzeW_chevron{color:var(--qilin-alias-label-caption)}@media (prefers-reduced-motion:reduce){.RrwzeW_arrow{transition:none;animation:none}}";
981
1095
  const tagId$2 = "@qilin-agent/client-ui-workspace/Rows.module.css";
982
1096
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
983
1097
  const tag = document.createElement("style");
@@ -1007,7 +1121,6 @@ window.__ModuleLoader__.load({
1007
1121
  "projectRow": "RrwzeW_projectRow",
1008
1122
  "projectText": "RrwzeW_projectText",
1009
1123
  "renameInput": "RrwzeW_renameInput",
1010
- "row-in": "RrwzeW_row-in",
1011
1124
  "rowActions": "RrwzeW_rowActions",
1012
1125
  "searchResultHeading": "RrwzeW_searchResultHeading",
1013
1126
  "searchResultMeta": "RrwzeW_searchResultMeta",
@@ -1015,6 +1128,7 @@ window.__ModuleLoader__.load({
1015
1128
  "searchResultSnippet": "RrwzeW_searchResultSnippet",
1016
1129
  "searchResultTitle": "RrwzeW_searchResultTitle",
1017
1130
  "searchResultWorkspace": "RrwzeW_searchResultWorkspace",
1131
+ "secondaryAction": "RrwzeW_secondaryAction",
1018
1132
  "selected": "RrwzeW_selected",
1019
1133
  "sessionRow": "RrwzeW_sessionRow",
1020
1134
  "slot": "RrwzeW_slot",
@@ -1029,9 +1143,10 @@ window.__ModuleLoader__.load({
1029
1143
  * all data and callbacks arrive via props. Hover swaps (folder->chevron,
1030
1144
  * time->ellipsis, action buttons) are CSS-only, and a session row's clipped
1031
1145
  * title marquees programmatically while the row is hovered, fading both
1032
- * clipping edges from the depth of its travel. Row ... menus are
1033
- * visual-only except workspace Rename/Delete and session Rename/Fork/Archive; the
1034
- * session and workspace hover cards are suppressed while a menu is open.
1146
+ * clipping edges from the depth of its travel. Row ... menus cover workspace
1147
+ * Rename/Delete and session Rename/Fork/Archive; a right-click on the row
1148
+ * captures the same object for the shared context menu. The session and
1149
+ * workspace hover cards are suppressed while a menu is open.
1035
1150
  */
1036
1151
  /** Row display title: blank rows show the localized New Session label. */
1037
1152
  function displayTitle(node, t) {
@@ -1039,10 +1154,6 @@ window.__ModuleLoader__.load({
1039
1154
  }
1040
1155
  const MIN_TITLE_REVEAL_PX = 8;
1041
1156
  const TITLE_MARQUEE_PX_PER_MS = .03;
1042
- /** Whether the platform asked for reduced motion; jsdom implements no matchMedia. */
1043
- function prefersReducedMotion() {
1044
- return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
1045
- }
1046
1157
  /**
1047
1158
  * Place the title's scroll position and publish the stylesheet's fade-mask
1048
1159
  * hooks: `data-scrolled` while the title has left its start (left fade) and
@@ -1094,13 +1205,14 @@ window.__ModuleLoader__.load({
1094
1205
  cancelAnimationFrame(frame.current);
1095
1206
  }, []);
1096
1207
  return (0, react.useMemo)(() => ({
1097
- enter: () => {
1208
+ enter: (event) => {
1209
+ if (event.pointerType === "touch") return;
1098
1210
  /* v8 ignore next -- defensive: the title span renders unconditionally. */
1099
1211
  if (title.current === null) return;
1100
1212
  const element = title.current;
1101
1213
  const range = element.scrollWidth - element.clientWidth;
1102
1214
  if (range <= MIN_TITLE_REVEAL_PX) return;
1103
- if (prefersReducedMotion()) {
1215
+ if ((0, _qilin_agent_client_ui_primitives.prefersReducedMotion)()) {
1104
1216
  placeTitle(element, range, range);
1105
1217
  return;
1106
1218
  }
@@ -1186,7 +1298,7 @@ window.__ModuleLoader__.load({
1186
1298
  * @param props.t - the browser root's locale seat.
1187
1299
  * @returns the row element.
1188
1300
  */
1189
- function ProjectRowItem({ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag, home, t }) {
1301
+ function ProjectRowItem({ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag, home, menu, t }) {
1190
1302
  const row = group;
1191
1303
  const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
1192
1304
  const active = containsCurrentDescendant || group.expanded && group.containsCurrent;
@@ -1201,11 +1313,22 @@ window.__ModuleLoader__.load({
1201
1313
  icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconTrashOutlineRegular, {}),
1202
1314
  danger: true
1203
1315
  }];
1316
+ const menuProps = row.workspaceId === void 0 ? void 0 : menu?.(row.workspaceId, row.cwd, label);
1204
1317
  const ownRow = (0, react_jsx_runtime.jsxs)("div", {
1205
1318
  className: clsx(Rows_module_css_default.projectRow, menuOpen && Rows_module_css_default.menuOpen),
1206
1319
  role: "treeitem",
1320
+ "data-row-key": `workspace:${row.key}`,
1207
1321
  "aria-expanded": row.expanded,
1322
+ tabIndex: 0,
1323
+ ...menuProps,
1208
1324
  onClick: onToggle,
1325
+ onKeyDown: (event) => {
1326
+ menuProps?.onKeyDown?.(event);
1327
+ if (event.defaultPrevented) return;
1328
+ if (event.target !== event.currentTarget || !["Enter", " "].includes(event.key)) return;
1329
+ event.preventDefault();
1330
+ onToggle();
1331
+ },
1209
1332
  draggable: drag !== void 0,
1210
1333
  onDragStart: drag === void 0 ? void 0 : (e) => {
1211
1334
  e.dataTransfer.effectAllowed = "move";
@@ -1264,7 +1387,7 @@ window.__ModuleLoader__.load({
1264
1387
  e.stopPropagation();
1265
1388
  onCreate();
1266
1389
  },
1267
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPlusOutlineRegular, {})
1390
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPlusOutlineRegular, { size: 14 })
1268
1391
  })]
1269
1392
  })
1270
1393
  ]
@@ -1445,8 +1568,9 @@ window.__ModuleLoader__.load({
1445
1568
  * @param props.t - the browser root's locale seat.
1446
1569
  * @returns the session row.
1447
1570
  */
1448
- function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onCopyId, onPin, onUnpin, onReveal, drag, renderSlot, t }) {
1571
+ function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onCopyId, onPin, onUnpin, onReveal, drag, renderSlot, menu, t }) {
1449
1572
  const row = node;
1573
+ const menuProps = row.blank ? void 0 : menu?.(node.id, row.title);
1450
1574
  const title = displayTitle(node, t);
1451
1575
  const selected = node.id === currentId;
1452
1576
  const statuses = sessionStatuses(node, t);
@@ -1493,7 +1617,9 @@ window.__ModuleLoader__.load({
1493
1617
  ref: rowRef,
1494
1618
  className: clsx(Rows_module_css_default.sessionRow, selected && Rows_module_css_default.selected, menuOpen && Rows_module_css_default.menuOpen, drag?.marker === "before" && Rows_module_css_default.dropBefore, drag?.marker === "after" && Rows_module_css_default.dropAfter),
1495
1619
  role: "treeitem",
1620
+ "data-row-key": `session:${node.id}`,
1496
1621
  "aria-selected": selected,
1622
+ ...menuProps,
1497
1623
  onClick: () => {
1498
1624
  onOpen(node.id);
1499
1625
  },
@@ -1569,7 +1695,7 @@ window.__ModuleLoader__.load({
1569
1695
  portal: true,
1570
1696
  children: (0, react_jsx_runtime.jsx)("button", {
1571
1697
  type: "button",
1572
- className: Rows_module_css_default.iconButton,
1698
+ className: clsx(Rows_module_css_default.iconButton, Rows_module_css_default.secondaryAction),
1573
1699
  "aria-label": t("menu.archiveSession"),
1574
1700
  onClick: (e) => {
1575
1701
  e.stopPropagation();
@@ -1782,7 +1908,7 @@ window.__ModuleLoader__.load({
1782
1908
  }
1783
1909
  //#endregion
1784
1910
  //#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css.mjs
1785
- const css = ".CO5s3G_root{--qilin-session-list-edge-inset:var(--qilin-sidebar-inline-padding);--qilin-session-list-scrollbar-width:8px;--qilin-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--qilin-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.CO5s3G_root.CO5s3G_rail{padding-right:0}.CO5s3G_iconButton{corner-shape:round;cursor:pointer;width:28px;height:28px;color:var(--qilin-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.CO5s3G_iconButton:focus-visible,.CO5s3G_searchButton:focus-visible,.CO5s3G_clearButton:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary));outline-offset:-2px}.CO5s3G_iconButton:hover{background:var(--qilin-alias-interactive-bg-hover)}.CO5s3G_sectionHeader{box-sizing:border-box;height:36px;color:var(--qilin-alias-label-tertiary);border-radius:12px;flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;display:flex;overflow:hidden}.CO5s3G_root:not(.CO5s3G_rail) .CO5s3G_sectionHeader{margin-top:2px;margin-right:-4px}.CO5s3G_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;overflow:hidden}.CO5s3G_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.CO5s3G_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.CO5s3G_searchSlotExpanded{max-width:100%;padding-left:0}.CO5s3G_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.CO5s3G_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.CO5s3G_search{box-sizing:border-box;corner-shape:round;cursor:text;width:100%;height:28px;color:var(--qilin-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;border-radius:50%;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.CO5s3G_searchExpanded{border:.5px solid var(--qilin-alias-border-l4);width:calc(100% + 4px);height:30px;color:var(--qilin-alias-label-caption);background:0 0;border-radius:10px;margin-inline:-2px;padding:0 4px 0 0}.CO5s3G_searchButton{corner-shape:round;cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.CO5s3G_searchExpanded .CO5s3G_searchButton{width:28px;height:30px}.CO5s3G_searchButton:hover{background:var(--qilin-alias-interactive-bg-hover)}.CO5s3G_searchExpanded .CO5s3G_searchButton:hover{background:0 0}.CO5s3G_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--qilin-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.CO5s3G_searchExpanded .CO5s3G_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.CO5s3G_searchInput::placeholder{color:var(--qilin-alias-label-tertiary)}.CO5s3G_clearButton{corner-shape:round;cursor:pointer;width:24px;height:24px;color:var(--qilin-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.CO5s3G_clearButton:hover{background:var(--qilin-alias-interactive-bg-hover)}.CO5s3G_rail .CO5s3G_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.CO5s3G_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--qilin-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.CO5s3G_rail .CO5s3G_listArea{margin-left:0;margin-right:0;padding-left:0}.CO5s3G_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.CO5s3G_fade{left:0;right:var(--qilin-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--qilin-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}[data-platform=darwin] .CO5s3G_fade{display:none}.CO5s3G_wide{animation:CO5s3G_wide-in .2s var(--ds-ease-in-out)}@keyframes CO5s3G_wide-in{0%{opacity:0}}.CO5s3G_list{min-height:0;margin-left:-4px;margin-right:var(--qilin-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--qilin-session-list-edge-inset) - var(--qilin-session-list-scrollbar-width) - var(--qilin-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.CO5s3G_flatList>*+*,.CO5s3G_searchTree>[role=treeitem]+[role=treeitem],.CO5s3G_groupSection>*+*{margin-top:2px}.CO5s3G_searchStatus,.CO5s3G_searchWarning{color:var(--qilin-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.CO5s3G_searchWarning{color:var(--qilin-alias-label-secondary)}.CO5s3G_groupSection{position:relative}.CO5s3G_groupSection+.CO5s3G_groupSection{margin-top:4px}.CO5s3G_listTopDropIndicator,.CO5s3G_workspaceDropBefore:before,.CO5s3G_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--qilin-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.CO5s3G_listTopDropIndicator{top:-8px;left:0;right:var(--qilin-session-list-edge-inset)}.CO5s3G_listTopDropActive>.CO5s3G_workspaceDropBefore:first-child:before{display:none}.CO5s3G_workspaceDropBefore:before{top:-8px}.CO5s3G_workspaceDropAfter:after{bottom:-8px}.CO5s3G_sessionOverflowButton{width:100%;height:28px;padding:0 12px 0 calc(28px + var(--qilin-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--qilin-alias-label-tertiary);background:0 0;border:none;border-radius:8px;font-size:12px}.CO5s3G_groupSection>.CO5s3G_sessionOverflowButton{margin-top:0}.CO5s3G_sessionOverflowButton:hover{color:var(--qilin-alias-label-secondary);background:0 0}.CO5s3G_empty{color:var(--qilin-alias-label-tertiary);padding:16px 12px;font-size:13px}.CO5s3G_renameInput{box-sizing:border-box;border:.5px solid var(--qilin-alias-border-l4);width:100%;height:44px;color:var(--qilin-alias-label-primary);background:0 0;border-radius:22px;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.CO5s3G_renameInput:disabled{color:var(--qilin-alias-label-dimmed)}.CO5s3G_renameError{color:var(--qilin-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.CO5s3G_deleteAction:not(:disabled){color:var(--qilin-alias-state-error-primary)}.CO5s3G_deleteStatus{color:var(--qilin-alias-label-secondary);font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.CO5s3G_wide{animation:none}.CO5s3G_search,.CO5s3G_sectionLabel,.CO5s3G_searchSlot,.CO5s3G_searchInput,.CO5s3G_headerActions{transition:none}}";
1911
+ const css = ".CO5s3G_root{--qilin-session-list-edge-inset:var(--qilin-sidebar-inline-padding);--qilin-session-list-scrollbar-width:8px;--qilin-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--qilin-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.CO5s3G_root.CO5s3G_rail{padding-right:0}.CO5s3G_iconButton{corner-shape:round;cursor:pointer;width:28px;height:28px;color:var(--qilin-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.CO5s3G_iconButton:focus-visible,.CO5s3G_searchButton:focus-visible,.CO5s3G_clearButton:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary));outline-offset:-2px}.CO5s3G_iconButton:hover{background:var(--qilin-alias-interactive-bg-hover)}.CO5s3G_sectionHeader{height:36px;padding-left:var(--qilin-sidebar-row-padding);box-sizing:border-box;color:var(--qilin-alias-label-tertiary);border-radius:12px;flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;display:flex;overflow:hidden}.CO5s3G_root:not(.CO5s3G_rail) .CO5s3G_sectionHeader{transition:margin-right .18s var(--ds-ease-in-out), padding-inline .18s var(--ds-ease-in-out), gap .18s var(--ds-ease-in-out);margin-top:2px;margin-right:-4px}.CO5s3G_root:not(.CO5s3G_rail) .CO5s3G_sectionHeader:has(.CO5s3G_searchExpanded){padding-inline:calc(var(--qilin-sidebar-paint-inset) + 1px);gap:0;margin-right:0}.CO5s3G_sectionHeader:has(.CO5s3G_searchExpanded) .CO5s3G_sectionLabelHidden{margin-right:0}.CO5s3G_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;position:relative;left:-1px;overflow:hidden}.CO5s3G_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.CO5s3G_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.CO5s3G_searchSlotExpanded{max-width:100%;padding-left:0}.CO5s3G_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.CO5s3G_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.CO5s3G_search{box-sizing:border-box;corner-shape:round;cursor:text;width:100%;height:28px;color:var(--qilin-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;border-radius:50%;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.CO5s3G_searchExpanded{border:1px solid var(--qilin-alias-border-l4);width:100%;height:32px;color:var(--qilin-alias-label-caption);background:0 0;border-radius:10px;margin-inline:0;padding:0 4px 0 0}.CO5s3G_searchButton{corner-shape:round;cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.CO5s3G_searchExpanded .CO5s3G_searchButton{width:28px;height:32px}.CO5s3G_searchButton:hover{background:var(--qilin-alias-interactive-bg-hover)}.CO5s3G_searchExpanded .CO5s3G_searchButton:hover{background:0 0}.CO5s3G_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--qilin-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.CO5s3G_searchExpanded .CO5s3G_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.CO5s3G_searchInput::placeholder{color:var(--qilin-alias-label-tertiary)}.CO5s3G_clearButton{corner-shape:round;cursor:pointer;width:24px;height:24px;color:var(--qilin-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.CO5s3G_clearButton:hover{background:var(--qilin-alias-interactive-bg-hover)}.CO5s3G_rail .CO5s3G_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.CO5s3G_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--qilin-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.CO5s3G_rail .CO5s3G_listArea{margin-left:0;margin-right:0;padding-left:0}.CO5s3G_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.CO5s3G_fade{left:0;right:var(--qilin-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--qilin-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}[data-platform=darwin] .CO5s3G_fade{display:none}.CO5s3G_wide{animation:CO5s3G_wide-in .2s var(--ds-ease-in-out)}@keyframes CO5s3G_wide-in{0%{opacity:0}}.CO5s3G_list{min-height:0;margin-left:-4px;margin-right:var(--qilin-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--qilin-session-list-edge-inset) - var(--qilin-session-list-scrollbar-width) - var(--qilin-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.CO5s3G_flatList>*+*,.CO5s3G_searchTree>[role=treeitem]+[role=treeitem],.CO5s3G_groupSection>*+*{margin-top:2px}.CO5s3G_searchStatus,.CO5s3G_searchWarning{color:var(--qilin-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.CO5s3G_searchWarning{color:var(--qilin-alias-label-secondary)}.CO5s3G_groupSection{position:relative}.CO5s3G_groupSection+.CO5s3G_groupSection{margin-top:4px}.CO5s3G_listTopDropIndicator,.CO5s3G_workspaceDropBefore:before,.CO5s3G_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--qilin-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.CO5s3G_listTopDropIndicator{top:-8px;left:0;right:var(--qilin-session-list-edge-inset)}.CO5s3G_listTopDropActive>.CO5s3G_workspaceDropBefore:first-child:before{display:none}.CO5s3G_workspaceDropBefore:before{top:-8px}.CO5s3G_workspaceDropAfter:after{bottom:-8px}.CO5s3G_sessionOverflowButton{width:100%;height:28px;padding:0 var(--qilin-sidebar-row-padding) 0 calc(var(--qilin-sidebar-row-padding) + 16px + var(--qilin-sidebar-label-gap) + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--qilin-alias-label-tertiary);background:0 0;border:none;border-radius:8px;font-size:12px}.CO5s3G_groupSection>.CO5s3G_sessionOverflowButton{margin-top:0}.CO5s3G_sessionOverflowButton:hover{color:var(--qilin-alias-label-secondary);background:0 0}.CO5s3G_empty{color:var(--qilin-alias-label-tertiary);padding:16px 12px;font-size:13px}.CO5s3G_renameInput{box-sizing:border-box;border:1px solid var(--qilin-alias-border-l4);width:100%;height:44px;color:var(--qilin-alias-label-primary);background:0 0;border-radius:22px;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.CO5s3G_renameInput:disabled{color:var(--qilin-alias-label-dimmed)}.CO5s3G_renameError{color:var(--qilin-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.CO5s3G_deleteAction:not(:disabled){color:var(--qilin-alias-state-error-primary)}.CO5s3G_deleteStatus{color:var(--qilin-alias-label-secondary);font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.CO5s3G_wide{animation:none}.CO5s3G_root:not(.CO5s3G_rail) .CO5s3G_sectionHeader,.CO5s3G_search,.CO5s3G_sectionLabel,.CO5s3G_searchSlot,.CO5s3G_searchInput,.CO5s3G_headerActions{transition:none}}";
1786
1912
  const tagId = "@qilin-agent/client-ui-workspace/WorkspaceBrowser.module.css";
1787
1913
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1788
1914
  const tag = document.createElement("style");
@@ -2001,17 +2127,19 @@ window.__ModuleLoader__.load({
2001
2127
  return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
2002
2128
  }
2003
2129
  /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
2004
- function SessionTree({ list, useSessionStatus, startSession, open, forkSession, copySessionId, workspaces, ungroupedSessionIds, archivedSessionIds, pinnedSessionIds, onSessionPin, onSessionUnpin, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, renderSlot, revealSessionId, onSessionRevealed }) {
2130
+ function SessionTree({ list, useSessionStatus, startSession, open, forkSession, copySessionId, workspaces, ungroupedSessionIds, archivedSessionIds, pinnedSessionIds, onSessionPin, onSessionUnpin, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, renderSlot, menu, revealSessionId, onSessionRevealed, animationResetKey }) {
2005
2131
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
2006
2132
  const statuses = useSessionStatus((s) => s);
2007
2133
  const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
2008
2134
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
2009
2135
  const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
2136
+ const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
2010
2137
  const [drag, setDrag] = (0, react.useState)(null);
2011
2138
  const sessionDropCommitted = (0, react.useRef)(false);
2012
2139
  const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
2013
2140
  const workspaceDropCommitted = (0, react.useRef)(false);
2014
- useNativeDragAcceptance(drag !== null || workspaceDrag !== null);
2141
+ const nativeDragActive = drag !== null || workspaceDrag !== null;
2142
+ useNativeDragAcceptance(nativeDragActive);
2015
2143
  const currentGroup = current === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, current);
2016
2144
  (0, react.useEffect)(() => {
2017
2145
  if (current === void 0 || currentGroup === void 0 || Object.hasOwn(groupExpansion, currentGroup)) return;
@@ -2159,6 +2287,7 @@ window.__ModuleLoader__.load({
2159
2287
  }, [groups, parents]);
2160
2288
  const rootGroups = childrenByParent.get(void 0) ?? [];
2161
2289
  const workspaceDropAtListStart = rootGroups[0]?.workspaceId !== void 0 && workspaceDrag?.over?.id === rootGroups[0].workspaceId && workspaceDrag.over.half === "before";
2290
+ const rowKeys = groups.length === 0 ? ["empty"] : [];
2162
2291
  const renderGroup = (group, depth) => {
2163
2292
  const workspaceId = group.workspaceId;
2164
2293
  const children = childrenByParent.get(group.key) ?? [];
@@ -2167,6 +2296,10 @@ window.__ModuleLoader__.load({
2167
2296
  const collapsed = watermarkRows(group.sessions, baselines.get(group.key) ?? SESSION_WATERMARK_BASE);
2168
2297
  const visible = watermarkRows(group.sessions, watermark);
2169
2298
  const sessionsExpanded = visible.hiddenCount === 0;
2299
+ rowKeys.push(`workspace:${group.key}`);
2300
+ const childRows = group.expanded ? children.map((child) => renderGroup(child, depth + 1)) : [];
2301
+ for (const node of visible.rows) rowKeys.push(`session:${node.id}`);
2302
+ if (collapsed.hiddenCount > 0) rowKeys.push(`overflow:${group.key}`);
2170
2303
  const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
2171
2304
  const workspaceDragProps = workspaceId === void 0 ? void 0 : {
2172
2305
  start: () => {
@@ -2241,6 +2374,7 @@ window.__ModuleLoader__.load({
2241
2374
  }
2242
2375
  },
2243
2376
  drag: workspaceDragProps,
2377
+ menu: menu.workspace,
2244
2378
  actions: group.workspaceId === void 0 ? void 0 : {
2245
2379
  rename: () => {
2246
2380
  /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
@@ -2254,7 +2388,7 @@ window.__ModuleLoader__.load({
2254
2388
  }),
2255
2389
  group.expanded && children.length > 0 && (0, react_jsx_runtime.jsx)("div", {
2256
2390
  role: "group",
2257
- children: children.map((child) => renderGroup(child, depth + 1))
2391
+ children: childRows
2258
2392
  }),
2259
2393
  visible.rows.map((node) => {
2260
2394
  const sameGroupDrag = drag !== null && drag.accountKey === group.key;
@@ -2312,12 +2446,14 @@ window.__ModuleLoader__.load({
2312
2446
  } : void 0,
2313
2447
  drag: dragProps,
2314
2448
  renderSlot,
2449
+ menu: menu.session,
2315
2450
  t
2316
2451
  }, node.id);
2317
2452
  }),
2318
2453
  collapsed.hiddenCount > 0 && (0, react_jsx_runtime.jsx)("button", {
2319
2454
  type: "button",
2320
2455
  className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
2456
+ "data-row-key": `overflow:${group.key}`,
2321
2457
  "aria-expanded": sessionsExpanded,
2322
2458
  onClick: () => {
2323
2459
  if (sessionsExpanded) {
@@ -2328,12 +2464,14 @@ window.__ModuleLoader__.load({
2328
2464
  ...marks,
2329
2465
  [group.key]: visible.hiddenCount <= SESSION_WATERMARK_BASE ? Infinity : watermark + SESSION_WATERMARK_BASE
2330
2466
  }));
2467
+ setWatermarkMotionKey((key) => key + 1);
2331
2468
  },
2332
2469
  children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
2333
2470
  })
2334
2471
  ]
2335
2472
  }, group.key);
2336
2473
  };
2474
+ const groupRows = rootGroups.map((group) => renderGroup(group, 0));
2337
2475
  return (0, react_jsx_runtime.jsxs)("div", {
2338
2476
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
2339
2477
  children: [
@@ -2341,21 +2479,25 @@ window.__ModuleLoader__.load({
2341
2479
  className: WorkspaceBrowser_module_css_default.listTopDropIndicator,
2342
2480
  "aria-hidden": "true"
2343
2481
  }),
2344
- (0, react_jsx_runtime.jsxs)("div", {
2482
+ (0, react_jsx_runtime.jsxs)(_qilin_agent_client_ui_primitives.AnimatedRows, {
2345
2483
  className: clsx(WorkspaceBrowser_module_css_default.list, workspaceDropAtListStart && WorkspaceBrowser_module_css_default.listTopDropActive),
2346
2484
  role: "tree",
2347
- "aria-label": t("section.sessions"),
2485
+ label: t("section.sessions"),
2486
+ rowKeys,
2487
+ ready: list.phase === "ready" && workspaceReady && !nativeDragActive,
2488
+ resetKey: JSON.stringify([animationResetKey, watermarkMotionKey]),
2348
2489
  children: [groups.length === 0 && (0, react_jsx_runtime.jsx)("div", {
2349
2490
  className: WorkspaceBrowser_module_css_default.empty,
2491
+ "data-row-key": "empty",
2350
2492
  children: t("empty.none")
2351
- }), rootGroups.map((group) => renderGroup(group, 0))]
2493
+ }), groupRows]
2352
2494
  }),
2353
2495
  (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })
2354
2496
  ]
2355
2497
  });
2356
2498
  }
2357
2499
  /** The flat "In one list" body: every session is one draggable top-level row. */
2358
- function FlatList({ list, sessionIds, pinnedSessionIds, useSessionStatus, open, forkSession, copySessionId, onSessionRename, onSessionArchive, onSessionPin, onSessionUnpin, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t, renderSlot }) {
2500
+ function FlatList({ list, sessionIds, pinnedSessionIds, useSessionStatus, open, forkSession, copySessionId, onSessionRename, onSessionArchive, onSessionPin, onSessionUnpin, usePanelInfo, setSessionOrder, workspaceReady, animationResetKey, revealSessionId, onSessionRevealed, t, renderSlot, menu }) {
2359
2501
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
2360
2502
  const statuses = useSessionStatus((s) => s);
2361
2503
  const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, pinnedSessionIds, statuses), [
@@ -2388,12 +2530,16 @@ window.__ModuleLoader__.load({
2388
2530
  const now = Date.now();
2389
2531
  return (0, react_jsx_runtime.jsxs)("div", {
2390
2532
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
2391
- children: [(0, react_jsx_runtime.jsxs)("div", {
2533
+ children: [(0, react_jsx_runtime.jsxs)(_qilin_agent_client_ui_primitives.AnimatedRows, {
2392
2534
  className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
2393
2535
  role: "tree",
2394
- "aria-label": t("section.sessions"),
2536
+ label: t("section.sessions"),
2537
+ rowKeys: rows.length === 0 ? ["empty"] : rows.map((row) => `session:${row.id}`),
2538
+ ready: list.phase === "ready" && workspaceReady && drag === null,
2539
+ resetKey: animationResetKey,
2395
2540
  children: [rows.length === 0 && (0, react_jsx_runtime.jsx)("div", {
2396
2541
  className: WorkspaceBrowser_module_css_default.empty,
2542
+ "data-row-key": "empty",
2397
2543
  children: t("empty.none")
2398
2544
  }), rows.map((node) => {
2399
2545
  const active = drag !== null;
@@ -2447,6 +2593,7 @@ window.__ModuleLoader__.load({
2447
2593
  dropCommitted.current = false;
2448
2594
  }
2449
2595
  },
2596
+ menu: menu.session,
2450
2597
  t
2451
2598
  }, node.id);
2452
2599
  })]
@@ -2519,7 +2666,7 @@ window.__ModuleLoader__.load({
2519
2666
  * @param props - composed slot props (shell owner share + store + injected actions).
2520
2667
  * @returns the region element tree.
2521
2668
  */
2522
- function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, copySessionId, pinSession, unpinSession, createWorkspace, searchSessions, searchResultLimit, requestSearch, requestAddWorkspace, closeAddWorkspace, closeRenameRequest, setDirectoryBusy, useDirectoryFlow, useHostInfo, useWorkspaceShortcuts, useShortcuts, renderSlot, t }) {
2669
+ function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, copySessionId, pinSession, unpinSession, createWorkspace, searchSessions, searchResultLimit, requestSearch, requestAddWorkspace, closeAddWorkspace, closeRenameRequest, setDirectoryBusy, useDirectoryFlow, useHostInfo, useWorkspaceShortcuts, useShortcuts, useContextMenu, useSessionRenameRequest, useWorkspaceActionRequest, settleSessionRename, settleWorkspaceAction, renderSlot, t }) {
2523
2670
  const home = useHostInfo((info) => info.home);
2524
2671
  const list = useSessions((state) => state);
2525
2672
  const workspaces = useWorkspaces((state) => state.items);
@@ -2882,6 +3029,54 @@ window.__ModuleLoader__.load({
2882
3029
  setDeleteError(reason instanceof Error ? reason.message : String(reason));
2883
3030
  });
2884
3031
  };
3032
+ const { getTriggerProps } = useContextMenu();
3033
+ const menus = {
3034
+ session: (sessionId, displayTitle) => getTriggerProps({
3035
+ kind: "session",
3036
+ value: {
3037
+ sessionId,
3038
+ displayTitle
3039
+ }
3040
+ }),
3041
+ workspace: (workspaceId, path, displayTitle) => getTriggerProps({
3042
+ kind: "workspace",
3043
+ value: {
3044
+ workspaceId,
3045
+ path: path ?? "",
3046
+ displayTitle
3047
+ }
3048
+ })
3049
+ };
3050
+ const pendingSessionRename = useSessionRenameRequest((request) => request);
3051
+ (0, react.useEffect)(() => {
3052
+ if (pendingSessionRename === null) return;
3053
+ setSessionRenameTarget({
3054
+ sessionId: pendingSessionRename.sessionId,
3055
+ currentTitle: pendingSessionRename.currentTitle
3056
+ });
3057
+ setSessionRenameDraft(pendingSessionRename.currentTitle);
3058
+ setSessionRenameError(null);
3059
+ settleSessionRename();
3060
+ }, [pendingSessionRename, settleSessionRename]);
3061
+ const pendingWorkspaceAction = useWorkspaceActionRequest((request) => request);
3062
+ (0, react.useEffect)(() => {
3063
+ if (pendingWorkspaceAction === null) return;
3064
+ if (pendingWorkspaceAction.action === "rename") {
3065
+ setRenameTarget({
3066
+ workspaceId: pendingWorkspaceAction.workspaceId,
3067
+ currentTitle: pendingWorkspaceAction.currentTitle
3068
+ });
3069
+ setRenameDraft(pendingWorkspaceAction.currentTitle);
3070
+ setRenameError(null);
3071
+ } else {
3072
+ setDeleteTarget({
3073
+ workspaceId: pendingWorkspaceAction.workspaceId,
3074
+ title: pendingWorkspaceAction.currentTitle
3075
+ });
3076
+ setDeleteError(null);
3077
+ }
3078
+ settleWorkspaceAction();
3079
+ }, [pendingWorkspaceAction, settleWorkspaceAction]);
2885
3080
  return (0, react_jsx_runtime.jsxs)("div", {
2886
3081
  className: clsx(WorkspaceBrowser_module_css_default.root, !wide && WorkspaceBrowser_module_css_default.rail),
2887
3082
  children: [
@@ -3022,6 +3217,8 @@ window.__ModuleLoader__.load({
3022
3217
  usePanelInfo,
3023
3218
  list,
3024
3219
  sessionIds: orderedFlatSessionIds,
3220
+ workspaceReady,
3221
+ animationResetKey: `${groupBy}/${orderBy}`,
3025
3222
  useSessionStatus,
3026
3223
  open,
3027
3224
  forkSession,
@@ -3035,13 +3232,16 @@ window.__ModuleLoader__.load({
3035
3232
  revealSessionId,
3036
3233
  onSessionRevealed: acknowledgeSessionReveal,
3037
3234
  renderSlot,
3235
+ menu: menus,
3038
3236
  t
3039
3237
  }) : (0, react_jsx_runtime.jsx)(SessionTree, {
3040
3238
  usePanelInfo,
3041
3239
  list,
3240
+ animationResetKey: `${groupBy}/${orderBy}`,
3042
3241
  useSessionStatus,
3043
3242
  copySessionId,
3044
3243
  renderSlot,
3244
+ menu: menus,
3045
3245
  onSessionRename,
3046
3246
  onSessionArchive,
3047
3247
  onSessionPin,
@@ -3482,6 +3682,7 @@ window.__ModuleLoader__.load({
3482
3682
  * declaration through `slots.inject()` instead of assuming order.
3483
3683
  */
3484
3684
  const inject = [
3685
+ "contextMenus",
3485
3686
  "slots",
3486
3687
  "sessions",
3487
3688
  "workspaces",
@@ -3507,6 +3708,8 @@ window.__ModuleLoader__.load({
3507
3708
  create: () => viewInstance
3508
3709
  };
3509
3710
  const rowToast = (0, _qilin_agent_client_store.createSnapshotStore)(null);
3711
+ const sessionRenameRequest = (0, _qilin_agent_client_store.createSnapshotStore)(null);
3712
+ const workspaceActionRequest = (0, _qilin_agent_client_store.createSnapshotStore)(null);
3510
3713
  let toastSeq = 0;
3511
3714
  const notify = (toast) => {
3512
3715
  rowToast.set({
@@ -3576,16 +3779,69 @@ window.__ModuleLoader__.load({
3576
3779
  copySessionId: async (sessionId) => {
3577
3780
  notify({ kind: await (0, _qilin_agent_client_ui_primitives.writeClipboard)(sessionId) ? "sessionIdCopied" : "sessionIdCopyFailed" });
3578
3781
  },
3782
+ settleSessionRename: () => {
3783
+ sessionRenameRequest.set(null);
3784
+ },
3785
+ settleWorkspaceAction: () => {
3786
+ workspaceActionRequest.set(null);
3787
+ },
3579
3788
  hooks: {
3580
3789
  directoryFlow: browserFlowSource,
3581
3790
  hostInfo,
3582
3791
  workspaceShortcuts: shortcutControls.state,
3583
- shortcuts: ctx.shortcuts.catalog
3792
+ shortcuts: ctx.shortcuts.catalog,
3793
+ sessionRenameRequest,
3794
+ workspaceActionRequest
3584
3795
  }
3585
3796
  });
3586
3797
  installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, (sessionId) => {
3587
3798
  uiWorkspace.archiveSession(sessionId);
3588
3799
  });
3800
+ ctx.slots.inject("contextMenu.icon", function* () {
3801
+ for (const [key, Icon] of Object.entries({
3802
+ "workspace.rename": _qilin_agent_client_ui_primitives.IconEditOutlineRegular,
3803
+ "workspace.delete": _qilin_agent_client_ui_primitives.IconTrashOutlineRegular,
3804
+ "workspace.pin": _qilin_agent_client_ui_primitives.IconPinOutlineRegular,
3805
+ "workspace.unpin": _qilin_agent_client_ui_primitives.IconPinFillRegular,
3806
+ "workspace.archive": _qilin_agent_client_ui_primitives.IconArchiveOutlineRegular,
3807
+ "workspace.fork": _qilin_agent_client_ui_primitives.IconBranchOutlineRegular,
3808
+ "workspace.copy": _qilin_agent_client_ui_primitives.IconCopyOutlineRegular
3809
+ })) yield ctx.slots.register({
3810
+ name: "contextMenu.icon",
3811
+ key
3812
+ }, Icon);
3813
+ });
3814
+ registerWorkspaceMenus(ctx, {
3815
+ pin: (sessionId) => {
3816
+ uiWorkspace.pinSession(sessionId);
3817
+ },
3818
+ unpin: (sessionId) => {
3819
+ uiWorkspace.unpinSession(sessionId);
3820
+ },
3821
+ requestSessionRename: (sessionId, currentTitle) => {
3822
+ sessionRenameRequest.set({
3823
+ sessionId,
3824
+ currentTitle
3825
+ });
3826
+ },
3827
+ fork: (sessionId) => {
3828
+ browserInjected().forkSession(sessionId);
3829
+ },
3830
+ copySessionId: (sessionId) => {
3831
+ browserInjected().copySessionId(sessionId);
3832
+ },
3833
+ archive: (sessionId) => {
3834
+ browserInjected().archiveSession(sessionId);
3835
+ },
3836
+ requestWorkspaceAction: (workspaceId, displayTitle, action) => {
3837
+ if (workspaces.list.getSnapshot().items.find((row) => row.workspaceId === workspaceId) === void 0) return;
3838
+ workspaceActionRequest.set({
3839
+ action,
3840
+ workspaceId,
3841
+ currentTitle: displayTitle
3842
+ });
3843
+ }
3844
+ });
3589
3845
  registerToasts(ctx);
3590
3846
  const rowToastInjected = () => ({
3591
3847
  hooks: { toast: rowToast },
@@ -0,0 +1,28 @@
1
+ /** Structured Session and Workspace actions over the workspace owner's existing callbacks. */
2
+ import type { Context } from '@qilin-agent/kylin';
3
+ import type { WorkspaceId } from '@qilin-agent/api-workspace-controller/client';
4
+ import type { SessionId } from '@qilin-agent/session/types';
5
+ /** The callbacks the browser already runs for its own row menus. */
6
+ export interface WorkspaceMenuActions {
7
+ /** Pin a Session ahead of unpinned rows. */
8
+ pin: (sessionId: SessionId) => void;
9
+ /** Drop one Session's pin. */
10
+ unpin: (sessionId: SessionId) => void;
11
+ /** Open the browser-owned Session rename dialog. */
12
+ requestSessionRename: (sessionId: SessionId, currentTitle: string) => void;
13
+ /** Fork a Session at its last completed turn. */
14
+ fork: (sessionId: SessionId) => void;
15
+ /** Copy a Session's complete id and report the outcome. */
16
+ copySessionId: (sessionId: SessionId) => void;
17
+ /** Archive a Session into the registry-global set. */
18
+ archive: (sessionId: SessionId) => void;
19
+ /** Open the browser-owned Workspace rename or delete dialog. */
20
+ requestWorkspaceAction: (workspaceId: WorkspaceId, currentTitle: string, action: 'rename' | 'delete') => void;
21
+ }
22
+ /**
23
+ * Register the built-in Session and Workspace actions on the shared menu.
24
+ * @param ctx - workspace plugin context owning menus, membership, and locale.
25
+ * @param actions - shared row callbacks; execution always uses the captured object.
26
+ */
27
+ export declare function registerWorkspaceMenus(ctx: Context, actions: WorkspaceMenuActions): void;
28
+ //# sourceMappingURL=context-menus.d.ts.map
@@ -30,6 +30,27 @@ import type { SessionId } from '@qilin-agent/session/types';
30
30
  import type { createWorkspaceViewStore } from '../stores.ts';
31
31
  import type { ShortcutCatalogEntry } from '@qilin-agent/client-shortcuts/client';
32
32
  import type { WorkspaceShortcutState } from '../shortcuts.ts';
33
+ declare module '@qilin-agent/client-ui-context-menu/client' {
34
+ interface ContextMenuTargetMap {
35
+ /** Registered Workspace header; path and title belong to the clicked row. */
36
+ workspace: {
37
+ readonly workspaceId: WorkspaceId;
38
+ readonly path: string;
39
+ readonly displayTitle: string;
40
+ };
41
+ }
42
+ }
43
+ /** One menu request for the browser-owned Session rename dialog. */
44
+ export type SessionRenameRequest = {
45
+ readonly sessionId: SessionId;
46
+ readonly currentTitle: string;
47
+ };
48
+ /** One menu request for the browser-owned Workspace rename or delete dialog. */
49
+ export type WorkspaceActionRequest = {
50
+ readonly action: 'rename' | 'delete';
51
+ readonly workspaceId: WorkspaceId;
52
+ readonly currentTitle: string;
53
+ };
33
54
  /**
34
55
  * Owner share of the directory-flow holes: the complete conversation between
35
56
  * the trigger surface and the picking interaction. The occupant reads `open`
@@ -130,6 +151,10 @@ export type WorkspaceBrowserInjected = {
130
151
  workspaceShortcuts: HostObservable<WorkspaceShortcutState>;
131
152
  /** Effective command catalog for row shortcut hints. */
132
153
  shortcuts: HostObservable<readonly ShortcutCatalogEntry[]>;
154
+ /** Session rename requested by the shared context menu. */
155
+ sessionRenameRequest: HostObservable<SessionRenameRequest | null>;
156
+ /** Workspace rename or delete requested by the shared context menu. */
157
+ workspaceActionRequest: HostObservable<WorkspaceActionRequest | null>;
133
158
  };
134
159
  /** Open the browser search and focus its input. */
135
160
  requestSearch: () => void;
@@ -192,6 +217,10 @@ export type WorkspaceBrowserInjected = {
192
217
  }) => Promise<WorkspaceView>;
193
218
  /** Copy the complete Session id and report acceptance or failure through the keyed notice. */
194
219
  copySessionId: (sessionId: SessionId) => Promise<void>;
220
+ /** Consume a menu-originated Session rename request once its dialog is open. */
221
+ settleSessionRename: () => void;
222
+ /** Consume a menu-originated Workspace action request once its dialog is open. */
223
+ settleWorkspaceAction: () => void;
195
224
  };
196
225
  /** One transient Workspace notice rendered by the overlay toast entry. */
197
226
  export type RowToast = {
@@ -1,8 +1,20 @@
1
+ import type { ContextMenuTriggerProps } from '@qilin-agent/client-ui-context-menu/client';
1
2
  import type { PropsRenderSlots } from '@qilin-agent/client-ui-slots';
3
+ import type { WorkspaceId } from '@qilin-agent/api-workspace-controller/client';
2
4
  import type { WorkspaceBrowserProps } from '../contract/slots.ts';
3
5
  import type { GroupNode, SearchResultNode, SessionNode } from '../tree.ts';
4
6
  /** The standard locale seat, prop-passed from the browser root. */
5
7
  type RowTranslate = WorkspaceBrowserProps['t'];
8
+ /**
9
+ * Shared-menu gesture bindings the browser resolves for one row. The rows own
10
+ * no menu state: the menu plugin captures the target and renders the actions.
11
+ */
12
+ export interface RowMenus {
13
+ /** Bind one Session row's captured identity. */
14
+ session: (sessionId: SessionNode['id'], displayTitle: string) => ContextMenuTriggerProps;
15
+ /** Bind one Workspace header's captured identity and directory. */
16
+ workspace: (workspaceId: WorkspaceId, path: string | undefined, displayTitle: string) => ContextMenuTriggerProps;
17
+ }
6
18
  /**
7
19
  * Row drag wiring supplied by the tree owner. `drop` reports the half of the
8
20
  * row where the pointer released so the owner can resolve an insert anchor.
@@ -38,7 +50,7 @@ interface WorkspaceRowDragProps {
38
50
  * @param props.t - the browser root's locale seat.
39
51
  * @returns the row element.
40
52
  */
41
- export declare function ProjectRowItem({ group, containsCurrentDescendant, onToggle, onCreate, actions, drag, home, t }: {
53
+ export declare function ProjectRowItem({ group, containsCurrentDescendant, onToggle, onCreate, actions, drag, home, menu, t }: {
42
54
  group: GroupNode;
43
55
  containsCurrentDescendant?: boolean;
44
56
  onToggle: () => void;
@@ -48,6 +60,8 @@ export declare function ProjectRowItem({ group, containsCurrentDescendant, onTog
48
60
  rename: () => void;
49
61
  delete: () => void;
50
62
  } | undefined;
63
+ /** Shared-menu gesture bindings for a real Workspace header. */
64
+ menu?: RowMenus['workspace'] | undefined;
51
65
  /** Present only for real Workspace rows in the grouped view. */
52
66
  drag?: WorkspaceRowDragProps | undefined;
53
67
  /** Host account home; POSIX home-rooted hover paths display as `~`. */
@@ -91,7 +105,7 @@ export declare function SearchResultItem({ result, currentId, onOpen, t }: {
91
105
  * @param props.t - the browser root's locale seat.
92
106
  * @returns the session row.
93
107
  */
94
- export declare function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onCopyId, onPin, onUnpin, onReveal, drag, renderSlot, t, }: {
108
+ export declare function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onCopyId, onPin, onUnpin, onReveal, drag, renderSlot, menu, t, }: {
95
109
  node: SessionNode;
96
110
  currentId: string | undefined;
97
111
  now: number;
@@ -114,6 +128,8 @@ export declare function SessionNodeItem({ node, currentId, now, onOpen, onRename
114
128
  drag?: RowDragProps | undefined;
115
129
  /** Child-seat renderer for the row's leading decoration and hover-card section. */
116
130
  renderSlot: SessionRowRenderSlots;
131
+ /** Shared-menu gesture bindings; blank rows capture nothing. */
132
+ menu?: RowMenus['session'] | undefined;
117
133
  t: RowTranslate;
118
134
  }): import("react").JSX.Element;
119
135
  export {};
@@ -4,5 +4,5 @@ import type { WorkspaceBrowserProps } from '../contract/slots.ts';
4
4
  * @param props - composed slot props (shell owner share + store + injected actions).
5
5
  * @returns the region element tree.
6
6
  */
7
- export declare function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, copySessionId, pinSession, unpinSession, createWorkspace, searchSessions, searchResultLimit, requestSearch, requestAddWorkspace, closeAddWorkspace, closeRenameRequest, setDirectoryBusy, useDirectoryFlow, useHostInfo, useWorkspaceShortcuts, useShortcuts, renderSlot, t, }: WorkspaceBrowserProps): import("react").JSX.Element;
7
+ export declare function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, copySessionId, pinSession, unpinSession, createWorkspace, searchSessions, searchResultLimit, requestSearch, requestAddWorkspace, closeAddWorkspace, closeRenameRequest, setDirectoryBusy, useDirectoryFlow, useHostInfo, useWorkspaceShortcuts, useShortcuts, useContextMenu, useSessionRenameRequest, useWorkspaceActionRequest, settleSessionRename, settleWorkspaceAction, renderSlot, t, }: WorkspaceBrowserProps): import("react").JSX.Element;
8
8
  //# sourceMappingURL=WorkspaceBrowser.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qilin-agent/client-ui-workspace",
3
3
  "description": "Workspace picker plugin: one WorkspacePicker registered into the sidebar and empty-state workspace slots",
4
- "version": "3.1.7",
4
+ "version": "3.1.8",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -37,6 +37,7 @@
37
37
  "@qilin-agent/client-connection",
38
38
  "@qilin-agent/client-locale",
39
39
  "@qilin-agent/client-shortcuts",
40
+ "@qilin-agent/client-ui-context-menu",
40
41
  "@qilin-agent/client-ui-conversation",
41
42
  "@qilin-agent/client-ui-layout",
42
43
  "@qilin-agent/client-ui-renderer",
@@ -48,33 +49,34 @@
48
49
  },
49
50
  "license": "MIT",
50
51
  "peerDependencies": {
51
- "@qilin-agent/kylin": "~4.0.5"
52
+ "@qilin-agent/kylin": "~4.0.6-alpha.2"
52
53
  },
53
54
  "devDependencies": {
55
+ "@qilin-agent/api-remotes": "3.1.8",
56
+ "@qilin-agent/api-session-controller": "3.1.8",
57
+ "@qilin-agent/api-workspace-controller": "3.1.8",
58
+ "@qilin-agent/client-connection": "3.1.8",
59
+ "@qilin-agent/client-locale": "3.1.8",
60
+ "@qilin-agent/client-shortcuts": "3.1.8",
61
+ "@qilin-agent/client-store": "3.1.8",
62
+ "@qilin-agent/client-test-runtime": "3.1.8",
63
+ "@qilin-agent/client-ui-context-menu": "3.1.8",
64
+ "@qilin-agent/client-ui-conversation": "3.1.8",
65
+ "@qilin-agent/client-ui-layout": "3.1.8",
66
+ "@qilin-agent/client-ui-primitives": "3.1.8",
67
+ "@qilin-agent/client-ui-renderer": "3.1.8",
68
+ "@qilin-agent/client-ui-session": "3.1.8",
69
+ "@qilin-agent/client-ui-sidebar": "3.1.8",
70
+ "@qilin-agent/client-ui-slots": "3.1.8",
71
+ "@qilin-agent/kylin": "~4.0.6-alpha.2",
72
+ "@qilin-agent/schedule": "3.1.8",
73
+ "@qilin-agent/session": "3.1.8",
74
+ "@qilin-agent/subagent": "3.1.8",
75
+ "@qilin-agent/typert-protocol": "3.1.8",
76
+ "@qilin-agent/util-workspace-path": "3.1.8",
54
77
  "@types/react": "~18.3.1",
55
78
  "clsx": "^2.0.0",
56
- "react": "^18.2.0",
57
- "@qilin-agent/api-remotes": "3.1.7",
58
- "@qilin-agent/api-session-controller": "3.1.7",
59
- "@qilin-agent/api-workspace-controller": "3.1.7",
60
- "@qilin-agent/client-connection": "3.1.7",
61
- "@qilin-agent/client-shortcuts": "3.1.7",
62
- "@qilin-agent/client-locale": "3.1.7",
63
- "@qilin-agent/client-test-runtime": "3.1.7",
64
- "@qilin-agent/client-store": "3.1.7",
65
- "@qilin-agent/client-ui-conversation": "3.1.7",
66
- "@qilin-agent/client-ui-layout": "3.1.7",
67
- "@qilin-agent/client-ui-primitives": "3.1.7",
68
- "@qilin-agent/client-ui-renderer": "3.1.7",
69
- "@qilin-agent/client-ui-session": "3.1.7",
70
- "@qilin-agent/client-ui-sidebar": "3.1.7",
71
- "@qilin-agent/client-ui-slots": "3.1.7",
72
- "@qilin-agent/schedule": "3.1.7",
73
- "@qilin-agent/session": "3.1.7",
74
- "@qilin-agent/subagent": "3.1.7",
75
- "@qilin-agent/typert-protocol": "3.1.7",
76
- "@qilin-agent/util-workspace-path": "3.1.7",
77
- "@qilin-agent/kylin": "~4.0.5"
79
+ "react": "^18.2.0"
78
80
  },
79
81
  "files": [
80
82
  "lib/index.js",