@dungsil/dsh-plugin-ui-worktree 0.2.0 → 0.4.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,9 +3,160 @@ 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 = (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, svg) {
61
+ const attribute = "data-dsh-settings-icon";
62
+ const selector = `[${attribute}="${id}"]`;
63
+ const removeStyles = installStyles(`
64
+ ${selector} > svg { display: none; }
65
+ ${selector}::before { content: ''; display: block; flex: none; width: 16px; height: 16px; background: currentColor; mask: url("data:image/svg+xml,${encodeURIComponent(svg)}") center / contain no-repeat; }
66
+ `);
67
+ const marked = /* @__PURE__ */ new Set();
68
+ const refresh = () => {
69
+ const rows = new Set(document.querySelectorAll("[data-shortcut-modal=\"settings\"] nav button"));
70
+ for (const row of marked) if (!rows.has(row) || !labels.includes(row.textContent?.trim() ?? "")) {
71
+ row.removeAttribute(attribute);
72
+ marked.delete(row);
73
+ }
74
+ for (const row of rows) if (labels.includes(row.textContent?.trim() ?? "")) {
75
+ row.setAttribute(attribute, id);
76
+ marked.add(row);
77
+ }
78
+ };
79
+ const observer = new MutationObserver(refresh);
80
+ observer.observe(document.body, {
81
+ childList: true,
82
+ subtree: true,
83
+ characterData: true
84
+ });
85
+ refresh();
86
+ return () => {
87
+ observer.disconnect();
88
+ for (const row of marked) row.removeAttribute(attribute);
89
+ removeStyles();
90
+ };
91
+ }
92
+ const settingsStyles = `
93
+ .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); }
94
+ .dsh-plugin-settings-text { flex: 1; min-width: 0; }
95
+ .dsh-plugin-settings-picker { min-width: 0; }
96
+ .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; }
97
+ .dsh-plugin-settings-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
98
+ .dsh-plugin-settings-selector svg { flex: none; }
99
+ .dsh-plugin-settings-selector:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
100
+ .dsh-plugin-settings-selector:disabled { opacity: .5; cursor: default; }
101
+ .dsh-plugin-settings-selector:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary-new-colorprimary-new-color); outline-offset: 2px; }
102
+ `;
103
+ function SettingsRow({ label, children }) {
104
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
105
+ className: "dsh-plugin-settings-row",
106
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
107
+ className: "dsh-plugin-settings-text",
108
+ children: label
109
+ }), children]
110
+ });
111
+ }
112
+ function SettingsPicker({ className = "", value, selectedId, items, onSelect, disabled = false, ...buttonProps }) {
113
+ const [open, setOpen] = (0, react.useState)(false);
114
+ (0, react.useEffect)(() => {
115
+ if (disabled) setOpen(false);
116
+ }, [disabled]);
117
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
118
+ className: `dsh-plugin-settings-picker ${className}`,
119
+ open: open && !disabled,
120
+ onClose: () => setOpen(false),
121
+ items,
122
+ selectedId,
123
+ onSelect: (id) => {
124
+ setOpen(false);
125
+ if (!disabled) onSelect(id);
126
+ },
127
+ align: "end",
128
+ portal: true,
129
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
130
+ ...buttonProps,
131
+ type: "button",
132
+ className: "dsh-plugin-settings-selector",
133
+ "aria-haspopup": "menu",
134
+ "aria-expanded": open && !disabled,
135
+ disabled,
136
+ onClick: () => setOpen((current) => !current),
137
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
138
+ className: "dsh-plugin-settings-value",
139
+ children: value
140
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
141
+ size: 16,
142
+ "aria-hidden": "true"
143
+ })]
144
+ })
145
+ });
146
+ }
6
147
  // src/protocol.ts
7
148
  const channel = "/api";
8
149
  const endpointPrefix = "dungsil-worktree/";
150
+ function isWorktreeProgress(value) {
151
+ if (!value || typeof value !== "object") return false;
152
+ const progress = value;
153
+ return typeof progress.id === "string" && Number.isInteger(progress.step) && progress.step >= 0 && progress.step < 4 && [
154
+ "running",
155
+ "completed",
156
+ "failed",
157
+ "cancelled"
158
+ ].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");
159
+ }
9
160
  function comparablePath(path) {
10
161
  return (/^[a-z]:[\\/]/i.test(path) || path.startsWith("\\\\") || path.startsWith("//") ? path.replaceAll("\\", "/").replace(/^[A-Z]:/, (drive) => drive.toLowerCase()) : path).replace(/\/+$/, "");
11
162
  }
