@dungsil/dsh-plugin-ui-worktree 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -3,6 +3,164 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  let react = require("react");
4
4
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
5
5
  let react_jsx_runtime = require("react/jsx-runtime");
6
+ // ../../tools/ui/src/client.ts
7
+ function useLocaleSnapshot(locale) {
8
+ const subscribe = (0, react.useCallback)((listener) => locale.subscribe(listener), [locale]);
9
+ const getSnapshot = (0, react.useCallback)(() => locale.getSnapshot(), [locale]);
10
+ return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
11
+ }
12
+ /** Each effect owns its sheet, even when another plugin installs the same defaults. */
13
+ function installStyles(css) {
14
+ const style = document.createElement("style");
15
+ style.textContent = css;
16
+ document.head.append(style);
17
+ return () => style.remove();
18
+ }
19
+ // ../../tools/ui/src/icons/git-branch.svg
20
+ var git_branch_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"6\" cy=\"5\" r=\"3\"/>\n <circle cx=\"6\" cy=\"19\" r=\"3\"/>\n <circle cx=\"18\" cy=\"6\" r=\"3\"/>\n <path d=\"M6 8v8M6 14h7a5 5 0 0 0 5-5\"/>\n</svg>\n";
21
+ // ../../tools/ui/src/composer.tsx
22
+ const composerStyles = `
23
+ :where(.dsh-plugin-composer-trigger) { display: flex; align-items: center; gap: 4px; min-width: 0; height: 28px; padding: 0 4px 0 8px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; line-height: 20px; cursor: pointer; }
24
+ :where(.dsh-plugin-composer-trigger):disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
25
+ .dsh-plugin-composer-icon, .dsh-plugin-composer-chevron { display: inline-flex; flex: none; }
26
+ .dsh-plugin-composer-label, .dsh-plugin-composer-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
27
+ .dsh-plugin-composer-chevron, .dsh-plugin-composer-detail { color: var(--dsw-alias-label-caption); }
28
+ .dsh-plugin-composer-trigger[aria-expanded=true] .dsh-plugin-composer-chevron { transform: rotate(180deg); }
29
+ `;
30
+ const ComposerTrigger = /* @__PURE__ */ (0, react.forwardRef)(function ComposerTrigger({ className = "", icon, label, detail, ...props }, ref) {
31
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
32
+ ...props,
33
+ ref,
34
+ type: "button",
35
+ className: `dsh-plugin-composer-trigger ${className}`,
36
+ children: [
37
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
38
+ className: "dsh-plugin-composer-icon",
39
+ "aria-hidden": "true",
40
+ children: icon
41
+ }),
42
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
43
+ className: "dsh-plugin-composer-label",
44
+ children: label
45
+ }),
46
+ detail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
47
+ className: "dsh-plugin-composer-detail",
48
+ children: detail
49
+ }),
50
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
51
+ className: "dsh-plugin-composer-chevron",
52
+ "aria-hidden": "true",
53
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
54
+ })
55
+ ]
56
+ });
57
+ });
58
+ // ../../tools/ui/src/settings.tsx
59
+ /** The rc.5 settings shell has no icon slot, so decorate only the matching navigation rows. */
60
+ function installSettingsIcon(id, labels, icon) {
61
+ const attribute = "data-dsh-settings-icon";
62
+ const selector = `[${attribute}="${id}"]`;
63
+ const removeStyles = installStyles(`
64
+ ${selector} > svg { display: none; }
65
+ ${typeof icon === "string" ? `${selector}::before { content: ''; display: block; flex: none; width: 16px; height: 16px; background: currentColor; mask: url("data:image/svg+xml,${encodeURIComponent(icon)}") center / contain no-repeat; }` : ""}
66
+ ${selector} > [data-dsh-settings-icon-glyph] { display: inline-flex; flex: none; }
67
+ `);
68
+ const marked = /* @__PURE__ */ new Map();
69
+ const refresh = () => {
70
+ const rows = new Set(document.querySelectorAll("[data-shortcut-modal=\"settings\"] nav button"));
71
+ for (const [row, dispose] of marked) if (!rows.has(row) || !labels.includes(row.textContent?.trim() ?? "")) {
72
+ row.removeAttribute(attribute);
73
+ dispose();
74
+ marked.delete(row);
75
+ }
76
+ for (const row of rows) if (!marked.has(row) && labels.includes(row.textContent?.trim() ?? "")) {
77
+ row.setAttribute(attribute, id);
78
+ let dispose = () => {};
79
+ if (typeof icon !== "string") {
80
+ const glyph = document.createElement("span");
81
+ glyph.setAttribute("data-dsh-settings-icon-glyph", "");
82
+ glyph.setAttribute("aria-hidden", "true");
83
+ row.prepend(glyph);
84
+ const unmount = icon(glyph);
85
+ dispose = () => {
86
+ unmount();
87
+ glyph.remove();
88
+ };
89
+ }
90
+ marked.set(row, dispose);
91
+ }
92
+ };
93
+ const observer = new MutationObserver(refresh);
94
+ observer.observe(document.body, {
95
+ childList: true,
96
+ subtree: true,
97
+ characterData: true
98
+ });
99
+ refresh();
100
+ return () => {
101
+ observer.disconnect();
102
+ for (const [row, dispose] of marked) {
103
+ row.removeAttribute(attribute);
104
+ dispose();
105
+ }
106
+ removeStyles();
107
+ };
108
+ }
109
+ const settingsStyles = `
110
+ .dsh-plugin-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 0; border-bottom: .5px solid var(--dsw-alias-border-l2); }
111
+ .dsh-plugin-settings-text { flex: 1; min-width: 0; }
112
+ .dsh-plugin-settings-picker { min-width: 0; }
113
+ .dsh-plugin-settings-selector { box-sizing: border-box; display: inline-flex; align-items: center; gap: 12px; max-width: 100%; height: 36px; padding: 0 14px; border: none; border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-module-platform); color: inherit; font: inherit; cursor: pointer; }
114
+ .dsh-plugin-settings-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
115
+ .dsh-plugin-settings-selector svg { flex: none; }
116
+ .dsh-plugin-settings-selector:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
117
+ .dsh-plugin-settings-selector:disabled { opacity: .5; cursor: default; }
118
+ .dsh-plugin-settings-selector:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary-new-colorprimary-new-color); outline-offset: 2px; }
119
+ `;
120
+ function SettingsRow({ label, children }) {
121
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
122
+ className: "dsh-plugin-settings-row",
123
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
124
+ className: "dsh-plugin-settings-text",
125
+ children: label
126
+ }), children]
127
+ });
128
+ }
129
+ function SettingsPicker({ className = "", value, selectedId, items, onSelect, disabled = false, ...buttonProps }) {
130
+ const [open, setOpen] = (0, react.useState)(false);
131
+ (0, react.useEffect)(() => {
132
+ if (disabled) setOpen(false);
133
+ }, [disabled]);
134
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
135
+ className: `dsh-plugin-settings-picker ${className}`,
136
+ open: open && !disabled,
137
+ onClose: () => setOpen(false),
138
+ items,
139
+ selectedId,
140
+ onSelect: (id) => {
141
+ setOpen(false);
142
+ if (!disabled) onSelect(id);
143
+ },
144
+ align: "end",
145
+ portal: true,
146
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
147
+ ...buttonProps,
148
+ type: "button",
149
+ className: "dsh-plugin-settings-selector",
150
+ "aria-haspopup": "menu",
151
+ "aria-expanded": open && !disabled,
152
+ disabled,
153
+ onClick: () => setOpen((current) => !current),
154
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
155
+ className: "dsh-plugin-settings-value",
156
+ children: value
157
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
158
+ size: 16,
159
+ "aria-hidden": "true"
160
+ })]
161
+ })
162
+ });
163
+ }
6
164
  // src/protocol.ts
