@linxin666/dsh-client-ui-task-board 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -451,10 +451,30 @@ window.__ModuleLoader__.load({
451
451
  * fresh updatedAt. Pure ledger transition (no persistence or notify — the
452
452
  * controller orchestrates those).
453
453
  *
454
- * An explicit `undefined` in the patch clears the field (the task falls
455
- * back to the runtime default); an unknown permission string is ignored so
456
- * stale UI can never persist a value the execution service rejects.
454
+ * An explicit `undefined` in the patch clears an execution-target field
455
+ * (the task falls back to the runtime default); content fields are trimmed
456
+ * and never cleared. An unknown permission string is ignored so stale UI
457
+ * can never persist a value the execution service rejects.
457
458
  */
459
+ /**
460
+ * The fields that edit the task's content (what the user reads and what the
461
+ * next execution sends). Unlike the execution targets they stay editable only
462
+ * while the task has never started executing — after the first run the
463
+ * recorded prompt is the record of what actually ran, so it becomes read-only.
464
+ */
465
+ const TASK_CONTENT_FIELDS = [
466
+ "title",
467
+ "description",
468
+ "prompt"
469
+ ];
470
+ /**
471
+ * Whether a task's content may still be edited: the task must be on-board
472
+ * (not archived) and must never have started executing. Fail-closed: a
473
+ * running, settled, or cancelled-before-launch task keeps its content fixed.
474
+ */
475
+ function canEditTaskContent(task) {
476
+ return task.archivedAt === void 0 && task.status !== "running" && task.executions.length === 0;
477
+ }
458
478
  /** Keep an unknown permission string from entering the ledger. */
459
479
  function normalizePermission(current, value) {
460
480
  if (value === void 0) return void 0;
@@ -479,6 +499,11 @@ window.__ModuleLoader__.load({
479
499
  ...patch,
480
500
  updatedAt: now
481
501
  };
502
+ for (const field of TASK_CONTENT_FIELDS) {
503
+ if (!(field in patch)) continue;
504
+ const value = patch[field];
505
+ next[field] = value === void 0 ? task[field] : value.trim();
506
+ }
482
507
  if (workspaceId !== void 0 || "workspaceId" in patch) next.workspaceId = workspaceId;
483
508
  if (mode !== void 0 || "mode" in patch) next.mode = mode;
484
509
  if (permission !== void 0 || "permission" in patch) next.permission = permission;
@@ -641,17 +666,23 @@ window.__ModuleLoader__.load({
641
666
  input
642
667
  }, id) ? this.tasks.find((task) => task.id === id) : void 0;
643
668
  }
644
- updateTask(id, patch) {
645
- if (this.deps.transport !== void 0) {
646
- this.commitRemote({
647
- kind: "update",
648
- taskId: id,
649
- patch
650
- }, id);
651
- return;
652
- }
669
+ /**
670
+ * Apply an editable-field patch (task content + execution targets).
671
+ * Host-backed: the Host ledger owns the fail-closed checks (the content of
672
+ * an executed task is read-only) and confirms the mutation, so the resolved
673
+ * value reflects whether the Host accepted it; the legacy in-memory path
674
+ * applies and persists synchronously.
675
+ * @returns true when the patch was accepted by the authority.
676
+ */
677
+ async updateTask(id, patch) {
678
+ if (this.deps.transport !== void 0) return await this.commitRemote({
679
+ kind: "update",
680
+ taskId: id,
681
+ patch
682
+ }, id);
653
683
  this.tasks = [...applyUpdateTask(this.tasks, id, patch, this.now())];
654
684
  this.persistAndNotify();
685
+ return true;
655
686
  }
656
687
  /**
657
688
  * Replace (a part of) the picker option sets the UI feeds (workspace list
@@ -1143,7 +1174,10 @@ window.__ModuleLoader__.load({
1143
1174
  "new.cancel": "取消",
1144
1175
  "new.required": "标题不能为空",
1145
1176
  "detail.title": "任务详情",
1177
+ "detail.edit": "编辑",
1146
1178
  "detail.close": "关闭",
1179
+ "edit.title": "编辑任务",
1180
+ "edit.save": "保存",
1147
1181
  "detail.prompt": "执行 Prompt",
1148
1182
  "detail.description": "描述",
1149
1183
  "detail.execution": "执行记录",
@@ -1264,7 +1298,10 @@ window.__ModuleLoader__.load({
1264
1298
  "new.cancel": "Cancel",
1265
1299
  "new.required": "Title is required",
1266
1300
  "detail.title": "Task Detail",
1301
+ "detail.edit": "Edit",
1267
1302
  "detail.close": "Close",
1303
+ "edit.title": "Edit Task",
1304
+ "edit.save": "Save",
1268
1305
  "detail.prompt": "Run Prompt",
1269
1306
  "detail.description": "Description",
1270
1307
  "detail.execution": "Execution History",
@@ -1877,6 +1914,125 @@ window.__ModuleLoader__.load({
1877
1914
  });
1878
1915
  }
1879
1916
  //#endregion
1917
+ //#region src/client/board/EditTaskModal.tsx
1918
+ /**
1919
+ * Edit-task modal: title + description + the prompt the next execution will
1920
+ * send, pre-filled from the task. Shown only for tasks that have never
1921
+ * started executing (the detail view gates on canEditTaskContent); the Host
1922
+ * still re-checks at submit, so a task that started running while the modal
1923
+ * was open fails closed and the error surfaces here.
1924
+ */
1925
+ /** Edit-task form overlay. */
1926
+ function EditTaskModal({ controller, task, onClose }) {
1927
+ const [title, setTitle] = (0, react.useState)(task.title);
1928
+ const [description, setDescription] = (0, react.useState)(task.description);
1929
+ const [prompt, setPrompt] = (0, react.useState)(task.prompt);
1930
+ const [error, setError] = (0, react.useState)(void 0);
1931
+ const [pending, setPending] = (0, react.useState)(false);
1932
+ const submit = async () => {
1933
+ if (title.trim() === "") {
1934
+ setError(t("new.required"));
1935
+ return;
1936
+ }
1937
+ setPending(true);
1938
+ if (await controller.updateTask(task.id, {
1939
+ title,
1940
+ description,
1941
+ prompt
1942
+ })) {
1943
+ onClose();
1944
+ return;
1945
+ }
1946
+ setPending(false);
1947
+ setError(controller.getSnapshot().transportError ?? t("new.required"));
1948
+ };
1949
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1950
+ className: board_module_css_default.modalBackdrop,
1951
+ onMouseDown: (event) => {
1952
+ if (event.target === event.currentTarget) onClose();
1953
+ },
1954
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
1955
+ className: board_module_css_default.modal,
1956
+ role: "dialog",
1957
+ "aria-label": t("edit.title"),
1958
+ onSubmit: (event) => {
1959
+ event.preventDefault();
1960
+ submit();
1961
+ },
1962
+ children: [
1963
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1964
+ className: board_module_css_default.modalTitle,
1965
+ children: t("edit.title")
1966
+ }),
1967
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1968
+ className: board_module_css_default.field,
1969
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1970
+ className: board_module_css_default.fieldLabel,
1971
+ children: t("new.title")
1972
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1973
+ className: board_module_css_default.input,
1974
+ value: title,
1975
+ autoFocus: true,
1976
+ placeholder: t("new.titlePlaceholder"),
1977
+ onChange: (event) => {
1978
+ setTitle(event.target.value);
1979
+ setError(void 0);
1980
+ }
1981
+ })]
1982
+ }),
1983
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1984
+ className: board_module_css_default.field,
1985
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1986
+ className: board_module_css_default.fieldLabel,
1987
+ children: t("new.description")
1988
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1989
+ className: board_module_css_default.input,
1990
+ rows: 3,
1991
+ value: description,
1992
+ placeholder: t("new.descriptionPlaceholder"),
1993
+ onChange: (event) => {
1994
+ setDescription(event.target.value);
1995
+ }
1996
+ })]
1997
+ }),
1998
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1999
+ className: board_module_css_default.field,
2000
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2001
+ className: board_module_css_default.fieldLabel,
2002
+ children: t("new.prompt")
2003
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
2004
+ className: board_module_css_default.input,
2005
+ rows: 4,
2006
+ value: prompt,
2007
+ placeholder: t("new.promptPlaceholder"),
2008
+ onChange: (event) => {
2009
+ setPrompt(event.target.value);
2010
+ }
2011
+ })]
2012
+ }),
2013
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2014
+ className: board_module_css_default.formError,
2015
+ children: error
2016
+ }),
2017
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2018
+ className: board_module_css_default.modalFooter,
2019
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2020
+ type: "button",
2021
+ className: board_module_css_default.ghostButton,
2022
+ onClick: onClose,
2023
+ children: t("new.cancel")
2024
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2025
+ type: "submit",
2026
+ className: board_module_css_default.primaryButton,
2027
+ disabled: pending,
2028
+ children: t("edit.save")
2029
+ })]
2030
+ })
2031
+ ]
2032
+ })
2033
+ });
2034
+ }
2035
+ //#endregion
1880
2036
  //#region src/client/board/TaskDetail.tsx