@@ -20,7 +171,7 @@ function currentWorktree(view) {
20
171
  function isWorktreeView(value) {
21
172
  if (!value || typeof value !== "object") return false;
22
173
  const view = value;
23
- return typeof view.cwd === "string" && Array.isArray(view.worktrees) && view.worktrees.every((item) => item && typeof item.path === "string" && (item.branch === null || typeof item.branch === "string"));
174
+ 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"));
24
175
  }
25
176
  // src/errors.ts
26
177
  const failureCodes = [
@@ -54,16 +205,55 @@ function failureCode(error) {
54
205
  }
55
206
  // src/requests.ts
56
207
  const copy$1 = {
208
+ zh: {
209
+ title: "Git 工作树",
210
+ detached: "分离的 HEAD",
211
+ unknown: "无法获取分支",
212
+ create: "新工作树",
213
+ reservationHint: "发送下一条提示后,将先创建并切换到新工作树再开始任务。选择现有工作树可取消。",
214
+ creationError: "无法确认创建结果,尚未开始任务。请刷新列表,选择工作树或重新预约,然后重新发送提示。",
215
+ close: "关闭",
216
+ refresh: "刷新工作树",
217
+ loading: "正在加载工作树…",
218
+ busy: "正在切换工作树…",
219
+ changed: "已切换工作树。",
220
+ empty: "未找到 Git 工作树。",
221
+ wait: "请等待会话准备就绪且当前任务结束。",
222
+ error: "无法确认当前状态。请刷新工作树以检查状态后再重试。",
223
+ lookupError: "查询失败",
224
+ mutationError: "需确认工作树状态",
225
+ errorTitle: "工作树错误详情",
226
+ readError: "无法加载工作树列表。请重新查询以确认当前分支。",
227
+ errorCode: "错误代码",
228
+ errorOperation: "请求",
229
+ operations: {
230
+ describe: "查询工作树",
231
+ create: "创建工作树",
232
+ switch: "切换工作树"
233
+ },
234
+ reasons: {
235
+ busy: "会话中有正在执行的任务。请在任务结束后重新查询。",
236
+ conflict: "当前工作目录已更改。请刷新列表后再进行更改。",
237
+ invalid: "请求无效。请刷新列表并检查分支名称或工作树。",
238
+ operation_failed: "主机无法完成操作。请检查 Git 和工作树配置。",
239
+ session_not_found: "会话不可用。请重新打开会话后重试。",
240
+ forbidden: "此操作需要所有者连接。请以所有者身份重新打开应用。",
241
+ auth_required: "连接需要身份验证。请重新打开应用后重试。",
242
+ endpoint_missing: "主机没有工作树接口。请确认插件已启用并重启应用。",
243
+ client_unavailable: "连接缺少工作树 RPC 客户端。请刷新页面或更新应用。",
244
+ invalid_response: "主机返回的工作树列表无效。请检查应用和插件版本。",
245
+ connection_error: "收到有效响应前连接失败。请检查连接后重新查询。",
246
+ timeout: "请求超时。请在当前任务结束后重新查询。"
247
+ }
248
+ },
57
249
  en: {
58
250
  title: "Git worktree",
59
251
  detached: "Detached HEAD",
60
252
  unknown: "Branch unavailable",
61
- create: "New worktree…",
62
- createTitle: "Create worktree",
63
- branch: "New branch name (optional)",
64
- hint: "Leave the name empty for model naming. Uncommitted changes stay in the source checkout.",
253
+ create: "New worktree",
254
+ reservationHint: "The next prompt will create and enter a new worktree before starting work. Select an existing worktree to cancel.",
255
+ 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.",
65
256
  close: "Close",
66
- submit: "Create",
67
257
  refresh: "Refresh worktrees",
68
258
  loading: "Loading worktrees…",
69
259
  busy: "Changing worktree…",
@@ -101,12 +291,10 @@ const copy$1 = {
101
291
  title: "Git worktree",
102
292
  detached: "분리된 HEAD",
103
293
  unknown: "브랜치 확인 불가",
104
- create: "신규 워크트리…",
105
- createTitle: "워크트리 생성",
106
- branch: "새 브랜치 이름 (선택)",
107
- hint: "이름을 비우면 모델이 이름을 정합니다. 커밋하지 않은 변경은 원래 체크아웃에 남습니다.",
294
+ create: "새 워크트리",
295
+ reservationHint: "다음 프롬프트를 전송하면 새 워크트리를 생성·전환한 뒤 작업을 시작합니다. 기존 워크트리를 선택하면 예약을 취소합니다.",
296
+ creationError: "생성 결과를 확인하지 못해 작업을 시작하지 않았습니다. 목록을 새로고침하고 워크트리를 선택하거나 다시 예약한 뒤 프롬프트를 다시 전송하세요.",
108
297
  close: "닫기",
109
- submit: "생성",
110
298
  refresh: "워크트리 새로고침",
111
299
  loading: "워크트리를 불러오는 중…",
112
300
  busy: "워크트리 변경 중…",
@@ -145,89 +333,107 @@ const copy$1 = {
145
333
  const copy = {
146
334
  ko: {
147
335
  nav: "워크트리",
336
+ defaultSelection: "기본 선택",
337
+ current: "현재 체크아웃",
338
+ new: "새 워크트리",
148
339
  directory: "워크트리 풀 디렉터리",
149
- directoryHint: "원본 저장소 루트 기준 상대 경로 또는 절대 경로를 입력하세요.",
150
340
  remote: "추적 중인 원격 브랜치 사용",
151
- remoteHint: "로컬에 가져온 원격 참조를 사용합니다. 자동으로 fetch하지 않습니다.",
152
341
  naming: "자동 브랜치 이름 형식",
153
- namingHint: "워크트리 생성 시 브랜치 입력을 비운 경우에만 적용합니다.",
154
342
  semantic: "시멘틱 (feat/… 또는 fix/…)",
155
343
  dsh: "DSH (dsh/*)",
156
344
  custom: "사용자 지정 접두사",
157
345
  prefix: "브랜치 접두사",
158
- prefixHint: "영문·숫자·하이픈·밑줄과 /를 사용하세요. 예: team/, task-",
159
- save: "저장",
160
- refresh: "설정 새로고침",
161
346
  loading: "설정을 불러오는 중입니다…",
162
347
  saving: "저장 중…",
163
- saved: "설정을 저장했습니다. 다음 워크트리 생성부터 적용합니다.",
164
- error: "설정을 확인하지 못했습니다. 다시 저장하기 전에 설정을 새로고침하세요.",
165
- conflict: "다른 곳에서 설정을 변경했습니다. 설정을 새로고침한 뒤 다시 저장하세요."
348
+ error: "변경 결과를 확인하지 못했습니다. 저장된 값을 확인한 뒤 다시 변경하세요.",
349
+ conflict: "다른 곳에서 설정을 변경했습니다. 저장된 값을 확인한 뒤 다시 변경하세요.",
350
+ unavailable: "설정을 불러오지 못했습니다. 연결을 확인한 뒤 설정 화면을 다시 여세요.",
351
+ invalidDirectory: "공백이 아닌 경로를 4096자 이내로 입력하세요. 줄바꿈은 사용할 수 없습니다.",
352
+ invalidPrefix: "올바른 접두사를 100자 이내로 입력하세요. 사용자 지정 접두사는 비워 둘 수 없습니다."
353
+ },
354
+ zh: {
355
+ nav: "工作树",
356
+ defaultSelection: "默认选择",
357
+ current: "当前检出",
358
+ new: "新工作树",
359
+ directory: "工作树池目录",
360
+ remote: "使用跟踪的远程分支",
361
+ naming: "自动分支命名格式",
362
+ semantic: "语义化(feat/… 或 fix/…)",
363
+ dsh: "DSH(dsh/*)",
364
+ custom: "自定义前缀",
365
+ prefix: "分支前缀",
366
+ loading: "正在加载设置…",
367
+ saving: "正在保存…",
368
+ error: "无法确认更改结果,请检查已保存的值后再进行更改。",
369
+ conflict: "设置已在其他位置更改,请检查已保存的值后再进行更改。",
370
+ unavailable: "无法加载设置,请检查连接后重新打开此设置页面。",
371
+ invalidDirectory: "请输入不超过 4096 个字符的非空路径,不能包含换行符。",
372
+ invalidPrefix: "请输入不超过 100 个字符的有效前缀,自定义前缀不能为空。"
166
373
  },
167
374
  en: {
168
375
  nav: "Worktrees",
376
+ defaultSelection: "Default selection",
377
+ current: "Current checkout",
378
+ new: "New worktree",
169
379
  directory: "Worktree pool directory",
170
- directoryHint: "Enter a path relative to the source repository root, or an absolute path.",
171
380
  remote: "Use tracked remote branch",
172
- remoteHint: "Uses the locally fetched remote reference. Does not fetch automatically.",
173
381
  naming: "Automatic branch naming",
174
- namingHint: "Applies only when the branch input is left empty when creating a worktree.",
175
382
  semantic: "Semantic (feat/… or fix/…)",
176
383
  dsh: "DSH (dsh/*)",
177
384
  custom: "Custom prefix",
178
385
  prefix: "Branch prefix",
179
- prefixHint: "Use letters, digits, hyphens, underscores and /. For example: team/, task-",
180
- save: "Save",
181
- refresh: "Reload settings",
182
386
  loading: "Loading settings…",
183
387
  saving: "Saving…",
184
- saved: "Settings saved. Applies to the next worktree creation.",
185
- error: "Could not confirm settings. Reload settings before saving again.",
186
- conflict: "Settings changed elsewhere. Reload settings before saving again."
388
+ error: "Could not confirm the change. Check the saved values before you change them again.",
389
+ conflict: "Settings changed elsewhere. Check the saved values before you change them again.",
390
+ unavailable: "Could not load settings. Check the connection and reopen this settings page.",
391
+ invalidDirectory: "Enter a nonblank path of at most 4096 characters without line breaks.",
392
+ invalidPrefix: "Enter a valid prefix of at most 100 characters. A custom prefix cannot be empty."
187
393
  }
188
394
  };
189
- const settingsLabel = (language) => copy[language.startsWith("ko") ? "ko" : "en"].nav;
395
+ const languageKey = (language) => language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en";
396
+ const settingsLabel = (language) => copy[languageKey(language)].nav;
397
+ const validDirectory = (value) => value.trim().length > 0 && value.length <= 4096 && !/[\0\r\n]/.test(value);
398
+ const validPrefix = (value) => value.length <= 100 && (value === "" || /^[a-zA-Z0-9][a-zA-Z0-9_-]*(\/[a-zA-Z0-9][a-zA-Z0-9_-]*)*\/?$/.test(value));
399
+ const validSettings = (settings) => validDirectory(settings.directory) && validPrefix(settings.prefix) && (settings.naming !== "custom" || !!settings.prefix);
400
+ const sameSettings = (left, right) => JSON.stringify(left) === JSON.stringify(right);
190
401
  function WorktreeSettingsSection({ ctx }) {
191
- const language = (0, react.useSyncExternalStore)((listener) => ctx.locale.subscribe(listener), () => ctx.locale.getSnapshot().active);
192
- const text = copy[language.startsWith("ko") ? "ko" : "en"];
402
+ const { active: language } = useLocaleSnapshot(ctx.locale);
403
+ const text = copy[languageKey(language)];
193
404
  const id = (0, react.useId)();
194
405
  const connection = ctx.get("connection");
195
- const [view, setView] = (0, react.useState)();
196
406
  const [draft, setDraft] = (0, react.useState)();
197
407
  const [operation, setOperation] = (0, react.useState)("loading");
198
408
  const [notice, setNotice] = (0, react.useState)();
199
- const [namingOpen, setNamingOpen] = (0, react.useState)(false);
409
+ const saved = (0, react.useRef)(void 0);
410
+ const latest = (0, react.useRef)(void 0);
200
411
  const pending = (0, react.useRef)(false);
201
- const needsReload = (0, react.useRef)(false);
412
+ const blocked = (0, react.useRef)(true);
202
413
  const lifetime = (0, react.useRef)(null);
203
- const request = async (save = false) => {
414
+ const read = async (controller) => {
415
+ const result = await connection.rpc.call(channel, `${endpointPrefix}settings`, {}, controller.signal);
416
+ if (controller.signal.aborted) return;
417
+ if (!result.ok) throw new Error("settings failed");
418
+ const next = result.value;
419
+ saved.current = next;
420
+ latest.current = { ...next.settings };
421
+ setDraft(latest.current);
422
+ blocked.current = false;
423
+ };
424
+ const load = async () => {
204
425
  const controller = lifetime.current;
205
426
  if (!controller || controller.signal.aborted || pending.current) return;
206
- if (save && (!view || !draft || needsReload.current)) return;
207
427
  pending.current = true;
208
- setNamingOpen(false);
209
- setOperation(save ? "saving" : "loading");
210
- setNotice(void 0);
428
+ blocked.current = true;
429
+ setOperation("loading");
211
430
  try {
212
- const result = await connection.rpc.call(channel, `${endpointPrefix}${save ? "save-settings" : "settings"}`, save ? {
213
- revision: view.revision,
214
- settings: { ...draft }
215
- } : {}, controller.signal);
216
- if (controller.signal.aborted) return;
217
- if (!result.ok) {
218
- needsReload.current = true;
219
- setNotice(result.error.code === "conflict" ? "conflict" : "error");
220
- return;
221
- }
222
- const next = result.value;
223
- setView(next);
224
- setDraft({ ...next.settings });
225
- needsReload.current = false;
226
- if (save) setNotice("saved");
431
+ await read(controller);
432
+ if (!controller.signal.aborted) setNotice(void 0);
227
433
  } catch {
228
434
  if (!controller.signal.aborted) {
229
- needsReload.current = true;
230
- setNotice("error");
435
+ blocked.current = true;
436
+ setNotice("unavailable");
231
437
  }
232
438
  } finally {
233
439
  if (!controller.signal.aborted) {
@@ -236,40 +442,125 @@ function WorktreeSettingsSection({ ctx }) {
236
442
  }
237
443
  }
238
444
  };
445
+ const save = async () => {
446
+ const controller = lifetime.current;
447
+ if (!controller || controller.signal.aborted || pending.current || blocked.current || !saved.current) return;
448
+ pending.current = true;
449
+ setNotice(void 0);
450
+ try {
451
+ while (latest.current && validSettings(latest.current) && !sameSettings(latest.current, saved.current.settings)) {
452
+ const submitted = { ...latest.current };
453
+ setOperation("saving");
454
+ const result = await connection.rpc.call(channel, `${endpointPrefix}save-settings`, {
455
+ revision: saved.current.revision,
456
+ settings: submitted
457
+ }, controller.signal);
458
+ if (controller.signal.aborted) return;
459
+ if (!result.ok) {
460
+ setNotice(result.error.code === "conflict" ? "conflict" : "error");
461
+ blocked.current = true;
462
+ break;
463
+ }
464
+ const next = result.value;
465
+ saved.current = next;
466
+ if (sameSettings(latest.current, submitted)) {
467
+ latest.current = { ...next.settings };
468
+ setDraft(latest.current);
469
+ }
470
+ }
471
+ } catch {
472
+ if (controller.signal.aborted) return;
473
+ blocked.current = true;
474
+ setNotice("error");
475
+ } finally {
476
+ if (!controller.signal.aborted) {
477
+ if (blocked.current) {
478
+ setOperation("loading");
479
+ try {
480
+ await read(controller);
481
+ } catch {
482
+ if (!controller.signal.aborted) setNotice("unavailable");
483
+ }
484
+ }
485
+ if (!controller.signal.aborted) {
486
+ pending.current = false;
487
+ setOperation(void 0);
488
+ }
489
+ }
490
+ }
491
+ };
492
+ const change = (patch) => {
493
+ if (!latest.current || blocked.current) return;
494
+ latest.current = {
495
+ ...latest.current,
496
+ ...patch
497
+ };
498
+ if (patch.naming && patch.naming !== "custom" && !validPrefix(latest.current.prefix)) latest.current.prefix = "";
499
+ setDraft(latest.current);
500
+ setNotice(void 0);
501
+ if (validSettings(latest.current)) save();
502
+ };
239
503
  (0, react.useEffect)(() => {
240
504
  const controller = new AbortController();
241
505
  lifetime.current = controller;
242
506
  pending.current = false;
243
- request();
244
- return () => controller.abort();
507
+ blocked.current = true;
508
+ load();
509
+ const refresh = () => {
510
+ if (blocked.current || sameSettings(latest.current, saved.current?.settings)) load();
511
+ };
512
+ window.addEventListener("focus", refresh);
513
+ window.addEventListener("online", refresh);
514
+ return () => {
515
+ controller.abort();
516
+ window.removeEventListener("focus", refresh);
517
+ window.removeEventListener("online", refresh);
518
+ };
245
519
  }, []);
246
- const busy = operation !== void 0;
247
- const dirty = !!draft && JSON.stringify(draft) !== JSON.stringify(view?.settings);
520
+ const disabled = operation === "loading" || blocked.current;
521
+ const invalidDirectory = !!draft && !validDirectory(draft.directory);
522
+ const invalidPrefix = !!draft && (!validPrefix(draft.prefix) || draft.naming === "custom" && !draft.prefix);
248
523
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
249
524
  className: "dsh-worktree-settings",
250
525
  "aria-label": text.nav,
251
- "aria-busy": busy,
526
+ "aria-busy": operation !== void 0,
252
527
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
253
- onSubmit: (event) => {
254
- event.preventDefault();
255
- request(true);
256
- },
528
+ onSubmit: (event) => event.preventDefault(),
257
529
  children: [
258
530
  draft && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
259
- disabled: busy,
531
+ disabled,
260
532
  children: [
261
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
262
- className: "dsh-worktree-settings-row",
263
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
264
- className: "dsh-worktree-settings-text",
265
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
266
- htmlFor: `${id}-directory`,
267
- children: text.directory
268
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
269
- id: `${id}-directory-hint`,
270
- children: text.directoryHint
271
- })]
272
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
533
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
534
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
535
+ id: `${id}-default-selection`,
536
+ className: "dsh-worktree-settings-title",
537
+ children: text.defaultSelection
538
+ }),
539
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsPicker, {
540
+ className: "dsh-worktree-settings-picker",
541
+ disabled,
542
+ "aria-labelledby": `${id}-default-selection`,
543
+ value: text[draft.defaultSelection],
544
+ selectedId: draft.defaultSelection,
545
+ items: ["current", "new"].map((id) => ({
546
+ id,
547
+ label: text[id]
548
+ })),
549
+ onSelect: (defaultSelection) => {
550
+ change({ defaultSelection });
551
+ }
552
+ })
553
+ }),
554
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
555
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
556
+ htmlFor: `${id}-directory`,
557
+ children: text.directory
558
+ }), invalidDirectory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
559
+ id: `${id}-directory-error`,
560
+ role: "alert",
561
+ children: text.invalidDirectory
562
+ })] }),
563
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
273
564
  className: "dsh-worktree-settings-input",