7
165
  const channel = "/api";
8
166
  const endpointPrefix = "dungsil-worktree/";
@@ -17,7 +175,10 @@ function isWorktreeProgress(value) {
17
175
  ].includes(progress.status) && Number.isFinite(progress.updatedAt) && Array.isArray(progress.durations) && progress.durations.length === 4 && progress.durations.every((duration) => Number.isFinite(duration) && duration >= 0) && (progress.branch === void 0 || typeof progress.branch === "string");
18
176
  }
19
177
  function comparablePath(path) {
20
- return (/^[a-z]:[\\/]/i.test(path) || path.startsWith("\\\\") || path.startsWith("//") ? path.replaceAll("\\", "/").replace(/^[A-Z]:/, (drive) => drive.toLowerCase()) : path).replace(/\/+$/, "");
178
+ const value = /^[a-z]:[\\/]/i.test(path) || path.startsWith("\\\\") || path.startsWith("//") ? path.replaceAll("\\", "/").replace(/^[A-Z]:/, (drive) => drive.toLowerCase()) : path;
179
+ let end = value.length;
180
+ while (end > 0 && value[end - 1] === "/") end--;
181
+ return value.slice(0, end);
21
182
  }
22
183
  function currentWorktree(view) {
23
184
  if (!view) return void 0;
@@ -30,7 +191,7 @@ function currentWorktree(view) {
30
191
  function isWorktreeView(value) {
31
192
  if (!value || typeof value !== "object") return false;
32
193
  const view = value;
33
- return typeof view.cwd === "string" && (view.pendingCreation === void 0 || view.pendingCreation !== null && typeof view.pendingCreation.name === "string" && typeof view.pendingCreation.failed === "boolean") && Array.isArray(view.worktrees) && view.worktrees.every((item) => item && typeof item.path === "string" && (item.branch === null || typeof item.branch === "string"));
194
+ return typeof view.cwd === "string" && (view.needsInitialization === void 0 || typeof view.needsInitialization === "boolean") && (view.pendingCreation === void 0 || view.pendingCreation !== null && typeof view.pendingCreation.name === "string" && typeof view.pendingCreation.failed === "boolean") && Array.isArray(view.worktrees) && view.worktrees.every((item) => item && typeof item.path === "string" && (item.branch === null || typeof item.branch === "string"));
34
195
  }
35
196
  // src/errors.ts
36
197
  const failureCodes = [
@@ -69,6 +230,7 @@ const copy$1 = {
69
230
  detached: "分离的 HEAD",
70
231
  unknown: "无法获取分支",
71
232
  create: "新工作树",
233
+ primaryBranch: "当前分支",
72
234
  reservationHint: "发送下一条提示后,将先创建并切换到新工作树再开始任务。选择现有工作树可取消。",
73
235
  creationError: "无法确认创建结果,尚未开始任务。请刷新列表,选择工作树或重新预约,然后重新发送提示。",
74
236
  close: "关闭",
@@ -87,6 +249,7 @@ const copy$1 = {
87
249
  errorOperation: "请求",
88
250
  operations: {
89
251
  describe: "查询工作树",
252
+ initialize: "初始化工作树选择",
90
253
  create: "创建工作树",
91
254
  switch: "切换工作树"
92
255
  },
@@ -110,6 +273,7 @@ const copy$1 = {
110
273
  detached: "Detached HEAD",
111
274
  unknown: "Branch unavailable",
112
275
  create: "New worktree",
276
+ primaryBranch: "Current branch",
113
277
  reservationHint: "The next prompt will create and enter a new worktree before starting work. Select an existing worktree to cancel.",
114
278
  creationError: "Creation could not be confirmed, so work did not start. Refresh the list, select a worktree or schedule a new one, then resend your prompt.",
115
279
  close: "Close",
@@ -128,6 +292,7 @@ const copy$1 = {
128
292
  errorOperation: "Request",
129
293
  operations: {
130
294
  describe: "List worktrees",
295
+ initialize: "Initialize worktree selection",
131
296
  create: "Create worktree",
132
297
  switch: "Switch worktree"
133
298
  },
@@ -151,6 +316,7 @@ const copy$1 = {
151
316
  detached: "분리된 HEAD",
152
317
  unknown: "브랜치 확인 불가",
153
318
  create: "새 워크트리",
319
+ primaryBranch: "현재 브랜치",
154
320
  reservationHint: "다음 프롬프트를 전송하면 새 워크트리를 생성·전환한 뒤 작업을 시작합니다. 기존 워크트리를 선택하면 예약을 취소합니다.",
155
321
  creationError: "생성 결과를 확인하지 못해 작업을 시작하지 않았습니다. 목록을 새로고침하고 워크트리를 선택하거나 다시 예약한 뒤 프롬프트를 다시 전송하세요.",
156
322
  close: "닫기",
@@ -169,6 +335,7 @@ const copy$1 = {
169
335
  errorOperation: "요청",
170
336
  operations: {
171
337
  describe: "워크트리 조회",
338
+ initialize: "워크트리 선택 초기화",
172
339
  create: "워크트리 생성",
173
340
  switch: "워크트리 전환"
174
341
  },
@@ -192,8 +359,13 @@ const copy$1 = {
192
359
  const copy = {
193
360
  ko: {
194
361
  nav: "워크트리",
362
+ defaultSelection: "기본 선택",
363
+ current: "현재 체크아웃",
364
+ new: "새 워크트리",
195
365
  directory: "워크트리 풀 디렉터리",
196
366
  remote: "추적 중인 원격 브랜치 사용",
367
+ deleteOnArchive: "보관 시 워크트리 삭제",
368
+ deleteOnArchiveDescription: "이 플러그인이 만든 워크트리만 삭제합니다. 미커밋·미추적·무시된 파일이나 다른 세션이 있으면 유지하며, 브랜치는 삭제하지 않습니다.",
197
369
  naming: "자동 브랜치 이름 형식",
198
370
  semantic: "시멘틱 (feat/… 또는 fix/…)",
199
371
  dsh: "DSH (dsh/*)",
@@ -209,8 +381,13 @@ const copy = {
209
381
  },
210
382
  zh: {
211
383
  nav: "工作树",
384
+ defaultSelection: "默认选择",
385
+ current: "当前检出",
386
+ new: "新工作树",
212
387
  directory: "工作树池目录",
213
388
  remote: "使用跟踪的远程分支",
389
+ deleteOnArchive: "归档时删除工作树",
390
+ deleteOnArchiveDescription: "仅删除此插件创建的工作树。有未提交、未跟踪、被忽略的文件或其他会话使用时保留,不删除分支。",
214
391
  naming: "自动分支命名格式",
215
392
  semantic: "语义化(feat/… 或 fix/…)",
216
393
  dsh: "DSH(dsh/*)",
@@ -226,8 +403,13 @@ const copy = {
226
403
  },
227
404
  en: {
228
405
  nav: "Worktrees",
406
+ defaultSelection: "Default selection",
407
+ current: "Current checkout",
408
+ new: "New worktree",
229
409
  directory: "Worktree pool directory",
230
410
  remote: "Use tracked remote branch",
411
+ deleteOnArchive: "Delete worktree on archive",
412
+ deleteOnArchiveDescription: "Only removes worktrees created by this plugin. Keeps worktrees with uncommitted, untracked or ignored files, or other sessions. Branches are kept.",
231
413
  naming: "Automatic branch naming",
232
414
  semantic: "Semantic (feat/… or fix/…)",
233
415
  dsh: "DSH (dsh/*)",
@@ -249,14 +431,13 @@ const validPrefix = (value) => value.length <= 100 && (value === "" || /^[a-zA-Z
249
431
  const validSettings = (settings) => validDirectory(settings.directory) && validPrefix(settings.prefix) && (settings.naming !== "custom" || !!settings.prefix);
250
432
  const sameSettings = (left, right) => JSON.stringify(left) === JSON.stringify(right);
251
433
  function WorktreeSettingsSection({ ctx }) {
252
- const language = (0, react.useSyncExternalStore)((listener) => ctx.locale.subscribe(listener), () => ctx.locale.getSnapshot().active);
434
+ const { active: language } = useLocaleSnapshot(ctx.locale);
253
435
  const text = copy[languageKey(language)];
254
436
  const id = (0, react.useId)();
255
437
  const connection = ctx.get("connection");
256
438
  const [draft, setDraft] = (0, react.useState)();
257
439
  const [operation, setOperation] = (0, react.useState)("loading");
258
440
  const [notice, setNotice] = (0, react.useState)();
259
- const [namingOpen, setNamingOpen] = (0, react.useState)(false);
260
441
  const saved = (0, react.useRef)(void 0);
261
442
  const latest = (0, react.useRef)(void 0);
262
443
  const pending = (0, react.useRef)(false);
@@ -277,7 +458,6 @@ function WorktreeSettingsSection({ ctx }) {
277
458
  if (!controller || controller.signal.aborted || pending.current) return;
278
459
  pending.current = true;
279
460
  blocked.current = true;
280
- setNamingOpen(false);
281
461
  setOperation("loading");
282
462
  try {
283
463
  await read(controller);
@@ -327,7 +507,6 @@ function WorktreeSettingsSection({ ctx }) {
327
507
  } finally {
328
508
  if (!controller.signal.aborted) {
329
509
  if (blocked.current) {
330
- setNamingOpen(false);
331
510
  setOperation("loading");
332
511
  try {
333
512
  await read(controller);
@@ -383,19 +562,37 @@ function WorktreeSettingsSection({ ctx }) {
383
562
  draft && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
384
563
  disabled,
385
564
  children: [
386
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
387
- className: "dsh-worktree-settings-row",
388
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
389
- className: "dsh-worktree-settings-text",
390
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
391
- htmlFor: `${id}-directory`,
392
- children: text.directory
393
- }), invalidDirectory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
394
- id: `${id}-directory-error`,
395
- role: "alert",
396
- children: text.invalidDirectory
397
- })]
398
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
566
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
567
+ id: `${id}-default-selection`,
568
+ className: "dsh-worktree-settings-title",
569
+ children: text.defaultSelection
570
+ }),
571
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsPicker, {
572
+ className: "dsh-worktree-settings-picker",
573
+ disabled,
574
+ "aria-labelledby": `${id}-default-selection`,
575
+ value: text[draft.defaultSelection],
576
+ selectedId: draft.defaultSelection,
577
+ items: ["current", "new"].map((id) => ({
578
+ id,
579
+ label: text[id]
580
+ })),
581
+ onSelect: (defaultSelection) => {
582
+ change({ defaultSelection });
583
+ }
584
+ })
585
+ }),
586
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
587
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
588
+ htmlFor: `${id}-directory`,
589
+ children: text.directory
590
+ }), invalidDirectory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
591
+ id: `${id}-directory-error`,
592
+ role: "alert",
593
+ children: text.invalidDirectory
594
+ })] }),
595
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
399
596
  className: "dsh-worktree-settings-input",
400
597
  id: `${id}-directory`,
401
598
  type: "text",
@@ -407,36 +604,43 @@ function WorktreeSettingsSection({ ctx }) {
407
604
  "aria-invalid": invalidDirectory || void 0,
408
605
  "aria-describedby": invalidDirectory ? `${id}-directory-error` : void 0,
409
606
  onChange: (event) => change({ directory: event.target.value })
410
- })]
607
+ })
411
608
  }),
412
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
413
- className: "dsh-worktree-settings-row",
414
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
415
- className: "dsh-worktree-settings-text",
416
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
417
- className: "dsh-worktree-settings-title",
418
- children: text.remote
419
- })
420
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
610
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
611
+ className: "dsh-worktree-settings-title",
612
+ children: text.remote
613
+ }),
614
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
421
615
  label: text.remote,
