@deepseek-ai/dsh-client-ui-workspace 0.1.6-alpha.1 → 0.1.6-alpha.2

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 CHANGED
@@ -28,6 +28,8 @@ window.__ModuleLoader__.load({
28
28
  sessions;
29
29
  connecting = /* @__PURE__ */ new Map();
30
30
  lifetime = new AbortController();
31
+ selection = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({}, { persist: { name: "dsh.sessions.current" } });
32
+ mainReference;
31
33
  /**
32
34
  * @param ctx - Client root Context.
33
35
  * @param directoryPicker - the directory-picking Remote namespace.
@@ -39,7 +41,16 @@ window.__ModuleLoader__.load({
39
41
  this.directoryPicker = directoryPicker;
40
42
  this.workspaces = workspaces;
41
43
  this.sessions = sessions;
42
- ctx.effect(() => this.watchNavigation(), "ui-workspace: Workspace navigation policy");
44
+ ctx.effect(() => {
45
+ const stop = this.watchNavigation();
46
+ return () => {
47
+ stop();
48
+ this.lifetime.abort();
49
+ const reference = this.mainReference;
50
+ this.mainReference = void 0;
51
+ reference?.release();
52
+ };
53
+ }, "ui-workspace: Workspace navigation policy");
43
54
  }
44
55
  async connectWorkspace(workspaceId) {
45
56
  const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
@@ -58,17 +69,14 @@ window.__ModuleLoader__.load({
58
69
  this.connecting.set(workspaceId, attempt);
59
70
  return attempt;
60
71
  }
61
- openSession(sessionId) {
62
- this.sessions.open(sessionId);
63
- this.ctx.layout.selectPanel(null);
72
+ openSession(target) {
73
+ this.replaceMain(target, this.lifetime.signal);
64
74
  }
65
75
  async openWorkspace(workspaceId, beforeOpen) {
66
76
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
67
- const isCurrent = () => !navigation.aborted;
68
77
  const sessionId = await this.connectWorkspace(workspaceId);
69
- if (!isCurrent()) return;
70
- beforeOpen?.(sessionId);
71
- if (isCurrent()) this.openSession(sessionId);
78
+ if (navigation.aborted) return;
79
+ this.replaceMain(sessionId, navigation, beforeOpen);
72
80
  }
73
81
  async forkSession(sessionId) {
74
82
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
@@ -76,18 +84,17 @@ window.__ModuleLoader__.load({
76
84
  sessionId,
77
85
  increaseTitle: true
78
86
  });
79
- if (!navigation.aborted) this.openSession(childId);
87
+ if (!navigation.aborted) this.replaceMain(childId, navigation);
80
88
  }
81
89
  startSession(workspaceId) {
82
90
  const workspace = this.workspaces.list.getSnapshot();
83
91
  const sessions = this.sessions.list.getSnapshot();
84
- const current = sessions.current;
92
+ const current = this.mainReference?.sessionId;
85
93
  const currentWorkspaceId = current === void 0 ? void 0 : workspace.items.find((item) => item.sessionIds.includes(current))?.workspaceId;
86
94
  const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
87
95
  const target = workspaceId ?? currentWorkspaceId ?? recent;
88
96
  if (target === void 0) {
89
- this.sessions.clear();
90
- this.ctx.layout.selectPanel(null);
97
+ this.clearMain();
91
98
  return;
92
99
  }
93
100
  this.openWorkspace(target).catch((reason) => {
@@ -96,6 +103,7 @@ window.__ModuleLoader__.load({
96
103
  }
97
104
  async archiveSession(sessionId) {
98
105
  await this.workspaces.archiveSession(sessionId);
106
+ if (this.mainReference?.sessionId === sessionId) this.clearMain();
99
107
  }
100
108
  async unarchiveSession(sessionId) {
101
109
  await this.workspaces.unarchiveSession(sessionId);
@@ -124,10 +132,24 @@ window.__ModuleLoader__.load({
124
132
  const workspace = this.workspaces.list.getSnapshot();
125
133
  const sessions = this.sessions.list.getSnapshot();
126
134
  if (workspace.phase !== "ready" || sessions.phase !== "ready") return;
127
- if (sessions.current !== void 0) {
135
+ if (this.mainReference !== void 0) {
128
136
  initial = "done";
129
137
  return;
130
138
  }
139
+ const saved = this.selection.getSnapshot();
140
+ const savedTarget = saved.subagentAddress ?? (saved.sessionId !== void 0 && sessions.byId[saved.sessionId] !== void 0 ? saved.sessionId : void 0);
141
+ if (savedTarget !== void 0) {
142
+ initial = "connecting";
143
+ try {
144
+ if (saved.subagentAddress !== void 0) this.sessions.refreshSubagents(saved.subagentAddress.parentSessionId);
145
+ this.openSession(savedTarget);
146
+ initial = "done";
147
+ } catch (reason) {
148
+ initial = "waiting";
149
+ console.warn("initial Session restoration failed:", reason);
150
+ }
151
+ return;
152
+ }
131
153
  const target = recentWorkspace(workspace.items, sessions.byId);
132
154
  if (target === void 0) {
133
155
  initial = "done";
@@ -135,8 +157,8 @@ window.__ModuleLoader__.load({
135
157
  }
136
158
  initial = "connecting";
137
159
  this.connectWorkspace(target).then((sessionId) => {
138
- if (this.lifetime.signal.aborted) return;
139
- if (this.sessions.list.getSnapshot().current === void 0) this.sessions.open(sessionId);
160
+ if (this.mainReference === void 0) this.openSession(sessionId);
161
+ }).then(() => {
140
162
  initial = "done";
141
163
  }, (reason) => {
142
164
  if (this.lifetime.signal.aborted) return;
@@ -155,11 +177,43 @@ window.__ModuleLoader__.load({
155
177
  }
156
178
  /** @returns true when an archived current selection was cleared. */
157
179
  clearArchivedCurrent() {
158
- const current = this.sessions.list.getSnapshot().current;
180
+ const current = this.mainReference?.sessionId;
159
181
  if (current === void 0 || !this.workspaces.list.getSnapshot().archivedSessionIds.includes(current)) return false;
160
- this.sessions.clear();
182
+ this.clearMain();
161
183
  return true;
162
184
  }
185
+ clearMain() {
186
+ const previous = this.mainReference;
187
+ this.mainReference = void 0;
188
+ this.selection.set({});
189
+ previous?.release();
190
+ this.ctx.layout.selectPanel(null);
191
+ }
192
+ replaceMain(target, signal, beforeOpen) {
193
+ signal.throwIfAborted();
194
+ const reference = this.sessions.retain(target, { source: "mainView" });
195
+ try {
196
+ signal.throwIfAborted();
197
+ beforeOpen?.(reference.sessionId);
198
+ if (signal.aborted) {
199
+ reference.release();
200
+ return;
201
+ }
202
+ const subagentAddress = typeof target === "string" ? this.sessions.subagentAddress(reference.sessionId) : target;
203
+ this.selection.set({
204
+ sessionId: reference.sessionId,
205
+ ...subagentAddress === void 0 ? {} : { subagentAddress }
206
+ });
207
+ } catch (error) {
208
+ reference.release();
209
+ throw error;
210
+ }
211
+ const previous = this.mainReference;
212
+ this.mainReference = reference;
213
+ previous?.release();
214
+ this.sessions.refreshSubagents(reference.sessionId);
215
+ this.ctx.layout.selectPanel(null);
216
+ }
163
217
  };
164
218
  /** Stable tie-breaking follows Host Workspace order. */
165
219
  function recentWorkspace(workspaces, sessions) {
@@ -323,6 +377,9 @@ window.__ModuleLoader__.load({
323
377
  function owningGroupKey(workspaces, sessionId) {
324
378
  return workspaces.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? "";
325
379
  }
380
+ function mainSessionId(list) {
381
+ return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
382
+ }
326
383
  /**
327
384
  * Directory display label: basename of the path (both separators accepted).
328
385
  * Ungrouped-bucket fallback for surfaces without a workspace title.
@@ -434,6 +491,7 @@ window.__ModuleLoader__.load({
434
491
  * recency before that order is initialized.
435
492
  */
436
493
  function groupByWorkspace(list, workspaces, archived, ungroupedOrder) {
494
+ const current = mainSessionId(list);
437
495
  const groups = [];
438
496
  const accounted = /* @__PURE__ */ new Set();
439
497
  for (const workspace of workspaces) {
@@ -442,12 +500,12 @@ window.__ModuleLoader__.load({
442
500
  const summary = list.byId[id];
443
501
  if (summary === void 0) continue;
444
502
  accounted.add(id);
445
- if (!sessionVisible(summary, list.current, archived)) continue;
503
+ if (!sessionVisible(summary, current, archived)) continue;
446
504
  members.push(summary);
447
505
  }
448
506
  groups.push(buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members));
449
507
  }
450
- const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, list.current, archived));
508
+ const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, current, archived));
451
509
  if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, "", orderedUngrouped(stray, ungroupedOrder, list.byId)));