274
565
  id: `${id}-directory`,
275
566
  type: "text",
@@ -278,47 +569,34 @@ function WorktreeSettingsSection({ ctx }) {
278
569
  maxLength: 4096,
279
570
  autoComplete: "off",
280
571
  spellCheck: false,
281
- "aria-describedby": `${id}-directory-hint`,
282
- onChange: (event) => setDraft({
283
- ...draft,
284
- directory: event.target.value
285
- })
286
- })]
572
+ "aria-invalid": invalidDirectory || void 0,
573
+ "aria-describedby": invalidDirectory ? `${id}-directory-error` : void 0,
574
+ onChange: (event) => change({ directory: event.target.value })
575
+ })
287
576
  }),
288
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
289
- className: "dsh-worktree-settings-row",
290
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
291
- className: "dsh-worktree-settings-text",
292
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
293
- className: "dsh-worktree-settings-title",
294
- children: text.remote
295
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.remoteHint })]
296
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
577
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
578
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
579
+ className: "dsh-worktree-settings-title",
580
+ children: text.remote
581
+ }),
582
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
297
583
  label: text.remote,
298
584
  checked: draft.remote,
299
- disabled: busy,
300
- onChange: (remote) => setDraft({
301
- ...draft,
302
- remote
303
- })
304
- })]
585
+ disabled,
586
+ onChange: (remote) => change({ remote })
587
+ })
305
588
  }),