422
616
  checked: draft.remote,
423
617
  disabled,
424
618
  onChange: (remote) => change({ remote })
425
- })]
619
+ })
426
620
  }),
427
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
428
- className: "dsh-worktree-settings-row",
429
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
430
- className: "dsh-worktree-settings-text",
431
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
432
- id: `${id}-naming`,
433
- className: "dsh-worktree-settings-title",
434
- children: text.naming
435
- })
436
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
437
- open: namingOpen,
438
- align: "end",
439
- portal: true,
621
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
622
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
623
+ className: "dsh-worktree-settings-title",
624
+ children: text.deleteOnArchive
625
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.deleteOnArchiveDescription })] }),
626
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
627
+ label: text.deleteOnArchive,
628
+ checked: draft.deleteOnArchive,
629
+ disabled,
630
+ onChange: (deleteOnArchive) => change({ deleteOnArchive })
631
+ })
632
+ }),
633
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
634
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
635
+ id: `${id}-naming`,
636
+ className: "dsh-worktree-settings-title",
637
+ children: text.naming
638
+ }),
639
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsPicker, {
640
+ className: "dsh-worktree-settings-picker",
641
+ disabled,
642
+ "aria-labelledby": `${id}-naming`,
643
+ value: text[draft.naming],
440
644
  selectedId: draft.naming,
441
645
  items: [
442
646
  "semantic",
@@ -446,39 +650,21 @@ function WorktreeSettingsSection({ ctx }) {
446
650
  id,
447
651
  label: text[id]
448
652
  })),