1881
2037
  /**
1882
2038
  * Task detail: the full view of one task — content, prompt, execution
@@ -2156,10 +2312,14 @@ window.__ModuleLoader__.load({
2156
2312
  /** Task detail overlay. */
2157
2313
  function TaskDetail({ controller, task }) {
2158
2314
  const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
2315
+ const [showEdit, setShowEdit] = (0, react.useState)(false);
2159
2316
  const [latest, setLatest] = (0, react.useState)(task);
2160
2317
  (0, react.useEffect)(() => {
2161
2318
  setLatest(task);
2162
2319
  }, [task]);
2320
+ (0, react.useEffect)(() => {
2321
+ setShowEdit(false);
2322
+ }, [task.id]);
2163
2323
  const current = latest;
2164
2324
  const snapshot = controller.getSnapshot();
2165
2325
  const running = current.status === "running";
@@ -2172,177 +2332,196 @@ window.__ModuleLoader__.load({
2172
2332
  onMouseDown: (event) => {
2173
2333
  if (event.target === event.currentTarget) controller.closeTask();
2174
2334
  },
2175
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2176
- className: board_module_css_default.detail,
2177
- role: "dialog",
2178
- "aria-label": t("detail.title"),
2179
- children: [
2180
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2181
- className: board_module_css_default.detailHeader,
2182
- children: [
2183
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2184
- className: board_module_css_default.detailTitle,
2185
- children: current.title
2186
- }),
2187
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2188
- className: board_module_css_default.statusBadge,
2189
- "data-status": archived ? "archived" : current.status,
2190
- children: archived ? t("board.archive") : t(STATUS_KEY[current.status])
2191
- }),
2192
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2193
- type: "button",
2194
- className: board_module_css_default.iconButton,
2195
- "aria-label": t("detail.close"),
2196
- onClick: () => {
2197
- controller.closeTask();
2198
- },
2199
- children: "×"
2200
- })
2201
- ]
2202
- }),
2203
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2204
- className: board_module_css_default.detailBody,
2205
- children: [
2206
- transportError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2207
- className: board_module_css_default.formError,
2208
- children: [
2209
- t("board.hostError", { error: transportError }),
2210
- " ",
2211
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2212
- type: "button",
2213
- className: board_module_css_default.linkButton,
2214
- onClick: () => {
2215
- controller.retryHostSync();
2216
- },
2217
- children: t("board.retryHost")
2218
- })
2219
- ]
2220
- }),
2221
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2222
- className: board_module_css_default.detailSection,
2223
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.description") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2224
- className: board_module_css_default.detailText,
2225
- children: current.description !== "" ? current.description : "—"
2226
- })]
2227
- }),
2228
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2229
- className: board_module_css_default.detailSection,
2230
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.prompt") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
2231
- className: board_module_css_default.promptBlock,
2232
- children: current.prompt !== "" ? current.prompt : current.title
2233
- })]
2234
- }),
2235
- !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExecutionSettingsSection, {
2236
- controller,
2237
- task: current,
2238
- pending
2239
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScheduleSection, {
2240
- controller,
2241
- task: current,
2242
- pending
2243
- })] }),
2244
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2245
- className: board_module_css_default.detailSection,
2246
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.execution") }), current.executions.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2247
- className: board_module_css_default.detailText,
2248
- children: t("detail.noExecution")
2249
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2250
- className: board_module_css_default.executionList,
2251
- children: [...current.executions].reverse().map((execution) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExecutionRow, {
2252
- execution,
2253
- timeZone,
2254
- onOpen: (sessionId) => {
2255
- controller.openSession(sessionId);
2256
- }
2257
- }, execution.id))
2258
- })]
2259
- }),
2260
- !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2261
- className: board_module_css_default.detailSection,
2262
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("board.status") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2263
- className: board_module_css_default.moveRow,
2264
- children: MANUAL_STATUSES.map((status) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2265
- type: "button",
2266
- className: board_module_css_default.ghostButton,
2267
- disabled: current.status === status || running || pending,
2268
- onClick: () => {
2269
- controller.moveTask(current.id, status);
2270
- },
2271
- children: t(`status.move.${status}`)
2272
- }, status))
2273
- })]
2274
- })
2275
- ]
2276
- }),
2277
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2278
- className: board_module_css_default.detailFooter,
2279
- children: [
2280
- !archived && pending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2281
- className: board_module_css_default.detailMeta,
2282
- children: [t("board.pending"), "…"]
2283
- }),
2284
- !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2285
- type: "button",
2286
- className: board_module_css_default.primaryButton,
2287
- disabled: running || pending,
2288
- onClick: () => {
2289
- controller.rerunTask(current.id).then(() => {
2290
- if (controller.getSnapshot().transportError === void 0) controller.closeTask();
2291
- });
2292
- },
2293
- children: current.executions.length === 0 ? t("detail.run") : t("detail.rerun")
2294
- }),
2295
- archived ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2296
- type: "button",
2297
- className: board_module_css_default.primaryButton,
2298
- disabled: pending,
2299
- onClick: () => {
2300
- controller.restoreTask(current.id);
2301
- },
2302
- children: t("detail.restore")
2303
- }) : (current.status === "done" || current.status === "failed") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2304
- type: "button",
2305
- className: board_module_css_default.ghostButton,
2306
- disabled: pending,
2307
- onClick: () => {
2308
- controller.archiveTask(current.id);
2309
- },
2310
- children: t("detail.archive")
2311
- }),
2312
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2313
- type: "button",
2314
- className: board_module_css_default.dangerButton,
2315
- disabled: pending,
2316
- onClick: () => {
2317
- setConfirmDelete(true);
2318
- },
2319
- children: t("detail.delete")
2320
- }),
2321
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2322
- className: board_module_css_default.detailMeta,
2323
- children: [
2324
- t("board.created"),
2325
- " ",
2326
- formatTime(current.createdAt, timeZone),
2327
- archived && ` · ${t("detail.archivedAt", { time: formatTime(current.archivedAt, timeZone) })}`
2328
- ]
2329
- })
2330
- ]
2331
- })
2332
- ]
2333
- }), confirmDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
2334
- title: t("delete.title"),
2335
- message: t("delete.confirm", { name: current.title }),
2336
- confirmLabel: t("delete.ok"),
2337
- danger: true,
2338
- onCancel: () => {
2339
- setConfirmDelete(false);
2340
- },
2341
- onConfirm: () => {
2342
- setConfirmDelete(false);
2343
- controller.deleteTask(current.id);
2344
- }
2345
- })]
2335
+ children: [
2336
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2337
+ className: board_module_css_default.detail,
2338
+ role: "dialog",
2339
+ "aria-label": t("detail.title"),
2340
+ children: [
2341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2342
+ className: board_module_css_default.detailHeader,
2343
+ children: [
2344
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2345
+ className: board_module_css_default.detailTitle,
2346
+ children: current.title
2347
+ }),
2348
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2349
+ className: board_module_css_default.statusBadge,
2350
+ "data-status": archived ? "archived" : current.status,
2351
+ children: archived ? t("board.archive") : t(STATUS_KEY[current.status])
2352
+ }),
2353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2354
+ type: "button",
2355
+ className: board_module_css_default.iconButton,
2356
+ "aria-label": t("detail.close"),
2357
+ onClick: () => {
2358
+ controller.closeTask();
2359
+ },
2360
+ children: "×"
2361
+ })
2362
+ ]
2363
+ }),
2364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2365
+ className: board_module_css_default.detailBody,
2366
+ children: [
2367
+ transportError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2368
+ className: board_module_css_default.formError,
2369
+ children: [
2370
+ t("board.hostError", { error: transportError }),
2371
+ " ",
2372
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2373
+ type: "button",
2374
+ className: board_module_css_default.linkButton,
2375
+ onClick: () => {
2376
+ controller.retryHostSync();
2377
+ },
2378
+ children: t("board.retryHost")
2379
+ })
2380
+ ]
2381
+ }),
2382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2383
+ className: board_module_css_default.detailSection,
2384
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.description") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2385
+ className: board_module_css_default.detailText,
2386
+ children: current.description !== "" ? current.description : "—"
2387
+ })]
2388
+ }),
2389
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2390
+ className: board_module_css_default.detailSection,
2391
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.prompt") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
2392
+ className: board_module_css_default.promptBlock,
2393
+ children: current.prompt !== "" ? current.prompt : current.title
2394
+ })]
2395
+ }),
2396
+ !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExecutionSettingsSection, {
2397
+ controller,
2398
+ task: current,
2399
+ pending
2400
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScheduleSection, {
2401
+ controller,
2402
+ task: current,
2403
+ pending
2404
+ })] }),
2405
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2406
+ className: board_module_css_default.detailSection,
2407
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.execution") }), current.executions.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2408
+ className: board_module_css_default.detailText,
2409
+ children: t("detail.noExecution")
2410
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2411
+ className: board_module_css_default.executionList,
2412
+ children: [...current.executions].reverse().map((execution) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExecutionRow, {
2413
+ execution,
2414
+ timeZone,
2415
+ onOpen: (sessionId) => {
2416
+ controller.openSession(sessionId);
2417
+ }
2418
+ }, execution.id))
2419
+ })]
2420
+ }),
2421
+ !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2422
+ className: board_module_css_default.detailSection,
2423
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("board.status") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2424
+ className: board_module_css_default.moveRow,
2425
+ children: MANUAL_STATUSES.map((status) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2426
+ type: "button",
2427
+ className: board_module_css_default.ghostButton,
2428
+ disabled: current.status === status || running || pending,
2429
+ onClick: () => {
2430
+ controller.moveTask(current.id, status);
2431
+ },
2432
+ children: t(`status.move.${status}`)
2433
+ }, status))
2434
+ })]
2435
+ })
2436
+ ]
2437
+ }),
2438
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2439
+ className: board_module_css_default.detailFooter,
2440
+ children: [
2441
+ !archived && pending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2442
+ className: board_module_css_default.detailMeta,
2443
+ children: [t("board.pending"), "…"]
2444
+ }),
2445
+ !archived && canEditTaskContent(current) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2446
+ type: "button",
2447
+ className: board_module_css_default.ghostButton,
2448
+ disabled: pending,
2449
+ onClick: () => {
2450
+ setShowEdit(true);
2451
+ },
2452
+ children: t("detail.edit")
2453
+ }),
2454
+ !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2455
+ type: "button",
2456
+ className: board_module_css_default.primaryButton,
2457
+ disabled: running || pending,
2458
+ onClick: () => {
2459
+ controller.rerunTask(current.id).then(() => {
2460
+ if (controller.getSnapshot().transportError === void 0) controller.closeTask();
2461
+ });
2462
+ },
2463
+ children: current.executions.length === 0 ? t("detail.run") : t("detail.rerun")
2464
+ }),
2465
+ archived ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2466
+ type: "button",
2467
+ className: board_module_css_default.primaryButton,
2468
+ disabled: pending,
2469
+ onClick: () => {
2470
+ controller.restoreTask(current.id);
2471
+ },
2472
+ children: t("detail.restore")
2473
+ }) : (current.status === "done" || current.status === "failed") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2474
+ type: "button",
2475
+ className: board_module_css_default.ghostButton,
2476
+ disabled: pending,
2477
+ onClick: () => {
2478
+ controller.archiveTask(current.id);
2479
+ },
2480
+ children: t("detail.archive")
2481
+ }),
2482
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2483
+ type: "button",
2484
+ className: board_module_css_default.dangerButton,
2485
+ disabled: pending,
2486
+ onClick: () => {
2487
+ setConfirmDelete(true);
2488
+ },
2489
+ children: t("detail.delete")
2490
+ }),
2491
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2492
+ className: board_module_css_default.detailMeta,
2493
+ children: [
2494
+ t("board.created"),
2495
+ " ",
2496
+ formatTime(current.createdAt, timeZone),
2497
+ archived && ` · ${t("detail.archivedAt", { time: formatTime(current.archivedAt, timeZone) })}`
2498
+ ]
2499
+ })
2500
+ ]
2501
+ })
2502
+ ]
2503
+ }),
2504
+ confirmDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
2505
+ title: t("delete.title"),
2506
+ message: t("delete.confirm", { name: current.title }),
2507
+ confirmLabel: t("delete.ok"),
2508
+ danger: true,
2509
+ onCancel: () => {
2510
+ setConfirmDelete(false);
2511
+ },
2512
+ onConfirm: () => {
2513
+ setConfirmDelete(false);
2514
+ controller.deleteTask(current.id);
2515
+ }
2516
+ }),
2517
+ showEdit && !archived && canEditTaskContent(current) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditTaskModal, {
2518
+ controller,
2519
+ task: current,
2520
+ onClose: () => {
2521
+ setShowEdit(false);
2522
+ }
2523
+ })
2524
+ ]
2346
2525
  });