306
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
307
- className: "dsh-worktree-settings-row",
308
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
309
- className: "dsh-worktree-settings-text",
310
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
311
- id: `${id}-naming`,
312
- className: "dsh-worktree-settings-title",
313
- children: text.naming
314
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
315
- id: `${id}-naming-hint`,
316
- children: text.namingHint
317
- })]
318
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
319
- open: namingOpen,
320
- align: "end",
321
- portal: true,
589
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
590
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
591
+ id: `${id}-naming`,
592
+ className: "dsh-worktree-settings-title",
593
+ children: text.naming
594
+ }),
595
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsPicker, {
596
+ className: "dsh-worktree-settings-picker",
597
+ disabled,
598
+ "aria-labelledby": `${id}-naming`,
599
+ value: text[draft.naming],
322
600
  selectedId: draft.naming,
323
601
  items: [
324
602
  "semantic",
@@ -328,42 +606,21 @@ function WorktreeSettingsSection({ ctx }) {
328
606
  id,
329
607
  label: text[id]
330
608
  })),
331
- onClose: () => setNamingOpen(false),
332
609
  onSelect: (naming) => {
333
- setNamingOpen(false);
334
- if (!busy) setDraft({
335
- ...draft,
336
- naming
337
- });
338
- },
339
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
340
- type: "button",
341
- className: "dsh-worktree-settings-selector",
342
- disabled: busy,
343
- "aria-labelledby": `${id}-naming`,
344
- "aria-describedby": `${id}-naming-hint`,
345
- "aria-haspopup": "menu",
346
- "aria-expanded": namingOpen,
347
- onClick: () => setNamingOpen((open) => !open),
348
- 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, {
349
- size: 16,
350
- "aria-hidden": "true"
351
- })]
352
- })
353
- })]
610
+ change({ naming });
611
+ }
612
+ })
354
613
  }),
355
- draft.naming === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
356
- className: "dsh-worktree-settings-row",
357
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
358
- className: "dsh-worktree-settings-text",
359
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
360
- htmlFor: `${id}-prefix`,
361
- children: text.prefix
362
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
363
- id: `${id}-prefix-hint`,
364
- children: text.prefixHint
365
- })]
366
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
614
+ draft.naming === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
615
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
616
+ htmlFor: `${id}-prefix`,
617
+ children: text.prefix
618
+ }), invalidPrefix && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
619
+ id: `${id}-prefix-error`,
620
+ role: "alert",
621
+ children: text.invalidPrefix
622
+ })] }),
623
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
367
624
  className: "dsh-worktree-settings-input",
368
625
  id: `${id}-prefix`,
369
626
  type: "text",
@@ -371,66 +628,44 @@ function WorktreeSettingsSection({ ctx }) {
371
628
  required: true,
372
629
  maxLength: 100,
373
630
  pattern: "[a-zA-Z0-9][a-zA-Z0-9_\\-]*(/[a-zA-Z0-9][a-zA-Z0-9_\\-]*)*/?",
374
- "aria-describedby": `${id}-prefix-hint`,
631
+ "aria-invalid": invalidPrefix || void 0,
632
+ "aria-describedby": invalidPrefix ? `${id}-prefix-error` : void 0,
375
633
  autoComplete: "off",
376
634
  spellCheck: false,
377
- onChange: (event) => setDraft({
378
- ...draft,
379
- prefix: event.target.value
380
- })
381
- })]
635
+ onChange: (event) => change({ prefix: event.target.value })
636
+ })
382
637
  })