449
- onClose: () => setNamingOpen(false),
450
653
  onSelect: (naming) => {
451
- setNamingOpen(false);
452
- if (!disabled) change({ naming });
453
- },
454
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
455
- type: "button",
456
- className: "dsh-worktree-settings-selector",
457
- disabled,
458
- "aria-labelledby": `${id}-naming`,
459
- "aria-haspopup": "menu",
460
- "aria-expanded": namingOpen,
461
- onClick: () => setNamingOpen((open) => !open),
462
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text[draft.naming] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
463
- size: 16,
464
- "aria-hidden": "true"
465
- })]
466
- })
467
- })]
654
+ change({ naming });
655
+ }
656
+ })
468
657
  }),
469
- draft.naming === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
470
- className: "dsh-worktree-settings-row",
471
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
472
- className: "dsh-worktree-settings-text",
473
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
474
- htmlFor: `${id}-prefix`,
475
- children: text.prefix
476
- }), invalidPrefix && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
477
- id: `${id}-prefix-error`,
478
- role: "alert",
479
- children: text.invalidPrefix
480
- })]
481
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
658
+ draft.naming === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
659
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
660
+ htmlFor: `${id}-prefix`,
661
+ children: text.prefix
662
+ }), invalidPrefix && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
663
+ id: `${id}-prefix-error`,
664
+ role: "alert",
665
+ children: text.invalidPrefix
666
+ })] }),
667
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
482
668
  className: "dsh-worktree-settings-input",
483
669
  id: `${id}-prefix`,
484
670
  type: "text",
@@ -491,7 +677,7 @@ function WorktreeSettingsSection({ ctx }) {
491
677
  autoComplete: "off",
492
678
  spellCheck: false,
493
679
  onChange: (event) => change({ prefix: event.target.value })
494
- })]
680
+ })
495
681
  })
496
682
  ]
497
683
  }),
@@ -508,30 +694,28 @@ function WorktreeSettingsSection({ ctx }) {
508
694
  });
509
695
  }
510
696
  // src/styles.ts