452
510
  return groups;
453
511
  }
@@ -460,15 +518,16 @@ window.__ModuleLoader__.load({
460
518
  default: return;
461
519
  }
462
520
  }
463
- function sessionNode(s, descendants, pendingInteractions) {
464
- const pendingInteraction = visiblePendingKind(pendingInteractions.get(s.id)?.kind);
521
+ function sessionNode(s, descendants, statuses) {
522
+ const status = statuses.get(s.id);
523
+ const pendingInteraction = visiblePendingKind(status?.pendingInteraction?.kind);
465
524
  return {
466
525
  id: s.id,
467
526
  title: sessionTitle(s),
468
527
  blank: s.blank,
469
- running: s.running,
528
+ running: status?.running ?? s.running,
470
529
  runningSubagentCount: descendants.get(s.id)?.runningCount ?? 0,
471
- completed: s.completed === true,
530
+ completed: status?.completionUnread === true,
472
531
  hasActiveSchedule: hasActiveSchedule(s),
473
532
  updatedAt: s.updatedAt,
474
533
  ...pendingInteraction === void 0 ? {} : { pendingInteraction }
@@ -482,18 +541,19 @@ window.__ModuleLoader__.load({
482
541
  * provisional New Session row; archived sessions are excluded everywhere.
483
542
  * Content search lives outside this derivation
484
543
  * (see {@link deriveSearchResults}).
485
- * @param list - sessions list snapshot (`current` feeds containsCurrent).
544
+ * @param list - sessions list snapshot (`mainView` retention feeds containsCurrent).
486
545
  * @param workspaces - real Workspaces in Host group order with caller-projected Session order.
487
546
  * @param archivedSessionIds - registry-global archive set.
488
- * @param pendingInteractions - pending UI interactions by Session.
547
+ * @param statuses - unified UI status by Session.
489
548
  * @param view - local expansion arrays.
490
549
  * @returns group sections in render order.
491
550
  */
492
- function deriveGroups(list, workspaces, archivedSessionIds, pendingInteractions, view) {
551
+ function deriveGroups(list, workspaces, archivedSessionIds, statuses, view) {
493
552
  const archived = new Set(archivedSessionIds);
494
553
  const expandedGroups = new Set(view.expandedGroups);
495
554
  const descendants = indexSubagentDescendants(list.byId);
496
- const currentGroup = list.current === void 0 ? void 0 : owningGroupKey(workspaces, list.current);
555
+ const current = mainSessionId(list);
556
+ const currentGroup = current === void 0 ? void 0 : owningGroupKey(workspaces, current);
497
557
  const groups = [];
498
558
  for (const g of groupByWorkspace(list, workspaces, archived, view.ungroupedOrder)) {
499
559
  const expanded = expandedGroups.has(g.key);
@@ -506,7 +566,7 @@ window.__ModuleLoader__.load({
506
566
  sessionCount: g.sessions.length,
507
567
  expanded,
508
568
  containsCurrent: g.key === currentGroup,
509
- sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, pendingInteractions)) : []
569
+ sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, statuses)) : []
510
570
  });
511
571
  }
512
572
  return groups;
@@ -519,21 +579,22 @@ window.__ModuleLoader__.load({
519
579
  */
520
580
  function visibleSessionIds(list, archivedSessionIds) {
521
581
  const archived = new Set(archivedSessionIds);
582
+ const current = mainSessionId(list);
522
583
  return list.ids.filter((id) => {
523
584
  const s = list.byId[id];
524
- return s !== void 0 && sessionVisible(s, list.current, archived);
585
+ return s !== void 0 && sessionVisible(s, current, archived);
525
586
  });
526
587
  }
527
588
  /**
528
589
  * Derive flat rows from the browser's ordered visible Session ids.
529
590
  * @param list - sessions list snapshot used to select the ids.
530
591
  * @param sessionIds - known visible members in render order, including any pinned blank.
531
- * @param pendingInteractions - pending UI interactions by Session.
592
+ * @param statuses - unified UI status by Session.
532
593
  * @returns flat rows in the supplied order with current status indicators.
533
594
  */
534
- function deriveFlat(list, sessionIds, pendingInteractions) {
595
+ function deriveFlat(list, sessionIds, statuses) {
535
596
  const descendants = indexSubagentDescendants(list.byId);
536
- return sessionIds.map((id) => sessionNode(list.byId[id], descendants, pendingInteractions));
597
+ return sessionIds.map((id) => sessionNode(list.byId[id], descendants, statuses));
537
598
  }
538
599
  /**
539
600
  * Merge immediate title/Workspace substring matches with ranked Host content
@@ -543,12 +604,12 @@ window.__ModuleLoader__.load({
543
604
  * @param workspaces - Workspace membership and display labels.
544
605
  * @param query - caller text; surrounding whitespace is ignored.
545
606
  * @param archivedSessionIds - registry-global archive set (members never match).
546
- * @param pendingInteractions - pending UI interactions by Session.
607
+ * @param statuses - unified UI status by Session.
547
608
  * @param content - ranked Host content-search page.
548
609
  * @param limit - protocol-owned maximum merged row count.
549
610
  * @returns bounded deduplicated flat rows and a refine-query hint bit.
550
611
  */
551
- function deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, content, limit) {
612
+ function deriveSearchResults(list, workspaces, query, archivedSessionIds, statuses, content, limit) {
552
613
  const q = query.trim().toLowerCase();
553
614
  if (q === "") return {
554
615
  items: [],
@@ -556,6 +617,7 @@ window.__ModuleLoader__.load({
556
617
  };
557
618
  const archived = new Set(archivedSessionIds);
558
619
  const descendants = indexSubagentDescendants(list.byId);
620
+ const current = mainSessionId(list);
559
621
  const workspaceBySession = /* @__PURE__ */ new Map();
560
622
  for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
561
623
  const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
@@ -564,7 +626,7 @@ window.__ModuleLoader__.load({
564
626
  const local = [];
565
627
  for (const id of list.ids) {
566
628
  const summary = list.byId[id];
567
- if (summary === void 0 || summary.blank || !sessionVisible(summary, list.current, archived)) continue;
629
+ if (summary === void 0 || summary.blank || !sessionVisible(summary, current, archived)) continue;
568
630
  if (sessionTitle(summary).toLowerCase().includes(q) || labelOf(summary).toLowerCase().includes(q)) local.push(summary);
569
631
  }
570
632
  const localById = new Map(local.map((summary) => [summary.id, summary]));
@@ -579,20 +641,21 @@ window.__ModuleLoader__.load({
579
641
  for (const summary of orderedLocal) include(summary);
580
642
  for (const item of content.items) {
581
643
  const summary = list.byId[item.sessionId];
582
- if (summary !== void 0 && !summary.blank && sessionVisible(summary, list.current, archived)) include(summary);
644
+ if (summary !== void 0 && !summary.blank && sessionVisible(summary, current, archived)) include(summary);
583
645
  }
584
646
  return {
585
647
  items: ordered.slice(0, limit).map((summary) => {
586
648
  const match = contentBySession.get(summary.id);
587
- const pendingInteraction = visiblePendingKind(pendingInteractions.get(summary.id)?.kind);
649
+ const status = statuses.get(summary.id);
650
+ const pendingInteraction = visiblePendingKind(status?.pendingInteraction?.kind);
588
651
  return {
589
652
  id: summary.id,
590
653
  title: sessionTitle(summary),
591
654
  workspace: labelOf(summary),
592
- running: summary.running,
655
+ running: status?.running ?? summary.running,
593
656
  runningSubagentCount: descendants.get(summary.id)?.runningCount ?? 0,
594
657
  ...pendingInteraction === void 0 ? {} : { pendingInteraction },
595
- completed: summary.completed === true,
658
+ completed: status?.completionUnread === true,
596
659
  hasActiveSchedule: hasActiveSchedule(summary),
597
660
  ...match === void 0 ? {} : { snippet: match.snippet }
598
661
  };
@@ -600,9 +663,33 @@ window.__ModuleLoader__.load({
600
663
  hasMore: content.hasMore || ordered.length > limit
601
664
  };
602
665
  }
666
+ /** Normalize separators for comparison without interpreting POSIX backslashes as separators. */
667
+ function folderPath(path) {
668
+ return (/^[A-Za-z]:[/\\]/.test(path) || path.startsWith("\\\\") ? path.replaceAll("\\", "/") : path).replace(/\/+$/, "");
669
+ }
670
+ /**
671
+ * Find the nearest registered ancestor, excluding the Workspace directory itself.
672
+ * Paths use Host spelling; matching is case-sensitive, like Workspace identity.
673
+ * @param path - Workspace directory.
674
+ * @param parents - registered Workspace directory paths.
675
+ * @returns the owning parent path, or undefined when no parent contains the Workspace.
676
+ */
677
+ function owningParentFolder(path, parents) {
678
+ const child = folderPath(path);
679
+ let owner;
680
+ let length = -1;
681
+ for (const parent of parents) {
682
+ const root = folderPath(parent);
683
+ if (root.length > length && child !== root && child.startsWith(`${root}/`)) {
684
+ owner = parent;
685
+ length = root.length;
686
+ }
687
+ }
688
+ return owner;
689
+ }
603
690
  //#endregion
604
691
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
605
- 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;flex:0 auto;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:.5px solid var(--dsw-alias-border-l4);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_scheduleIndicator{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex}.YDXeBa_searchScheduleIndicator{margin-left:4px;margin-right:0}.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}}";
692
+ const css$2 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));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;flex:0 auto;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:.5px solid var(--dsw-alias-border-l4);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{scroll-behavior:smooth;flex:1}@media (hover:hover){.YDXeBa_sessionRow:hover .YDXeBa_title{text-overflow:clip}}.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_scheduleIndicator{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex}.YDXeBa_searchScheduleIndicator{margin-left:4px;margin-right:0}.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}.YDXeBa_sessionRow .YDXeBa_title{scroll-behavior:auto}}";
606
693
  const tagId$2 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
607
694
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
608
695
  const tag = document.createElement("style");
@@ -654,14 +741,39 @@ window.__ModuleLoader__.load({
654
741
  /**
655
742
  * Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
656
743
  * all data and callbacks arrive via props. Hover swaps (folder->chevron,
657
- * time->ellipsis, action buttons) are CSS-only. Row ... menus are visual-only
658
- * except workspace Rename/Delete and session Rename/Fork/Archive; the session
659
- * and workspace hover cards are suppressed while a menu is open.
744
+ * time->ellipsis, action buttons) are CSS-only, and a session row's clipped
745
+ * title is scrolled programmatically while the row is hovered. Row ... menus are
746
+ * visual-only except workspace Rename/Delete and session Rename/Fork/Archive; the
747
+ * session and workspace hover cards are suppressed while a menu is open.
660
748
  */
661
749
  /** Row display title: blank rows show the localized New Session label. */
662
750
  function displayTitle(node, t) {
663
751
  return node.blank ? t("session.new") : node.title;
664
752
  }
753
+ /**
754
+ * Reveal a title wider than its one-line cell while its row is hovered: the
755
+ * title clips its own text, so the far edge (a fork's incremented title, for
756
+ * example) is reachable by scrolling the element to its end. Leaving returns it
757
+ * to the start in one step, because the resting ellipsis and the narrowed cell
758
+ * would otherwise meet the text while it travelled back. A title that fits has
759
+ * no scroll range to move, and the stylesheet decides whether either move
760
+ * glides or jumps.
761
+ * @param title - the row's clipping title element.
762
+ * @param revealed - whether the pointer is on the row.
763
+ */
764
+ function revealClippedTitle(title, revealed) {
765
+ /* v8 ignore next -- defensive: the title span renders unconditionally. */
766
+ if (title === null) return;
767
+ if (revealed) {
768
+ title.scrollLeft = title.scrollWidth - title.clientWidth;
769
+ return;
770
+ }
771
+ if (typeof title.scrollTo === "function") title.scrollTo({
772
+ left: 0,
773
+ behavior: "instant"
774
+ });
775
+ else title.scrollLeft = 0;
776
+ }
665
777
  /** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
666
778
  function timeLabel(updatedAt, now, t) {
667
779
  const { unit, n } = (0, _deepseek_ai_dsh_client_ui_primitives.relativeTime)(updatedAt, now);
@@ -717,6 +829,7 @@ window.__ModuleLoader__.load({
717
829
  * Workspace shows its hover card (the ungrouped bucket has none).
718
830
  * `containsCurrent` arrives on the node (derivation fact, no renderer scan).
719
831
  * @param props.group - derived group node.
832
+ * @param props.containsCurrentDescendant - highlight an ancestor even when its subtree is collapsed.
720
833
  * @param props.onToggle - expand/collapse the group.
721
834
  * @param props.onCreate - start a frontend Session inside this Workspace.
722
835
  * @param props.drag - optional workspace-row drag wiring.
@@ -724,10 +837,10 @@ window.__ModuleLoader__.load({
724
837
  * @param props.t - the browser root's locale seat.
725
838
  * @returns the row element.
726
839
  */
727
- function ProjectRowItem({ group, onToggle, onCreate, actions, drag, home, t }) {
840
+ function ProjectRowItem({ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag, home, t }) {
728
841
  const row = group;
729
842
  const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
730
- const active = group.expanded && group.containsCurrent;
843
+ const active = containsCurrentDescendant || group.expanded && group.containsCurrent;
731
844
  const [menuOpen, setMenuOpen] = (0, react.useState)(false);
732
845
  const workspaceMenuItems = [{
733
846
  id: "rename",
@@ -991,6 +1104,7 @@ window.__ModuleLoader__.load({
991
1104
  const draggable = drag !== void 0 && !row.blank;
992
1105
  const [menuOpen, setMenuOpen] = (0, react.useState)(false);
993
1106
  const rowRef = (0, react.useRef)(null);
1107
+ const titleRef = (0, react.useRef)(null);
994
1108
  (0, react.useEffect)(() => {
995
1109
  if (onReveal === void 0) return;
996
1110
  rowRef.current?.scrollIntoView({ block: "nearest" });
@@ -1022,6 +1136,12 @@ window.__ModuleLoader__.load({
1022
1136
  onClick: () => {
1023
1137
  onOpen(node.id);
1024
1138
  },
1139
+ onPointerEnter: () => {
1140
+ revealClippedTitle(titleRef.current, true);
1141
+ },
1142
+ onPointerLeave: () => {
1143
+ revealClippedTitle(titleRef.current, false);
1144
+ },
1025
1145
  draggable,
1026
1146
  onDragStart: drag === void 0 || row.blank ? void 0 : (e) => {
1027
1147
  e.dataTransfer.effectAllowed = "move";
@@ -1046,6 +1166,7 @@ window.__ModuleLoader__.load({
1046
1166
  children: showStatus && (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
1047
1167
  }),
1048
1168
  (0, react_jsx_runtime.jsx)("span", {
1169
+ ref: titleRef,
1049
1170
  className: Rows_module_css_default.title,
1050
1171
  children: title
1051
1172
  }),
@@ -1267,7 +1388,7 @@ window.__ModuleLoader__.load({
1267
1388
  }
1268
1389
  //#endregion
1269
1390
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css.mjs
1270
- const css = ".bhn1Oq_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}.bhn1Oq_root.bhn1Oq_rail{padding-right:0}.bhn1Oq_iconButton{corner-shape:round;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}.bhn1Oq_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_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}.bhn1Oq_root:not(.bhn1Oq_rail) .bhn1Oq_sectionHeader{margin-top:2px;margin-right:-4px}.bhn1Oq_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}.bhn1Oq_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.bhn1Oq_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}.bhn1Oq_searchSlotExpanded{max-width:100%;padding-left:0}.bhn1Oq_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}.bhn1Oq_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.bhn1Oq_search{box-sizing:border-box;corner-shape:round;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}.bhn1Oq_searchExpanded{border:.5px solid var(--dsw-alias-border-l4);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}.bhn1Oq_searchButton{corner-shape:round;cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.bhn1Oq_searchExpanded .bhn1Oq_searchButton{width:28px;height:30px}.bhn1Oq_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_searchExpanded .bhn1Oq_searchButton:hover{background:0 0}.bhn1Oq_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}.bhn1Oq_searchExpanded .bhn1Oq_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.bhn1Oq_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.bhn1Oq_clearButton{corner-shape:round;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}.bhn1Oq_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_rail .bhn1Oq_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.bhn1Oq_rail .bhn1Oq_headerActions{max-width:none}.bhn1Oq_rail .bhn1Oq_iconButton{width:36px;height:36px;color:var(--dsw-alias-label-primary)}.bhn1Oq_rail .bhn1Oq_search{background:0 0;border-color:#0000;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.bhn1Oq_rail .bhn1Oq_searchButton{width:36px;height:36px;color:var(--dsw-alias-label-primary)}.bhn1Oq_rail .bhn1Oq_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_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}.bhn1Oq_rail .bhn1Oq_listArea{margin-left:0;margin-right:0;padding-left:0}.bhn1Oq_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.bhn1Oq_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}.bhn1Oq_wide{animation:bhn1Oq_wide-in .2s var(--ds-ease-in-out)}@keyframes bhn1Oq_wide-in{0%{opacity:0}}.bhn1Oq_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}.bhn1Oq_flatList>*+*,.bhn1Oq_searchTree>[role=treeitem]+[role=treeitem],.bhn1Oq_groupSection>*+*{margin-top:2px}.bhn1Oq_searchStatus,.bhn1Oq_searchWarning{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.bhn1Oq_searchWarning{color:var(--dsw-alias-label-secondary)}.bhn1Oq_groupSection{position:relative}.bhn1Oq_groupSection+.bhn1Oq_groupSection{margin-top:4px}.bhn1Oq_listTopDropIndicator,.bhn1Oq_workspaceDropBefore:before,.bhn1Oq_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}.bhn1Oq_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.bhn1Oq_listTopDropActive>.bhn1Oq_workspaceDropBefore:first-child:before{display:none}.bhn1Oq_workspaceDropBefore:before{top:-8px}.bhn1Oq_workspaceDropAfter:after{bottom:-8px}.bhn1Oq_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}.bhn1Oq_groupSection>.bhn1Oq_sessionOverflowButton{margin-top:0}.bhn1Oq_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.bhn1Oq_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.bhn1Oq_renameInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);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}.bhn1Oq_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.bhn1Oq_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.bhn1Oq_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.bhn1Oq_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.bhn1Oq_wide{animation:none}.bhn1Oq_search,.bhn1Oq_sectionLabel,.bhn1Oq_searchSlot,.bhn1Oq_searchInput,.bhn1Oq_headerActions{transition:none}}";
1391
+ const css = ".bhn1Oq_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}.bhn1Oq_root.bhn1Oq_rail{padding-right:0}.bhn1Oq_iconButton{corner-shape:round;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}.bhn1Oq_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_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}.bhn1Oq_root:not(.bhn1Oq_rail) .bhn1Oq_sectionHeader{margin-top:2px;margin-right:-4px}.bhn1Oq_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}.bhn1Oq_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.bhn1Oq_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}.bhn1Oq_searchSlotExpanded{max-width:100%;padding-left:0}.bhn1Oq_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}.bhn1Oq_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.bhn1Oq_search{box-sizing:border-box;corner-shape:round;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}.bhn1Oq_searchExpanded{border:.5px solid var(--dsw-alias-border-l4);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}.bhn1Oq_searchButton{corner-shape:round;cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.bhn1Oq_searchExpanded .bhn1Oq_searchButton{width:28px;height:30px}.bhn1Oq_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_searchExpanded .bhn1Oq_searchButton:hover{background:0 0}.bhn1Oq_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}.bhn1Oq_searchExpanded .bhn1Oq_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.bhn1Oq_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.bhn1Oq_clearButton{corner-shape:round;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}.bhn1Oq_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_rail .bhn1Oq_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.bhn1Oq_rail .bhn1Oq_headerActions{max-width:none}.bhn1Oq_rail .bhn1Oq_iconButton{width:36px;height:36px;color:var(--dsw-alias-label-primary);border-radius:12px}.bhn1Oq_rail .bhn1Oq_search{background:0 0;border-color:#0000;border-radius:12px;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.bhn1Oq_rail .bhn1Oq_searchButton{width:36px;height:36px;color:var(--dsw-alias-label-primary);border-radius:12px}.bhn1Oq_rail .bhn1Oq_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_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}.bhn1Oq_rail .bhn1Oq_listArea{margin-left:0;margin-right:0;padding-left:0}.bhn1Oq_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.bhn1Oq_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}[data-platform=darwin] .bhn1Oq_fade{display:none}.bhn1Oq_wide{animation:bhn1Oq_wide-in .2s var(--ds-ease-in-out)}@keyframes bhn1Oq_wide-in{0%{opacity:0}}.bhn1Oq_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}.bhn1Oq_flatList>*+*,.bhn1Oq_searchTree>[role=treeitem]+[role=treeitem],.bhn1Oq_groupSection>*+*{margin-top:2px}.bhn1Oq_searchStatus,.bhn1Oq_searchWarning{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.bhn1Oq_searchWarning{color:var(--dsw-alias-label-secondary)}.bhn1Oq_groupSection{position:relative}.bhn1Oq_groupSection+.bhn1Oq_groupSection{margin-top:4px}.bhn1Oq_listTopDropIndicator,.bhn1Oq_workspaceDropBefore:before,.bhn1Oq_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}.bhn1Oq_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.bhn1Oq_listTopDropActive>.bhn1Oq_workspaceDropBefore:first-child:before{display:none}.bhn1Oq_workspaceDropBefore:before{top:-8px}.bhn1Oq_workspaceDropAfter:after{bottom:-8px}.bhn1Oq_sessionOverflowButton{width:100%;height:28px;padding:0 12px 0 calc(28px + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:8px;font-size:12px}.bhn1Oq_groupSection>.bhn1Oq_sessionOverflowButton{margin-top:0}.bhn1Oq_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.bhn1Oq_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.bhn1Oq_renameInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);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}.bhn1Oq_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.bhn1Oq_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.bhn1Oq_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.bhn1Oq_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.bhn1Oq_wide{animation:none}.bhn1Oq_search,.bhn1Oq_sectionLabel,.bhn1Oq_searchSlot,.bhn1Oq_searchInput,.bhn1Oq_headerActions{transition:none}}";
1271
1392
  const tagId = "@deepseek-ai/dsh-client-ui-workspace/WorkspaceBrowser.module.css";
1272
1393
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1273
1394
  const tag = document.createElement("style");
@@ -1407,6 +1528,10 @@ window.__ModuleLoader__.load({
1407
1528
  id: "workspace",
1408
1529
  label: t("groupBy.workspace")
1409
1530
  },
1531
+ {
1532
+ id: "workspace-tree",
1533
+ label: t("groupBy.workspaceTree")
1534
+ },
1410
1535
  {
1411
1536
  id: "flat",
1412
1537
  label: t("groupBy.flat")
@@ -1431,7 +1556,7 @@ window.__ModuleLoader__.load({
1431
1556
  ],
1432
1557
  selectedIds: [groupBy, orderBy],
1433
1558
  onSelect: (id) => {
1434
- if (id === "workspace" || id === "flat") onGroupPick(id);
1559
+ if (id === "workspace" || id === "workspace-tree" || id === "flat") onGroupPick(id);
1435
1560
  else if (id === "manual" || id === "updated") onOrderPick(id);
1436
1561
  setOpen(false);
1437
1562
  },
@@ -1454,16 +1579,16 @@ window.__ModuleLoader__.load({
1454
1579
  })
1455
1580
  });
1456
1581
  }
1457
- /** Resolve an insertion side from the full rendered workspace group. */
1582
+ /** Resolve an insertion side across the Workspace header, descendants, and Sessions. */
1458
1583
  function workspaceGroupHalf(e) {
1459
1584
  const rect = e.currentTarget.getBoundingClientRect();
1460
1585
  return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
1461
1586
  }
1462
1587
  /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
1463
- function SessionTree({ list, useSessionPendingInteraction, startSession, open, forkSession, workspaces, ungroupedSessionIds, archivedSessionIds, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, groupExpansion, setGroupExpanded, setSessionOrder, home, t, revealSessionId, onSessionRevealed }) {
1588
+ function SessionTree({ list, useSessionStatus, startSession, open, forkSession, workspaces, ungroupedSessionIds, archivedSessionIds, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, revealSessionId, onSessionRevealed }) {
1464
1589
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1465
- const pendingInteractions = useSessionPendingInteraction((s) => s);
1466
- const current = panelActive ? void 0 : list.current;
1590
+ const statuses = useSessionStatus((s) => s);
1591
+ const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
1467
1592
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
1468
1593
  const [expandedSessionGroups, setExpandedSessionGroups] = (0, react.useState)([]);
1469
1594
  const [drag, setDrag] = (0, react.useState)(null);
@@ -1481,23 +1606,44 @@ window.__ModuleLoader__.load({
1481
1606
  setGroupExpanded,
1482
1607
  groupExpansion
1483
1608
  ]);
1484
- const expandedGroups = (0, react.useMemo)(() => Object.entries(groupExpansion).filter(([, expanded]) => expanded).map(([key]) => key), [groupExpansion]);
1485
- const groups = (0, react.useMemo)(() => deriveGroups(list, workspaces, archivedSessionIds, pendingInteractions, {
1609
+ const parents = (0, react.useMemo)(() => {
1610
+ if (!nestWorkspaces) return /* @__PURE__ */ new Map();
1611
+ const keysByPath = new Map(workspaces.map((workspace) => [workspace.path, workspace.workspaceId]));
1612
+ const paths = [...keysByPath.keys()];
1613
+ return new Map(workspaces.map((workspace) => {
1614
+ const path = owningParentFolder(workspace.path, paths);
1615
+ return [workspace.workspaceId, path === void 0 ? void 0 : keysByPath.get(path)];
1616
+ }));
1617
+ }, [nestWorkspaces, workspaces]);
1618
+ const currentAncestors = (0, react.useMemo)(() => {
1619
+ const keys = /* @__PURE__ */ new Set();
1620
+ for (let key = currentGroup === void 0 ? void 0 : parents.get(currentGroup); key !== void 0; key = parents.get(key)) keys.add(key);
1621
+ return keys;
1622
+ }, [currentGroup, parents]);
1623
+ const expandedGroups = (0, react.useMemo)(() => {
1624
+ const ancestorKeys = new Set(parents.values());
1625
+ return [...workspaces.map((workspace) => workspace.workspaceId), ""].filter((key) => groupExpansion[key] ?? ancestorKeys.has(key));
1626
+ }, [
1627
+ groupExpansion,
1628
+ parents,
1629
+ workspaces
1630
+ ]);
1631
+ const groups = (0, react.useMemo)(() => deriveGroups(list, workspaces, archivedSessionIds, statuses, {
1486
1632
  expandedGroups,
1487
1633
  ungroupedOrder: ungroupedSessionIds
1488
1634
  }), [
1489
1635
  list,
1490
1636
  workspaces,
1491
1637
  archivedSessionIds,
1492
- pendingInteractions,
1638
+ statuses,
1493
1639
  expandedGroups,
1494
1640
  ungroupedSessionIds
1495
1641
  ]);
1496
1642
  (0, react.useEffect)(() => {
1497
- if (revealGroup === void 0 || groupExpansion[revealGroup] === true) return;
1498
- setGroupExpanded(revealGroup, true);
1643
+ for (let key = revealGroup; key !== void 0; key = parents.get(key)) if (groupExpansion[key] === false || key === revealGroup && groupExpansion[key] !== true) setGroupExpanded(key, true);
1499
1644
  }, [
1500
1645
  groupExpansion,
1646
+ parents,
1501
1647
  revealGroup,
1502
1648
  setGroupExpanded
1503
1649
  ]);
@@ -1560,18 +1706,191 @@ window.__ModuleLoader__.load({
1560
1706
  if (workspaceDropCommitted.current) return;
1561
1707
  workspaceDropCommitted.current = true;
1562
1708
  setWorkspaceDrag(null);
1563
- const rowIndex = workspaces.findIndex((workspace) => workspace.workspaceId === over.id);
1709
+ const owner = parents.get(activeDrag.workspaceId);
1710
+ const siblings = workspaces.filter((workspace) => parents.get(workspace.workspaceId) === owner);
1711
+ const rowIndex = siblings.findIndex((workspace) => workspace.workspaceId === over.id);
1564
1712
  if (rowIndex === -1) return;
1565
- const anchor = over.half === "before" ? over.id : workspaces[rowIndex + 1]?.workspaceId;
1713
+ const anchor = over.half === "before" ? over.id : siblings[rowIndex + 1]?.workspaceId;
1566
1714
  if (anchor === activeDrag.workspaceId) return;
1567
- const sourceIndex = workspaces.findIndex((workspace) => workspace.workspaceId === activeDrag.workspaceId);
1568
- const anchorIndex = anchor === void 0 ? workspaces.length : workspaces.findIndex((workspace) => workspace.workspaceId === anchor);
1715
+ const sourceIndex = siblings.findIndex((workspace) => workspace.workspaceId === activeDrag.workspaceId);
1716
+ const anchorIndex = anchor === void 0 ? siblings.length : siblings.findIndex((workspace) => workspace.workspaceId === anchor);
1569
1717
  if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
1570
1718
  insertWorkspaceBefore(activeDrag.workspaceId, anchor).catch((reason) => {
1571
1719
  console.warn("workspace reorder rejected:", reason);
1572
1720
  });
1573
1721
  };
1574
- const workspaceDropAtListStart = groups[0]?.workspaceId !== void 0 && workspaceDrag?.over?.id === groups[0].workspaceId && workspaceDrag.over.half === "before";
1722
+ const childrenByParent = (0, react.useMemo)(() => {
1723
+ const children = /* @__PURE__ */ new Map();
1724
+ for (const group of groups) {
1725
+ const parent = parents.get(group.key);
1726
+ const siblings = children.get(parent);
1727
+ if (siblings === void 0) children.set(parent, [group]);
1728
+ else siblings.push(group);
1729
+ }
1730
+ return children;
1731
+ }, [groups, parents]);
1732
+ const rootGroups = childrenByParent.get(void 0) ?? [];
1733
+ const workspaceDropAtListStart = rootGroups[0]?.workspaceId !== void 0 && workspaceDrag?.over?.id === rootGroups[0].workspaceId && workspaceDrag.over.half === "before";
1734
+ const renderGroup = (group, depth) => {
1735
+ const workspaceId = group.workspaceId;
1736
+ const children = childrenByParent.get(group.key) ?? [];
1737
+ const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
1738
+ const collapsed = collapsedSessionRows(group.sessions);
1739
+ const sessionsExpanded = expandedSessionGroups.includes(group.key);
1740
+ const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
1741
+ const workspaceDragProps = workspaceId === void 0 ? void 0 : {
1742
+ start: () => {
1743
+ workspaceDropCommitted.current = false;
1744
+ setWorkspaceDrag({
1745
+ workspaceId,
1746
+ over: null
1747
+ });
1748
+ },
1749
+ end: () => {
1750
+ if (workspaceDrag?.over !== null && workspaceDrag?.over !== void 0) commitWorkspaceDrag(workspaceDrag, workspaceDrag.over);
1751
+ else setWorkspaceDrag(null);
1752
+ workspaceDropCommitted.current = false;
1753
+ }
1754
+ };
1755
+ const hoverWorkspace = workspaceId === void 0 || !compatibleDrag ? void 0 : (half) => {
1756
+ setWorkspaceDrag((active) => active === null ? active : {
1757
+ ...active,
1758
+ over: {
1759
+ id: workspaceId,
1760
+ half
1761
+ }
1762
+ });
1763
+ };
1764
+ const dropWorkspace = workspaceId === void 0 || !compatibleDrag ? void 0 : (half) => {
1765
+ commitWorkspaceDrag(workspaceDrag, {
1766
+ id: workspaceId,
1767
+ half
1768
+ });
1769
+ };
1770
+ return (0, react_jsx_runtime.jsxs)("div", {
1771
+ style: { "--dsh-workspace-indent": `${depth * 12}px` },
1772
+ className: clsx(WorkspaceBrowser_module_css_default.groupSection, workspaceMarker === "before" && WorkspaceBrowser_module_css_default.workspaceDropBefore, workspaceMarker === "after" && WorkspaceBrowser_module_css_default.workspaceDropAfter),
1773
+ onDragOver: workspaceDrag === null ? void 0 : (e) => {
1774
+ e.preventDefault();
1775
+ if (hoverWorkspace === void 0 && parents.get(group.key) !== void 0) return;
1776
+ e.stopPropagation();
1777
+ if (hoverWorkspace === void 0) {
1778
+ e.dataTransfer.dropEffect = "none";
1779
+ if (workspaceDrag.over !== null) setWorkspaceDrag({
1780
+ ...workspaceDrag,
1781
+ over: null
1782
+ });
1783
+ } else {
1784
+ e.dataTransfer.dropEffect = "move";
1785
+ hoverWorkspace(workspaceGroupHalf(e));
1786
+ }
1787
+ },
1788
+ onDrop: workspaceDrag === null ? void 0 : (e) => {
1789
+ e.preventDefault();
1790
+ if (dropWorkspace === void 0 && parents.get(group.key) !== void 0) return;
1791
+ e.stopPropagation();
1792
+ if (dropWorkspace === void 0) {
1793
+ workspaceDropCommitted.current = true;
1794
+ setWorkspaceDrag(null);
1795
+ } else dropWorkspace(workspaceGroupHalf(e));
1796
+ },
1797
+ children: [
1798
+ (0, react_jsx_runtime.jsx)(ProjectRowItem, {
1799
+ group,
1800
+ containsCurrentDescendant: currentAncestors.has(group.key),
1801
+ home,
1802
+ t,
1803
+ onToggle: () => {
1804
+ if (group.expanded) setExpandedSessionGroups((keys) => keys.filter((key) => key !== group.key));
1805
+ setGroupExpanded(group.key, !group.expanded);
1806
+ },
1807
+ onCreate: () => {
1808
+ if (group.workspaceId !== void 0) {
1809
+ setGroupExpanded(group.key, true);
1810
+ startSession(group.workspaceId);
1811
+ }
1812
+ },
1813
+ drag: workspaceDragProps,
1814
+ actions: group.workspaceId === void 0 ? void 0 : {
1815
+ rename: () => {
1816
+ /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1817
+ if (group.workspaceId !== void 0) onRenameRequest(group.workspaceId, group.label);
1818
+ },
1819
+ delete: () => {
1820
+ /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1821
+ if (group.workspaceId !== void 0) onDeleteRequest(group.workspaceId, group.label);
1822
+ }
1823
+ }
1824
+ }),
1825
+ group.expanded && children.length > 0 && (0, react_jsx_runtime.jsx)("div", {
1826
+ role: "group",
1827
+ children: children.map((child) => renderGroup(child, depth + 1))
1828
+ }),
1829
+ (sessionsExpanded ? group.sessions : collapsed.rows).map((node) => {
1830
+ const sameGroupDrag = drag !== null && drag.accountKey === group.key;
1831
+ const normalizeHalf = (half) => node.blank ? "after" : half;
1832
+ const dragProps = {
1833
+ start: () => {
1834
+ sessionDropCommitted.current = false;
1835
+ setDrag({
1836
+ accountKey: group.key,
1837
+ sessionId: node.id,
1838
+ over: null
1839
+ });
1840
+ },
1841
+ active: sameGroupDrag,
1842
+ marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null,
1843
+ hover: (half) => {
1844
+ /* v8 ignore next -- narrowing guard: Rows gates hover on `active`, which is false while the drag state is null. */
1845
+ setDrag((d) => d === null ? d : {
1846
+ ...d,
1847
+ over: {
1848
+ id: node.id,
1849
+ half: normalizeHalf(half)
1850
+ }
1851
+ });
1852
+ },
1853
+ drop: (half) => {
1854
+ /* v8 ignore next -- narrowing guard: Rows gates drop on `active`, which is false while the drag state is null. */
1855
+ if (drag === null) return;
1856
+ commitSessionDrag(drag, {
1857
+ id: node.id,
1858
+ half: normalizeHalf(half)
1859
+ });
1860
+ },
1861
+ end: () => {
1862
+ if (drag?.over !== null && drag?.over !== void 0) commitSessionDrag(drag, drag.over);
1863
+ else setDrag(null);
1864
+ sessionDropCommitted.current = false;
1865
+ }
1866
+ };
1867
+ return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1868
+ node,
1869
+ currentId: current,
1870
+ now,
1871
+ onOpen: open,
1872
+ onRename: onSessionRename,
1873
+ onFork: forkSession,
1874
+ onArchive: onSessionArchive,
1875
+ onReveal: node.id === revealSessionId && group.key === revealGroup ? () => {
1876
+ onSessionRevealed(node.id);
1877
+ } : void 0,
1878
+ drag: dragProps,
1879
+ t
1880
+ }, node.id);
1881
+ }),
1882
+ collapsed.hiddenCount > 0 && (0, react_jsx_runtime.jsx)("button", {
1883
+ type: "button",
1884
+ className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
1885
+ "aria-expanded": sessionsExpanded,
1886
+ onClick: () => {
1887
+ setExpandedSessionGroups((keys) => toggled(keys, group.key));
1888
+ },
1889
+ children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: collapsed.hiddenCount })
1890
+ })
1891
+ ]
1892
+ }, group.key);
1893
+ };
1575
1894
  return (0, react_jsx_runtime.jsxs)("div", {
1576
1895
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1577
1896
  children: [
@@ -1586,161 +1905,25 @@ window.__ModuleLoader__.load({
1586
1905
  children: [groups.length === 0 && (0, react_jsx_runtime.jsx)("div", {
1587
1906
  className: WorkspaceBrowser_module_css_default.empty,
1588
1907
  children: t("empty.none")
1589
- }), groups.map((group) => {
1590
- const workspaceId = group.workspaceId;
1591
- const collapsed = collapsedSessionRows(group.sessions);
1592
- const sessionsExpanded = expandedSessionGroups.includes(group.key);
1593
- const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
1594
- const workspaceDragProps = workspaceId === void 0 ? void 0 : {
1595
- start: () => {
1596
- workspaceDropCommitted.current = false;
1597
- setWorkspaceDrag({
1598
- workspaceId,
1599
- over: null
1600
- });
1601
- },
1602
- end: () => {
1603
- if (workspaceDrag?.over !== null && workspaceDrag?.over !== void 0) commitWorkspaceDrag(workspaceDrag, workspaceDrag.over);
1604
- else setWorkspaceDrag(null);
1605
- workspaceDropCommitted.current = false;
1606
- }
1607
- };
1608
- const hoverWorkspace = workspaceId === void 0 ? void 0 : (half) => {
1609
- setWorkspaceDrag((active) => active === null ? active : {
1610
- ...active,
1611
- over: {
1612
- id: workspaceId,
1613
- half
1614
- }
1615
- });
1616
- };
1617
- const dropWorkspace = workspaceId === void 0 ? void 0 : (half) => {
1618
- if (workspaceDrag === null) return;
1619
- commitWorkspaceDrag(workspaceDrag, {
1620
- id: workspaceId,
1621
- half
1622
- });
1623
- };
1624
- return (0, react_jsx_runtime.jsxs)("div", {
1625
- className: clsx(WorkspaceBrowser_module_css_default.groupSection, workspaceMarker === "before" && WorkspaceBrowser_module_css_default.workspaceDropBefore, workspaceMarker === "after" && WorkspaceBrowser_module_css_default.workspaceDropAfter),
1626
- onDragOver: workspaceDrag === null || hoverWorkspace === void 0 ? void 0 : (e) => {
1627
- e.preventDefault();
1628
- e.dataTransfer.dropEffect = "move";
1629
- hoverWorkspace(workspaceGroupHalf(e));
1630
- },
1631
- onDrop: workspaceDrag === null || dropWorkspace === void 0 ? void 0 : (e) => {
1632
- e.preventDefault();
1633
- dropWorkspace(workspaceGroupHalf(e));
1634
- },
1635
- children: [
1636
- (0, react_jsx_runtime.jsx)(ProjectRowItem, {
1637
- group,
1638
- home,
1639
- t,
1640
- onToggle: () => {
1641
- if (group.expanded) setExpandedSessionGroups((keys) => keys.filter((key) => key !== group.key));
1642
- setGroupExpanded(group.key, !group.expanded);
1643
- },
1644
- onCreate: () => {
1645
- if (group.workspaceId !== void 0) {
1646
- setGroupExpanded(group.key, true);
1647
- startSession(group.workspaceId);
1648
- }
1649
- },
1650
- drag: workspaceDragProps,
1651
- actions: group.workspaceId === void 0 ? void 0 : {
1652
- rename: () => {
1653
- /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1654
- if (group.workspaceId !== void 0) onRenameRequest(group.workspaceId, group.label);
1655
- },
1656
- delete: () => {
1657
- /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1658
- if (group.workspaceId !== void 0) onDeleteRequest(group.workspaceId, group.label);
1659
- }
1660
- }
1661
- }),
1662
- (sessionsExpanded ? group.sessions : collapsed.rows).map((node) => {
1663
- const sameGroupDrag = drag !== null && drag.accountKey === group.key;
1664
- const normalizeHalf = (half) => node.blank ? "after" : half;
1665
- const dragProps = {
1666
- start: () => {
1667
- sessionDropCommitted.current = false;
1668
- setDrag({
1669
- accountKey: group.key,
1670
- sessionId: node.id,
1671
- over: null
1672
- });
1673
- },
1674
- active: sameGroupDrag,
1675
- marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null,
1676
- hover: (half) => {
1677
- /* v8 ignore next -- narrowing guard: Rows gates hover on `active`, which is false while the drag state is null. */
1678
- setDrag((d) => d === null ? d : {
1679
- ...d,
1680
- over: {
1681
- id: node.id,
1682
- half: normalizeHalf(half)
1683
- }
1684
- });
1685
- },
1686
- drop: (half) => {
1687
- /* v8 ignore next -- narrowing guard: Rows gates drop on `active`, which is false while the drag state is null. */
1688
- if (drag === null) return;
1689
- commitSessionDrag(drag, {
1690
- id: node.id,
1691
- half: normalizeHalf(half)
1692
- });
1693
- },
1694
- end: () => {
1695
- if (drag?.over !== null && drag?.over !== void 0) commitSessionDrag(drag, drag.over);
1696
- else setDrag(null);
1697
- sessionDropCommitted.current = false;
1698
- }
1699
- };
1700
- return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1701
- node,
1702
- currentId: current,
1703
- now,
1704
- onOpen: open,
1705
- onRename: onSessionRename,
1706
- onFork: forkSession,
1707
- onArchive: onSessionArchive,
1708
- onReveal: node.id === revealSessionId && group.key === revealGroup ? () => {
1709
- onSessionRevealed(node.id);
1710
- } : void 0,
1711
- drag: dragProps,
1712
- t
1713
- }, node.id);
1714
- }),
1715
- collapsed.hiddenCount > 0 && (0, react_jsx_runtime.jsx)("button", {
1716
- type: "button",
1717
- className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
1718
- "aria-expanded": sessionsExpanded,
1719
- onClick: () => {
1720
- setExpandedSessionGroups((keys) => toggled(keys, group.key));
1721
- },
1722
- children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: collapsed.hiddenCount })
1723
- })
1724
- ]
1725
- }, group.key);
1726
- })]
1908
+ }), rootGroups.map((group) => renderGroup(group, 0))]
1727
1909
  }),
1728
1910
  (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })
1729
1911
  ]
1730
1912
  });
1731
1913
  }
1732
1914
  /** The flat "In one list" body: every session is one draggable top-level row. */
1733
- function FlatList({ list, sessionIds, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t }) {
1915
+ function FlatList({ list, sessionIds, useSessionStatus, open, forkSession, onSessionRename, onSessionArchive, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t }) {
1734
1916
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1735
- const pendingInteractions = useSessionPendingInteraction((s) => s);
1736
- const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, pendingInteractions), [
1917
+ const statuses = useSessionStatus((s) => s);
1918
+ const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, statuses), [
1737
1919
  list,
1738
1920
  sessionIds,
1739
- pendingInteractions
1921
+ statuses
1740
1922
  ]);
1741
1923
  const [drag, setDrag] = (0, react.useState)(null);
1742
1924
  const dropCommitted = (0, react.useRef)(false);
1743
1925
  useNativeDragAcceptance(drag !== null);
1926
+ const currentId = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
1744
1927
  const commitDrag = (activeDrag, over) => {
1745
1928
  if (dropCommitted.current) return;
1746
1929
  dropCommitted.current = true;
@@ -1773,7 +1956,7 @@ window.__ModuleLoader__.load({
1773
1956
  const normalizeHalf = (half) => node.blank ? "after" : half;
1774
1957
  return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1775
1958
  node,
1776
- currentId: panelActive ? void 0 : list.current,
1959
+ currentId,
1777
1960
  now,
1778
1961
  onOpen: open,
1779
1962
  onRename: onSessionRename,
@@ -1822,27 +2005,28 @@ window.__ModuleLoader__.load({
1822
2005
  });
1823
2006
  }
1824
2007
  /** Flat search body: local metadata matches plus the current Host result page. */
1825
- function SearchResults({ useSessions, useSessionPendingInteraction, open, workspaces, archivedSessionIds, query, remote, resultLimit, usePanelInfo, t }) {
2008
+ function SearchResults({ useSessions, useSessionStatus, open, workspaces, archivedSessionIds, query, remote, resultLimit, usePanelInfo, t }) {
1826
2009
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1827
2010
  const list = useSessions((s) => s);
1828
- const pendingInteractions = useSessionPendingInteraction((s) => s);
2011
+ const statuses = useSessionStatus((s) => s);
1829
2012
  const currentRemote = remote.query === query ? remote : {
1830
2013
  query,
1831
2014
  status: "loading",
1832
2015
  items: [],
1833
2016
  hasMore: false
1834
2017
  };
1835
- const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, currentRemote, resultLimit), [
2018
+ const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, statuses, currentRemote, resultLimit), [
1836
2019
  list,
1837
2020
  workspaces,
1838
2021
  query,
1839
2022
  archivedSessionIds,
1840
- pendingInteractions,
2023
+ statuses,
1841
2024
  currentRemote,
1842
2025
  resultLimit
1843
2026
  ]);
1844
2027
  const pending = currentRemote.status === "loading";
1845
2028
  const failed = currentRemote.status === "error";
2029
+ const currentId = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
1846
2030
  return (0, react_jsx_runtime.jsxs)("div", {
1847
2031
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1848
2032
  children: [(0, react_jsx_runtime.jsxs)("div", {
@@ -1854,7 +2038,7 @@ window.__ModuleLoader__.load({
1854
2038
  "aria-label": t("search.results.aria"),
1855
2039
  children: results.items.map((result) => (0, react_jsx_runtime.jsx)(SearchResultItem, {
1856
2040
  result,
1857
- currentId: panelActive ? void 0 : list.current,
2041
+ currentId,
1858
2042
  onOpen: open,
1859
2043
  t
1860
2044
  }, result.id))
@@ -1886,7 +2070,7 @@ window.__ModuleLoader__.load({
1886
2070
  * @param props - composed slot props (shell owner share + store + injected actions).
1887
2071
  * @returns the region element tree.
1888
2072
  */
1889
- function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t }) {
2073
+ function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t }) {
1890
2074
  const home = useHostInfo((info) => info.home);
1891
2075
  const list = useSessions((state) => state);
1892
2076
  const workspaces = useWorkspaces((state) => state.items);
@@ -1899,7 +2083,8 @@ window.__ModuleLoader__.load({
1899
2083
  const groupExpansion = useStore((s) => s.groupExpansion);
1900
2084
  const sessionOrderByAccount = useStore((s) => s.sessionOrderByAccount);
1901
2085
  const workspaceReady = workspacePhase === "ready" && workspaceStreamState !== "loading";
1902
- const currentBlank = list.current !== void 0 && list.byId[list.current]?.blank === true ? list.current : void 0;
2086
+ const mainSessionId = Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
2087
+ const currentBlank = mainSessionId !== void 0 && list.byId[mainSessionId]?.blank === true ? mainSessionId : void 0;
1903
2088
  const ungroupedMemberIds = (0, react.useMemo)(() => {
1904
2089
  const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
1905
2090
  return list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id));
@@ -2329,7 +2514,7 @@ window.__ModuleLoader__.load({
2329
2514
  children: wide && (normalizedQuery !== "" ? (0, react_jsx_runtime.jsx)(SearchResults, {
2330
2515
  usePanelInfo,
2331
2516
  useSessions,
2332
- useSessionPendingInteraction,
2517
+ useSessionStatus,
2333
2518
  open: openSearchResult,
2334
2519
  workspaces,
2335
2520
  archivedSessionIds,
@@ -2341,7 +2526,7 @@ window.__ModuleLoader__.load({
2341
2526
  usePanelInfo,
2342
2527
  list,
2343
2528
  sessionIds: orderedFlatSessionIds,
2344
- useSessionPendingInteraction,
2529
+ useSessionStatus,
2345
2530
  open,
2346
2531
  forkSession,
2347
2532
  onSessionRename,
@@ -2353,13 +2538,14 @@ window.__ModuleLoader__.load({
2353
2538
  }) : (0, react_jsx_runtime.jsx)(SessionTree, {
2354
2539
  usePanelInfo,
2355
2540
  list,
2356
- useSessionPendingInteraction,
2541
+ useSessionStatus,
2357
2542
  onSessionRename,
2358
2543
  onSessionArchive,
2359
2544
  forkSession,
2360
2545
  workspaces: orderedWorkspaces,
2361
2546
  ungroupedSessionIds: orderedUngroupedSessionIds,
2362
2547
  workspaceReady,
2548
+ nestWorkspaces: groupBy === "workspace-tree",
2363
2549
  groupExpansion,
2364
2550
  setGroupExpanded: actions.setGroupExpanded,
2365
2551
  setSessionOrder: saveSessionOrder,
@@ -2537,6 +2723,7 @@ window.__ModuleLoader__.load({
2537
2723
  "viewOptions.label": "视图选项",
2538
2724
  "groupBy.label": "分组方式",
2539
2725
  "groupBy.workspace": "按工作区",
2726
+ "groupBy.workspaceTree": "按工作区树",
2540
2727
  "groupBy.flat": "单列表",
2541
2728
  "orderBy.label": "排序方式",
2542
2729
  "orderBy.manual": "手动排序",
@@ -2603,6 +2790,7 @@ window.__ModuleLoader__.load({
2603
2790
  "viewOptions.label": "View options",
2604
2791
  "groupBy.label": "Group by",
2605
2792
  "groupBy.workspace": "WorkSpace",
2793
+ "groupBy.workspaceTree": "Workspace Tree",
2606
2794
  "groupBy.flat": "In one list",
2607
2795
  "orderBy.label": "Order by",
2608
2796
  "orderBy.manual": "Manual",
@@ -2722,9 +2910,7 @@ window.__ModuleLoader__.load({
2722
2910
  searchSessions,
2723
2911
  searchResultLimit: sessions.searchResultLimit,
2724
2912
  renameSession: async (sessionId, title) => {
2725
- const session = sessions.binding(sessionId)?.session;
2726
- if (session === void 0) throw new Error(`unknown session "${sessionId}"`);
2727
- const result = await session.rename(title);
2913
+ const result = await sessions.using(sessionId, { source: "workspaceOperation" }, (reference) => reference.binding.session.rename(title));
2728
2914
  if (!result.ok) throw new Error(result.error.message);
2729
2915
  },
2730
2916
  forkSession: (sessionId) => {