383
638
  ]
384
639
  }),
385
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
640
+ operation && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
386
641
  role: "status",
387
- children: operation ? text[operation] : notice === "saved" ? text.saved : ""
642
+ children: text[operation]
388
643
  }),
389
- (notice === "error" || notice === "conflict") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
644
+ notice && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
390
645
  role: "alert",
391
646
  children: text[notice]
392
- }),
393
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
394
- type: "button",
395
- variant: "outline",
396
- disabled: busy,
397
- onClick: () => void request(),
398
- children: text.refresh
399
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
400
- type: "submit",
401
- variant: "primary",
402
- disabled: busy || !view || !dirty || needsReload.current,
403
- children: operation === "saving" ? text.saving : text.save
404
- })] })
647
+ })
405
648
  ]
406
649
  })
407
650
  });
408
651
  }
409
652
  // src/styles.ts
410
- const styles = `
653
+ const styles = `${settingsStyles}${composerStyles}
411
654
  .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; }
412
655
  .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; }
413
- .dsh-worktree-controls { display: inline-flex; flex-wrap: nowrap; align-items: center; min-width: 0; max-width: 100%; }
414
656
  .dsh-worktree-picker { display: inline-flex; min-width: 0; max-width: 100%; }
415
- .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; }
416
- .dsh-worktree-icon, .dsh-worktree-chevron { display: inline-flex; flex: none; }
417
- .dsh-worktree-icon svg { width: 14px; height: 14px; }
418
- .dsh-worktree-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
419
- .dsh-worktree-chevron { color: var(--dsw-alias-label-caption); }
420
- .dsh-worktree-trigger[aria-expanded=true] .dsh-worktree-chevron { transform: rotate(180deg); }
421
- .dsh-worktree-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
657
+ .dsh-worktree-trigger { display: inline-flex; max-width: 220px; font-weight: 500; }
658
+ .dsh-worktree-trigger .dsh-plugin-composer-icon svg { width: 14px; height: 14px; }
422
659
  .dsh-worktree-trigger:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 0; }
423
660
  @container (width <= 460px) {
424
- .dsh-worktree-name { display: none; }
661
+ .dsh-worktree-trigger .dsh-plugin-composer-label { display: none; }
425
662
  }
426
663
  @media (prefers-reduced-motion: no-preference) {
427
- .dsh-worktree-chevron { transition: transform .12s; }
664
+ .dsh-worktree-trigger .dsh-plugin-composer-chevron { transition: transform .12s; }
428
665
  }
429
666
  @media (hover: hover) {
430
- .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); }
667
+ .dsh-worktree-trigger:hover:not(:disabled), .dsh-worktree-dialog button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
431
668
  }
432
- .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; }
433
- .dsh-worktree-error-trigger:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 0; }
434
669
  .dsh-worktree-dialog.dsh-worktree-error-dialog { inset: 0; margin: auto; }
435
670
  .dsh-worktree-error-dialog dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px 12px; }
436
671
  .dsh-worktree-error-dialog dt { color: var(--dsw-alias-label-secondary); }
@@ -442,36 +677,39 @@ const styles = `
442
677
  .dsh-worktree-dialog p { margin: 10px 0; color: var(--dsw-alias-label-secondary); }
443
678
  .dsh-worktree-dialog button { border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm); background: transparent; color: inherit; padding: 7px 10px; cursor: pointer; font: inherit; }
444
679
  .dsh-worktree-dialog header button { display: inline-flex; align-items: center; justify-content: center; padding: 0; width: 28px; height: 28px; border: 0; }
445
- .dsh-worktree-dialog form > label { display: block; font-weight: 500; margin: 14px 0 6px; }
446
- .dsh-worktree-dialog input[type=text] { box-sizing: border-box; width: 100%; padding: 9px 10px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-bg-layer-1); color: inherit; font: inherit; }
447
- .dsh-worktree-dialog .dsh-worktree-hint { font-size: 12px; }
448
680
  .dsh-worktree-dialog footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 16px; }
449
- .dsh-worktree-dialog .dsh-worktree-primary, .dsh-worktree-dialog .dsh-worktree-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-fill); border-color: transparent; color: var(--dsw-alias-label-primary-foreground); }
450
- .dsh-worktree-dialog [role=alert] { color: var(--dsw-alias-state-error-primary); }
451
681
  .dsh-worktree-dialog button:disabled { opacity: .45; cursor: not-allowed; }
452
- .dsh-worktree-dialog button:focus-visible, .dsh-worktree-dialog input:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
682
+ .dsh-worktree-dialog button:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
453
683
  .dsh-worktree-sr { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
684
+ .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)); }
685
+ .dsh-worktree-progress > p { margin: 0 0 8px; overflow-wrap: anywhere; }
686
+ .dsh-worktree-progress ol { list-style: none; margin: 0; padding: 8px 0 0; border-top: 1px solid var(--dsw-alias-border-l2); }
687
+ .dsh-worktree-progress li { display: flex; align-items: center; gap: 10px; padding: 3px 4px; }
688
+ .dsh-worktree-progress li[data-state=pending] { opacity: .4; }
689
+ .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)); }
690
+ .dsh-worktree-progress-mark svg { width: calc(14px + var(--dsh-content-font-delta, 0px)); height: calc(14px + var(--dsh-content-font-delta, 0px)); }
691
+ .dsh-worktree-progress li[data-state=failed], .dsh-worktree-progress [role=alert] { color: var(--dsw-alias-state-error-primary); }
692
+ .dsh-worktree-progress [role=alert] { margin-top: 10px; font: inherit; }
693
+ .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; }
694
+ @keyframes dsh-worktree-spin { to { transform: rotate(360deg); } }
695
+ @media (prefers-reduced-motion: no-preference) {
696
+ .dsh-worktree-progress li[data-state=running] .dsh-worktree-progress-mark { animation: dsh-worktree-spin .8s linear infinite; }
697
+ }
454
698
  .dsh-worktree-settings { width: 100%; color: var(--dsw-alias-label-primary); font: inherit; }
455
699
  .dsh-worktree-settings fieldset { min-width: 0; padding: 0; margin: 0; border: 0; }
456
- .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); }
457
- .dsh-worktree-settings-row:last-child { border-bottom: 0; }
458
- .dsh-worktree-settings-text { flex: 1; min-width: 0; }
700
+ .dsh-worktree-settings .dsh-plugin-settings-row { gap: 24px; }
701
+ .dsh-worktree-settings .dsh-plugin-settings-row:last-child { border-bottom: 0; }
459
702
  .dsh-worktree-settings label, .dsh-worktree-settings-title { display: block; font-size: 14px; font-weight: 400; line-height: 20px; }
460
- .dsh-worktree-settings-text p { margin: 4px 0 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
703
+ .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; }
461
704
  .dsh-worktree-settings-input { box-sizing: border-box; width: 220px; max-width: 100%; flex: 0 1 220px; min-width: 0; }
462
- .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; }
463
- .dsh-worktree-settings-selector span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
464
- .dsh-worktree-settings-selector svg { flex: none; }
465
- .dsh-worktree-settings-selector:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
466
- .dsh-worktree-settings-selector:disabled { opacity: .5; cursor: default; }
467
- .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; }
705
+ .dsh-worktree-settings-picker .dsh-plugin-settings-selector { max-width: 220px; color: var(--dsw-alias-label-primary); font-size: 14px; line-height: 22px; }
706
+ .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)); }
468
707
  .dsh-worktree-settings [role=status], .dsh-worktree-settings [role=alert] { margin: 12px 0; font-size: 12px; line-height: 18px; }
469
708
  .dsh-worktree-settings [role=status]:empty { margin: 0; }
470
- .dsh-worktree-settings footer { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; margin-top: 16px; }
471
709
  .dsh-worktree-settings [role=alert] { color: var(--dsw-alias-state-error-primary); }
472
710
  @media (max-width: 600px) {
473
- .dsh-worktree-settings-row { flex-wrap: wrap; gap: 12px; }
474
- .dsh-worktree-settings-text { flex-basis: 160px; }
711
+ .dsh-worktree-settings .dsh-plugin-settings-row { flex-wrap: wrap; gap: 12px; }
712
+ .dsh-worktree-settings .dsh-plugin-settings-text { flex-basis: 160px; }
475
713
  .dsh-worktree-settings-input { flex-grow: 1; }
476
714
  }
477
715
  `;