511
- const styles = `
697
+ const styles = `${settingsStyles}${composerStyles}
512
698
  .dsh-worktree-sidebar-name { flex: 0 1 auto; min-width: 0; max-width: 88px; margin-inline: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-caption); font-size: 10px; line-height: 16px; }
699
+ [data-row-key]:has(.dsh-vcs-session-request) > .dsh-worktree-sidebar-name { display: none; }
513
700
  .dsh-worktree { display: inline-flex; flex-wrap: wrap; align-items: center; min-width: 0; max-width: 100%; color: var(--dsw-alias-label-secondary); font: inherit; }
514
- .dsh-worktree-controls { display: inline-flex; flex-wrap: nowrap; align-items: center; min-width: 0; max-width: 100%; }
515
701
  .dsh-worktree-picker { display: inline-flex; min-width: 0; max-width: 100%; }
516
- .dsh-worktree-trigger { display: inline-flex; align-items: center; gap: 4px; min-width: 0; max-width: 220px; height: 28px; padding: 0 4px 0 8px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; font-weight: 500; line-height: 20px; cursor: pointer; }
517
- .dsh-worktree-icon, .dsh-worktree-chevron { display: inline-flex; flex: none; }
518
- .dsh-worktree-icon svg { width: 14px; height: 14px; }
519
- .dsh-worktree-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
520
- .dsh-worktree-chevron { color: var(--dsw-alias-label-caption); }
521
- .dsh-worktree-trigger[aria-expanded=true] .dsh-worktree-chevron { transform: rotate(180deg); }
522
- .dsh-worktree-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
702
+ .dsh-worktree-pinned { position: sticky; top: 0; z-index: 1; background: var(--dsw-alias-bg-layer-1); }
703
+ .dsh-worktree-option { display: flex; align-items: center; gap: 8px; min-width: 0; }
704
+ .dsh-worktree-option > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
705
+ .dsh-worktree-option > svg { flex: none; width: 14px; height: 14px; margin-inline-start: auto; color: var(--dsw-alias-label-primary); }
706
+ .dsh-worktree-badge { flex: none; padding: 1px 5px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm); color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 16px; font-weight: 400; }
707
+ .dsh-worktree-trigger { display: inline-flex; max-width: 220px; font-weight: 500; }
708
+ .dsh-worktree-trigger .dsh-plugin-composer-icon svg { width: 14px; height: 14px; }
523
709
  .dsh-worktree-trigger:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 0; }
524
710
  @container (width <= 460px) {
525
- .dsh-worktree-name { display: none; }
711
+ .dsh-worktree-trigger .dsh-plugin-composer-label { display: none; }
526
712
  }
527
713
  @media (prefers-reduced-motion: no-preference) {
528
- .dsh-worktree-chevron { transition: transform .12s; }
714
+ .dsh-worktree-trigger .dsh-plugin-composer-chevron { transition: transform .12s; }
529
715
  }
530
716
  @media (hover: hover) {
531
- .dsh-worktree-trigger:hover:not(:disabled), .dsh-worktree-error-trigger:hover, .dsh-worktree-dialog button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
717
+ .dsh-worktree-trigger:hover:not(:disabled), .dsh-worktree-dialog button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
532
718
  }
533
- .dsh-worktree-error-trigger { flex: 0 0 auto; min-height: 28px; padding: 2px 6px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 12px; line-height: 20px; white-space: nowrap; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
534
- .dsh-worktree-error-trigger:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 0; }
535
719
  .dsh-worktree-dialog.dsh-worktree-error-dialog { inset: 0; margin: auto; }
536
720
  .dsh-worktree-error-dialog dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px 12px; }
537
721
  .dsh-worktree-error-dialog dt { color: var(--dsw-alias-label-secondary); }
