@michengai/dsh-archive-manager 0.1.0

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/lib/client.js ADDED
@@ -0,0 +1,2903 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@michengai/dsh-archive-manager",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ let react = require("react");
10
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
+ //#region dsh-archive-manager: typert remote contribution + archived styling
12
+ /**
13
+ * Strict codec shims for the archive-manager Remote descriptors. The
14
+ * client typert gateway requires `mode: "strict"` codecs whose schema
15
+ * exposes a `parse()` function; plain functions satisfy the contract
16
+ * without shipping a second zod copy into this bundle.
17
+ */
18
+ const sessionIdSchema = {
19
+ parse(value) {
20
+ if (typeof value !== "string" || value.length === 0) throw new TypeError(`sessionId must be a non-empty string, got ${String(value)}`);
21
+ return value;
22
+ }
23
+ };
24
+ const archivedSetSchema = {
25
+ parse(value) {
26
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
27
+ const ids = value.archivedSessionIds;
28
+ if (!Array.isArray(ids) || ids.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIds must be a string array");
29
+ return value;
30
+ }
31
+ };
32
+ const deletedSchema = {
33
+ parse(value) {
34
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value.deleted !== true) throw new TypeError("deleted must be true");
35
+ return value;
36
+ }
37
+ };
38
+ /**
39
+ * 客户端通过 `ctx.remote.$mount` 注册 workspaceRegistry 的远程方法。
40
+ * 调用走 typert gateway SRC 路径,不影响既有 `/api/workspace.*` 网关。
41
+ */
42
+ const ARCHIVE_MANAGER_REMOTE = {
43
+ package: "@michengai/dsh-archive-manager",
44
+ descriptors: [
45
+ {
46
+ id: "@michengai/dsh-archive-manager#workspaceRegistry/unarchiveSession",
47
+ service: "workspaceRegistry",
48
+ namespace: "workspaceRegistry",
49
+ method: "unarchiveSession",
50
+ invocation: { kind: "direct" },
51
+ parameters: [{
52
+ name: "sessionId",
53
+ wire: "sessionId",
54
+ source: "json",
55
+ codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-session/types#SessionId", schema: sessionIdSchema }
56
+ }],
57
+ result: {
58
+ mode: "strict",
59
+ typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedSessionIds",
60
+ schema: archivedSetSchema
61
+ },
62
+ sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
63
+ },
64
+ {
65
+ id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteSession",
66
+ service: "workspaceRegistry",
67
+ namespace: "workspaceRegistry",
68
+ method: "deleteSession",
69
+ invocation: { kind: "direct" },
70
+ parameters: [{
71
+ name: "sessionId",
72
+ wire: "sessionId",
73
+ source: "json",
74
+ codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-session/types#SessionId", schema: sessionIdSchema }
75
+ }],
76
+ result: {
77
+ mode: "strict",
78
+ typeSymbol: "@michengai/dsh-archive-manager/types#Deleted",
79
+ schema: deletedSchema
80
+ },
81
+ sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
82
+ }
83
+ ]
84
+ };
85
+ /** Archived-session row presentation classes (theme-token driven). */
86
+ const ARCHIVED_CLASSES = {
87
+ row: "dsham_archivedRow",
88
+ title: "dsham_archivedTitle",
89
+ badge: "dsham_archivedBadge",
90
+ content: "dsham_archiveCardContent",
91
+ meta: "dsham_archiveCardMeta",
92
+ actions: "dsham_archiveCardActions",
93
+ unarchive: "dsham_archiveCardUnarchive",
94
+ delete: "dsham_archiveCardDelete"
95
+ };
96
+ const ARCHIVED_CSS = ".YDXeBa_sessionRow.dsham_archivedRow{box-sizing:border-box;cursor:default;min-height:64px;height:auto;background:var(--dsw-alias-button-elevated-fill);border:1px solid var(--dsw-alias-border-l2);border-radius:16px;gap:12px;margin:8px 0;padding:10px 16px}.YDXeBa_sessionRow.dsham_archivedRow:hover{background:var(--dsw-alias-button-elevated-fill);border-color:var(--dsw-alias-border-l3)}.YDXeBa_searchResultRow.dsham_archivedRow{background:var(--dsw-alias-button-elevated-fill);border:1px solid var(--dsw-alias-border-l2);border-radius:16px;margin:8px 0;padding:10px 16px}.dsham_archivedTitle{color:var(--dsw-alias-label-primary);font-weight:600}.dsham_archivedBadge{display:none}.dsham_archiveCardContent{min-width:0;flex:1;flex-direction:column;gap:2px;display:flex}.dsham_archiveCardMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.YDXeBa_sessionRow.dsham_archivedRow>.YDXeBa_time,.YDXeBa_sessionRow.dsham_archivedRow>.YDXeBa_rowActions{display:none}.dsham_archiveCardActions{align-items:center;gap:12px;display:inline-flex}.dsham_archiveCardActions button{cursor:pointer;border:none;flex:none}.dsham_archiveCardDelete{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:transparent;border-radius:8px;align-items:center;justify-content:center;display:inline-flex}.dsham_archiveCardDelete:hover{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_archiveCardUnarchive{height:32px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-elevated-fill);border:1px solid var(--dsw-alias-border-l2)!important;border-radius:10px;padding:0 12px;font-size:13px;font-weight:600;line-height:20px}.dsham_archiveCardUnarchive:hover{background:var(--dsw-alias-interactive-bg-hover)}";
97
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/Archived.module.css") + "]") === null) {
98
+ const tag = document.createElement("style");
99
+ tag.dataset.plugin = "@michengai/dsh-archive-manager";
100
+ tag.dataset.pluginCss = "dsh-archive-manager/Archived.module.css";
101
+ tag.textContent = ARCHIVED_CSS;
102
+ document.head.appendChild(tag);
103
+ }
104
+ //#endregion
105
+ //#region lib/types/client/stores.js
106
+ /**
107
+ * The workspace browser's viewing store: the session-list grouping mode,
108
+ * persisted across reloads. Module level exports the factory only (a
109
+ * module-level handle would pin the store identity across plugin reloads);
110
+ * register() receives the factory and the browser derives its PropsStore
111
+ * share from the return type.
112
+ */
113
+ /** Browser-local order account for the hierarchy-free flat Session list. */
114
+ const FLAT_SESSION_ORDER_KEY = "__flat_session_order__";
115
+ /**
116
+ * Create the workspace browser viewing store handle.
117
+ * @returns the store handle (spec + type + identity + factory in one).
118
+ */
119
+ function createWorkspaceViewStore() {
120
+ return (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
121
+ init: () => ({
122
+ groupBy: "workspace",
123
+ orderBy: "updated",
124
+ showArchived: false,
125
+ groupExpansion: {},
126
+ sessionOrderByAccount: {},
127
+ sessionUpdatedAtByAccount: {}
128
+ }),
129
+ persist: "dsh.workspace.view.v5",
130
+ actions: {
131
+ setGroupBy: (d, mode) => {
132
+ d.groupBy = mode;
133
+ },
134
+ setOrderBy: (d, mode) => {
135
+ d.orderBy = mode;
136
+ },
137
+ setShowArchived: (d, value) => {
138
+ d.showArchived = value === true;
139
+ },
140
+ setGroupExpanded: (d, key, expanded) => {
141
+ d.groupExpansion[key] = expanded;
142
+ },
143
+ retainAccountKeys: (d, workspaceKeys) => {
144
+ const retained = new Set(workspaceKeys);
145
+ d.groupExpansion = Object.fromEntries(Object.entries(d.groupExpansion).filter(([key]) => retained.has(key)));
146
+ d.sessionOrderByAccount = Object.fromEntries(Object.entries(d.sessionOrderByAccount).filter(([key]) => retained.has(key)));
147
+ d.sessionUpdatedAtByAccount = Object.fromEntries(Object.entries(d.sessionUpdatedAtByAccount).filter(([key]) => retained.has(key)));
148
+ },
149
+ syncSessionOrderAccount: (d, accountKey, order, updatedAt) => {
150
+ d.sessionOrderByAccount[accountKey] = order;
151
+ d.sessionUpdatedAtByAccount[accountKey] = updatedAt;
152
+ },
153
+ setSessionOrder: (d, accountKey, order) => {
154
+ d.sessionOrderByAccount[accountKey] = order;
155
+ }
156
+ }
157
+ });
158
+ }
159
+ //#endregion
160
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
161
+ function r(e) {
162
+ var t, f, n = "";
163
+ if ("string" == typeof e || "number" == typeof e) n += e;
164
+ else if ("object" == typeof e) if (Array.isArray(e)) {
165
+ var o = e.length;
166
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
167
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
168
+ return n;
169
+ }
170
+ function clsx() {
171
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
172
+ return n;
173
+ }
174
+ /** Display label for the ungrouped bucket row. */
175
+ const UNGROUPED_LABEL = "Ungrouped";
176
+ /**
177
+ * Directory display label: basename of the path (both separators accepted).
178
+ * Ungrouped-bucket fallback for surfaces without a workspace title.
179
+ * @param cwd - directory path, or undefined for the ungrouped bucket.
180
+ * @returns basename, the raw cwd when it has no basename, or the ungrouped label.
181
+ */
182
+ function workspaceLabel(cwd) {
183
+ if (cwd === void 0 || cwd === "") return UNGROUPED_LABEL;
184
+ const base = cwd.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
185
+ return base !== void 0 && base !== "" ? base : cwd;
186
+ }
187
+ /** Recency comparator: newest first, id as the deterministic tiebreak (ids are unique per group). */
188
+ function byRecency(a, b) {
189
+ if (b.updatedAt !== a.updatedAt) return b.updatedAt - a.updatedAt;
190
+ return a.id < b.id ? -1 : 1;
191
+ }
192
+ /**
193
+ * Ordinary sessions are visible; among blank sessions, only the current one
194
+ * is visible. Subagent children use their parent header catalog. Archived
195
+ * sessions are hidden unless the "show archived" view toggle is on
196
+ * (`showArchived`); their accounting slots remain either way, so
197
+ * unarchiving restores position.
198
+ */
199
+ function sessionVisible(session, current, archived, showArchived) {
200
+ return session.origin !== "subagent" && (!archived.has(session.id) || showArchived === true) && (!session.blank || session.id === current);
201
+ }
202
+ /**
203
+ * A blank session is the selected Workspace's provisional New Session row;
204
+ * its canonical title never enters search (blank rows are query-excluded)
205
+ * and the renderer localizes its display label.
206
+ */
207
+ function sessionTitle(session) {
208
+ return session.blank ? "New Session" : session.displayTitle;
209
+ }
210
+ /** Build one group without projecting session lineage into presentation. */
211
+ function buildGroup(key, workspaceId, cwd, createdAt, label, members, order) {
212
+ const sessions = [...members];
213
+ if (order === "recency") sessions.sort(byRecency);
214
+ return {
215
+ key,
216
+ workspaceId,
217
+ cwd,
218
+ createdAt,
219
+ label,
220
+ sessions
221
+ };
222
+ }
223
+ /** Apply a stored Ungrouped order and append newly loose Sessions by recency. */
224
+ function orderedUngrouped(members, stored) {
225
+ const byId = new Map(members.map((session) => [session.id, session]));
226
+ const included = /* @__PURE__ */ new Set();
227
+ const ordered = [];
228
+ for (const key of stored) {
229
+ const session = byId.get(key);
230
+ if (session === void 0 || included.has(key)) continue;
231
+ ordered.push(session);
232
+ included.add(key);
233
+ }
234
+ for (const session of [...members].sort(byRecency)) {
235
+ if (included.has(session.id)) continue;
236
+ ordered.push(session);
237
+ }
238
+ return ordered;
239
+ }
240
+ /**
241
+ * Group Sessions by Host Workspace: one group per entity in stable Host
242
+ * order, with members resolved from sessionIds in their stored order. Sessions
243
+ * outside every Workspace trail in the browser-local Ungrouped order, which
244
+ * falls back to recency before that order is initialized.
245
+ */
246
+ function groupByWorkspace(list, workspaces, archived, ungroupedOrder, showArchived) {
247
+ const groups = [];
248
+ const accounted = /* @__PURE__ */ new Set();
249
+ for (const workspace of workspaces) {
250
+ const members = [];
251
+ for (const id of workspace.sessionIds) {
252
+ const summary = list.byId[id];
253
+ if (summary === void 0) continue;
254
+ accounted.add(id);
255
+ if (!sessionVisible(summary, list.current, archived, showArchived)) continue;
256
+ members.push(summary);
257
+ }
258
+ groups.push(buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members, "account"));
259
+ }
260
+ const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, list.current, archived, showArchived));
261
+ if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, UNGROUPED_LABEL, ungroupedOrder === void 0 ? stray : orderedUngrouped(stray, ungroupedOrder), ungroupedOrder === void 0 ? "recency" : "account"));
262
+ return groups;
263
+ }
264
+ function sessionNode(s, descendants, archived) {
265
+ return {
266
+ id: s.id,
267
+ title: sessionTitle(s),
268
+ blank: s.blank,
269
+ running: s.running,
270
+ runningSubagentCount: descendants.get(s.id)?.runningCount ?? 0,
271
+ completed: s.completed === true,
272
+ updatedAt: s.updatedAt,
273
+ archived: archived.has(s.id),
274
+ ...s.pendingInteraction === void 0 ? {} : { pendingInteraction: s.pendingInteraction }
275
+ };
276
+ }
277
+ /**
278
+ * Derive the workspace browser groups with every session as a top-level row.
279
+ *
280
+ * Every group shows; sessions populate under expanded groups in the selected
281
+ * local order. Blank sessions are excluded except for the selected
282
+ * provisional New Session row; archived sessions are excluded unless the
283
+ * "show archived" view toggle is on.
284
+ * Content search lives outside this derivation
285
+ * (see {@link deriveSearchResults}).
286
+ * @param list - sessions list snapshot (`current` feeds containsCurrent).
287
+ * @param workspaces - real workspaces in stable Host order.
288
+ * @param archivedSessionIds - registry-global archive set.
289
+ * @param view - local expansion arrays and the show-archived toggle.
290
+ * @returns group sections in render order.
291
+ */
292
+ function deriveGroups(list, workspaces, archivedSessionIds, view) {
293
+ const archived = new Set(archivedSessionIds);
294
+ const expandedGroups = new Set(view.expandedGroups);
295
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
296
+ const currentGroup = list.current === void 0 ? void 0 : workspaces.find((w) => w.sessionIds.includes(list.current))?.workspaceId ?? "";
297
+ const groups = [];
298
+ for (const g of groupByWorkspace(list, workspaces, archived, view.ungroupedOrder, view.showArchived)) {
299
+ const expanded = expandedGroups.has(g.key);
300
+ groups.push({
301
+ key: g.key,
302
+ workspaceId: g.workspaceId,
303
+ cwd: g.cwd,
304
+ createdAt: g.createdAt,
305
+ label: g.label,
306
+ sessionCount: g.sessions.length,
307
+ expanded,
308
+ containsCurrent: g.key === currentGroup,
309
+ sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, archived)) : []
310
+ });
311
+ }
312
+ return groups;
313
+ }
314
+ /**
315
+ * Derive the flat session list ("In one list" mode): every session — fork
316
+ * children included — as a top-level row, strictly newest-first. No grouping,
317
+ * no parent/child adjacency. Archived sessions are excluded unless the
318
+ * "show archived" view toggle is on.
319
+ * Content search lives outside this derivation
320
+ * (see {@link deriveSearchResults}).
321
+ * @param list - sessions list snapshot.
322
+ * @param archivedSessionIds - registry-global archive set.
323
+ * @param showArchived - "show archived" view toggle.
324
+ * @returns flat rows in render order.
325
+ */
326
+ function deriveFlat(list, archivedSessionIds, showArchived) {
327
+ const archived = new Set(archivedSessionIds);
328
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
329
+ const rows = [];
330
+ for (const id of list.ids) {
331
+ const s = list.byId[id];
332
+ if (s === void 0 || !sessionVisible(s, list.current, archived, showArchived)) continue;
333
+ rows.push(s);
334
+ }
335
+ rows.sort(byRecency);
336
+ return rows.map((session) => sessionNode(session, descendants, archived));
337
+ }
338
+ /**
339
+ * Merge immediate title/Workspace substring matches with ranked Host content
340
+ * matches. Local rows lead newest-first, content-only rows retain backend
341
+ * order, and duplicate sessions receive the backend snippet in place.
342
+ * Archived sessions are excluded unless the "show archived" view toggle is on.
343
+ * @param list - session metadata authority.
344
+ * @param workspaces - Workspace membership and display labels.
345
+ * @param query - caller text; surrounding whitespace is ignored.
346
+ * @param archivedSessionIds - registry-global archive set.
347
+ * @param content - ranked Host content-search page.
348
+ * @param limit - protocol-owned maximum merged row count.
349
+ * @param showArchived - "show archived" view toggle.
350
+ * @returns bounded deduplicated flat rows and a refine-query hint bit.
351
+ */
352
+ function deriveSearchResults(list, workspaces, query, archivedSessionIds, content, limit, showArchived) {
353
+ const q = query.trim().toLowerCase();
354
+ if (q === "") return {
355
+ items: [],
356
+ hasMore: false
357
+ };
358
+ const archived = new Set(archivedSessionIds);
359
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
360
+ const workspaceBySession = /* @__PURE__ */ new Map();
361
+ for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
362
+ const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
363
+ const contentBySession = /* @__PURE__ */ new Map();
364
+ for (const item of content.items) if (!contentBySession.has(item.sessionId)) contentBySession.set(item.sessionId, item);
365
+ const local = [];
366
+ for (const id of list.ids) {
367
+ const summary = list.byId[id];
368
+ if (summary === void 0 || summary.blank || !sessionVisible(summary, list.current, archived, showArchived)) continue;
369
+ if (sessionTitle(summary).toLowerCase().includes(q) || labelOf(summary).toLowerCase().includes(q)) local.push(summary);
370
+ }
371
+ local.sort(byRecency);
372
+ const ordered = [];
373
+ const included = /* @__PURE__ */ new Set();
374
+ const include = (summary) => {
375
+ if (included.has(summary.id)) return;
376
+ included.add(summary.id);
377
+ ordered.push(summary);
378
+ };
379
+ for (const summary of local) include(summary);
380
+ for (const item of content.items) {
381
+ const summary = list.byId[item.sessionId];
382
+ if (summary !== void 0 && !summary.blank && sessionVisible(summary, list.current, archived, showArchived)) include(summary);
383
+ }
384
+ return {
385
+ items: ordered.slice(0, limit).map((summary) => {
386
+ const match = contentBySession.get(summary.id);
387
+ return {
388
+ id: summary.id,
389
+ title: sessionTitle(summary),
390
+ workspace: labelOf(summary),
391
+ running: summary.running,
392
+ runningSubagentCount: descendants.get(summary.id)?.runningCount ?? 0,
393
+ archived: archived.has(summary.id),
394
+ ...summary.pendingInteraction === void 0 ? {} : { pendingInteraction: summary.pendingInteraction },
395
+ completed: summary.completed === true,
396
+ ...match === void 0 ? {} : { snippet: match.snippet }
397
+ };
398
+ }),
399
+ hasMore: content.hasMore || ordered.length > limit
400
+ };
401
+ }
402
+ /**
403
+ * Compact relative time for session rows, as a structured bucket the
404
+ * renderer localizes ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
405
+ * @param updatedAt - epoch ms of the session's last activity.
406
+ * @param now - current epoch ms (injected for pure rendering).
407
+ * @returns the row's trailing time bucket and magnitude.
408
+ */
409
+ function relativeTime(updatedAt, now) {
410
+ const MIN = 6e4;
411
+ const HOUR = 36e5;
412
+ const DAY = 864e5;
413
+ const diff = Math.max(0, now - updatedAt);
414
+ if (diff < MIN) return {
415
+ unit: "now",
416
+ n: 0
417
+ };
418
+ if (diff < HOUR) return {
419
+ unit: "minutes",
420
+ n: Math.floor(diff / MIN)
421
+ };
422
+ if (diff < DAY) return {
423
+ unit: "hours",
424
+ n: Math.floor(diff / HOUR)
425
+ };
426
+ if (diff < 30 * DAY) return {
427
+ unit: "days",
428
+ n: Math.floor(diff / DAY)
429
+ };
430
+ if (diff < 365 * DAY) return {
431
+ unit: "months",
432
+ n: Math.floor(diff / (30 * DAY))
433
+ };
434
+ return {
435
+ unit: "years",
436
+ n: Math.floor(diff / (365 * DAY))
437
+ };
438
+ }
439
+ //#endregion
440
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
441
+ const css$2 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding:0 8px;display:flex}.YDXeBa_projectRow:hover,.YDXeBa_sessionRow:hover,.YDXeBa_sessionRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.YDXeBa_searchResultRow:hover,.YDXeBa_searchResultRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultHeading{align-items:center;min-width:0;display:flex}.YDXeBa_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.YDXeBa_searchResultWorkspace,.YDXeBa_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.YDXeBa_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}.YDXeBa_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}.YDXeBa_projectRow{box-sizing:border-box;align-items:center;height:34px}.YDXeBa_projectRow .YDXeBa_rowActions{height:20px}.YDXeBa_sessionRow{height:32px;animation:YDXeBa_row-in .15s var(--ds-ease-in-out);gap:0}.YDXeBa_sessionRow .YDXeBa_title{margin:0 6px 0 4px}.YDXeBa_flatSessionRowWithoutStatus .YDXeBa_title{margin-left:0}@keyframes YDXeBa_row-in{0%{opacity:0}}.YDXeBa_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.YDXeBa_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.YDXeBa_folderActive{color:var(--dsw-alias-state-business-primary)}.YDXeBa_projectRow .YDXeBa_chevron{display:none}.YDXeBa_projectRow:hover .YDXeBa_chevron{display:inline-flex}.YDXeBa_projectRow:hover .YDXeBa_folder{display:none}.YDXeBa_arrow{transition:transform .15s var(--ds-ease-in-out)}.YDXeBa_arrowOpen{transform:rotate(90deg)}.YDXeBa_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.YDXeBa_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_renameInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.YDXeBa_sessionRow .YDXeBa_title{flex:1}.YDXeBa_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.YDXeBa_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:20px}.YDXeBa_dot{flex:none}.YDXeBa_rowActions{flex:none;align-items:center;gap:12px;display:none}.YDXeBa_projectRow:hover .YDXeBa_rowActions,.YDXeBa_sessionRow:hover .YDXeBa_rowActions,.YDXeBa_projectRow.YDXeBa_menuOpen .YDXeBa_rowActions,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_rowActions{display:inline-flex}.YDXeBa_sessionRow:hover .YDXeBa_time,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_time{display:none}.YDXeBa_projectRow.YDXeBa_menuOpen,.YDXeBa_sessionRow.YDXeBa_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_sessionRow.YDXeBa_dropBefore,.YDXeBa_sessionRow.YDXeBa_dropAfter{position:relative}.YDXeBa_sessionRow.YDXeBa_dropBefore:before,.YDXeBa_sessionRow.YDXeBa_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-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(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-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}.YDXeBa_sessionRow.YDXeBa_dropBefore:before{top:-7px}.YDXeBa_sessionRow.YDXeBa_dropAfter:after{bottom:-7px}.YDXeBa_hoverContent{flex-direction:column;gap:8px;display:flex}.YDXeBa_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.YDXeBa_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.YDXeBa_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.YDXeBa_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.YDXeBa_iconButton{cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.YDXeBa_iconButton:hover{color:var(--dsw-alias-label-primary)}.YDXeBa_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){.YDXeBa_sessionRow,.YDXeBa_arrow{transition:none;animation:none}}";
442
+ const tagId$2 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
443
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
444
+ const tag = document.createElement("style");
445
+ tag.dataset.plugin = "@michengai/dsh-archive-manager";
446
+ tag.dataset.pluginCss = tagId$2;
447
+ tag.textContent = css$2;
448
+ document.head.appendChild(tag);
449
+ }
450
+ var Rows_module_css_default = {
451
+ "hoverTitle": "YDXeBa_hoverTitle",
452
+ "title": "YDXeBa_title",
453
+ "hoverContent": "YDXeBa_hoverContent",
454
+ "dropAfter": "YDXeBa_dropAfter",
455
+ "renameInput": "YDXeBa_renameInput",
456
+ "dot": "YDXeBa_dot",
457
+ "hoverTime": "YDXeBa_hoverTime",
458
+ "iconButton": "YDXeBa_iconButton",
459
+ "flatSessionRowWithoutStatus": "YDXeBa_flatSessionRowWithoutStatus",
460
+ "row-in": "YDXeBa_row-in",
461
+ "folder": "YDXeBa_folder",
462
+ "menuOpen": "YDXeBa_menuOpen",
463
+ "selected": "YDXeBa_selected",
464
+ "searchResultHeading": "YDXeBa_searchResultHeading",
465
+ "searchResultWorkspace": "YDXeBa_searchResultWorkspace",
466
+ "visuallyHidden": "YDXeBa_visuallyHidden",
467
+ "projectRow": "YDXeBa_projectRow",
468
+ "hoverStatus": "YDXeBa_hoverStatus",
469
+ "arrowOpen": "YDXeBa_arrowOpen",
470
+ "rowActions": "YDXeBa_rowActions",
471
+ "chevron": "YDXeBa_chevron",
472
+ "arrow": "YDXeBa_arrow",
473
+ "searchResultTitle": "YDXeBa_searchResultTitle",
474
+ "searchResultMeta": "YDXeBa_searchResultMeta",
475
+ "slot": "YDXeBa_slot",
476
+ "folderActive": "YDXeBa_folderActive",
477
+ "time": "YDXeBa_time",
478
+ "sessionRow": "YDXeBa_sessionRow",
479
+ "meta": "YDXeBa_meta",
480
+ "dropBefore": "YDXeBa_dropBefore",
481
+ "searchResultSnippet": "YDXeBa_searchResultSnippet",
482
+ "projectText": "YDXeBa_projectText",
483
+ "hoverPath": "YDXeBa_hoverPath",
484
+ "searchResultRow": "YDXeBa_searchResultRow"
485
+ };
486
+ //#endregion
487
+ //#region lib/types/client/rows/Rows.js
488
+ /**
489
+ * Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
490
+ * all data and callbacks arrive via props. Hover swaps (folder->chevron,
491
+ * time->ellipsis, action buttons) are CSS-only. Row ... menus are visual-only
492
+ * except workspace Rename/Delete and session Rename/Fork/Archive; the session
493
+ * and workspace hover cards are suppressed while a menu is open.
494
+ */
495
+ /** Row display title: blank rows show the localized New Session label. */
496
+ function displayTitle(node, t) {
497
+ return node.blank ? t("session.new") : node.title;
498
+ }
499
+ /** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
500
+ function timeLabel(updatedAt, now, t) {
501
+ const { unit, n } = relativeTime(updatedAt, now);
502
+ return unit === "now" ? t("time.now") : t(`time.${unit}`, { n });
503
+ }
504
+ /** 归档页使用绝对时间,方便在历史记录中准确辨识会话。 */
505
+ function archiveTimeLabel(updatedAt, t) {
506
+ const date = new Date(updatedAt);
507
+ const time = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
508
+ return t("archives.timestamp", { date: t("date.ymd", { y: date.getFullYear(), m: date.getMonth() + 1, d: date.getDate() }), time });
509
+ }
510
+ /** Hover-card variant: distances wrap in the ago template; the now bucket stays bare (no "now ago"). */
511
+ function hoverTimeLabel(updatedAt, now, t) {
512
+ const { unit, n } = relativeTime(updatedAt, now);
513
+ return unit === "now" ? t("time.now") : t("time.ago", { t: t(`time.${unit}`, { n }) });
514
+ }
515
+ /**
516
+ * Absolute creation time through the dictionary's date template (the message
517
+ * clock pattern): `toLocaleString` would follow the browser language, not the
518
+ * app locale, and produce mixed-language text after a switch.
519
+ */
520
+ function createdLabel(createdAt, t) {
521
+ const d = new Date(createdAt);
522
+ const pad2 = (v) => String(v).padStart(2, "0");
523
+ return t("hover.created", { time: `${t("date.ymd", {
524
+ y: d.getFullYear(),
525
+ m: d.getMonth() + 1,
526
+ d: d.getDate()
527
+ })} ${pad2(d.getHours())}:${pad2(d.getMinutes())}` });
528
+ }
529
+ /** Hover-card body: workspace title, full directory path, absolute creation time. */
530
+ function WorkspaceHoverContent({ label, cwd, createdAt, t }) {
531
+ return (0, react_jsx_runtime.jsxs)("div", {
532
+ className: Rows_module_css_default.hoverContent,
533
+ children: [
534
+ (0, react_jsx_runtime.jsx)("div", {
535
+ className: Rows_module_css_default.hoverTitle,
536
+ children: label
537
+ }),
538
+ (0, react_jsx_runtime.jsx)("div", {
539
+ className: Rows_module_css_default.hoverPath,
540
+ children: cwd
541
+ }),
542
+ (0, react_jsx_runtime.jsx)("div", {
543
+ className: Rows_module_css_default.hoverTime,
544
+ children: createdLabel(createdAt, t)
545
+ })
546
+ ]
547
+ });
548
+ }
549
+ /** Pointer-position half of a row (insert line above or below). */
550
+ function rowHalf(e) {
551
+ const rect = e.currentTarget.getBoundingClientRect();
552
+ return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
553
+ }
554
+ /**
555
+ * Project (workspace) header row: folder + title;
556
+ * hover reveals the chevron and create button, and dwelling on a real
557
+ * Workspace shows its hover card (the ungrouped bucket has none).
558
+ * `containsCurrent` arrives on the node (derivation fact, no renderer scan).
559
+ * @param props.group - derived group node.
560
+ * @param props.onToggle - expand/collapse the group.
561
+ * @param props.onCreate - start a frontend Session inside this Workspace.
562
+ * @param props.drag - optional workspace-row drag wiring.
563
+ * @param props.t - the browser root's locale seat.
564
+ * @returns the row element.
565
+ */
566
+ function ProjectRowItem({ group, onToggle, onCreate, actions, drag, t }) {
567
+ const row = group;
568
+ const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
569
+ const active = group.expanded && group.containsCurrent;
570
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
571
+ const workspaceMenuItems = [{
572
+ id: "rename",
573
+ label: t("rename"),
574
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
575
+ }, {
576
+ id: "delete",
577
+ label: t("delete.workspace"),
578
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
579
+ danger: true
580
+ }];
581
+ const ownRow = (0, react_jsx_runtime.jsxs)("div", {
582
+ className: clsx(Rows_module_css_default.projectRow, menuOpen && Rows_module_css_default.menuOpen),
583
+ role: "treeitem",
584
+ "aria-expanded": row.expanded,
585
+ onClick: onToggle,
586
+ draggable: drag !== void 0,
587
+ onDragStart: drag === void 0 ? void 0 : (e) => {
588
+ e.dataTransfer.effectAllowed = "move";
589
+ e.dataTransfer.setData("text/plain", row.key);
590
+ drag.start();
591
+ },
592
+ onDragEnd: drag?.end,
593
+ children: [
594
+ (0, react_jsx_runtime.jsx)("span", {
595
+ className: clsx(Rows_module_css_default.slot, Rows_module_css_default.folder, active && Rows_module_css_default.folderActive),
596
+ children: row.expanded ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {}) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {})
597
+ }),
598
+ (0, react_jsx_runtime.jsx)("span", {
599
+ className: clsx(Rows_module_css_default.slot, Rows_module_css_default.chevron),
600
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTriangleRightFill14, { className: clsx(Rows_module_css_default.arrow, row.expanded && Rows_module_css_default.arrowOpen) })
601
+ }),
602
+ (0, react_jsx_runtime.jsx)("span", {
603
+ className: Rows_module_css_default.projectText,
604
+ children: (0, react_jsx_runtime.jsx)("span", {
605
+ className: Rows_module_css_default.title,
606
+ children: label
607
+ })
608
+ }),
609
+ (0, react_jsx_runtime.jsxs)("span", {
610
+ className: Rows_module_css_default.rowActions,
611
+ children: [actions !== void 0 && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
612
+ open: menuOpen,
613
+ onClose: () => {
614
+ setMenuOpen(false);
615
+ },
616
+ items: workspaceMenuItems,
617
+ onSelect: (id) => {
618
+ setMenuOpen(false);
619
+ /* v8 ignore next -- workspaceMenuItems carries exactly these two rows today. */
620
+ if (id !== "rename" && id !== "delete") return;
621
+ if (id === "rename") actions.rename();
622
+ else actions.delete();
623
+ },
624
+ portal: true,
625
+ closeOnPointerLeave: true,
626
+ anchor: (0, react_jsx_runtime.jsx)("button", {
627
+ type: "button",
628
+ className: Rows_module_css_default.iconButton,
629
+ "aria-label": t("actions.workspace.aria", { name: label }),
630
+ onClick: (e) => {
631
+ e.stopPropagation();
632
+ setMenuOpen((v) => !v);
633
+ },
634
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
635
+ })
636
+ }), (0, react_jsx_runtime.jsx)("button", {
637
+ type: "button",
638
+ className: Rows_module_css_default.iconButton,
639
+ "aria-label": t("actions.newSession.aria", { name: label }),
640
+ onClick: (e) => {
641
+ e.stopPropagation();
642
+ onCreate();
643
+ },
644
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, {})
645
+ })]
646
+ })
647
+ ]
648
+ });
649
+ if (row.createdAt === void 0) return ownRow;
650
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
651
+ anchor: ownRow,
652
+ content: (0, react_jsx_runtime.jsx)(WorkspaceHoverContent, {
653
+ label: row.label,
654
+ cwd: row.cwd,
655
+ createdAt: row.createdAt,
656
+ t
657
+ }),
658
+ disabled: menuOpen,
659
+ copyText: row.cwd,
660
+ copyLabel: t("copy"),
661
+ copiedLabel: t("hover.copied")
662
+ });
663
+ }
664
+ /* v8 ignore next 3 -- closed-union backstop; only reached if the status is forged */
665
+ function assertNever(value) {
666
+ throw new Error(`unknown pending interaction: ${String(value)}`);
667
+ }
668
+ /**
669
+ * Session status presentation; pending interaction is primary and live activity
670
+ * outranks completion reminders.
671
+ */
672
+ function sessionStatuses(node, t) {
673
+ const subagents = node.runningSubagentCount === 0 ? void 0 : {
674
+ state: "ongoing",
675
+ label: t(node.runningSubagentCount === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: node.runningSubagentCount })
676
+ };
677
+ let pending;
678
+ switch (node.pendingInteraction) {
679
+ case "approval":
680
+ pending = {
681
+ state: "warning",
682
+ label: t("status.waitingApproval")
683
+ };
684
+ break;
685
+ case "plan-review":
686
+ pending = {
687
+ state: "warning",
688
+ label: t("status.planReview")
689
+ };
690
+ break;
691
+ case "question":
692
+ pending = {
693
+ state: "warning",
694
+ label: t("status.waitingAnswer")
695
+ };
696
+ break;
697
+ case void 0: break;
698
+ /* v8 ignore next -- closed PendingInteractionStatus union */
699
+ default: return assertNever(node.pendingInteraction);
700
+ }
701
+ if (pending !== void 0) return subagents === void 0 ? [pending] : [pending, subagents];
702
+ if (node.running) {
703
+ const primary = {
704
+ state: "ongoing",
705
+ label: t("status.running")
706
+ };
707
+ return subagents === void 0 ? [primary] : [primary, subagents];
708
+ }
709
+ if (subagents !== void 0) return [subagents];
710
+ if (node.completed) return [{
711
+ state: "done",
712
+ label: t("status.completed")
713
+ }];
714
+ return [{
715
+ state: "done",
716
+ label: t("status.idle")
717
+ }];
718
+ }
719
+ /** Primary status dot plus every status's screen-reader label, shared by the search and session rows. */
720
+ function SessionStatusDots({ statuses }) {
721
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: statuses[0].state }), statuses.map((status) => (0, react_jsx_runtime.jsx)("span", {
722
+ className: Rows_module_css_default.visuallyHidden,
723
+ children: status.label
724
+ }, status.label))] });
725
+ }
726
+ /** Hover-card body: full title, relative time, and every relevant live status. */
727
+ function SessionHoverContent({ node, now, t }) {
728
+ const statuses = sessionStatuses(node, t);
729
+ return (0, react_jsx_runtime.jsxs)("div", {
730
+ className: Rows_module_css_default.hoverContent,
731
+ children: [
732
+ (0, react_jsx_runtime.jsx)("div", {
733
+ className: Rows_module_css_default.hoverTitle,
734
+ children: displayTitle(node, t)
735
+ }),
736
+ !node.blank && (0, react_jsx_runtime.jsx)("div", {
737
+ className: Rows_module_css_default.hoverTime,
738
+ children: hoverTimeLabel(node.updatedAt, now, t)
739
+ }),
740
+ statuses.map((status) => (0, react_jsx_runtime.jsxs)("div", {
741
+ className: Rows_module_css_default.hoverStatus,
742
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: status.state }), (0, react_jsx_runtime.jsx)("span", { children: status.label })]
743
+ }, status.label))
744
+ ]
745
+ });
746
+ }
747
+ /**
748
+ * One flat search result: title, Workspace context, and optional content
749
+ * excerpt. Search navigation opens the session only; it does not address an
750
+ * event inside the conversation. Archived results render with the archived
751
+ * treatment; the browser-level open guard keeps them from opening.
752
+ * @param props.result - merged local/content search row.
753
+ * @param props.currentId - selected session id.
754
+ * @param props.onOpen - open the selected session.
755
+ * @param props.t - Workspace-browser translation seat.
756
+ * @returns the result button.
757
+ */
758
+ function SearchResultItem({ result, currentId, onOpen, t }) {
759
+ const selected = result.id === currentId;
760
+ const statuses = sessionStatuses(result, t);
761
+ const primaryStatus = statuses[0];
762
+ return (0, react_jsx_runtime.jsxs)("button", {
763
+ type: "button",
764
+ className: clsx(Rows_module_css_default.searchResultRow, selected && Rows_module_css_default.selected, result.archived === true && ARCHIVED_CLASSES.row),
765
+ role: "treeitem",
766
+ "aria-selected": selected,
767
+ onClick: () => {
768
+ onOpen(result.id);
769
+ },
770
+ children: [(0, react_jsx_runtime.jsxs)("span", {
771
+ className: Rows_module_css_default.searchResultHeading,
772
+ children: [(0, react_jsx_runtime.jsx)("span", {
773
+ className: Rows_module_css_default.slot,
774
+ children: (primaryStatus.state !== "done" || result.completed) && (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
775
+ }), result.archived === true && (0, react_jsx_runtime.jsx)("span", {
776
+ className: ARCHIVED_CLASSES.badge,
777
+ children: t("archived.badge")
778
+ }), (0, react_jsx_runtime.jsx)("span", {
779
+ className: clsx(Rows_module_css_default.searchResultTitle, result.archived === true && ARCHIVED_CLASSES.title),
780
+ children: result.title
781
+ })]
782
+ }), (0, react_jsx_runtime.jsxs)("span", {
783
+ className: Rows_module_css_default.searchResultMeta,
784
+ children: [(0, react_jsx_runtime.jsx)("span", {
785
+ className: Rows_module_css_default.searchResultWorkspace,
786
+ children: result.workspace
787
+ }), result.snippet !== void 0 && (0, react_jsx_runtime.jsx)("span", {
788
+ className: Rows_module_css_default.searchResultSnippet,
789
+ children: result.snippet
790
+ })]
791
+ })]
792
+ });
793
+ }
794
+ /**
795
+ * One top-level 34px session row: status dot (pending user interaction outranks
796
+ * own or descendant activity), title, relative time, and the row actions menu.
797
+ * Archived rows get the archived treatment (red title + tinted background +
798
+ * "Archived" badge), never open on click, and their menu is only
799
+ * [Unarchive, Delete session].
800
+ * @param props.node - derived session node.
801
+ * @param props.currentId - selected session id (row highlight).
802
+ * @param props.now - epoch ms for relative-time formatting.
803
+ * @param props.onOpen - open a session by id.
804
+ * @param props.onRename - open the session rename dialog (id + current title).
805
+ * @param props.onFork - fork a session at its last completed turn.
806
+ * @param props.onArchive - archive a session by id.
807
+ * @param props.onUnarchive - unarchive a session by id.
808
+ * @param props.onDeleteSession - request the delete-session confirmation.
809
+ * @param props.drag - optional draggable-row wiring.
810
+ * @param props.flat - omit the empty status slot in the hierarchy-free flat list.
811
+ * @param props.t - the browser root's locale seat.
812
+ * @returns the session row.
813
+ */
814
+ function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onUnarchive, onDeleteSession, drag, flat = false, t }) {
815
+ const row = node;
816
+ const title = displayTitle(node, t);
817
+ const selected = node.id === currentId;
818
+ const archived = row.archived === true;
819
+ const statuses = sessionStatuses(node, t);
820
+ const showStatus = statuses[0].state !== "done" || row.completed;
821
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
822
+ const sessionMenuItems = archived ? [
823
+ {
824
+ id: "unarchive",
825
+ label: t("menu.unarchive"),
826
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
827
+ },
828
+ {
829
+ id: "delete-session",
830
+ label: t("menu.deleteSession"),
831
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
832
+ danger: true
833
+ }
834
+ ] : [
835
+ {
836
+ id: "rename",
837
+ label: t("rename"),
838
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
839
+ },
840
+ {
841
+ id: "fork",
842
+ label: t("menu.fork"),
843
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {})
844
+ },
845
+ {
846
+ id: "archive",
847
+ label: t("menu.archiveSession"),
848
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
849
+ },
850
+ {
851
+ id: "delete-session",
852
+ label: t("menu.deleteSession"),
853
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
854
+ danger: true
855
+ }
856
+ ];
857
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
858
+ anchor: (0, react_jsx_runtime.jsxs)("div", {
859
+ className: clsx(Rows_module_css_default.sessionRow, selected && Rows_module_css_default.selected, menuOpen && Rows_module_css_default.menuOpen, flat && !showStatus && Rows_module_css_default.flatSessionRowWithoutStatus, archived && ARCHIVED_CLASSES.row, drag?.marker === "before" && Rows_module_css_default.dropBefore, drag?.marker === "after" && Rows_module_css_default.dropAfter),
860
+ role: "treeitem",
861
+ "aria-selected": selected,
862
+ onClick: () => {
863
+ if (archived) return;
864
+ onOpen(node.id);
865
+ },
866
+ draggable: drag !== void 0,
867
+ onDragStart: drag === void 0 ? void 0 : (e) => {
868
+ e.dataTransfer.effectAllowed = "move";
869
+ e.dataTransfer.setData("text/plain", node.id);
870
+ drag.start();
871
+ },
872
+ onDragEnd: drag?.end,
873
+ onDragOver: drag === void 0 ? void 0 : (e) => {
874
+ if (!drag.active) return;
875
+ e.preventDefault();
876
+ e.dataTransfer.dropEffect = "move";
877
+ drag.hover(rowHalf(e));
878
+ },
879
+ onDrop: drag === void 0 ? void 0 : (e) => {
880
+ if (!drag.active) return;
881
+ e.preventDefault();
882
+ drag.drop(rowHalf(e));
883
+ },
884
+ children: [
885
+ (!flat || showStatus) && (0, react_jsx_runtime.jsx)("span", {
886
+ className: Rows_module_css_default.slot,
887
+ children: showStatus && (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
888
+ }),
889
+ archived && (0, react_jsx_runtime.jsx)("span", {
890
+ className: ARCHIVED_CLASSES.badge,
891
+ children: t("archived.badge")
892
+ }),
893
+ archived ? (0, react_jsx_runtime.jsxs)("span", {
894
+ className: ARCHIVED_CLASSES.content,
895
+ children: [(0, react_jsx_runtime.jsx)("span", {
896
+ className: clsx(Rows_module_css_default.title, ARCHIVED_CLASSES.title),
897
+ children: title
898
+ }), !row.blank && (0, react_jsx_runtime.jsx)("span", {
899
+ className: ARCHIVED_CLASSES.meta,
900
+ children: timeLabel(row.updatedAt, now, t)
901
+ })]
902
+ }) : (0, react_jsx_runtime.jsx)("span", {
903
+ className: Rows_module_css_default.title,
904
+ children: title
905
+ }),
906
+ !row.blank && (0, react_jsx_runtime.jsx)("span", {
907
+ className: Rows_module_css_default.time,
908
+ children: timeLabel(row.updatedAt, now, t)
909
+ }),
910
+ archived && !row.blank && (0, react_jsx_runtime.jsxs)("span", {
911
+ className: ARCHIVED_CLASSES.actions,
912
+ onClick: (e) => {
913
+ e.stopPropagation();
914
+ },
915
+ children: [(0, react_jsx_runtime.jsx)("button", {
916
+ type: "button",
917
+ className: ARCHIVED_CLASSES.delete,
918
+ "aria-label": t("menu.deleteSession"),
919
+ onClick: () => onDeleteSession(node.id, row.title),
920
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {})
921
+ }), (0, react_jsx_runtime.jsx)("button", {
922
+ type: "button",
923
+ className: ARCHIVED_CLASSES.unarchive,
924
+ onClick: () => onUnarchive(node.id),
925
+ children: t("menu.unarchive")
926
+ })]
927
+ }),
928
+ !row.blank && (0, react_jsx_runtime.jsx)("span", {
929
+ className: Rows_module_css_default.rowActions,
930
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
931
+ open: menuOpen,
932
+ onClose: () => {
933
+ setMenuOpen(false);
934
+ },
935
+ items: sessionMenuItems,
936
+ onSelect: (id) => {
937
+ setMenuOpen(false);
938
+ if (id === "rename") onRename(node.id, row.title);
939
+ if (id === "fork") onFork(node.id);
940
+ if (id === "archive") onArchive(node.id);
941
+ if (id === "unarchive") onUnarchive(node.id);
942
+ if (id === "delete-session") onDeleteSession(node.id, row.title);
943
+ },
944
+ portal: true,
945
+ closeOnPointerLeave: true,
946
+ anchor: (0, react_jsx_runtime.jsx)("button", {
947
+ type: "button",
948
+ className: Rows_module_css_default.iconButton,
949
+ "aria-label": t("actions.session.aria", { name: title }),
950
+ onClick: (e) => {
951
+ e.stopPropagation();
952
+ setMenuOpen((v) => !v);
953
+ },
954
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
955
+ })
956
+ })
957
+ })
958
+ ]
959
+ }),
960
+ content: (0, react_jsx_runtime.jsx)(SessionHoverContent, {
961
+ node,
962
+ now,
963
+ t
964
+ }),
965
+ disabled: menuOpen || drag?.active === true,
966
+ copyText: row.blank ? void 0 : row.title,
967
+ copyLabel: t("copy"),
968
+ copiedLabel: t("hover.copied")
969
+ });
970
+ }
971
+ //#endregion
972
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/WorkspacePicker.module.css.mjs
973
+ const css$1 = "._G5b-a_modalAction{min-width:72px}._G5b-a_modalError,._G5b-a_menuStatus{margin-top:8px;font-size:12px;line-height:18px}._G5b-a_modalError{color:var(--dsw-alias-state-error-primary)}._G5b-a_menuStatus{color:var(--dsw-alias-label-secondary)}";
974
+ const tagId$1 = "@deepseek-ai/dsh-client-ui-workspace/WorkspacePicker.module.css";
975
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
976
+ const tag = document.createElement("style");
977
+ tag.dataset.plugin = "@michengai/dsh-archive-manager";
978
+ tag.dataset.pluginCss = tagId$1;
979
+ tag.textContent = css$1;
980
+ document.head.appendChild(tag);
981
+ }
982
+ var WorkspacePicker_module_css_default = {
983
+ "modalAction": "_G5b-a_modalAction",
984
+ "menuStatus": "_G5b-a_menuStatus",
985
+ "modalError": "_G5b-a_modalError"
986
+ };
987
+ //#endregion
988
+ //#region lib/types/client/WorkspacePicker.js
989
+ const ADD_WORKSPACE = "::add-workspace";
990
+ /**
991
+ * Render the pick menu plus the adoption error dialog.
992
+ * @param props - owner-controlled flow props.
993
+ * @returns menu + dialog elements.
994
+ */
995
+ function WorkspacePickFlow({ t, open, anchorRef, useWorkspaces, createWorkspace, useDirectoryFlow, renderDirectoryFlow, onPick, onClose, addOnly = false, side = "bottom", selectedId }) {
996
+ const workspaceSnapshot = useWorkspaces((state) => state);
997
+ const workspaces = workspaceSnapshot.items;
998
+ const getAnchorRect = (0, react.useCallback)(() => anchorRef?.current?.getBoundingClientRect() ?? null, [anchorRef]);
999
+ const [errorOpen, setErrorOpen] = (0, react.useState)(false);
1000
+ const [modalError, setModalError] = (0, react.useState)(null);
1001
+ const [flowOpen, setFlowOpen] = (0, react.useState)(false);
1002
+ const [pickingFolder, setPickingFolder] = (0, react.useState)(false);
1003
+ const flowBusy = flowOpen || pickingFolder;
1004
+ const flowAvailable = useDirectoryFlow((occupied) => occupied);
1005
+ (0, react.useEffect)(() => {
1006
+ if (flowOpen && !flowAvailable) setFlowOpen(false);
1007
+ }, [flowOpen, flowAvailable]);
1008
+ const addEntries = flowAvailable ? [{
1009
+ id: ADD_WORKSPACE,
1010
+ label: t("menu.addWorkspace"),
1011
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 }),
1012
+ disabled: flowBusy
1013
+ }] : [];
1014
+ const pinAdd = !addOnly && workspaces.length > 0;
1015
+ const items = pinAdd ? workspaces.map((workspace) => ({
1016
+ id: workspace.workspaceId,
1017
+ label: workspace.title,
1018
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 }),
1019
+ disabled: flowBusy
1020
+ })) : addEntries;
1021
+ const menuIsEmpty = items.length === 0;
1022
+ const closeModal = () => {
1023
+ setErrorOpen(false);
1024
+ setModalError(null);
1025
+ };
1026
+ /** Adopt a picked directory; failures land in the folder-error dialog (Choose again reopens the flow). */
1027
+ const adoptDirectory = (path) => createWorkspace({ path }).then((workspace) => {
1028
+ setFlowOpen(false);
1029
+ onPick(workspace.workspaceId);
1030
+ }).catch((reason) => {
1031
+ setModalError(reason instanceof Error ? reason.message : String(reason));
1032
+ setFlowOpen(false);
1033
+ setErrorOpen(true);
1034
+ });
1035
+ const openDirectoryFlow = (0, react.useCallback)(() => {
1036
+ onClose();
1037
+ setErrorOpen(false);
1038
+ setModalError(null);
1039
+ setFlowOpen(true);
1040
+ }, [onClose]);
1041
+ const listSettled = addOnly || workspaceSnapshot.phase === "ready";
1042
+ const addIsTheOnlyEntry = !pinAdd && listSettled && addEntries.length === 1;
1043
+ (0, react.useEffect)(() => {
1044
+ if (open && addIsTheOnlyEntry && !flowBusy) openDirectoryFlow();
1045
+ }, [
1046
+ open,
1047
+ addIsTheOnlyEntry,
1048
+ flowBusy,
1049
+ openDirectoryFlow
1050
+ ]);
1051
+ /** Owner side of the flow conversation: adopt keeps the flow open (busy) until the Host answers. */
1052
+ const flowOwner = {
1053
+ open: flowOpen,
1054
+ busy: pickingFolder,
1055
+ onPicked: (path) => {
1056
+ setPickingFolder(true);
1057
+ adoptDirectory(path).finally(() => {
1058
+ setPickingFolder(false);
1059
+ });
1060
+ },
1061
+ onCancel: () => {
1062
+ setFlowOpen(false);
1063
+ },
1064
+ onError: (message) => {
1065
+ setFlowOpen(false);
1066
+ setModalError(message);
1067
+ setErrorOpen(true);
1068
+ }
1069
+ };
1070
+ const handleSelect = (id) => {
1071
+ if (id === ADD_WORKSPACE) {
1072
+ openDirectoryFlow();
1073
+ return;
1074
+ }
1075
+ onPick(id);
1076
+ };
1077
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1078
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1079
+ open: open && !addIsTheOnlyEntry && !menuIsEmpty,
1080
+ anchor: null,
1081
+ items,
1082
+ ...pinAdd ? { footer: addEntries } : {},
1083
+ selectedId,
1084
+ onSelect: handleSelect,
1085
+ onClose,
1086
+ side,
1087
+ portal: true,
1088
+ getAnchorRect
1089
+ }),
1090
+ open && !addIsTheOnlyEntry && !menuIsEmpty && workspaceSnapshot.phase === "pending" && (0, react_jsx_runtime.jsx)("div", {
1091
+ className: WorkspacePicker_module_css_default.menuStatus,
1092
+ role: "status",
1093
+ children: t("picker.loading")
1094
+ }),
1095
+ renderDirectoryFlow(flowOwner),
1096
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1097
+ open: errorOpen,
1098
+ onClose: closeModal,
1099
+ closeLabel: t("close"),
1100
+ title: t("folderError.title"),
1101
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1102
+ variant: "outline",
1103
+ className: WorkspacePicker_module_css_default.modalAction,
1104
+ onClick: closeModal,
1105
+ children: t("cancel")
1106
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1107
+ variant: "primary",
1108
+ className: WorkspacePicker_module_css_default.modalAction,
1109
+ disabled: !flowAvailable,
1110
+ onClick: openDirectoryFlow,
1111
+ children: t("folderError.retry")
1112
+ })] }),
1113
+ children: (0, react_jsx_runtime.jsx)("div", {
1114
+ className: WorkspacePicker_module_css_default.modalError,
1115
+ role: "alert",
1116
+ children: modalError
1117
+ })
1118
+ })
1119
+ ] });
1120
+ }
1121
+ /**
1122
+ * The conversation empty-state registration: adapts the owner share to the
1123
+ * core flow (all state and semantics live in the flow / the owner).
1124
+ * @param props - empty-state slot props (owner share + injected creation callback).
1125
+ * @returns the flow element.
1126
+ */
1127
+ function WorkspacePicker({ open, anchorRef, useWorkspaces, selectedId, onPick, onClose, createWorkspace, useDirectoryFlow, renderSlot, t }) {
1128
+ return (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
1129
+ t,
1130
+ open,
1131
+ anchorRef,
1132
+ useWorkspaces,
1133
+ createWorkspace,
1134
+ useDirectoryFlow,
1135
+ renderDirectoryFlow: (owner) => renderSlot("conversation.hero.workspace.directoryFlow", owner),
1136
+ selectedId,
1137
+ onPick,
1138
+ onClose
1139
+ });
1140
+ }
1141
+ //#endregion
1142
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css.mjs
1143
+ const css = ".qDHVXG_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:8px;--dsh-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--dsh-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.qDHVXG_root.qDHVXG_rail{padding-right:0}.qDHVXG_iconButton{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.qDHVXG_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_sectionHeader{box-sizing:border-box;height:36px;color:var(--dsw-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}.qDHVXG_root:not(.qDHVXG_rail) .qDHVXG_sectionHeader{margin-top:2px;margin-right:-4px}.qDHVXG_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}.qDHVXG_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.qDHVXG_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}.qDHVXG_searchSlotExpanded{max-width:100%;padding-left:0}.qDHVXG_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}.qDHVXG_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.qDHVXG_search{box-sizing:border-box;cursor:text;width:100%;height:28px;color:var(--dsw-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}.qDHVXG_searchExpanded{border:1px solid var(--dsw-alias-border-l2);width:calc(100% + 4px);height:30px;color:var(--dsw-alias-label-caption);background:0 0;border-radius:10px;margin-inline:-2px;padding:0 4px 0 0}.qDHVXG_searchButton{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}.qDHVXG_searchExpanded .qDHVXG_searchButton{width:28px;height:30px}.qDHVXG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_searchExpanded .qDHVXG_searchButton:hover{background:0 0}.qDHVXG_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--dsw-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}.qDHVXG_searchExpanded .qDHVXG_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.qDHVXG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.qDHVXG_clearButton{cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.qDHVXG_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_rail .qDHVXG_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.qDHVXG_rail .qDHVXG_headerActions{max-width:none}.qDHVXG_rail .qDHVXG_iconButton{width:36px;height:36px;color:var(--dsw-alias-label-primary)}.qDHVXG_rail .qDHVXG_search{background:0 0;border-color:#0000;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.qDHVXG_rail .qDHVXG_searchButton{width:36px;height:36px;color:var(--dsw-alias-label-primary)}.qDHVXG_rail .qDHVXG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.qDHVXG_rail .qDHVXG_listArea{margin-left:0;margin-right:0;padding-left:0}.qDHVXG_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.qDHVXG_fade{left:0;right:var(--dsh-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--dsw-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}.qDHVXG_wide{animation:qDHVXG_wide-in .2s var(--ds-ease-in-out)}@keyframes qDHVXG_wide-in{0%{opacity:0}}.qDHVXG_list{min-height:0;margin-left:-4px;margin-right:var(--dsh-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.qDHVXG_flatList>*+*,.qDHVXG_searchTree>[role=treeitem]+[role=treeitem],.qDHVXG_groupSection>*+*{margin-top:2px}.qDHVXG_searchStatus,.qDHVXG_searchWarning{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.qDHVXG_searchWarning{color:var(--dsw-alias-label-secondary)}.qDHVXG_groupSection{position:relative}.qDHVXG_groupSection+.qDHVXG_groupSection{margin-top:4px}.qDHVXG_listTopDropIndicator,.qDHVXG_workspaceDropBefore:before,.qDHVXG_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-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(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-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}.qDHVXG_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.qDHVXG_listTopDropActive>.qDHVXG_workspaceDropBefore:first-child:before{display:none}.qDHVXG_workspaceDropBefore:before{top:-8px}.qDHVXG_workspaceDropAfter:after{bottom:-8px}.qDHVXG_sessionOverflowButton{cursor:pointer;text-align:left;width:100%;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:8px;padding:0 12px 0 28px;font-size:12px}.qDHVXG_groupSection>.qDHVXG_sessionOverflowButton{margin-top:0}.qDHVXG_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.qDHVXG_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.qDHVXG_renameInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:22px;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.qDHVXG_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.qDHVXG_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.qDHVXG_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.qDHVXG_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.qDHVXG_wide{animation:none}.qDHVXG_search,.qDHVXG_sectionLabel,.qDHVXG_searchSlot,.qDHVXG_searchInput,.qDHVXG_headerActions{transition:none}}";
1144
+ const tagId = "@deepseek-ai/dsh-client-ui-workspace/WorkspaceBrowser.module.css";
1145
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1146
+ const tag = document.createElement("style");
1147
+ tag.dataset.plugin = "@michengai/dsh-archive-manager";
1148
+ tag.dataset.pluginCss = tagId;
1149
+ tag.textContent = css;
1150
+ document.head.appendChild(tag);
1151
+ }
1152
+ var WorkspaceBrowser_module_css_default = {
1153
+ "wide-in": "qDHVXG_wide-in",
1154
+ "searchWarning": "qDHVXG_searchWarning",
1155
+ "empty": "qDHVXG_empty",
1156
+ "deleteStatus": "qDHVXG_deleteStatus",
1157
+ "search": "qDHVXG_search",
1158
+ "fade": "qDHVXG_fade",
1159
+ "workspaceDropAfter": "qDHVXG_workspaceDropAfter",
1160
+ "searchSlot": "qDHVXG_searchSlot",
1161
+ "rail": "qDHVXG_rail",
1162
+ "searchSlotExpanded": "qDHVXG_searchSlotExpanded",
1163
+ "searchButton": "qDHVXG_searchButton",
1164
+ "workspaceDropBefore": "qDHVXG_workspaceDropBefore",
1165
+ "deleteAction": "qDHVXG_deleteAction",
1166
+ "root": "qDHVXG_root",
1167
+ "clearButton": "qDHVXG_clearButton",
1168
+ "listTopDropIndicator": "qDHVXG_listTopDropIndicator",
1169
+ "listTopDropActive": "qDHVXG_listTopDropActive",
1170
+ "headerActions": "qDHVXG_headerActions",
1171
+ "searchStatus": "qDHVXG_searchStatus",
1172
+ "sectionLabelHidden": "qDHVXG_sectionLabelHidden",
1173
+ "searchInput": "qDHVXG_searchInput",
1174
+ "listArea": "qDHVXG_listArea",
1175
+ "searchExpanded": "qDHVXG_searchExpanded",
1176
+ "list": "qDHVXG_list",
1177
+ "iconButton": "qDHVXG_iconButton",
1178
+ "sectionLabel": "qDHVXG_sectionLabel",
1179
+ "groupSection": "qDHVXG_groupSection",
1180
+ "renameInput": "qDHVXG_renameInput",
1181
+ "sessionOverflowButton": "qDHVXG_sessionOverflowButton",
1182
+ "treeBody": "qDHVXG_treeBody",
1183
+ "wide": "qDHVXG_wide",
1184
+ "flatList": "qDHVXG_flatList",
1185
+ "searchTree": "qDHVXG_searchTree",
1186
+ "sectionHeader": "qDHVXG_sectionHeader",
1187
+ "headerActionsHidden": "qDHVXG_headerActionsHidden",
1188
+ "renameError": "qDHVXG_renameError"
1189
+ };
1190
+ //#endregion
1191
+ //#region lib/types/client/WorkspaceBrowser.js
1192
+ /**
1193
+ * The workspace/session browsing region filling the sidebar shell's
1194
+ * `sidebar.workspaces` hole: section header (title + view options + add
1195
+ * workspace), search, the grouped tree or flat list, and the workspace
1196
+ * dialogs. Wide state renders the full browser; rail state renders the two
1197
+ * region icons (search / add workspace) as 36px controls on the shell's shared
1198
+ * rail entry path, each requesting expansion through the owner share. Adding
1199
+ * is the header button's one action, so it raises the directory flow with no
1200
+ * menu in between; the flow and its error dialog live in WorkspacePicker
1201
+ * (same package — direct composition, no slot between them).
1202
+ */
1203
+ /**
1204
+ * Column slide length (--ds-transition-duration-slow): rail-search focus waits it out —
1205
+ * focus() forces a synchronous layout and would jank the slide.
1206
+ */
1207
+ const EXPAND_SLIDE_MS = 300;
1208
+ /** Pause between the latest keystroke and a Host content-search request. */
1209
+ const SEARCH_DEBOUNCE_MS = 250;
1210
+ /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
1211
+ const SEARCH_QUERY_MAX_CODE_UNITS = 500;
1212
+ /** Session rows visible per Workspace before the local overflow control. */
1213
+ const COLLAPSED_SESSION_LIMIT = 5;
1214
+ /** Keep controlled input and RPC payload inside the session.search wire contract. */
1215
+ function sanitizeSearchQuery(value) {
1216
+ const withoutNul = value.replaceAll("\0", "");
1217
+ if (withoutNul.length <= SEARCH_QUERY_MAX_CODE_UNITS) return withoutNul;
1218
+ let end = SEARCH_QUERY_MAX_CODE_UNITS;
1219
+ const last = withoutNul.charCodeAt(end - 1);
1220
+ const next = withoutNul.charCodeAt(end);
1221
+ if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
1222
+ return withoutNul.slice(0, end);
1223
+ }
1224
+ /** Immutable membership toggle for the local expand-all array. */
1225
+ function toggled(list, key) {
1226
+ return list.includes(key) ? list.filter((k) => k !== key) : [...list, key];
1227
+ }
1228
+ /**
1229
+ * Accept the native drag at document level while a row drag is active: row
1230
+ * hover still owns the insertion marker, and releasing outside the list must
1231
+ * not be rendered as a rejected drop before dragend commits that last marker.
1232
+ */
1233
+ function useNativeDragAcceptance(active) {
1234
+ (0, react.useEffect)(() => {
1235
+ if (!active) return;
1236
+ const acceptDrag = (event) => {
1237
+ event.preventDefault();
1238
+ if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "move";
1239
+ };
1240
+ const acceptDrop = (event) => {
1241
+ event.preventDefault();
1242
+ };
1243
+ document.addEventListener("dragover", acceptDrag);
1244
+ document.addEventListener("drop", acceptDrop);
1245
+ return () => {
1246
+ document.removeEventListener("dragover", acceptDrag);
1247
+ document.removeEventListener("drop", acceptDrop);
1248
+ };
1249
+ }, [active]);
1250
+ }
1251
+ /** Reconcile a stored view order with the Workspace's current session account. */
1252
+ function reconciledSessionOrder(sessionIds, stored) {
1253
+ if (stored === void 0) return [...sessionIds];
1254
+ const byId = new Map(sessionIds.map((id) => [id, id]));
1255
+ const ordered = [];
1256
+ const included = /* @__PURE__ */ new Set();
1257
+ for (const key of stored) {
1258
+ const id = byId.get(key);
1259
+ if (id === void 0 || included.has(key)) continue;
1260
+ ordered.push(id);
1261
+ included.add(key);
1262
+ }
1263
+ for (const id of sessionIds) {
1264
+ if (included.has(id)) continue;
1265
+ ordered.push(id);
1266
+ }
1267
+ return ordered;
1268
+ }
1269
+ /** Newest update first with stable Session identity as the tie-break. */
1270
+ function compareSessionRecency(a, b, byId) {
1271
+ const aUpdatedAt = byId[a]?.updatedAt ?? Number.NEGATIVE_INFINITY;
1272
+ const bUpdatedAt = byId[b]?.updatedAt ?? Number.NEGATIVE_INFINITY;
1273
+ if (aUpdatedAt !== bUpdatedAt) return bUpdatedAt - aUpdatedAt;
1274
+ return a < b ? -1 : 1;
1275
+ }
1276
+ /** Reconcile one editable order account and apply its activity-promotion policy. */
1277
+ function nextSessionOrderAccount({ sessionIds, previousOrder, previousUpdatedAt, list, orderBy, sortByRecency }) {
1278
+ let order = reconciledSessionOrder(sessionIds, previousOrder);
1279
+ if (sortByRecency) order.sort((a, b) => compareSessionRecency(a, b, list.byId));
1280
+ else if (orderBy === "updated") {
1281
+ const promoted = sessionIds.filter((id) => {
1282
+ const session = list.byId[id];
1283
+ return session !== void 0 && (previousUpdatedAt[id] === void 0 || session.updatedAt > previousUpdatedAt[id]);
1284
+ }).sort((a, b) => compareSessionRecency(a, b, list.byId));
1285
+ if (promoted.length > 0) {
1286
+ const promotedIds = new Set(promoted);
1287
+ order = [...promoted, ...order.filter((id) => !promotedIds.has(id))];
1288
+ }
1289
+ }
1290
+ const updatedAt = {};
1291
+ for (const id of sessionIds) {
1292
+ const session = list.byId[id];
1293
+ if (session !== void 0) updatedAt[id] = session.updatedAt;
1294
+ }
1295
+ const orderChanged = previousOrder === void 0 || order.length !== previousOrder.length || order.some((id, index) => id !== previousOrder[index]);
1296
+ const timestampsChanged = Object.keys(updatedAt).length !== Object.keys(previousUpdatedAt).length || Object.entries(updatedAt).some(([id, timestamp]) => previousUpdatedAt[id] !== timestamp);
1297
+ return {
1298
+ order,
1299
+ updatedAt,
1300
+ changed: orderChanged || timestampsChanged
1301
+ };
1302
+ }
1303
+ /** Grouping and ordering controls for the workspace browser. */
1304
+ function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }) {
1305
+ const [open, setOpen] = (0, react.useState)(false);
1306
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1307
+ open,
1308
+ onClose: () => {
1309
+ setOpen(false);
1310
+ },
1311
+ items: [
1312
+ {
1313
+ type: "label",
1314
+ id: "group-by",
1315
+ text: t("groupBy.label")
1316
+ },
1317
+ {
1318
+ id: "workspace",
1319
+ label: t("groupBy.workspace")
1320
+ },
1321
+ {
1322
+ id: "flat",
1323
+ label: t("groupBy.flat")
1324
+ },
1325
+ {
1326
+ type: "separator",
1327
+ id: "order-by-separator"
1328
+ },
1329
+ {
1330
+ type: "label",
1331
+ id: "order-by",
1332
+ text: t("orderBy.label")
1333
+ },
1334
+ {
1335
+ id: "manual",
1336
+ label: t("orderBy.manual")
1337
+ },
1338
+ {
1339
+ id: "updated",
1340
+ label: t("orderBy.updated")
1341
+ },
1342
+ ],
1343
+ selectedIds: [groupBy, orderBy],
1344
+ onSelect: (id) => {
1345
+ if (id === "workspace" || id === "flat") onGroupPick(id);
1346
+ else if (id === "manual" || id === "updated") onOrderPick(id);
1347
+ setOpen(false);
1348
+ },
1349
+ align: "end",
1350
+ dense: true,
1351
+ portal: true,
1352
+ anchor: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1353
+ label: t("viewOptions.label"),
1354
+ side: "bottom",
1355
+ delayMs: 500,
1356
+ children: (0, react_jsx_runtime.jsx)("button", {
1357
+ type: "button",
1358
+ className: clsx(WorkspaceBrowser_module_css_default.iconButton, WorkspaceBrowser_module_css_default.wide),
1359
+ "aria-label": t("viewOptions.label"),
1360
+ onClick: () => {
1361
+ setOpen((v) => !v);
1362
+ },
1363
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, {})
1364
+ })
1365
+ })
1366
+ });
1367
+ }
1368
+ /** Resolve an insertion side from the full rendered workspace group. */
1369
+ function workspaceGroupHalf(e) {
1370
+ const rect = e.currentTarget.getBoundingClientRect();
1371
+ return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
1372
+ }
1373
+ /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
1374
+ function SessionTree({ useSessions, startSession, open, forkSession, workspaces, archivedSessionIds, showArchived, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
1375
+ const list = useSessions((s) => s);
1376
+ const current = list.current;
1377
+ const [expandedSessionGroups, setExpandedSessionGroups] = (0, react.useState)([]);
1378
+ const [drag, setDrag] = (0, react.useState)(null);
1379
+ const sessionDropCommitted = (0, react.useRef)(false);
1380
+ const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
1381
+ const workspaceDropCommitted = (0, react.useRef)(false);
1382
+ const previousOrderBy = (0, react.useRef)(orderBy);
1383
+ useNativeDragAcceptance(drag !== null || workspaceDrag !== null);
1384
+ const currentGroup = current === void 0 ? void 0 : workspaces.find((w) => w.sessionIds.includes(current))?.workspaceId ?? "";
1385
+ (0, react.useEffect)(() => {
1386
+ if (current === void 0 || currentGroup === void 0 || Object.hasOwn(groupExpansion, currentGroup)) return;
1387
+ setGroupExpanded(currentGroup, true);
1388
+ }, [
1389
+ current,
1390
+ currentGroup,
1391
+ setGroupExpanded,
1392
+ groupExpansion
1393
+ ]);
1394
+ const expandedGroups = (0, react.useMemo)(() => Object.entries(groupExpansion).filter(([, expanded]) => expanded).map(([key]) => key), [groupExpansion]);
1395
+ const ungroupedSessionIds = (0, react.useMemo)(() => {
1396
+ const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
1397
+ return list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id));
1398
+ }, [list, workspaces]);
1399
+ (0, react.useEffect)(() => {
1400
+ if (list.phase !== "ready") return;
1401
+ const switchedToUpdated = previousOrderBy.current !== "updated" && orderBy === "updated";
1402
+ previousOrderBy.current = orderBy;
1403
+ const accounts = [...workspaces.map((workspace) => ({
1404
+ key: workspace.workspaceId,
1405
+ sessionIds: workspace.sessionIds.filter((id) => list.byId[id] !== void 0)
1406
+ })), {
1407
+ key: "",
1408
+ sessionIds: ungroupedSessionIds
1409
+ }];
1410
+ for (const { key, sessionIds } of accounts) {
1411
+ const previousOrder = sessionOrderByAccount[key];
1412
+ const next = nextSessionOrderAccount({
1413
+ sessionIds,
1414
+ previousOrder,
1415
+ previousUpdatedAt: sessionUpdatedAtByAccount[key] ?? {},
1416
+ list,
1417
+ orderBy,
1418
+ sortByRecency: orderBy === "updated" && (previousOrder === void 0 || switchedToUpdated)
1419
+ });
1420
+ if (next.changed) syncSessionOrderAccount(key, next.order.map((id) => id), next.updatedAt);
1421
+ }
1422
+ }, [
1423
+ list,
1424
+ orderBy,
1425
+ sessionOrderByAccount,
1426
+ sessionUpdatedAtByAccount,
1427
+ syncSessionOrderAccount,
1428
+ ungroupedSessionIds,
1429
+ workspaces
1430
+ ]);
1431
+ const orderedWorkspaces = (0, react.useMemo)(() => {
1432
+ return workspaces.map((workspace) => {
1433
+ const stored = sessionOrderByAccount[workspace.workspaceId];
1434
+ const sessionIds = reconciledSessionOrder(workspace.sessionIds, stored);
1435
+ return {
1436
+ ...workspace,
1437
+ sessionIds
1438
+ };
1439
+ });
1440
+ }, [sessionOrderByAccount, workspaces]);
1441
+ const orderedUngroupedSessionIds = (0, react.useMemo)(() => reconciledSessionOrder(ungroupedSessionIds, sessionOrderByAccount[""]), [sessionOrderByAccount, ungroupedSessionIds]);
1442
+ const groups = (0, react.useMemo)(() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, {
1443
+ expandedGroups,
1444
+ showArchived,
1445
+ ...sessionOrderByAccount[""] === void 0 ? {} : { ungroupedOrder: sessionOrderByAccount[""] }
1446
+ }), [
1447
+ list,
1448
+ orderedWorkspaces,
1449
+ archivedSessionIds,
1450
+ showArchived,
1451
+ expandedGroups,
1452
+ sessionOrderByAccount
1453
+ ]);
1454
+ const now = Date.now();
1455
+ const commitSessionDrag = (activeDrag, over) => {
1456
+ if (sessionDropCommitted.current) return;
1457
+ sessionDropCommitted.current = true;
1458
+ setDrag(null);
1459
+ const group = groups.find((candidate) => candidate.key === activeDrag.accountKey);
1460
+ if (group === void 0) return;
1461
+ const targetIndex = group.sessions.findIndex((session) => session.id === over.id);
1462
+ if (targetIndex === -1) return;
1463
+ const anchor = over.half === "before" ? over.id : group.sessions[targetIndex + 1]?.id;
1464
+ if (anchor === activeDrag.sessionId) return;
1465
+ const sourceIndex = group.sessions.findIndex((session) => session.id === activeDrag.sessionId);
1466
+ const anchorIndex = anchor === void 0 ? group.sessions.length : group.sessions.findIndex((session) => session.id === anchor);
1467
+ if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
1468
+ const accountSessionIds = activeDrag.accountKey === "" ? orderedUngroupedSessionIds : orderedWorkspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
1469
+ if (accountSessionIds === void 0) return;
1470
+ const nextOrder = accountSessionIds.filter((id) => id !== activeDrag.sessionId);
1471
+ const insertAt = anchor === void 0 ? nextOrder.length : nextOrder.indexOf(anchor);
1472
+ nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
1473
+ setSessionOrder(activeDrag.accountKey, nextOrder.map((id) => id));
1474
+ if (orderBy === "updated" || activeDrag.accountKey === "") return;
1475
+ insertSessionBefore(activeDrag.accountKey, activeDrag.sessionId, anchor).catch((reason) => {
1476
+ console.warn("session reorder rejected:", reason);
1477
+ });
1478
+ };
1479
+ const commitWorkspaceDrag = (activeDrag, over) => {
1480
+ if (workspaceDropCommitted.current) return;
1481
+ workspaceDropCommitted.current = true;
1482
+ setWorkspaceDrag(null);
1483
+ const rowIndex = workspaces.findIndex((workspace) => workspace.workspaceId === over.id);
1484
+ if (rowIndex === -1) return;
1485
+ const anchor = over.half === "before" ? over.id : workspaces[rowIndex + 1]?.workspaceId;
1486
+ if (anchor === activeDrag.workspaceId) return;
1487
+ const sourceIndex = workspaces.findIndex((workspace) => workspace.workspaceId === activeDrag.workspaceId);
1488
+ const anchorIndex = anchor === void 0 ? workspaces.length : workspaces.findIndex((workspace) => workspace.workspaceId === anchor);
1489
+ if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
1490
+ insertWorkspaceBefore(activeDrag.workspaceId, anchor).catch((reason) => {
1491
+ console.warn("workspace reorder rejected:", reason);
1492
+ });
1493
+ };
1494
+ const workspaceDropAtListStart = groups[0]?.workspaceId !== void 0 && workspaceDrag?.over?.id === groups[0].workspaceId && workspaceDrag.over.half === "before";
1495
+ return (0, react_jsx_runtime.jsxs)("div", {
1496
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1497
+ children: [
1498
+ workspaceDropAtListStart && (0, react_jsx_runtime.jsx)("span", {
1499
+ className: WorkspaceBrowser_module_css_default.listTopDropIndicator,
1500
+ "aria-hidden": "true"
1501
+ }),
1502
+ (0, react_jsx_runtime.jsxs)("div", {
1503
+ className: clsx(WorkspaceBrowser_module_css_default.list, workspaceDropAtListStart && WorkspaceBrowser_module_css_default.listTopDropActive),
1504
+ role: "tree",
1505
+ "aria-label": t("section.sessions"),
1506
+ children: [groups.length === 0 && (0, react_jsx_runtime.jsx)("div", {
1507
+ className: WorkspaceBrowser_module_css_default.empty,
1508
+ children: t("empty.none")
1509
+ }), groups.map((group) => {
1510
+ const workspaceId = group.workspaceId;
1511
+ const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
1512
+ const workspaceDragProps = workspaceId === void 0 ? void 0 : {
1513
+ start: () => {
1514
+ workspaceDropCommitted.current = false;
1515
+ setWorkspaceDrag({
1516
+ workspaceId,
1517
+ over: null
1518
+ });
1519
+ },
1520
+ end: () => {
1521
+ if (workspaceDrag?.over !== null && workspaceDrag?.over !== void 0) commitWorkspaceDrag(workspaceDrag, workspaceDrag.over);
1522
+ else setWorkspaceDrag(null);
1523
+ workspaceDropCommitted.current = false;
1524
+ }
1525
+ };
1526
+ const hoverWorkspace = workspaceId === void 0 ? void 0 : (half) => {
1527
+ setWorkspaceDrag((active) => active === null ? active : {
1528
+ ...active,
1529
+ over: {
1530
+ id: workspaceId,
1531
+ half
1532
+ }
1533
+ });
1534
+ };
1535
+ const dropWorkspace = workspaceId === void 0 ? void 0 : (half) => {
1536
+ if (workspaceDrag === null) return;
1537
+ commitWorkspaceDrag(workspaceDrag, {
1538
+ id: workspaceId,
1539
+ half
1540
+ });
1541
+ };
1542
+ return (0, react_jsx_runtime.jsxs)("div", {
1543
+ className: clsx(WorkspaceBrowser_module_css_default.groupSection, workspaceMarker === "before" && WorkspaceBrowser_module_css_default.workspaceDropBefore, workspaceMarker === "after" && WorkspaceBrowser_module_css_default.workspaceDropAfter),
1544
+ onDragOver: workspaceDrag === null || hoverWorkspace === void 0 ? void 0 : (e) => {
1545
+ e.preventDefault();
1546
+ e.dataTransfer.dropEffect = "move";
1547
+ hoverWorkspace(workspaceGroupHalf(e));
1548
+ },
1549
+ onDrop: workspaceDrag === null || dropWorkspace === void 0 ? void 0 : (e) => {
1550
+ e.preventDefault();
1551
+ dropWorkspace(workspaceGroupHalf(e));
1552
+ },
1553
+ children: [
1554
+ (0, react_jsx_runtime.jsx)(ProjectRowItem, {
1555
+ group,
1556
+ t,
1557
+ onToggle: () => {
1558
+ if (group.expanded) setExpandedSessionGroups((keys) => keys.filter((key) => key !== group.key));
1559
+ setGroupExpanded(group.key, !group.expanded);
1560
+ },
1561
+ onCreate: () => {
1562
+ if (group.workspaceId !== void 0) {
1563
+ setGroupExpanded(group.key, true);
1564
+ startSession(group.workspaceId);
1565
+ }
1566
+ },
1567
+ drag: workspaceDragProps,
1568
+ actions: group.workspaceId === void 0 ? void 0 : {
1569
+ rename: () => {
1570
+ /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1571
+ if (group.workspaceId !== void 0) onRenameRequest(group.workspaceId, group.label);
1572
+ },
1573
+ delete: () => {
1574
+ /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1575
+ if (group.workspaceId !== void 0) onDeleteRequest(group.workspaceId, group.label);
1576
+ }
1577
+ }
1578
+ }),
1579
+ (expandedSessionGroups.includes(group.key) ? group.sessions : group.sessions.slice(0, COLLAPSED_SESSION_LIMIT)).map((node) => {
1580
+ const sameGroupDrag = drag !== null && drag.accountKey === group.key;
1581
+ return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1582
+ node,
1583
+ currentId: current,
1584
+ now,
1585
+ onOpen: open,
1586
+ onRename: onSessionRename,
1587
+ onFork: forkSession,
1588
+ onArchive: onSessionArchive,
1589
+ onUnarchive: onSessionUnarchive,
1590
+ onDeleteSession: onSessionDelete,
1591
+ drag: {
1592
+ start: () => {
1593
+ sessionDropCommitted.current = false;
1594
+ setDrag({
1595
+ accountKey: group.key,
1596
+ sessionId: node.id,
1597
+ over: null
1598
+ });
1599
+ },
1600
+ active: sameGroupDrag,
1601
+ marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null,
1602
+ hover: (half) => {
1603
+ /* v8 ignore next -- narrowing guard: Rows gates hover on `active`, which is false while the drag state is null. */
1604
+ setDrag((d) => d === null ? d : {
1605
+ ...d,
1606
+ over: {
1607
+ id: node.id,
1608
+ half
1609
+ }
1610
+ });
1611
+ },
1612
+ drop: (half) => {
1613
+ /* v8 ignore next -- narrowing guard: Rows gates drop on `active`, which is false while the drag state is null. */
1614
+ if (drag === null) return;
1615
+ commitSessionDrag(drag, {
1616
+ id: node.id,
1617
+ half
1618
+ });
1619
+ },
1620
+ end: () => {
1621
+ if (drag?.over !== null && drag?.over !== void 0) commitSessionDrag(drag, drag.over);
1622
+ else setDrag(null);
1623
+ sessionDropCommitted.current = false;
1624
+ }
1625
+ },
1626
+ t
1627
+ }, node.id);
1628
+ }),
1629
+ group.sessions.length > COLLAPSED_SESSION_LIMIT && (0, react_jsx_runtime.jsx)("button", {
1630
+ type: "button",
1631
+ className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
1632
+ "aria-expanded": expandedSessionGroups.includes(group.key),
1633
+ onClick: () => {
1634
+ setExpandedSessionGroups((keys) => toggled(keys, group.key));
1635
+ },
1636
+ children: expandedSessionGroups.includes(group.key) ? t("sessions.collapse") : t("sessions.expand", { n: group.sessions.length - COLLAPSED_SESSION_LIMIT })
1637
+ })
1638
+ ]
1639
+ }, group.key);
1640
+ })]
1641
+ }),
1642
+ (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })
1643
+ ]
1644
+ });
1645
+ }
1646
+ /** The flat "In one list" body: every session is one draggable top-level row. */
1647
+ function FlatList({ useSessions, open, forkSession, onSessionRename, onSessionArchive, onSessionUnarchive, onSessionDelete, archivedSessionIds, showArchived, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
1648
+ const list = useSessions((s) => s);
1649
+ const baseRows = (0, react.useMemo)(() => deriveFlat(list, archivedSessionIds, showArchived), [list, archivedSessionIds, showArchived]);
1650
+ const sessionIds = (0, react.useMemo)(() => baseRows.map((row) => row.id), [baseRows]);
1651
+ const previousOrderBy = (0, react.useRef)(orderBy);
1652
+ (0, react.useEffect)(() => {
1653
+ if (list.phase !== "ready") return;
1654
+ const previousOrder = sessionOrderByAccount[FLAT_SESSION_ORDER_KEY];
1655
+ const previousUpdatedAt = sessionUpdatedAtByAccount["__flat_session_order__"] ?? {};
1656
+ const switchedToUpdated = previousOrderBy.current !== "updated" && orderBy === "updated";
1657
+ previousOrderBy.current = orderBy;
1658
+ const next = nextSessionOrderAccount({
1659
+ sessionIds,
1660
+ previousOrder,
1661
+ previousUpdatedAt,
1662
+ list,
1663
+ orderBy,
1664
+ sortByRecency: orderBy === "updated" && (previousOrder === void 0 || switchedToUpdated)
1665
+ });
1666
+ if (next.changed) syncSessionOrderAccount(FLAT_SESSION_ORDER_KEY, next.order.map((id) => id), next.updatedAt);
1667
+ }, [
1668
+ list,
1669
+ orderBy,
1670
+ sessionOrderByAccount,
1671
+ sessionUpdatedAtByAccount,
1672
+ sessionIds,
1673
+ syncSessionOrderAccount
1674
+ ]);
1675
+ const rows = (0, react.useMemo)(() => {
1676
+ const byId = new Map(baseRows.map((row) => [row.id, row]));
1677
+ return reconciledSessionOrder(sessionIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]).flatMap((id) => {
1678
+ const row = byId.get(id);
1679
+ return row === void 0 ? [] : [row];
1680
+ });
1681
+ }, [
1682
+ baseRows,
1683
+ sessionOrderByAccount,
1684
+ sessionIds
1685
+ ]);
1686
+ const [drag, setDrag] = (0, react.useState)(null);
1687
+ const dropCommitted = (0, react.useRef)(false);
1688
+ useNativeDragAcceptance(drag !== null);
1689
+ const commitDrag = (activeDrag, over) => {
1690
+ if (dropCommitted.current) return;
1691
+ dropCommitted.current = true;
1692
+ setDrag(null);
1693
+ const targetIndex = rows.findIndex((row) => row.id === over.id);
1694
+ if (targetIndex === -1) return;
1695
+ const anchor = over.half === "before" ? over.id : rows[targetIndex + 1]?.id;
1696
+ if (anchor === activeDrag.sessionId) return;
1697
+ const sourceIndex = rows.findIndex((row) => row.id === activeDrag.sessionId);
1698
+ const anchorIndex = anchor === void 0 ? rows.length : rows.findIndex((row) => row.id === anchor);
1699
+ if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
1700
+ const nextOrder = rows.map((row) => row.id).filter((id) => id !== activeDrag.sessionId);
1701
+ const insertAt = anchor === void 0 ? nextOrder.length : nextOrder.indexOf(anchor);
1702
+ nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
1703
+ setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder.map((id) => id));
1704
+ };
1705
+ const now = Date.now();
1706
+ return (0, react_jsx_runtime.jsxs)("div", {
1707
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1708
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1709
+ className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
1710
+ role: "tree",
1711
+ "aria-label": t("section.sessions"),
1712
+ children: [rows.length === 0 && (0, react_jsx_runtime.jsx)("div", {
1713
+ className: WorkspaceBrowser_module_css_default.empty,
1714
+ children: t("empty.none")
1715
+ }), rows.map((node) => {
1716
+ const active = drag !== null;
1717
+ return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1718
+ node,
1719
+ currentId: list.current,
1720
+ now,
1721
+ onOpen: open,
1722
+ onRename: onSessionRename,
1723
+ onFork: forkSession,
1724
+ onArchive: onSessionArchive,
1725
+ onUnarchive: onSessionUnarchive,
1726
+ onDeleteSession: onSessionDelete,
1727
+ flat: true,
1728
+ drag: {
1729
+ start: () => {
1730
+ dropCommitted.current = false;
1731
+ setDrag({
1732
+ accountKey: FLAT_SESSION_ORDER_KEY,
1733
+ sessionId: node.id,
1734
+ over: null
1735
+ });
1736
+ },
1737
+ active,
1738
+ marker: active && drag.over?.id === node.id ? drag.over.half : null,
1739
+ hover: (half) => {
1740
+ setDrag((current) => current === null ? current : {
1741
+ ...current,
1742
+ over: {
1743
+ id: node.id,
1744
+ half
1745
+ }
1746
+ });
1747
+ },
1748
+ drop: (half) => {
1749
+ if (drag !== null) commitDrag(drag, {
1750
+ id: node.id,
1751
+ half
1752
+ });
1753
+ },
1754
+ end: () => {
1755
+ if (drag?.over !== null && drag?.over !== void 0) commitDrag(drag, drag.over);
1756
+ else setDrag(null);
1757
+ dropCommitted.current = false;
1758
+ }
1759
+ },
1760
+ t
1761
+ }, node.id);
1762
+ })]
1763
+ }), (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
1764
+ });
1765
+ }
1766
+ /** Flat search body: local metadata matches plus the current Host result page. */
1767
+ function SearchResults({ useSessions, open, workspaces, archivedSessionIds, showArchived, query, remote, resultLimit, t }) {
1768
+ const list = useSessions((s) => s);
1769
+ const currentRemote = remote.query === query ? remote : {
1770
+ query,
1771
+ status: "loading",
1772
+ items: [],
1773
+ hasMore: false
1774
+ };
1775
+ const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, currentRemote, resultLimit, showArchived), [
1776
+ list,
1777
+ workspaces,
1778
+ query,
1779
+ archivedSessionIds,
1780
+ currentRemote,
1781
+ resultLimit,
1782
+ showArchived
1783
+ ]);
1784
+ const pending = currentRemote.status === "loading";
1785
+ const failed = currentRemote.status === "error";
1786
+ return (0, react_jsx_runtime.jsxs)("div", {
1787
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1788
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1789
+ className: WorkspaceBrowser_module_css_default.list,
1790
+ children: [
1791
+ (0, react_jsx_runtime.jsx)("div", {
1792
+ className: WorkspaceBrowser_module_css_default.searchTree,
1793
+ role: "tree",
1794
+ "aria-label": t("search.results.aria"),
1795
+ children: results.items.map((result) => (0, react_jsx_runtime.jsx)(SearchResultItem, {
1796
+ result,
1797
+ currentId: list.current,
1798
+ onOpen: open,
1799
+ t
1800
+ }, result.id))
1801
+ }),
1802
+ pending && (0, react_jsx_runtime.jsx)("div", {
1803
+ className: WorkspaceBrowser_module_css_default.searchStatus,
1804
+ role: "status",
1805
+ children: t("search.pending")
1806
+ }),
1807
+ failed && (0, react_jsx_runtime.jsx)("div", {
1808
+ className: WorkspaceBrowser_module_css_default.searchWarning,
1809
+ role: "status",
1810
+ children: t("search.unavailable")
1811
+ }),
1812
+ !pending && results.items.length === 0 && (0, react_jsx_runtime.jsx)("div", {
1813
+ className: WorkspaceBrowser_module_css_default.empty,
1814
+ children: t("search.noMatches")
1815
+ }),
1816
+ results.hasMore && (0, react_jsx_runtime.jsx)("div", {
1817
+ className: WorkspaceBrowser_module_css_default.searchStatus,
1818
+ children: t("search.hasMore", { n: resultLimit })
1819
+ })
1820
+ ]
1821
+ }), (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
1822
+ });
1823
+ }
1824
+ /**
1825
+ * Render the browsing region.
1826
+ * @param props - composed slot props (shell owner share + store + injected actions).
1827
+ * @returns the region element tree.
1828
+ */
1829
+ function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, unarchiveSession, deleteSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
1830
+ const workspaces = useWorkspaces((state) => state.items);
1831
+ const workspacePhase = useWorkspaces((state) => state.phase);
1832
+ const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
1833
+ const directoryFlowAvailable = useDirectoryFlow((occupied) => occupied);
1834
+ const groupBy = useStore((s) => s.groupBy);
1835
+ const orderBy = useStore((s) => s.orderBy);
1836
+ const showArchived = useStore((s) => s.showArchived) === true;
1837
+ const groupExpansion = useStore((s) => s.groupExpansion);
1838
+ const sessionOrderByAccount = useStore((s) => s.sessionOrderByAccount);
1839
+ const sessionUpdatedAtByAccount = useStore((s) => s.sessionUpdatedAtByAccount);
1840
+ const archivedSet = (0, react.useMemo)(() => new Set(archivedSessionIds), [archivedSessionIds]);
1841
+ const [archivedToast, setArchivedToast] = (0, react.useState)(null);
1842
+ const archivedToastSeq = (0, react.useRef)(0);
1843
+ /** Open a session, unless it is archived (show the archived hint instead). */
1844
+ const guardedOpen = (sessionId) => {
1845
+ if (archivedSet.has(sessionId)) {
1846
+ archivedToastSeq.current += 1;
1847
+ setArchivedToast({
1848
+ text: t("archived.notOpenable"),
1849
+ seq: archivedToastSeq.current
1850
+ });
1851
+ return;
1852
+ }
1853
+ open(sessionId);
1854
+ };
1855
+ (0, react.useEffect)(() => {
1856
+ if (workspacePhase !== "ready") return;
1857
+ actions.retainAccountKeys([
1858
+ "",
1859
+ FLAT_SESSION_ORDER_KEY,
1860
+ ...workspaces.map((workspace) => workspace.workspaceId)
1861
+ ]);
1862
+ }, [
1863
+ actions.retainAccountKeys,
1864
+ workspacePhase,
1865
+ workspaces
1866
+ ]);
1867
+ const [query, setQuery] = (0, react.useState)("");
1868
+ const [searchExpanded, setSearchExpanded] = (0, react.useState)(false);
1869
+ const normalizedQuery = sanitizeSearchQuery(query).trim();
1870
+ const [remoteSearch, setRemoteSearch] = (0, react.useState)({
1871
+ query: "",
1872
+ status: "idle",
1873
+ items: [],
1874
+ hasMore: false
1875
+ });
1876
+ const searchRoot = (0, react.useRef)(null);
1877
+ const searchInput = (0, react.useRef)(null);
1878
+ const [wsPickerOpen, setWsPickerOpen] = (0, react.useState)(false);
1879
+ const wsPlusRef = (0, react.useRef)(null);
1880
+ const composingRef = (0, react.useRef)(false);
1881
+ const [searchOnExpand, setSearchOnExpand] = (0, react.useState)(false);
1882
+ (0, react.useEffect)(() => {
1883
+ if (wide && searchOnExpand) {
1884
+ const timer = window.setTimeout(() => {
1885
+ searchInput.current?.focus({ preventScroll: true });
1886
+ setSearchOnExpand(false);
1887
+ }, EXPAND_SLIDE_MS);
1888
+ return () => {
1889
+ window.clearTimeout(timer);
1890
+ };
1891
+ }
1892
+ }, [wide, searchOnExpand]);
1893
+ (0, react.useEffect)(() => {
1894
+ if (!wide || !searchExpanded || searchOnExpand) return;
1895
+ searchInput.current?.focus({ preventScroll: true });
1896
+ }, [
1897
+ wide,
1898
+ searchExpanded,
1899
+ searchOnExpand
1900
+ ]);
1901
+ (0, react.useEffect)(() => {
1902
+ if (!wide || !searchExpanded) return;
1903
+ const onClick = (event) => {
1904
+ if (!(event.target instanceof Node) || searchRoot.current?.contains(event.target) === true) return;
1905
+ searchInput.current?.blur();
1906
+ if (normalizedQuery !== "") return;
1907
+ setSearchExpanded(false);
1908
+ };
1909
+ document.addEventListener("click", onClick);
1910
+ return () => {
1911
+ document.removeEventListener("click", onClick);
1912
+ };
1913
+ }, [
1914
+ normalizedQuery,
1915
+ wide,
1916
+ searchExpanded
1917
+ ]);
1918
+ (0, react.useEffect)(() => {
1919
+ if (normalizedQuery === "") {
1920
+ setRemoteSearch({
1921
+ query: "",
1922
+ status: "idle",
1923
+ items: [],
1924
+ hasMore: false
1925
+ });
1926
+ return;
1927
+ }
1928
+ const controller = new AbortController();
1929
+ setRemoteSearch({
1930
+ query: normalizedQuery,
1931
+ status: "loading",
1932
+ items: [],
1933
+ hasMore: false
1934
+ });
1935
+ const timer = window.setTimeout(() => {
1936
+ searchSessions(normalizedQuery, controller.signal).then((result) => {
1937
+ if (controller.signal.aborted) return;
1938
+ setRemoteSearch({
1939
+ query: normalizedQuery,
1940
+ status: "ready",
1941
+ items: result.items,
1942
+ hasMore: result.hasMore
1943
+ });
1944
+ }).catch(() => {
1945
+ if (controller.signal.aborted) return;
1946
+ setRemoteSearch({
1947
+ query: normalizedQuery,
1948
+ status: "error",
1949
+ items: [],
1950
+ hasMore: false
1951
+ });
1952
+ });
1953
+ }, SEARCH_DEBOUNCE_MS);
1954
+ return () => {
1955
+ window.clearTimeout(timer);
1956
+ controller.abort();
1957
+ };
1958
+ }, [normalizedQuery, searchSessions]);
1959
+ const [renameTarget, setRenameTarget] = (0, react.useState)(null);
1960
+ const [renameDraft, setRenameDraft] = (0, react.useState)("");
1961
+ const [renaming, setRenaming] = (0, react.useState)(false);
1962
+ const [renameError, setRenameError] = (0, react.useState)(null);
1963
+ const renameTrimmed = renameDraft.trim();
1964
+ const renameDuplicate = renameTarget !== null && renameTrimmed !== "" && renameTrimmed !== renameTarget.currentTitle && workspaces.some((w) => w.title === renameTrimmed);
1965
+ const renameBlocked = renaming || renameTrimmed === "" || renameTarget === null || renameTrimmed === renameTarget.currentTitle || renameDuplicate;
1966
+ const closeRename = () => {
1967
+ if (renaming) return;
1968
+ setRenameTarget(null);
1969
+ setRenameError(null);
1970
+ };
1971
+ const confirmRename = () => {
1972
+ if (renameBlocked) return;
1973
+ setRenaming(true);
1974
+ setRenameError(null);
1975
+ renameWorkspace(renameTarget.workspaceId, renameTrimmed).then(() => {
1976
+ setRenaming(false);
1977
+ setRenameTarget(null);
1978
+ }).catch((reason) => {
1979
+ setRenaming(false);
1980
+ setRenameError(reason instanceof Error ? reason.message : String(reason));
1981
+ });
1982
+ };
1983
+ const [sessionRenameTarget, setSessionRenameTarget] = (0, react.useState)(null);
1984
+ const [sessionRenameDraft, setSessionRenameDraft] = (0, react.useState)("");
1985
+ const [sessionRenaming, setSessionRenaming] = (0, react.useState)(false);
1986
+ const [sessionRenameError, setSessionRenameError] = (0, react.useState)(null);
1987
+ const sessionRenameTrimmed = sessionRenameDraft.trim();
1988
+ const sessionRenameBlocked = sessionRenaming || sessionRenameTrimmed === "" || sessionRenameTarget === null;
1989
+ const closeSessionRename = () => {
1990
+ if (sessionRenaming) return;
1991
+ setSessionRenameTarget(null);
1992
+ setSessionRenameError(null);
1993
+ };
1994
+ const confirmSessionRename = () => {
1995
+ if (sessionRenameBlocked) return;
1996
+ setSessionRenaming(true);
1997
+ setSessionRenameError(null);
1998
+ renameSession(sessionRenameTarget.sessionId, sessionRenameTrimmed).then(() => {
1999
+ setSessionRenaming(false);
2000
+ setSessionRenameTarget(null);
2001
+ }).catch((reason) => {
2002
+ setSessionRenaming(false);
2003
+ setSessionRenameError(reason instanceof Error ? reason.message : String(reason));
2004
+ });
2005
+ };
2006
+ const onSessionRename = (sessionId, currentTitle) => {
2007
+ setSessionRenameTarget({
2008
+ sessionId,
2009
+ currentTitle
2010
+ });
2011
+ setSessionRenameDraft(currentTitle);
2012
+ setSessionRenameError(null);
2013
+ };
2014
+ const onSessionArchive = (sessionId) => {
2015
+ archiveSession(sessionId).catch((reason) => {
2016
+ console.warn("session archive rejected:", reason);
2017
+ });
2018
+ };
2019
+ const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
2020
+ const [deleting, setDeleting] = (0, react.useState)(false);
2021
+ const [deleteCommittedId, setDeleteCommittedId] = (0, react.useState)(null);
2022
+ const [deleteError, setDeleteError] = (0, react.useState)(null);
2023
+ (0, react.useEffect)(() => {
2024
+ if (deleteCommittedId === null || workspaces.some((workspace) => workspace.workspaceId === deleteCommittedId)) return;
2025
+ setDeleting(false);
2026
+ setDeleteCommittedId(null);
2027
+ setDeleteTarget(null);
2028
+ }, [deleteCommittedId, workspaces]);
2029
+ const closeDelete = () => {
2030
+ if (deleting) return;
2031
+ setDeleteTarget(null);
2032
+ setDeleteError(null);
2033
+ };
2034
+ const confirmDelete = () => {
2035
+ /* v8 ignore next -- the Modal is absent without a target and its button is disabled while deleting. */
2036
+ if (deleting || deleteTarget === null) return;
2037
+ setDeleting(true);
2038
+ setDeleteCommittedId(null);
2039
+ setDeleteError(null);
2040
+ deleteWorkspace(deleteTarget.workspaceId).then(() => {
2041
+ setDeleteCommittedId(deleteTarget.workspaceId);
2042
+ }).catch((reason) => {
2043
+ setDeleting(false);
2044
+ setDeleteError(reason instanceof Error ? reason.message : String(reason));
2045
+ });
2046
+ };
2047
+ const onSessionUnarchive = (sessionId) => {
2048
+ unarchiveSession(sessionId).catch((reason) => {
2049
+ console.warn("session unarchive rejected:", reason);
2050
+ });
2051
+ };
2052
+ const [deleteSessionTarget, setDeleteSessionTarget] = (0, react.useState)(null);
2053
+ const [deletingSession, setDeletingSession] = (0, react.useState)(false);
2054
+ const [deleteSessionCommittedId, setDeleteSessionCommittedId] = (0, react.useState)(null);
2055
+ const [deleteSessionError, setDeleteSessionError] = (0, react.useState)(null);
2056
+ (0, react.useEffect)(() => {
2057
+ if (deleteSessionCommittedId === null || archivedSet.has(deleteSessionCommittedId) || workspaces.some((workspace) => workspace.sessionIds.includes(deleteSessionCommittedId))) return;
2058
+ setDeletingSession(false);
2059
+ setDeleteSessionCommittedId(null);
2060
+ setDeleteSessionTarget(null);
2061
+ }, [deleteSessionCommittedId, archivedSet, workspaces]);
2062
+ const closeDeleteSession = () => {
2063
+ if (deletingSession) return;
2064
+ setDeleteSessionTarget(null);
2065
+ setDeleteSessionError(null);
2066
+ };
2067
+ const confirmDeleteSession = () => {
2068
+ /* v8 ignore next -- the Modal is absent without a target and its button is disabled while deleting. */
2069
+ if (deletingSession || deleteSessionTarget === null) return;
2070
+ setDeletingSession(true);
2071
+ setDeleteSessionError(null);
2072
+ deleteSession(deleteSessionTarget.sessionId).then(() => {
2073
+ setDeletingSession(false);
2074
+ setDeleteSessionTarget(null);
2075
+ setDeleteSessionCommittedId(deleteSessionTarget.sessionId);
2076
+ }).catch((reason) => {
2077
+ setDeletingSession(false);
2078
+ setDeleteSessionError(reason instanceof Error ? reason.message : String(reason));
2079
+ });
2080
+ };
2081
+ return (0, react_jsx_runtime.jsxs)("div", {
2082
+ className: clsx(WorkspaceBrowser_module_css_default.root, !wide && WorkspaceBrowser_module_css_default.rail),
2083
+ children: [
2084
+ (0, react_jsx_runtime.jsxs)("div", {
2085
+ className: WorkspaceBrowser_module_css_default.sectionHeader,
2086
+ children: [
2087
+ wide && (0, react_jsx_runtime.jsx)("span", {
2088
+ className: clsx(WorkspaceBrowser_module_css_default.sectionLabel, WorkspaceBrowser_module_css_default.wide, searchExpanded && WorkspaceBrowser_module_css_default.sectionLabelHidden),
2089
+ children: groupBy === "flat" ? t("section.sessions") : t("section.workspaces")
2090
+ }),
2091
+ wide && (0, react_jsx_runtime.jsx)("div", {
2092
+ className: clsx(WorkspaceBrowser_module_css_default.searchSlot, searchExpanded && WorkspaceBrowser_module_css_default.searchSlotExpanded),
2093
+ children: (0, react_jsx_runtime.jsxs)("div", {
2094
+ ref: searchRoot,
2095
+ className: clsx(WorkspaceBrowser_module_css_default.search, searchExpanded && WorkspaceBrowser_module_css_default.searchExpanded),
2096
+ onClick: () => {
2097
+ setWsPickerOpen(false);
2098
+ setSearchExpanded(true);
2099
+ searchInput.current?.focus();
2100
+ },
2101
+ children: [
2102
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
2103
+ label: t("search"),
2104
+ side: "bottom",
2105
+ delayMs: 500,
2106
+ disabled: searchExpanded,
2107
+ children: (0, react_jsx_runtime.jsx)("button", {
2108
+ type: "button",
2109
+ className: WorkspaceBrowser_module_css_default.searchButton,
2110
+ "aria-label": t("search.sessions.aria"),
2111
+ "aria-expanded": searchExpanded,
2112
+ onClick: () => {
2113
+ setWsPickerOpen(false);
2114
+ setSearchExpanded(true);
2115
+ },
2116
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: searchExpanded ? 11 : 14 })
2117
+ })
2118
+ }),
2119
+ (0, react_jsx_runtime.jsx)("input", {
2120
+ ref: searchInput,
2121
+ className: WorkspaceBrowser_module_css_default.searchInput,
2122
+ type: "text",
2123
+ placeholder: t("search.placeholder"),
2124
+ maxLength: SEARCH_QUERY_MAX_CODE_UNITS,
2125
+ value: query,
2126
+ tabIndex: searchExpanded ? 0 : -1,
2127
+ onChange: (e) => {
2128
+ setQuery(sanitizeSearchQuery(e.target.value));
2129
+ },
2130
+ onKeyDown: (e) => {
2131
+ if (e.key !== "Escape") return;
2132
+ setQuery("");
2133
+ setSearchExpanded(false);
2134
+ }
2135
+ }),
2136
+ searchExpanded && (0, react_jsx_runtime.jsx)("button", {
2137
+ type: "button",
2138
+ className: WorkspaceBrowser_module_css_default.clearButton,
2139
+ "aria-label": t("search.clear"),
2140
+ onClick: (e) => {
2141
+ e.stopPropagation();
2142
+ setQuery("");
2143
+ setSearchExpanded(false);
2144
+ },
2145
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFill14, {})
2146
+ })
2147
+ ]
2148
+ })
2149
+ }),
2150
+ (0, react_jsx_runtime.jsxs)("div", {
2151
+ className: clsx(WorkspaceBrowser_module_css_default.headerActions, wide && searchExpanded && WorkspaceBrowser_module_css_default.headerActionsHidden),
2152
+ children: [wide && (0, react_jsx_runtime.jsx)(ViewOptionsMenu, {
2153
+ groupBy,
2154
+ orderBy,
2155
+ onGroupPick: (mode) => {
2156
+ actions.setGroupBy(mode);
2157
+ },
2158
+ onOrderPick: (mode) => {
2159
+ actions.setOrderBy(mode);
2160
+ },
2161
+ t
2162
+ }), directoryFlowAvailable && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
2163
+ label: t("workspace.add"),
2164
+ side: "bottom",
2165
+ delayMs: 500,
2166
+ children: (0, react_jsx_runtime.jsx)("button", {
2167
+ ref: wsPlusRef,
2168
+ type: "button",
2169
+ className: WorkspaceBrowser_module_css_default.iconButton,
2170
+ "aria-label": t("workspace.add"),
2171
+ onClick: () => {
2172
+ setWsPickerOpen((v) => !v);
2173
+ },
2174
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconProjectAddOutline16, { size: wide ? 16 : 18 })
2175
+ })
2176
+ })]
2177
+ }),
2178
+ (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
2179
+ t,
2180
+ open: wsPickerOpen,
2181
+ anchorRef: wsPlusRef,
2182
+ useWorkspaces,
2183
+ createWorkspace,
2184
+ useDirectoryFlow,
2185
+ renderDirectoryFlow: (owner) => renderSlot("sidebar.workspaces.directoryFlow", owner),
2186
+ addOnly: true,
2187
+ side: "right",
2188
+ onPick: (workspaceId) => {
2189
+ setWsPickerOpen(false);
2190
+ startSession(workspaceId);
2191
+ },
2192
+ onClose: () => {
2193
+ setWsPickerOpen(false);
2194
+ }
2195
+ })
2196
+ ]
2197
+ }),
2198
+ !wide && (0, react_jsx_runtime.jsx)("div", {
2199
+ className: WorkspaceBrowser_module_css_default.search,
2200
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
2201
+ label: t("search"),
2202
+ children: (0, react_jsx_runtime.jsx)("button", {
2203
+ type: "button",
2204
+ className: WorkspaceBrowser_module_css_default.searchButton,
2205
+ "aria-label": t("search.sessions.aria"),
2206
+ onClick: () => {
2207
+ setSearchExpanded(true);
2208
+ setSearchOnExpand(true);
2209
+ expandSidebar();
2210
+ },
2211
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 18 })
2212
+ })
2213
+ })
2214
+ }),
2215
+ (0, react_jsx_runtime.jsx)("div", {
2216
+ className: WorkspaceBrowser_module_css_default.listArea,
2217
+ children: wide && (normalizedQuery !== "" ? (0, react_jsx_runtime.jsx)(SearchResults, {
2218
+ useSessions,
2219
+ open: guardedOpen,
2220
+ workspaces,
2221
+ archivedSessionIds,
2222
+ showArchived,
2223
+ query: normalizedQuery,
2224
+ remote: remoteSearch,
2225
+ resultLimit: searchResultLimit,
2226
+ t
2227
+ }) : groupBy === "flat" ? (0, react_jsx_runtime.jsx)(FlatList, {
2228
+ useSessions,
2229
+ open: guardedOpen,
2230
+ forkSession,
2231
+ onSessionRename,
2232
+ onSessionArchive,
2233
+ onSessionUnarchive,
2234
+ onSessionDelete: (sessionId, title) => {
2235
+ setDeleteSessionTarget({ sessionId, title });
2236
+ setDeleteSessionError(null);
2237
+ },
2238
+ archivedSessionIds,
2239
+ showArchived,
2240
+ orderBy,
2241
+ sessionOrderByAccount,
2242
+ sessionUpdatedAtByAccount,
2243
+ syncSessionOrderAccount: actions.syncSessionOrderAccount,
2244
+ setSessionOrder: actions.setSessionOrder,
2245
+ t
2246
+ }) : (0, react_jsx_runtime.jsx)(SessionTree, {
2247
+ useSessions,
2248
+ onSessionRename,
2249
+ onSessionArchive,
2250
+ onSessionUnarchive,
2251
+ onSessionDelete: (sessionId, title) => {
2252
+ setDeleteSessionTarget({ sessionId, title });
2253
+ setDeleteSessionError(null);
2254
+ },
2255
+ forkSession,
2256
+ workspaces,
2257
+ groupExpansion,
2258
+ setGroupExpanded: actions.setGroupExpanded,
2259
+ sessionOrderByAccount,
2260
+ sessionUpdatedAtByAccount,
2261
+ syncSessionOrderAccount: actions.syncSessionOrderAccount,
2262
+ setSessionOrder: actions.setSessionOrder,
2263
+ archivedSessionIds,
2264
+ showArchived,
2265
+ startSession,
2266
+ open: guardedOpen,
2267
+ insertWorkspaceBefore,
2268
+ insertSessionBefore,
2269
+ orderBy,
2270
+ t,
2271
+ onRenameRequest: (workspaceId, currentTitle) => {
2272
+ setRenameTarget({
2273
+ workspaceId,
2274
+ currentTitle
2275
+ });
2276
+ setRenameDraft(currentTitle);
2277
+ setRenameError(null);
2278
+ },
2279
+ onDeleteRequest: (workspaceId, title) => {
2280
+ setDeleteTarget({
2281
+ workspaceId,
2282
+ title
2283
+ });
2284
+ setDeleteError(null);
2285
+ }
2286
+ }))
2287
+ }),
2288
+ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2289
+ open: renameTarget !== null,
2290
+ onClose: closeRename,
2291
+ closeLabel: t("close"),
2292
+ title: t("rename.workspace.title"),
2293
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2294
+ variant: "outline",
2295
+ disabled: renaming,
2296
+ onClick: closeRename,
2297
+ children: t("cancel")
2298
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2299
+ variant: "primary",
2300
+ disabled: renameBlocked,
2301
+ onClick: confirmRename,
2302
+ children: t("rename")
2303
+ })] }),
2304
+ children: [
2305
+ (0, react_jsx_runtime.jsx)("input", {
2306
+ className: WorkspaceBrowser_module_css_default.renameInput,
2307
+ value: renameDraft,
2308
+ "aria-label": t("field.workspaceName"),
2309
+ autoFocus: true,
2310
+ disabled: renaming,
2311
+ onFocus: (e) => {
2312
+ e.target.select();
2313
+ },
2314
+ onChange: (e) => {
2315
+ setRenameDraft(e.target.value);
2316
+ setRenameError(null);
2317
+ },
2318
+ onCompositionStart: () => {
2319
+ composingRef.current = true;
2320
+ },
2321
+ onCompositionEnd: () => {
2322
+ composingRef.current = false;
2323
+ },
2324
+ onKeyDown: (e) => {
2325
+ if (e.key === "Enter" && !composingRef.current) {
2326
+ e.preventDefault();
2327
+ confirmRename();
2328
+ }
2329
+ }
2330
+ }),
2331
+ renameDuplicate && (0, react_jsx_runtime.jsx)("div", {
2332
+ className: WorkspaceBrowser_module_css_default.renameError,
2333
+ role: "alert",
2334
+ children: t("conflict.named", { name: renameTrimmed })
2335
+ }),
2336
+ renameError !== null && (0, react_jsx_runtime.jsx)("div", {
2337
+ className: WorkspaceBrowser_module_css_default.renameError,
2338
+ role: "alert",
2339
+ children: renameError
2340
+ })
2341
+ ]
2342
+ }),
2343
+ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2344
+ open: sessionRenameTarget !== null,
2345
+ onClose: closeSessionRename,
2346
+ closeLabel: t("close"),
2347
+ title: t("rename.session.title"),
2348
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2349
+ variant: "outline",
2350
+ disabled: sessionRenaming,
2351
+ onClick: closeSessionRename,
2352
+ children: t("cancel")
2353
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2354
+ variant: "primary",
2355
+ disabled: sessionRenameBlocked,
2356
+ onClick: confirmSessionRename,
2357
+ children: t("rename")
2358
+ })] }),
2359
+ children: [(0, react_jsx_runtime.jsx)("input", {
2360
+ className: WorkspaceBrowser_module_css_default.renameInput,
2361
+ value: sessionRenameDraft,
2362
+ "aria-label": t("field.sessionName"),
2363
+ autoFocus: true,
2364
+ disabled: sessionRenaming,
2365
+ onFocus: (e) => {
2366
+ e.target.select();
2367
+ },
2368
+ onChange: (e) => {
2369
+ setSessionRenameDraft(e.target.value);
2370
+ setSessionRenameError(null);
2371
+ },
2372
+ onCompositionStart: () => {
2373
+ composingRef.current = true;
2374
+ },
2375
+ onCompositionEnd: () => {
2376
+ composingRef.current = false;
2377
+ },
2378
+ onKeyDown: (e) => {
2379
+ if (e.key === "Enter" && !composingRef.current) {
2380
+ e.preventDefault();
2381
+ confirmSessionRename();
2382
+ }
2383
+ }
2384
+ }), sessionRenameError !== null && (0, react_jsx_runtime.jsx)("div", {
2385
+ className: WorkspaceBrowser_module_css_default.renameError,
2386
+ role: "alert",
2387
+ children: sessionRenameError
2388
+ })]
2389
+ }),
2390
+ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2391
+ open: deleteTarget !== null,
2392
+ onClose: closeDelete,
2393
+ closeLabel: t("close"),
2394
+ title: t("delete.workspace"),
2395
+ ...deleteTarget === null ? {} : { description: t("delete.desc", { name: deleteTarget.title }) },
2396
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2397
+ variant: "outline",
2398
+ disabled: deleting,
2399
+ onClick: closeDelete,
2400
+ children: t("cancel")
2401
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2402
+ variant: "outline",
2403
+ className: WorkspaceBrowser_module_css_default.deleteAction,
2404
+ disabled: deleting,
2405
+ onClick: confirmDelete,
2406
+ children: t("delete.workspace")
2407
+ })] }),
2408
+ children: [deleting && (0, react_jsx_runtime.jsx)("div", {
2409
+ className: WorkspaceBrowser_module_css_default.deleteStatus,
2410
+ role: "status",
2411
+ children: t("delete.pending")
2412
+ }), deleteError !== null && (0, react_jsx_runtime.jsx)("div", {
2413
+ className: WorkspaceBrowser_module_css_default.renameError,
2414
+ role: "alert",
2415
+ children: deleteError
2416
+ })]
2417
+ }),
2418
+ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2419
+ open: deleteSessionTarget !== null,
2420
+ onClose: closeDeleteSession,
2421
+ closeLabel: t("close"),
2422
+ title: t("deleteSession.title"),
2423
+ ...deleteSessionTarget === null ? {} : { description: t("deleteSession.desc", { name: deleteSessionTarget.title }) },
2424
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2425
+ variant: "outline",
2426
+ disabled: deletingSession,
2427
+ onClick: closeDeleteSession,
2428
+ children: t("cancel")
2429
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2430
+ variant: "outline",
2431
+ className: WorkspaceBrowser_module_css_default.deleteAction,
2432
+ disabled: deletingSession,
2433
+ onClick: confirmDeleteSession,
2434
+ children: t("deleteSession.title")
2435
+ })] }),
2436
+ children: [deletingSession && (0, react_jsx_runtime.jsx)("div", {
2437
+ className: WorkspaceBrowser_module_css_default.deleteStatus,
2438
+ role: "status",
2439
+ children: t("deleteSession.pending")
2440
+ }), deleteSessionError !== null && (0, react_jsx_runtime.jsx)("div", {
2441
+ className: WorkspaceBrowser_module_css_default.renameError,
2442
+ role: "alert",
2443
+ children: deleteSessionError
2444
+ })]
2445
+ }),
2446
+ archivedToast !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
2447
+ key: archivedToast.seq,
2448
+ text: archivedToast.text,
2449
+ onDone: () => {
2450
+ setArchivedToast(null);
2451
+ }
2452
+ })
2453
+ ]
2454
+ });
2455
+ }
2456
+ //#endregion
2457
+ //#region dsh-archive-manager: settings section
2458
+ const ARCHIVE_SETTINGS_CSS = ".dsham_settings{box-sizing:border-box;width:min(100%,768px);margin:64px auto 80px;padding:0 20px;color:var(--dsw-alias-label-primary)}.dsham_settingsHeader{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:48px}.dsham_settings h2{margin:0;font-size:24px;font-weight:650;letter-spacing:-.02em;line-height:32px}.dsham_settingsDanger{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 11px;color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);border:0;border-radius:9px;cursor:pointer;font-size:12px;font-weight:600}.dsham_settingsDanger:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 20%,transparent)}.dsham_settingsToolbar{display:flex;gap:8px;margin-bottom:40px}.dsham_settingsSearch{display:flex;align-items:center;gap:8px;min-width:0;flex:1;height:32px;padding:0 12px;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-button-elevated-fill);border:1px solid var(--dsw-alias-border-l2);border-radius:16px}.dsham_settingsSearch:focus-within{border-color:var(--dsw-alias-label-tertiary)}.dsham_settingsSearch input{width:100%;min-width:0;padding:0;color:var(--dsw-alias-label-primary);background:transparent;border:0;outline:0;font:inherit;font-size:12px}.dsham_settingsSearch input::placeholder{color:var(--dsw-alias-label-tertiary)}.dsham_settingsFilter{min-width:132px;height:30px;padding:0 28px 0 11px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-elevated-fill);border:1px solid var(--dsw-alias-border-l2);border-radius:9px;cursor:pointer;font:inherit;font-size:12px}.dsham_settingsGroup{margin:0 0 48px}.dsham_settingsGroupHeading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 14px}.dsham_settingsGroupTitle{display:flex;align-items:center;gap:8px;min-width:0;margin:0;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}.dsham_settingsGroupTitle svg{flex:none;color:var(--dsw-alias-label-secondary)}.dsham_settingsCount{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}.dsham_settingsList{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-button-elevated-fill)}.dsham_settingsRow{display:flex;align-items:center;gap:12px;min-height:60px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dsham_settingsRow:last-child{border-bottom:0}.dsham_settingsContent{min-width:0;flex:1}.dsham_settingsTitle{overflow:hidden;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.dsham_settingsMeta{margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}.dsham_settingsActions{display:flex;align-items:center;gap:8px}.dsham_settingsAction{height:30px;padding:0 10px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);border:0;border-radius:9px;cursor:pointer;font-size:12px;font-weight:600}.dsham_settingsAction:hover{filter:brightness(1.12)}.dsham_settingsDelete{display:flex;align-items:center;justify-content:center;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);background:transparent;border:0;border-radius:8px;cursor:pointer}.dsham_settingsDelete:hover{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover)}.dsham_settingsEmpty{padding:28px 8px;color:var(--dsw-alias-label-secondary);text-align:center}.dsham_settingsError{margin-top:10px;color:var(--dsw-alias-state-error-primary);font-size:12px}@media(max-width:720px){.dsham_settings{width:100%;margin:28px auto 48px;padding:0 16px}.dsham_settingsHeader{margin-bottom:28px}.dsham_settingsToolbar{flex-wrap:wrap;margin-bottom:28px}.dsham_settingsSearch{flex-basis:100%}.dsham_settingsFilter{flex:1;min-width:0}.dsham_settingsGroup{margin-bottom:32px}.dsham_settingsRow{padding:10px 12px}.dsham_settingsActions{gap:4px}}";
2459
+ const ARCHIVE_SETTINGS_LAYOUT_OVERRIDE = ".dsham_settings{margin:24px auto 80px!important}.dsham_settingsFilter{color-scheme:dark;background-color:var(--dsw-alias-button-elevated-fill)!important;color:var(--dsw-alias-label-primary)!important}.dsham_settingsFilter option{color-scheme:dark;background-color:#2b2b2b;color:#f1f1f1}@media(max-width:720px){.dsham_settings{margin:16px auto 48px!important}}";
2460
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify("dsh-archive-manager/ArchiveSettings.layout.css") + "]") === null) {
2461
+ const tag = document.createElement("style");
2462
+ tag.dataset.plugin = "@michengai/dsh-archive-manager";
2463
+ tag.dataset.pluginCss = "dsh-archive-manager/ArchiveSettings.layout.css";
2464
+ tag.textContent = ARCHIVE_SETTINGS_LAYOUT_OVERRIDE;
2465
+ document.head.appendChild(tag);
2466
+ }
2467
+ /** 管理设置页中的归档会话,数据直接订阅 DSH 的会话与工作区投影。 */
2468
+ function ArchivedSessionsSection({ sessionStore, workspaceStore, unarchiveSession, deleteSession, t }) {
2469
+ const sessions = (0, react.useSyncExternalStore)(sessionStore.subscribe, sessionStore.getSnapshot);
2470
+ const workspaceState = (0, react.useSyncExternalStore)(workspaceStore.subscribe, workspaceStore.getSnapshot);
2471
+ const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
2472
+ const [busy, setBusy] = (0, react.useState)(false);
2473
+ const [error, setError] = (0, react.useState)(null);
2474
+ const [query, setQuery] = (0, react.useState)("");
2475
+ const [chatType, setChatType] = (0, react.useState)("all");
2476
+ const [project, setProject] = (0, react.useState)("all");
2477
+ const groups = (0, react.useMemo)(() => {
2478
+ const archived = new Set(workspaceState.archivedSessionIds);
2479
+ const byWorkspace = workspaceState.items.map((workspace) => ({
2480
+ title: workspace.title,
2481
+ ids: new Set(workspace.sessionIds),
2482
+ sessions: []
2483
+ }));
2484
+ const ungrouped = [];
2485
+ for (const id of workspaceState.archivedSessionIds) {
2486
+ const session = sessions.byId[id];
2487
+ if (session === void 0 || session.origin === "subagent" || !archived.has(id)) continue;
2488
+ const group = byWorkspace.find((workspace) => workspace.ids.has(id));
2489
+ (group === void 0 ? ungrouped : group.sessions).push(session);
2490
+ }
2491
+ const result = byWorkspace.filter((group) => group.sessions.length > 0);
2492
+ if (ungrouped.length > 0) result.push({ title: t("group.ungrouped"), sessions: ungrouped });
2493
+ return result;
2494
+ }, [sessions, workspaceState, t]);
2495
+ const filteredGroups = (0, react.useMemo)(() => {
2496
+ const normalizedQuery = query.trim().toLocaleLowerCase();
2497
+ return groups.filter((group) => project === "all" || project === group.title).map((group) => ({
2498
+ ...group,
2499
+ sessions: group.sessions.filter((session) => (chatType === "all" || session.origin === chatType) && (normalizedQuery === "" || displayTitle(session, t).toLocaleLowerCase().includes(normalizedQuery)))
2500
+ })).filter((group) => group.sessions.length > 0);
2501
+ }, [chatType, groups, project, query, t]);
2502
+ const archivedSessionIds = (0, react.useMemo)(() => groups.flatMap((group) => group.sessions.map((session) => session.id)), [groups]);
2503
+ const onUnarchive = (sessionId) => {
2504
+ setError(null);
2505
+ unarchiveSession(sessionId).catch((reason) => {
2506
+ setError(reason instanceof Error ? reason.message : String(reason));
2507
+ });
2508
+ };
2509
+ const closeDelete = () => {
2510
+ if (!busy) setDeleteTarget(null);
2511
+ };
2512
+ const confirmDelete = async () => {
2513
+ if (busy || deleteTarget === null) return;
2514
+ setBusy(true);
2515
+ setError(null);
2516
+ try {
2517
+ for (const id of deleteTarget.all ? archivedSessionIds : [deleteTarget.id]) await deleteSession(id);
2518
+ setDeleteTarget(null);
2519
+ } catch (reason) {
2520
+ setError(reason instanceof Error ? reason.message : String(reason));
2521
+ } finally {
2522
+ setBusy(false);
2523
+ }
2524
+ };
2525
+ return (0, react_jsx_runtime.jsxs)("section", {
2526
+ className: "dsham_settings",
2527
+ "aria-label": t("archives.title"),
2528
+ children: [(0, react_jsx_runtime.jsx)("style", { children: ARCHIVE_SETTINGS_CSS }), (0, react_jsx_runtime.jsxs)("header", { className: "dsham_settingsHeader", children: [(0, react_jsx_runtime.jsx)("h2", { children: t("archives.title") }), (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "dsham_settingsDanger", disabled: busy || archivedSessionIds.length === 0, onClick: () => setDeleteTarget({ all: true }), children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}), t("archives.deleteAll")] })] }), (0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsToolbar", children: [(0, react_jsx_runtime.jsxs)("label", { className: "dsham_settingsSearch", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}), (0, react_jsx_runtime.jsx)("input", { type: "search", value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("archives.searchPlaceholder"), "aria-label": t("archives.searchPlaceholder") })] }), (0, react_jsx_runtime.jsx)("select", { className: "dsham_settingsFilter", value: chatType, onChange: (event) => setChatType(event.target.value), "aria-label": t("archives.chatFilter"), children: (0, react_jsx_runtime.jsx)("option", { value: "all", children: t("archives.allChats") }) }), (0, react_jsx_runtime.jsx)("select", { className: "dsham_settingsFilter", value: project, onChange: (event) => setProject(event.target.value), "aria-label": t("archives.projectFilter"), children: [(0, react_jsx_runtime.jsx)("option", { value: "all", children: t("archives.allProjects") }), groups.map((group) => (0, react_jsx_runtime.jsx)("option", { value: group.title, children: group.title }, group.title))] })] }), groups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.empty") }) : filteredGroups.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsEmpty", children: t("archives.emptyFiltered") }) : filteredGroups.map((group) => (0, react_jsx_runtime.jsxs)("section", {
2529
+ className: "dsham_settingsGroup",
2530
+ children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsGroupHeading", children: [(0, react_jsx_runtime.jsxs)("h3", { className: "dsham_settingsGroupTitle", children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, {}), group.title] }), (0, react_jsx_runtime.jsx)("span", { className: "dsham_settingsCount", children: t("archives.sessionCount", { n: group.sessions.length }) })] }), (0, react_jsx_runtime.jsx)("div", {
2531
+ className: "dsham_settingsList",
2532
+ children: group.sessions.map((session) => (0, react_jsx_runtime.jsxs)("article", {
2533
+ className: "dsham_settingsRow",
2534
+ children: [(0, react_jsx_runtime.jsxs)("div", { className: "dsham_settingsContent", children: [(0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsTitle", children: displayTitle(session, t) }), (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsMeta", children: archiveTimeLabel(session.updatedAt, t) })] }), (0, react_jsx_runtime.jsxs)("div", {
2535
+ className: "dsham_settingsActions",
2536
+ children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsAction", disabled: busy, onClick: () => onUnarchive(session.id), children: t("menu.unarchive") }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: "dsham_settingsDelete", disabled: busy, "aria-label": t("menu.deleteSession"), onClick: () => setDeleteTarget(session), children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}) })]
2537
+ })]
2538
+ }, session.id))
2539
+ })]
2540
+ }, group.title)), error !== null && (0, react_jsx_runtime.jsx)("div", { className: "dsham_settingsError", role: "alert", children: error }), (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
2541
+ open: deleteTarget !== null,
2542
+ onClose: closeDelete,
2543
+ closeLabel: t("close"),
2544
+ title: deleteTarget?.all ? t("archives.deleteAllTitle") : t("deleteSession.title"),
2545
+ ...deleteTarget === null ? {} : { description: deleteTarget.all ? t("archives.deleteAllDesc", { n: archivedSessionIds.length }) : t("deleteSession.desc", { name: displayTitle(deleteTarget, t) }) },
2546
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: closeDelete, children: t("cancel") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, { variant: "outline", disabled: busy, onClick: confirmDelete, children: deleteTarget?.all ? t("archives.deleteAll") : t("deleteSession.title") })] }),
2547
+ children: busy && (0, react_jsx_runtime.jsx)("div", { role: "status", children: deleteTarget?.all ? t("archives.deleteAllPending") : t("deleteSession.pending") })
2548
+ })]
2549
+ });
2550
+ }
2551
+ //#endregion
2552
+ //#region lib/types/client/locales.js
2553
+ /**
2554
+ * `workspace` namespace dictionaries: the browsing region (section header,
2555
+ * search, tree rows, dialogs) and the pick/add flow. Runtime failure
2556
+ * messages (wire error strings) pass through untranslated by policy.
2557
+ */
2558
+ /** Simplified Chinese dictionary (the key-set source of truth). */
2559
+ const zh = {
2560
+ "group.ungrouped": "未分组",
2561
+ "session.new": "新会话",
2562
+ "section.workspaces": "工作区",
2563
+ "section.sessions": "会话",
2564
+ "viewOptions.label": "视图选项",
2565
+ "groupBy.label": "分组方式",
2566
+ "groupBy.workspace": "按工作区",
2567
+ "groupBy.flat": "单列表",
2568
+ "orderBy.label": "排序方式",
2569
+ "orderBy.manual": "手动排序",
2570
+ "orderBy.updated": "最近更新",
2571
+ "sessions.expand": "展开其余 {n} 个会话",
2572
+ "sessions.collapse": "收起",
2573
+ "empty.none": "暂无会话",
2574
+ "empty.noMatches": "无匹配结果",
2575
+ "workspace.add": "添加工作区",
2576
+ "search.sessions.aria": "搜索会话",
2577
+ "search.placeholder": "搜索会话…",
2578
+ "search.clear": "清除搜索",
2579
+ "search.results.aria": "搜索结果",
2580
+ "search.pending": "正在搜索会话历史…",
2581
+ "search.unavailable": "内容搜索暂不可用,仅显示名称匹配。",
2582
+ "search.noMatches": "无匹配会话",
2583
+ "search.hasMore": "仅显示前 {n} 条结果,请缩小搜索范围。",
2584
+ "menu.addWorkspace": "添加工作区…",
2585
+ "menu.unarchive": "取消归档",
2586
+ "menu.deleteSession": "删除会话",
2587
+ "archived.badge": "已归档",
2588
+ "archived.notOpenable": "已归档,取消归档后可继续对话",
2589
+ "archives.title": "已归档的聊天",
2590
+ "archives.description": "管理已归档的会话。",
2591
+ "archives.empty": "暂无已归档会话",
2592
+ "archives.emptyFiltered": "没有匹配的已归档聊天",
2593
+ "archives.searchPlaceholder": "搜索已归档聊天",
2594
+ "archives.chatFilter": "按聊天类型筛选",
2595
+ "archives.allChats": "全部聊天",
2596
+ "archives.projectFilter": "按项目筛选",
2597
+ "archives.allProjects": "所有项目",
2598
+ "archives.sessionCount": "{n} 个聊天",
2599
+ "archives.timestamp": "{date},{time}",
2600
+ "archives.deleteAll": "全部删除",
2601
+ "archives.deleteAllTitle": "删除全部已归档聊天",
2602
+ "archives.deleteAllDesc": "将永久删除全部 {n} 个已归档聊天及其记录,此操作不可恢复。",
2603
+ "archives.deleteAllPending": "正在删除已归档聊天…",
2604
+ "deleteSession.title": "删除会话",
2605
+ "deleteSession.desc": "将永久删除会话“{name}”及其全部记录(对话内容、统计、缓存),此操作不可恢复。",
2606
+ "deleteSession.pending": "正在删除会话…",
2607
+ "picker.loading": "正在加载工作区…",
2608
+ "conflict.named": "已存在名为“{name}”的工作区。",
2609
+ "folderError.title": "无法打开文件夹",
2610
+ "folderError.retry": "重新选择",
2611
+ "rename": "重命名",
2612
+ "rename.workspace.title": "重命名工作区",
2613
+ "rename.session.title": "重命名会话",
2614
+ "field.workspaceName": "工作区名称",
2615
+ "field.sessionName": "会话名称",
2616
+ "delete.workspace": "删除工作区",
2617
+ "delete.desc": "将把“{name}”从工作区列表中移除。文件夹与会话记录会保留,其会话将显示在“未分组”下。",
2618
+ "delete.pending": "正在删除工作区…",
2619
+ "menu.fork": "分叉会话",
2620
+ "menu.archiveSession": "归档会话",
2621
+ "sessions.count.one": "{n} 个会话",
2622
+ "sessions.count.other": "{n} 个会话",
2623
+ "actions.workspace.aria": "工作区“{name}”的操作",
2624
+ "actions.session.aria": "会话“{name}”的操作",
2625
+ "actions.newSession.aria": "在“{name}”中新建会话",
2626
+ "status.running": "进行中",
2627
+ "status.subagentsRunning.one": "{n} 个子代理运行中",
2628
+ "status.subagentsRunning.other": "{n} 个子代理运行中",
2629
+ "status.idle": "空闲",
2630
+ "status.waitingApproval": "等待审批",
2631
+ "status.planReview": "计划待审",
2632
+ "status.waitingAnswer": "等待回答",
2633
+ "status.completed": "已完成",
2634
+ "hover.created": "创建于 {time}",
2635
+ "hover.copied": "已复制",
2636
+ "date.ymd": "{y}年{m}月{d}日",
2637
+ "time.now": "刚刚",
2638
+ "time.minutes": "{n}分钟",
2639
+ "time.hours": "{n}小时",
2640
+ "time.days": "{n}天",
2641
+ "time.months": "{n}个月",
2642
+ "time.years": "{n}年",
2643
+ "time.ago": "{t}前"
2644
+ };
2645
+ /** English dictionary, checked complete against the zh key set. */
2646
+ const en = {
2647
+ "group.ungrouped": "Ungrouped",
2648
+ "session.new": "New Session",
2649
+ "section.workspaces": "Workspaces",
2650
+ "section.sessions": "Sessions",
2651
+ "viewOptions.label": "View options",
2652
+ "groupBy.label": "Group by",
2653
+ "groupBy.workspace": "WorkSpace",
2654
+ "groupBy.flat": "In one list",
2655
+ "orderBy.label": "Order by",
2656
+ "orderBy.manual": "Manual",
2657
+ "orderBy.updated": "Last updated",
2658
+ "sessions.expand": "Show {n} more sessions",
2659
+ "sessions.collapse": "Show less",
2660
+ "empty.none": "No sessions yet",
2661
+ "empty.noMatches": "No matches",
2662
+ "workspace.add": "Add workspace",
2663
+ "search.sessions.aria": "Search sessions",
2664
+ "search.placeholder": "Search sessions...",
2665
+ "search.clear": "Clear search",
2666
+ "search.results.aria": "Search results",
2667
+ "search.pending": "Searching session history…",
2668
+ "search.unavailable": "Content search is temporarily unavailable. Showing name matches.",
2669
+ "search.noMatches": "No matching sessions",
2670
+ "search.hasMore": "Showing the first {n} results. Narrow your search.",
2671
+ "menu.addWorkspace": "Add workspace…",
2672
+ "menu.unarchive": "Unarchive",
2673
+ "menu.deleteSession": "Delete session",
2674
+ "archived.badge": "Archived",
2675
+ "archived.notOpenable": "This session is archived. Unarchive it to continue the conversation.",
2676
+ "archives.title": "Archived chats",
2677
+ "archives.description": "Manage archived sessions.",
2678
+ "archives.empty": "No archived sessions.",
2679
+ "archives.emptyFiltered": "No archived chats match your filters.",
2680
+ "archives.searchPlaceholder": "Search archived chats",
2681
+ "archives.chatFilter": "Filter by chat type",
2682
+ "archives.allChats": "All chats",
2683
+ "archives.projectFilter": "Filter by project",
2684
+ "archives.allProjects": "All projects",
2685
+ "archives.sessionCount": "{n} chats",
2686
+ "archives.timestamp": "{date}, {time}",
2687
+ "archives.deleteAll": "Delete all",
2688
+ "archives.deleteAllTitle": "Delete all archived chats",
2689
+ "archives.deleteAllDesc": "This permanently deletes all {n} archived chats and their records. This cannot be undone.",
2690
+ "archives.deleteAllPending": "Deleting archived chats…",
2691
+ "deleteSession.title": "Delete session",
2692
+ "deleteSession.desc": "This permanently deletes session “{name}” and all of its records (conversation, stats, cache). This cannot be undone.",
2693
+ "deleteSession.pending": "Deleting session…",
2694
+ "picker.loading": "Loading workspaces…",
2695
+ "conflict.named": "A workspace named “{name}” already exists.",
2696
+ "folderError.title": "Couldn’t open folder",
2697
+ "folderError.retry": "Choose again",
2698
+ "rename": "Rename",
2699
+ "rename.workspace.title": "Rename workspace",
2700
+ "rename.session.title": "Rename session",
2701
+ "field.workspaceName": "Workspace name",
2702
+ "field.sessionName": "Session name",
2703
+ "delete.workspace": "Delete workspace",
2704
+ "delete.desc": "This removes “{name}” from the workspace list. The folder and session logs will be kept. Its sessions will appear under Ungrouped.",
2705
+ "delete.pending": "Deleting workspace…",
2706
+ "menu.fork": "Fork session",
2707
+ "menu.archiveSession": "Archive session",
2708
+ "sessions.count.one": "{n} session",
2709
+ "sessions.count.other": "{n} sessions",
2710
+ "actions.workspace.aria": "Workspace actions for {name}",
2711
+ "actions.session.aria": "Session actions for {name}",
2712
+ "actions.newSession.aria": "New session in {name}",
2713
+ "status.running": "Running",
2714
+ "status.subagentsRunning.one": "{n} subagent running",
2715
+ "status.subagentsRunning.other": "{n} subagents running",
2716
+ "status.idle": "Idle",
2717
+ "status.waitingApproval": "Waiting for approval",
2718
+ "status.planReview": "Plan awaiting review",
2719
+ "status.waitingAnswer": "Waiting for answer",
2720
+ "status.completed": "Completed",
2721
+ "hover.created": "Created {time}",
2722
+ "hover.copied": "Copied",
2723
+ "date.ymd": "{y}-{m}-{d}",
2724
+ "time.now": "now",
2725
+ "time.minutes": "{n}min",
2726
+ "time.hours": "{n}h",
2727
+ "time.days": "{n}d",
2728
+ "time.months": "{n}mo",
2729
+ "time.years": "{n}y",
2730
+ "time.ago": "{t} ago"
2731
+ };
2732
+ //#endregion
2733
+ //#region lib/types/client/index.js
2734
+ /** Dictionary namespace owned by this plugin. */
2735
+ const NS = "workspace";
2736
+ /**
2737
+ * Required services (cordis fiber inject). The target slots are declared by
2738
+ * the ui-sidebar / ui-conversation applies, whose activation order relative
2739
+ * to this one is NOT constrained: dsh.client.inject edges are informational
2740
+ * (loading/prefetch metadata, never apply sequencing) and neither owner
2741
+ * provides a waitable service. apply therefore depends on each slot
2742
+ * declaration through `slots.inject()` instead of assuming order.
2743
+ */
2744
+ const inject = [
2745
+ "slots",
2746
+ "sessions",
2747
+ "workspaces",
2748
+ "locale",
2749
+ "remote",
2750
+ "typert"
2751
+ ];
2752
+ /**
2753
+ * Plugin body: mount the archive-manager Remote contribution, then
2754
+ * register the workspace browser. `$mount` must complete before the
2755
+ * injected actions can reach `ctx.remote.workspaceRegistry`, so the
2756
+ * plugin applies asynchronously and returns a disposer that unmounts the
2757
+ * contribution (the slot registrations and dictionaries are
2758
+ * effect-scoped inside `applyWorkspaceBrowser`).
2759
+ * @param ctx - client root context.
2760
+ */
2761
+ async function apply(ctx) {
2762
+ const remote = ctx.get("remote");
2763
+ let disposeRemote = () => {};
2764
+ if (remote !== void 0) disposeRemote = await remote.$mount(ARCHIVE_MANAGER_REMOTE);
2765
+ applyWorkspaceBrowser(ctx);
2766
+ return async () => {
2767
+ await disposeRemote();
2768
+ };
2769
+ }
2770
+ /**
2771
+ * Register the browser and picker once their slot declarations are on the
2772
+ * ledger. Inject factories return plain callbacks; data reads use the
2773
+ * framework's global hooks.
2774
+ * @param ctx - client root context.
2775
+ */
2776
+ function applyWorkspaceBrowser(ctx) {
2777
+ ctx.effect(() => ctx.locale.register(NS, {
2778
+ zh,
2779
+ en
2780
+ }), "dsh-archive-manager: dictionaries");
2781
+ const searchSessions = async (query, signal) => {
2782
+ const result = await ctx.sessions.search(query, signal);
2783
+ if (!result.ok) throw new Error(result.error.message);
2784
+ return result.value;
2785
+ };
2786
+ const flowSource = (hole) => ({
2787
+ getSnapshot: () => ctx.slots.entries(hole).length > 0,
2788
+ subscribe: (listener) => ctx.slots.subscribe(hole, listener)
2789
+ });
2790
+ const browserFlowSource = flowSource("sidebar.workspaces.directoryFlow");
2791
+ const pickerFlowSource = flowSource("conversation.hero.workspace.directoryFlow");
2792
+ const unarchiveSession = async (sessionId) => {
2793
+ const registry = ctx.get("remote.workspaceRegistry");
2794
+ if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
2795
+ const result = await registry.unarchiveSession(sessionId);
2796
+ if (!result.ok) throw new Error(result.error.message);
2797
+ };
2798
+ const deleteSession = async (sessionId) => {
2799
+ const registry = ctx.get("remote.workspaceRegistry");
2800
+ if (registry === void 0) throw new Error("archive-manager remote service is unavailable");
2801
+ const result = await registry.deleteSession(sessionId);
2802
+ if (!result.ok) throw new Error(result.error.message);
2803
+ };
2804
+ const browserInjected = () => ({
2805
+ startSession: (workspaceId) => {
2806
+ ctx.workspaces.startSession(workspaceId);
2807
+ },
2808
+ open: (sessionId) => {
2809
+ ctx.sessions.open(sessionId);
2810
+ },
2811
+ searchSessions,
2812
+ searchResultLimit: ctx.sessions.searchResultLimit,
2813
+ renameSession: async (sessionId, title) => {
2814
+ const session = ctx.sessions.binding(sessionId)?.session;
2815
+ if (session === void 0) throw new Error(`unknown session "${sessionId}"`);
2816
+ const result = await session.rename(title);
2817
+ if (!result.ok) throw new Error(result.error.message);
2818
+ },
2819
+ forkSession: (sessionId) => {
2820
+ ctx.sessions.fork({
2821
+ sessionId,
2822
+ increaseTitle: true
2823
+ }).then((childId) => {
2824
+ ctx.sessions.open(childId);
2825
+ }).catch(() => {});
2826
+ },
2827
+ renameWorkspace: async (workspaceId, title) => {
2828
+ await ctx.workspaces.rename(workspaceId, title);
2829
+ },
2830
+ deleteWorkspace: async (workspaceId) => {
2831
+ await ctx.workspaces.delete(workspaceId);
2832
+ },
2833
+ insertWorkspaceBefore: async (workspaceId, beforeWorkspaceId) => {
2834
+ await ctx.workspaces.insertBefore(workspaceId, beforeWorkspaceId);
2835
+ },
2836
+ archiveSession: async (sessionId) => {
2837
+ await ctx.workspaces.archiveSession(sessionId);
2838
+ },
2839
+ unarchiveSession,
2840
+ deleteSession,
2841
+ insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
2842
+ await ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
2843
+ },
2844
+ createWorkspace: (input) => ctx.workspaces.create(input),
2845
+ hooks: { directoryFlow: browserFlowSource }
2846
+ });
2847
+ const pickerInjected = () => ({
2848
+ createWorkspace: (input) => ctx.workspaces.create(input),
2849
+ hooks: { directoryFlow: pickerFlowSource }
2850
+ });
2851
+ ctx.slots.inject("sidebar.workspaces", () => ctx.slots.register({
2852
+ name: "sidebar.workspaces",
2853
+ children: { "sidebar.workspaces.directoryFlow": {
2854
+ kind: "single",
2855
+ scope: "root"
2856
+ } },
2857
+ store: createWorkspaceViewStore(),
2858
+ inject: browserInjected,
2859
+ locale: NS
2860
+ }, WorkspaceBrowser));
2861
+ ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
2862
+ name: "conversation.hero.workspace",
2863
+ children: { "conversation.hero.workspace.directoryFlow": {
2864
+ kind: "single",
2865
+ scope: "root"
2866
+ } },
2867
+ inject: pickerInjected,
2868
+ locale: NS
2869
+ }, WorkspacePicker));
2870
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
2871
+ name: "settings.section",
2872
+ id: "archived-sessions",
2873
+ order: 18,
2874
+ label: () => ctx.locale.bind(NS)("archived.badge"),
2875
+ locale: NS,
2876
+ inject: () => ({
2877
+ sessionStore: ctx.sessions.list,
2878
+ workspaceStore: ctx.workspaces.list,
2879
+ unarchiveSession,
2880
+ deleteSession,
2881
+ t: ctx.locale.bind(NS)
2882
+ })
2883
+ }, ArchivedSessionsSection));
2884
+ }
2885
+ //#endregion
2886
+ /** Pure derivation surface for dsh-archive-manager self-tests (no-op for the runtime). */
2887
+ exports.__test = {
2888
+ sessionVisible,
2889
+ deriveGroups,
2890
+ deriveFlat,
2891
+ deriveSearchResults,
2892
+ createWorkspaceViewStore,
2893
+ groupByWorkspace,
2894
+ byRecency,
2895
+ ARCHIVE_MANAGER_REMOTE
2896
+ };
2897
+ exports.apply = apply;
2898
+ exports.inject = inject;
2899
+ return module.exports;
2900
+ }
2901
+ });
2902
+
2903
+ //# sourceMappingURL=client.js.map