@@ -512,22 +750,167 @@ function installSidebarLabel(sessionId, name, path) {
512
750
  for (const label of labels.values()) label.remove();
513
751
  };
514
752
  }
753
+ // src/progress.tsx
754
+ const labels = {
755
+ ko: {
756
+ running: "워크트리 준비 중…",
757
+ completed: "워크트리 준비 완료",
758
+ failed: "워크트리 생성 실패",
759
+ cancelled: "워크트리 준비 중단",
760
+ steps: [
761
+ "브랜치 이름 정하기",
762
+ "기준 커밋 및 경로 확인",
763
+ "파일 체크아웃",
764
+ "작업 경로 전환"
765
+ ],
766
+ failure: "작업을 시작하지 않았습니다. 워크트리를 선택한 뒤 프롬프트를 다시 전송하세요."
767
+ },
768
+ en: {
769
+ running: "Setting up worktree…",
770
+ completed: "Worktree ready",
771
+ failed: "Worktree creation failed",
772
+ cancelled: "Worktree setup stopped",
773
+ steps: [
774
+ "Name branch",
775
+ "Check base commit and path",
776
+ "Check out files",
777
+ "Switch working directory"
778
+ ],
779
+ failure: "Work did not start. Select a worktree, then resend your prompt."
780
+ },
781
+ zh: {
782
+ running: "正在准备工作树…",
783
+ completed: "工作树已就绪",
784
+ failed: "工作树创建失败",
785
+ cancelled: "工作树准备已停止",
786
+ steps: [
787
+ "生成分支名称",
788
+ "检查基准提交和路径",
789
+ "检出文件",
790
+ "切换工作目录"
791
+ ],
792
+ failure: "尚未开始任务。请选择工作树,然后重新发送提示。"
793
+ }
794
+ };
795
+ const progressDefinition = {
796
+ kind: "dungsil-worktree-progress",
797
+ target: "chat",
798
+ match: (event) => {
799
+ const record = event;
800
+ if (record.type === "plugin:dungsil/ui-worktree/progress" && isWorktreeProgress(record.data)) return {
801
+ id: record.data.id,
802
+ role: "start"
803
+ };
804
+ if (event.type === "user/message") return {
805
+ id: event.data.id,
806
+ role: "update"
807
+ };
808
+ return null;
809
+ },
810
+ start: (_context, match) => ({
811
+ progress: match.event.data,
812
+ anchorSeq: match.event.seq
813
+ }),
814
+ update: (context, match) => match.event.type === "user/message" ? {
815
+ ...context.state,
816
+ anchorSeq: match.event.seq + .1
817
+ } : {
818
+ ...context.state,
819
+ progress: match.event.data
820
+ },
821
+ buildViewNode: (context) => {
822
+ if (!context.state || !context.start) return null;
823
+ return {
824
+ key: context.key,
825
+ id: context.id,
826
+ kind: "dungsil-worktree-progress",
827
+ target: "chat",
828
+ anchorSeq: context.state.anchorSeq,
829
+ location: context.start.location,
830
+ visibility: "visible",
831
+ groupTree: context.state.progress.status === "completed" ? void 0 : "turn",
832
+ data: context.state.progress
833
+ };
834
+ }
835
+ };
836
+ function installProgress(ctx) {
837
+ ctx.uiConversation.events.register(progressDefinition);
838
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
839
+ name: "conversation.chat.node",
840
+ key: "dungsil-worktree-progress",
841
+ inject: () => ({})
842
+ }, function Progress({ node }) {
843
+ const { active: language } = useLocaleSnapshot(ctx.locale);
844
+ const text = labels[language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en"];
845
+ const progress = node.data;
846
+ const ended = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn.status === "closed" : false;
847
+ const status = progress.status === "running" && ended ? "cancelled" : progress.status;
848
+ const [now, setNow] = (0, react.useState)(Date.now);
849
+ (0, react.useEffect)(() => {
850
+ if (status !== "running") return;
851
+ const timer = window.setInterval(() => setNow(Date.now()), 100);
852
+ return () => window.clearInterval(timer);
853
+ }, [status]);
854
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
855
+ className: "dsh-worktree-progress",
856
+ "aria-label": text[status],
857
+ "aria-busy": status === "running",
858
+ children: [
859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
860
+ role: "status",
861
+ children: [text[status], status === "completed" && progress.branch ? ` · ${progress.branch}` : ""]
862
+ }),
863
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", { children: text.steps.map((label, index) => {
864
+ const state = index < progress.step || status === "completed" ? "completed" : index === progress.step ? status : "pending";
865
+ const duration = progress.durations[index] + (state === "running" ? Math.max(0, now - progress.updatedAt) : 0);
866
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
867
+ "data-state": state,
868
+ "aria-current": state === "running" ? "step" : void 0,
869
+ children: [
870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
871
+ className: "dsh-worktree-progress-mark",
872
+ "aria-hidden": "true",
873
+ 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 })
874
+ }),
875
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
876
+ state !== "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
877
+ className: "dsh-worktree-progress-time",
878
+ children: duration < 1e3 ? `${Math.floor(duration)}ms` : `${(duration / 1e3).toFixed(1)}s`
879
+ })
880
+ ]
881
+ }, label);
882
+ }) }),
883
+ (status === "failed" || status === "cancelled") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
884
+ role: "alert",
885
+ children: text.failure
886
+ })
887
+ ]
888
+ });
889
+ }));
890
+ }
515
891
  // src/client.tsx
516
892
  const name = "dungsil-ui-worktree";
517
893
  const inject = [
518
894
  "slots",
519
895
  "locale",
520
- "connection"
896
+ "connection",
897
+ "uiConversation"
521
898
  ];