@@ -547,40 +731,35 @@ const styles = `
547
731
  .dsh-worktree-dialog button:disabled { opacity: .45; cursor: not-allowed; }
548
732
  .dsh-worktree-dialog button:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
549
733
  .dsh-worktree-sr { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
550
- .dsh-worktree-progress { margin: 12px 0 20px; color: var(--dsw-alias-label-secondary); font: 13px/1.6 ui-monospace, monospace; }
734
+ .dsh-worktree-progress { margin: 12px 0 20px; color: var(--dsw-alias-label-secondary); font: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px)); }
551
735
  .dsh-worktree-progress > p { margin: 0 0 8px; overflow-wrap: anywhere; }
552
736
  .dsh-worktree-progress ol { list-style: none; margin: 0; padding: 8px 0 0; border-top: 1px solid var(--dsw-alias-border-l2); }
553
737
  .dsh-worktree-progress li { display: flex; align-items: center; gap: 10px; padding: 3px 4px; }
554
738
  .dsh-worktree-progress li[data-state=pending] { opacity: .4; }
555
- .dsh-worktree-progress-mark { box-sizing: border-box; display: inline-flex; flex: none; align-items: center; justify-content: center; width: 14px; height: 14px; border: 1px solid currentColor; border-radius: 50%; font-size: 11px; line-height: 1; }
556
- .dsh-worktree-progress li[data-state=running] .dsh-worktree-progress-mark { border-right-color: transparent; }
739
+ .dsh-worktree-progress-mark { display: inline-flex; flex: none; align-items: center; justify-content: center; width: calc(16px + var(--dsh-content-font-delta, 0px)); height: calc(16px + var(--dsh-content-font-delta, 0px)); }
740
+ .dsh-worktree-progress-mark svg { width: calc(14px + var(--dsh-content-font-delta, 0px)); height: calc(14px + var(--dsh-content-font-delta, 0px)); }
557
741
  .dsh-worktree-progress li[data-state=failed], .dsh-worktree-progress [role=alert] { color: var(--dsw-alias-state-error-primary); }
558
742
  .dsh-worktree-progress [role=alert] { margin-top: 10px; font: inherit; }
559
- .dsh-worktree-progress-time { margin-inline-start: auto; padding-inline-start: 8px; font-size: 11px; }
743
+ .dsh-worktree-progress-time { margin-inline-start: auto; padding-inline-start: 8px; font-size: calc(var(--dsh-content-font-size-secondary, 13px) - 1px); font-variant-numeric: tabular-nums; }
560
744
  @keyframes dsh-worktree-spin { to { transform: rotate(360deg); } }
561
745
  @media (prefers-reduced-motion: no-preference) {
562
746
  .dsh-worktree-progress li[data-state=running] .dsh-worktree-progress-mark { animation: dsh-worktree-spin .8s linear infinite; }
563
747
  }
564
748
  .dsh-worktree-settings { width: 100%; color: var(--dsw-alias-label-primary); font: inherit; }
565
749
  .dsh-worktree-settings fieldset { min-width: 0; padding: 0; margin: 0; border: 0; }
566
- .dsh-worktree-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 16px 0; border-bottom: .5px solid var(--dsw-alias-border-l2); }
567
- .dsh-worktree-settings-row:last-child { border-bottom: 0; }
568
- .dsh-worktree-settings-text { flex: 1; min-width: 0; }
750
+ .dsh-worktree-settings .dsh-plugin-settings-row { gap: 24px; }
751
+ .dsh-worktree-settings .dsh-plugin-settings-row:last-child { border-bottom: 0; }
569
752
  .dsh-worktree-settings label, .dsh-worktree-settings-title { display: block; font-size: 14px; font-weight: 400; line-height: 20px; }
570
- .dsh-worktree-settings-text p { margin: 4px 0 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
753
+ .dsh-worktree-settings .dsh-plugin-settings-text p { margin: 4px 0 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
571
754
  .dsh-worktree-settings-input { box-sizing: border-box; width: 220px; max-width: 100%; flex: 0 1 220px; min-width: 0; }
572
- .dsh-worktree-settings-selector { display: inline-flex; align-items: center; gap: 12px; max-width: 220px; height: 36px; padding: 0 14px; border: 0; border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-primary); font: inherit; font-size: 14px; line-height: 22px; cursor: pointer; }
573
- .dsh-worktree-settings-selector span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
574
- .dsh-worktree-settings-selector svg { flex: none; }
575
- .dsh-worktree-settings-selector:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
576
- .dsh-worktree-settings-selector:disabled { opacity: .5; cursor: default; }
577
- .dsh-worktree-settings-selector:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
755
+ .dsh-worktree-settings-picker .dsh-plugin-settings-selector { max-width: 220px; color: var(--dsw-alias-label-primary); font-size: 14px; line-height: 22px; }
756
+ .dsh-worktree-settings-picker .dsh-plugin-settings-selector:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
578
757
  .dsh-worktree-settings [role=status], .dsh-worktree-settings [role=alert] { margin: 12px 0; font-size: 12px; line-height: 18px; }
579
758
  .dsh-worktree-settings [role=status]:empty { margin: 0; }
580
759
  .dsh-worktree-settings [role=alert] { color: var(--dsw-alias-state-error-primary); }
581
760
  @media (max-width: 600px) {
582
- .dsh-worktree-settings-row { flex-wrap: wrap; gap: 12px; }
583
- .dsh-worktree-settings-text { flex-basis: 160px; }
761
+ .dsh-worktree-settings .dsh-plugin-settings-row { flex-wrap: wrap; gap: 12px; }
762
+ .dsh-worktree-settings .dsh-plugin-settings-text { flex-basis: 160px; }
584
763
  .dsh-worktree-settings-input { flex-grow: 1; }
585
764
  }
586
765
  `;
@@ -699,7 +878,7 @@ const progressDefinition = {
699
878
  anchorSeq: context.state.anchorSeq,
700
879
  location: context.start.location,
701
880
  visibility: "visible",
702
- groupTree: "turn",
881
+ groupTree: context.state.progress.status === "completed" ? void 0 : "turn",
703
882
  data: context.state.progress
704
883
  };
705
884
  }
@@ -711,7 +890,7 @@ function installProgress(ctx) {
711
890
  key: "dungsil-worktree-progress",
712
891
  inject: () => ({})
713
892
  }, function Progress({ node }) {
714
- const language = (0, react.useSyncExternalStore)((listener) => ctx.locale.subscribe(listener), () => ctx.locale.getSnapshot().active);
893
+ const { active: language } = useLocaleSnapshot(ctx.locale);
715
894
  const text = labels[language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en"];
716
895
  const progress = node.data;
717
896
  const ended = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn.status === "closed" : false;
@@ -741,7 +920,7 @@ function installProgress(ctx) {
741
920
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
742
921
  className: "dsh-worktree-progress-mark",
743
922
  "aria-hidden": "true",
744
- children: state === "completed" ? "✓" : state === "failed" ? "×" : state === "cancelled" ? "−" : ""
923
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(state === "completed" ? _deepseek_ai_dsh_client_ui_primitives.IconCheckCircleOutlineRegular : state === "failed" ? _deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular : state === "cancelled" ? _deepseek_ai_dsh_client_ui_primitives.IconStopFillRegular : state === "running" ? _deepseek_ai_dsh_client_ui_primitives.IconLoadingOutlineRegular : _deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, { size: 14 })
745
924
  }),
746
925
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
747
926
  state !== "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -768,15 +947,26 @@ const inject = [
768
947
  "uiConversation"
769
948
  ];