2347
2526
  }
2348
2527
  //#endregion
@@ -2767,7 +2946,7 @@ window.__ModuleLoader__.load({
2767
2946
  }
2768
2947
  //#endregion
2769
2948
  //#region \0dsh-css:packages/dsh-task-board/src/client/settings-card.module.css.mjs
2770
- const css = ".Jh0q7G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Jh0q7G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Jh0q7G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Jh0q7G_headerStatic{border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.Jh0q7G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Jh0q7G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Jh0q7G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Jh0q7G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_chevronOpen{transform:rotate(180deg)}.Jh0q7G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Jh0q7G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Jh0q7G_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Jh0q7G_discard,.Jh0q7G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Jh0q7G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Jh0q7G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Jh0q7G_discard:disabled,.Jh0q7G_save:disabled{opacity:.4;cursor:default}.Jh0q7G_discard:focus-visible,.Jh0q7G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Jh0q7G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Jh0q7G_field+.Jh0q7G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Jh0q7G_head{align-items:center;gap:8px;display:flex}.Jh0q7G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Jh0q7G_badges{align-items:center;gap:8px;display:inline-flex}.Jh0q7G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Jh0q7G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Jh0q7G_reset:disabled{cursor:default}.Jh0q7G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Jh0q7G_input,.Jh0q7G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_input:focus-visible,.Jh0q7G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Jh0q7G_input:disabled,.Jh0q7G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Jh0q7G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Jh0q7G_selectWrap{position:relative}.Jh0q7G_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.Jh0q7G_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Jh0q7G_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_selectChevronOpen{transform:rotate(180deg)}.Jh0q7G_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.Jh0q7G_selectPopupOpen{opacity:1;transform:none}.Jh0q7G_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.Jh0q7G_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;flex-shrink:0;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.Jh0q7G_selectOption:hover,.Jh0q7G_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.Jh0q7G_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.Jh0q7G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Jh0q7G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Jh0q7G_card,.Jh0q7G_header,.Jh0q7G_chevron,.Jh0q7G_chevronOpen,.Jh0q7G_discard,.Jh0q7G_save,.Jh0q7G_selectChevron,.Jh0q7G_selectChevronOpen,.Jh0q7G_selectPopup{transition:none}}";
2949
+ const css = ".Jh0q7G_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.Jh0q7G_card:hover{border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_header{appearance:none;box-sizing:border-box;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.Jh0q7G_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.Jh0q7G_headerStatic{box-sizing:border-box;border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.Jh0q7G_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.Jh0q7G_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.Jh0q7G_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.Jh0q7G_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_chevronOpen{transform:rotate(180deg)}.Jh0q7G_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.Jh0q7G_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.Jh0q7G_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.Jh0q7G_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.Jh0q7G_discard,.Jh0q7G_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.Jh0q7G_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.Jh0q7G_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.Jh0q7G_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.Jh0q7G_discard:disabled,.Jh0q7G_save:disabled{opacity:.4;cursor:default}.Jh0q7G_discard:focus-visible,.Jh0q7G_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.Jh0q7G_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.Jh0q7G_field+.Jh0q7G_field{border-top:1px solid var(--dsw-alias-border-l2)}.Jh0q7G_head{align-items:center;gap:8px;display:flex}.Jh0q7G_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.Jh0q7G_badges{align-items:center;gap:8px;display:inline-flex}.Jh0q7G_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.Jh0q7G_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.Jh0q7G_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.Jh0q7G_reset:disabled{cursor:default}.Jh0q7G_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.Jh0q7G_input,.Jh0q7G_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_input:focus-visible,.Jh0q7G_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.Jh0q7G_input:disabled,.Jh0q7G_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Jh0q7G_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.Jh0q7G_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.Jh0q7G_selectWrap{position:relative}.Jh0q7G_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.Jh0q7G_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.Jh0q7G_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.Jh0q7G_selectChevronOpen{transform:rotate(180deg)}.Jh0q7G_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.Jh0q7G_selectPopupOpen{opacity:1;transform:none}.Jh0q7G_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.Jh0q7G_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;flex-shrink:0;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.Jh0q7G_selectOption:hover,.Jh0q7G_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.Jh0q7G_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.Jh0q7G_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.Jh0q7G_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.Jh0q7G_card,.Jh0q7G_header,.Jh0q7G_chevron,.Jh0q7G_chevronOpen,.Jh0q7G_discard,.Jh0q7G_save,.Jh0q7G_selectChevron,.Jh0q7G_selectChevronOpen,.Jh0q7G_selectPopup{transition:none}}";
2771
2950
  const tagId = "@linxin666/dsh-client-ui-task-board/settings-card.module.css";
2772
2951
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2773
2952
  const tag = document.createElement("style");
@@ -2852,7 +3031,7 @@ window.__ModuleLoader__.load({
2852
3031
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2853
3032
  className: settings_card_module_css_default.description,
2854
3033
  title: description,
2855
- children: description
3034
+ children: props.descriptionNode ?? description
2856
3035
  })]
2857
3036
  }), state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2858
3037
  className: settings_card_module_css_default.pending,
@@ -2877,7 +3056,7 @@ window.__ModuleLoader__.load({
2877
3056
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2878
3057
  className: settings_card_module_css_default.description,
2879
3058
  title: description,
2880
- children: description
3059
+ children: props.descriptionNode ?? description
2881
3060
  })]
2882
3061
  }),
2883
3062
  state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -3675,7 +3854,7 @@ window.__ModuleLoader__.load({
3675
3854
  /** The building package's version, when the bundle carries it. */
3676
3855
  function bakedVersion() {
3677
3856
  try {
3678
- return "0.3.3";
3857
+ return "0.3.4";
3679
3858
  } catch {
3680
3859
  return;
3681
3860
  }