@deepseek-ai/dsh-client-ui-workspace 0.1.5-rc.2 → 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,10 @@ 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();
107
+ }
108
+ async unarchiveSession(sessionId) {
109
+ await this.workspaces.unarchiveSession(sessionId);
99
110
  }
100
111
  async pickDirectory() {
101
112
  const result = await this.directoryPicker.pick();
@@ -121,10 +132,24 @@ window.__ModuleLoader__.load({
121
132
  const workspace = this.workspaces.list.getSnapshot();
122
133
  const sessions = this.sessions.list.getSnapshot();
123
134
  if (workspace.phase !== "ready" || sessions.phase !== "ready") return;
124
- if (sessions.current !== void 0) {
135
+ if (this.mainReference !== void 0) {
125
136
  initial = "done";
126
137
  return;
127
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
+ }
128
153
  const target = recentWorkspace(workspace.items, sessions.byId);
129
154
  if (target === void 0) {
130
155
  initial = "done";
@@ -132,8 +157,8 @@ window.__ModuleLoader__.load({
132
157
  }
133
158
  initial = "connecting";
134
159
  this.connectWorkspace(target).then((sessionId) => {
135
- if (this.lifetime.signal.aborted) return;
136
- if (this.sessions.list.getSnapshot().current === void 0) this.sessions.open(sessionId);
160
+ if (this.mainReference === void 0) this.openSession(sessionId);
161
+ }).then(() => {
137
162
  initial = "done";
138
163
  }, (reason) => {
139
164
  if (this.lifetime.signal.aborted) return;
@@ -152,11 +177,43 @@ window.__ModuleLoader__.load({
152
177
  }
153
178
  /** @returns true when an archived current selection was cleared. */
154
179
  clearArchivedCurrent() {
155
- const current = this.sessions.list.getSnapshot().current;
180
+ const current = this.mainReference?.sessionId;
156
181
  if (current === void 0 || !this.workspaces.list.getSnapshot().archivedSessionIds.includes(current)) return false;
157
- this.sessions.clear();
182
+ this.clearMain();
158
183
  return true;
159
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
+ }
160
217
  };
161
218
  /** Stable tie-breaking follows Host Workspace order. */
162
219
  function recentWorkspace(workspaces, sessions) {
@@ -187,6 +244,10 @@ window.__ModuleLoader__.load({
187
244
  */
188
245
  /** Browser-local order account for the hierarchy-free flat Session list. */
189
246
  const FLAT_SESSION_ORDER_KEY = "__flat_session_order__";
247
+ /** Copy read-only projections into the persisted mutable store representation. */
248
+ function copySessionOrders(orders) {
249
+ return Object.fromEntries(Object.entries(orders).map(([key, order]) => [key, [...order]]));
250
+ }
190
251
  /**
191
252
  * Create the workspace browser viewing store handle.
192
253
  * @returns the store handle (spec + type + identity + factory in one).
@@ -197,15 +258,16 @@ window.__ModuleLoader__.load({
197
258
  groupBy: "workspace",
198
259
  orderBy: "updated",
199
260
  groupExpansion: {},
200
- sessionOrderByAccount: {},
201
- sessionUpdatedAtByAccount: {}
261
+ sessionOrderByAccount: {}
202
262
  }),
203
263
  persist: "dsh.workspace.view.v5",
204
264
  actions: {
205
265
  setGroupBy: (d, mode) => {
206
266
  d.groupBy = mode;
207
267
  },
208
- setOrderBy: (d, mode) => {
268
+ setOrderBy: (d, mode, initialOrders) => {
269
+ if (mode === d.orderBy) return;
270
+ d.sessionOrderByAccount = mode === "manual" ? copySessionOrders(initialOrders) : {};
209
271
  d.orderBy = mode;
210
272
  },
211
273
  setGroupExpanded: (d, key, expanded) => {
@@ -215,14 +277,16 @@ window.__ModuleLoader__.load({
215
277
  const retained = new Set(workspaceKeys);
216
278
  d.groupExpansion = Object.fromEntries(Object.entries(d.groupExpansion).filter(([key]) => retained.has(key)));
217
279
  d.sessionOrderByAccount = Object.fromEntries(Object.entries(d.sessionOrderByAccount).filter(([key]) => retained.has(key)));
218
- d.sessionUpdatedAtByAccount = Object.fromEntries(Object.entries(d.sessionUpdatedAtByAccount).filter(([key]) => retained.has(key)));
280
+ delete d.sessionUpdatedAtByAccount;
219
281
  },
220
- syncSessionOrderAccount: (d, accountKey, order, updatedAt) => {
221
- d.sessionOrderByAccount[accountKey] = order;
222
- d.sessionUpdatedAtByAccount[accountKey] = updatedAt;
282
+ syncSessionOrders: (d, orders) => {
283
+ if (d.orderBy !== "manual") return;
284
+ Object.assign(d.sessionOrderByAccount, copySessionOrders(orders));
223
285
  },
224
- setSessionOrder: (d, accountKey, order) => {
225
- d.sessionOrderByAccount[accountKey] = order;
286
+ setSessionOrder: (d, accountKey, order, initialOrders) => {
287
+ if (d.orderBy === "updated") d.sessionOrderByAccount = copySessionOrders(initialOrders);
288
+ d.orderBy = "manual";
289
+ d.sessionOrderByAccount[accountKey] = [...order];
226
290
  }
227
291
  }
228
292
  });
@@ -313,6 +377,9 @@ window.__ModuleLoader__.load({
313
377
  function owningGroupKey(workspaces, sessionId) {
314
378
  return workspaces.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? "";
315
379
  }
380
+ function mainSessionId(list) {
381
+ return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
382
+ }
316
383
  /**
317
384
  * Directory display label: basename of the path (both separators accepted).
318
385
  * Ungrouped-bucket fallback for surfaces without a workspace title.
@@ -324,10 +391,57 @@ window.__ModuleLoader__.load({
324
391
  const base = workspaceTitleOf(cwd);
325
392
  return base !== "" ? base : cwd;
326
393
  }
327
- /** Recency comparator: newest first, id as the deterministic tiebreak (ids are unique per group). */
328
- function byRecency(a, b) {
329
- if (b.updatedAt !== a.updatedAt) return b.updatedAt - a.updatedAt;
330
- return a.id < b.id ? -1 : 1;
394
+ /**
395
+ * Project known account members by current Session recency.
396
+ * @param sessionIds - authoritative account membership.
397
+ * @param summaries - current Session summaries; members without a summary are omitted until it arrives.
398
+ * @returns known members newest first, with Session identity as the deterministic tie-break.
399
+ */
400
+ function orderByRecency(sessionIds, summaries) {
401
+ return sessionIds.flatMap((id) => {
402
+ const summary = summaries[id];
403
+ return summary === void 0 ? [] : [{
404
+ id,
405
+ updatedAt: summary.updatedAt
406
+ }];
407
+ }).sort((a, b) => {
408
+ if (a.updatedAt !== b.updatedAt) return b.updatedAt - a.updatedAt;
409
+ return a.id < b.id ? -1 : 1;
410
+ }).map((member) => member.id);
411
+ }
412
+ /**
413
+ * Reconcile a browser-local manual order with current account membership.
414
+ * @param memberIds - authoritative account membership.
415
+ * @param savedOrder - previously saved browser-local order.
416
+ * @param summaries - current Session summaries used to append newly known members by recency.
417
+ * @returns retained saved slots followed by newly known members; departed members and unknown new members are omitted.
418
+ */
419
+ function reconcileManualOrder(memberIds, savedOrder, summaries) {
420
+ const members = new Map(memberIds.map((id) => [id, id]));
421
+ const included = /* @__PURE__ */ new Set();
422
+ const ordered = [];
423
+ for (const key of savedOrder ?? []) {
424
+ const id = members.get(key);
425
+ if (id === void 0 || included.has(key)) continue;
426
+ ordered.push(id);
427
+ included.add(key);
428
+ }
429
+ for (const id of orderByRecency(memberIds, summaries)) {
430
+ if (included.has(id)) continue;
431
+ ordered.push(id);
432
+ included.add(id);
433
+ }
434
+ return ordered;
435
+ }
436
+ /**
437
+ * Keep the selected provisional New Session ahead of either base order.
438
+ * @param order - recency or reconciled manual order.
439
+ * @param currentBlank - selected blank Session in this account, when present.
440
+ * @returns a copy with the selected blank first and no duplicate slot.
441
+ */
442
+ function pinCurrentBlank(order, currentBlank) {
443
+ if (currentBlank === void 0) return [...order];
444
+ return [currentBlank, ...order.filter((id) => id !== currentBlank)];
331
445
  }
332
446
  /**
333
447
  * Ordinary sessions are visible; among blank sessions, only the current one
@@ -351,42 +465,33 @@ window.__ModuleLoader__.load({
351
465
  return (session.projectionValues?.schedule?.length ?? 0) > 0;
352
466
  }
353
467
  /** Build one group without projecting session lineage into presentation. */
354
- function buildGroup(key, workspaceId, cwd, createdAt, label, members, order) {
355
- const sessions = [...members];
356
- if (order === "recency") sessions.sort(byRecency);
468
+ function buildGroup(key, workspaceId, cwd, createdAt, label, members) {
357
469
  return {
358
470
  key,
359
471
  workspaceId,
360
472
  cwd,
361
473
  createdAt,
362
474
  label,
363
- sessions
475
+ sessions: [...members]
364
476
  };
365
477
  }
366
478
  /** Apply a stored Ungrouped order and append newly loose Sessions by recency. */
367
- function orderedUngrouped(members, stored) {
479
+ function orderedUngrouped(members, stored, summaries) {
368
480
  const byId = new Map(members.map((session) => [session.id, session]));
369
- const included = /* @__PURE__ */ new Set();
370
- const ordered = [];
371
- for (const key of stored) {
372
- const session = byId.get(key);
373
- if (session === void 0 || included.has(key)) continue;
374
- ordered.push(session);
375
- included.add(key);
376
- }
377
- for (const session of [...members].sort(byRecency)) {
378
- if (included.has(session.id)) continue;
379
- ordered.push(session);
380
- }
381
- return ordered;
481
+ return (stored === void 0 ? orderByRecency(members.map((session) => session.id), summaries) : reconcileManualOrder(members.map((session) => session.id), stored, summaries)).flatMap((id) => {
482
+ const session = byId.get(id);
483
+ /* v8 ignore next -- ids are projected exclusively from the members used to build byId. */
484
+ return session === void 0 ? [] : [session];
485
+ });
382
486
  }
383
487
  /**
384
- * Group Sessions by Host Workspace: one group per entity in stable Host
385
- * order, with members resolved from sessionIds in their stored order. Sessions
386
- * outside every Workspace trail in the browser-local Ungrouped order, which
387
- * falls back to recency before that order is initialized.
488
+ * Group Sessions by Workspace: one group per caller-ordered entity, with
489
+ * members resolved from caller-ordered sessionIds. Sessions outside every
490
+ * Workspace trail in the browser-local Ungrouped order, which falls back to
491
+ * recency before that order is initialized.
388
492
  */
389
493
  function groupByWorkspace(list, workspaces, archived, ungroupedOrder) {
494
+ const current = mainSessionId(list);
390
495
  const groups = [];
391
496
  const accounted = /* @__PURE__ */ new Set();
392
497
  for (const workspace of workspaces) {
@@ -395,13 +500,13 @@ window.__ModuleLoader__.load({
395
500
  const summary = list.byId[id];
396
501
  if (summary === void 0) continue;
397
502
  accounted.add(id);
398
- if (!sessionVisible(summary, list.current, archived)) continue;
503
+ if (!sessionVisible(summary, current, archived)) continue;
399
504
  members.push(summary);
400
505
  }
401
- groups.push(buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members, "account"));
506
+ groups.push(buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members));
402
507
  }
403
- const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, list.current, archived));
404
- if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, "", ungroupedOrder === void 0 ? stray : orderedUngrouped(stray, ungroupedOrder), ungroupedOrder === void 0 ? "recency" : "account"));
508
+ const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, current, archived));
509
+ if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, "", orderedUngrouped(stray, ungroupedOrder, list.byId)));
405
510
  return groups;
406
511
  }
407
512
  /** Keep navigation presentation independent from domain-owned interaction objects. */
@@ -413,15 +518,16 @@ window.__ModuleLoader__.load({
413
518
  default: return;
414
519
  }
415
520
  }
416
- function sessionNode(s, descendants, pendingInteractions) {
417
- 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);
418
524
  return {
419
525
  id: s.id,
420
526
  title: sessionTitle(s),
421
527
  blank: s.blank,
422
- running: s.running,
528
+ running: status?.running ?? s.running,
423
529
  runningSubagentCount: descendants.get(s.id)?.runningCount ?? 0,
424
- completed: s.completed === true,
530
+ completed: status?.completionUnread === true,
425
531
  hasActiveSchedule: hasActiveSchedule(s),
426
532
  updatedAt: s.updatedAt,
427
533
  ...pendingInteraction === void 0 ? {} : { pendingInteraction }
@@ -435,18 +541,19 @@ window.__ModuleLoader__.load({
435
541
  * provisional New Session row; archived sessions are excluded everywhere.
436
542
  * Content search lives outside this derivation
437
543
  * (see {@link deriveSearchResults}).
438
- * @param list - sessions list snapshot (`current` feeds containsCurrent).
439
- * @param workspaces - real workspaces in stable Host order.
544
+ * @param list - sessions list snapshot (`mainView` retention feeds containsCurrent).
545
+ * @param workspaces - real Workspaces in Host group order with caller-projected Session order.
440
546
  * @param archivedSessionIds - registry-global archive set.
441
- * @param pendingInteractions - pending UI interactions by Session.
547
+ * @param statuses - unified UI status by Session.
442
548
  * @param view - local expansion arrays.
443
549
  * @returns group sections in render order.
444
550
  */
445
- function deriveGroups(list, workspaces, archivedSessionIds, pendingInteractions, view) {
551
+ function deriveGroups(list, workspaces, archivedSessionIds, statuses, view) {
446
552
  const archived = new Set(archivedSessionIds);
447
553
  const expandedGroups = new Set(view.expandedGroups);
448
554
  const descendants = indexSubagentDescendants(list.byId);
449
- 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);
450
557
  const groups = [];
451
558
  for (const g of groupByWorkspace(list, workspaces, archived, view.ungroupedOrder)) {
452
559
  const expanded = expandedGroups.has(g.key);
@@ -459,32 +566,35 @@ window.__ModuleLoader__.load({
459
566
  sessionCount: g.sessions.length,
460
567
  expanded,
461
568
  containsCurrent: g.key === currentGroup,
462
- sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, pendingInteractions)) : []
569
+ sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, statuses)) : []
463
570
  });
464
571
  }
465
572
  return groups;
466
573
  }
467
574
  /**
468
- * Derive the flat session list ("In one list" mode): every session — fork
469
- * children included — as a top-level row, strictly newest-first. No grouping,
470
- * no parent/child adjacency. Content search lives outside this derivation
471
- * (see {@link deriveSearchResults}).
575
+ * Select flat-list members without deriving row presentation or ordering.
472
576
  * @param list - sessions list snapshot.
473
577
  * @param archivedSessionIds - registry-global archive set.
474
- * @param pendingInteractions - pending UI interactions by Session.
475
- * @returns flat rows in render order.
578
+ * @returns known visible Session ids in list order, including ordinary forks and only the current blank.
476
579
  */
477
- function deriveFlat(list, archivedSessionIds, pendingInteractions) {
580
+ function visibleSessionIds(list, archivedSessionIds) {
478
581
  const archived = new Set(archivedSessionIds);
479
- const descendants = indexSubagentDescendants(list.byId);
480
- const rows = [];
481
- for (const id of list.ids) {
582
+ const current = mainSessionId(list);
583
+ return list.ids.filter((id) => {
482
584
  const s = list.byId[id];
483
- if (s === void 0 || !sessionVisible(s, list.current, archived)) continue;
484
- rows.push(s);
485
- }
486
- rows.sort(byRecency);
487
- return rows.map((session) => sessionNode(session, descendants, pendingInteractions));
585
+ return s !== void 0 && sessionVisible(s, current, archived);
586
+ });
587
+ }
588
+ /**
589
+ * Derive flat rows from the browser's ordered visible Session ids.
590
+ * @param list - sessions list snapshot used to select the ids.
591
+ * @param sessionIds - known visible members in render order, including any pinned blank.
592
+ * @param statuses - unified UI status by Session.
593
+ * @returns flat rows in the supplied order with current status indicators.
594
+ */
595
+ function deriveFlat(list, sessionIds, statuses) {
596
+ const descendants = indexSubagentDescendants(list.byId);
597
+ return sessionIds.map((id) => sessionNode(list.byId[id], descendants, statuses));
488
598
  }
489
599
  /**
490
600
  * Merge immediate title/Workspace substring matches with ranked Host content
@@ -494,12 +604,12 @@ window.__ModuleLoader__.load({
494
604
  * @param workspaces - Workspace membership and display labels.
495
605
  * @param query - caller text; surrounding whitespace is ignored.
496
606
  * @param archivedSessionIds - registry-global archive set (members never match).
497
- * @param pendingInteractions - pending UI interactions by Session.
607
+ * @param statuses - unified UI status by Session.
498
608
  * @param content - ranked Host content-search page.
499
609
  * @param limit - protocol-owned maximum merged row count.
500
610
  * @returns bounded deduplicated flat rows and a refine-query hint bit.
501
611
  */
502
- function deriveSearchResults(list, workspaces, query, archivedSessionIds, pendingInteractions, content, limit) {
612
+ function deriveSearchResults(list, workspaces, query, archivedSessionIds, statuses, content, limit) {
503
613
  const q = query.trim().toLowerCase();
504
614
  if (q === "") return {
505
615
  items: [],
@@ -507,6 +617,7 @@ window.__ModuleLoader__.load({
507
617
  };
508
618
  const archived = new Set(archivedSessionIds);
509
619
  const descendants = indexSubagentDescendants(list.byId);
620
+ const current = mainSessionId(list);
510
621
  const workspaceBySession = /* @__PURE__ */ new Map();
511
622
  for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
512
623
  const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
@@ -515,10 +626,11 @@ window.__ModuleLoader__.load({
515
626
  const local = [];
516
627
  for (const id of list.ids) {
517
628
  const summary = list.byId[id];
518
- if (summary === void 0 || summary.blank || !sessionVisible(summary, list.current, archived)) continue;
629
+ if (summary === void 0 || summary.blank || !sessionVisible(summary, current, archived)) continue;
519
630
  if (sessionTitle(summary).toLowerCase().includes(q) || labelOf(summary).toLowerCase().includes(q)) local.push(summary);
520
631
  }
521
- local.sort(byRecency);
632
+ const localById = new Map(local.map((summary) => [summary.id, summary]));
633
+ const orderedLocal = orderByRecency(local.map((summary) => summary.id), list.byId).map((id) => localById.get(id));
522
634
  const ordered = [];
523
635
  const included = /* @__PURE__ */ new Set();
524
636
  const include = (summary) => {
@@ -526,23 +638,24 @@ window.__ModuleLoader__.load({
526
638
  included.add(summary.id);
527
639
  ordered.push(summary);
528
640
  };
529
- for (const summary of local) include(summary);
641
+ for (const summary of orderedLocal) include(summary);
530
642
  for (const item of content.items) {
531
643
  const summary = list.byId[item.sessionId];
532
- 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);
533
645
  }
534
646
  return {
535
647
  items: ordered.slice(0, limit).map((summary) => {
536
648
  const match = contentBySession.get(summary.id);
537
- const pendingInteraction = visiblePendingKind(pendingInteractions.get(summary.id)?.kind);
649
+ const status = statuses.get(summary.id);
650
+ const pendingInteraction = visiblePendingKind(status?.pendingInteraction?.kind);
538
651
  return {
539
652
  id: summary.id,
540
653
  title: sessionTitle(summary),
541
654
  workspace: labelOf(summary),
542
- running: summary.running,
655
+ running: status?.running ?? summary.running,
543
656
  runningSubagentCount: descendants.get(summary.id)?.runningCount ?? 0,
544
657
  ...pendingInteraction === void 0 ? {} : { pendingInteraction },
545
- completed: summary.completed === true,
658
+ completed: status?.completionUnread === true,
546
659
  hasActiveSchedule: hasActiveSchedule(summary),
547
660
  ...match === void 0 ? {} : { snippet: match.snippet }
548
661
  };
@@ -550,9 +663,33 @@ window.__ModuleLoader__.load({
550
663
  hasMore: content.hasMore || ordered.length > limit
551
664
  };
552
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
+ }
553
690
  //#endregion
554
691
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
555
- 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}}";
556
693
  const tagId$2 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
557
694
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
558
695
  const tag = document.createElement("style");
@@ -604,14 +741,39 @@ window.__ModuleLoader__.load({
604
741
  /**
605
742
  * Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
606
743
  * all data and callbacks arrive via props. Hover swaps (folder->chevron,
607
- * time->ellipsis, action buttons) are CSS-only. Row ... menus are visual-only
608
- * except workspace Rename/Delete and session Rename/Fork/Archive; the session
609
- * 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.
610
748
  */
611
749
  /** Row display title: blank rows show the localized New Session label. */
612
750
  function displayTitle(node, t) {
613
751
  return node.blank ? t("session.new") : node.title;
614
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
+ }
615
777
  /** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
616
778
  function timeLabel(updatedAt, now, t) {
617
779
  const { unit, n } = (0, _deepseek_ai_dsh_client_ui_primitives.relativeTime)(updatedAt, now);
@@ -667,6 +829,7 @@ window.__ModuleLoader__.load({
667
829
  * Workspace shows its hover card (the ungrouped bucket has none).
668
830
  * `containsCurrent` arrives on the node (derivation fact, no renderer scan).
669
831
  * @param props.group - derived group node.
832
+ * @param props.containsCurrentDescendant - highlight an ancestor even when its subtree is collapsed.
670
833
  * @param props.onToggle - expand/collapse the group.
671
834
  * @param props.onCreate - start a frontend Session inside this Workspace.
672
835
  * @param props.drag - optional workspace-row drag wiring.
@@ -674,10 +837,10 @@ window.__ModuleLoader__.load({
674
837
  * @param props.t - the browser root's locale seat.
675
838
  * @returns the row element.
676
839
  */
677
- function ProjectRowItem({ group, onToggle, onCreate, actions, drag, home, t }) {
840
+ function ProjectRowItem({ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag, home, t }) {
678
841
  const row = group;
679
842
  const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
680
- const active = group.expanded && group.containsCurrent;
843
+ const active = containsCurrentDescendant || group.expanded && group.containsCurrent;
681
844
  const [menuOpen, setMenuOpen] = (0, react.useState)(false);
682
845
  const workspaceMenuItems = [{
683
846
  id: "rename",
@@ -927,7 +1090,7 @@ window.__ModuleLoader__.load({
927
1090
  * @param props.onFork - fork a session at its last completed turn.
928
1091
  * @param props.onArchive - archive a session by id.
929
1092
  * @param props.onReveal - scroll this row into view after search navigation, then acknowledge it.
930
- * @param props.drag - optional draggable-row wiring.
1093
+ * @param props.drag - optional row-drag target wiring; blank rows cannot start a drag.
931
1094
  * @param props.flat - omit the empty status slot in the hierarchy-free flat list.
932
1095
  * @param props.t - the browser root's locale seat.
933
1096
  * @returns the session row.
@@ -938,8 +1101,10 @@ window.__ModuleLoader__.load({
938
1101
  const selected = node.id === currentId;
939
1102
  const statuses = sessionStatuses(node, t);
940
1103
  const showStatus = statuses[0].state !== "done" || row.completed;
1104
+ const draggable = drag !== void 0 && !row.blank;
941
1105
  const [menuOpen, setMenuOpen] = (0, react.useState)(false);
942
1106
  const rowRef = (0, react.useRef)(null);
1107
+ const titleRef = (0, react.useRef)(null);
943
1108
  (0, react.useEffect)(() => {
944
1109
  if (onReveal === void 0) return;
945
1110
  rowRef.current?.scrollIntoView({ block: "nearest" });
@@ -971,13 +1136,19 @@ window.__ModuleLoader__.load({
971
1136
  onClick: () => {
972
1137
  onOpen(node.id);
973
1138
  },
974
- draggable: drag !== void 0,
975
- onDragStart: drag === void 0 ? void 0 : (e) => {
1139
+ onPointerEnter: () => {
1140
+ revealClippedTitle(titleRef.current, true);
1141
+ },
1142
+ onPointerLeave: () => {
1143
+ revealClippedTitle(titleRef.current, false);
1144
+ },
1145
+ draggable,
1146
+ onDragStart: drag === void 0 || row.blank ? void 0 : (e) => {
976
1147
  e.dataTransfer.effectAllowed = "move";
977
1148
  e.dataTransfer.setData("text/plain", node.id);
978
1149
  drag.start();
979
1150
  },
980
- onDragEnd: drag?.end,
1151
+ onDragEnd: drag === void 0 || row.blank ? void 0 : drag.end,
981
1152
  onDragOver: drag === void 0 ? void 0 : (e) => {
982
1153
  if (!drag.active) return;
983
1154
  e.preventDefault();
@@ -995,6 +1166,7 @@ window.__ModuleLoader__.load({
995
1166
  children: showStatus && (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
996
1167
  }),
997
1168
  (0, react_jsx_runtime.jsx)("span", {
1169
+ ref: titleRef,
998
1170
  className: Rows_module_css_default.title,
999
1171
  children: title
1000
1172
  }),
@@ -1216,7 +1388,7 @@ window.__ModuleLoader__.load({
1216
1388
  }
1217
1389
  //#endregion
1218
1390
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css.mjs
1219
- 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}}";
1220
1392
  const tagId = "@deepseek-ai/dsh-client-ui-workspace/WorkspaceBrowser.module.css";
1221
1393
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1222
1394
  const tag = document.createElement("style");
@@ -1338,58 +1510,6 @@ window.__ModuleLoader__.load({
1338
1510
  };
1339
1511
  }, [active]);
1340
1512
  }
1341
- /** Reconcile a stored view order with the Workspace's current session account. */
1342
- function reconciledSessionOrder(sessionIds, stored) {
1343
- if (stored === void 0) return [...sessionIds];
1344
- const byId = new Map(sessionIds.map((id) => [id, id]));
1345
- const ordered = [];
1346
- const included = /* @__PURE__ */ new Set();
1347
- for (const key of stored) {
1348
- const id = byId.get(key);
1349
- if (id === void 0 || included.has(key)) continue;
1350
- ordered.push(id);
1351
- included.add(key);
1352
- }
1353
- for (const id of sessionIds) {
1354
- if (included.has(id)) continue;
1355
- ordered.push(id);
1356
- }
1357
- return ordered;
1358
- }
1359
- /** Newest update first with stable Session identity as the tie-break. */
1360
- function compareSessionRecency(a, b, byId) {
1361
- const aUpdatedAt = byId[a]?.updatedAt ?? Number.NEGATIVE_INFINITY;
1362
- const bUpdatedAt = byId[b]?.updatedAt ?? Number.NEGATIVE_INFINITY;
1363
- if (aUpdatedAt !== bUpdatedAt) return bUpdatedAt - aUpdatedAt;
1364
- return a < b ? -1 : 1;
1365
- }
1366
- /** Reconcile one editable order account and apply its activity-promotion policy. */
1367
- function nextSessionOrderAccount({ sessionIds, previousOrder, previousUpdatedAt, list, orderBy, sortByRecency }) {
1368
- let order = reconciledSessionOrder(sessionIds, previousOrder);
1369
- if (sortByRecency) order.sort((a, b) => compareSessionRecency(a, b, list.byId));
1370
- else if (orderBy === "updated") {
1371
- const promoted = sessionIds.filter((id) => {
1372
- const session = list.byId[id];
1373
- return session !== void 0 && (previousUpdatedAt[id] === void 0 || session.updatedAt > previousUpdatedAt[id]);
1374
- }).sort((a, b) => compareSessionRecency(a, b, list.byId));
1375
- if (promoted.length > 0) {
1376
- const promotedIds = new Set(promoted);
1377
- order = [...promoted, ...order.filter((id) => !promotedIds.has(id))];
1378
- }
1379
- }
1380
- const updatedAt = {};
1381
- for (const id of sessionIds) {
1382
- const session = list.byId[id];
1383
- if (session !== void 0) updatedAt[id] = session.updatedAt;
1384
- }
1385
- const orderChanged = previousOrder === void 0 || order.length !== previousOrder.length || order.some((id, index) => id !== previousOrder[index]);
1386
- const timestampsChanged = Object.keys(updatedAt).length !== Object.keys(previousUpdatedAt).length || Object.entries(updatedAt).some(([id, timestamp]) => previousUpdatedAt[id] !== timestamp);
1387
- return {
1388
- order,
1389
- updatedAt,
1390
- changed: orderChanged || timestampsChanged
1391
- };
1392
- }
1393
1513
  /** Grouping and ordering menu; own open state so it resets with the wide chrome. */
1394
1514
  function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }) {
1395
1515
  const [open, setOpen] = (0, react.useState)(false);
@@ -1408,6 +1528,10 @@ window.__ModuleLoader__.load({
1408
1528
  id: "workspace",
1409
1529
  label: t("groupBy.workspace")
1410
1530
  },
1531
+ {
1532
+ id: "workspace-tree",
1533
+ label: t("groupBy.workspaceTree")
1534
+ },
1411
1535
  {
1412
1536
  id: "flat",
1413
1537
  label: t("groupBy.flat")
@@ -1432,7 +1556,7 @@ window.__ModuleLoader__.load({
1432
1556
  ],
1433
1557
  selectedIds: [groupBy, orderBy],
1434
1558
  onSelect: (id) => {
1435
- if (id === "workspace" || id === "flat") onGroupPick(id);
1559
+ if (id === "workspace" || id === "workspace-tree" || id === "flat") onGroupPick(id);
1436
1560
  else if (id === "manual" || id === "updated") onOrderPick(id);
1437
1561
  setOpen(false);
1438
1562
  },
@@ -1455,24 +1579,22 @@ window.__ModuleLoader__.load({
1455
1579
  })
1456
1580
  });
1457
1581
  }
1458
- /** Resolve an insertion side from the full rendered workspace group. */
1582
+ /** Resolve an insertion side across the Workspace header, descendants, and Sessions. */
1459
1583
  function workspaceGroupHalf(e) {
1460
1584
  const rect = e.currentTarget.getBoundingClientRect();
1461
1585
  return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
1462
1586
  }
1463
1587
  /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
1464
- function SessionTree({ useSessions, useSessionPendingInteraction, startSession, open, forkSession, workspaces, archivedSessionIds, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, 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 }) {
1465
1589
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1466
- const list = useSessions((s) => s);
1467
- const pendingInteractions = useSessionPendingInteraction((s) => s);
1468
- 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;
1469
1592
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
1470
1593
  const [expandedSessionGroups, setExpandedSessionGroups] = (0, react.useState)([]);
1471
1594
  const [drag, setDrag] = (0, react.useState)(null);
1472
1595
  const sessionDropCommitted = (0, react.useRef)(false);
1473
1596
  const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
1474
1597
  const workspaceDropCommitted = (0, react.useRef)(false);
1475
- const previousOrderBy = (0, react.useRef)(orderBy);
1476
1598
  useNativeDragAcceptance(drag !== null || workspaceDrag !== null);
1477
1599
  const currentGroup = current === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, current);
1478
1600
  (0, react.useEffect)(() => {
@@ -1484,70 +1606,44 @@ window.__ModuleLoader__.load({
1484
1606
  setGroupExpanded,
1485
1607
  groupExpansion
1486
1608
  ]);
1487
- const expandedGroups = (0, react.useMemo)(() => Object.entries(groupExpansion).filter(([, expanded]) => expanded).map(([key]) => key), [groupExpansion]);
1488
- const ungroupedSessionIds = (0, react.useMemo)(() => {
1489
- const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
1490
- return list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id));
1491
- }, [list, workspaces]);
1492
- (0, react.useEffect)(() => {
1493
- if (list.phase !== "ready") return;
1494
- const switchedToUpdated = previousOrderBy.current !== "updated" && orderBy === "updated";
1495
- previousOrderBy.current = orderBy;
1496
- const accounts = [...workspaces.map((workspace) => ({
1497
- key: workspace.workspaceId,
1498
- sessionIds: workspace.sessionIds.filter((id) => list.byId[id] !== void 0)
1499
- })), {
1500
- key: "",
1501
- sessionIds: ungroupedSessionIds
1502
- }];
1503
- for (const { key, sessionIds } of accounts) {
1504
- const previousOrder = sessionOrderByAccount[key];
1505
- const next = nextSessionOrderAccount({
1506
- sessionIds,
1507
- previousOrder,
1508
- previousUpdatedAt: sessionUpdatedAtByAccount[key] ?? {},
1509
- list,
1510
- orderBy,
1511
- sortByRecency: orderBy === "updated" && (previousOrder === void 0 || switchedToUpdated)
1512
- });
1513
- if (next.changed) syncSessionOrderAccount(key, next.order.map((id) => id), next.updatedAt);
1514
- }
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));
1515
1626
  }, [
1516
- list,
1517
- orderBy,
1518
- sessionOrderByAccount,
1519
- sessionUpdatedAtByAccount,
1520
- syncSessionOrderAccount,
1521
- ungroupedSessionIds,
1627
+ groupExpansion,
1628
+ parents,
1522
1629
  workspaces
1523
1630
  ]);
1524
- const orderedWorkspaces = (0, react.useMemo)(() => {
1525
- return workspaces.map((workspace) => {
1526
- const stored = sessionOrderByAccount[workspace.workspaceId];
1527
- const sessionIds = reconciledSessionOrder(workspace.sessionIds, stored);
1528
- return {
1529
- ...workspace,
1530
- sessionIds
1531
- };
1532
- });
1533
- }, [sessionOrderByAccount, workspaces]);
1534
- const orderedUngroupedSessionIds = (0, react.useMemo)(() => reconciledSessionOrder(ungroupedSessionIds, sessionOrderByAccount[""]), [sessionOrderByAccount, ungroupedSessionIds]);
1535
- const groups = (0, react.useMemo)(() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, pendingInteractions, {
1631
+ const groups = (0, react.useMemo)(() => deriveGroups(list, workspaces, archivedSessionIds, statuses, {
1536
1632
  expandedGroups,
1537
- ...sessionOrderByAccount[""] === void 0 ? {} : { ungroupedOrder: sessionOrderByAccount[""] }
1633
+ ungroupedOrder: ungroupedSessionIds
1538
1634
  }), [
1539
1635
  list,
1540
- orderedWorkspaces,
1636
+ workspaces,
1541
1637
  archivedSessionIds,
1542
- pendingInteractions,
1638
+ statuses,
1543
1639
  expandedGroups,
1544
- sessionOrderByAccount
1640
+ ungroupedSessionIds
1545
1641
  ]);
1546
1642
  (0, react.useEffect)(() => {
1547
- if (revealGroup === void 0 || groupExpansion[revealGroup] === true) return;
1548
- 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);
1549
1644
  }, [
1550
1645
  groupExpansion,
1646
+ parents,
1551
1647
  revealGroup,
1552
1648
  setGroupExpanded
1553
1649
  ]);
@@ -1580,8 +1676,8 @@ window.__ModuleLoader__.load({
1580
1676
  if (targetWithoutSourceIndex === -1) return;
1581
1677
  const visibleInsertAt = over.half === "before" ? targetWithoutSourceIndex : targetWithoutSourceIndex + 1;
1582
1678
  if (sourceIndex !== -1 && visibleInsertAt === sourceIndex) return;
1583
- const accountSessionIds = activeDrag.accountKey === "" ? orderedUngroupedSessionIds : orderedWorkspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
1584
- if (accountSessionIds === void 0) return;
1679
+ const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
1680
+ if (accountSessionIds === void 0 || !accountSessionIds.includes(activeDrag.sessionId)) return;
1585
1681
  const nextOrder = accountSessionIds.filter((id) => id !== activeDrag.sessionId);
1586
1682
  let anchor;
1587
1683
  if (sessionsExpanded) anchor = over.half === "before" ? over.id : renderedSessions[targetIndex + 1]?.id;
@@ -1603,28 +1699,198 @@ window.__ModuleLoader__.load({
1603
1699
  return node === void 0 ? [] : [node];
1604
1700
  })).rows.some((node) => node.id === activeDrag.sessionId)) return;
1605
1701
  }
1606
- setSessionOrder(activeDrag.accountKey, nextOrder.map((id) => id));
1607
- if (orderBy === "updated" || activeDrag.accountKey === "") return;
1608
- insertSessionBefore(activeDrag.accountKey, activeDrag.sessionId, anchor).catch((reason) => {
1609
- console.warn("session reorder rejected:", reason);
1610
- });
1702
+ const currentBlank = group.sessions.find((node) => node.blank)?.id;
1703
+ setSessionOrder(activeDrag.accountKey, pinCurrentBlank(nextOrder, currentBlank));
1611
1704
  };
1612
1705
  const commitWorkspaceDrag = (activeDrag, over) => {
1613
1706
  if (workspaceDropCommitted.current) return;
1614
1707
  workspaceDropCommitted.current = true;
1615
1708
  setWorkspaceDrag(null);
1616
- 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);
1617
1712
  if (rowIndex === -1) return;
1618
- const anchor = over.half === "before" ? over.id : workspaces[rowIndex + 1]?.workspaceId;
1713
+ const anchor = over.half === "before" ? over.id : siblings[rowIndex + 1]?.workspaceId;
1619
1714
  if (anchor === activeDrag.workspaceId) return;
1620
- const sourceIndex = workspaces.findIndex((workspace) => workspace.workspaceId === activeDrag.workspaceId);
1621
- 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);
1622
1717
  if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
1623
1718
  insertWorkspaceBefore(activeDrag.workspaceId, anchor).catch((reason) => {
1624
1719
  console.warn("workspace reorder rejected:", reason);
1625
1720
  });
1626
1721
  };
1627
- 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
+ };
1628
1894
  return (0, react_jsx_runtime.jsxs)("div", {
1629
1895
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1630
1896
  children: [
@@ -1639,197 +1905,25 @@ window.__ModuleLoader__.load({
1639
1905
  children: [groups.length === 0 && (0, react_jsx_runtime.jsx)("div", {
1640
1906
  className: WorkspaceBrowser_module_css_default.empty,
1641
1907
  children: t("empty.none")
1642
- }), groups.map((group) => {
1643
- const workspaceId = group.workspaceId;
1644
- const collapsed = collapsedSessionRows(group.sessions);
1645
- const sessionsExpanded = expandedSessionGroups.includes(group.key);
1646
- const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
1647
- const workspaceDragProps = workspaceId === void 0 ? void 0 : {
1648
- start: () => {
1649
- workspaceDropCommitted.current = false;
1650
- setWorkspaceDrag({
1651
- workspaceId,
1652
- over: null
1653
- });
1654
- },
1655
- end: () => {
1656
- if (workspaceDrag?.over !== null && workspaceDrag?.over !== void 0) commitWorkspaceDrag(workspaceDrag, workspaceDrag.over);
1657
- else setWorkspaceDrag(null);
1658
- workspaceDropCommitted.current = false;
1659
- }
1660
- };
1661
- const hoverWorkspace = workspaceId === void 0 ? void 0 : (half) => {
1662
- setWorkspaceDrag((active) => active === null ? active : {
1663
- ...active,
1664
- over: {
1665
- id: workspaceId,
1666
- half
1667
- }
1668
- });
1669
- };
1670
- const dropWorkspace = workspaceId === void 0 ? void 0 : (half) => {
1671
- if (workspaceDrag === null) return;
1672
- commitWorkspaceDrag(workspaceDrag, {
1673
- id: workspaceId,
1674
- half
1675
- });
1676
- };
1677
- return (0, react_jsx_runtime.jsxs)("div", {
1678
- className: clsx(WorkspaceBrowser_module_css_default.groupSection, workspaceMarker === "before" && WorkspaceBrowser_module_css_default.workspaceDropBefore, workspaceMarker === "after" && WorkspaceBrowser_module_css_default.workspaceDropAfter),
1679
- onDragOver: workspaceDrag === null || hoverWorkspace === void 0 ? void 0 : (e) => {
1680
- e.preventDefault();
1681
- e.dataTransfer.dropEffect = "move";
1682
- hoverWorkspace(workspaceGroupHalf(e));
1683
- },
1684
- onDrop: workspaceDrag === null || dropWorkspace === void 0 ? void 0 : (e) => {
1685
- e.preventDefault();
1686
- dropWorkspace(workspaceGroupHalf(e));
1687
- },
1688
- children: [
1689
- (0, react_jsx_runtime.jsx)(ProjectRowItem, {
1690
- group,
1691
- home,
1692
- t,
1693
- onToggle: () => {
1694
- if (group.expanded) setExpandedSessionGroups((keys) => keys.filter((key) => key !== group.key));
1695
- setGroupExpanded(group.key, !group.expanded);
1696
- },
1697
- onCreate: () => {
1698
- if (group.workspaceId !== void 0) {
1699
- setGroupExpanded(group.key, true);
1700
- startSession(group.workspaceId);
1701
- }
1702
- },
1703
- drag: workspaceDragProps,
1704
- actions: group.workspaceId === void 0 ? void 0 : {
1705
- rename: () => {
1706
- /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1707
- if (group.workspaceId !== void 0) onRenameRequest(group.workspaceId, group.label);
1708
- },
1709
- delete: () => {
1710
- /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1711
- if (group.workspaceId !== void 0) onDeleteRequest(group.workspaceId, group.label);
1712
- }
1713
- }
1714
- }),
1715
- (sessionsExpanded ? group.sessions : collapsed.rows).map((node) => {
1716
- const sameGroupDrag = drag !== null && drag.accountKey === group.key;
1717
- const dragProps = {
1718
- start: () => {
1719
- sessionDropCommitted.current = false;
1720
- setDrag({
1721
- accountKey: group.key,
1722
- sessionId: node.id,
1723
- over: null
1724
- });
1725
- },
1726
- active: sameGroupDrag,
1727
- marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null,
1728
- hover: (half) => {
1729
- /* v8 ignore next -- narrowing guard: Rows gates hover on `active`, which is false while the drag state is null. */
1730
- setDrag((d) => d === null ? d : {
1731
- ...d,
1732
- over: {
1733
- id: node.id,
1734
- half
1735
- }
1736
- });
1737
- },
1738
- drop: (half) => {
1739
- /* v8 ignore next -- narrowing guard: Rows gates drop on `active`, which is false while the drag state is null. */
1740
- if (drag === null) return;
1741
- commitSessionDrag(drag, {
1742
- id: node.id,
1743
- half
1744
- });
1745
- },
1746
- end: () => {
1747
- if (drag?.over !== null && drag?.over !== void 0) commitSessionDrag(drag, drag.over);
1748
- else setDrag(null);
1749
- sessionDropCommitted.current = false;
1750
- }
1751
- };
1752
- return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1753
- node,
1754
- currentId: current,
1755
- now,
1756
- onOpen: open,
1757
- onRename: onSessionRename,
1758
- onFork: forkSession,
1759
- onArchive: onSessionArchive,
1760
- onReveal: node.id === revealSessionId && group.key === revealGroup ? () => {
1761
- onSessionRevealed(node.id);
1762
- } : void 0,
1763
- drag: dragProps,
1764
- t
1765
- }, node.id);
1766
- }),
1767
- collapsed.hiddenCount > 0 && (0, react_jsx_runtime.jsx)("button", {
1768
- type: "button",
1769
- className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
1770
- "aria-expanded": sessionsExpanded,
1771
- onClick: () => {
1772
- setExpandedSessionGroups((keys) => toggled(keys, group.key));
1773
- },
1774
- children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: collapsed.hiddenCount })
1775
- })
1776
- ]
1777
- }, group.key);
1778
- })]
1908
+ }), rootGroups.map((group) => renderGroup(group, 0))]
1779
1909
  }),
1780
1910
  (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })
1781
1911
  ]
1782
1912
  });
1783
1913
  }
1784
1914
  /** The flat "In one list" body: every session is one draggable top-level row. */
1785
- function FlatList({ useSessions, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, archivedSessionIds, usePanelInfo, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, revealSessionId, onSessionRevealed, t }) {
1915
+ function FlatList({ list, sessionIds, useSessionStatus, open, forkSession, onSessionRename, onSessionArchive, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t }) {
1786
1916
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1787
- const list = useSessions((s) => s);
1788
- const pendingInteractions = useSessionPendingInteraction((s) => s);
1789
- const baseRows = (0, react.useMemo)(() => deriveFlat(list, archivedSessionIds, pendingInteractions), [
1790
- list,
1791
- archivedSessionIds,
1792
- pendingInteractions
1793
- ]);
1794
- const sessionIds = (0, react.useMemo)(() => baseRows.map((row) => row.id), [baseRows]);
1795
- const previousOrderBy = (0, react.useRef)(orderBy);
1796
- (0, react.useEffect)(() => {
1797
- if (list.phase !== "ready") return;
1798
- const previousOrder = sessionOrderByAccount[FLAT_SESSION_ORDER_KEY];
1799
- const previousUpdatedAt = sessionUpdatedAtByAccount["__flat_session_order__"] ?? {};
1800
- const switchedToUpdated = previousOrderBy.current !== "updated" && orderBy === "updated";
1801
- previousOrderBy.current = orderBy;
1802
- const next = nextSessionOrderAccount({
1803
- sessionIds,
1804
- previousOrder,
1805
- previousUpdatedAt,
1806
- list,
1807
- orderBy,
1808
- sortByRecency: orderBy === "updated" && (previousOrder === void 0 || switchedToUpdated)
1809
- });
1810
- if (next.changed) syncSessionOrderAccount(FLAT_SESSION_ORDER_KEY, next.order.map((id) => id), next.updatedAt);
1811
- }, [
1917
+ const statuses = useSessionStatus((s) => s);
1918
+ const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, statuses), [
1812
1919
  list,
1813
- orderBy,
1814
- sessionOrderByAccount,
1815
- sessionUpdatedAtByAccount,
1816
1920
  sessionIds,
1817
- syncSessionOrderAccount
1818
- ]);
1819
- const rows = (0, react.useMemo)(() => {
1820
- const byId = new Map(baseRows.map((row) => [row.id, row]));
1821
- return reconciledSessionOrder(sessionIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]).flatMap((id) => {
1822
- const row = byId.get(id);
1823
- return row === void 0 ? [] : [row];
1824
- });
1825
- }, [
1826
- baseRows,
1827
- sessionOrderByAccount,
1828
- sessionIds
1921
+ statuses
1829
1922
  ]);
1830
1923
  const [drag, setDrag] = (0, react.useState)(null);
1831
1924
  const dropCommitted = (0, react.useRef)(false);
1832
1925
  useNativeDragAcceptance(drag !== null);
1926
+ const currentId = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
1833
1927
  const commitDrag = (activeDrag, over) => {
1834
1928
  if (dropCommitted.current) return;
1835
1929
  dropCommitted.current = true;
@@ -1844,7 +1938,8 @@ window.__ModuleLoader__.load({
1844
1938
  const nextOrder = rows.map((row) => row.id).filter((id) => id !== activeDrag.sessionId);
1845
1939
  const insertAt = anchor === void 0 ? nextOrder.length : nextOrder.indexOf(anchor);
1846
1940
  nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
1847
- setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder.map((id) => id));
1941
+ const currentBlank = rows.find((node) => node.blank)?.id;
1942
+ setSessionOrder(FLAT_SESSION_ORDER_KEY, pinCurrentBlank(nextOrder, currentBlank));
1848
1943
  };
1849
1944
  const now = Date.now();
1850
1945
  return (0, react_jsx_runtime.jsxs)("div", {
@@ -1858,9 +1953,10 @@ window.__ModuleLoader__.load({
1858
1953
  children: t("empty.none")
1859
1954
  }), rows.map((node) => {
1860
1955
  const active = drag !== null;
1956
+ const normalizeHalf = (half) => node.blank ? "after" : half;
1861
1957
  return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1862
1958
  node,
1863
- currentId: panelActive ? void 0 : list.current,
1959
+ currentId,
1864
1960
  now,
1865
1961
  onOpen: open,
1866
1962
  onRename: onSessionRename,
@@ -1886,14 +1982,14 @@ window.__ModuleLoader__.load({
1886
1982
  ...current,
1887
1983
  over: {
1888
1984
  id: node.id,
1889
- half
1985
+ half: normalizeHalf(half)
1890
1986
  }
1891
1987
  });
1892
1988
  },
1893
1989
  drop: (half) => {
1894
1990
  if (drag !== null) commitDrag(drag, {
1895
1991
  id: node.id,
1896
- half
1992
+ half: normalizeHalf(half)
1897
1993
  });
1898
1994
  },
1899
1995
  end: () => {
@@ -1909,27 +2005,28 @@ window.__ModuleLoader__.load({
1909
2005
  });
1910
2006
  }
1911
2007
  /** Flat search body: local metadata matches plus the current Host result page. */
1912
- 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 }) {
1913
2009
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1914
2010
  const list = useSessions((s) => s);
1915
- const pendingInteractions = useSessionPendingInteraction((s) => s);
2011
+ const statuses = useSessionStatus((s) => s);
1916
2012
  const currentRemote = remote.query === query ? remote : {
1917
2013
  query,
1918
2014
  status: "loading",
1919
2015
  items: [],
1920
2016
  hasMore: false
1921
2017
  };
1922
- 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), [
1923
2019
  list,
1924
2020
  workspaces,
1925
2021
  query,
1926
2022
  archivedSessionIds,
1927
- pendingInteractions,
2023
+ statuses,
1928
2024
  currentRemote,
1929
2025
  resultLimit
1930
2026
  ]);
1931
2027
  const pending = currentRemote.status === "loading";
1932
2028
  const failed = currentRemote.status === "error";
2029
+ const currentId = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
1933
2030
  return (0, react_jsx_runtime.jsxs)("div", {
1934
2031
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1935
2032
  children: [(0, react_jsx_runtime.jsxs)("div", {
@@ -1941,7 +2038,7 @@ window.__ModuleLoader__.load({
1941
2038
  "aria-label": t("search.results.aria"),
1942
2039
  children: results.items.map((result) => (0, react_jsx_runtime.jsx)(SearchResultItem, {
1943
2040
  result,
1944
- currentId: panelActive ? void 0 : list.current,
2041
+ currentId,
1945
2042
  onOpen: open,
1946
2043
  t
1947
2044
  }, result.id))
@@ -1973,8 +2070,9 @@ window.__ModuleLoader__.load({
1973
2070
  * @param props - composed slot props (shell owner share + store + injected actions).
1974
2071
  * @returns the region element tree.
1975
2072
  */
1976
- function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionPendingInteraction, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, 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 }) {
1977
2074
  const home = useHostInfo((info) => info.home);
2075
+ const list = useSessions((state) => state);
1978
2076
  const workspaces = useWorkspaces((state) => state.items);
1979
2077
  const workspacePhase = useWorkspaces((state) => state.phase);
1980
2078
  const workspaceStreamState = useWorkspaces((state) => state.state);
@@ -1984,34 +2082,55 @@ window.__ModuleLoader__.load({
1984
2082
  const orderBy = useStore((s) => s.orderBy);
1985
2083
  const groupExpansion = useStore((s) => s.groupExpansion);
1986
2084
  const sessionOrderByAccount = useStore((s) => s.sessionOrderByAccount);
1987
- const sessionUpdatedAtByAccount = useStore((s) => s.sessionUpdatedAtByAccount);
1988
- const currentBlankSessionId = useSessions((state) => {
1989
- const current = state.current;
1990
- return current !== void 0 && state.byId[current]?.blank === true ? current : void 0;
1991
- });
1992
- const currentBlankAccount = currentBlankSessionId === void 0 || workspacePhase !== "ready" ? void 0 : owningGroupKey(workspaces, currentBlankSessionId);
1993
- const promotedBlank = (0, react.useRef)(void 0);
1994
- (0, react.useEffect)(() => {
1995
- if (currentBlankSessionId === void 0 || currentBlankAccount === void 0) {
1996
- promotedBlank.current = void 0;
1997
- return;
1998
- }
1999
- const promoted = promotedBlank.current;
2000
- if (promoted !== void 0 && promoted.sessionId === currentBlankSessionId && promoted.accountKey === currentBlankAccount) return;
2001
- promotedBlank.current = {
2002
- sessionId: currentBlankSessionId,
2003
- accountKey: currentBlankAccount
2085
+ const workspaceReady = workspacePhase === "ready" && workspaceStreamState !== "loading";
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;
2088
+ const ungroupedMemberIds = (0, react.useMemo)(() => {
2089
+ const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
2090
+ return list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id));
2091
+ }, [list, workspaces]);
2092
+ const flatMemberIds = (0, react.useMemo)(() => visibleSessionIds(list, archivedSessionIds), [archivedSessionIds, list]);
2093
+ const orderedWorkspaces = (0, react.useMemo)(() => workspaces.map((workspace) => {
2094
+ const memberIds = workspace.sessionIds;
2095
+ const baseOrder = orderBy === "updated" ? orderByRecency(memberIds, list.byId) : reconcileManualOrder(memberIds, sessionOrderByAccount[workspace.workspaceId], list.byId);
2096
+ return {
2097
+ ...workspace,
2098
+ sessionIds: pinCurrentBlank(baseOrder, currentBlank !== void 0 && memberIds.includes(currentBlank) ? currentBlank : void 0)
2004
2099
  };
2005
- for (const accountKey of new Set([currentBlankAccount, FLAT_SESSION_ORDER_KEY])) {
2006
- const previous = sessionOrderByAccount[accountKey] ?? [];
2007
- actions.setSessionOrder(accountKey, [currentBlankSessionId, ...previous.filter((id) => id !== currentBlankSessionId)]);
2008
- }
2100
+ }), [
2101
+ currentBlank,
2102
+ list.byId,
2103
+ orderBy,
2104
+ sessionOrderByAccount,
2105
+ workspaces
2106
+ ]);
2107
+ const orderedUngroupedSessionIds = (0, react.useMemo)(() => {
2108
+ return pinCurrentBlank(orderBy === "updated" ? orderByRecency(ungroupedMemberIds, list.byId) : reconcileManualOrder(ungroupedMemberIds, sessionOrderByAccount[""], list.byId), currentBlank !== void 0 && ungroupedMemberIds.includes(currentBlank) ? currentBlank : void 0);
2009
2109
  }, [
2010
- actions.setSessionOrder,
2011
- currentBlankAccount,
2012
- currentBlankSessionId,
2110
+ currentBlank,
2111
+ list.byId,
2112
+ orderBy,
2113
+ sessionOrderByAccount,
2114
+ ungroupedMemberIds
2115
+ ]);
2116
+ const orderedFlatSessionIds = (0, react.useMemo)(() => {
2117
+ return pinCurrentBlank(orderBy === "updated" ? orderByRecency(flatMemberIds, list.byId) : reconcileManualOrder(flatMemberIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY], list.byId), currentBlank !== void 0 && flatMemberIds.includes(currentBlank) ? currentBlank : void 0);
2118
+ }, [
2119
+ currentBlank,
2120
+ flatMemberIds,
2121
+ list.byId,
2122
+ orderBy,
2013
2123
  sessionOrderByAccount
2014
2124
  ]);
2125
+ const activeSessionOrders = (0, react.useMemo)(() => Object.fromEntries([
2126
+ ...orderedWorkspaces.map((workspace) => [workspace.workspaceId, workspace.sessionIds]),
2127
+ ["", orderedUngroupedSessionIds],
2128
+ [FLAT_SESSION_ORDER_KEY, orderedFlatSessionIds]
2129
+ ]), [
2130
+ orderedFlatSessionIds,
2131
+ orderedUngroupedSessionIds,
2132
+ orderedWorkspaces
2133
+ ]);
2015
2134
  (0, react.useEffect)(() => {
2016
2135
  if (workspacePhase !== "ready") return;
2017
2136
  actions.retainAccountKeys([
@@ -2024,6 +2143,44 @@ window.__ModuleLoader__.load({
2024
2143
  workspacePhase,
2025
2144
  workspaces
2026
2145
  ]);
2146
+ (0, react.useEffect)(() => {
2147
+ if (list.phase !== "ready" || workspaceReady || orderBy !== "manual" || currentBlank === void 0) return;
2148
+ const changed = {};
2149
+ for (const [key, ids] of Object.entries(activeSessionOrders)) {
2150
+ if (key !== "__flat_session_order__" && workspacePhase !== "ready") continue;
2151
+ const saved = sessionOrderByAccount[key] ?? [];
2152
+ if (ids[0] !== currentBlank || saved[0] === currentBlank) continue;
2153
+ changed[key] = [currentBlank, ...saved.filter((id) => id !== currentBlank)];
2154
+ }
2155
+ if (Object.keys(changed).length > 0) actions.syncSessionOrders(changed);
2156
+ }, [
2157
+ actions.syncSessionOrders,
2158
+ activeSessionOrders,
2159
+ currentBlank,
2160
+ list.phase,
2161
+ orderBy,
2162
+ sessionOrderByAccount,
2163
+ workspacePhase,
2164
+ workspaceReady
2165
+ ]);
2166
+ (0, react.useEffect)(() => {
2167
+ if (list.phase !== "ready" || !workspaceReady || orderBy !== "manual") return;
2168
+ const changed = Object.fromEntries(Object.entries(activeSessionOrders).filter(([key, ids]) => {
2169
+ const saved = sessionOrderByAccount[key];
2170
+ return saved === void 0 || saved.length !== ids.length || ids.some((id, index) => id !== saved[index]);
2171
+ }));
2172
+ if (Object.keys(changed).length > 0) actions.syncSessionOrders(changed);
2173
+ }, [
2174
+ actions.syncSessionOrders,
2175
+ activeSessionOrders,
2176
+ list.phase,
2177
+ orderBy,
2178
+ sessionOrderByAccount,
2179
+ workspaceReady
2180
+ ]);
2181
+ const saveSessionOrder = (accountKey, order) => {
2182
+ actions.setSessionOrder(accountKey, order, activeSessionOrders);
2183
+ };
2027
2184
  const [query, setQuery] = (0, react.useState)("");
2028
2185
  const [searchExpanded, setSearchExpanded] = (0, react.useState)(false);
2029
2186
  const [revealSessionId, setRevealSessionId] = (0, react.useState)(void 0);
@@ -2296,7 +2453,7 @@ window.__ModuleLoader__.load({
2296
2453
  actions.setGroupBy(mode);
2297
2454
  },
2298
2455
  onOrderPick: (mode) => {
2299
- actions.setOrderBy(mode);
2456
+ actions.setOrderBy(mode, activeSessionOrders);
2300
2457
  },
2301
2458
  t
2302
2459
  }), directoryFlowAvailable && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
@@ -2357,7 +2514,7 @@ window.__ModuleLoader__.load({
2357
2514
  children: wide && (normalizedQuery !== "" ? (0, react_jsx_runtime.jsx)(SearchResults, {
2358
2515
  usePanelInfo,
2359
2516
  useSessions,
2360
- useSessionPendingInteraction,
2517
+ useSessionStatus,
2361
2518
  open: openSearchResult,
2362
2519
  workspaces,
2363
2520
  archivedSessionIds,
@@ -2367,42 +2524,35 @@ window.__ModuleLoader__.load({
2367
2524
  t
2368
2525
  }) : groupBy === "flat" ? (0, react_jsx_runtime.jsx)(FlatList, {
2369
2526
  usePanelInfo,
2370
- useSessions,
2371
- useSessionPendingInteraction,
2527
+ list,
2528
+ sessionIds: orderedFlatSessionIds,
2529
+ useSessionStatus,
2372
2530
  open,
2373
2531
  forkSession,
2374
2532
  onSessionRename,
2375
2533
  onSessionArchive,
2376
- archivedSessionIds,
2377
- orderBy,
2378
- sessionOrderByAccount,
2379
- sessionUpdatedAtByAccount,
2380
- syncSessionOrderAccount: actions.syncSessionOrderAccount,
2381
- setSessionOrder: actions.setSessionOrder,
2534
+ setSessionOrder: saveSessionOrder,
2382
2535
  revealSessionId,
2383
2536
  onSessionRevealed: acknowledgeSessionReveal,
2384
2537
  t
2385
2538
  }) : (0, react_jsx_runtime.jsx)(SessionTree, {
2386
2539
  usePanelInfo,
2387
- useSessions,
2388
- useSessionPendingInteraction,
2540
+ list,
2541
+ useSessionStatus,
2389
2542
  onSessionRename,
2390
2543
  onSessionArchive,
2391
2544
  forkSession,
2392
- workspaces,
2393
- workspaceReady: workspacePhase === "ready" && workspaceStreamState !== "loading",
2545
+ workspaces: orderedWorkspaces,
2546
+ ungroupedSessionIds: orderedUngroupedSessionIds,
2547
+ workspaceReady,
2548
+ nestWorkspaces: groupBy === "workspace-tree",
2394
2549
  groupExpansion,
2395
2550
  setGroupExpanded: actions.setGroupExpanded,
2396
- sessionOrderByAccount,
2397
- sessionUpdatedAtByAccount,
2398
- syncSessionOrderAccount: actions.syncSessionOrderAccount,
2399
- setSessionOrder: actions.setSessionOrder,
2551
+ setSessionOrder: saveSessionOrder,
2400
2552
  archivedSessionIds,
2401
2553
  startSession,
2402
2554
  open,
2403
2555
  insertWorkspaceBefore,
2404
- insertSessionBefore,
2405
- orderBy,
2406
2556
  revealSessionId,
2407
2557
  onSessionRevealed: acknowledgeSessionReveal,
2408
2558
  home,
@@ -2573,6 +2723,7 @@ window.__ModuleLoader__.load({
2573
2723
  "viewOptions.label": "视图选项",
2574
2724
  "groupBy.label": "分组方式",
2575
2725
  "groupBy.workspace": "按工作区",
2726
+ "groupBy.workspaceTree": "按工作区树",
2576
2727
  "groupBy.flat": "单列表",
2577
2728
  "orderBy.label": "排序方式",
2578
2729
  "orderBy.manual": "手动排序",
@@ -2639,6 +2790,7 @@ window.__ModuleLoader__.load({
2639
2790
  "viewOptions.label": "View options",
2640
2791
  "groupBy.label": "Group by",
2641
2792
  "groupBy.workspace": "WorkSpace",
2793
+ "groupBy.workspaceTree": "Workspace Tree",
2642
2794
  "groupBy.flat": "In one list",
2643
2795
  "orderBy.label": "Order by",
2644
2796
  "orderBy.manual": "Manual",
@@ -2758,9 +2910,7 @@ window.__ModuleLoader__.load({
2758
2910
  searchSessions,
2759
2911
  searchResultLimit: sessions.searchResultLimit,
2760
2912
  renameSession: async (sessionId, title) => {
2761
- const session = sessions.binding(sessionId)?.session;
2762
- if (session === void 0) throw new Error(`unknown session "${sessionId}"`);
2763
- const result = await session.rename(title);
2913
+ const result = await sessions.using(sessionId, { source: "workspaceOperation" }, (reference) => reference.binding.session.rename(title));
2764
2914
  if (!result.ok) throw new Error(result.error.message);
2765
2915
  },
2766
2916
  forkSession: (sessionId) => {
@@ -2778,9 +2928,6 @@ window.__ModuleLoader__.load({
2778
2928
  archiveSession: async (sessionId) => {
2779
2929
  await uiWorkspace.archiveSession(sessionId);
2780
2930
  },
2781
- insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
2782
- await workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
2783
- },
2784
2931
  createWorkspace: (input) => workspaces.create(input),
2785
2932
  hooks: {
2786
2933
  directoryFlow: browserFlowSource,