dsh-git-worktree 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/NOTICE +14 -0
  3. package/README.en.md +74 -286
  4. package/README.md +78 -307
  5. package/cordis.patch.yml +7 -3
  6. package/lib/client/WorktreeReviewRow.d.ts +1 -1
  7. package/lib/client/WorktreeReviewRow.js +8 -0
  8. package/lib/client/WorktreeReviewRow.js.map +1 -1
  9. package/lib/client/console-remote/adapter.js +1 -0
  10. package/lib/client/console-remote/adapter.js.map +1 -1
  11. package/lib/client/index.d.ts +1 -1
  12. package/lib/client/index.js +3 -1
  13. package/lib/client/index.js.map +1 -1
  14. package/lib/client/pre-session/controller.js +9 -0
  15. package/lib/client/pre-session/controller.js.map +1 -1
  16. package/lib/client/review-console/ReviewActions.js +14 -12
  17. package/lib/client/review-console/ReviewActions.js.map +1 -1
  18. package/lib/client/styles.js +61 -0
  19. package/lib/client/styles.js.map +1 -1
  20. package/lib/client/target-console/TargetStatusAction.js +2 -2
  21. package/lib/client/target-console/TargetStatusAction.js.map +1 -1
  22. package/lib/client/target-console/WorktreeReviewStatus.js +6 -6
  23. package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
  24. package/lib/client/workspace-sidebar/index.d.ts +33 -0
  25. package/lib/client/workspace-sidebar/index.js +127 -0
  26. package/lib/client/workspace-sidebar/index.js.map +1 -0
  27. package/lib/client/workspace-sidebar/model.d.ts +37 -0
  28. package/lib/client/workspace-sidebar/model.js +88 -0
  29. package/lib/client/workspace-sidebar/model.js.map +1 -0
  30. package/lib/client.js +2885 -25
  31. package/lib/console-contract.d.ts +19 -0
  32. package/lib/console-contract.js.map +1 -1
  33. package/lib/console-host/control-plane.d.ts +2 -1
  34. package/lib/console-host/control-plane.js +53 -1
  35. package/lib/console-host/control-plane.js.map +1 -1
  36. package/lib/console-host/service.d.ts +2 -1
  37. package/lib/console-host/service.js +6 -0
  38. package/lib/console-host/service.js.map +1 -1
  39. package/lib/console-remote/descriptors.js +4 -3
  40. package/lib/console-remote/descriptors.js.map +1 -1
  41. package/lib/console-remote/remote.d.ts +2 -1
  42. package/lib/console-remote/remote.js.map +1 -1
  43. package/lib/console-remote/schemas.d.ts +2 -1
  44. package/lib/console-remote/schemas.js +14 -0
  45. package/lib/console-remote/schemas.js.map +1 -1
  46. package/lib/session-checkout-module.js +7 -3
  47. package/lib/session-checkout-module.js.map +1 -1
  48. package/lib/tools.js +1 -1
  49. package/lib/tools.js.map +1 -1
  50. package/package.json +10 -2
  51. package/scripts/check-publish.mjs +17 -2
  52. package/scripts/check-workspace-sidebar-version.mjs +14 -0
  53. package/scripts/dev-dsh-lib.mjs +1 -1
  54. package/scripts/workspace-sidebar-upstream.d.mts +8 -0
  55. package/scripts/workspace-sidebar-upstream.mjs +160 -0
package/lib/client.js CHANGED
@@ -1,8 +1,40 @@
1
1
  window.__ModuleLoader__.load({ id: "dsh-git-worktree", factory: (require) => {
2
2
  var module = { exports: {} }; var exports = module.exports;
3
+ //#region rolldown:runtime
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
13
+ key = keys[i];
14
+ if (!__hasOwnProp.call(to, key) && key !== except) {
15
+ __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ }
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
25
+ value: mod,
26
+ enumerable: true
27
+ }) : target, mod));
28
+
29
+ //#endregion
3
30
  let react = require("react");
31
+ react = __toESM(react);
4
32
  let react_jsx_runtime = require("react/jsx-runtime");
33
+ react_jsx_runtime = __toESM(react_jsx_runtime);
5
34
  let __deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
35
+ __deepseek_ai_dsh_client_ui_primitives = __toESM(__deepseek_ai_dsh_client_ui_primitives);
36
+ let __deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
37
+ __deepseek_ai_dsh_client_runtime_client = __toESM(__deepseek_ai_dsh_client_runtime_client);
6
38
 
7
39
  //#region node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
8
40
  var _a$1;
@@ -4885,6 +4917,35 @@ const listResponseSchema = strict({
4885
4917
  project: projectSchema,
4886
4918
  worktrees: array(targetSummarySchema)
4887
4919
  });
4920
+ const sidebarTopologyResponseSchema = strict({ projects: array(strict({
4921
+ project: projectSchema,
4922
+ tasks: array(strict({
4923
+ checkoutId: checkoutIdSchema,
4924
+ ownerSessionId: sessionIdSchema,
4925
+ sourceSessionId: sessionIdSchema,
4926
+ iteration: number().int().positive(),
4927
+ revision: revisionSchema,
4928
+ phase: _enum([
4929
+ "preparing",
4930
+ "ready",
4931
+ "applying",
4932
+ "mutating",
4933
+ "finalized",
4934
+ "retained",
4935
+ "discarded",
4936
+ "recovery_required"
4937
+ ]),
4938
+ state: _enum([
4939
+ "working",
4940
+ "ready_for_review",
4941
+ "preview_active",
4942
+ "preview_detached",
4943
+ "recovery_required",
4944
+ "finalized",
4945
+ "discarded"
4946
+ ])
4947
+ }))
4948
+ })) });
4888
4949
  const mutationResponseSchema = strict({
4889
4950
  target: targetSummarySchema,
4890
4951
  changedFiles: array(string()).optional(),
@@ -5056,14 +5117,14 @@ function json(name, schema, typeSymbol, acceptsUndefined = false) {
5056
5117
  }
5057
5118
  };
5058
5119
  }
5059
- function descriptor(method, parameters, resultSchema, resultType) {
5120
+ function descriptor(method, parameters, resultSchema, resultType, withAgent = true) {
5060
5121
  return {
5061
5122
  id: `${PACKAGE}#${SERVICE}/${method}`,
5062
5123
  service: SERVICE,
5063
5124
  namespace: SERVICE,
5064
5125
  method,
5065
5126
  invocation: { kind: "direct" },
5066
- parameters: [agentParameter, ...parameters],
5127
+ parameters: withAgent ? [agentParameter, ...parameters] : parameters,
5067
5128
  result: {
5068
5129
  mode: "strict",
5069
5130
  typeSymbol: `${PACKAGE}/console-contract#${resultType}`,
@@ -5072,6 +5133,7 @@ function descriptor(method, parameters, resultSchema, resultType) {
5072
5133
  };
5073
5134
  }
5074
5135
  const WORKTREE_CONSOLE_DESCRIPTORS = Object.freeze([
5136
+ descriptor("sidebarTopology", [], outcomeSchema(sidebarTopologyResponseSchema), "WorktreeConsoleOutcome<WorktreeSidebarTopologyResponse>", false),
5075
5137
  descriptor("current", [], outcomeSchema(currentResponseSchema), "WorktreeConsoleOutcome<WorktreeConsoleCurrentResponse>"),
5076
5138
  descriptor("list", [json("needsAttention", optionalBooleanSchema, "boolean | undefined", true), json("includeDelivered", optionalBooleanSchema, "boolean | undefined", true)], outcomeSchema(listResponseSchema), "WorktreeConsoleOutcome<WorktreeConsoleListResponse>"),
5077
5139
  descriptor("create", [], outcomeSchema(createResponseSchema), "WorktreeConsoleOutcome<WorktreeConsoleCreateResponse>"),
@@ -7070,13 +7132,13 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7070
7132
  onClick: () => {
7071
7133
  previewLocal();
7072
7134
  },
7073
- children: submitting === "preview" ? "同步中…" : autoPreflightEnabled && (preflightSnapshot.status === "idle" || preflightSnapshot.status === "loading") ? "检查中…" : "同步到 Local 验收"
7135
+ children: submitting === "preview" ? "同步中…" : autoPreflightEnabled && (preflightSnapshot.status === "idle" || preflightSnapshot.status === "loading") ? "检查中…" : "预览修改"
7074
7136
  }) : previewActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7075
7137
  type: "button",
7076
7138
  className: "dsh-wt-button dsh-wt-primary",
7077
7139
  disabled: allDisabled || !target.capabilities.finalizePreview,
7078
7140
  onClick: () => setCommitMode("finalize_preview"),
7079
- children: "验收通过并提交"
7141
+ children: "确认并保存"
7080
7142
  }) : previewRecovery ? rollbackRecoverySafe ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7081
7143
  type: "button",
7082
7144
  className: "dsh-wt-button",
@@ -7084,7 +7146,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7084
7146
  onClick: () => {
7085
7147
  rollbackPreview(true);
7086
7148
  },
7087
- children: submitting === "rollback" ? "处理中…" : "安全撤回 Preview"
7149
+ children: submitting === "rollback" ? "处理中…" : "恢复并撤回预览"
7088
7150
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7089
7151
  type: "button",
7090
7152
  className: "dsh-wt-button",
@@ -7168,7 +7230,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7168
7230
  setCommitMode("finish");
7169
7231
  closeMoreMenu();
7170
7232
  },
7171
- children: "跳过验收,直接提交"
7233
+ children: "跳过预览并保存"
7172
7234
  })
7173
7235
  ] }) : null,
7174
7236
  previewActive && target.capabilities.checkpoint && target.checkpointGeneration ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -7180,7 +7242,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7180
7242
  openCheckpoint();
7181
7243
  closeMoreMenu();
7182
7244
  },
7183
- children: "撤回 Preview,保存阶段并继续"
7245
+ children: "保存阶段并继续"
7184
7246
  }) : null,
7185
7247
  previewActive || rollbackRecoverySafe ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7186
7248
  type: "button",
@@ -7191,7 +7253,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7191
7253
  rollbackPreview(true);
7192
7254
  closeMoreMenu();
7193
7255
  },
7194
- children: previewActive ? "撤回本次预览" : "安全撤回 Preview"
7256
+ children: previewActive ? "撤回本次预览" : "重新尝试撤回"
7195
7257
  }) : null,
7196
7258
  finalizeRecoverySafe ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7197
7259
  type: "button",
@@ -7202,7 +7264,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7202
7264
  setCommitMode("finalize_preview");
7203
7265
  closeMoreMenu();
7204
7266
  },
7205
- children: "直接提交当前任务"
7267
+ children: "保存修改"
7206
7268
  }) : null,
7207
7269
  canDiscard ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7208
7270
  type: "button",
@@ -7222,9 +7284,9 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7222
7284
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
7223
7285
  open: checkpointOpen,
7224
7286
  onClose: closeCheckpoint,
7225
- title: "保存 Worktree 阶段并继续?",
7226
- closeLabel: "关闭保存阶段确认",
7227
- description: previewActive ? "会先按现有 receipt 安全撤回 Local Preview,再把验收快照保存为仅存在于 managed Worktree 的阶段提交。撤回失败或 Local 漂移时会停止。" : "把当前验收快照保存为仅存在于 managed Worktree 的阶段提交,然后在同一 Session 继续开发;Local 不会收到该提交。",
7287
+ title: "保存当前进度并继续?",
7288
+ closeLabel: "关闭保存进度确认",
7289
+ description: previewActive ? "会先安全撤回当前预览,再保存本轮任务进度并继续开发;无法证明可以安全撤回时会停止。当前项目不会立即更新。" : "保存当前任务进度并继续下一阶段;当前项目不会立即更新,最终确认时仍只会生成一次交付。",
7228
7290
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7229
7291
  className: "dsh-wt-modal-footer",
7230
7292
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -7240,7 +7302,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7240
7302
  onClick: () => {
7241
7303
  checkpoint();
7242
7304
  },
7243
- children: submitting === "checkpoint" ? "正在保存…" : "确认保存并继续"
7305
+ children: submitting === "checkpoint" ? "正在保存…" : "保存进度并继续"
7244
7306
  })]
7245
7307
  }),
7246
7308
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -7276,9 +7338,9 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7276
7338
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
7277
7339
  open: commitMode !== null,
7278
7340
  onClose: closeCommit,
7279
- title: commitMode === "finalize_preview" ? "验收通过并提交?" : "跳过 Local 验收,直接提交?",
7280
- closeLabel: "关闭提交确认",
7281
- description: commitMode === "finalize_preview" ? "只会提交当前可撤回 Preview 对应的任务增量;Local 中无关修改不会进入该 Commit。" : "将跳过 Local Preview 验收并直接提交本轮 Worktree 增量。",
7341
+ title: commitMode === "finalize_preview" ? "确认并保存本次修改?" : target?.state === "preview_detached" ? "保存本次修改?" : "跳过预览并直接保存?",
7342
+ closeLabel: "关闭保存确认",
7343
+ description: commitMode === "finalize_preview" ? "只会保存当前预览对应的本轮任务内容;Local 中已有或之后新增的无关修改不会进入该 Commit。" : "将跳过 Local Preview,直接把本轮 Worktree 增量保存为一个 Commit。",
7282
7344
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7283
7345
  className: "dsh-wt-modal-footer",
7284
7346
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -7294,7 +7356,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
7294
7356
  onClick: () => {
7295
7357
  submitCommit();
7296
7358
  },
7297
- children: submitting === "finish" || submitting === "finalize_preview" ? "正在提交…" : retainEnvironment ? "确认提交并保留环境" : "确认提交并清理"
7359
+ children: submitting === "finish" || submitting === "finalize_preview" ? "正在保存…" : retainEnvironment ? "确认交付并保留环境" : "确认交付并清理"
7298
7360
  })]
7299
7361
  }),