770
949
  function apply(ctx) {
950
+ ctx.effect(() => installSettingsIcon(name, [
951
+ "en",
952
+ "ko",
953
+ "zh"
954
+ ].map(settingsLabel), git_branch_default));
771
955
  const connection = ctx.get("connection");
772
- const subscribeLocale = (listener) => ctx.locale.subscribe(listener);
773
956
  installProgress(ctx);
774
957
  function Picker({ sessionId, useSession }) {
775
- const language = (0, react.useSyncExternalStore)(subscribeLocale, () => ctx.locale.getSnapshot().active);
958
+ const { active: language } = useLocaleSnapshot(ctx.locale);
776
959
  const text = copy$1[language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en"];
777
960
  const unavailable = useSession((s) => s.running || s.removed || s.openState !== "open" || s.pendingSubmissions.length > 0);
778
- const running = useSession((s) => s.running);
779
- const wasRunning = (0, react.useRef)(running);
961
+ const unavailableRef = (0, react.useRef)(unavailable);
962
+ unavailableRef.current = unavailable;
963
+ const unreadable = useSession((s) => s.removed || s.openState !== "open");
964
+ const unreadableRef = (0, react.useRef)(unreadable);
965
+ unreadableRef.current = unreadable;
966
+ const previousReadiness = (0, react.useRef)({
967
+ unreadable: true,
968
+ unavailable
969
+ });
780
970
  const [view, setView] = (0, react.useState)();
781
971
  const [loading, setLoading] = (0, react.useState)(true);
782
972
  const [busy, setBusy] = (0, react.useState)(false);
@@ -784,11 +974,11 @@ function apply(ctx) {
784
974
  const [changed, setChanged] = (0, react.useState)(false);
785
975
  const [open, setOpen] = (0, react.useState)(false);
786
976
  const pending = (0, react.useRef)(false);
977
+ const reloadRequested = (0, react.useRef)(false);
787
978
  const needsRefresh = (0, react.useRef)(false);
788
979
  const lifetime = (0, react.useRef)(null);
789
980
  const trigger = (0, react.useRef)(null);
790
981
  const errorDialog = (0, react.useRef)(null);
791
- const errorTrigger = (0, react.useRef)(null);
792
982
  const errorTitle = (0, react.useId)();
793
983
  const errorDescription = (0, react.useId)();
794
984
  const status = (0, react.useId)();
@@ -823,21 +1013,40 @@ function apply(ctx) {
823
1013
  };
824
1014
  const load = async (explicit = false) => {
825
1015
  const controller = lifetime.current;
826
- if (!controller || controller.signal.aborted || pending.current || needsRefresh.current && !explicit) return;
1016
+ if (!controller || controller.signal.aborted || unreadableRef.current || needsRefresh.current && !explicit) return;
1017
+ if (pending.current) {
1018
+ reloadRequested.current = true;
1019
+ return;
1020
+ }
1021
+ reloadRequested.current = false;
827
1022
  pending.current = true;
828
1023
  setLoading(true);
1024
+ let operation = "describe";
829
1025
  try {
830
1026
  const view = await request("describe", { sessionId }, controller.signal);
831
1027
  if (controller.signal.aborted) return;
832
1028
  setView(view);
1029
+ if (view.needsInitialization && !unavailableRef.current) {
1030
+ operation = "initialize";
1031
+ const initialized = await request("initialize", {
1032
+ sessionId,
1033
+ cwd: view.cwd
1034
+ }, controller.signal);
1035
+ if (controller.signal.aborted) return;
1036
+ setView(initialized);
1037
+ }
833
1038
  needsRefresh.current = false;
834
1039
  setError(void 0);
835
1040
  } catch (cause) {
836
- if (!controller.signal.aborted) recordFailure("describe", cause);
1041
+ if (!controller.signal.aborted && failureCode(cause) !== "busy") recordFailure(operation, cause);
837
1042
  } finally {
838
1043
  if (!controller.signal.aborted) {
839
1044
  pending.current = false;
840
1045
  setLoading(false);
1046
+ if (reloadRequested.current) {
1047
+ reloadRequested.current = false;
1048
+ load();
1049
+ }
841
1050
  }
842
1051
  }
843
1052
  };
@@ -845,7 +1054,6 @@ function apply(ctx) {
845
1054
  const controller = new AbortController();
846
1055
  lifetime.current = controller;
847
1056
  pending.current = false;
848
- load();
849
1057
  const focus = () => {
850
1058
  load();
851
1059
  };
@@ -861,9 +1069,13 @@ function apply(ctx) {
861
1069
  };
862
1070
  }, []);
863
1071
  (0, react.useEffect)(() => {
864
- if (wasRunning.current && !running) load();
865
- wasRunning.current = running;
866
- }, [running]);
1072
+ const previous = previousReadiness.current;
1073
+ previousReadiness.current = {
1074
+ unreadable,
1075
+ unavailable
1076
+ };
1077
+ if (!unreadable && (previous.unreadable || previous.unavailable && !unavailable)) load();
1078
+ }, [unreadable, unavailable]);
867
1079
  (0, react.useEffect)(() => {
868
1080
  if (disabled) setOpen(false);
869
1081
  }, [disabled]);
@@ -904,7 +1116,7 @@ function apply(ctx) {
904
1116
  if (!detailError && errorDialog.current?.open) errorDialog.current.close();
905
1117
  }, [error, creation?.failed]);
906
1118
  const restoreErrorFocus = () => {
907
- (errorTrigger.current ?? trigger.current)?.focus();
1119
+ trigger.current?.focus();
908
1120
  };
909
1121
  const current = currentWorktree(view);