522
899
  function apply(ctx) {
900
+ ctx.effect(() => installSettingsIcon(name, [
901
+ "en",
902
+ "ko",
903
+ "zh"
904
+ ].map(settingsLabel), git_branch_default));
523
905
  const connection = ctx.get("connection");
524
- const subscribeLocale = (listener) => ctx.locale.subscribe(listener);
906
+ installProgress(ctx);
525
907
  function Picker({ sessionId, useSession }) {
526
- const language = (0, react.useSyncExternalStore)(subscribeLocale, () => ctx.locale.getSnapshot().active);
527
- const text = copy$1[language.startsWith("ko") ? "ko" : "en"];
908
+ const { active: language } = useLocaleSnapshot(ctx.locale);
909
+ const text = copy$1[language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en"];
528
910
  const unavailable = useSession((s) => s.running || s.removed || s.openState !== "open" || s.pendingSubmissions.length > 0);
911
+ const running = useSession((s) => s.running);
912
+ const wasRunning = (0, react.useRef)(running);
529
913
  const [view, setView] = (0, react.useState)();
530
- const [value, setValue] = (0, react.useState)("");
531
914
  const [loading, setLoading] = (0, react.useState)(true);
532
915
  const [busy, setBusy] = (0, react.useState)(false);
533
916
  const [error, setError] = (0, react.useState)();
@@ -537,16 +920,18 @@ function apply(ctx) {
537
920
  const needsRefresh = (0, react.useRef)(false);
538
921
  const lifetime = (0, react.useRef)(null);
539
922
  const trigger = (0, react.useRef)(null);
540
- const dialog = (0, react.useRef)(null);
541
923
  const errorDialog = (0, react.useRef)(null);
542
- const errorTrigger = (0, react.useRef)(null);
543
- const title = (0, react.useId)();
544
924
  const errorTitle = (0, react.useId)();
545
925
  const errorDescription = (0, react.useId)();
546
- const field = (0, react.useId)();
547
926
  const status = (0, react.useId)();
548
927
  const disabled = unavailable || loading || busy || !!error || !view;
549
- const errorLabel = error?.uncertain ? text.mutationError : text.lookupError;
928
+ const creation = view?.pendingCreation;
929
+ const detailError = error ?? (creation?.failed ? {
930
+ operation: "create",
931
+ code: "operation_failed",
932
+ uncertain: true
933
+ } : void 0);
934
+ const errorLabel = detailError?.uncertain ? text.mutationError : text.lookupError;
550
935
  const recordFailure = (operation, cause) => {
551
936
  const code = failureCode(cause);
552
937
  needsRefresh.current = true;
@@ -598,7 +983,6 @@ function apply(ctx) {
598
983
  };
599
984
  const resize = () => {
600
985
  setOpen(false);
601
- dialog.current?.close();
602
986
  };
603
987
  window.addEventListener("focus", focus);
604
988
  window.addEventListener("resize", resize);
@@ -608,19 +992,13 @@ function apply(ctx) {
608
992
  window.removeEventListener("resize", resize);
609
993
  };
610
994
  }, []);
995
+ (0, react.useEffect)(() => {
996
+ if (wasRunning.current && !running) load();
997
+ wasRunning.current = running;
998
+ }, [running]);
611
999
  (0, react.useEffect)(() => {
612
1000
  if (disabled) setOpen(false);
613
1001
  }, [disabled]);
614
- const show = () => {
615
- const element = dialog.current;
616
- const button = trigger.current;
617
- if (disabled || pending.current || !element || !button) return;
618
- element.showModal();
619
- const anchor = button.getBoundingClientRect();
620
- const box = element.getBoundingClientRect();
621
- element.style.left = `${Math.max(8, Math.min(anchor.left, window.innerWidth - box.width - 8))}px`;
622
- element.style.top = `${Math.max(8, anchor.top - box.height - 8)}px`;
623
- };
624
1002
  const change = async (endpoint, path) => {
625
1003
  const controller = lifetime.current;
626
1004
  if (disabled || pending.current || needsRefresh.current || !view || !controller || controller.signal.aborted) return;
@@ -630,8 +1008,7 @@ function apply(ctx) {
630
1008
  try {
631
1009
  const payload = endpoint === "create" ? {
632
1010
  sessionId,
633
- cwd: view.cwd,
634
- ...value.trim() ? { name: value.trim() } : {}
1011
+ cwd: view.cwd
635
1012
  } : {
636
1013
  sessionId,
637
1014
  cwd: view.cwd,
@@ -641,10 +1018,6 @@ function apply(ctx) {
641
1018
  if (controller.signal.aborted) return;
642
1019
  setView(nextView);
643
1020
  setChanged(true);
644
- if (endpoint === "create") {
645
- setValue("");
646
- dialog.current?.close();
647
- }
648
1021
  pending.current = false;
649
1022
  await load();
650
1023
  } catch (cause) {
@@ -657,13 +1030,13 @@ function apply(ctx) {
657
1030
  }
658
1031
  };
659
1032
  (0, react.useEffect)(() => {
660
- if (!busy && changed && !dialog.current?.open) trigger.current?.focus();
1033
+ if (!busy && changed) trigger.current?.focus();
661
1034
  }, [busy, changed]);
662
1035
  (0, react.useEffect)(() => {
663
- if (!error && errorDialog.current?.open) errorDialog.current.close();
664
- }, [error]);
1036
+ if (!detailError && errorDialog.current?.open) errorDialog.current.close();
1037
+ }, [error, creation?.failed]);
665
1038
  const restoreErrorFocus = () => {
666
- (errorTrigger.current ?? trigger.current)?.focus();
1039
+ trigger.current?.focus();
667
1040
  };
668
1041
  const current = currentWorktree(view);
669
1042
  (0, react.useEffect)(() => {
@@ -674,79 +1047,58 @@ function apply(ctx) {
674
1047
  current,
675
1048
  view
676
1049
  ]);
677
- const branch = current ? current.branch ?? text.detached : text.unknown;
678
- const notice = loading ? text.loading : busy ? text.busy : error ? errorLabel : unavailable ? text.wait : changed ? text.changed : view?.worktrees.length === 0 ? text.empty : "";
1050
+ const branch = creation ? creation.failed ? text.mutationError : text.create : current ? current.branch ?? text.detached : text.unknown;
1051
+ const label = detailError ? errorLabel : view ? branch : loading ? text.loading : text.title;
1052
+ 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 : "";
679
1053
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
680
1054
  className: "dsh-worktree",
681
1055
  children: [
682
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
683
- className: "dsh-worktree-controls",
684
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
685
- className: "dsh-worktree-picker",
686
- open,
687
- side: "top",
688
- portal: true,
689
- selectedId: current ? `path:${current.path}` : void 0,
690
- items: [{
691
- id: "create",
692
- label: text.create,
693
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, {})
694
- }, ...view?.worktrees.map((item) => ({
695
- id: `path:${item.path}`,
696
- label: item.branch ?? text.detached,
697
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {})
698
- })) ?? []],
699
- onClose: () => setOpen(false),
700
- onSelect: (id) => {
701
- setOpen(false);
702
- if (disabled || pending.current) return;
703
- if (id === "create") show();
704
- else if (id.startsWith("path:") && id !== `path:${current?.path}`) change("switch", id.slice(5));
1056
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1057
+ className: "dsh-worktree-picker",
1058
+ open,
1059
+ side: "top",
1060
+ portal: true,
1061
+ selectedId: creation ? "create" : current ? `path:${current.path}` : void 0,
1062
+ items: [{
1063
+ id: "create",
1064
+ label: text.create,
1065
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, {})
1066
+ }, ...view?.worktrees.map((item) => ({
1067
+ id: `path:${item.path}`,
1068
+ label: item.branch ?? text.detached,
1069
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {})
1070
+ })) ?? []],
1071
+ onClose: () => setOpen(false),
1072
+ onSelect: (id) => {
1073
+ setOpen(false);
1074
+ if (disabled || pending.current) return;
1075
+ if (id === "create") {
1076
+ if (!creation || creation.failed) change("create");
1077
+ } else if (id.startsWith("path:") && (creation || id !== `path:${current?.path}`)) change("switch", id.slice(5));
1078
+ },
1079
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerTrigger, {
1080
+ ref: trigger,
1081
+ className: "dsh-worktree-trigger",
1082
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
1083
+ label,
1084
+ disabled: detailError ? loading || busy : disabled,
1085
+ "aria-label": `${text.title}: ${label}`,
1086
+ "aria-haspopup": detailError ? "dialog" : "menu",
1087
+ "aria-expanded": detailError ? void 0 : open,
1088
+ "aria-describedby": status,
1089
+ "aria-busy": loading || busy,
1090
+ title: label,
1091
+ onClick: () => {
1092
+ if (detailError) errorDialog.current?.showModal();
1093
+ else setOpen((value) => !value);
705
1094
  },
706
- anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
707
- ref: trigger,
708
- type: "button",
709
- className: "dsh-worktree-trigger",
710
- disabled,
711
- "aria-label": view ? `${text.title}: ${branch}` : text.title,
712
- "aria-haspopup": "menu",
713
- "aria-expanded": open,
714
- "aria-describedby": status,
715
- "aria-busy": loading || busy,
716
- title: view ? branch : text.title,
717
- onClick: () => setOpen((value) => !value),
718
- onKeyDown: (event) => {
719
- if (!open && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
720
- event.preventDefault();
721
- setOpen(true);
722
- }
723
- },
724
- children: [
725
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
726
- className: "dsh-worktree-icon",
727
- "aria-hidden": "true",
728
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, { className: "dsh-worktree-branch" })
729
- }),
730
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
731
- className: "dsh-worktree-name",
732
- children: view ? branch : loading ? text.loading : text.title
733
- }),
734
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
735
- className: "dsh-worktree-chevron",
736
- "aria-hidden": "true",
737
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
738
- })
739
- ]
740
- })
741
- }), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
742
- ref: errorTrigger,
743
- type: "button",
744
- className: "dsh-worktree-error-trigger",
745
- "aria-haspopup": "dialog",
746
- "aria-label": `${errorLabel}: ${text.errorTitle}`,
747
- onClick: () => errorDialog.current?.showModal(),
748
- children: errorLabel
749
- })]
1095
+ onKeyDown: (event) => {
1096
+ if (!detailError && !disabled && !open && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
1097
+ event.preventDefault();
1098
+ setOpen(true);
1099
+ }
1100
+ }
1101
+ })
750
1102
  }),