7300
7362
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -7736,6 +7798,10 @@ function WorktreeReviewPanel({ review, adapter, services, identity, target, onRe
7736
7798
 
7737
7799
  //#endregion
7738
7800
  //#region src/client/WorktreeReviewRow.tsx
7801
+ function isLocalTargetUnselected(error) {
7802
+ if (error === null) return false;
7803
+ return /(?:^|\b)target_unselected(?:\b|$)/iu.test(error) || error.includes("会话尚未选择 Session Target");
7804
+ }
7739
7805
  function WorktreeReviewRow({ block, sessionId, adapter, services }) {
7740
7806
  const model = parseReviewTool(block);
7741
7807
  const payload = model.payload;
@@ -7803,6 +7869,7 @@ function WorktreeReviewRow({ block, sessionId, adapter, services }) {
7803
7869
  refreshNonce,
7804
7870
  sessionId
7805
7871
  ]);
7872
+ if (model.lifecycle === "error" && isLocalTargetUnselected(model.error)) return null;
7806
7873
  const identity = payload && sessionId && liveTarget?.checkoutId ? {
7807
7874
  sessionId,
7808
7875
  checkoutId: liveTarget.checkoutId,
@@ -7970,6 +8037,67 @@ const REVIEW_CONSOLE_STYLES = String.raw`
7970
8037
  //#endregion
7971
8038
  //#region src/client/styles.ts
7972
8039
  const WORKTREE_STYLES = String.raw`
8040
+ /* Managed owner decoration inside the version/hash-gated official Workspace row. */
8041
+ .dsh-git-worktree-sidebar-icon,
8042
+ .dsh-git-worktree-sidebar-badge {
8043
+ flex: 0 0 auto;
8044
+ }
8045
+ .dsh-git-worktree-sidebar-icon {
8046
+ width: 16px;
8047
+ height: 16px;
8048
+ display: inline-flex;
8049
+ align-items: center;
8050
+ justify-content: center;
8051
+ color: var(--dsw-alias-state-business-primary);
8052
+ }
8053
+ .dsh-git-worktree-sidebar-badge {
8054
+ box-sizing: border-box;
8055
+ max-width: 76px;
8056
+ min-height: 20px;
8057
+ padding: 1px 6px;
8058
+ overflow: hidden;
8059
+ border: 1px solid color-mix(in srgb, var(--dsh-wt-sidebar-accent) 24%, transparent);
8060
+ border-radius: 999px;
8061
+ background: color-mix(in srgb, var(--dsh-wt-sidebar-accent) 10%, transparent);
8062
+ color: var(--dsh-wt-sidebar-accent);
8063
+ font-size: 11px;
8064
+ font-weight: 600;
8065
+ line-height: 16px;
8066
+ text-overflow: ellipsis;
8067
+ white-space: nowrap;
8068
+ }
8069
+ .dsh-git-worktree-sidebar-icon,
8070
+ .dsh-git-worktree-sidebar-badge[data-worktree-state="working"] {
8071
+ --dsh-wt-sidebar-accent: var(--dsw-alias-state-business-primary);
8072
+ }
8073
+ .dsh-git-worktree-sidebar-icon[data-worktree-state="ready_for_review"],
8074
+ .dsh-git-worktree-sidebar-badge[data-worktree-state="ready_for_review"] {
8075
+ --dsh-wt-sidebar-accent: var(--dsw-alias-state-warn-label);
8076
+ color: var(--dsw-alias-state-warn-label);
8077
+ }
8078
+ .dsh-git-worktree-sidebar-icon[data-worktree-state="preview_active"],
8079
+ .dsh-git-worktree-sidebar-badge[data-worktree-state="preview_active"] {
8080
+ --dsh-wt-sidebar-accent: color-mix(in srgb, var(--dsw-alias-state-business-primary) 68%, var(--dsw-alias-state-error-primary));
8081
+ color: var(--dsh-wt-sidebar-accent);
8082
+ }
8083
+ .dsh-git-worktree-sidebar-icon[data-worktree-state="preview_detached"],
8084
+ .dsh-git-worktree-sidebar-badge[data-worktree-state="preview_detached"],
8085
+ .dsh-git-worktree-sidebar-icon[data-worktree-state="recovery_required"],
8086
+ .dsh-git-worktree-sidebar-badge[data-worktree-state="recovery_required"] {
8087
+ --dsh-wt-sidebar-accent: var(--dsw-alias-state-error-primary);
8088
+ color: var(--dsw-alias-state-error-primary);
8089
+ }
8090
+ .dsh-git-worktree-sidebar-icon[data-worktree-state="finalized"],
8091
+ .dsh-git-worktree-sidebar-badge[data-worktree-state="finalized"] {
8092
+ --dsh-wt-sidebar-accent: var(--dsw-alias-state-success-primary);
8093
+ color: var(--dsw-alias-state-success-primary);
8094
+ }
8095
+ .dsh-git-worktree-sidebar-icon[data-worktree-state="discarded"],
8096
+ .dsh-git-worktree-sidebar-badge[data-worktree-state="discarded"] {
8097
+ --dsh-wt-sidebar-accent: var(--dsw-alias-label-tertiary);
8098
+ color: var(--dsw-alias-label-tertiary);
8099
+ }
8100
+
7973
8101
  .dsh-wt-card {
7974
8102
  --wt-ink: color-mix(in srgb, currentColor 92%, transparent);
7975
8103
  --wt-muted: color-mix(in srgb, currentColor 58%, transparent);
@@ -8115,6 +8243,9 @@ var PreSessionWorktreeController = class {
8115
8243
  this.services.sessions.open(created.targetSessionId);
8116
8244
  request.inputActions.setDraft("");
8117
8245
  for (const imageId of request.input.imageIds) request.inputActions.removeImage(imageId);
8246
+ try {
8247
+ await this.services.workspaces.archiveSession(request.sessionId);
8248
+ } catch {}
8118
8249
  return created.target;
8119
8250
  } catch (error) {
8120
8251
  if (created !== void 0) {
@@ -8346,7 +8477,7 @@ function registerPreSessionWorktree(ctx, adapter, services) {
8346
8477
 
8347
8478
  //#endregion
8348
8479
  //#region src/client/target-console/WorktreeConsoleView.tsx
8349
- const STATE_LABELS$1 = {
8480
+ const STATE_LABELS$2 = {
8350
8481
  local: "Local",
8351
8482
  creating: "创建中…",
8352
8483
  working: "修改中",
@@ -8380,7 +8511,7 @@ function TargetState({ target }) {
8380
8511
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8381
8512
  className: "dsh-wtc-state-dot",
8382
8513
  "aria-hidden": true
8383
- }), STATE_LABELS$1[target.state]]
8514
+ }), STATE_LABELS$2[target.state]]
8384
8515
  });
8385
8516
  }
8386
8517
  const ATTENTION_RANK = {
@@ -8822,7 +8953,7 @@ function WorktreeManagerModal({ open, sessionId, adapter, services, focusCheckou
8822
8953
 
8823
8954
  //#endregion
8824
8955
  //#region src/client/target-console/TargetStatusAction.tsx
8825
- const STATE_LABELS = {
8956
+ const STATE_LABELS$1 = {
8826
8957
  local: "Local",
8827
8958
  creating: "创建中…",
8828
8959
  working: "修改中",
@@ -8891,7 +9022,7 @@ function TargetStatusAction({ sessionId, adapter, services }) {
8891
9022
  };
8892
9023
  }, [refresh, sessionId]);
8893
9024
  const state = target?.state ?? (loading ? "loading" : "error");
8894
- const stateLabel = state === "loading" ? "加载中…" : state === "error" ? "不可用" : STATE_LABELS[state];
9025
+ const stateLabel = state === "loading" ? "加载中…" : state === "error" ? "不可用" : STATE_LABELS$1[state];
8895
9026
  const triggerLabel = target?.state === "local" ? "Local" : target ? `Worktree · ${stateLabel}` : stateLabel;
8896
9027
  const expiry = target?.state === "retained" && target.expiresAt ? new Date(target.expiresAt).toLocaleDateString() : null;
8897
9028
  const accessibleLabel = expiry === null ? `Session Target:${triggerLabel}` : `Session Target:${triggerLabel},到期 ${expiry}`;
@@ -9022,7 +9153,7 @@ function TargetStatusAction({ sessionId, adapter, services }) {
9022
9153
  disabled: pendingAction !== null
9023
9154
  }] : target?.capabilities.discard ? [{
9024
9155
  id: "discard",
9025
- label: pendingAction === "discard" ? "处理中…" : target.state === "preview_active" ? "撤回验收并清理 Worktree" : "放弃任务并清理 Worktree",
9156
+ label: pendingAction === "discard" ? "处理中…" : "放弃任务并清理 Worktree",
9026
9157
  danger: true,
9027
9158
  disabled: pendingAction !== null
9028
9159
  }] : [],
@@ -9080,7 +9211,7 @@ function TargetStatusAction({ sessionId, adapter, services }) {
9080
9211
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
9081
9212
  open: cleanupConfirmOpen,
9082
9213
  onClose: () => setCleanupConfirmOpen(false),
9083
- title: target?.state === "preview_active" ? "撤回验收并清理 Worktree?" : "放弃任务并清理 Worktree?",
9214
+ title: "放弃任务并清理 Worktree?",
9084
9215
  closeLabel: "取消清理 Worktree",
9085
9216
  description: target?.state === "preview_active" ? "Host 会先安全撤回 Local Preview;无法证明可无损撤回时会停止并保留恢复现场。" : "Worktree 中尚未交付的修改会永久丢弃;Local Checkout 不会被静默覆盖。",
9086
9217
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -9306,7 +9437,7 @@ function WorktreeReviewStatus({ session, adapter, services }) {
9306
9437
  } : current);
9307
9438
  };
9308
9439
  const detachedFromHeadDrift = target.state === "preview_detached" && target.previewRecovery?.reason === "stale_local";
9309
- const label = target.state === "ready_for_review" ? target.reviewSlot === "waiting" ? "另一个任务正在占用 Local 验收槽位" : "Worktree 已准备好同步到 Local 验收" : target.state === "preview_active" ? "本轮修改正在 Local 等待验收" : target.state === "preview_detached" ? detachedFromHeadDrift ? "Local branch/HEAD 已变化,Preview 等待安全撤回" : "Preview 与 Local 发生冲突,已保留恢复现场" : target.state === "recovery_required" ? "验收操作中断,需要恢复 Preview" : target.state === "cleanup_pending" ? "Commit 已创建,Worktree 清理待重试" : "本轮已提交,运行环境暂时保留";
9440
+ const label = target.state === "ready_for_review" ? target.reviewSlot === "waiting" ? "另一个任务正在占用 Local 验收槽位" : "修改已完成,等待你预览确认" : target.state === "preview_active" ? "正在预览本次修改,确认后即可保存" : target.state === "preview_detached" ? detachedFromHeadDrift ? "当前项目已有新变化,预览等待安全恢复" : "预览与 Local 发生冲突,已保留恢复现场" : target.state === "recovery_required" ? "预览需要恢复,安全记录已保留" : target.state === "cleanup_pending" ? "修改已保存,Worktree 清理待重试" : "修改已保存,运行环境暂时保留";
9310
9441
  const detail = target.state === "preview_detached" ? detachedFromHeadDrift ? "同分支快进可安全重试;切分支或改写历史时不会写入。" : "自动撤回会重新检查冲突;无法证明安全时不会写入。" : `${review.changedFiles.length} 个文件 · ${review.validationStatus === "passed" ? "自动验证通过" : "请检查验证结果"}`;
9311
9442
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
9312
9443
  className: "dsh-wt-review-dock",
@@ -9523,6 +9654,2731 @@ const TARGET_CONSOLE_STYLES = String.raw`
9523
9654
  }
9524
9655
  `;
9525
9656
 
9657
+ //#endregion
9658
+ //#region \0virtual:dsh-official-workspace-client
9659
+ const modules = {
9660
+ "@deepseek-ai/dsh-client-runtime/client": __deepseek_ai_dsh_client_runtime_client,
9661
+ "react/jsx-runtime": react_jsx_runtime,
9662
+ "react": react,
9663
+ "@deepseek-ai/dsh-client-ui-primitives": __deepseek_ai_dsh_client_ui_primitives
9664
+ };
9665
+ const official = ((require$1) => {
9666
+ var module$1 = { exports: {} };
9667
+ var exports$1 = module$1.exports;
9668
+ Object.defineProperty(exports$1, Symbol.toStringTag, { value: "Module" });
9669
+ let _deepseek_ai_dsh_client_runtime_client = require$1("@deepseek-ai/dsh-client-runtime/client");
9670
+ let react_jsx_runtime$1 = require$1("react/jsx-runtime");
9671
+ let react$1 = require$1("react");
9672
+ let _deepseek_ai_dsh_client_ui_primitives = require$1("@deepseek-ai/dsh-client-ui-primitives");
9673
+ /**
9674
+ * The workspace browser's viewing store: the session-list grouping mode,
9675
+ * persisted across reloads. Module level exports the factory only (a
9676
+ * module-level handle would pin the store identity across plugin reloads);
9677
+ * register() receives the factory and the browser derives its PropsStore
9678
+ * share from the return type.
9679
+ */
9680
+ /** Browser-local order account for the hierarchy-free flat Session list. */
9681
+ const FLAT_SESSION_ORDER_KEY = "__flat_session_order__";
9682
+ /**
9683
+ * Create the workspace browser viewing store handle.
9684
+ * @returns the store handle (spec + type + identity + factory in one).
9685
+ */
9686
+ function createWorkspaceViewStore() {
9687
+ return (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
9688
+ init: () => ({
9689
+ groupBy: "workspace",
9690
+ orderBy: "updated",
9691
+ groupExpansion: {},
9692
+ sessionOrderByAccount: {},
9693
+ sessionUpdatedAtByAccount: {}
9694
+ }),
9695
+ persist: "dsh.workspace.view.v5",
9696
+ actions: {
9697
+ setGroupBy: (d, mode) => {
9698
+ d.groupBy = mode;
9699
+ },
9700
+ setOrderBy: (d, mode) => {
9701
+ d.orderBy = mode;
9702
+ },
9703
+ setGroupExpanded: (d, key, expanded) => {
9704
+ d.groupExpansion[key] = expanded;
9705
+ },
9706
+ retainAccountKeys: (d, workspaceKeys) => {
9707
+ const retained = new Set(workspaceKeys);
9708
+ d.groupExpansion = Object.fromEntries(Object.entries(d.groupExpansion).filter(([key]) => retained.has(key)));
9709
+ d.sessionOrderByAccount = Object.fromEntries(Object.entries(d.sessionOrderByAccount).filter(([key]) => retained.has(key)));
9710
+ d.sessionUpdatedAtByAccount = Object.fromEntries(Object.entries(d.sessionUpdatedAtByAccount).filter(([key]) => retained.has(key)));
9711
+ },
9712
+ syncSessionOrderAccount: (d, accountKey, order, updatedAt) => {
9713
+ d.sessionOrderByAccount[accountKey] = order;
9714
+ d.sessionUpdatedAtByAccount[accountKey] = updatedAt;
9715
+ },
9716
+ setSessionOrder: (d, accountKey, order) => {
9717
+ d.sessionOrderByAccount[accountKey] = order;
9718
+ }
9719
+ }
9720
+ });
9721
+ }
9722
+ function r(e) {
9723
+ var t, f, n = "";
9724
+ if ("string" == typeof e || "number" == typeof e) n += e;
9725
+ else if ("object" == typeof e) if (Array.isArray(e)) {
9726
+ var o = e.length;
9727
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
9728
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
9729
+ return n;
9730
+ }
9731
+ function clsx() {
9732
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
9733
+ return n;
9734
+ }
9735
+ /** Display label for the ungrouped bucket row. */
9736
+ const UNGROUPED_LABEL = "Ungrouped";
9737
+ /**
9738
+ * Directory display label: basename of the path (both separators accepted).
9739
+ * Ungrouped-bucket fallback for surfaces without a workspace title.
9740
+ * @param cwd - directory path, or undefined for the ungrouped bucket.
9741
+ * @returns basename, the raw cwd when it has no basename, or the ungrouped label.
9742
+ */
9743
+ function workspaceLabel(cwd) {
9744
+ if (cwd === void 0 || cwd === "") return UNGROUPED_LABEL;
9745
+ const base = cwd.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
9746
+ return base !== void 0 && base !== "" ? base : cwd;
9747
+ }
9748
+ /** Recency comparator: newest first, id as the deterministic tiebreak (ids are unique per group). */
9749
+ function byRecency(a, b) {
9750
+ if (b.updatedAt !== a.updatedAt) return b.updatedAt - a.updatedAt;
9751
+ return a.id < b.id ? -1 : 1;
9752
+ }
9753
+ /**
9754
+ * Ordinary sessions are visible; among blank sessions, only the current one
9755
+ * is visible. Subagent children use their parent header catalog; archived
9756
+ * sessions are visible nowhere, while their accounting slots remain so
9757
+ * unarchiving restores position.
9758
+ */
9759
+ function sessionVisible(session, current, archived) {
9760
+ return session.origin !== "subagent" && !archived.has(session.id) && (!session.blank || session.id === current);
9761
+ }
9762
+ /**
9763
+ * A blank session is the selected Workspace's provisional New Session row;
9764
+ * its canonical title never enters search (blank rows are query-excluded)
9765
+ * and the renderer localizes its display label.
9766
+ */
9767
+ function sessionTitle(session) {
9768
+ return session.blank ? "New Session" : session.displayTitle;
9769
+ }
9770
+ /** Build one group without projecting session lineage into presentation. */
9771
+ function buildGroup(key, workspaceId, cwd, createdAt, label, members, order) {
9772
+ const sessions = [...members];
9773
+ if (order === "recency") sessions.sort(byRecency);
9774
+ return {
9775
+ key,
9776
+ workspaceId,
9777
+ cwd,
9778
+ createdAt,
9779
+ label,
9780
+ sessions
9781
+ };
9782
+ }
9783
+ /** Apply a stored Ungrouped order and append newly loose Sessions by recency. */
9784
+ function orderedUngrouped(members, stored) {
9785
+ const byId = new Map(members.map((session) => [session.id, session]));
9786
+ const included = /* @__PURE__ */ new Set();
9787
+ const ordered = [];
9788
+ for (const key of stored) {
9789
+ const session = byId.get(key);
9790
+ if (session === void 0 || included.has(key)) continue;
9791
+ ordered.push(session);
9792
+ included.add(key);
9793
+ }
9794
+ for (const session of [...members].sort(byRecency)) {
9795
+ if (included.has(session.id)) continue;
9796
+ ordered.push(session);
9797
+ }
9798
+ return ordered;
9799
+ }
9800
+ /**
9801
+ * Group Sessions by Host Workspace: one group per entity in stable Host
9802
+ * order, with members resolved from sessionIds in their stored order. Sessions
9803
+ * outside every Workspace trail in the browser-local Ungrouped order, which
9804
+ * falls back to recency before that order is initialized.
9805
+ */
9806
+ function groupByWorkspace(list, workspaces, archived, ungroupedOrder) {
9807
+ const groups = [];
9808
+ const accounted = /* @__PURE__ */ new Set();
9809
+ for (const workspace of workspaces) {
9810
+ const members = [];
9811
+ for (const id of workspace.sessionIds) {
9812
+ const summary = list.byId[id];
9813
+ if (summary === void 0) continue;
9814
+ accounted.add(id);
9815
+ if (!sessionVisible(summary, list.current, archived)) continue;
9816
+ members.push(summary);
9817
+ }
9818
+ const group = buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members, "account");
9819
+ if (workspace.__dshGitWorktreeProtected === true) group.__dshGitWorktreeProtected = true;
9820
+ groups.push(group);
9821
+ }
9822
+ const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, list.current, archived));
9823
+ if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, UNGROUPED_LABEL, ungroupedOrder === void 0 ? stray : orderedUngrouped(stray, ungroupedOrder), ungroupedOrder === void 0 ? "recency" : "account"));
9824
+ return groups;
9825
+ }
9826
+ function sessionNode(s, descendants) {
9827
+ return {
9828
+ id: s.id,
9829
+ title: sessionTitle(s),
9830
+ blank: s.blank,
9831
+ running: s.running,
9832
+ runningSubagentCount: descendants.get(s.id)?.runningCount ?? 0,
9833
+ completed: s.completed === true,
9834
+ updatedAt: s.updatedAt,
9835
+ ...s.__dshGitWorktree === void 0 ? {} : { __dshGitWorktree: s.__dshGitWorktree },
9836
+ ...s.pendingInteraction === void 0 ? {} : { pendingInteraction: s.pendingInteraction }
9837
+ };
9838
+ }
9839
+ /**
9840
+ * Derive the workspace browser groups with every session as a top-level row.
9841
+ *
9842
+ * Every group shows; sessions populate under expanded groups in the selected
9843
+ * local order. Blank sessions are excluded except for the selected
9844
+ * provisional New Session row; archived sessions are excluded everywhere.
9845
+ * Content search lives outside this derivation
9846
+ * (see {@link deriveSearchResults}).
9847
+ * @param list - sessions list snapshot (`current` feeds containsCurrent).
9848
+ * @param workspaces - real workspaces in stable Host order.
9849
+ * @param archivedSessionIds - registry-global archive set.
9850
+ * @param view - local expansion arrays.
9851
+ * @returns group sections in render order.
9852
+ */
9853
+ function deriveGroups(list, workspaces, archivedSessionIds, view) {
9854
+ const archived = new Set(archivedSessionIds);
9855
+ const expandedGroups = new Set(view.expandedGroups);
9856
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
9857
+ const currentGroup = list.current === void 0 ? void 0 : workspaces.find((w) => w.sessionIds.includes(list.current))?.workspaceId ?? "";
9858
+ const groups = [];
9859
+ for (const g of groupByWorkspace(list, workspaces, archived, view.ungroupedOrder)) {
9860
+ const expanded = expandedGroups.has(g.key);
9861
+ groups.push({
9862
+ key: g.key,
9863
+ workspaceId: g.workspaceId,
9864
+ cwd: g.cwd,
9865
+ createdAt: g.createdAt,
9866
+ label: g.label,
9867
+ ...g.__dshGitWorktreeProtected === true ? { __dshGitWorktreeProtected: true } : {},
9868
+ sessionCount: g.sessions.length,
9869
+ expanded,
9870
+ containsCurrent: g.key === currentGroup,
9871
+ sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants)) : []
9872
+ });
9873
+ }
9874
+ return groups;
9875
+ }
9876
+ /**
9877
+ * Derive the flat session list ("In one list" mode): every session — fork
9878
+ * children included — as a top-level row, strictly newest-first. No grouping,
9879
+ * no parent/child adjacency. Content search lives outside this derivation
9880
+ * (see {@link deriveSearchResults}).
9881
+ * @param list - sessions list snapshot.
9882
+ * @param archivedSessionIds - registry-global archive set.
9883
+ * @returns flat rows in render order.
9884
+ */
9885
+ function deriveFlat(list, archivedSessionIds) {
9886
+ const archived = new Set(archivedSessionIds);
9887
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
9888
+ const rows = [];
9889
+ for (const id of list.ids) {
9890
+ const s = list.byId[id];
9891
+ if (s === void 0 || !sessionVisible(s, list.current, archived)) continue;
9892
+ rows.push(s);
9893
+ }
9894
+ rows.sort(byRecency);
9895
+ return rows.map((session) => sessionNode(session, descendants));
9896
+ }
9897
+ /**
9898
+ * Merge immediate title/Workspace substring matches with ranked Host content
9899
+ * matches. Local rows lead newest-first, content-only rows retain backend
9900
+ * order, and duplicate sessions receive the backend snippet in place.
9901
+ * @param list - session metadata authority.
9902
+ * @param workspaces - Workspace membership and display labels.
9903
+ * @param query - caller text; surrounding whitespace is ignored.
9904
+ * @param archivedSessionIds - registry-global archive set (members never match).
9905
+ * @param content - ranked Host content-search page.
9906
+ * @param limit - protocol-owned maximum merged row count.
9907
+ * @returns bounded deduplicated flat rows and a refine-query hint bit.
9908
+ */
9909
+ function deriveSearchResults(list, workspaces, query, archivedSessionIds, content, limit) {
9910
+ const q = query.trim().toLowerCase();
9911
+ if (q === "") return {
9912
+ items: [],
9913
+ hasMore: false
9914
+ };
9915
+ const archived = new Set(archivedSessionIds);
9916
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
9917
+ const workspaceBySession = /* @__PURE__ */ new Map();
9918
+ for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
9919
+ const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
9920
+ const contentBySession = /* @__PURE__ */ new Map();
9921
+ for (const item of content.items) if (!contentBySession.has(item.sessionId)) contentBySession.set(item.sessionId, item);
9922
+ const local = [];
9923
+ for (const id of list.ids) {
9924
+ const summary = list.byId[id];
9925
+ if (summary === void 0 || summary.blank || !sessionVisible(summary, list.current, archived)) continue;
9926
+ if (sessionTitle(summary).toLowerCase().includes(q) || labelOf(summary).toLowerCase().includes(q)) local.push(summary);
9927
+ }
9928
+ local.sort(byRecency);
9929
+ const ordered = [];
9930
+ const included = /* @__PURE__ */ new Set();
9931
+ const include = (summary) => {
9932
+ if (included.has(summary.id)) return;
9933
+ included.add(summary.id);
9934
+ ordered.push(summary);
9935
+ };
9936
+ for (const summary of local) include(summary);
9937
+ for (const item of content.items) {
9938
+ const summary = list.byId[item.sessionId];
9939
+ if (summary !== void 0 && !summary.blank && sessionVisible(summary, list.current, archived)) include(summary);
9940
+ }
9941
+ return {
9942
+ items: ordered.slice(0, limit).map((summary) => {
9943
+ const match = contentBySession.get(summary.id);
9944
+ return {
9945
+ id: summary.id,
9946
+ title: sessionTitle(summary),
9947
+ workspace: labelOf(summary),
9948
+ running: summary.running,
9949
+ runningSubagentCount: descendants.get(summary.id)?.runningCount ?? 0,
9950
+ ...summary.__dshGitWorktree === void 0 ? {} : { __dshGitWorktree: summary.__dshGitWorktree },
9951
+ ...summary.pendingInteraction === void 0 ? {} : { pendingInteraction: summary.pendingInteraction },
9952
+ completed: summary.completed === true,
9953
+ ...match === void 0 ? {} : { snippet: match.snippet }
9954
+ };
9955
+ }),
9956
+ hasMore: content.hasMore || ordered.length > limit
9957
+ };
9958
+ }
9959
+ /**
9960
+ * Compact relative time for session rows, as a structured bucket the
9961
+ * renderer localizes ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
9962
+ * @param updatedAt - epoch ms of the session's last activity.
9963
+ * @param now - current epoch ms (injected for pure rendering).
9964
+ * @returns the row's trailing time bucket and magnitude.
9965
+ */
9966
+ function relativeTime(updatedAt, now) {
9967
+ const MIN = 6e4;
9968
+ const HOUR = 36e5;
9969
+ const DAY = 864e5;
9970
+ const diff = Math.max(0, now - updatedAt);
9971
+ if (diff < MIN) return {
9972
+ unit: "now",
9973
+ n: 0
9974
+ };
9975
+ if (diff < HOUR) return {
9976
+ unit: "minutes",
9977
+ n: Math.floor(diff / MIN)
9978
+ };
9979
+ if (diff < DAY) return {
9980
+ unit: "hours",
9981
+ n: Math.floor(diff / HOUR)
9982
+ };
9983
+ if (diff < 30 * DAY) return {
9984
+ unit: "days",
9985
+ n: Math.floor(diff / DAY)
9986
+ };
9987
+ if (diff < 365 * DAY) return {
9988
+ unit: "months",
9989
+ n: Math.floor(diff / (30 * DAY))
9990
+ };
9991
+ return {
9992
+ unit: "years",
9993
+ n: Math.floor(diff / (365 * DAY))
9994
+ };
9995
+ }
9996
+ const css$2 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding:0 8px;display:flex}.YDXeBa_projectRow:hover,.YDXeBa_sessionRow:hover,.YDXeBa_sessionRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.YDXeBa_searchResultRow:hover,.YDXeBa_searchResultRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultHeading{align-items:center;min-width:0;display:flex}.YDXeBa_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.YDXeBa_searchResultWorkspace,.YDXeBa_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.YDXeBa_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}.YDXeBa_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}.YDXeBa_projectRow{box-sizing:border-box;align-items:center;height:34px}.YDXeBa_projectRow .YDXeBa_rowActions{height:20px}.YDXeBa_sessionRow{height:32px;animation:YDXeBa_row-in .15s var(--ds-ease-in-out);gap:0}.YDXeBa_sessionRow .YDXeBa_title{margin:0 6px 0 4px}.YDXeBa_flatSessionRowWithoutStatus .YDXeBa_title{margin-left:0}@keyframes YDXeBa_row-in{0%{opacity:0}}.YDXeBa_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.YDXeBa_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.YDXeBa_folderActive{color:var(--dsw-alias-state-business-primary)}.YDXeBa_projectRow .YDXeBa_chevron{display:none}.YDXeBa_projectRow:hover .YDXeBa_chevron{display:inline-flex}.YDXeBa_projectRow:hover .YDXeBa_folder{display:none}.YDXeBa_arrow{transition:transform .15s var(--ds-ease-in-out)}.YDXeBa_arrowOpen{transform:rotate(90deg)}.YDXeBa_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.YDXeBa_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_renameInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.YDXeBa_sessionRow .YDXeBa_title{flex:1}.YDXeBa_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.YDXeBa_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:20px}.YDXeBa_dot{flex:none}.YDXeBa_rowActions{flex:none;align-items:center;gap:12px;display:none}.YDXeBa_projectRow:hover .YDXeBa_rowActions,.YDXeBa_sessionRow:hover .YDXeBa_rowActions,.YDXeBa_projectRow.YDXeBa_menuOpen .YDXeBa_rowActions,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_rowActions{display:inline-flex}.YDXeBa_sessionRow:hover .YDXeBa_time,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_time{display:none}.YDXeBa_projectRow.YDXeBa_menuOpen,.YDXeBa_sessionRow.YDXeBa_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_sessionRow.YDXeBa_dropBefore,.YDXeBa_sessionRow.YDXeBa_dropAfter{position:relative}.YDXeBa_sessionRow.YDXeBa_dropBefore:before,.YDXeBa_sessionRow.YDXeBa_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}.YDXeBa_sessionRow.YDXeBa_dropBefore:before{top:-7px}.YDXeBa_sessionRow.YDXeBa_dropAfter:after{bottom:-7px}.YDXeBa_hoverContent{flex-direction:column;gap:8px;display:flex}.YDXeBa_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.YDXeBa_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.YDXeBa_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.YDXeBa_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.YDXeBa_iconButton{cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.YDXeBa_iconButton:hover{color:var(--dsw-alias-label-primary)}.YDXeBa_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){.YDXeBa_sessionRow,.YDXeBa_arrow{transition:none;animation:none}}";
9997
+ const tagId$2 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
9998
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
9999
+ const tag = document.createElement("style");
10000
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workspace";
10001
+ tag.dataset.pluginCss = tagId$2;
10002
+ tag.textContent = css$2;
10003
+ document.head.appendChild(tag);
10004
+ }
10005
+ var Rows_module_css_default = {
10006
+ "arrow": "YDXeBa_arrow",
10007
+ "arrowOpen": "YDXeBa_arrowOpen",
10008
+ "chevron": "YDXeBa_chevron",
10009
+ "dot": "YDXeBa_dot",
10010
+ "dropAfter": "YDXeBa_dropAfter",
10011
+ "dropBefore": "YDXeBa_dropBefore",
10012
+ "flatSessionRowWithoutStatus": "YDXeBa_flatSessionRowWithoutStatus",
10013
+ "folder": "YDXeBa_folder",
10014
+ "folderActive": "YDXeBa_folderActive",
10015
+ "hoverContent": "YDXeBa_hoverContent",
10016
+ "hoverPath": "YDXeBa_hoverPath",
10017
+ "hoverStatus": "YDXeBa_hoverStatus",
10018
+ "hoverTime": "YDXeBa_hoverTime",
10019
+ "hoverTitle": "YDXeBa_hoverTitle",
10020
+ "iconButton": "YDXeBa_iconButton",
10021
+ "menuOpen": "YDXeBa_menuOpen",
10022
+ "meta": "YDXeBa_meta",
10023
+ "projectRow": "YDXeBa_projectRow",
10024
+ "projectText": "YDXeBa_projectText",
10025
+ "renameInput": "YDXeBa_renameInput",
10026
+ "row-in": "YDXeBa_row-in",
10027
+ "rowActions": "YDXeBa_rowActions",
10028
+ "searchResultHeading": "YDXeBa_searchResultHeading",
10029
+ "searchResultMeta": "YDXeBa_searchResultMeta",
10030
+ "searchResultRow": "YDXeBa_searchResultRow",
10031
+ "searchResultSnippet": "YDXeBa_searchResultSnippet",
10032
+ "searchResultTitle": "YDXeBa_searchResultTitle",
10033
+ "searchResultWorkspace": "YDXeBa_searchResultWorkspace",
10034
+ "selected": "YDXeBa_selected",
10035
+ "sessionRow": "YDXeBa_sessionRow",
10036
+ "slot": "YDXeBa_slot",
10037
+ "time": "YDXeBa_time",
10038
+ "title": "YDXeBa_title",
10039
+ "visuallyHidden": "YDXeBa_visuallyHidden"
10040
+ };
10041
+ /**
10042
+ * Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
10043
+ * all data and callbacks arrive via props. Hover swaps (folder->chevron,
10044
+ * time->ellipsis, action buttons) are CSS-only. Row ... menus are visual-only
10045
+ * except workspace Rename/Delete and session Rename/Fork/Archive; the session
10046
+ * and workspace hover cards are suppressed while a menu is open.
10047
+ */
10048
+ /** Row display title: blank rows show the localized New Session label. */
10049
+ function displayTitle(node, t) {
10050
+ return node.blank ? t("session.new") : node.title;
10051
+ }
10052
+ /** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
10053
+ function timeLabel(updatedAt, now, t) {
10054
+ const { unit, n } = relativeTime(updatedAt, now);
10055
+ return unit === "now" ? t("time.now") : t(`time.${unit}`, { n });
10056
+ }
10057
+ /** Hover-card variant: distances wrap in the ago template; the now bucket stays bare (no "now ago"). */
10058
+ function hoverTimeLabel(updatedAt, now, t) {
10059
+ const { unit, n } = relativeTime(updatedAt, now);
10060
+ return unit === "now" ? t("time.now") : t("time.ago", { t: t(`time.${unit}`, { n }) });
10061
+ }
10062
+ /**
10063
+ * Absolute creation time through the dictionary's date template (the message
10064
+ * clock pattern): `toLocaleString` would follow the browser language, not the
10065
+ * app locale, and produce mixed-language text after a switch.
10066
+ */
10067
+ function createdLabel(createdAt, t) {
10068
+ const d = new Date(createdAt);
10069
+ const pad2 = (v) => String(v).padStart(2, "0");
10070
+ return t("hover.created", { time: `${t("date.ymd", {
10071
+ y: d.getFullYear(),
10072
+ m: d.getMonth() + 1,
10073
+ d: d.getDate()
10074
+ })} ${pad2(d.getHours())}:${pad2(d.getMinutes())}` });
10075
+ }
10076
+ /** Hover-card body: workspace title, display directory path, absolute creation time. */
10077
+ function WorkspaceHoverContent({ label, cwd, createdAt, t }) {
10078
+ return (0, react_jsx_runtime$1.jsxs)("div", {
10079
+ className: Rows_module_css_default.hoverContent,
10080
+ children: [
10081
+ (0, react_jsx_runtime$1.jsx)("div", {
10082
+ className: Rows_module_css_default.hoverTitle,
10083
+ children: label
10084
+ }),
10085
+ (0, react_jsx_runtime$1.jsx)("div", {
10086
+ className: Rows_module_css_default.hoverPath,
10087
+ children: cwd
10088
+ }),
10089
+ (0, react_jsx_runtime$1.jsx)("div", {
10090
+ className: Rows_module_css_default.hoverTime,
10091
+ children: createdLabel(createdAt, t)
10092
+ })
10093
+ ]
10094
+ });
10095
+ }
10096
+ /** Pointer-position half of a row (insert line above or below). */
10097
+ function rowHalf(e) {
10098
+ const rect = e.currentTarget.getBoundingClientRect();
10099
+ return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
10100
+ }
10101
+ /**
10102
+ * Project (workspace) header row: folder + title;
10103
+ * hover reveals the chevron and create button, and dwelling on a real
10104
+ * Workspace shows its hover card (the ungrouped bucket has none).
10105
+ * `containsCurrent` arrives on the node (derivation fact, no renderer scan).
10106
+ * @param props.group - derived group node.
10107
+ * @param props.onToggle - expand/collapse the group.
10108
+ * @param props.onCreate - start a frontend Session inside this Workspace.
10109
+ * @param props.drag - optional workspace-row drag wiring.
10110
+ * @param props.home - host account home for POSIX hover-path abbreviation.
10111
+ * @param props.t - the browser root's locale seat.
10112
+ * @returns the row element.
10113
+ */
10114
+ function ProjectRowItem({ group, onToggle, onCreate, actions, drag, home, t }) {
10115
+ const row = group;
10116
+ const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
10117
+ const protectedManagedWorkspace = row.__dshGitWorktreeProtected === true;
10118
+ const active = group.expanded && group.containsCurrent;
10119
+ const [menuOpen, setMenuOpen] = (0, react$1.useState)(false);
10120
+ const workspaceMenuItems = [{
10121
+ id: "rename",
10122
+ label: t("rename"),
10123
+ icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
10124
+ }, {
10125
+ id: "delete",
10126
+ label: t("delete.workspace"),
10127
+ icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
10128
+ danger: true
10129
+ }];
10130
+ const ownRow = (0, react_jsx_runtime$1.jsxs)("div", {
10131
+ className: clsx(Rows_module_css_default.projectRow, menuOpen && Rows_module_css_default.menuOpen),
10132
+ role: "treeitem",
10133
+ "aria-expanded": row.expanded,
10134
+ onClick: onToggle,
10135
+ draggable: !protectedManagedWorkspace && drag !== void 0,
10136
+ onDragStart: drag === void 0 ? void 0 : (e) => {
10137
+ e.dataTransfer.effectAllowed = "move";
10138
+ e.dataTransfer.setData("text/plain", row.key);
10139
+ drag.start();
10140
+ },
10141
+ onDragEnd: drag?.end,
10142
+ children: [
10143
+ (0, react_jsx_runtime$1.jsx)("span", {
10144
+ className: clsx(Rows_module_css_default.slot, Rows_module_css_default.folder, active && Rows_module_css_default.folderActive),
10145
+ children: row.expanded ? (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {}) : (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {})
10146
+ }),
10147
+ (0, react_jsx_runtime$1.jsx)("span", {
10148
+ className: clsx(Rows_module_css_default.slot, Rows_module_css_default.chevron),
10149
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTriangleRightFill14, { className: clsx(Rows_module_css_default.arrow, row.expanded && Rows_module_css_default.arrowOpen) })
10150
+ }),
10151
+ (0, react_jsx_runtime$1.jsx)("span", {
10152
+ className: Rows_module_css_default.projectText,
10153
+ children: (0, react_jsx_runtime$1.jsx)("span", {
10154
+ className: Rows_module_css_default.title,
10155
+ children: label
10156
+ })
10157
+ }),
10158
+ (0, react_jsx_runtime$1.jsxs)("span", {
10159
+ className: Rows_module_css_default.rowActions,
10160
+ children: [!protectedManagedWorkspace && actions !== void 0 && (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
10161
+ open: menuOpen,
10162
+ onClose: () => {
10163
+ setMenuOpen(false);
10164
+ },
10165
+ items: workspaceMenuItems,
10166
+ onSelect: (id) => {
10167
+ setMenuOpen(false);
10168
+ /* v8 ignore next -- workspaceMenuItems carries exactly these two rows today. */
10169
+ if (id !== "rename" && id !== "delete") return;
10170
+ if (id === "rename") actions.rename();
10171
+ else actions.delete();
10172
+ },
10173
+ portal: true,
10174
+ closeOnPointerLeave: true,
10175
+ anchor: (0, react_jsx_runtime$1.jsx)("button", {
10176
+ type: "button",
10177
+ className: Rows_module_css_default.iconButton,
10178
+ "aria-label": t("actions.workspace.aria", { name: label }),
10179
+ onClick: (e) => {
10180
+ e.stopPropagation();
10181
+ setMenuOpen((v) => !v);
10182
+ },
10183
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
10184
+ })
10185
+ }), !protectedManagedWorkspace && (0, react_jsx_runtime$1.jsx)("button", {
10186
+ type: "button",
10187
+ className: Rows_module_css_default.iconButton,
10188
+ "aria-label": t("actions.newSession.aria", { name: label }),
10189
+ onClick: (e) => {
10190
+ e.stopPropagation();
10191
+ onCreate();
10192
+ },
10193
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, {})
10194
+ })]
10195
+ })
10196
+ ]
10197
+ });
10198
+ if (row.createdAt === void 0) return ownRow;
10199
+ return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
10200
+ anchor: ownRow,
10201
+ content: (0, react_jsx_runtime$1.jsx)(WorkspaceHoverContent, {
10202
+ label: row.label,
10203
+ cwd: row.cwd === void 0 ? void 0 : (0, _deepseek_ai_dsh_client_runtime_client.abbreviateHomePath)(row.cwd, home),
10204
+ createdAt: row.createdAt,
10205
+ t
10206
+ }),
10207
+ disabled: menuOpen,
10208
+ copyText: row.cwd,
10209
+ copyLabel: t("copy"),
10210
+ copiedLabel: t("hover.copied")
10211
+ });
10212
+ }
10213
+ /* v8 ignore next 3 -- closed-union backstop; only reached if the status is forged */
10214
+ function assertNever(value) {
10215
+ throw new Error(`unknown pending interaction: ${String(value)}`);
10216
+ }
10217
+ /**
10218
+ * Session status presentation; pending interaction is primary and live activity
10219
+ * outranks completion reminders.
10220
+ */
10221
+ function sessionStatuses(node, t) {
10222
+ const subagents = node.runningSubagentCount === 0 ? void 0 : {
10223
+ state: "ongoing",
10224
+ label: t(node.runningSubagentCount === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: node.runningSubagentCount })
10225
+ };
10226
+ let pending;
10227
+ switch (node.pendingInteraction) {
10228
+ case "approval":
10229
+ pending = {
10230
+ state: "warning",
10231
+ label: t("status.waitingApproval")
10232
+ };
10233
+ break;
10234
+ case "plan-review":
10235
+ pending = {
10236
+ state: "warning",
10237
+ label: t("status.planReview")
10238
+ };
10239
+ break;
10240
+ case "question":
10241
+ pending = {
10242
+ state: "warning",
10243
+ label: t("status.waitingAnswer")
10244
+ };
10245
+ break;
10246
+ case void 0: break;
10247
+ default: return assertNever(node.pendingInteraction);
10248
+ }
10249
+ if (pending !== void 0) return subagents === void 0 ? [pending] : [pending, subagents];
10250
+ if (node.running) {
10251
+ const primary = {
10252
+ state: "ongoing",
10253
+ label: t("status.running")
10254
+ };
10255
+ return subagents === void 0 ? [primary] : [primary, subagents];
10256
+ }
10257
+ if (subagents !== void 0) return [subagents];
10258
+ if (node.completed) return [{
10259
+ state: "done",
10260
+ label: t("status.completed")
10261
+ }];
10262
+ return [{
10263
+ state: "done",
10264
+ label: t("status.idle")
10265
+ }];
10266
+ }
10267
+ /** Primary status dot plus every status's screen-reader label, shared by the search and session rows. */
10268
+ function SessionStatusDots({ statuses }) {
10269
+ return (0, react_jsx_runtime$1.jsxs)(react_jsx_runtime$1.Fragment, { children: [(0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: statuses[0].state }), statuses.map((status) => (0, react_jsx_runtime$1.jsx)("span", {
10270
+ className: Rows_module_css_default.visuallyHidden,
10271
+ children: status.label
10272
+ }, status.label))] });
10273
+ }
10274
+ function managedWorktreeDecoration(node, t) {
10275
+ const value = node.__dshGitWorktree;
10276
+ if (value?.kind !== "managed-worktree" || typeof value.state !== "string") return void 0;
10277
+ const label = t(`dshGitWorktree.state.${value.state}`);
10278
+ return {
10279
+ state: value.state,
10280
+ label,
10281
+ ariaLabel: `${t("dshGitWorktree.managed")}, ${label}, ${displayTitle(node, t)}`
10282
+ };
10283
+ }
10284
+ function ManagedWorktreeIdentity({ decoration }) {
10285
+ return (0, react_jsx_runtime$1.jsxs)(react_jsx_runtime$1.Fragment, { children: [(0, react_jsx_runtime$1.jsx)("span", {
10286
+ className: "dsh-git-worktree-sidebar-icon",
10287
+ "data-worktree-state": decoration.state,
10288
+ "aria-hidden": "true",
10289
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {})
10290
+ }), (0, react_jsx_runtime$1.jsx)("span", {
10291
+ className: "dsh-git-worktree-sidebar-badge",
10292
+ "data-worktree-state": decoration.state,
10293
+ "aria-hidden": "true",
10294
+ children: decoration.label
10295
+ })] });
10296
+ }
10297
+ /** Hover-card body: full title, relative time, and every relevant live status. */
10298
+ function SessionHoverContent({ node, now, t }) {
10299
+ const statuses = sessionStatuses(node, t);
10300
+ const worktreeDecoration = managedWorktreeDecoration(node, t);
10301
+ return (0, react_jsx_runtime$1.jsxs)("div", {
10302
+ className: Rows_module_css_default.hoverContent,
10303
+ children: [
10304
+ (0, react_jsx_runtime$1.jsx)("div", {
10305
+ className: Rows_module_css_default.hoverTitle,
10306
+ children: displayTitle(node, t)
10307
+ }),
10308
+ worktreeDecoration !== void 0 && (0, react_jsx_runtime$1.jsxs)("div", {
10309
+ className: Rows_module_css_default.hoverStatus,
10310
+ children: [(0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {}), (0, react_jsx_runtime$1.jsx)("span", { children: worktreeDecoration.ariaLabel })]
10311
+ }),
10312
+ !node.blank && (0, react_jsx_runtime$1.jsx)("div", {
10313
+ className: Rows_module_css_default.hoverTime,
10314
+ children: hoverTimeLabel(node.updatedAt, now, t)
10315
+ }),
10316
+ statuses.map((status) => (0, react_jsx_runtime$1.jsxs)("div", {
10317
+ className: Rows_module_css_default.hoverStatus,
10318
+ children: [(0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: status.state }), (0, react_jsx_runtime$1.jsx)("span", { children: status.label })]
10319
+ }, status.label))
10320
+ ]
10321
+ });
10322
+ }
10323
+ /**
10324
+ * One flat search result: title, Workspace context, and optional content
10325
+ * excerpt. Search navigation opens the session only; it does not address an
10326
+ * event inside the conversation.
10327
+ * @param props.result - merged local/content search row.
10328
+ * @param props.currentId - selected session id.
10329
+ * @param props.onOpen - open the selected session.
10330
+ * @param props.t - Workspace-browser translation seat.
10331
+ * @returns the result button.
10332
+ */
10333
+ function SearchResultItem({ result, currentId, onOpen, t }) {
10334
+ const selected = result.id === currentId;
10335
+ const statuses = sessionStatuses(result, t);
10336
+ const primaryStatus = statuses[0];
10337
+ const worktreeDecoration = managedWorktreeDecoration(result, t);
10338
+ return (0, react_jsx_runtime$1.jsxs)("button", {
10339
+ type: "button",
10340
+ className: clsx(Rows_module_css_default.searchResultRow, selected && Rows_module_css_default.selected),
10341
+ ...worktreeDecoration === void 0 ? {} : {
10342
+ "aria-label": worktreeDecoration.ariaLabel,
10343
+ "data-managed-worktree": "true"
10344
+ },
10345
+ role: "treeitem",
10346
+ "aria-selected": selected,
10347
+ onClick: () => {
10348
+ onOpen(result.id);
10349
+ },
10350
+ children: [(0, react_jsx_runtime$1.jsxs)("span", {
10351
+ className: Rows_module_css_default.searchResultHeading,
10352
+ children: [
10353
+ (0, react_jsx_runtime$1.jsx)("span", {
10354
+ className: Rows_module_css_default.slot,
10355
+ children: (primaryStatus.state !== "done" || result.completed) && (0, react_jsx_runtime$1.jsx)(SessionStatusDots, { statuses })
10356
+ }),
10357
+ worktreeDecoration !== void 0 && (0, react_jsx_runtime$1.jsx)(ManagedWorktreeIdentity, { decoration: worktreeDecoration }),
10358
+ (0, react_jsx_runtime$1.jsx)("span", {
10359
+ className: Rows_module_css_default.searchResultTitle,
10360
+ children: result.title
10361
+ })
10362
+ ]
10363
+ }), (0, react_jsx_runtime$1.jsxs)("span", {
10364
+ className: Rows_module_css_default.searchResultMeta,
10365
+ children: [(0, react_jsx_runtime$1.jsx)("span", {
10366
+ className: Rows_module_css_default.searchResultWorkspace,
10367
+ children: result.workspace
10368
+ }), result.snippet !== void 0 && (0, react_jsx_runtime$1.jsx)("span", {
10369
+ className: Rows_module_css_default.searchResultSnippet,
10370
+ children: result.snippet
10371
+ })]
10372
+ })]
10373
+ });
10374
+ }
10375
+ /**
10376
+ * One top-level 34px session row: status dot (pending user interaction outranks
10377
+ * own or descendant activity), title, relative time, and the row actions menu.
10378
+ * @param props.node - derived session node.
10379
+ * @param props.currentId - selected session id (row highlight).
10380
+ * @param props.now - epoch ms for relative-time formatting.
10381
+ * @param props.onOpen - open a session by id.
10382
+ * @param props.onRename - open the session rename dialog (id + current title).
10383
+ * @param props.onFork - fork a session at its last completed turn.
10384
+ * @param props.onArchive - archive a session by id.
10385
+ * @param props.drag - optional draggable-row wiring.
10386
+ * @param props.flat - omit the empty status slot in the hierarchy-free flat list.
10387
+ * @param props.t - the browser root's locale seat.
10388
+ * @returns the session row.
10389
+ */
10390
+ function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, drag, flat = false, t }) {
10391
+ const row = node;
10392
+ const title = displayTitle(node, t);
10393
+ const selected = node.id === currentId;
10394
+ const statuses = sessionStatuses(node, t);
10395
+ const showStatus = statuses[0].state !== "done" || row.completed;
10396
+ const worktreeDecoration = managedWorktreeDecoration(row, t);
10397
+ const [menuOpen, setMenuOpen] = (0, react$1.useState)(false);
10398
+ const sessionMenuItems = [
10399
+ {
10400
+ id: "rename",
10401
+ label: t("rename"),
10402
+ icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
10403
+ },
10404
+ {
10405
+ id: "fork",
10406
+ label: t("menu.fork"),
10407
+ icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {})
10408
+ },
10409
+ {
10410
+ id: "archive",
10411
+ label: t("menu.archiveSession"),
10412
+ icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
10413
+ }
10414
+ ];
10415
+ const visibleSessionMenuItems = worktreeDecoration === void 0 ? sessionMenuItems : sessionMenuItems.filter((item) => item.id !== "fork");
10416
+ return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
10417
+ anchor: (0, react_jsx_runtime$1.jsxs)("div", {
10418
+ className: clsx(Rows_module_css_default.sessionRow, selected && Rows_module_css_default.selected, menuOpen && Rows_module_css_default.menuOpen, flat && !showStatus && Rows_module_css_default.flatSessionRowWithoutStatus, drag?.marker === "before" && Rows_module_css_default.dropBefore, drag?.marker === "after" && Rows_module_css_default.dropAfter),
10419
+ role: "treeitem",
10420
+ ...worktreeDecoration === void 0 ? {} : {
10421
+ "aria-label": worktreeDecoration.ariaLabel,
10422
+ "data-managed-worktree": "true"
10423
+ },
10424
+ "aria-selected": selected,
10425
+ onClick: () => {
10426
+ onOpen(node.id);
10427
+ },
10428
+ draggable: worktreeDecoration === void 0 && drag !== void 0,
10429
+ onDragStart: drag === void 0 ? void 0 : (e) => {
10430
+ e.dataTransfer.effectAllowed = "move";
10431
+ e.dataTransfer.setData("text/plain", node.id);
10432
+ drag.start();
10433
+ },
10434
+ onDragEnd: drag?.end,
10435
+ onDragOver: drag === void 0 ? void 0 : (e) => {
10436
+ if (!drag.active) return;
10437
+ e.preventDefault();
10438
+ e.dataTransfer.dropEffect = "move";
10439
+ drag.hover(rowHalf(e));
10440
+ },
10441
+ onDrop: drag === void 0 ? void 0 : (e) => {
10442
+ if (!drag.active) return;
10443
+ e.preventDefault();
10444
+ drag.drop(rowHalf(e));
10445
+ },
10446
+ children: [
10447
+ worktreeDecoration === void 0 && (!flat || showStatus) && (0, react_jsx_runtime$1.jsx)("span", {
10448
+ className: Rows_module_css_default.slot,
10449
+ children: showStatus && (0, react_jsx_runtime$1.jsx)(SessionStatusDots, { statuses })
10450
+ }),
10451
+ worktreeDecoration !== void 0 && (0, react_jsx_runtime$1.jsx)(ManagedWorktreeIdentity, { decoration: worktreeDecoration }),
10452
+ (0, react_jsx_runtime$1.jsx)("span", {
10453
+ className: Rows_module_css_default.title,
10454
+ children: title
10455
+ }),
10456
+ !row.blank && (0, react_jsx_runtime$1.jsx)("span", {
10457
+ className: Rows_module_css_default.time,
10458
+ children: timeLabel(row.updatedAt, now, t)
10459
+ }),
10460
+ !row.blank && (0, react_jsx_runtime$1.jsx)("span", {
10461
+ className: Rows_module_css_default.rowActions,
10462
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
10463
+ open: menuOpen,
10464
+ onClose: () => {
10465
+ setMenuOpen(false);
10466
+ },
10467
+ items: visibleSessionMenuItems,
10468
+ onSelect: (id) => {
10469
+ setMenuOpen(false);
10470
+ if (id === "rename") onRename(node.id, row.title);
10471
+ if (id === "fork") onFork(node.id);
10472
+ if (id === "archive") onArchive(node.id);
10473
+ },
10474
+ portal: true,
10475
+ closeOnPointerLeave: true,
10476
+ anchor: (0, react_jsx_runtime$1.jsx)("button", {
10477
+ type: "button",
10478
+ className: Rows_module_css_default.iconButton,
10479
+ "aria-label": t("actions.session.aria", { name: title }),
10480
+ onClick: (e) => {
10481
+ e.stopPropagation();
10482
+ setMenuOpen((v) => !v);
10483
+ },
10484
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
10485
+ })
10486
+ })
10487
+ })
10488
+ ]
10489
+ }),
10490
+ content: (0, react_jsx_runtime$1.jsx)(SessionHoverContent, {
10491
+ node,
10492
+ now,
10493
+ t
10494
+ }),
10495
+ disabled: menuOpen || drag?.active === true,
10496
+ copyText: row.blank ? void 0 : row.title,
10497
+ copyLabel: t("copy"),
10498
+ copiedLabel: t("hover.copied")
10499
+ });
10500
+ }
10501
+ const css$1 = "._G5b-a_modalAction{min-width:72px}._G5b-a_modalError,._G5b-a_menuStatus{margin-top:8px;font-size:12px;line-height:18px}._G5b-a_modalError{color:var(--dsw-alias-state-error-primary)}._G5b-a_menuStatus{color:var(--dsw-alias-label-secondary)}";
10502
+ const tagId$1 = "@deepseek-ai/dsh-client-ui-workspace/WorkspacePicker.module.css";
10503
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
10504
+ const tag = document.createElement("style");
10505
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workspace";
10506
+ tag.dataset.pluginCss = tagId$1;
10507
+ tag.textContent = css$1;
10508
+ document.head.appendChild(tag);
10509
+ }
10510
+ var WorkspacePicker_module_css_default = {
10511
+ "menuStatus": "_G5b-a_menuStatus",
10512
+ "modalAction": "_G5b-a_modalAction",
10513
+ "modalError": "_G5b-a_modalError"
10514
+ };
10515
+ const ADD_WORKSPACE = "::add-workspace";
10516
+ /**
10517
+ * Render the pick menu plus the adoption error dialog.
10518
+ * @param props - owner-controlled flow props.
10519
+ * @returns menu + dialog elements.
10520
+ */
10521
+ function WorkspacePickFlow({ t, open, anchorRef, useWorkspaces, createWorkspace, useDirectoryFlow, renderDirectoryFlow, onPick, onClose, addOnly = false, side = "bottom", selectedId }) {
10522
+ const workspaceSnapshot = useWorkspaces((state) => state);
10523
+ const workspaces = workspaceSnapshot.items;
10524
+ const getAnchorRect = (0, react$1.useCallback)(() => anchorRef?.current?.getBoundingClientRect() ?? null, [anchorRef]);
10525
+ const [errorOpen, setErrorOpen] = (0, react$1.useState)(false);
10526
+ const [modalError, setModalError] = (0, react$1.useState)(null);
10527
+ const [flowOpen, setFlowOpen] = (0, react$1.useState)(false);
10528
+ const [pickingFolder, setPickingFolder] = (0, react$1.useState)(false);
10529
+ const flowBusy = flowOpen || pickingFolder;
10530
+ const flowAvailable = useDirectoryFlow((occupied) => occupied);
10531
+ (0, react$1.useEffect)(() => {
10532
+ if (flowOpen && !flowAvailable) setFlowOpen(false);
10533
+ }, [flowOpen, flowAvailable]);
10534
+ const addEntries = flowAvailable ? [{
10535
+ id: ADD_WORKSPACE,
10536
+ label: t("menu.addWorkspace"),
10537
+ icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 }),
10538
+ disabled: flowBusy
10539
+ }] : [];
10540
+ const pinAdd = !addOnly && workspaces.length > 0;
10541
+ const items = pinAdd ? workspaces.map((workspace) => ({
10542
+ id: workspace.workspaceId,
10543
+ label: workspace.title,
10544
+ icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 }),
10545
+ disabled: flowBusy
10546
+ })) : addEntries;
10547
+ const menuIsEmpty = items.length === 0;
10548
+ const closeModal = () => {
10549
+ setErrorOpen(false);
10550
+ setModalError(null);
10551
+ };
10552
+ /** Adopt a picked directory; failures land in the folder-error dialog (Choose again reopens the flow). */
10553
+ const adoptDirectory = (path) => createWorkspace({ path }).then((workspace) => {
10554
+ setFlowOpen(false);
10555
+ onPick(workspace.workspaceId);
10556
+ }).catch((reason) => {
10557
+ setModalError(reason instanceof Error ? reason.message : String(reason));
10558
+ setFlowOpen(false);
10559
+ setErrorOpen(true);
10560
+ });
10561
+ const openDirectoryFlow = (0, react$1.useCallback)(() => {
10562
+ onClose();
10563
+ setErrorOpen(false);
10564
+ setModalError(null);
10565
+ setFlowOpen(true);
10566
+ }, [onClose]);
10567
+ const listSettled = addOnly || workspaceSnapshot.phase === "ready";
10568
+ const addIsTheOnlyEntry = !pinAdd && listSettled && addEntries.length === 1;
10569
+ (0, react$1.useEffect)(() => {
10570
+ if (open && addIsTheOnlyEntry && !flowBusy) openDirectoryFlow();
10571
+ }, [
10572
+ open,
10573
+ addIsTheOnlyEntry,
10574
+ flowBusy,
10575
+ openDirectoryFlow
10576
+ ]);
10577
+ /** Owner side of the flow conversation: adopt keeps the flow open (busy) until the Host answers. */
10578
+ const flowOwner = {
10579
+ open: flowOpen,
10580
+ busy: pickingFolder,
10581
+ onPicked: (path) => {
10582
+ setPickingFolder(true);
10583
+ adoptDirectory(path).finally(() => {
10584
+ setPickingFolder(false);
10585
+ });
10586
+ },
10587
+ onCancel: () => {
10588
+ setFlowOpen(false);
10589
+ },
10590
+ onError: (message) => {
10591
+ setFlowOpen(false);
10592
+ setModalError(message);
10593
+ setErrorOpen(true);
10594
+ }
10595
+ };
10596
+ const handleSelect = (id) => {
10597
+ if (id === ADD_WORKSPACE) {
10598
+ openDirectoryFlow();
10599
+ return;
10600
+ }
10601
+ onPick(id);
10602
+ };
10603
+ return (0, react_jsx_runtime$1.jsxs)(react_jsx_runtime$1.Fragment, { children: [
10604
+ (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
10605
+ open: open && !addIsTheOnlyEntry && !menuIsEmpty,
10606
+ anchor: null,
10607
+ items,
10608
+ ...pinAdd ? { footer: addEntries } : {},
10609
+ selectedId,
10610
+ onSelect: handleSelect,
10611
+ onClose,
10612
+ side,
10613
+ portal: true,
10614
+ getAnchorRect
10615
+ }),
10616
+ open && !addIsTheOnlyEntry && !menuIsEmpty && workspaceSnapshot.phase === "pending" && (0, react_jsx_runtime$1.jsx)("div", {
10617
+ className: WorkspacePicker_module_css_default.menuStatus,
10618
+ role: "status",
10619
+ children: t("picker.loading")
10620
+ }),
10621
+ renderDirectoryFlow(flowOwner),
10622
+ (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
10623
+ open: errorOpen,
10624
+ onClose: closeModal,
10625
+ closeLabel: t("close"),
10626
+ title: t("folderError.title"),
10627
+ footer: (0, react_jsx_runtime$1.jsxs)(react_jsx_runtime$1.Fragment, { children: [(0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
10628
+ variant: "outline",
10629
+ className: WorkspacePicker_module_css_default.modalAction,
10630
+ onClick: closeModal,
10631
+ children: t("cancel")
10632
+ }), (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
10633
+ variant: "primary",
10634
+ className: WorkspacePicker_module_css_default.modalAction,
10635
+ disabled: !flowAvailable,
10636
+ onClick: openDirectoryFlow,
10637
+ children: t("folderError.retry")
10638
+ })] }),
10639
+ children: (0, react_jsx_runtime$1.jsx)("div", {
10640
+ className: WorkspacePicker_module_css_default.modalError,
10641
+ role: "alert",
10642
+ children: modalError
10643
+ })
10644
+ })
10645
+ ] });
10646
+ }
10647
+ /**
10648
+ * The conversation empty-state registration: adapts the owner share to the
10649
+ * core flow (all state and semantics live in the flow / the owner).
10650
+ * @param props - empty-state slot props (owner share + injected creation callback).
10651
+ * @returns the flow element.
10652
+ */
10653
+ function WorkspacePicker({ open, anchorRef, useWorkspaces, selectedId, onPick, onClose, createWorkspace, useDirectoryFlow, renderSlot, t }) {
10654
+ return (0, react_jsx_runtime$1.jsx)(WorkspacePickFlow, {
10655
+ t,
10656
+ open,
10657
+ anchorRef,
10658
+ useWorkspaces,
10659
+ createWorkspace,
10660
+ useDirectoryFlow,
10661
+ renderDirectoryFlow: (owner) => renderSlot("conversation.hero.workspace.directoryFlow", owner),
10662
+ selectedId,
10663
+ onPick,
10664
+ onClose
10665
+ });
10666
+ }
10667
+ const css = ".qDHVXG_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:8px;--dsh-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--dsh-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.qDHVXG_root.qDHVXG_rail{padding-right:0}.qDHVXG_iconButton{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.qDHVXG_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_sectionHeader{box-sizing:border-box;height:36px;color:var(--dsw-alias-label-tertiary);border-radius:12px;flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;padding-left:4px;display:flex;overflow:hidden}.qDHVXG_root:not(.qDHVXG_rail) .qDHVXG_sectionHeader{margin-top:2px;margin-right:-4px}.qDHVXG_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;overflow:hidden}.qDHVXG_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.qDHVXG_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.qDHVXG_searchSlotExpanded{max-width:100%;padding-left:0}.qDHVXG_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.qDHVXG_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.qDHVXG_search{box-sizing:border-box;cursor:text;width:100%;height:28px;color:var(--dsw-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;border-radius:50%;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.qDHVXG_searchExpanded{border:1px solid var(--dsw-alias-border-l2);width:calc(100% + 4px);height:30px;color:var(--dsw-alias-label-caption);background:0 0;border-radius:10px;margin-inline:-2px;padding:0 4px 0 0}.qDHVXG_searchButton{cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.qDHVXG_searchExpanded .qDHVXG_searchButton{width:28px;height:30px}.qDHVXG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_searchExpanded .qDHVXG_searchButton:hover{background:0 0}.qDHVXG_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--dsw-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.qDHVXG_searchExpanded .qDHVXG_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.qDHVXG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.qDHVXG_clearButton{cursor:pointer;width:24px;height:24px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.qDHVXG_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_rail .qDHVXG_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.qDHVXG_rail .qDHVXG_headerActions{max-width:none}.qDHVXG_rail .qDHVXG_iconButton{width:36px;height:36px;color:var(--dsw-alias-label-primary)}.qDHVXG_rail .qDHVXG_search{background:0 0;border-color:#0000;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.qDHVXG_rail .qDHVXG_searchButton{width:36px;height:36px;color:var(--dsw-alias-label-primary)}.qDHVXG_rail .qDHVXG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.qDHVXG_rail .qDHVXG_listArea{margin-left:0;margin-right:0;padding-left:0}.qDHVXG_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.qDHVXG_fade{left:0;right:var(--dsh-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--dsw-specific-sidebar-fill));pointer-events:none;height:24px;position:absolute;bottom:0}.qDHVXG_wide{animation:qDHVXG_wide-in .2s var(--ds-ease-in-out)}@keyframes qDHVXG_wide-in{0%{opacity:0}}.qDHVXG_list{min-height:0;margin-left:-4px;margin-right:var(--dsh-session-list-scrollbar-offset);padding-left:4px;padding-right:calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:16px;overflow-y:auto}.qDHVXG_flatList>*+*,.qDHVXG_searchTree>[role=treeitem]+[role=treeitem],.qDHVXG_groupSection>*+*{margin-top:2px}.qDHVXG_searchStatus,.qDHVXG_searchWarning{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.qDHVXG_searchWarning{color:var(--dsw-alias-label-secondary)}.qDHVXG_groupSection{position:relative}.qDHVXG_groupSection+.qDHVXG_groupSection{margin-top:4px}.qDHVXG_listTopDropIndicator,.qDHVXG_workspaceDropBefore:before,.qDHVXG_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.qDHVXG_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.qDHVXG_listTopDropActive>.qDHVXG_workspaceDropBefore:first-child:before{display:none}.qDHVXG_workspaceDropBefore:before{top:-8px}.qDHVXG_workspaceDropAfter:after{bottom:-8px}.qDHVXG_sessionOverflowButton{cursor:pointer;text-align:left;width:100%;height:28px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:8px;padding:0 12px 0 28px;font-size:12px}.qDHVXG_groupSection>.qDHVXG_sessionOverflowButton{margin-top:0}.qDHVXG_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.qDHVXG_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.qDHVXG_renameInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:22px;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.qDHVXG_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.qDHVXG_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.qDHVXG_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.qDHVXG_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.qDHVXG_wide{animation:none}.qDHVXG_search,.qDHVXG_sectionLabel,.qDHVXG_searchSlot,.qDHVXG_searchInput,.qDHVXG_headerActions{transition:none}}";
10668
+ const tagId = "@deepseek-ai/dsh-client-ui-workspace/WorkspaceBrowser.module.css";
10669
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
10670
+ const tag = document.createElement("style");
10671
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workspace";
10672
+ tag.dataset.pluginCss = tagId;
10673
+ tag.textContent = css;
10674
+ document.head.appendChild(tag);
10675
+ }
10676
+ var WorkspaceBrowser_module_css_default = {
10677
+ "clearButton": "qDHVXG_clearButton",
10678
+ "deleteAction": "qDHVXG_deleteAction",
10679
+ "deleteStatus": "qDHVXG_deleteStatus",
10680
+ "empty": "qDHVXG_empty",
10681
+ "fade": "qDHVXG_fade",
10682
+ "flatList": "qDHVXG_flatList",
10683
+ "groupSection": "qDHVXG_groupSection",
10684
+ "headerActions": "qDHVXG_headerActions",
10685
+ "headerActionsHidden": "qDHVXG_headerActionsHidden",
10686
+ "iconButton": "qDHVXG_iconButton",
10687
+ "list": "qDHVXG_list",
10688
+ "listArea": "qDHVXG_listArea",
10689
+ "listTopDropActive": "qDHVXG_listTopDropActive",
10690
+ "listTopDropIndicator": "qDHVXG_listTopDropIndicator",
10691
+ "rail": "qDHVXG_rail",
10692
+ "renameError": "qDHVXG_renameError",
10693
+ "renameInput": "qDHVXG_renameInput",
10694
+ "root": "qDHVXG_root",
10695
+ "search": "qDHVXG_search",
10696
+ "searchButton": "qDHVXG_searchButton",
10697
+ "searchExpanded": "qDHVXG_searchExpanded",
10698
+ "searchInput": "qDHVXG_searchInput",
10699
+ "searchSlot": "qDHVXG_searchSlot",
10700
+ "searchSlotExpanded": "qDHVXG_searchSlotExpanded",
10701
+ "searchStatus": "qDHVXG_searchStatus",
10702
+ "searchTree": "qDHVXG_searchTree",
10703
+ "searchWarning": "qDHVXG_searchWarning",
10704
+ "sectionHeader": "qDHVXG_sectionHeader",
10705
+ "sectionLabel": "qDHVXG_sectionLabel",
10706
+ "sectionLabelHidden": "qDHVXG_sectionLabelHidden",
10707
+ "sessionOverflowButton": "qDHVXG_sessionOverflowButton",
10708
+ "treeBody": "qDHVXG_treeBody",
10709
+ "wide": "qDHVXG_wide",
10710
+ "wide-in": "qDHVXG_wide-in",
10711
+ "workspaceDropAfter": "qDHVXG_workspaceDropAfter",
10712
+ "workspaceDropBefore": "qDHVXG_workspaceDropBefore"
10713
+ };
10714
+ /**
10715
+ * The workspace/session browsing region filling the sidebar shell's
10716
+ * `sidebar.workspaces` hole: section header (title + view options + add
10717
+ * workspace), search, the grouped tree or flat list, and the workspace
10718
+ * dialogs. Wide state renders the full browser; rail state renders the two
10719
+ * region icons (search / add workspace) as 36px controls on the shell's shared
10720
+ * rail entry path, each requesting expansion through the owner share. Adding
10721
+ * is the header button's one action, so it raises the directory flow with no
10722
+ * menu in between; the flow and its error dialog live in WorkspacePicker
10723
+ * (same package — direct composition, no slot between them).
10724
+ */
10725
+ /**
10726
+ * Column slide length (--ds-transition-duration-slow): rail-search focus waits it out —
10727
+ * focus() forces a synchronous layout and would jank the slide.
10728
+ */
10729
+ const EXPAND_SLIDE_MS = 300;
10730
+ /** Pause between the latest keystroke and a Host content-search request. */
10731
+ const SEARCH_DEBOUNCE_MS = 250;
10732
+ /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
10733
+ const SEARCH_QUERY_MAX_CODE_UNITS = 500;
10734
+ /** Session rows visible per Workspace before the local overflow control. */
10735
+ const COLLAPSED_SESSION_LIMIT = 5;
10736
+ /** Keep controlled input and RPC payload inside the session.search wire contract. */
10737
+ function sanitizeSearchQuery(value) {
10738
+ const withoutNul = value.replaceAll("\0", "");
10739
+ if (withoutNul.length <= SEARCH_QUERY_MAX_CODE_UNITS) return withoutNul;
10740
+ let end = SEARCH_QUERY_MAX_CODE_UNITS;
10741
+ const last = withoutNul.charCodeAt(end - 1);
10742
+ const next = withoutNul.charCodeAt(end);
10743
+ if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
10744
+ return withoutNul.slice(0, end);
10745
+ }
10746
+ /** Immutable membership toggle for the local expand-all array. */
10747
+ function toggled(list, key) {
10748
+ return list.includes(key) ? list.filter((k) => k !== key) : [...list, key];
10749
+ }
10750
+ /**
10751
+ * Accept the native drag at document level while a row drag is active: row
10752
+ * hover still owns the insertion marker, and releasing outside the list must
10753
+ * not be rendered as a rejected drop before dragend commits that last marker.
10754
+ */
10755
+ function useNativeDragAcceptance(active) {
10756
+ (0, react$1.useEffect)(() => {
10757
+ if (!active) return;
10758
+ const acceptDrag = (event) => {
10759
+ event.preventDefault();
10760
+ if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "move";
10761
+ };
10762
+ const acceptDrop = (event) => {
10763
+ event.preventDefault();
10764
+ };
10765
+ document.addEventListener("dragover", acceptDrag);
10766
+ document.addEventListener("drop", acceptDrop);
10767
+ return () => {
10768
+ document.removeEventListener("dragover", acceptDrag);
10769
+ document.removeEventListener("drop", acceptDrop);
10770
+ };
10771
+ }, [active]);
10772
+ }
10773
+ /** Reconcile a stored view order with the Workspace's current session account. */
10774
+ function reconciledSessionOrder(sessionIds, stored) {
10775
+ if (stored === void 0) return [...sessionIds];
10776
+ const byId = new Map(sessionIds.map((id) => [id, id]));
10777
+ const ordered = [];
10778
+ const included = /* @__PURE__ */ new Set();
10779
+ for (const key of stored) {
10780
+ const id = byId.get(key);
10781
+ if (id === void 0 || included.has(key)) continue;
10782
+ ordered.push(id);
10783
+ included.add(key);
10784
+ }
10785
+ for (const id of sessionIds) {
10786
+ if (included.has(id)) continue;
10787
+ ordered.push(id);
10788
+ }
10789
+ return ordered;
10790
+ }
10791
+ /** Newest update first with stable Session identity as the tie-break. */
10792
+ function compareSessionRecency(a, b, byId) {
10793
+ const aUpdatedAt = byId[a]?.updatedAt ?? Number.NEGATIVE_INFINITY;
10794
+ const bUpdatedAt = byId[b]?.updatedAt ?? Number.NEGATIVE_INFINITY;
10795
+ if (aUpdatedAt !== bUpdatedAt) return bUpdatedAt - aUpdatedAt;
10796
+ return a < b ? -1 : 1;
10797
+ }
10798
+ /** Reconcile one editable order account and apply its activity-promotion policy. */
10799
+ function nextSessionOrderAccount({ sessionIds, previousOrder, previousUpdatedAt, list, orderBy, sortByRecency }) {
10800
+ let order = reconciledSessionOrder(sessionIds, previousOrder);
10801
+ if (sortByRecency) order.sort((a, b) => compareSessionRecency(a, b, list.byId));
10802
+ else if (orderBy === "updated") {
10803
+ const promoted = sessionIds.filter((id) => {
10804
+ const session = list.byId[id];
10805
+ return session !== void 0 && (previousUpdatedAt[id] === void 0 || session.updatedAt > previousUpdatedAt[id]);
10806
+ }).sort((a, b) => compareSessionRecency(a, b, list.byId));
10807
+ if (promoted.length > 0) {
10808
+ const promotedIds = new Set(promoted);
10809
+ order = [...promoted, ...order.filter((id) => !promotedIds.has(id))];
10810
+ }
10811
+ }
10812
+ const updatedAt = {};
10813
+ for (const id of sessionIds) {
10814
+ const session = list.byId[id];
10815
+ if (session !== void 0) updatedAt[id] = session.updatedAt;
10816
+ }
10817
+ const orderChanged = previousOrder === void 0 || order.length !== previousOrder.length || order.some((id, index) => id !== previousOrder[index]);
10818
+ const timestampsChanged = Object.keys(updatedAt).length !== Object.keys(previousUpdatedAt).length || Object.entries(updatedAt).some(([id, timestamp]) => previousUpdatedAt[id] !== timestamp);
10819
+ return {
10820
+ order,
10821
+ updatedAt,
10822
+ changed: orderChanged || timestampsChanged
10823
+ };
10824
+ }
10825
+ /** Grouping and ordering menu; own open state so it resets with the wide chrome. */
10826
+ function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }) {
10827
+ const [open, setOpen] = (0, react$1.useState)(false);
10828
+ return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
10829
+ open,
10830
+ onClose: () => {
10831
+ setOpen(false);
10832
+ },
10833
+ items: [
10834
+ {
10835
+ type: "label",
10836
+ id: "group-by",
10837
+ text: t("groupBy.label")
10838
+ },
10839
+ {
10840
+ id: "workspace",
10841
+ label: t("groupBy.workspace")
10842
+ },
10843
+ {
10844
+ id: "flat",
10845
+ label: t("groupBy.flat")
10846
+ },
10847
+ {
10848
+ type: "separator",
10849
+ id: "order-by-separator"
10850
+ },
10851
+ {
10852
+ type: "label",
10853
+ id: "order-by",
10854
+ text: t("orderBy.label")
10855
+ },
10856
+ {
10857
+ id: "manual",
10858
+ label: t("orderBy.manual")
10859
+ },
10860
+ {
10861
+ id: "updated",
10862
+ label: t("orderBy.updated")
10863
+ }
10864
+ ],
10865
+ selectedIds: [groupBy, orderBy],
10866
+ onSelect: (id) => {
10867
+ if (id === "workspace" || id === "flat") onGroupPick(id);
10868
+ else if (id === "manual" || id === "updated") onOrderPick(id);
10869
+ setOpen(false);
10870
+ },
10871
+ align: "end",
10872
+ dense: true,
10873
+ portal: true,
10874
+ anchor: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
10875
+ label: t("viewOptions.label"),
10876
+ side: "bottom",
10877
+ delayMs: 500,
10878
+ children: (0, react_jsx_runtime$1.jsx)("button", {
10879
+ type: "button",
10880
+ className: clsx(WorkspaceBrowser_module_css_default.iconButton, WorkspaceBrowser_module_css_default.wide),
10881
+ "aria-label": t("viewOptions.label"),
10882
+ onClick: () => {
10883
+ setOpen((v) => !v);
10884
+ },
10885
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, {})
10886
+ })
10887
+ })
10888
+ });
10889
+ }
10890
+ /** Resolve an insertion side from the full rendered workspace group. */
10891
+ function workspaceGroupHalf(e) {
10892
+ const rect = e.currentTarget.getBoundingClientRect();
10893
+ return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
10894
+ }
10895
+ /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
10896
+ function SessionTree({ useSessions, startSession, open, forkSession, workspaces, archivedSessionIds, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, home, t }) {
10897
+ const list = useSessions((s) => s);
10898
+ const current = list.current;
10899
+ const [expandedSessionGroups, setExpandedSessionGroups] = (0, react$1.useState)([]);
10900
+ const [drag, setDrag] = (0, react$1.useState)(null);
10901
+ const sessionDropCommitted = (0, react$1.useRef)(false);
10902
+ const [workspaceDrag, setWorkspaceDrag] = (0, react$1.useState)(null);
10903
+ const workspaceDropCommitted = (0, react$1.useRef)(false);
10904
+ const previousOrderBy = (0, react$1.useRef)(orderBy);
10905
+ useNativeDragAcceptance(drag !== null || workspaceDrag !== null);
10906
+ const currentGroup = current === void 0 ? void 0 : workspaces.find((w) => w.sessionIds.includes(current))?.workspaceId ?? "";
10907
+ (0, react$1.useEffect)(() => {
10908
+ if (current === void 0 || currentGroup === void 0 || Object.hasOwn(groupExpansion, currentGroup)) return;
10909
+ setGroupExpanded(currentGroup, true);
10910
+ }, [
10911
+ current,
10912
+ currentGroup,
10913
+ setGroupExpanded,
10914
+ groupExpansion
10915
+ ]);
10916
+ const expandedGroups = (0, react$1.useMemo)(() => Object.entries(groupExpansion).filter(([, expanded]) => expanded).map(([key]) => key), [groupExpansion]);
10917
+ const ungroupedSessionIds = (0, react$1.useMemo)(() => {
10918
+ const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
10919
+ return list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id));
10920
+ }, [list, workspaces]);
10921
+ (0, react$1.useEffect)(() => {
10922
+ if (list.phase !== "ready") return;
10923
+ const switchedToUpdated = previousOrderBy.current !== "updated" && orderBy === "updated";
10924
+ previousOrderBy.current = orderBy;
10925
+ const accounts = [...workspaces.map((workspace) => ({
10926
+ key: workspace.workspaceId,
10927
+ sessionIds: workspace.sessionIds.filter((id) => list.byId[id] !== void 0)
10928
+ })), {
10929
+ key: "",
10930
+ sessionIds: ungroupedSessionIds
10931
+ }];
10932
+ for (const { key, sessionIds } of accounts) {
10933
+ const previousOrder = sessionOrderByAccount[key];
10934
+ const next = nextSessionOrderAccount({
10935
+ sessionIds,
10936
+ previousOrder,
10937
+ previousUpdatedAt: sessionUpdatedAtByAccount[key] ?? {},
10938
+ list,
10939
+ orderBy,
10940
+ sortByRecency: orderBy === "updated" && (previousOrder === void 0 || switchedToUpdated)
10941
+ });
10942
+ if (next.changed) syncSessionOrderAccount(key, next.order.map((id) => id), next.updatedAt);
10943
+ }
10944
+ }, [
10945
+ list,
10946
+ orderBy,
10947
+ sessionOrderByAccount,
10948
+ sessionUpdatedAtByAccount,
10949
+ syncSessionOrderAccount,
10950
+ ungroupedSessionIds,
10951
+ workspaces
10952
+ ]);
10953
+ const orderedWorkspaces = (0, react$1.useMemo)(() => {
10954
+ return workspaces.map((workspace) => {
10955
+ const stored = sessionOrderByAccount[workspace.workspaceId];
10956
+ const sessionIds = reconciledSessionOrder(workspace.sessionIds, stored);
10957
+ return {
10958
+ ...workspace,
10959
+ sessionIds
10960
+ };
10961
+ });
10962
+ }, [sessionOrderByAccount, workspaces]);
10963
+ const orderedUngroupedSessionIds = (0, react$1.useMemo)(() => reconciledSessionOrder(ungroupedSessionIds, sessionOrderByAccount[""]), [sessionOrderByAccount, ungroupedSessionIds]);
10964
+ const groups = (0, react$1.useMemo)(() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, {
10965
+ expandedGroups,
10966
+ ...sessionOrderByAccount[""] === void 0 ? {} : { ungroupedOrder: sessionOrderByAccount[""] }
10967
+ }), [
10968
+ list,
10969
+ orderedWorkspaces,
10970
+ archivedSessionIds,
10971
+ expandedGroups,
10972
+ sessionOrderByAccount
10973
+ ]);
10974
+ const now = Date.now();
10975
+ const commitSessionDrag = (activeDrag, over) => {
10976
+ if (sessionDropCommitted.current) return;
10977
+ sessionDropCommitted.current = true;
10978
+ setDrag(null);
10979
+ const group = groups.find((candidate) => candidate.key === activeDrag.accountKey);
10980
+ if (group === void 0) return;
10981
+ const targetIndex = group.sessions.findIndex((session) => session.id === over.id);
10982
+ if (targetIndex === -1) return;
10983
+ const anchor = over.half === "before" ? over.id : group.sessions[targetIndex + 1]?.id;
10984
+ if (anchor === activeDrag.sessionId) return;
10985
+ const sourceIndex = group.sessions.findIndex((session) => session.id === activeDrag.sessionId);
10986
+ const anchorIndex = anchor === void 0 ? group.sessions.length : group.sessions.findIndex((session) => session.id === anchor);
10987
+ if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
10988
+ const accountSessionIds = activeDrag.accountKey === "" ? orderedUngroupedSessionIds : orderedWorkspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
10989
+ if (accountSessionIds === void 0) return;
10990
+ const nextOrder = accountSessionIds.filter((id) => id !== activeDrag.sessionId);
10991
+ const insertAt = anchor === void 0 ? nextOrder.length : nextOrder.indexOf(anchor);
10992
+ nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
10993
+ setSessionOrder(activeDrag.accountKey, nextOrder.map((id) => id));
10994
+ if (orderBy === "updated" || activeDrag.accountKey === "") return;
10995
+ insertSessionBefore(activeDrag.accountKey, activeDrag.sessionId, anchor).catch((reason) => {
10996
+ console.warn("session reorder rejected:", reason);
10997
+ });
10998
+ };
10999
+ const commitWorkspaceDrag = (activeDrag, over) => {
11000
+ if (workspaceDropCommitted.current) return;
11001
+ workspaceDropCommitted.current = true;
11002
+ setWorkspaceDrag(null);
11003
+ const rowIndex = workspaces.findIndex((workspace) => workspace.workspaceId === over.id);
11004
+ if (rowIndex === -1) return;
11005
+ const anchor = over.half === "before" ? over.id : workspaces[rowIndex + 1]?.workspaceId;
11006
+ if (anchor === activeDrag.workspaceId) return;
11007
+ const sourceIndex = workspaces.findIndex((workspace) => workspace.workspaceId === activeDrag.workspaceId);
11008
+ const anchorIndex = anchor === void 0 ? workspaces.length : workspaces.findIndex((workspace) => workspace.workspaceId === anchor);
11009
+ if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
11010
+ insertWorkspaceBefore(activeDrag.workspaceId, anchor).catch((reason) => {
11011
+ console.warn("workspace reorder rejected:", reason);
11012
+ });
11013
+ };
11014
+ const workspaceDropAtListStart = groups[0]?.workspaceId !== void 0 && workspaceDrag?.over?.id === groups[0].workspaceId && workspaceDrag.over.half === "before";
11015
+ return (0, react_jsx_runtime$1.jsxs)("div", {
11016
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
11017
+ children: [
11018
+ workspaceDropAtListStart && (0, react_jsx_runtime$1.jsx)("span", {
11019
+ className: WorkspaceBrowser_module_css_default.listTopDropIndicator,
11020
+ "aria-hidden": "true"
11021
+ }),
11022
+ (0, react_jsx_runtime$1.jsxs)("div", {
11023
+ className: clsx(WorkspaceBrowser_module_css_default.list, workspaceDropAtListStart && WorkspaceBrowser_module_css_default.listTopDropActive),
11024
+ role: "tree",
11025
+ "aria-label": t("section.sessions"),
11026
+ children: [groups.length === 0 && (0, react_jsx_runtime$1.jsx)("div", {
11027
+ className: WorkspaceBrowser_module_css_default.empty,
11028
+ children: t("empty.none")
11029
+ }), groups.map((group) => {
11030
+ const workspaceId = group.workspaceId;
11031
+ const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
11032
+ const workspaceDragProps = workspaceId === void 0 ? void 0 : {
11033
+ start: () => {
11034
+ workspaceDropCommitted.current = false;
11035
+ setWorkspaceDrag({
11036
+ workspaceId,
11037
+ over: null
11038
+ });
11039
+ },
11040
+ end: () => {
11041
+ if (workspaceDrag?.over !== null && workspaceDrag?.over !== void 0) commitWorkspaceDrag(workspaceDrag, workspaceDrag.over);
11042
+ else setWorkspaceDrag(null);
11043
+ workspaceDropCommitted.current = false;
11044
+ }
11045
+ };
11046
+ const hoverWorkspace = workspaceId === void 0 ? void 0 : (half) => {
11047
+ setWorkspaceDrag((active) => active === null ? active : {
11048
+ ...active,
11049
+ over: {
11050
+ id: workspaceId,
11051
+ half
11052
+ }
11053
+ });
11054
+ };
11055
+ const dropWorkspace = workspaceId === void 0 ? void 0 : (half) => {
11056
+ if (workspaceDrag === null) return;
11057
+ commitWorkspaceDrag(workspaceDrag, {
11058
+ id: workspaceId,
11059
+ half
11060
+ });
11061
+ };
11062
+ return (0, react_jsx_runtime$1.jsxs)("div", {
11063
+ className: clsx(WorkspaceBrowser_module_css_default.groupSection, workspaceMarker === "before" && WorkspaceBrowser_module_css_default.workspaceDropBefore, workspaceMarker === "after" && WorkspaceBrowser_module_css_default.workspaceDropAfter),
11064
+ onDragOver: workspaceDrag === null || hoverWorkspace === void 0 ? void 0 : (e) => {
11065
+ e.preventDefault();
11066
+ e.dataTransfer.dropEffect = "move";
11067
+ hoverWorkspace(workspaceGroupHalf(e));
11068
+ },
11069
+ onDrop: workspaceDrag === null || dropWorkspace === void 0 ? void 0 : (e) => {
11070
+ e.preventDefault();
11071
+ dropWorkspace(workspaceGroupHalf(e));
11072
+ },
11073
+ children: [
11074
+ (0, react_jsx_runtime$1.jsx)(ProjectRowItem, {
11075
+ group,
11076
+ home,
11077
+ t,
11078
+ onToggle: () => {
11079
+ if (group.expanded) setExpandedSessionGroups((keys) => keys.filter((key) => key !== group.key));
11080
+ setGroupExpanded(group.key, !group.expanded);
11081
+ },
11082
+ onCreate: () => {
11083
+ if (group.workspaceId !== void 0) {
11084
+ setGroupExpanded(group.key, true);
11085
+ startSession(group.workspaceId);
11086
+ }
11087
+ },
11088
+ drag: workspaceDragProps,
11089
+ actions: group.workspaceId === void 0 ? void 0 : {
11090
+ rename: () => {
11091
+ /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
11092
+ if (group.workspaceId !== void 0) onRenameRequest(group.workspaceId, group.label);
11093
+ },
11094
+ delete: () => {
11095
+ /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
11096
+ if (group.workspaceId !== void 0) onDeleteRequest(group.workspaceId, group.label);
11097
+ }
11098
+ }
11099
+ }),
11100
+ (expandedSessionGroups.includes(group.key) ? group.sessions : group.sessions.slice(0, COLLAPSED_SESSION_LIMIT)).map((node) => {
11101
+ const sameGroupDrag = drag !== null && drag.accountKey === group.key;
11102
+ return (0, react_jsx_runtime$1.jsx)(SessionNodeItem, {
11103
+ node,
11104
+ currentId: current,
11105
+ now,
11106
+ onOpen: open,
11107
+ onRename: onSessionRename,
11108
+ onFork: forkSession,
11109
+ onArchive: onSessionArchive,
11110
+ drag: {
11111
+ start: () => {
11112
+ sessionDropCommitted.current = false;
11113
+ setDrag({
11114
+ accountKey: group.key,
11115
+ sessionId: node.id,
11116
+ over: null
11117
+ });
11118
+ },
11119
+ active: sameGroupDrag,
11120
+ marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null,
11121
+ hover: (half) => {
11122
+ /* v8 ignore next -- narrowing guard: Rows gates hover on `active`, which is false while the drag state is null. */
11123
+ setDrag((d) => d === null ? d : {
11124
+ ...d,
11125
+ over: {
11126
+ id: node.id,
11127
+ half
11128
+ }
11129
+ });
11130
+ },
11131
+ drop: (half) => {
11132
+ /* v8 ignore next -- narrowing guard: Rows gates drop on `active`, which is false while the drag state is null. */
11133
+ if (drag === null) return;
11134
+ commitSessionDrag(drag, {
11135
+ id: node.id,
11136
+ half
11137
+ });
11138
+ },
11139
+ end: () => {
11140
+ if (drag?.over !== null && drag?.over !== void 0) commitSessionDrag(drag, drag.over);
11141
+ else setDrag(null);
11142
+ sessionDropCommitted.current = false;
11143
+ }
11144
+ },
11145
+ t
11146
+ }, node.id);
11147
+ }),
11148
+ group.sessions.length > COLLAPSED_SESSION_LIMIT && (0, react_jsx_runtime$1.jsx)("button", {
11149
+ type: "button",
11150
+ className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
11151
+ "aria-expanded": expandedSessionGroups.includes(group.key),
11152
+ onClick: () => {
11153
+ setExpandedSessionGroups((keys) => toggled(keys, group.key));
11154
+ },
11155
+ children: expandedSessionGroups.includes(group.key) ? t("sessions.collapse") : t("sessions.expand", { n: group.sessions.length - COLLAPSED_SESSION_LIMIT })
11156
+ })
11157
+ ]
11158
+ }, group.key);
11159
+ })]
11160
+ }),
11161
+ (0, react_jsx_runtime$1.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })
11162
+ ]
11163
+ });
11164
+ }
11165
+ /** The flat "In one list" body: every session is one draggable top-level row. */
11166
+ function FlatList({ useSessions, open, forkSession, onSessionRename, onSessionArchive, archivedSessionIds, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
11167
+ const list = useSessions((s) => s);
11168
+ const baseRows = (0, react$1.useMemo)(() => deriveFlat(list, archivedSessionIds), [list, archivedSessionIds]);
11169
+ const sessionIds = (0, react$1.useMemo)(() => baseRows.map((row) => row.id), [baseRows]);
11170
+ const previousOrderBy = (0, react$1.useRef)(orderBy);
11171
+ (0, react$1.useEffect)(() => {
11172
+ if (list.phase !== "ready") return;
11173
+ const previousOrder = sessionOrderByAccount[FLAT_SESSION_ORDER_KEY];
11174
+ const previousUpdatedAt = sessionUpdatedAtByAccount["__flat_session_order__"] ?? {};
11175
+ const switchedToUpdated = previousOrderBy.current !== "updated" && orderBy === "updated";
11176
+ previousOrderBy.current = orderBy;
11177
+ const next = nextSessionOrderAccount({
11178
+ sessionIds,
11179
+ previousOrder,
11180
+ previousUpdatedAt,
11181
+ list,
11182
+ orderBy,
11183
+ sortByRecency: orderBy === "updated" && (previousOrder === void 0 || switchedToUpdated)
11184
+ });
11185
+ if (next.changed) syncSessionOrderAccount(FLAT_SESSION_ORDER_KEY, next.order.map((id) => id), next.updatedAt);
11186
+ }, [
11187
+ list,
11188
+ orderBy,
11189
+ sessionOrderByAccount,
11190
+ sessionUpdatedAtByAccount,
11191
+ sessionIds,
11192
+ syncSessionOrderAccount
11193
+ ]);
11194
+ const rows = (0, react$1.useMemo)(() => {
11195
+ const byId = new Map(baseRows.map((row) => [row.id, row]));
11196
+ return reconciledSessionOrder(sessionIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]).flatMap((id) => {
11197
+ const row = byId.get(id);
11198
+ return row === void 0 ? [] : [row];
11199
+ });
11200
+ }, [
11201
+ baseRows,
11202
+ sessionOrderByAccount,
11203
+ sessionIds
11204
+ ]);
11205
+ const [drag, setDrag] = (0, react$1.useState)(null);
11206
+ const dropCommitted = (0, react$1.useRef)(false);
11207
+ useNativeDragAcceptance(drag !== null);
11208
+ const commitDrag = (activeDrag, over) => {
11209
+ if (dropCommitted.current) return;
11210
+ dropCommitted.current = true;
11211
+ setDrag(null);
11212
+ const targetIndex = rows.findIndex((row) => row.id === over.id);
11213
+ if (targetIndex === -1) return;
11214
+ const anchor = over.half === "before" ? over.id : rows[targetIndex + 1]?.id;
11215
+ if (anchor === activeDrag.sessionId) return;
11216
+ const sourceIndex = rows.findIndex((row) => row.id === activeDrag.sessionId);
11217
+ const anchorIndex = anchor === void 0 ? rows.length : rows.findIndex((row) => row.id === anchor);
11218
+ if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
11219
+ const nextOrder = rows.map((row) => row.id).filter((id) => id !== activeDrag.sessionId);
11220
+ const insertAt = anchor === void 0 ? nextOrder.length : nextOrder.indexOf(anchor);
11221
+ nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
11222
+ setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder.map((id) => id));
11223
+ };
11224
+ const now = Date.now();
11225
+ return (0, react_jsx_runtime$1.jsxs)("div", {
11226
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
11227
+ children: [(0, react_jsx_runtime$1.jsxs)("div", {
11228
+ className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
11229
+ role: "tree",
11230
+ "aria-label": t("section.sessions"),
11231
+ children: [rows.length === 0 && (0, react_jsx_runtime$1.jsx)("div", {
11232
+ className: WorkspaceBrowser_module_css_default.empty,
11233
+ children: t("empty.none")
11234
+ }), rows.map((node) => {
11235
+ const active = drag !== null;
11236
+ return (0, react_jsx_runtime$1.jsx)(SessionNodeItem, {
11237
+ node,
11238
+ currentId: list.current,
11239
+ now,
11240
+ onOpen: open,
11241
+ onRename: onSessionRename,
11242
+ onFork: forkSession,
11243
+ onArchive: onSessionArchive,
11244
+ flat: true,
11245
+ drag: {
11246
+ start: () => {
11247
+ dropCommitted.current = false;
11248
+ setDrag({
11249
+ accountKey: FLAT_SESSION_ORDER_KEY,
11250
+ sessionId: node.id,
11251
+ over: null
11252
+ });
11253
+ },
11254
+ active,
11255
+ marker: active && drag.over?.id === node.id ? drag.over.half : null,
11256
+ hover: (half) => {
11257
+ setDrag((current) => current === null ? current : {
11258
+ ...current,
11259
+ over: {
11260
+ id: node.id,
11261
+ half
11262
+ }
11263
+ });
11264
+ },
11265
+ drop: (half) => {
11266
+ if (drag !== null) commitDrag(drag, {
11267
+ id: node.id,
11268
+ half
11269
+ });
11270
+ },
11271
+ end: () => {
11272
+ if (drag?.over !== null && drag?.over !== void 0) commitDrag(drag, drag.over);
11273
+ else setDrag(null);
11274
+ dropCommitted.current = false;
11275
+ }
11276
+ },
11277
+ t
11278
+ }, node.id);
11279
+ })]
11280
+ }), (0, react_jsx_runtime$1.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
11281
+ });
11282
+ }
11283
+ /** Flat search body: local metadata matches plus the current Host result page. */
11284
+ function SearchResults({ useSessions, open, workspaces, archivedSessionIds, query, remote, resultLimit, t }) {
11285
+ const list = useSessions((s) => s);
11286
+ const currentRemote = remote.query === query ? remote : {
11287
+ query,
11288
+ status: "loading",
11289
+ items: [],
11290
+ hasMore: false
11291
+ };
11292
+ const results = (0, react$1.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, currentRemote, resultLimit), [
11293
+ list,
11294
+ workspaces,
11295
+ query,
11296
+ archivedSessionIds,
11297
+ currentRemote,
11298
+ resultLimit
11299
+ ]);
11300
+ const pending = currentRemote.status === "loading";
11301
+ const failed = currentRemote.status === "error";
11302
+ return (0, react_jsx_runtime$1.jsxs)("div", {
11303
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
11304
+ children: [(0, react_jsx_runtime$1.jsxs)("div", {
11305
+ className: WorkspaceBrowser_module_css_default.list,
11306
+ children: [
11307
+ (0, react_jsx_runtime$1.jsx)("div", {
11308
+ className: WorkspaceBrowser_module_css_default.searchTree,
11309
+ role: "tree",
11310
+ "aria-label": t("search.results.aria"),
11311
+ children: results.items.map((result) => (0, react_jsx_runtime$1.jsx)(SearchResultItem, {
11312
+ result,
11313
+ currentId: list.current,
11314
+ onOpen: open,
11315
+ t
11316
+ }, result.id))
11317
+ }),
11318
+ pending && (0, react_jsx_runtime$1.jsx)("div", {
11319
+ className: WorkspaceBrowser_module_css_default.searchStatus,
11320
+ role: "status",
11321
+ children: t("search.pending")
11322
+ }),
11323
+ failed && (0, react_jsx_runtime$1.jsx)("div", {
11324
+ className: WorkspaceBrowser_module_css_default.searchWarning,
11325
+ role: "status",
11326
+ children: t("search.unavailable")
11327
+ }),
11328
+ !pending && results.items.length === 0 && (0, react_jsx_runtime$1.jsx)("div", {
11329
+ className: WorkspaceBrowser_module_css_default.empty,
11330
+ children: t("search.noMatches")
11331
+ }),
11332
+ results.hasMore && (0, react_jsx_runtime$1.jsx)("div", {
11333
+ className: WorkspaceBrowser_module_css_default.searchStatus,
11334
+ children: t("search.hasMore", { n: resultLimit })
11335
+ })
11336
+ ]
11337
+ }), (0, react_jsx_runtime$1.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
11338
+ });
11339
+ }
11340
+ /**
11341
+ * Render the browsing region.
11342
+ * @param props - composed slot props (shell owner share + store + injected actions).
11343
+ * @returns the region element tree.
11344
+ */
11345
+ function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostDescription, renderSlot, t }) {
11346
+ const home = useHostDescription((description) => description?.home);
11347
+ const workspaces = useWorkspaces((state) => state.items);
11348
+ const workspacePhase = useWorkspaces((state) => state.phase);
11349
+ const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
11350
+ const directoryFlowAvailable = useDirectoryFlow((occupied) => occupied);
11351
+ const groupBy = useStore((s) => s.groupBy);
11352
+ const orderBy = useStore((s) => s.orderBy);
11353
+ const groupExpansion = useStore((s) => s.groupExpansion);
11354
+ const sessionOrderByAccount = useStore((s) => s.sessionOrderByAccount);
11355
+ const sessionUpdatedAtByAccount = useStore((s) => s.sessionUpdatedAtByAccount);
11356
+ const currentBlankSessionId = useSessions((state) => {
11357
+ const current = state.current;
11358
+ return current !== void 0 && state.byId[current]?.blank === true ? current : void 0;
11359
+ });
11360
+ const currentBlankAccount = currentBlankSessionId === void 0 ? void 0 : workspaces.find((workspace) => workspace.sessionIds.includes(currentBlankSessionId))?.workspaceId ?? "";
11361
+ const promotedBlank = (0, react$1.useRef)(void 0);
11362
+ (0, react$1.useEffect)(() => {
11363
+ if (currentBlankSessionId === void 0 || currentBlankAccount === void 0) {
11364
+ promotedBlank.current = void 0;
11365
+ return;
11366
+ }
11367
+ if (promotedBlank.current?.sessionId === currentBlankSessionId && promotedBlank.current.accountKey === currentBlankAccount) return;
11368
+ promotedBlank.current = {
11369
+ sessionId: currentBlankSessionId,
11370
+ accountKey: currentBlankAccount
11371
+ };
11372
+ for (const accountKey of new Set([currentBlankAccount, FLAT_SESSION_ORDER_KEY])) {
11373
+ const previous = sessionOrderByAccount[accountKey] ?? [];
11374
+ actions.setSessionOrder(accountKey, [currentBlankSessionId, ...previous.filter((id) => id !== currentBlankSessionId)]);
11375
+ }
11376
+ }, [
11377
+ actions.setSessionOrder,
11378
+ currentBlankAccount,
11379
+ currentBlankSessionId,
11380
+ sessionOrderByAccount
11381
+ ]);
11382
+ (0, react$1.useEffect)(() => {
11383
+ if (workspacePhase !== "ready") return;
11384
+ actions.retainAccountKeys([
11385
+ "",
11386
+ FLAT_SESSION_ORDER_KEY,
11387
+ ...workspaces.map((workspace) => workspace.workspaceId)
11388
+ ]);
11389
+ }, [
11390
+ actions.retainAccountKeys,
11391
+ workspacePhase,
11392
+ workspaces
11393
+ ]);
11394
+ const [query, setQuery] = (0, react$1.useState)("");
11395
+ const [searchExpanded, setSearchExpanded] = (0, react$1.useState)(false);
11396
+ const normalizedQuery = sanitizeSearchQuery(query).trim();
11397
+ const [remoteSearch, setRemoteSearch] = (0, react$1.useState)({
11398
+ query: "",
11399
+ status: "idle",
11400
+ items: [],
11401
+ hasMore: false
11402
+ });
11403
+ const searchRoot = (0, react$1.useRef)(null);
11404
+ const searchInput = (0, react$1.useRef)(null);
11405
+ const [wsPickerOpen, setWsPickerOpen] = (0, react$1.useState)(false);
11406
+ const wsPlusRef = (0, react$1.useRef)(null);
11407
+ const composingRef = (0, react$1.useRef)(false);
11408
+ const [searchOnExpand, setSearchOnExpand] = (0, react$1.useState)(false);
11409
+ (0, react$1.useEffect)(() => {
11410
+ if (wide && searchOnExpand) {
11411
+ const timer = window.setTimeout(() => {
11412
+ searchInput.current?.focus({ preventScroll: true });
11413
+ setSearchOnExpand(false);
11414
+ }, EXPAND_SLIDE_MS);
11415
+ return () => {
11416
+ window.clearTimeout(timer);
11417
+ };
11418
+ }
11419
+ }, [wide, searchOnExpand]);
11420
+ (0, react$1.useEffect)(() => {
11421
+ if (!wide || !searchExpanded || searchOnExpand) return;
11422
+ searchInput.current?.focus({ preventScroll: true });
11423
+ }, [
11424
+ wide,
11425
+ searchExpanded,
11426
+ searchOnExpand
11427
+ ]);
11428
+ (0, react$1.useEffect)(() => {
11429
+ if (!wide || !searchExpanded || searchOnExpand) return;
11430
+ const onClick = (event) => {
11431
+ if (!(event.target instanceof Node) || searchRoot.current?.contains(event.target) === true) return;
11432
+ searchInput.current?.blur();
11433
+ if (normalizedQuery !== "") return;
11434
+ setSearchExpanded(false);
11435
+ };
11436
+ document.addEventListener("click", onClick);
11437
+ return () => {
11438
+ document.removeEventListener("click", onClick);
11439
+ };
11440
+ }, [
11441
+ normalizedQuery,
11442
+ wide,
11443
+ searchExpanded,
11444
+ searchOnExpand
11445
+ ]);
11446
+ (0, react$1.useEffect)(() => {
11447
+ if (normalizedQuery === "") {
11448
+ setRemoteSearch({
11449
+ query: "",
11450
+ status: "idle",
11451
+ items: [],
11452
+ hasMore: false
11453
+ });
11454
+ return;
11455
+ }
11456
+ const controller = new AbortController();
11457
+ setRemoteSearch({
11458
+ query: normalizedQuery,
11459
+ status: "loading",
11460
+ items: [],
11461
+ hasMore: false
11462
+ });
11463
+ const timer = window.setTimeout(() => {
11464
+ searchSessions(normalizedQuery, controller.signal).then((result) => {
11465
+ if (controller.signal.aborted) return;
11466
+ setRemoteSearch({
11467
+ query: normalizedQuery,
11468
+ status: "ready",
11469
+ items: result.items,
11470
+ hasMore: result.hasMore
11471
+ });
11472
+ }).catch(() => {
11473
+ if (controller.signal.aborted) return;
11474
+ setRemoteSearch({
11475
+ query: normalizedQuery,
11476
+ status: "error",
11477
+ items: [],
11478
+ hasMore: false
11479
+ });
11480
+ });
11481
+ }, SEARCH_DEBOUNCE_MS);
11482
+ return () => {
11483
+ window.clearTimeout(timer);
11484
+ controller.abort();
11485
+ };
11486
+ }, [normalizedQuery, searchSessions]);
11487
+ const [renameTarget, setRenameTarget] = (0, react$1.useState)(null);
11488
+ const [renameDraft, setRenameDraft] = (0, react$1.useState)("");
11489
+ const [renaming, setRenaming] = (0, react$1.useState)(false);
11490
+ const [renameError, setRenameError] = (0, react$1.useState)(null);
11491
+ const renameTrimmed = renameDraft.trim();
11492
+ const renameDuplicate = renameTarget !== null && renameTrimmed !== "" && renameTrimmed !== renameTarget.currentTitle && workspaces.some((w) => w.title === renameTrimmed);
11493
+ const renameBlocked = renaming || renameTrimmed === "" || renameTarget === null || renameTrimmed === renameTarget.currentTitle || renameDuplicate;
11494
+ const closeRename = () => {
11495
+ if (renaming) return;
11496
+ setRenameTarget(null);
11497
+ setRenameError(null);
11498
+ };
11499
+ const confirmRename = () => {
11500
+ if (renameBlocked) return;
11501
+ setRenaming(true);
11502
+ setRenameError(null);
11503
+ renameWorkspace(renameTarget.workspaceId, renameTrimmed).then(() => {
11504
+ setRenaming(false);
11505
+ setRenameTarget(null);
11506
+ }).catch((reason) => {
11507
+ setRenaming(false);
11508
+ setRenameError(reason instanceof Error ? reason.message : String(reason));
11509
+ });
11510
+ };
11511
+ const [sessionRenameTarget, setSessionRenameTarget] = (0, react$1.useState)(null);
11512
+ const [sessionRenameDraft, setSessionRenameDraft] = (0, react$1.useState)("");
11513
+ const [sessionRenaming, setSessionRenaming] = (0, react$1.useState)(false);
11514
+ const [sessionRenameError, setSessionRenameError] = (0, react$1.useState)(null);
11515
+ const sessionRenameTrimmed = sessionRenameDraft.trim();
11516
+ const sessionRenameBlocked = sessionRenaming || sessionRenameTrimmed === "" || sessionRenameTarget === null;
11517
+ const closeSessionRename = () => {
11518
+ if (sessionRenaming) return;
11519
+ setSessionRenameTarget(null);
11520
+ setSessionRenameError(null);
11521
+ };
11522
+ const confirmSessionRename = () => {
11523
+ if (sessionRenameBlocked) return;
11524
+ setSessionRenaming(true);
11525
+ setSessionRenameError(null);
11526
+ renameSession(sessionRenameTarget.sessionId, sessionRenameTrimmed).then(() => {
11527
+ setSessionRenaming(false);
11528
+ setSessionRenameTarget(null);
11529
+ }).catch((reason) => {
11530
+ setSessionRenaming(false);
11531
+ setSessionRenameError(reason instanceof Error ? reason.message : String(reason));
11532
+ });
11533
+ };
11534
+ const onSessionRename = (sessionId, currentTitle) => {
11535
+ setSessionRenameTarget({
11536
+ sessionId,
11537
+ currentTitle
11538
+ });
11539
+ setSessionRenameDraft(currentTitle);
11540
+ setSessionRenameError(null);
11541
+ };
11542
+ const onSessionArchive = (sessionId) => {
11543
+ archiveSession(sessionId).catch((reason) => {
11544
+ console.warn("session archive rejected:", reason);
11545
+ });
11546
+ };
11547
+ const [deleteTarget, setDeleteTarget] = (0, react$1.useState)(null);
11548
+ const [deleting, setDeleting] = (0, react$1.useState)(false);
11549
+ const [deleteCommittedId, setDeleteCommittedId] = (0, react$1.useState)(null);
11550
+ const [deleteError, setDeleteError] = (0, react$1.useState)(null);
11551
+ (0, react$1.useEffect)(() => {
11552
+ if (deleteCommittedId === null || workspaces.some((workspace) => workspace.workspaceId === deleteCommittedId)) return;
11553
+ setDeleting(false);
11554
+ setDeleteCommittedId(null);
11555
+ setDeleteTarget(null);
11556
+ }, [deleteCommittedId, workspaces]);
11557
+ const closeDelete = () => {
11558
+ if (deleting) return;
11559
+ setDeleteTarget(null);
11560
+ setDeleteError(null);
11561
+ };
11562
+ const confirmDelete = () => {
11563
+ /* v8 ignore next -- the Modal is absent without a target and its button is disabled while deleting. */
11564
+ if (deleting || deleteTarget === null) return;
11565
+ setDeleting(true);
11566
+ setDeleteCommittedId(null);
11567
+ setDeleteError(null);
11568
+ deleteWorkspace(deleteTarget.workspaceId).then(() => {
11569
+ setDeleteCommittedId(deleteTarget.workspaceId);
11570
+ }).catch((reason) => {
11571
+ setDeleting(false);
11572
+ setDeleteError(reason instanceof Error ? reason.message : String(reason));
11573
+ });
11574
+ };
11575
+ return (0, react_jsx_runtime$1.jsxs)("div", {
11576
+ className: clsx(WorkspaceBrowser_module_css_default.root, !wide && WorkspaceBrowser_module_css_default.rail),
11577
+ children: [
11578
+ (0, react_jsx_runtime$1.jsxs)("div", {
11579
+ className: WorkspaceBrowser_module_css_default.sectionHeader,
11580
+ children: [
11581
+ wide && (0, react_jsx_runtime$1.jsx)("span", {
11582
+ className: clsx(WorkspaceBrowser_module_css_default.sectionLabel, WorkspaceBrowser_module_css_default.wide, searchExpanded && WorkspaceBrowser_module_css_default.sectionLabelHidden),
11583
+ children: groupBy === "flat" ? t("section.sessions") : t("section.workspaces")
11584
+ }),
11585
+ wide && (0, react_jsx_runtime$1.jsx)("div", {
11586
+ className: clsx(WorkspaceBrowser_module_css_default.searchSlot, searchExpanded && WorkspaceBrowser_module_css_default.searchSlotExpanded),
11587
+ children: (0, react_jsx_runtime$1.jsxs)("div", {
11588
+ ref: searchRoot,
11589
+ className: clsx(WorkspaceBrowser_module_css_default.search, searchExpanded && WorkspaceBrowser_module_css_default.searchExpanded),
11590
+ onClick: () => {
11591
+ setWsPickerOpen(false);
11592
+ setSearchExpanded(true);
11593
+ searchInput.current?.focus();
11594
+ },
11595
+ children: [
11596
+ (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
11597
+ label: t("search"),
11598
+ side: "bottom",
11599
+ delayMs: 500,
11600
+ disabled: searchExpanded,
11601
+ children: (0, react_jsx_runtime$1.jsx)("button", {
11602
+ type: "button",
11603
+ className: WorkspaceBrowser_module_css_default.searchButton,
11604
+ "aria-label": t("search.sessions.aria"),
11605
+ "aria-expanded": searchExpanded,
11606
+ onClick: () => {
11607
+ setWsPickerOpen(false);
11608
+ setSearchExpanded(true);
11609
+ },
11610
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: searchExpanded ? 11 : 14 })
11611
+ })
11612
+ }),
11613
+ (0, react_jsx_runtime$1.jsx)("input", {
11614
+ ref: searchInput,
11615
+ className: WorkspaceBrowser_module_css_default.searchInput,
11616
+ type: "text",
11617
+ placeholder: t("search.placeholder"),
11618
+ maxLength: SEARCH_QUERY_MAX_CODE_UNITS,
11619
+ value: query,
11620
+ tabIndex: searchExpanded ? 0 : -1,
11621
+ onChange: (e) => {
11622
+ setQuery(sanitizeSearchQuery(e.target.value));
11623
+ },
11624
+ onKeyDown: (e) => {
11625
+ if (e.key !== "Escape") return;
11626
+ setQuery("");
11627
+ setSearchExpanded(false);
11628
+ }
11629
+ }),
11630
+ searchExpanded && (0, react_jsx_runtime$1.jsx)("button", {
11631
+ type: "button",
11632
+ className: WorkspaceBrowser_module_css_default.clearButton,
11633
+ "aria-label": t("search.clear"),
11634
+ onClick: (e) => {
11635
+ e.stopPropagation();
11636
+ setQuery("");
11637
+ setSearchExpanded(false);
11638
+ },
11639
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFill14, {})
11640
+ })
11641
+ ]
11642
+ })
11643
+ }),
11644
+ (0, react_jsx_runtime$1.jsxs)("div", {
11645
+ className: clsx(WorkspaceBrowser_module_css_default.headerActions, wide && searchExpanded && WorkspaceBrowser_module_css_default.headerActionsHidden),
11646
+ children: [wide && (0, react_jsx_runtime$1.jsx)(ViewOptionsMenu, {
11647
+ groupBy,
11648
+ orderBy,
11649
+ onGroupPick: (mode) => {
11650
+ actions.setGroupBy(mode);
11651
+ },
11652
+ onOrderPick: (mode) => {
11653
+ actions.setOrderBy(mode);
11654
+ },
11655
+ t
11656
+ }), directoryFlowAvailable && (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
11657
+ label: t("workspace.add"),
11658
+ side: "bottom",
11659
+ delayMs: 500,
11660
+ children: (0, react_jsx_runtime$1.jsx)("button", {
11661
+ ref: wsPlusRef,
11662
+ type: "button",
11663
+ className: WorkspaceBrowser_module_css_default.iconButton,
11664
+ "aria-label": t("workspace.add"),
11665
+ onClick: () => {
11666
+ setWsPickerOpen((v) => !v);
11667
+ },
11668
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconProjectAddOutline16, { size: wide ? 16 : 18 })
11669
+ })
11670
+ })]
11671
+ }),
11672
+ (0, react_jsx_runtime$1.jsx)(WorkspacePickFlow, {
11673
+ t,
11674
+ open: wsPickerOpen,
11675
+ anchorRef: wsPlusRef,
11676
+ useWorkspaces,
11677
+ createWorkspace,
11678
+ useDirectoryFlow,
11679
+ renderDirectoryFlow: (owner) => renderSlot("sidebar.workspaces.directoryFlow", owner),
11680
+ addOnly: true,
11681
+ side: "right",
11682
+ onPick: (workspaceId) => {
11683
+ setWsPickerOpen(false);
11684
+ startSession(workspaceId);
11685
+ },
11686
+ onClose: () => {
11687
+ setWsPickerOpen(false);
11688
+ }
11689
+ })
11690
+ ]
11691
+ }),
11692
+ !wide && (0, react_jsx_runtime$1.jsx)("div", {
11693
+ className: WorkspaceBrowser_module_css_default.search,
11694
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
11695
+ label: t("search"),
11696
+ children: (0, react_jsx_runtime$1.jsx)("button", {
11697
+ type: "button",
11698
+ className: WorkspaceBrowser_module_css_default.searchButton,
11699
+ "aria-label": t("search.sessions.aria"),
11700
+ onClick: () => {
11701
+ setSearchExpanded(true);
11702
+ setSearchOnExpand(true);
11703
+ expandSidebar();
11704
+ },
11705
+ children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 18 })
11706
+ })
11707
+ })
11708
+ }),
11709
+ (0, react_jsx_runtime$1.jsx)("div", {
11710
+ className: WorkspaceBrowser_module_css_default.listArea,
11711
+ children: wide && (normalizedQuery !== "" ? (0, react_jsx_runtime$1.jsx)(SearchResults, {
11712
+ useSessions,
11713
+ open,
11714
+ workspaces,
11715
+ archivedSessionIds,
11716
+ query: normalizedQuery,
11717
+ remote: remoteSearch,
11718
+ resultLimit: searchResultLimit,
11719
+ t
11720
+ }) : groupBy === "flat" ? (0, react_jsx_runtime$1.jsx)(FlatList, {
11721
+ useSessions,
11722
+ open,
11723
+ forkSession,
11724
+ onSessionRename,
11725
+ onSessionArchive,
11726
+ archivedSessionIds,
11727
+ orderBy,
11728
+ sessionOrderByAccount,
11729
+ sessionUpdatedAtByAccount,
11730
+ syncSessionOrderAccount: actions.syncSessionOrderAccount,
11731
+ setSessionOrder: actions.setSessionOrder,
11732
+ t
11733
+ }) : (0, react_jsx_runtime$1.jsx)(SessionTree, {
11734
+ useSessions,
11735
+ onSessionRename,
11736
+ onSessionArchive,
11737
+ forkSession,
11738
+ workspaces,
11739
+ groupExpansion,
11740
+ setGroupExpanded: actions.setGroupExpanded,
11741
+ sessionOrderByAccount,
11742
+ sessionUpdatedAtByAccount,
11743
+ syncSessionOrderAccount: actions.syncSessionOrderAccount,
11744
+ setSessionOrder: actions.setSessionOrder,
11745
+ archivedSessionIds,
11746
+ startSession,
11747
+ open,
11748
+ insertWorkspaceBefore,
11749
+ insertSessionBefore,
11750
+ orderBy,
11751
+ home,
11752
+ t,
11753
+ onRenameRequest: (workspaceId, currentTitle) => {
11754
+ setRenameTarget({
11755
+ workspaceId,
11756
+ currentTitle
11757
+ });
11758
+ setRenameDraft(currentTitle);
11759
+ setRenameError(null);
11760
+ },
11761
+ onDeleteRequest: (workspaceId, title) => {
11762
+ setDeleteTarget({
11763
+ workspaceId,
11764
+ title
11765
+ });
11766
+ setDeleteError(null);
11767
+ }
11768
+ }))
11769
+ }),
11770
+ (0, react_jsx_runtime$1.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
11771
+ open: renameTarget !== null,
11772
+ onClose: closeRename,
11773
+ closeLabel: t("close"),
11774
+ title: t("rename.workspace.title"),
11775
+ footer: (0, react_jsx_runtime$1.jsxs)(react_jsx_runtime$1.Fragment, { children: [(0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
11776
+ variant: "outline",
11777
+ disabled: renaming,
11778
+ onClick: closeRename,
11779
+ children: t("cancel")
11780
+ }), (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
11781
+ variant: "primary",
11782
+ disabled: renameBlocked,
11783
+ onClick: confirmRename,
11784
+ children: t("rename")
11785
+ })] }),
11786
+ children: [
11787
+ (0, react_jsx_runtime$1.jsx)("input", {
11788
+ className: WorkspaceBrowser_module_css_default.renameInput,
11789
+ value: renameDraft,
11790
+ "aria-label": t("field.workspaceName"),
11791
+ autoFocus: true,
11792
+ disabled: renaming,
11793
+ onFocus: (e) => {
11794
+ e.target.select();
11795
+ },
11796
+ onChange: (e) => {
11797
+ setRenameDraft(e.target.value);
11798
+ setRenameError(null);
11799
+ },
11800
+ onCompositionStart: () => {
11801
+ composingRef.current = true;
11802
+ },
11803
+ onCompositionEnd: () => {
11804
+ composingRef.current = false;
11805
+ },
11806
+ onKeyDown: (e) => {
11807
+ if (e.key === "Enter" && !composingRef.current) {
11808
+ e.preventDefault();
11809
+ confirmRename();
11810
+ }
11811
+ }
11812
+ }),
11813
+ renameDuplicate && (0, react_jsx_runtime$1.jsx)("div", {
11814
+ className: WorkspaceBrowser_module_css_default.renameError,
11815
+ role: "alert",
11816
+ children: t("conflict.named", { name: renameTrimmed })
11817
+ }),
11818
+ renameError !== null && (0, react_jsx_runtime$1.jsx)("div", {
11819
+ className: WorkspaceBrowser_module_css_default.renameError,
11820
+ role: "alert",
11821
+ children: renameError
11822
+ })
11823
+ ]
11824
+ }),
11825
+ (0, react_jsx_runtime$1.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
11826
+ open: sessionRenameTarget !== null,
11827
+ onClose: closeSessionRename,
11828
+ closeLabel: t("close"),
11829
+ title: t("rename.session.title"),
11830
+ footer: (0, react_jsx_runtime$1.jsxs)(react_jsx_runtime$1.Fragment, { children: [(0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
11831
+ variant: "outline",
11832
+ disabled: sessionRenaming,
11833
+ onClick: closeSessionRename,
11834
+ children: t("cancel")
11835
+ }), (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
11836
+ variant: "primary",
11837
+ disabled: sessionRenameBlocked,
11838
+ onClick: confirmSessionRename,
11839
+ children: t("rename")
11840
+ })] }),
11841
+ children: [(0, react_jsx_runtime$1.jsx)("input", {
11842
+ className: WorkspaceBrowser_module_css_default.renameInput,
11843
+ value: sessionRenameDraft,
11844
+ "aria-label": t("field.sessionName"),
11845
+ autoFocus: true,
11846
+ disabled: sessionRenaming,
11847
+ onFocus: (e) => {
11848
+ e.target.select();
11849
+ },
11850
+ onChange: (e) => {
11851
+ setSessionRenameDraft(e.target.value);
11852
+ setSessionRenameError(null);
11853
+ },
11854
+ onCompositionStart: () => {
11855
+ composingRef.current = true;
11856
+ },
11857
+ onCompositionEnd: () => {
11858
+ composingRef.current = false;
11859
+ },
11860
+ onKeyDown: (e) => {
11861
+ if (e.key === "Enter" && !composingRef.current) {
11862
+ e.preventDefault();
11863
+ confirmSessionRename();
11864
+ }
11865
+ }
11866
+ }), sessionRenameError !== null && (0, react_jsx_runtime$1.jsx)("div", {
11867
+ className: WorkspaceBrowser_module_css_default.renameError,
11868
+ role: "alert",
11869
+ children: sessionRenameError
11870
+ })]
11871
+ }),
11872
+ (0, react_jsx_runtime$1.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
11873
+ open: deleteTarget !== null,
11874
+ onClose: closeDelete,
11875
+ closeLabel: t("close"),
11876
+ title: t("delete.workspace"),
11877
+ ...deleteTarget === null ? {} : { description: t("delete.desc", { name: deleteTarget.title }) },
11878
+ footer: (0, react_jsx_runtime$1.jsxs)(react_jsx_runtime$1.Fragment, { children: [(0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
11879
+ variant: "outline",
11880
+ disabled: deleting,
11881
+ onClick: closeDelete,
11882
+ children: t("cancel")
11883
+ }), (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
11884
+ variant: "outline",
11885
+ className: WorkspaceBrowser_module_css_default.deleteAction,
11886
+ disabled: deleting,
11887
+ onClick: confirmDelete,
11888
+ children: t("delete.workspace")
11889
+ })] }),
11890
+ children: [deleting && (0, react_jsx_runtime$1.jsx)("div", {
11891
+ className: WorkspaceBrowser_module_css_default.deleteStatus,
11892
+ role: "status",
11893
+ children: t("delete.pending")
11894
+ }), deleteError !== null && (0, react_jsx_runtime$1.jsx)("div", {
11895
+ className: WorkspaceBrowser_module_css_default.renameError,
11896
+ role: "alert",
11897
+ children: deleteError
11898
+ })]
11899
+ })
11900
+ ]
11901
+ });
11902
+ }
11903
+ /**
11904
+ * `workspace` namespace dictionaries: the browsing region (section header,
11905
+ * search, tree rows, dialogs) and the pick/add flow. Runtime failure
11906
+ * messages (wire error strings) pass through untranslated by policy.
11907
+ */
11908
+ /** Simplified Chinese dictionary (the key-set source of truth). */
11909
+ const zh = {
11910
+ "dshGitWorktree.managed": "托管 Worktree",
11911
+ "dshGitWorktree.state.working": "进行中",
11912
+ "dshGitWorktree.state.ready_for_review": "待验收",
11913
+ "dshGitWorktree.state.preview_active": "预览中",
11914
+ "dshGitWorktree.state.preview_detached": "待恢复",
11915
+ "dshGitWorktree.state.recovery_required": "需要恢复",
11916
+ "dshGitWorktree.state.finalized": "已完成",
11917
+ "dshGitWorktree.state.discarded": "已放弃",
11918
+ "group.ungrouped": "未分组",
11919
+ "session.new": "新会话",
11920
+ "section.workspaces": "工作区",
11921
+ "section.sessions": "会话",
11922
+ "viewOptions.label": "视图选项",
11923
+ "groupBy.label": "分组方式",
11924
+ "groupBy.workspace": "按工作区",
11925
+ "groupBy.flat": "单列表",
11926
+ "orderBy.label": "排序方式",
11927
+ "orderBy.manual": "手动排序",
11928
+ "orderBy.updated": "最近更新",
11929
+ "sessions.expand": "展开其余 {n} 个会话",
11930
+ "sessions.collapse": "收起",
11931
+ "empty.none": "暂无会话",
11932
+ "empty.noMatches": "无匹配结果",
11933
+ "workspace.add": "添加工作区",
11934
+ "search.sessions.aria": "搜索会话",
11935
+ "search.placeholder": "搜索会话…",
11936
+ "search.clear": "清除搜索",
11937
+ "search.results.aria": "搜索结果",
11938
+ "search.pending": "正在搜索会话历史…",
11939
+ "search.unavailable": "内容搜索暂不可用,仅显示名称匹配。",
11940
+ "search.noMatches": "无匹配会话",
11941
+ "search.hasMore": "仅显示前 {n} 条结果,请缩小搜索范围。",
11942
+ "menu.addWorkspace": "添加工作区…",
11943
+ "picker.loading": "正在加载工作区…",
11944
+ "conflict.named": "已存在名为“{name}”的工作区。",
11945
+ "folderError.title": "无法打开文件夹",
11946
+ "folderError.retry": "重新选择",
11947
+ "rename": "重命名",
11948
+ "rename.workspace.title": "重命名工作区",
11949
+ "rename.session.title": "重命名会话",
11950
+ "field.workspaceName": "工作区名称",
11951
+ "field.sessionName": "会话名称",
11952
+ "delete.workspace": "删除工作区",
11953
+ "delete.desc": "将把“{name}”从工作区列表中移除。文件夹与会话记录会保留,其会话将显示在“未分组”下。",
11954
+ "delete.pending": "正在删除工作区…",
11955
+ "menu.fork": "分叉会话",
11956
+ "menu.archiveSession": "归档会话",
11957
+ "sessions.count.one": "{n} 个会话",
11958
+ "sessions.count.other": "{n} 个会话",
11959
+ "actions.workspace.aria": "工作区“{name}”的操作",
11960
+ "actions.session.aria": "会话“{name}”的操作",
11961
+ "actions.newSession.aria": "在“{name}”中新建会话",
11962
+ "status.running": "进行中",
11963
+ "status.subagentsRunning.one": "{n} 个子代理运行中",
11964
+ "status.subagentsRunning.other": "{n} 个子代理运行中",
11965
+ "status.idle": "空闲",
11966
+ "status.waitingApproval": "等待审批",
11967
+ "status.planReview": "计划待审",
11968
+ "status.waitingAnswer": "等待回答",
11969
+ "status.completed": "已完成",
11970
+ "hover.created": "创建于 {time}",
11971
+ "hover.copied": "已复制",
11972
+ "date.ymd": "{y}年{m}月{d}日",
11973
+ "time.now": "刚刚",
11974
+ "time.minutes": "{n}分钟",
11975
+ "time.hours": "{n}小时",
11976
+ "time.days": "{n}天",
11977
+ "time.months": "{n}个月",
11978
+ "time.years": "{n}年",
11979
+ "time.ago": "{t}前"
11980
+ };
11981
+ /** English dictionary, checked complete against the zh key set. */
11982
+ const en = {
11983
+ "dshGitWorktree.managed": "Managed Worktree",
11984
+ "dshGitWorktree.state.working": "Working",
11985
+ "dshGitWorktree.state.ready_for_review": "Ready",
11986
+ "dshGitWorktree.state.preview_active": "Preview",
11987
+ "dshGitWorktree.state.preview_detached": "Resume preview",
11988
+ "dshGitWorktree.state.recovery_required": "Recovery required",
11989
+ "dshGitWorktree.state.finalized": "Done",
11990
+ "dshGitWorktree.state.discarded": "Discarded",
11991
+ "group.ungrouped": "Ungrouped",
11992
+ "session.new": "New Session",
11993
+ "section.workspaces": "Workspaces",
11994
+ "section.sessions": "Sessions",
11995
+ "viewOptions.label": "View options",
11996
+ "groupBy.label": "Group by",
11997
+ "groupBy.workspace": "WorkSpace",
11998
+ "groupBy.flat": "In one list",
11999
+ "orderBy.label": "Order by",
12000
+ "orderBy.manual": "Manual",
12001
+ "orderBy.updated": "Last updated",
12002
+ "sessions.expand": "Show {n} more sessions",
12003
+ "sessions.collapse": "Show less",
12004
+ "empty.none": "No sessions yet",
12005
+ "empty.noMatches": "No matches",
12006
+ "workspace.add": "Add workspace",
12007
+ "search.sessions.aria": "Search sessions",
12008
+ "search.placeholder": "Search sessions...",
12009
+ "search.clear": "Clear search",
12010
+ "search.results.aria": "Search results",
12011
+ "search.pending": "Searching session history…",
12012
+ "search.unavailable": "Content search is temporarily unavailable. Showing name matches.",
12013
+ "search.noMatches": "No matching sessions",
12014
+ "search.hasMore": "Showing the first {n} results. Narrow your search.",
12015
+ "menu.addWorkspace": "Add workspace…",
12016
+ "picker.loading": "Loading workspaces…",
12017
+ "conflict.named": "A workspace named “{name}” already exists.",
12018
+ "folderError.title": "Couldn’t open folder",
12019
+ "folderError.retry": "Choose again",
12020
+ "rename": "Rename",
12021
+ "rename.workspace.title": "Rename workspace",
12022
+ "rename.session.title": "Rename session",
12023
+ "field.workspaceName": "Workspace name",
12024
+ "field.sessionName": "Session name",
12025
+ "delete.workspace": "Delete workspace",
12026
+ "delete.desc": "This removes “{name}” from the workspace list. The folder and session logs will be kept. Its sessions will appear under Ungrouped.",
12027
+ "delete.pending": "Deleting workspace…",
12028
+ "menu.fork": "Fork session",
12029
+ "menu.archiveSession": "Archive session",
12030
+ "sessions.count.one": "{n} session",
12031
+ "sessions.count.other": "{n} sessions",
12032
+ "actions.workspace.aria": "Workspace actions for {name}",
12033
+ "actions.session.aria": "Session actions for {name}",
12034
+ "actions.newSession.aria": "New session in {name}",
12035
+ "status.running": "Running",
12036
+ "status.subagentsRunning.one": "{n} subagent running",
12037
+ "status.subagentsRunning.other": "{n} subagents running",
12038
+ "status.idle": "Idle",
12039
+ "status.waitingApproval": "Waiting for approval",
12040
+ "status.planReview": "Plan awaiting review",
12041
+ "status.waitingAnswer": "Waiting for answer",
12042
+ "status.completed": "Completed",
12043
+ "hover.created": "Created {time}",
12044
+ "hover.copied": "Copied",
12045
+ "date.ymd": "{y}-{m}-{d}",
12046
+ "time.now": "now",
12047
+ "time.minutes": "{n}min",
12048
+ "time.hours": "{n}h",
12049
+ "time.days": "{n}d",
12050
+ "time.months": "{n}mo",
12051
+ "time.years": "{n}y",
12052
+ "time.ago": "{t} ago"
12053
+ };
12054
+ /** Dictionary namespace owned by this plugin. */
12055
+ const NS = "workspace";
12056
+ /**
12057
+ * Required services (cordis fiber inject). The target slots are declared by
12058
+ * the ui-sidebar / ui-conversation applies, whose activation order relative
12059
+ * to this one is NOT constrained: dsh.client.inject edges are informational
12060
+ * (loading/prefetch metadata, never apply sequencing) and neither owner
12061
+ * provides a waitable service. apply therefore depends on each slot
12062
+ * declaration through `slots.inject()` instead of assuming order.
12063
+ */
12064
+ const inject$3 = [
12065
+ "slots",
12066
+ "sessions",
12067
+ "workspaces",
12068
+ "locale",
12069
+ "connection"
12070
+ ];
12071
+ /**
12072
+ * Register the browser and picker once their slot declarations are on the
12073
+ * ledger. Inject factories return plain callbacks; data reads use the
12074
+ * framework's global hooks.
12075
+ * @param ctx - client root context.
12076
+ */
12077
+ function apply$3(ctx) {
12078
+ const hostDescription = ctx.get("connection").hostDescription;
12079
+ ctx.effect(() => ctx.locale.register(NS, {
12080
+ zh,
12081
+ en
12082
+ }), "ui-workspace: dictionaries");
12083
+ const searchSessions = async (query, signal) => {
12084
+ const result = await ctx.sessions.search(query, signal);
12085
+ if (!result.ok) throw new Error(result.error.message);
12086
+ return result.value;
12087
+ };
12088
+ const flowSource = (hole) => ({
12089
+ getSnapshot: () => ctx.slots.entries(hole).length > 0,
12090
+ subscribe: (listener) => ctx.slots.subscribe(hole, listener)
12091
+ });
12092
+ const browserFlowSource = flowSource("sidebar.workspaces.directoryFlow");
12093
+ const pickerFlowSource = flowSource("conversation.hero.workspace.directoryFlow");
12094
+ const browserInjected = () => ({
12095
+ startSession: (workspaceId) => {
12096
+ ctx.workspaces.startSession(workspaceId);
12097
+ },
12098
+ open: (sessionId) => {
12099
+ ctx.sessions.open(sessionId);
12100
+ },
12101
+ searchSessions,
12102
+ searchResultLimit: ctx.sessions.searchResultLimit,
12103
+ renameSession: async (sessionId, title) => {
12104
+ const session = ctx.sessions.binding(sessionId)?.session;
12105
+ if (session === void 0) throw new Error(`unknown session "${sessionId}"`);
12106
+ const result = await session.rename(title);
12107
+ if (!result.ok) throw new Error(result.error.message);
12108
+ },
12109
+ forkSession: (sessionId) => {
12110
+ ctx.sessions.fork({
12111
+ sessionId,
12112
+ increaseTitle: true
12113
+ }).then((childId) => {
12114
+ ctx.sessions.open(childId);
12115
+ }).catch(() => {});
12116
+ },
12117
+ renameWorkspace: async (workspaceId, title) => {
12118
+ await ctx.workspaces.rename(workspaceId, title);
12119
+ },
12120
+ deleteWorkspace: async (workspaceId) => {
12121
+ await ctx.workspaces.delete(workspaceId);
12122
+ },
12123
+ insertWorkspaceBefore: async (workspaceId, beforeWorkspaceId) => {
12124
+ await ctx.workspaces.insertBefore(workspaceId, beforeWorkspaceId);
12125
+ },
12126
+ archiveSession: async (sessionId) => {
12127
+ await ctx.workspaces.archiveSession(sessionId);
12128
+ },
12129
+ insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
12130
+ await ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
12131
+ },
12132
+ createWorkspace: (input) => ctx.workspaces.create(input),
12133
+ hooks: {
12134
+ directoryFlow: browserFlowSource,
12135
+ hostDescription
12136
+ }
12137
+ });
12138
+ const pickerInjected = () => ({
12139
+ createWorkspace: (input) => ctx.workspaces.create(input),
12140
+ hooks: { directoryFlow: pickerFlowSource }
12141
+ });
12142
+ ctx.slots.inject("sidebar.workspaces", () => ctx.slots.register({
12143
+ name: "sidebar.workspaces",
12144
+ children: { "sidebar.workspaces.directoryFlow": {
12145
+ kind: "single",
12146
+ scope: "root"
12147
+ } },
12148
+ store: createWorkspaceViewStore(),
12149
+ inject: browserInjected,
12150
+ locale: NS
12151
+ }, WorkspaceBrowser));
12152
+ ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
12153
+ name: "conversation.hero.workspace",
12154
+ children: { "conversation.hero.workspace.directoryFlow": {
12155
+ kind: "single",
12156
+ scope: "root"
12157
+ } },
12158
+ inject: pickerInjected,
12159
+ locale: NS
12160
+ }, WorkspacePicker));
12161
+ }
12162
+ exports$1.apply = apply$3;
12163
+ exports$1.inject = inject$3;
12164
+ return module$1.exports;
12165
+ })((specifier) => {
12166
+ const value = modules[specifier];
12167
+ if (value === void 0) throw new Error("Unsupported official Workspace Client require: " + specifier);
12168
+ return value;
12169
+ });
12170
+ const apply$2 = official.apply;
12171
+ const inject$2 = official.inject;
12172
+
12173
+ //#endregion
12174
+ //#region src/client/workspace-sidebar/model.ts
12175
+ const STATE_LABELS = {
12176
+ working: "进行中",
12177
+ ready_for_review: "待验收",
12178
+ preview_active: "预览中",
12179
+ preview_detached: "待恢复",
12180
+ recovery_required: "需要恢复",
12181
+ finalized: "已完成",
12182
+ discarded: "已放弃"
12183
+ };
12184
+ function currentTasks(topology) {
12185
+ const byOwner = /* @__PURE__ */ new Map();
12186
+ for (const project of topology.projects) {
12187
+ if (!project?.project?.id || !project.project.name || !Array.isArray(project.tasks)) continue;
12188
+ for (const task of project.tasks) {
12189
+ if (!task?.checkoutId || !task.ownerSessionId || !task.sourceSessionId) continue;
12190
+ const projected = {
12191
+ ...task,
12192
+ projectId: project.project.id,
12193
+ projectName: project.project.name,
12194
+ label: STATE_LABELS[task.state] ?? "进行中"
12195
+ };
12196
+ const previous = byOwner.get(task.ownerSessionId);
12197
+ if (!previous || task.iteration > previous.iteration || task.iteration === previous.iteration && task.revision > previous.revision) byOwner.set(task.ownerSessionId, projected);
12198
+ }
12199
+ }
12200
+ return [...byOwner.values()];
12201
+ }
12202
+ /**
12203
+ * Merge one owner Session per Managed Checkout into the original Local project.
12204
+ * Ambiguous Workspace membership fails open: the official Workspace rows remain
12205
+ * visible rather than hiding a Session the projection cannot prove safe.
12206
+ */
12207
+ function projectManagedWorkspaceSidebar(input) {
12208
+ const tasks = currentTasks(input.topology);
12209
+ const protectedSessionIds = new Set(tasks.map((task) => task.ownerSessionId));
12210
+ const projected = input.workspaces.map((workspace) => ({
12211
+ ...workspace,
12212
+ sessionIds: [...workspace.sessionIds]
12213
+ }));
12214
+ const byWorkspace = new Map(projected.map((workspace) => [workspace.workspaceId, workspace]));
12215
+ const hiddenWorkspaceIds = /* @__PURE__ */ new Set();
12216
+ const protectedWorkspaceIds = /* @__PURE__ */ new Set();
12217
+ const suppressedSessionIds = /* @__PURE__ */ new Set();
12218
+ const managedBySessionId = {};
12219
+ for (const task of tasks) {
12220
+ const local = byWorkspace.get(task.projectId);
12221
+ const owner = input.sessions[task.ownerSessionId];
12222
+ if (!local || !owner) continue;
12223
+ const memberships = projected.filter((workspace) => workspace.sessionIds.includes(task.ownerSessionId));
12224
+ if (memberships.length === 0) {
12225
+ local.sessionIds = [...local.sessionIds, task.ownerSessionId];
12226
+ managedBySessionId[task.ownerSessionId] = task;
12227
+ continue;
12228
+ }
12229
+ if (memberships.length !== 1) continue;
12230
+ const managed = memberships[0];
12231
+ if (managed.workspaceId !== local.workspaceId) protectedWorkspaceIds.add(managed.workspaceId);
12232
+ if (managed.workspaceId === local.workspaceId) {
12233
+ managedBySessionId[task.ownerSessionId] = task;
12234
+ continue;
12235
+ }
12236
+ const extraMembers = managed.sessionIds.filter((sessionId) => input.sessions[sessionId] !== void 0).filter((sessionId) => sessionId !== task.ownerSessionId);
12237
+ if (extraMembers.some((sessionId) => input.sessions[sessionId]?.blank !== true)) continue;
12238
+ for (const sessionId of extraMembers) suppressedSessionIds.add(sessionId);
12239
+ if (!local.sessionIds.includes(task.ownerSessionId)) local.sessionIds = [...local.sessionIds, task.ownerSessionId];
12240
+ hiddenWorkspaceIds.add(managed.workspaceId);
12241
+ managedBySessionId[task.ownerSessionId] = task;
12242
+ }
12243
+ return {
12244
+ workspaces: projected.filter((workspace) => !hiddenWorkspaceIds.has(workspace.workspaceId)),
12245
+ managedBySessionId,
12246
+ protectedSessionIds,
12247
+ protectedWorkspaceIds,
12248
+ suppressedSessionIds
12249
+ };
12250
+ }
12251
+
12252
+ //#endregion
12253
+ //#region src/client/workspace-sidebar/index.tsx
12254
+ const EMPTY_TOPOLOGY = { projects: [] };
12255
+ /**
12256
+ * Preserve the complete official Workspace Browser and change only its data
12257
+ * projection plus mutations that are unsafe for one-to-one Managed owners.
12258
+ */
12259
+ function ManagedOfficialWorkspaceBrowser({ adapter, OfficialBrowser, ...officialProps }) {
12260
+ const props = officialProps;
12261
+ const workspaceState = props.useWorkspaces((state) => state);
12262
+ const sessionState = props.useSessions((state) => state);
12263
+ const [topology, setTopology] = (0, react.useState)(EMPTY_TOPOLOGY);
12264
+ const workspaceKey = workspaceState.items.map((workspace) => `${workspace.workspaceId}:${workspace.sessionIds.join(",")}`).join("|");
12265
+ (0, react.useEffect)(() => {
12266
+ let active = true;
12267
+ const refresh = () => {
12268
+ adapter.sidebarTopology().then((outcome) => {
12269
+ if (!active) return;
12270
+ setTopology(outcome.ok ? outcome.value : EMPTY_TOPOLOGY);
12271
+ }, () => {
12272
+ if (active) setTopology(EMPTY_TOPOLOGY);
12273
+ });
12274
+ };
12275
+ refresh();
12276
+ window.addEventListener(WORKTREE_REVIEW_REFRESH_EVENT, refresh);
12277
+ return () => {
12278
+ active = false;
12279
+ window.removeEventListener(WORKTREE_REVIEW_REFRESH_EVENT, refresh);
12280
+ };
12281
+ }, [
12282
+ adapter,
12283
+ sessionState.ids.join("|"),
12284
+ workspaceKey
12285
+ ]);
12286
+ const projection = (0, react.useMemo)(() => projectManagedWorkspaceSidebar({
12287
+ workspaces: workspaceState.items,
12288
+ sessions: sessionState.byId,
12289
+ topology
12290
+ }), [
12291
+ sessionState.byId,
12292
+ topology,
12293
+ workspaceState.items
12294
+ ]);
12295
+ const projectedWorkspaceState = (0, react.useMemo)(() => ({
12296
+ ...workspaceState,
12297
+ items: projection.workspaces.map((workspace) => projection.protectedWorkspaceIds.has(workspace.workspaceId) ? {
12298
+ ...workspace,
12299
+ __dshGitWorktreeProtected: true
12300
+ } : workspace)
12301
+ }), [
12302
+ projection.protectedWorkspaceIds,
12303
+ projection.workspaces,
12304
+ workspaceState
12305
+ ]);
12306
+ const projectedSessionState = (0, react.useMemo)(() => {
12307
+ const byId = { ...sessionState.byId };
12308
+ for (const sessionId of projection.suppressedSessionIds) delete byId[sessionId];
12309
+ for (const [sessionId, task] of Object.entries(projection.managedBySessionId)) {
12310
+ const summary = byId[sessionId];
12311
+ if (!summary || !task) continue;
12312
+ byId[sessionId] = {
12313
+ ...summary,
12314
+ __dshGitWorktree: {
12315
+ kind: "managed-worktree",
12316
+ state: task.state,
12317
+ label: task.label,
12318
+ originalTitle: summary.displayTitle
12319
+ }
12320
+ };
12321
+ }
12322
+ return {
12323
+ ...sessionState,
12324
+ ids: sessionState.ids.filter((sessionId) => !projection.suppressedSessionIds.has(sessionId)),
12325
+ byId
12326
+ };
12327
+ }, [
12328
+ projection.managedBySessionId,
12329
+ projection.suppressedSessionIds,
12330
+ sessionState
12331
+ ]);
12332
+ const useProjectedWorkspaces = ((selector) => selector(projectedWorkspaceState));
12333
+ const useProjectedSessions = ((selector) => selector(projectedSessionState));
12334
+ const protectedIds = projection.protectedSessionIds;
12335
+ const protectedWorkspaceIds = projection.protectedWorkspaceIds;
12336
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OfficialBrowser, {
12337
+ ...props,
12338
+ useWorkspaces: useProjectedWorkspaces,
12339
+ useSessions: useProjectedSessions,
12340
+ startSession: async (workspaceId) => {
12341
+ if (protectedWorkspaceIds.has(workspaceId)) return;
12342
+ await props.startSession(workspaceId);
12343
+ },
12344
+ forkSession: (sessionId) => {
12345
+ if (protectedIds.has(sessionId)) return;
12346
+ props.forkSession(sessionId);
12347
+ },
12348
+ insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
12349
+ if (protectedWorkspaceIds.has(workspaceId) || protectedIds.has(sessionId) || beforeSessionId !== void 0 && protectedIds.has(beforeSessionId)) return;
12350
+ await props.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
12351
+ }
12352
+ });
12353
+ }
12354
+ function officialContextProxy(ctx, adapter) {
12355
+ const proxySlots = new Proxy(ctx.slots, { get(target, key, receiver) {
12356
+ if (key === "register") return (descriptor$1, component) => {
12357
+ if (descriptor$1.name !== "sidebar.workspaces") return target.register(descriptor$1, component);
12358
+ const OfficialBrowser = component;
12359
+ const Browser = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ManagedOfficialWorkspaceBrowser, {
12360
+ ...props,
12361
+ adapter,
12362
+ OfficialBrowser
12363
+ });
12364
+ return target.register(descriptor$1, Browser);
12365
+ };
12366
+ return Reflect.get(target, key, receiver);
12367
+ } });
12368
+ return new Proxy(ctx, { get(target, key, receiver) {
12369
+ if (key === "slots") return proxySlots;
12370
+ return Reflect.get(target, key, receiver);
12371
+ } });
12372
+ }
12373
+ /**
12374
+ * Apply the version- and hash-gated official Workspace Client exactly once,
12375
+ * replacing only its Browser component while preserving its declaration tree,
12376
+ * locale, picker, stores, and directory-flow authorization.
12377
+ */
12378
+ function registerManagedWorkspaceSidebar(ctx, adapter) {
12379
+ apply$2(officialContextProxy(ctx, adapter));
12380
+ }
12381
+
9526
12382
  //#endregion