910
1122
  (0, react.useEffect)(() => {
@@ -916,79 +1128,86 @@ function apply(ctx) {
916
1128
  view
917
1129
  ]);
918
1130
  const branch = creation ? creation.failed ? text.mutationError : text.create : current ? current.branch ?? text.detached : text.unknown;
1131
+ const label = detailError ? errorLabel : view ? branch : loading ? text.loading : text.title;
919
1132
  const notice = loading ? text.loading : busy ? text.busy : error ? errorLabel : unavailable ? text.wait : creation ? creation.failed ? text.creationError : text.reservationHint : changed ? text.changed : view?.worktrees.length === 0 ? text.empty : "";
920
1133
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
921
1134
  className: "dsh-worktree",
922
1135
  children: [
923
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
924
- className: "dsh-worktree-controls",
925
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
926
- className: "dsh-worktree-picker",
927
- open,
928
- side: "top",
929
- portal: true,
930
- selectedId: creation ? "create" : current ? `path:${current.path}` : void 0,
931
- items: [{
932
- id: "create",
933
- label: text.create,
934
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, {})
935
- }, ...view?.worktrees.map((item) => ({
936
- id: `path:${item.path}`,
937
- label: item.branch ?? text.detached,
938
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {})
939
- })) ?? []],
940
- onClose: () => setOpen(false),
941
- onSelect: (id) => {
942
- setOpen(false);
943
- if (disabled || pending.current) return;
944
- if (id === "create") {
945
- if (!creation || creation.failed) change("create");
946
- } else if (id.startsWith("path:") && (creation || id !== `path:${current?.path}`)) change("switch", id.slice(5));
1136
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1137
+ className: "dsh-worktree-picker",
1138
+ open,
1139
+ side: "top",
1140
+ portal: true,
1141
+ listClassName: "dsh-worktree-menu",
1142
+ onClose: () => setOpen(false),
1143
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerTrigger, {
1144
+ ref: trigger,
1145
+ className: "dsh-worktree-trigger",
1146
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
1147
+ label,
1148
+ disabled: detailError ? loading || busy : disabled,
1149
+ "aria-label": `${text.title}: ${label}`,
1150
+ "aria-haspopup": detailError ? "dialog" : "menu",
1151
+ "aria-expanded": detailError ? void 0 : open,
1152
+ "aria-describedby": status,
1153
+ "aria-busy": loading || busy,
1154
+ title: label,
1155
+ onClick: () => {
1156
+ if (detailError) errorDialog.current?.showModal();
1157
+ else setOpen((value) => !value);
947
1158
  },
948
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
949
- ref: trigger,
950
- type: "button",
951
- className: "dsh-worktree-trigger",
952
- disabled,
953
- "aria-label": view ? `${text.title}: ${branch}` : text.title,
954
- "aria-haspopup": "menu",
955
- "aria-expanded": open,
956
- "aria-describedby": status,
957
- "aria-busy": loading || busy,
958
- title: view ? branch : text.title,
959
- onClick: () => setOpen((value) => !value),
960
- onKeyDown: (event) => {
961
- if (!open && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
962
- event.preventDefault();
963
- setOpen(true);
964
- }
1159
+ onKeyDown: (event) => {
1160
+ if (!detailError && !disabled && !open && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
1161
+ event.preventDefault();
1162
+ setOpen(true);
1163
+ }
1164
+ }
1165
+ }),
1166
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1167
+ className: "dsh-worktree-pinned",
1168
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
1169
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, {}),
1170
+ current: !!creation,
1171
+ onSelect: () => {
1172
+ setOpen(false);
1173
+ if (!creation || creation.failed) change("create");
1174
+ },
1175
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1176
+ className: "dsh-worktree-option",
1177
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text.create }), creation && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {})]
1178
+ })
1179
+ }), view?.worktrees.slice(0, 1).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
1180
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
1181
+ current: !creation && current === item,
1182
+ onSelect: () => {
1183
+ setOpen(false);
1184
+ if (creation || current !== item) change("switch", item.path);
965
1185
  },
966
- children: [
967
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
968
- className: "dsh-worktree-icon",
969
- "aria-hidden": "true",
970
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, { className: "dsh-worktree-branch" })
971
- }),
972
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
973
- className: "dsh-worktree-name",
974
- children: view ? branch : loading ? text.loading : text.title
975
- }),
976
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
977
- className: "dsh-worktree-chevron",
978
- "aria-hidden": "true",
979
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
980
- })
981
- ]
1186
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1187
+ className: "dsh-worktree-option",
1188
+ children: [
1189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.branch ?? text.detached }),
1190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1191
+ className: "dsh-worktree-badge",
1192
+ children: text.primaryBranch
1193
+ }),
1194
+ !creation && current === item && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {})
1195
+ ]
1196
+ })
1197
+ }, item.path))]
1198
+ }), view?.worktrees.slice(1).map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
1199
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
1200
+ current: !creation && current === item,
1201
+ separatorBefore: index === 0,
1202
+ onSelect: () => {
1203
+ setOpen(false);
1204
+ if (creation || current !== item) change("switch", item.path);
1205
+ },
1206
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1207
+ className: "dsh-worktree-option",
1208
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.branch ?? text.detached }), !creation && current === item && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {})]
982
1209
  })
983
- }), detailError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
984
- ref: errorTrigger,
985
- type: "button",
986
- className: "dsh-worktree-error-trigger",
987
- "aria-haspopup": "dialog",
988
- "aria-label": `${errorLabel}: ${text.errorTitle}`,
989
- onClick: () => errorDialog.current?.showModal(),
990
- children: errorLabel
991
- })]
1210
+ }, item.path))]
992
1211
  }),
993
1212
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
994
1213
  id: status,
@@ -1046,12 +1265,7 @@ function apply(ctx) {
1046
1265
  label: () => settingsLabel(ctx.locale.getSnapshot().active),
1047
1266
  inject: () => ({ ctx })
1048
1267
  }, WorktreeSettingsSection));
1049
- ctx.effect(() => {
1050
- const style = document.createElement("style");
1051
- style.textContent = styles;
1052
- document.head.append(style);
1053
- return () => style.remove();
1054
- }, "ui-worktree: composer styles");
1268
+ ctx.effect(() => installStyles(styles), "ui-worktree: composer styles");
1055
1269
  }
1056
1270
  exports.apply = apply;
1057
1271
  exports.inject = inject;