@deepseek-ai/dsh-client-ui-workspace 0.1.5-rc.2 → 0.1.6-alpha.1
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 +2 -2
- package/README.md +5 -5
- package/README.zh.md +9 -9
- package/lib/client.js +223 -262
- package/lib/types/client/contract/slots.d.ts +0 -6
- package/lib/types/client/navigation.d.ts +6 -0
- package/lib/types/client/rows/Rows.d.ts +2 -2
- package/lib/types/client/rows/WorkspaceBrowser.d.ts +1 -1
- package/lib/types/client/stores.d.ts +5 -7
- package/lib/types/client/tree.d.ts +36 -10
- package/package.json +20 -20
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-workspace/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 1edf9223c72e7094ce305734119975c3954cce59
|
|
6
|
+
README.zh.md: 122f1d97a5a4dd7c5b54b0d9a27e323cdc6fb8ca
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Use the sidebar to browse Workspaces and their Sessions, reorder them, and start
|
|
|
29
29
|
|
|
30
30
|
### Reordering and view options
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
**Last updated** orders ordinary Sessions by their latest user prompt or steer time, newest first, in both grouped and flat views. **Manual** freezes the current displayed order and holds positions when activity changes; newly discovered ordinary Sessions append to the end, newest first when several arrive together. Returning to Last updated discards every manual position, and entering Manual again freezes the then-current recency order. The browser defaults to Last updated and remembers the selected mode across reloads. Dragging an ordinary Session applies the move locally and selects Manual. The selected blank **New Session** is always pinned first and cannot be dragged; after its first prompt it becomes an ordinary draggable row, retaining its first position in Manual or following its current timestamp in Last updated. In a collapsed group, drag boundaries follow rendered rows and place the source before intervening hidden rows, so a drag cannot hide its source. Session display orders for real Workspaces, Ungrouped, and the flat list are browser-local; Workspace group drag order remains Host-durable.
|
|
33
33
|
|
|
34
34
|
### Search
|
|
35
35
|
|
|
@@ -67,7 +67,7 @@ Each registration declares a **directory-flow child hole** (`single` kind: `conv
|
|
|
67
67
|
|
|
68
68
|
### View state
|
|
69
69
|
|
|
70
|
-
Once the Workspace list baseline is ready, browser-persisted expansion and Session-order records retain only current Workspace ids plus Ungrouped and the flat-list account.
|
|
70
|
+
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 `@deepseek-ai/dsh-schedule/client` dependency and does not import the Schedule runtime or `ui-schedule`.
|
|
71
71
|
|
|
72
72
|
### Hover cards
|
|
73
73
|
|
|
@@ -85,7 +85,7 @@ These pages cover the sidebar host, the hero surface, and the picking backends.
|
|
|
85
85
|
- [ui-sidebar](../ui-sidebar/README.md) — the sidebar shell hosting the `sidebar.workspaces` hole.
|
|
86
86
|
- [ui-conversation](../ui-conversation/README.md) — the chat surface hosting the Session Intent hero's picker hole.
|
|
87
87
|
- [directory-picker-native](../../host/directory-picker-native/README.md) — the OS-chooser backend filling the directory-flow hole.
|
|
88
|
-
- [Workspace Controller](../../api/workspace-controller/README.md) — the Host mutations and framework-neutral Client projection that own
|
|
88
|
+
- [Workspace Controller](../../api/workspace-controller/README.md) — the Host mutations and framework-neutral Client projection that own Workspaces, membership, and Workspace group order.
|
|
89
89
|
|
|
90
90
|
-----
|
|
91
91
|
|
|
@@ -106,7 +106,7 @@ None; this package neither assembles nor sends a provider request.
|
|
|
106
106
|
These limits define the search depth, the archive surface, and the picking carrier; they are current package constraints.
|
|
107
107
|
|
|
108
108
|
- **No fuzzy content search or event deep links** — the content backend uses literal token/phrase matching, and selecting a result opens the Session rather than the matching event.
|
|
109
|
-
- **No Session deletion
|
|
109
|
+
- **No Session deletion, and unarchive lives in Settings** — sessions can be archived but never deleted; the archived-sessions Settings page ([ui-settings-unarchive-sessions](../ui-settings-unarchive-sessions/README.md)) owns viewing and restoring them, and Workspace registration deletion does not delete Sessions.
|
|
110
110
|
- **Pending user interaction is not aggregated into collapsed groups** — a waiting row inside a collapsed group lights no group-header indicator and becomes visible only after that group is expanded.
|
|
111
111
|
- **Native folder selection depends on the local Host carrier** — under the `-native` composition, in-process or remote browser deployments cannot open a local operating-system dialog; remote-capable picking is the `-browse` composition's in-app flow.
|
|
112
112
|
|
|
@@ -120,4 +120,4 @@ None.
|
|
|
120
120
|
|
|
121
121
|
</details>
|
|
122
122
|
|
|
123
|
-
**Runtime invariant:** No companion is published.
|
|
123
|
+
**Runtime invariant:** No companion is published. This is a pure-consumer plugin that registers presentational components into two host-declared slots and registers its locale dictionaries; its inject face consists of stateless RPC wrappers plus a create-and-open call. It emits no Cordis events and owns no cross-plugin mutable state.
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "dsh Web 客户端的共享 Workspace
|
|
2
|
+
description: "dsh Web 客户端的共享 Workspace 浏览器与选择器插件:分组或扁平的会话行、添加、重命名、重排序、搜索、fork、归档,以及目录流选取子 slot。"
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -29,7 +29,7 @@ kind: "package-reference"
|
|
|
29
29
|
|
|
30
30
|
### 重排序与视图选项
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
**最近更新**在分组和单列表视图中都按普通 Session 的最近一次用户提示词或 steering(中途引导)时间降序排列。**手动排序**会冻结当前显示顺序,并在活动变化时保持已有位置;新发现的普通 Session 追加到末尾,多条同时到达时按最近更新时间降序排列。返回最近更新会丢弃所有手动位置;再次进入手动排序时,则冻结当时的最近更新顺序。浏览器默认采用最近更新,并在重新加载后记住所选模式。拖拽普通 Session 会在浏览器本地应用移动并选中手动排序。当前选中的空白**新会话**始终固定在首位且无法拖拽;首条提示词落地后,它成为可拖拽的普通行,在手动排序中保留首位,在最近更新中按当前时间戳排列。折叠分组的拖拽边界按渲染行确定,并把来源行放在中间隐藏行之前,因此拖拽不会隐藏来源行。真实 Workspace、Ungrouped 与单列表的 Session 显示顺序都保留在浏览器本地;Workspace 分组的拖拽顺序仍由 Host 持久化。
|
|
33
33
|
|
|
34
34
|
### 搜索
|
|
35
35
|
|
|
@@ -45,13 +45,13 @@ Session 行渲染运行时的实时 `pendingInteraction` 分类:审批显示**
|
|
|
45
45
|
|
|
46
46
|
### 活动 Schedule 标识
|
|
47
47
|
|
|
48
|
-
分组与平铺 Session 行以及搜索结果会在 `SessionSummary.projectionValues.schedule` 为非空数组时显示一枚轮廓闹钟。标识位于标题之后;普通行的更新时间仍位于标识之后,搜索结果则没有更新时间。它不是按钮,没有独立 pointer 行为或 Tab stop,点击所在区域仍会打开整行。本地化 tooltip
|
|
48
|
+
分组与平铺 Session 行以及搜索结果会在 `SessionSummary.projectionValues.schedule` 为非空数组时显示一枚轮廓闹钟。标识位于标题之后;普通行的更新时间仍位于标识之后,搜索结果则没有更新时间。它不是按钮,没有独立 pointer 行为或 Tab stop,点击所在区域仍会打开整行。本地化 tooltip 与文本相同的读屏标签均为**有活动定时任务**。
|
|
49
49
|
|
|
50
|
-
对于 cold Session,该值有意采用尽力而为语义。身份匹配且可用的 projection-cache 行可以在不打开 Session 的情况下预热闹钟;cache 缺失或陈旧可能造成短暂漏显或残留。标识只表示当前列表值包含尚未 dispatch 或 delete 的 Schedule 记录,不表示 Schedule
|
|
50
|
+
对于 cold Session,该值有意采用尽力而为语义。身份匹配且可用的 projection-cache 行可以在不打开 Session 的情况下预热闹钟;cache 缺失或陈旧可能造成短暂漏显或残留。标识只表示当前列表值包含尚未 dispatch 或 delete 的 Schedule 记录,不表示 Schedule 运行时当前 live 或能够唤醒该 Session。
|
|
51
51
|
|
|
52
52
|
-----
|
|
53
53
|
|
|
54
|
-
`ctx.uiWorkspace.openSession(id)`
|
|
54
|
+
`ctx.uiWorkspace.openSession(id)` 会选中会话,并让主区域返回会话界面;这两项构成一次 UI 导航操作,即使目标会话已经是当前会话也同样执行。`openWorkspace(id, beforeOpen?)` 和 `forkSession(id)` 仅在请求未被后续导航替代时打开结果;新会话使用 `openWorkspace`。可选的同步准备回调仅对仍有效的工作区请求执行,因此过期请求不会搬移 composer 草稿。后续导航或所有者释放会阻止晚到的 UI 提交,但不取消底层会话创建。选中失败时保留当前全局面板。会话行读取 `usePanelInfo`,在全局面板活跃时不显示会话选中样式;仅把焦点移到搜索框或目录选择器不会离开该面板。
|
|
55
55
|
|
|
56
56
|
<a id="understand-the-implementation"></a>
|
|
57
57
|
## 理解实现
|
|
@@ -67,7 +67,7 @@ Session 行渲染运行时的实时 `pendingInteraction` 分类:审批显示**
|
|
|
67
67
|
|
|
68
68
|
### 视图状态
|
|
69
69
|
|
|
70
|
-
Workspace
|
|
70
|
+
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;本包只使用纯类型依赖 `@deepseek-ai/dsh-schedule/client`,不会导入 Schedule 运行时或 `ui-schedule`。
|
|
71
71
|
|
|
72
72
|
### 悬浮卡片
|
|
73
73
|
|
|
@@ -85,7 +85,7 @@ Workspace 与 Session 悬浮卡片会复制对应行被截断的值:激活 Wor
|
|
|
85
85
|
- [ui-sidebar](../ui-sidebar/README.zh.md)——承载 `sidebar.workspaces` 子 slot 的侧边栏外壳。
|
|
86
86
|
- [ui-conversation](../ui-conversation/README.zh.md)——承载 Session Intent 主视觉区选择器子 slot 的聊天界面。
|
|
87
87
|
- [directory-picker-native](../../host/directory-picker-native/README.zh.md)——填充目录流子 slot 的 OS 选择器后端。
|
|
88
|
-
- [Workspace Controller](../../api/workspace-controller/README.zh.md)——负责 Workspace
|
|
88
|
+
- [Workspace Controller](../../api/workspace-controller/README.zh.md)——负责 Workspace、成员关系与 Workspace 分组顺序的 Host 变更和框架无关 Client 投影。
|
|
89
89
|
|
|
90
90
|
-----
|
|
91
91
|
|
|
@@ -106,7 +106,7 @@ Workspace 与 Session 悬浮卡片会复制对应行被截断的值:激活 Wor
|
|
|
106
106
|
这些限制定义搜索深度、归档界面与选取载体;它们是当前包约束。
|
|
107
107
|
|
|
108
108
|
- **没有模糊内容搜索或事件深链接**:内容后端采用字面 token/短语匹配,选择结果会打开 Session,而不是匹配的事件。
|
|
109
|
-
- **没有 Session
|
|
109
|
+
- **没有 Session 删除,取消归档位于设置中**:会话可以归档但绝不会被删除;已归档会话的查看与恢复由「已归档会话」设置页([ui-settings-unarchive-sessions](../ui-settings-unarchive-sessions/README.zh.md))负责,删除 Workspace 注册记录不会删除 Session。
|
|
110
110
|
- **待处理的用户交互不会聚合到折叠的分组上**:折叠分组内正在等待的行不会点亮分组头指示,只有展开该分组后才可见。
|
|
111
111
|
- **原生文件夹选择依赖本地 Host 载体**:在 `-native` 组合下,进程内部署或远程浏览器部署无法打开本地操作系统对话框;可远程的选取是 `-browse` 组合的应用内流程。
|
|
112
112
|
|
|
@@ -120,4 +120,4 @@ Workspace 与 Session 悬浮卡片会复制对应行被截断的值:激活 Wor
|
|
|
120
120
|
|
|
121
121
|
</details>
|
|
122
122
|
|
|
123
|
-
**运行时不变式:**
|
|
123
|
+
**运行时不变式:** 不发布伴生入口。这是一个纯消费方插件,只向两个由宿主声明的 slot 注册展示组件,并注册自身的 locale dictionaries;inject face 由无状态 RPC 包装层和一次 create-and-open 调用组成;本插件不发出 Cordis 事件,也不持有跨插件可变状态。
|
package/lib/client.js
CHANGED
|
@@ -97,6 +97,9 @@ window.__ModuleLoader__.load({
|
|
|
97
97
|
async archiveSession(sessionId) {
|
|
98
98
|
await this.workspaces.archiveSession(sessionId);
|
|
99
99
|
}
|
|
100
|
+
async unarchiveSession(sessionId) {
|
|
101
|
+
await this.workspaces.unarchiveSession(sessionId);
|
|
102
|
+
}
|
|
100
103
|
async pickDirectory() {
|
|
101
104
|
const result = await this.directoryPicker.pick();
|
|
102
105
|
if (!result.ok) throw new Error(`directory picker failed: ${result.error.message}`);
|
|
@@ -187,6 +190,10 @@ window.__ModuleLoader__.load({
|
|
|
187
190
|
*/
|
|
188
191
|
/** Browser-local order account for the hierarchy-free flat Session list. */
|
|
189
192
|
const FLAT_SESSION_ORDER_KEY = "__flat_session_order__";
|
|
193
|
+
/** Copy read-only projections into the persisted mutable store representation. */
|
|
194
|
+
function copySessionOrders(orders) {
|
|
195
|
+
return Object.fromEntries(Object.entries(orders).map(([key, order]) => [key, [...order]]));
|
|
196
|
+
}
|
|
190
197
|
/**
|
|
191
198
|
* Create the workspace browser viewing store handle.
|
|
192
199
|
* @returns the store handle (spec + type + identity + factory in one).
|
|
@@ -197,15 +204,16 @@ window.__ModuleLoader__.load({
|
|
|
197
204
|
groupBy: "workspace",
|
|
198
205
|
orderBy: "updated",
|
|
199
206
|
groupExpansion: {},
|
|
200
|
-
sessionOrderByAccount: {}
|
|
201
|
-
sessionUpdatedAtByAccount: {}
|
|
207
|
+
sessionOrderByAccount: {}
|
|
202
208
|
}),
|
|
203
209
|
persist: "dsh.workspace.view.v5",
|
|
204
210
|
actions: {
|
|
205
211
|
setGroupBy: (d, mode) => {
|
|
206
212
|
d.groupBy = mode;
|
|
207
213
|
},
|
|
208
|
-
setOrderBy: (d, mode) => {
|
|
214
|
+
setOrderBy: (d, mode, initialOrders) => {
|
|
215
|
+
if (mode === d.orderBy) return;
|
|
216
|
+
d.sessionOrderByAccount = mode === "manual" ? copySessionOrders(initialOrders) : {};
|
|
209
217
|
d.orderBy = mode;
|
|
210
218
|
},
|
|
211
219
|
setGroupExpanded: (d, key, expanded) => {
|
|
@@ -215,14 +223,16 @@ window.__ModuleLoader__.load({
|
|
|
215
223
|
const retained = new Set(workspaceKeys);
|
|
216
224
|
d.groupExpansion = Object.fromEntries(Object.entries(d.groupExpansion).filter(([key]) => retained.has(key)));
|
|
217
225
|
d.sessionOrderByAccount = Object.fromEntries(Object.entries(d.sessionOrderByAccount).filter(([key]) => retained.has(key)));
|
|
218
|
-
|
|
226
|
+
delete d.sessionUpdatedAtByAccount;
|
|
219
227
|
},
|
|
220
|
-
|
|
221
|
-
d.
|
|
222
|
-
d.
|
|
228
|
+
syncSessionOrders: (d, orders) => {
|
|
229
|
+
if (d.orderBy !== "manual") return;
|
|
230
|
+
Object.assign(d.sessionOrderByAccount, copySessionOrders(orders));
|
|
223
231
|
},
|
|
224
|
-
setSessionOrder: (d, accountKey, order) => {
|
|
225
|
-
d.sessionOrderByAccount
|
|
232
|
+
setSessionOrder: (d, accountKey, order, initialOrders) => {
|
|
233
|
+
if (d.orderBy === "updated") d.sessionOrderByAccount = copySessionOrders(initialOrders);
|
|
234
|
+
d.orderBy = "manual";
|
|
235
|
+
d.sessionOrderByAccount[accountKey] = [...order];
|
|
226
236
|
}
|
|
227
237
|
}
|
|
228
238
|
});
|
|
@@ -324,10 +334,57 @@ window.__ModuleLoader__.load({
|
|
|
324
334
|
const base = workspaceTitleOf(cwd);
|
|
325
335
|
return base !== "" ? base : cwd;
|
|
326
336
|
}
|
|
327
|
-
/**
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Project known account members by current Session recency.
|
|
339
|
+
* @param sessionIds - authoritative account membership.
|
|
340
|
+
* @param summaries - current Session summaries; members without a summary are omitted until it arrives.
|
|
341
|
+
* @returns known members newest first, with Session identity as the deterministic tie-break.
|
|
342
|
+
*/
|
|
343
|
+
function orderByRecency(sessionIds, summaries) {
|
|
344
|
+
return sessionIds.flatMap((id) => {
|
|
345
|
+
const summary = summaries[id];
|
|
346
|
+
return summary === void 0 ? [] : [{
|
|
347
|
+
id,
|
|
348
|
+
updatedAt: summary.updatedAt
|
|
349
|
+
}];
|
|
350
|
+
}).sort((a, b) => {
|
|
351
|
+
if (a.updatedAt !== b.updatedAt) return b.updatedAt - a.updatedAt;
|
|
352
|
+
return a.id < b.id ? -1 : 1;
|
|
353
|
+
}).map((member) => member.id);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Reconcile a browser-local manual order with current account membership.
|
|
357
|
+
* @param memberIds - authoritative account membership.
|
|
358
|
+
* @param savedOrder - previously saved browser-local order.
|
|
359
|
+
* @param summaries - current Session summaries used to append newly known members by recency.
|
|
360
|
+
* @returns retained saved slots followed by newly known members; departed members and unknown new members are omitted.
|
|
361
|
+
*/
|
|
362
|
+
function reconcileManualOrder(memberIds, savedOrder, summaries) {
|
|
363
|
+
const members = new Map(memberIds.map((id) => [id, id]));
|
|
364
|
+
const included = /* @__PURE__ */ new Set();
|
|
365
|
+
const ordered = [];
|
|
366
|
+
for (const key of savedOrder ?? []) {
|
|
367
|
+
const id = members.get(key);
|
|
368
|
+
if (id === void 0 || included.has(key)) continue;
|
|
369
|
+
ordered.push(id);
|
|
370
|
+
included.add(key);
|
|
371
|
+
}
|
|
372
|
+
for (const id of orderByRecency(memberIds, summaries)) {
|
|
373
|
+
if (included.has(id)) continue;
|
|
374
|
+
ordered.push(id);
|
|
375
|
+
included.add(id);
|
|
376
|
+
}
|
|
377
|
+
return ordered;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Keep the selected provisional New Session ahead of either base order.
|
|
381
|
+
* @param order - recency or reconciled manual order.
|
|
382
|
+
* @param currentBlank - selected blank Session in this account, when present.
|
|
383
|
+
* @returns a copy with the selected blank first and no duplicate slot.
|
|
384
|
+
*/
|
|
385
|
+
function pinCurrentBlank(order, currentBlank) {
|
|
386
|
+
if (currentBlank === void 0) return [...order];
|
|
387
|
+
return [currentBlank, ...order.filter((id) => id !== currentBlank)];
|
|
331
388
|
}
|
|
332
389
|
/**
|
|
333
390
|
* Ordinary sessions are visible; among blank sessions, only the current one
|
|
@@ -351,40 +408,30 @@ window.__ModuleLoader__.load({
|
|
|
351
408
|
return (session.projectionValues?.schedule?.length ?? 0) > 0;
|
|
352
409
|
}
|
|
353
410
|
/** Build one group without projecting session lineage into presentation. */
|
|
354
|
-
function buildGroup(key, workspaceId, cwd, createdAt, label, members
|
|
355
|
-
const sessions = [...members];
|
|
356
|
-
if (order === "recency") sessions.sort(byRecency);
|
|
411
|
+
function buildGroup(key, workspaceId, cwd, createdAt, label, members) {
|
|
357
412
|
return {
|
|
358
413
|
key,
|
|
359
414
|
workspaceId,
|
|
360
415
|
cwd,
|
|
361
416
|
createdAt,
|
|
362
417
|
label,
|
|
363
|
-
sessions
|
|
418
|
+
sessions: [...members]
|
|
364
419
|
};
|
|
365
420
|
}
|
|
366
421
|
/** Apply a stored Ungrouped order and append newly loose Sessions by recency. */
|
|
367
|
-
function orderedUngrouped(members, stored) {
|
|
422
|
+
function orderedUngrouped(members, stored, summaries) {
|
|
368
423
|
const byId = new Map(members.map((session) => [session.id, session]));
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
ordered.push(session);
|
|
375
|
-
included.add(key);
|
|
376
|
-
}
|
|
377
|
-
for (const session of [...members].sort(byRecency)) {
|
|
378
|
-
if (included.has(session.id)) continue;
|
|
379
|
-
ordered.push(session);
|
|
380
|
-
}
|
|
381
|
-
return ordered;
|
|
424
|
+
return (stored === void 0 ? orderByRecency(members.map((session) => session.id), summaries) : reconcileManualOrder(members.map((session) => session.id), stored, summaries)).flatMap((id) => {
|
|
425
|
+
const session = byId.get(id);
|
|
426
|
+
/* v8 ignore next -- ids are projected exclusively from the members used to build byId. */
|
|
427
|
+
return session === void 0 ? [] : [session];
|
|
428
|
+
});
|
|
382
429
|
}
|
|
383
430
|
/**
|
|
384
|
-
* Group Sessions by
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
431
|
+
* Group Sessions by Workspace: one group per caller-ordered entity, with
|
|
432
|
+
* members resolved from caller-ordered sessionIds. Sessions outside every
|
|
433
|
+
* Workspace trail in the browser-local Ungrouped order, which falls back to
|
|
434
|
+
* recency before that order is initialized.
|
|
388
435
|
*/
|
|
389
436
|
function groupByWorkspace(list, workspaces, archived, ungroupedOrder) {
|
|
390
437
|
const groups = [];
|
|
@@ -398,10 +445,10 @@ window.__ModuleLoader__.load({
|
|
|
398
445
|
if (!sessionVisible(summary, list.current, archived)) continue;
|
|
399
446
|
members.push(summary);
|
|
400
447
|
}
|
|
401
|
-
groups.push(buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members
|
|
448
|
+
groups.push(buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members));
|
|
402
449
|
}
|
|
403
450
|
const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, list.current, archived));
|
|
404
|
-
if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, "",
|
|
451
|
+
if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, "", orderedUngrouped(stray, ungroupedOrder, list.byId)));
|
|
405
452
|
return groups;
|
|
406
453
|
}
|
|
407
454
|
/** Keep navigation presentation independent from domain-owned interaction objects. */
|
|
@@ -436,7 +483,7 @@ window.__ModuleLoader__.load({
|
|
|
436
483
|
* Content search lives outside this derivation
|
|
437
484
|
* (see {@link deriveSearchResults}).
|
|
438
485
|
* @param list - sessions list snapshot (`current` feeds containsCurrent).
|
|
439
|
-
* @param workspaces - real
|
|
486
|
+
* @param workspaces - real Workspaces in Host group order with caller-projected Session order.
|
|
440
487
|
* @param archivedSessionIds - registry-global archive set.
|
|
441
488
|
* @param pendingInteractions - pending UI interactions by Session.
|
|
442
489
|
* @param view - local expansion arrays.
|
|
@@ -465,26 +512,28 @@ window.__ModuleLoader__.load({
|
|
|
465
512
|
return groups;
|
|
466
513
|
}
|
|
467
514
|
/**
|
|
468
|
-
*
|
|
469
|
-
* children included — as a top-level row, strictly newest-first. No grouping,
|
|
470
|
-
* no parent/child adjacency. Content search lives outside this derivation
|
|
471
|
-
* (see {@link deriveSearchResults}).
|
|
515
|
+
* Select flat-list members without deriving row presentation or ordering.
|
|
472
516
|
* @param list - sessions list snapshot.
|
|
473
517
|
* @param archivedSessionIds - registry-global archive set.
|
|
474
|
-
* @
|
|
475
|
-
* @returns flat rows in render order.
|
|
518
|
+
* @returns known visible Session ids in list order, including ordinary forks and only the current blank.
|
|
476
519
|
*/
|
|
477
|
-
function
|
|
520
|
+
function visibleSessionIds(list, archivedSessionIds) {
|
|
478
521
|
const archived = new Set(archivedSessionIds);
|
|
479
|
-
|
|
480
|
-
const rows = [];
|
|
481
|
-
for (const id of list.ids) {
|
|
522
|
+
return list.ids.filter((id) => {
|
|
482
523
|
const s = list.byId[id];
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
524
|
+
return s !== void 0 && sessionVisible(s, list.current, archived);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Derive flat rows from the browser's ordered visible Session ids.
|
|
529
|
+
* @param list - sessions list snapshot used to select the ids.
|
|
530
|
+
* @param sessionIds - known visible members in render order, including any pinned blank.
|
|
531
|
+
* @param pendingInteractions - pending UI interactions by Session.
|
|
532
|
+
* @returns flat rows in the supplied order with current status indicators.
|
|
533
|
+
*/
|
|
534
|
+
function deriveFlat(list, sessionIds, pendingInteractions) {
|
|
535
|
+
const descendants = indexSubagentDescendants(list.byId);
|
|
536
|
+
return sessionIds.map((id) => sessionNode(list.byId[id], descendants, pendingInteractions));
|
|
488
537
|
}
|
|
489
538
|
/**
|
|
490
539
|
* Merge immediate title/Workspace substring matches with ranked Host content
|
|
@@ -518,7 +567,8 @@ window.__ModuleLoader__.load({
|
|
|
518
567
|
if (summary === void 0 || summary.blank || !sessionVisible(summary, list.current, archived)) continue;
|
|
519
568
|
if (sessionTitle(summary).toLowerCase().includes(q) || labelOf(summary).toLowerCase().includes(q)) local.push(summary);
|
|
520
569
|
}
|
|
521
|
-
local.
|
|
570
|
+
const localById = new Map(local.map((summary) => [summary.id, summary]));
|
|
571
|
+
const orderedLocal = orderByRecency(local.map((summary) => summary.id), list.byId).map((id) => localById.get(id));
|
|
522
572
|
const ordered = [];
|
|
523
573
|
const included = /* @__PURE__ */ new Set();
|
|
524
574
|
const include = (summary) => {
|
|
@@ -526,7 +576,7 @@ window.__ModuleLoader__.load({
|
|
|
526
576
|
included.add(summary.id);
|
|
527
577
|
ordered.push(summary);
|
|
528
578
|
};
|
|
529
|
-
for (const summary of
|
|
579
|
+
for (const summary of orderedLocal) include(summary);
|
|
530
580
|
for (const item of content.items) {
|
|
531
581
|
const summary = list.byId[item.sessionId];
|
|
532
582
|
if (summary !== void 0 && !summary.blank && sessionVisible(summary, list.current, archived)) include(summary);
|
|
@@ -927,7 +977,7 @@ window.__ModuleLoader__.load({
|
|
|
927
977
|
* @param props.onFork - fork a session at its last completed turn.
|
|
928
978
|
* @param props.onArchive - archive a session by id.
|
|
929
979
|
* @param props.onReveal - scroll this row into view after search navigation, then acknowledge it.
|
|
930
|
-
* @param props.drag - optional
|
|
980
|
+
* @param props.drag - optional row-drag target wiring; blank rows cannot start a drag.
|
|
931
981
|
* @param props.flat - omit the empty status slot in the hierarchy-free flat list.
|
|
932
982
|
* @param props.t - the browser root's locale seat.
|
|
933
983
|
* @returns the session row.
|
|
@@ -938,6 +988,7 @@ window.__ModuleLoader__.load({
|
|
|
938
988
|
const selected = node.id === currentId;
|
|
939
989
|
const statuses = sessionStatuses(node, t);
|
|
940
990
|
const showStatus = statuses[0].state !== "done" || row.completed;
|
|
991
|
+
const draggable = drag !== void 0 && !row.blank;
|
|
941
992
|
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
942
993
|
const rowRef = (0, react.useRef)(null);
|
|
943
994
|
(0, react.useEffect)(() => {
|
|
@@ -971,13 +1022,13 @@ window.__ModuleLoader__.load({
|
|
|
971
1022
|
onClick: () => {
|
|
972
1023
|
onOpen(node.id);
|
|
973
1024
|
},
|
|
974
|
-
draggable
|
|
975
|
-
onDragStart: drag === void 0 ? void 0 : (e) => {
|
|
1025
|
+
draggable,
|
|
1026
|
+
onDragStart: drag === void 0 || row.blank ? void 0 : (e) => {
|
|
976
1027
|
e.dataTransfer.effectAllowed = "move";
|
|
977
1028
|
e.dataTransfer.setData("text/plain", node.id);
|
|
978
1029
|
drag.start();
|
|
979
1030
|
},
|
|
980
|
-
onDragEnd: drag
|
|
1031
|
+
onDragEnd: drag === void 0 || row.blank ? void 0 : drag.end,
|
|
981
1032
|
onDragOver: drag === void 0 ? void 0 : (e) => {
|
|
982
1033
|
if (!drag.active) return;
|
|
983
1034
|
e.preventDefault();
|
|
@@ -1338,58 +1389,6 @@ window.__ModuleLoader__.load({
|
|
|
1338
1389
|
};
|
|
1339
1390
|
}, [active]);
|
|
1340
1391
|
}
|
|
1341
|
-
/** Reconcile a stored view order with the Workspace's current session account. */
|
|
1342
|
-
function reconciledSessionOrder(sessionIds, stored) {
|
|
1343
|
-
if (stored === void 0) return [...sessionIds];
|
|
1344
|
-
const byId = new Map(sessionIds.map((id) => [id, id]));
|
|
1345
|
-
const ordered = [];
|
|
1346
|
-
const included = /* @__PURE__ */ new Set();
|
|
1347
|
-
for (const key of stored) {
|
|
1348
|
-
const id = byId.get(key);
|
|
1349
|
-
if (id === void 0 || included.has(key)) continue;
|
|
1350
|
-
ordered.push(id);
|
|
1351
|
-
included.add(key);
|
|
1352
|
-
}
|
|
1353
|
-
for (const id of sessionIds) {
|
|
1354
|
-
if (included.has(id)) continue;
|
|
1355
|
-
ordered.push(id);
|
|
1356
|
-
}
|
|
1357
|
-
return ordered;
|
|
1358
|
-
}
|
|
1359
|
-
/** Newest update first with stable Session identity as the tie-break. */
|
|
1360
|
-
function compareSessionRecency(a, b, byId) {
|
|
1361
|
-
const aUpdatedAt = byId[a]?.updatedAt ?? Number.NEGATIVE_INFINITY;
|
|
1362
|
-
const bUpdatedAt = byId[b]?.updatedAt ?? Number.NEGATIVE_INFINITY;
|
|
1363
|
-
if (aUpdatedAt !== bUpdatedAt) return bUpdatedAt - aUpdatedAt;
|
|
1364
|
-
return a < b ? -1 : 1;
|
|
1365
|
-
}
|
|
1366
|
-
/** Reconcile one editable order account and apply its activity-promotion policy. */
|
|
1367
|
-
function nextSessionOrderAccount({ sessionIds, previousOrder, previousUpdatedAt, list, orderBy, sortByRecency }) {
|
|
1368
|
-
let order = reconciledSessionOrder(sessionIds, previousOrder);
|
|
1369
|
-
if (sortByRecency) order.sort((a, b) => compareSessionRecency(a, b, list.byId));
|
|
1370
|
-
else if (orderBy === "updated") {
|
|
1371
|
-
const promoted = sessionIds.filter((id) => {
|
|
1372
|
-
const session = list.byId[id];
|
|
1373
|
-
return session !== void 0 && (previousUpdatedAt[id] === void 0 || session.updatedAt > previousUpdatedAt[id]);
|
|
1374
|
-
}).sort((a, b) => compareSessionRecency(a, b, list.byId));
|
|
1375
|
-
if (promoted.length > 0) {
|
|
1376
|
-
const promotedIds = new Set(promoted);
|
|
1377
|
-
order = [...promoted, ...order.filter((id) => !promotedIds.has(id))];
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
const updatedAt = {};
|
|
1381
|
-
for (const id of sessionIds) {
|
|
1382
|
-
const session = list.byId[id];
|
|
1383
|
-
if (session !== void 0) updatedAt[id] = session.updatedAt;
|
|
1384
|
-
}
|
|
1385
|
-
const orderChanged = previousOrder === void 0 || order.length !== previousOrder.length || order.some((id, index) => id !== previousOrder[index]);
|
|
1386
|
-
const timestampsChanged = Object.keys(updatedAt).length !== Object.keys(previousUpdatedAt).length || Object.entries(updatedAt).some(([id, timestamp]) => previousUpdatedAt[id] !== timestamp);
|
|
1387
|
-
return {
|
|
1388
|
-
order,
|
|
1389
|
-
updatedAt,
|
|
1390
|
-
changed: orderChanged || timestampsChanged
|
|
1391
|
-
};
|
|
1392
|
-
}
|
|
1393
1392
|
/** Grouping and ordering menu; own open state so it resets with the wide chrome. */
|
|
1394
1393
|
function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }) {
|
|
1395
1394
|
const [open, setOpen] = (0, react.useState)(false);
|
|
@@ -1461,9 +1460,8 @@ window.__ModuleLoader__.load({
|
|
|
1461
1460
|
return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
1462
1461
|
}
|
|
1463
1462
|
/** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
|
|
1464
|
-
function SessionTree({
|
|
1463
|
+
function SessionTree({ list, useSessionPendingInteraction, startSession, open, forkSession, workspaces, ungroupedSessionIds, archivedSessionIds, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, groupExpansion, setGroupExpanded, setSessionOrder, home, t, revealSessionId, onSessionRevealed }) {
|
|
1465
1464
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
1466
|
-
const list = useSessions((s) => s);
|
|
1467
1465
|
const pendingInteractions = useSessionPendingInteraction((s) => s);
|
|
1468
1466
|
const current = panelActive ? void 0 : list.current;
|
|
1469
1467
|
const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
|
|
@@ -1472,7 +1470,6 @@ window.__ModuleLoader__.load({
|
|
|
1472
1470
|
const sessionDropCommitted = (0, react.useRef)(false);
|
|
1473
1471
|
const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
|
|
1474
1472
|
const workspaceDropCommitted = (0, react.useRef)(false);
|
|
1475
|
-
const previousOrderBy = (0, react.useRef)(orderBy);
|
|
1476
1473
|
useNativeDragAcceptance(drag !== null || workspaceDrag !== null);
|
|
1477
1474
|
const currentGroup = current === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, current);
|
|
1478
1475
|
(0, react.useEffect)(() => {
|
|
@@ -1485,63 +1482,16 @@ window.__ModuleLoader__.load({
|
|
|
1485
1482
|
groupExpansion
|
|
1486
1483
|
]);
|
|
1487
1484
|
const expandedGroups = (0, react.useMemo)(() => Object.entries(groupExpansion).filter(([, expanded]) => expanded).map(([key]) => key), [groupExpansion]);
|
|
1488
|
-
const
|
|
1489
|
-
const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
|
|
1490
|
-
return list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id));
|
|
1491
|
-
}, [list, workspaces]);
|
|
1492
|
-
(0, react.useEffect)(() => {
|
|
1493
|
-
if (list.phase !== "ready") return;
|
|
1494
|
-
const switchedToUpdated = previousOrderBy.current !== "updated" && orderBy === "updated";
|
|
1495
|
-
previousOrderBy.current = orderBy;
|
|
1496
|
-
const accounts = [...workspaces.map((workspace) => ({
|
|
1497
|
-
key: workspace.workspaceId,
|
|
1498
|
-
sessionIds: workspace.sessionIds.filter((id) => list.byId[id] !== void 0)
|
|
1499
|
-
})), {
|
|
1500
|
-
key: "",
|
|
1501
|
-
sessionIds: ungroupedSessionIds
|
|
1502
|
-
}];
|
|
1503
|
-
for (const { key, sessionIds } of accounts) {
|
|
1504
|
-
const previousOrder = sessionOrderByAccount[key];
|
|
1505
|
-
const next = nextSessionOrderAccount({
|
|
1506
|
-
sessionIds,
|
|
1507
|
-
previousOrder,
|
|
1508
|
-
previousUpdatedAt: sessionUpdatedAtByAccount[key] ?? {},
|
|
1509
|
-
list,
|
|
1510
|
-
orderBy,
|
|
1511
|
-
sortByRecency: orderBy === "updated" && (previousOrder === void 0 || switchedToUpdated)
|
|
1512
|
-
});
|
|
1513
|
-
if (next.changed) syncSessionOrderAccount(key, next.order.map((id) => id), next.updatedAt);
|
|
1514
|
-
}
|
|
1515
|
-
}, [
|
|
1516
|
-
list,
|
|
1517
|
-
orderBy,
|
|
1518
|
-
sessionOrderByAccount,
|
|
1519
|
-
sessionUpdatedAtByAccount,
|
|
1520
|
-
syncSessionOrderAccount,
|
|
1521
|
-
ungroupedSessionIds,
|
|
1522
|
-
workspaces
|
|
1523
|
-
]);
|
|
1524
|
-
const orderedWorkspaces = (0, react.useMemo)(() => {
|
|
1525
|
-
return workspaces.map((workspace) => {
|
|
1526
|
-
const stored = sessionOrderByAccount[workspace.workspaceId];
|
|
1527
|
-
const sessionIds = reconciledSessionOrder(workspace.sessionIds, stored);
|
|
1528
|
-
return {
|
|
1529
|
-
...workspace,
|
|
1530
|
-
sessionIds
|
|
1531
|
-
};
|
|
1532
|
-
});
|
|
1533
|
-
}, [sessionOrderByAccount, workspaces]);
|
|
1534
|
-
const orderedUngroupedSessionIds = (0, react.useMemo)(() => reconciledSessionOrder(ungroupedSessionIds, sessionOrderByAccount[""]), [sessionOrderByAccount, ungroupedSessionIds]);
|
|
1535
|
-
const groups = (0, react.useMemo)(() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, pendingInteractions, {
|
|
1485
|
+
const groups = (0, react.useMemo)(() => deriveGroups(list, workspaces, archivedSessionIds, pendingInteractions, {
|
|
1536
1486
|
expandedGroups,
|
|
1537
|
-
|
|
1487
|
+
ungroupedOrder: ungroupedSessionIds
|
|
1538
1488
|
}), [
|
|
1539
1489
|
list,
|
|
1540
|
-
|
|
1490
|
+
workspaces,
|
|
1541
1491
|
archivedSessionIds,
|
|
1542
1492
|
pendingInteractions,
|
|
1543
1493
|
expandedGroups,
|
|
1544
|
-
|
|
1494
|
+
ungroupedSessionIds
|
|
1545
1495
|
]);
|
|
1546
1496
|
(0, react.useEffect)(() => {
|
|
1547
1497
|
if (revealGroup === void 0 || groupExpansion[revealGroup] === true) return;
|
|
@@ -1580,8 +1530,8 @@ window.__ModuleLoader__.load({
|
|
|
1580
1530
|
if (targetWithoutSourceIndex === -1) return;
|
|
1581
1531
|
const visibleInsertAt = over.half === "before" ? targetWithoutSourceIndex : targetWithoutSourceIndex + 1;
|
|
1582
1532
|
if (sourceIndex !== -1 && visibleInsertAt === sourceIndex) return;
|
|
1583
|
-
const accountSessionIds = activeDrag.accountKey === "" ?
|
|
1584
|
-
if (accountSessionIds === void 0) return;
|
|
1533
|
+
const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
|
|
1534
|
+
if (accountSessionIds === void 0 || !accountSessionIds.includes(activeDrag.sessionId)) return;
|
|
1585
1535
|
const nextOrder = accountSessionIds.filter((id) => id !== activeDrag.sessionId);
|
|
1586
1536
|
let anchor;
|
|
1587
1537
|
if (sessionsExpanded) anchor = over.half === "before" ? over.id : renderedSessions[targetIndex + 1]?.id;
|
|
@@ -1603,11 +1553,8 @@ window.__ModuleLoader__.load({
|
|
|
1603
1553
|
return node === void 0 ? [] : [node];
|
|
1604
1554
|
})).rows.some((node) => node.id === activeDrag.sessionId)) return;
|
|
1605
1555
|
}
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
insertSessionBefore(activeDrag.accountKey, activeDrag.sessionId, anchor).catch((reason) => {
|
|
1609
|
-
console.warn("session reorder rejected:", reason);
|
|
1610
|
-
});
|
|
1556
|
+
const currentBlank = group.sessions.find((node) => node.blank)?.id;
|
|
1557
|
+
setSessionOrder(activeDrag.accountKey, pinCurrentBlank(nextOrder, currentBlank));
|
|
1611
1558
|
};
|
|
1612
1559
|
const commitWorkspaceDrag = (activeDrag, over) => {
|
|
1613
1560
|
if (workspaceDropCommitted.current) return;
|
|
@@ -1714,6 +1661,7 @@ window.__ModuleLoader__.load({
|
|
|
1714
1661
|
}),
|
|
1715
1662
|
(sessionsExpanded ? group.sessions : collapsed.rows).map((node) => {
|
|
1716
1663
|
const sameGroupDrag = drag !== null && drag.accountKey === group.key;
|
|
1664
|
+
const normalizeHalf = (half) => node.blank ? "after" : half;
|
|
1717
1665
|
const dragProps = {
|
|
1718
1666
|
start: () => {
|
|
1719
1667
|
sessionDropCommitted.current = false;
|
|
@@ -1731,7 +1679,7 @@ window.__ModuleLoader__.load({
|
|
|
1731
1679
|
...d,
|
|
1732
1680
|
over: {
|
|
1733
1681
|
id: node.id,
|
|
1734
|
-
half
|
|
1682
|
+
half: normalizeHalf(half)
|
|
1735
1683
|
}
|
|
1736
1684
|
});
|
|
1737
1685
|
},
|
|
@@ -1740,7 +1688,7 @@ window.__ModuleLoader__.load({
|
|
|
1740
1688
|
if (drag === null) return;
|
|
1741
1689
|
commitSessionDrag(drag, {
|
|
1742
1690
|
id: node.id,
|
|
1743
|
-
half
|
|
1691
|
+
half: normalizeHalf(half)
|
|
1744
1692
|
});
|
|
1745
1693
|
},
|
|
1746
1694
|
end: () => {
|
|
@@ -1782,50 +1730,13 @@ window.__ModuleLoader__.load({
|
|
|
1782
1730
|
});
|
|
1783
1731
|
}
|
|
1784
1732
|
/** The flat "In one list" body: every session is one draggable top-level row. */
|
|
1785
|
-
function FlatList({
|
|
1733
|
+
function FlatList({ list, sessionIds, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t }) {
|
|
1786
1734
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
1787
|
-
const list = useSessions((s) => s);
|
|
1788
1735
|
const pendingInteractions = useSessionPendingInteraction((s) => s);
|
|
1789
|
-
const
|
|
1736
|
+
const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, pendingInteractions), [
|
|
1790
1737
|
list,
|
|
1791
|
-
archivedSessionIds,
|
|
1792
|
-
pendingInteractions
|
|
1793
|
-
]);
|
|
1794
|
-
const sessionIds = (0, react.useMemo)(() => baseRows.map((row) => row.id), [baseRows]);
|
|
1795
|
-
const previousOrderBy = (0, react.useRef)(orderBy);
|
|
1796
|
-
(0, react.useEffect)(() => {
|
|
1797
|
-
if (list.phase !== "ready") return;
|
|
1798
|
-
const previousOrder = sessionOrderByAccount[FLAT_SESSION_ORDER_KEY];
|
|
1799
|
-
const previousUpdatedAt = sessionUpdatedAtByAccount["__flat_session_order__"] ?? {};
|
|
1800
|
-
const switchedToUpdated = previousOrderBy.current !== "updated" && orderBy === "updated";
|
|
1801
|
-
previousOrderBy.current = orderBy;
|
|
1802
|
-
const next = nextSessionOrderAccount({
|
|
1803
|
-
sessionIds,
|
|
1804
|
-
previousOrder,
|
|
1805
|
-
previousUpdatedAt,
|
|
1806
|
-
list,
|
|
1807
|
-
orderBy,
|
|
1808
|
-
sortByRecency: orderBy === "updated" && (previousOrder === void 0 || switchedToUpdated)
|
|
1809
|
-
});
|
|
1810
|
-
if (next.changed) syncSessionOrderAccount(FLAT_SESSION_ORDER_KEY, next.order.map((id) => id), next.updatedAt);
|
|
1811
|
-
}, [
|
|
1812
|
-
list,
|
|
1813
|
-
orderBy,
|
|
1814
|
-
sessionOrderByAccount,
|
|
1815
|
-
sessionUpdatedAtByAccount,
|
|
1816
1738
|
sessionIds,
|
|
1817
|
-
|
|
1818
|
-
]);
|
|
1819
|
-
const rows = (0, react.useMemo)(() => {
|
|
1820
|
-
const byId = new Map(baseRows.map((row) => [row.id, row]));
|
|
1821
|
-
return reconciledSessionOrder(sessionIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]).flatMap((id) => {
|
|
1822
|
-
const row = byId.get(id);
|
|
1823
|
-
return row === void 0 ? [] : [row];
|
|
1824
|
-
});
|
|
1825
|
-
}, [
|
|
1826
|
-
baseRows,
|
|
1827
|
-
sessionOrderByAccount,
|
|
1828
|
-
sessionIds
|
|
1739
|
+
pendingInteractions
|
|
1829
1740
|
]);
|
|
1830
1741
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
1831
1742
|
const dropCommitted = (0, react.useRef)(false);
|
|
@@ -1844,7 +1755,8 @@ window.__ModuleLoader__.load({
|
|
|
1844
1755
|
const nextOrder = rows.map((row) => row.id).filter((id) => id !== activeDrag.sessionId);
|
|
1845
1756
|
const insertAt = anchor === void 0 ? nextOrder.length : nextOrder.indexOf(anchor);
|
|
1846
1757
|
nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
|
|
1847
|
-
|
|
1758
|
+
const currentBlank = rows.find((node) => node.blank)?.id;
|
|
1759
|
+
setSessionOrder(FLAT_SESSION_ORDER_KEY, pinCurrentBlank(nextOrder, currentBlank));
|
|
1848
1760
|
};
|
|
1849
1761
|
const now = Date.now();
|
|
1850
1762
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1858,6 +1770,7 @@ window.__ModuleLoader__.load({
|
|
|
1858
1770
|
children: t("empty.none")
|
|
1859
1771
|
}), rows.map((node) => {
|
|
1860
1772
|
const active = drag !== null;
|
|
1773
|
+
const normalizeHalf = (half) => node.blank ? "after" : half;
|
|
1861
1774
|
return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
|
|
1862
1775
|
node,
|
|
1863
1776
|
currentId: panelActive ? void 0 : list.current,
|
|
@@ -1886,14 +1799,14 @@ window.__ModuleLoader__.load({
|
|
|
1886
1799
|
...current,
|
|
1887
1800
|
over: {
|
|
1888
1801
|
id: node.id,
|
|
1889
|
-
half
|
|
1802
|
+
half: normalizeHalf(half)
|
|
1890
1803
|
}
|
|
1891
1804
|
});
|
|
1892
1805
|
},
|
|
1893
1806
|
drop: (half) => {
|
|
1894
1807
|
if (drag !== null) commitDrag(drag, {
|
|
1895
1808
|
id: node.id,
|
|
1896
|
-
half
|
|
1809
|
+
half: normalizeHalf(half)
|
|
1897
1810
|
});
|
|
1898
1811
|
},
|
|
1899
1812
|
end: () => {
|
|
@@ -1973,8 +1886,9 @@ window.__ModuleLoader__.load({
|
|
|
1973
1886
|
* @param props - composed slot props (shell owner share + store + injected actions).
|
|
1974
1887
|
* @returns the region element tree.
|
|
1975
1888
|
*/
|
|
1976
|
-
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession,
|
|
1889
|
+
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t }) {
|
|
1977
1890
|
const home = useHostInfo((info) => info.home);
|
|
1891
|
+
const list = useSessions((state) => state);
|
|
1978
1892
|
const workspaces = useWorkspaces((state) => state.items);
|
|
1979
1893
|
const workspacePhase = useWorkspaces((state) => state.phase);
|
|
1980
1894
|
const workspaceStreamState = useWorkspaces((state) => state.state);
|
|
@@ -1984,34 +1898,54 @@ window.__ModuleLoader__.load({
|
|
|
1984
1898
|
const orderBy = useStore((s) => s.orderBy);
|
|
1985
1899
|
const groupExpansion = useStore((s) => s.groupExpansion);
|
|
1986
1900
|
const sessionOrderByAccount = useStore((s) => s.sessionOrderByAccount);
|
|
1987
|
-
const
|
|
1988
|
-
const
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
const
|
|
1994
|
-
(0, react.
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
if (promoted !== void 0 && promoted.sessionId === currentBlankSessionId && promoted.accountKey === currentBlankAccount) return;
|
|
2001
|
-
promotedBlank.current = {
|
|
2002
|
-
sessionId: currentBlankSessionId,
|
|
2003
|
-
accountKey: currentBlankAccount
|
|
1901
|
+
const workspaceReady = workspacePhase === "ready" && workspaceStreamState !== "loading";
|
|
1902
|
+
const currentBlank = list.current !== void 0 && list.byId[list.current]?.blank === true ? list.current : void 0;
|
|
1903
|
+
const ungroupedMemberIds = (0, react.useMemo)(() => {
|
|
1904
|
+
const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
|
|
1905
|
+
return list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id));
|
|
1906
|
+
}, [list, workspaces]);
|
|
1907
|
+
const flatMemberIds = (0, react.useMemo)(() => visibleSessionIds(list, archivedSessionIds), [archivedSessionIds, list]);
|
|
1908
|
+
const orderedWorkspaces = (0, react.useMemo)(() => workspaces.map((workspace) => {
|
|
1909
|
+
const memberIds = workspace.sessionIds;
|
|
1910
|
+
const baseOrder = orderBy === "updated" ? orderByRecency(memberIds, list.byId) : reconcileManualOrder(memberIds, sessionOrderByAccount[workspace.workspaceId], list.byId);
|
|
1911
|
+
return {
|
|
1912
|
+
...workspace,
|
|
1913
|
+
sessionIds: pinCurrentBlank(baseOrder, currentBlank !== void 0 && memberIds.includes(currentBlank) ? currentBlank : void 0)
|
|
2004
1914
|
};
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
1915
|
+
}), [
|
|
1916
|
+
currentBlank,
|
|
1917
|
+
list.byId,
|
|
1918
|
+
orderBy,
|
|
1919
|
+
sessionOrderByAccount,
|
|
1920
|
+
workspaces
|
|
1921
|
+
]);
|
|
1922
|
+
const orderedUngroupedSessionIds = (0, react.useMemo)(() => {
|
|
1923
|
+
return pinCurrentBlank(orderBy === "updated" ? orderByRecency(ungroupedMemberIds, list.byId) : reconcileManualOrder(ungroupedMemberIds, sessionOrderByAccount[""], list.byId), currentBlank !== void 0 && ungroupedMemberIds.includes(currentBlank) ? currentBlank : void 0);
|
|
2009
1924
|
}, [
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
1925
|
+
currentBlank,
|
|
1926
|
+
list.byId,
|
|
1927
|
+
orderBy,
|
|
1928
|
+
sessionOrderByAccount,
|
|
1929
|
+
ungroupedMemberIds
|
|
1930
|
+
]);
|
|
1931
|
+
const orderedFlatSessionIds = (0, react.useMemo)(() => {
|
|
1932
|
+
return pinCurrentBlank(orderBy === "updated" ? orderByRecency(flatMemberIds, list.byId) : reconcileManualOrder(flatMemberIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY], list.byId), currentBlank !== void 0 && flatMemberIds.includes(currentBlank) ? currentBlank : void 0);
|
|
1933
|
+
}, [
|
|
1934
|
+
currentBlank,
|
|
1935
|
+
flatMemberIds,
|
|
1936
|
+
list.byId,
|
|
1937
|
+
orderBy,
|
|
2013
1938
|
sessionOrderByAccount
|
|
2014
1939
|
]);
|
|
1940
|
+
const activeSessionOrders = (0, react.useMemo)(() => Object.fromEntries([
|
|
1941
|
+
...orderedWorkspaces.map((workspace) => [workspace.workspaceId, workspace.sessionIds]),
|
|
1942
|
+
["", orderedUngroupedSessionIds],
|
|
1943
|
+
[FLAT_SESSION_ORDER_KEY, orderedFlatSessionIds]
|
|
1944
|
+
]), [
|
|
1945
|
+
orderedFlatSessionIds,
|
|
1946
|
+
orderedUngroupedSessionIds,
|
|
1947
|
+
orderedWorkspaces
|
|
1948
|
+
]);
|
|
2015
1949
|
(0, react.useEffect)(() => {
|
|
2016
1950
|
if (workspacePhase !== "ready") return;
|
|
2017
1951
|
actions.retainAccountKeys([
|
|
@@ -2024,6 +1958,44 @@ window.__ModuleLoader__.load({
|
|
|
2024
1958
|
workspacePhase,
|
|
2025
1959
|
workspaces
|
|
2026
1960
|
]);
|
|
1961
|
+
(0, react.useEffect)(() => {
|
|
1962
|
+
if (list.phase !== "ready" || workspaceReady || orderBy !== "manual" || currentBlank === void 0) return;
|
|
1963
|
+
const changed = {};
|
|
1964
|
+
for (const [key, ids] of Object.entries(activeSessionOrders)) {
|
|
1965
|
+
if (key !== "__flat_session_order__" && workspacePhase !== "ready") continue;
|
|
1966
|
+
const saved = sessionOrderByAccount[key] ?? [];
|
|
1967
|
+
if (ids[0] !== currentBlank || saved[0] === currentBlank) continue;
|
|
1968
|
+
changed[key] = [currentBlank, ...saved.filter((id) => id !== currentBlank)];
|
|
1969
|
+
}
|
|
1970
|
+
if (Object.keys(changed).length > 0) actions.syncSessionOrders(changed);
|
|
1971
|
+
}, [
|
|
1972
|
+
actions.syncSessionOrders,
|
|
1973
|
+
activeSessionOrders,
|
|
1974
|
+
currentBlank,
|
|
1975
|
+
list.phase,
|
|
1976
|
+
orderBy,
|
|
1977
|
+
sessionOrderByAccount,
|
|
1978
|
+
workspacePhase,
|
|
1979
|
+
workspaceReady
|
|
1980
|
+
]);
|
|
1981
|
+
(0, react.useEffect)(() => {
|
|
1982
|
+
if (list.phase !== "ready" || !workspaceReady || orderBy !== "manual") return;
|
|
1983
|
+
const changed = Object.fromEntries(Object.entries(activeSessionOrders).filter(([key, ids]) => {
|
|
1984
|
+
const saved = sessionOrderByAccount[key];
|
|
1985
|
+
return saved === void 0 || saved.length !== ids.length || ids.some((id, index) => id !== saved[index]);
|
|
1986
|
+
}));
|
|
1987
|
+
if (Object.keys(changed).length > 0) actions.syncSessionOrders(changed);
|
|
1988
|
+
}, [
|
|
1989
|
+
actions.syncSessionOrders,
|
|
1990
|
+
activeSessionOrders,
|
|
1991
|
+
list.phase,
|
|
1992
|
+
orderBy,
|
|
1993
|
+
sessionOrderByAccount,
|
|
1994
|
+
workspaceReady
|
|
1995
|
+
]);
|
|
1996
|
+
const saveSessionOrder = (accountKey, order) => {
|
|
1997
|
+
actions.setSessionOrder(accountKey, order, activeSessionOrders);
|
|
1998
|
+
};
|
|
2027
1999
|
const [query, setQuery] = (0, react.useState)("");
|
|
2028
2000
|
const [searchExpanded, setSearchExpanded] = (0, react.useState)(false);
|
|
2029
2001
|
const [revealSessionId, setRevealSessionId] = (0, react.useState)(void 0);
|
|
@@ -2296,7 +2268,7 @@ window.__ModuleLoader__.load({
|
|
|
2296
2268
|
actions.setGroupBy(mode);
|
|
2297
2269
|
},
|
|
2298
2270
|
onOrderPick: (mode) => {
|
|
2299
|
-
actions.setOrderBy(mode);
|
|
2271
|
+
actions.setOrderBy(mode, activeSessionOrders);
|
|
2300
2272
|
},
|
|
2301
2273
|
t
|
|
2302
2274
|
}), directoryFlowAvailable && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
@@ -2367,42 +2339,34 @@ window.__ModuleLoader__.load({
|
|
|
2367
2339
|
t
|
|
2368
2340
|
}) : groupBy === "flat" ? (0, react_jsx_runtime.jsx)(FlatList, {
|
|
2369
2341
|
usePanelInfo,
|
|
2370
|
-
|
|
2342
|
+
list,
|
|
2343
|
+
sessionIds: orderedFlatSessionIds,
|
|
2371
2344
|
useSessionPendingInteraction,
|
|
2372
2345
|
open,
|
|
2373
2346
|
forkSession,
|
|
2374
2347
|
onSessionRename,
|
|
2375
2348
|
onSessionArchive,
|
|
2376
|
-
|
|
2377
|
-
orderBy,
|
|
2378
|
-
sessionOrderByAccount,
|
|
2379
|
-
sessionUpdatedAtByAccount,
|
|
2380
|
-
syncSessionOrderAccount: actions.syncSessionOrderAccount,
|
|
2381
|
-
setSessionOrder: actions.setSessionOrder,
|
|
2349
|
+
setSessionOrder: saveSessionOrder,
|
|
2382
2350
|
revealSessionId,
|
|
2383
2351
|
onSessionRevealed: acknowledgeSessionReveal,
|
|
2384
2352
|
t
|
|
2385
2353
|
}) : (0, react_jsx_runtime.jsx)(SessionTree, {
|
|
2386
2354
|
usePanelInfo,
|
|
2387
|
-
|
|
2355
|
+
list,
|
|
2388
2356
|
useSessionPendingInteraction,
|
|
2389
2357
|
onSessionRename,
|
|
2390
2358
|
onSessionArchive,
|
|
2391
2359
|
forkSession,
|
|
2392
|
-
workspaces,
|
|
2393
|
-
|
|
2360
|
+
workspaces: orderedWorkspaces,
|
|
2361
|
+
ungroupedSessionIds: orderedUngroupedSessionIds,
|
|
2362
|
+
workspaceReady,
|
|
2394
2363
|
groupExpansion,
|
|
2395
2364
|
setGroupExpanded: actions.setGroupExpanded,
|
|
2396
|
-
|
|
2397
|
-
sessionUpdatedAtByAccount,
|
|
2398
|
-
syncSessionOrderAccount: actions.syncSessionOrderAccount,
|
|
2399
|
-
setSessionOrder: actions.setSessionOrder,
|
|
2365
|
+
setSessionOrder: saveSessionOrder,
|
|
2400
2366
|
archivedSessionIds,
|
|
2401
2367
|
startSession,
|
|
2402
2368
|
open,
|
|
2403
2369
|
insertWorkspaceBefore,
|
|
2404
|
-
insertSessionBefore,
|
|
2405
|
-
orderBy,
|
|
2406
2370
|
revealSessionId,
|
|
2407
2371
|
onSessionRevealed: acknowledgeSessionReveal,
|
|
2408
2372
|
home,
|
|
@@ -2778,9 +2742,6 @@ window.__ModuleLoader__.load({
|
|
|
2778
2742
|
archiveSession: async (sessionId) => {
|
|
2779
2743
|
await uiWorkspace.archiveSession(sessionId);
|
|
2780
2744
|
},
|
|
2781
|
-
insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
|
|
2782
|
-
await workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
|
|
2783
|
-
},
|
|
2784
2745
|
createWorkspace: (input) => workspaces.create(input),
|
|
2785
2746
|
hooks: {
|
|
2786
2747
|
directoryFlow: browserFlowSource,
|
|
@@ -130,12 +130,6 @@ export type WorkspaceBrowserInjected = {
|
|
|
130
130
|
* session clears the selection into the New Session view state.
|
|
131
131
|
*/
|
|
132
132
|
archiveSession: (sessionId: SessionId) => Promise<void>;
|
|
133
|
-
/**
|
|
134
|
-
* Reorder a session inside its Workspace account (DOM-insertBefore
|
|
135
|
-
* semantics: omitted anchor appends to the end). The view refreshes from
|
|
136
|
-
* the Host response/changed frame; failures leave the order unchanged.
|
|
137
|
-
*/
|
|
138
|
-
insertSessionBefore: (workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId) => Promise<void>;
|
|
139
133
|
/** Adopt a picked host directory as a real Workspace before targeting a Session. */
|
|
140
134
|
createWorkspace: (input: {
|
|
141
135
|
path: string;
|
|
@@ -40,6 +40,11 @@ export interface UiWorkspace {
|
|
|
40
40
|
* @param sessionId - Session to archive.
|
|
41
41
|
*/
|
|
42
42
|
archiveSession(sessionId: SessionId): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Unarchive a Session, restoring it to its recorded Workspace position.
|
|
45
|
+
* @param sessionId - Session to unarchive.
|
|
46
|
+
*/
|
|
47
|
+
unarchiveSession(sessionId: SessionId): Promise<void>;
|
|
43
48
|
/**
|
|
44
49
|
* Open the Host-native directory picker.
|
|
45
50
|
* @returns the selected directory, or null when cancelled.
|
|
@@ -93,6 +98,7 @@ declare class UiWorkspaceService extends Service implements UiWorkspace {
|
|
|
93
98
|
forkSession(sessionId: SessionId): Promise<void>;
|
|
94
99
|
startSession(workspaceId?: WorkspaceId): void;
|
|
95
100
|
archiveSession(sessionId: SessionId): Promise<void>;
|
|
101
|
+
unarchiveSession(sessionId: SessionId): Promise<void>;
|
|
96
102
|
pickDirectory(): Promise<string | null>;
|
|
97
103
|
listDirectory(path?: string, signal?: AbortSignal): Promise<DirectoryListing>;
|
|
98
104
|
createDirectory(path: string, name: string): Promise<string>;
|
|
@@ -78,7 +78,7 @@ export declare function SearchResultItem({ result, currentId, onOpen, t }: {
|
|
|
78
78
|
* @param props.onFork - fork a session at its last completed turn.
|
|
79
79
|
* @param props.onArchive - archive a session by id.
|
|
80
80
|
* @param props.onReveal - scroll this row into view after search navigation, then acknowledge it.
|
|
81
|
-
* @param props.drag - optional
|
|
81
|
+
* @param props.drag - optional row-drag target wiring; blank rows cannot start a drag.
|
|
82
82
|
* @param props.flat - omit the empty status slot in the hierarchy-free flat list.
|
|
83
83
|
* @param props.t - the browser root's locale seat.
|
|
84
84
|
* @returns the session row.
|
|
@@ -96,7 +96,7 @@ export declare function SessionNodeItem({ node, currentId, now, onOpen, onRename
|
|
|
96
96
|
onArchive: (id: SessionNode['id']) => void;
|
|
97
97
|
/** Scroll this row into view after search navigation, then acknowledge it. */
|
|
98
98
|
onReveal?: (() => void) | undefined;
|
|
99
|
-
/** Present
|
|
99
|
+
/** Present on reorderable-list rows so every row can remain a drop target. */
|
|
100
100
|
drag?: RowDragProps | undefined;
|
|
101
101
|
/** The row is rendered without a parent Workspace header. */
|
|
102
102
|
flat?: boolean | undefined;
|
|
@@ -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, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession,
|
|
7
|
+
export declare function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t, }: WorkspaceBrowserProps): import("react").JSX.Element;
|
|
8
8
|
//# sourceMappingURL=WorkspaceBrowser.d.ts.map
|
|
@@ -10,7 +10,7 @@ import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-store';
|
|
|
10
10
|
export declare const FLAT_SESSION_ORDER_KEY = "__flat_session_order__";
|
|
11
11
|
/** Session-list grouping mode: workspace sections or one flat recency list. */
|
|
12
12
|
export type SessionGroupBy = 'workspace' | 'flat';
|
|
13
|
-
/** Session order:
|
|
13
|
+
/** Session order: saved manual positions or current recency. */
|
|
14
14
|
export type SessionOrderBy = 'manual' | 'updated';
|
|
15
15
|
/** Workspace browser viewing state persisted across surface remounts and reloads. */
|
|
16
16
|
type WorkspaceViewState = {
|
|
@@ -18,10 +18,8 @@ type WorkspaceViewState = {
|
|
|
18
18
|
orderBy: SessionOrderBy;
|
|
19
19
|
/** Explicit zero-or-five-session state keyed by Workspace group identity. */
|
|
20
20
|
groupExpansion: Record<string, boolean>;
|
|
21
|
-
/**
|
|
21
|
+
/** Saved manual order per Workspace group plus the browser-local flat-list account. */
|
|
22
22
|
sessionOrderByAccount: Record<string, string[]>;
|
|
23
|
-
/** Last observed update timestamps per order account for one-time promotion events. */
|
|
24
|
-
sessionUpdatedAtByAccount: Record<string, Record<string, number>>;
|
|
25
23
|
};
|
|
26
24
|
/**
|
|
27
25
|
* Annotation twin of the actions literal below (the export needs a declared
|
|
@@ -29,11 +27,11 @@ type WorkspaceViewState = {
|
|
|
29
27
|
*/
|
|
30
28
|
type WorkspaceViewActions = {
|
|
31
29
|
setGroupBy: (draft: WorkspaceViewState, mode: SessionGroupBy) => void;
|
|
32
|
-
setOrderBy: (draft: WorkspaceViewState, mode: SessionOrderBy) => void;
|
|
30
|
+
setOrderBy: (draft: WorkspaceViewState, mode: SessionOrderBy, initialOrders: Readonly<Record<string, readonly string[]>>) => void;
|
|
33
31
|
setGroupExpanded: (draft: WorkspaceViewState, key: string, expanded: boolean) => void;
|
|
34
32
|
retainAccountKeys: (draft: WorkspaceViewState, workspaceKeys: readonly string[]) => void;
|
|
35
|
-
|
|
36
|
-
setSessionOrder: (draft: WorkspaceViewState, accountKey: string, order: string[]) => void;
|
|
33
|
+
syncSessionOrders: (draft: WorkspaceViewState, orders: Readonly<Record<string, readonly string[]>>) => void;
|
|
34
|
+
setSessionOrder: (draft: WorkspaceViewState, accountKey: string, order: readonly string[], initialOrders: Readonly<Record<string, readonly string[]>>) => void;
|
|
37
35
|
};
|
|
38
36
|
/**
|
|
39
37
|
* Create the workspace browser viewing store handle.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Derives the workspace browser tree from
|
|
3
|
-
* Unassigned Sessions trail under Ungrouped; only the selected
|
|
4
|
-
* remains visible.
|
|
2
|
+
* Derives the workspace browser tree from caller-projected Workspace and
|
|
3
|
+
* Session order. Unassigned Sessions trail under Ungrouped; only the selected
|
|
4
|
+
* blank Session remains visible.
|
|
5
5
|
*/
|
|
6
6
|
import { type SessionListState, type SessionSearchResultItem } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
7
7
|
import type { WorkspaceId, WorkspaceView } from '@deepseek-ai/dsh-api-workspace-controller/client';
|
|
@@ -91,6 +91,28 @@ export interface TreeView {
|
|
|
91
91
|
* @returns basename, the raw cwd when it has no basename, or an empty ungrouped marker.
|
|
92
92
|
*/
|
|
93
93
|
export declare function workspaceLabel(cwd: string | undefined): string;
|
|
94
|
+
/**
|
|
95
|
+
* Project known account members by current Session recency.
|
|
96
|
+
* @param sessionIds - authoritative account membership.
|
|
97
|
+
* @param summaries - current Session summaries; members without a summary are omitted until it arrives.
|
|
98
|
+
* @returns known members newest first, with Session identity as the deterministic tie-break.
|
|
99
|
+
*/
|
|
100
|
+
export declare function orderByRecency(sessionIds: readonly SessionId[], summaries: SessionListState['byId']): SessionId[];
|
|
101
|
+
/**
|
|
102
|
+
* Reconcile a browser-local manual order with current account membership.
|
|
103
|
+
* @param memberIds - authoritative account membership.
|
|
104
|
+
* @param savedOrder - previously saved browser-local order.
|
|
105
|
+
* @param summaries - current Session summaries used to append newly known members by recency.
|
|
106
|
+
* @returns retained saved slots followed by newly known members; departed members and unknown new members are omitted.
|
|
107
|
+
*/
|
|
108
|
+
export declare function reconcileManualOrder(memberIds: readonly SessionId[], savedOrder: readonly string[] | undefined, summaries: SessionListState['byId']): SessionId[];
|
|
109
|
+
/**
|
|
110
|
+
* Keep the selected provisional New Session ahead of either base order.
|
|
111
|
+
* @param order - recency or reconciled manual order.
|
|
112
|
+
* @param currentBlank - selected blank Session in this account, when present.
|
|
113
|
+
* @returns a copy with the selected blank first and no duplicate slot.
|
|
114
|
+
*/
|
|
115
|
+
export declare function pinCurrentBlank(order: readonly SessionId[], currentBlank: SessionId | undefined): SessionId[];
|
|
94
116
|
/**
|
|
95
117
|
* Derive the workspace browser groups with every session as a top-level row.
|
|
96
118
|
*
|
|
@@ -100,7 +122,7 @@ export declare function workspaceLabel(cwd: string | undefined): string;
|
|
|
100
122
|
* Content search lives outside this derivation
|
|
101
123
|
* (see {@link deriveSearchResults}).
|
|
102
124
|
* @param list - sessions list snapshot (`current` feeds containsCurrent).
|
|
103
|
-
* @param workspaces - real
|
|
125
|
+
* @param workspaces - real Workspaces in Host group order with caller-projected Session order.
|
|
104
126
|
* @param archivedSessionIds - registry-global archive set.
|
|
105
127
|
* @param pendingInteractions - pending UI interactions by Session.
|
|
106
128
|
* @param view - local expansion arrays.
|
|
@@ -108,16 +130,20 @@ export declare function workspaceLabel(cwd: string | undefined): string;
|
|
|
108
130
|
*/
|
|
109
131
|
export declare function deriveGroups(list: SessionListState, workspaces: readonly WorkspaceView[], archivedSessionIds: readonly SessionId[], pendingInteractions: SessionPendingInteractions, view: TreeView): GroupNode[];
|
|
110
132
|
/**
|
|
111
|
-
*
|
|
112
|
-
* children included — as a top-level row, strictly newest-first. No grouping,
|
|
113
|
-
* no parent/child adjacency. Content search lives outside this derivation
|
|
114
|
-
* (see {@link deriveSearchResults}).
|
|
133
|
+
* Select flat-list members without deriving row presentation or ordering.
|
|
115
134
|
* @param list - sessions list snapshot.
|
|
116
135
|
* @param archivedSessionIds - registry-global archive set.
|
|
136
|
+
* @returns known visible Session ids in list order, including ordinary forks and only the current blank.
|
|
137
|
+
*/
|
|
138
|
+
export declare function visibleSessionIds(list: SessionListState, archivedSessionIds: readonly SessionId[]): SessionId[];
|
|
139
|
+
/**
|
|
140
|
+
* Derive flat rows from the browser's ordered visible Session ids.
|
|
141
|
+
* @param list - sessions list snapshot used to select the ids.
|
|
142
|
+
* @param sessionIds - known visible members in render order, including any pinned blank.
|
|
117
143
|
* @param pendingInteractions - pending UI interactions by Session.
|
|
118
|
-
* @returns flat rows in
|
|
144
|
+
* @returns flat rows in the supplied order with current status indicators.
|
|
119
145
|
*/
|
|
120
|
-
export declare function deriveFlat(list: SessionListState,
|
|
146
|
+
export declare function deriveFlat(list: SessionListState, sessionIds: readonly SessionId[], pendingInteractions: SessionPendingInteractions): SessionNode[];
|
|
121
147
|
/**
|
|
122
148
|
* Merge immediate title/Workspace substring matches with ranked Host content
|
|
123
149
|
* matches. Local rows lead newest-first, content-only rows retain backend
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-workspace",
|
|
3
3
|
"description": "Workspace picker plugin: one WorkspacePicker registered into the sidebar and empty-state workspace slots",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -50,25 +50,25 @@
|
|
|
50
50
|
"@types/react": "~18.3.1",
|
|
51
51
|
"react": "^18.2.0",
|
|
52
52
|
"clsx": "^2.0.0",
|
|
53
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.
|
|
54
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.
|
|
55
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-client-
|
|
57
|
-
"@deepseek-ai/dsh-client-
|
|
58
|
-
"@deepseek-ai/dsh-client-
|
|
59
|
-
"@deepseek-ai/dsh-client-
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-layout": "^0.1.
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-
|
|
67
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.
|
|
68
|
-
"@deepseek-ai/
|
|
69
|
-
"@deepseek-ai/dsh-util-workspace-path": "^0.1.
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/
|
|
53
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.6-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.6-alpha.1",
|
|
55
|
+
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.6-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.6-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-client-store": "^0.1.6-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-client-test-runtime": "^0.1.6-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.6-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.1.6-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.6-alpha.1",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.6-alpha.1",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.1",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.1",
|
|
67
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.6-alpha.1",
|
|
68
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
|
|
69
|
+
"@deepseek-ai/dsh-util-workspace-path": "^0.1.6-alpha.1",
|
|
70
|
+
"@deepseek-ai/dsh-schedule": "^0.1.6-alpha.1",
|
|
71
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
72
72
|
},
|
|
73
73
|
"files": [
|
|
74
74
|
"lib/index.js",
|