9527
12383
  //#region src/client/index.tsx
9528
12384
  const WORKTREE_CONSOLE_ADAPTER_SERVICE = "worktreeConsole";
@@ -9531,7 +12387,9 @@ const inject$1 = [
9531
12387
  "slots",
9532
12388
  "workspaces",
9533
12389
  "sessions",
9534
- "conversation"
12390
+ "conversation",
12391
+ "connection",
12392
+ "locale"
9535
12393
  ];
9536
12394
  function servicesOf(ctx) {
9537
12395
  return {
@@ -9573,6 +12431,7 @@ function apply$1(ctx, adapterOverride) {
9573
12431
  if (adapter !== void 0) {
9574
12432
  registerTargetConsole({ slots: ctx.slots }, adapter, services);
9575
12433
  registerPreSessionWorktree({ slots: ctx.slots }, adapter, services);
12434
+ registerManagedWorkspaceSidebar(ctx, adapter);
9576
12435
  }
9577
12436
  }
9578
12437
 
@@ -9625,6 +12484,7 @@ function createWorktreeConsoleRemoteAdapter(remote) {
9625
12484
  }
9626
12485
  }
9627
12486
  return {
12487
+ sidebarTopology: () => invoke("sidebarTopology", () => remote.sidebarTopology()),
9628
12488
  current: (request) => invoke("current", () => remote.current(request.sessionId)),
9629
12489
  list: (request) => invoke("list", () => remote.list(request.sessionId, request.needsAttention, request.includeDelivered)),
9630
12490
  create: (request) => invoke("create", () => remote.create(request.sourceSessionId)),