751
1103
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
752
1104
  id: status,
@@ -754,82 +1106,6 @@ function apply(ctx) {
754
1106
  role: "status",
755
1107
  children: notice
756
1108
  }),
757
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
758
- ref: dialog,
759
- className: "dsh-worktree-dialog",
760
- "aria-labelledby": title,
761
- onClose: () => trigger.current?.focus(),
762
- onCancel: (event) => {
763
- if (pending.current) event.preventDefault();
764
- },
765
- onClick: (event) => {
766
- const box = event.currentTarget.getBoundingClientRect();
767
- const outside = event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
768
- if (event.target === event.currentTarget && outside && !pending.current) event.currentTarget.close();
769
- },
770
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
771
- id: title,
772
- children: text.createTitle
773
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
774
- type: "button",
775
- "aria-label": text.close,
776
- disabled: busy,
777
- onClick: () => dialog.current?.close(),
778
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { "aria-hidden": "true" })
779
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
780
- onSubmit: (event) => {
781
- event.preventDefault();
782
- change("create");
783
- },
784
- children: [
785
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
786
- htmlFor: field,
787
- children: text.branch
788
- }),
789
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
790
- id: field,
791
- type: "text",
792
- value,
793
- disabled: busy,
794
- maxLength: 200,
795
- autoComplete: "off",
796
- spellCheck: false,
797
- placeholder: "fix-login",
798
- onChange: (event) => setValue(event.target.value),
799
- onKeyDown: (event) => {
800
- if (event.key === "Enter" && (event.nativeEvent.isComposing || event.keyCode === 229)) event.preventDefault();
801
- }
802
- }),
803
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
804
- className: "dsh-worktree-hint",
805
- children: text.hint
806
- }),
807
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
808
- role: "alert",
809
- children: [
810
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: error.uncertain ? text.error : text.readError }),
811
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.reasons[error.code] }),
812
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: error.code })
813
- ]
814
- }),
815
- unavailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
816
- role: "status",
817
- children: text.wait
818
- }),
819
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
820
- type: "button",
821
- disabled: loading || busy,
822
- onClick: () => void load(true),
823
- children: text.refresh
824
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
825
- type: "submit",
826
- className: "dsh-worktree-primary",
827
- disabled,
828
- children: busy ? text.busy : text.submit
829
- })] })
830
- ]
831
- })]
832
- }),
833
1109
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
834
1110
  ref: errorDialog,
835
1111
  className: "dsh-worktree-dialog dsh-worktree-error-dialog",
@@ -848,13 +1124,13 @@ function apply(ctx) {
848
1124
  })] }),
849
1125
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
850
1126
  id: errorDescription,
851
- children: error?.uncertain ? text.error : text.readError
1127
+ children: creation?.failed ? text.creationError : error?.uncertain ? text.error : text.readError
852
1128
  }),
853
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.reasons[error.code] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", { children: [
1129
+ detailError && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.reasons[detailError.code] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", { children: [
854
1130
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: text.errorOperation }),
855
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: text.operations[error.operation] }),
1131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: text.operations[detailError.operation] }),
856
1132
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: text.errorCode }),
857
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: error.code }) })
1133
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: detailError.code }) })
858
1134
  ] })] }),
859
1135
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
860
1136
  type: "button",
@@ -880,12 +1156,7 @@ function apply(ctx) {
880
1156
  label: () => settingsLabel(ctx.locale.getSnapshot().active),
881
1157
  inject: () => ({ ctx })
882
1158
  }, WorktreeSettingsSection));
883
- ctx.effect(() => {
884
- const style = document.createElement("style");
885
- style.textContent = styles;
886
- document.head.append(style);
887
- return () => style.remove();
888
- }, "ui-worktree: composer styles");
1159
+ ctx.effect(() => installStyles(styles), "ui-worktree: composer styles");
889
1160
  }
890
1161
  exports.apply = apply;
891
1162
  exports.inject = inject;