@linxin666/dsh-client-ui-task-board 0.1.17 → 0.1.18

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
@@ -9,6 +9,8 @@ window.__ModuleLoader__.load({
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
11
11
  //#region src/core/tasks.ts
12
+ /** Statuses a settled task may be archived from. */
13
+ const ARCHIVABLE_STATUSES = ["done", "failed"];
12
14
  /** Permission presets a task may pin on its execution session (the `/permission <id>` ids). */
13
15
  const TASK_PERMISSIONS = [
14
16
  "read-only",
@@ -166,44 +168,42 @@ window.__ModuleLoader__.load({
166
168
  return "running";
167
169
  }
168
170
  //#endregion
169
- //#region src/core/use-cases/task-create.ts
171
+ //#region src/core/use-cases/task-archive.ts
170
172
  /**
171
- * Create-task use case: mint a new task from user input, rejecting a blank
172
- * title. Pure ledger transition (no persistence or notify — the controller
173
- * orchestrates those), so it is unit-testable without any runtime face.
173
+ * Archive one task: only settled statuses (done/failed) can be archived;
174
+ * a running or not-yet-settled task stays on the board (its runner still
175
+ * owns its lifecycle). Already-archived tasks are a no-op.
174
176
  */
175
- /**
176
- * Apply a create against the current ledger. Returns the new task and the
177
- * appended ledger, or the unchanged ledger when the title is blank.
178
- * @param tasks - current ledger.
179
- * @param input - raw user input (title/description/prompt).
180
- * @param now - clock instant (ms epoch).
181
- * @param id - minted task id.
182
- */
183
- function applyCreateTask(tasks, input, now, id) {
184
- if (input.title.trim() === "") return {
185
- task: void 0,
186
- tasks
187
- };
188
- const task = createTask(input, now, id);
177
+ function applyArchiveTask(tasks, id, now) {
178
+ let applied = false;
189
179
  return {
190
- task,
191
- tasks: [...tasks, task]
180
+ tasks: tasks.map((task) => {
181
+ if (task.id !== id || task.archivedAt !== void 0) return task;
182
+ if (!ARCHIVABLE_STATUSES.includes(task.status)) return task;
183
+ applied = true;
184
+ return {
185
+ ...task,
186
+ archivedAt: now,
187
+ updatedAt: now
188
+ };
189
+ }),
190
+ archived: applied
192
191
  };
193
192
  }
194
- //#endregion
195
- //#region src/core/use-cases/task-delete.ts
196
- /**
197
- * Apply a delete across the ledger. The selection (a task id) is cleared when
198
- * it matches the removed task, so the UI never lingers on a vanished detail.
199
- * @param tasks - current ledger.
200
- * @param selectedTaskId - the currently selected task id (may be undefined).
201
- * @param id - the task to remove.
202
- */
203
- function applyDeleteTask(tasks, selectedTaskId, id) {
193
+ /** Restore one task back onto the main board (clears the archive marker). */
194
+ function applyRestoreTask(tasks, id, now) {
195
+ let applied = false;
204
196
  return {
205
- tasks: tasks.filter((task) => task.id !== id),
206
- selectionCleared: selectedTaskId === id
197
+ tasks: tasks.map((task) => {
198
+ if (task.id !== id || task.archivedAt === void 0) return task;
199
+ applied = true;
200
+ const { archivedAt: _archived, ...rest } = task;
201
+ return {
202
+ ...rest,
203
+ updatedAt: now
204
+ };
205
+ }),
206
+ archived: applied
207
207
  };
208
208
  }
209
209
  //#endregion
@@ -308,6 +308,56 @@ window.__ModuleLoader__.load({
308
308
  return /^\d+$/.test(value);
309
309
  }
310
310
  //#endregion
311
+ //#region src/core/use-cases/task-create.ts
312
+ /**
313
+ * Create-task use case: mint a new task from user input, rejecting a blank
314
+ * title. Pure ledger transition (no persistence or notify — the controller
315
+ * orchestrates those), so it is unit-testable without any runtime face.
316
+ */
317
+ /**
318
+ * Apply a create against the current ledger. Returns the new task and the
319
+ * appended ledger, or the unchanged ledger when the title is blank.
320
+ * @param tasks - current ledger.
321
+ * @param input - raw user input (title/description/prompt + optional schedule).
322
+ * @param now - clock instant (ms epoch).
323
+ * @param id - minted task id.
324
+ */
325
+ function applyCreateTask(tasks, input, now, id) {
326
+ if (input.title.trim() === "") return {
327
+ task: void 0,
328
+ tasks
329
+ };
330
+ let task = createTask(input, now, id);
331
+ const requested = input.schedule;
332
+ if (requested?.enabled === true && requested.cron.trim() !== "" && isValidCron(requested.cron)) {
333
+ const cron = requested.cron.trim();
334
+ task = withSchedule(task, {
335
+ enabled: true,
336
+ cron,
337
+ nextRunAt: nextRunAtMs(cron, now)
338
+ }, now);
339
+ }
340
+ return {
341
+ task,
342
+ tasks: [...tasks, task]
343
+ };
344
+ }
345
+ //#endregion
346
+ //#region src/core/use-cases/task-delete.ts
347
+ /**
348
+ * Apply a delete across the ledger. The selection (a task id) is cleared when
349
+ * it matches the removed task, so the UI never lingers on a vanished detail.
350
+ * @param tasks - current ledger.
351
+ * @param selectedTaskId - the currently selected task id (may be undefined).
352
+ * @param id - the task to remove.
353
+ */
354
+ function applyDeleteTask(tasks, selectedTaskId, id) {
355
+ return {
356
+ tasks: tasks.filter((task) => task.id !== id),
357
+ selectionCleared: selectedTaskId === id
358
+ };
359
+ }
360
+ //#endregion
311
361
  //#region src/core/use-cases/task-schedule.ts
312
362
  /**
313
363
  * Schedule use case: arm/disarm a task's cron rule and roll a rule forward.
@@ -436,6 +486,7 @@ window.__ModuleLoader__.load({
436
486
  deps;
437
487
  tasks = [];
438
488
  boardOpen = false;
489
+ archiveView = false;
439
490
  selectedTaskId;
440
491
  executionOptions = {
441
492
  workspaces: [],
@@ -476,6 +527,7 @@ window.__ModuleLoader__.load({
476
527
  return {
477
528
  tasks: this.tasks,
478
529
  boardOpen: this.boardOpen,
530
+ archiveView: this.archiveView,
479
531
  selectedTaskId: this.selectedTaskId,
480
532
  executionOptions: this.executionOptions
481
533
  };
@@ -501,6 +553,18 @@ window.__ModuleLoader__.load({
501
553
  if (this.boardOpen) this.closeBoard();
502
554
  else this.openBoard();
503
555
  }
556
+ /**
557
+ * Switch between the kanban columns and the archive view. Leaving the
558
+ * archive view with an archived task still selected closes the selection —
559
+ * the detail overlay must not linger over a task that is off-board.
560
+ */
561
+ toggleArchiveView() {
562
+ this.archiveView = !this.archiveView;
563
+ if (!this.archiveView && this.selectedTaskId !== void 0) {
564
+ if (this.tasks.find((task) => task.id === this.selectedTaskId)?.archivedAt !== void 0) this.selectedTaskId = void 0;
565
+ }
566
+ this.notify();
567
+ }
504
568
  openTask(id) {
505
569
  if (this.tasks.some((task) => task.id === id)) {
506
570
  this.selectedTaskId = id;
@@ -545,6 +609,27 @@ window.__ModuleLoader__.load({
545
609
  this.persistAndNotify();
546
610
  }
547
611
  /**
612
+ * Archive a settled task (done/failed). Running or on-board-unsettled
613
+ * tasks are refused so the runner keeps exclusive ownership of their
614
+ * lifecycle.
615
+ * @returns true when applied.
616
+ */
617
+ archiveTask(id) {
618
+ const { tasks, archived } = applyArchiveTask(this.tasks, id, this.now());
619
+ if (!archived) return false;
620
+ this.tasks = [...tasks];
621
+ this.persistAndNotify();
622
+ return true;
623
+ }
624
+ /** Restore an archived task back onto the board (same status column). */
625
+ restoreTask(id) {
626
+ const { tasks, archived } = applyRestoreTask(this.tasks, id, this.now());
627
+ if (!archived) return false;
628
+ this.tasks = [...tasks];
629
+ this.persistAndNotify();
630
+ return true;
631
+ }
632
+ /**
548
633
  * Update a task's schedule rule. A blank or invalid cron expression is
549
634
  * rejected (returns false, state untouched). When the rule ends up enabled
550
635
  * the next run instant is computed immediately; a disabled rule carries no
@@ -571,6 +656,17 @@ window.__ModuleLoader__.load({
571
656
  this.persistAndNotify();
572
657
  }
573
658
  /**
659
+ * Reload the ledger from the persisted store without notifying subscribers.
660
+ * The scheduler calls this before every tick so a task deleted in another
661
+ * tab (or a stale in-memory copy) can never be fired from this tab: the
662
+ * fire decision and the subsequent roll-forward both run on the freshest
663
+ * persisted truth. Deliberately silent — same-origin external changes still
664
+ * reach subscribers through the storage-event subscription.
665
+ */
666
+ reloadFromStore() {
667
+ this.tasks = this.deps.store.load();
668
+ }
669
+ /**
574
670
  * Jump to an execution's session transcript. Selecting the session changes
575
671
  * `current`, which closes the board (the conversation view takes over).
576
672
  * @param sessionId - the execution session to open.
@@ -729,6 +825,15 @@ window.__ModuleLoader__.load({
729
825
  */
730
826
  var ExecutionService = class {
731
827
  env;
828
+ /**
829
+ * In-flight preset switches keyed by `sessionId\u0000mode`. The session
830
+ * list mirror can lag behind a just-applied switch, so concurrent runs
831
+ * against one blank session would otherwise each issue the same select
832
+ * RPC (an amplified storm); sharing one in-flight call keeps the wire to
833
+ * a single request. The settled entry is forgotten so a later run can
834
+ * switch again.
835
+ */
836
+ presetSwitches = /* @__PURE__ */ new Map();
732
837
  /** @param env - the runtime faces (real or fake). */
733
838
  constructor(env) {
734
839
  this.env = env;
@@ -785,12 +890,13 @@ window.__ModuleLoader__.load({
785
890
  return false;
786
891
  }
787
892
  if (summary?.agentPreset === mode) return true;
788
- if (this.env.presets === void 0) {
893
+ const presets = this.env.presets;
894
+ if (presets === void 0) {
789
895
  settleFailed(`this deployment does not support agent presets (task asks for ${mode})`);
790
896
  return false;
791
897
  }
792
898
  try {
793
- const result = await this.env.presets.select(sessionId, mode);
899
+ const result = await this.switchPreset(presets, sessionId, mode);
794
900
  if (!result.ok) {
795
901
  if (presetAlreadyRuns(result.error, mode)) {
796
902
  this.env.sessions.noteAgentPreset?.(sessionId, mode);
@@ -807,6 +913,22 @@ window.__ModuleLoader__.load({
807
913
  return true;
808
914
  }
809
915
  /**
916
+ * One in-flight `select` per (session, preset): concurrent runs against
917
+ * the same blank session share the same wire call instead of each issuing
918
+ * a duplicate RPC. The entry is removed once the call settles, so a later
919
+ * run (after a shared failure, say) issues a fresh switch.
920
+ */
921
+ switchPreset(presets, sessionId, mode) {
922
+ const key = `${sessionId}\u0000${mode}`;
923
+ const inflight = this.presetSwitches.get(key);
924
+ if (inflight !== void 0) return inflight;
925
+ const attempt = presets.select(sessionId, mode).finally(() => {
926
+ if (this.presetSwitches.get(key) === attempt) this.presetSwitches.delete(key);
927
+ });
928
+ this.presetSwitches.set(key, attempt);
929
+ return attempt;
930
+ }
931
+ /**
810
932
  * Apply the task-pinned permission preset through the `/permission <id>`
811
933
  * slash command. No-op when the task pins none; fails the run when the
812
934
  * admission is rejected or no command claimed the line.
@@ -969,6 +1091,11 @@ window.__ModuleLoader__.load({
969
1091
  * a task still running at its due instant is skipped by the controller's
970
1092
  * runTask guard and simply waits for the next cron match.
971
1093
  *
1094
+ * A deleted task can never fire again from a stale in-memory copy: every
1095
+ * tick first re-reads the persisted ledger through the optional `refresh`
1096
+ * hook (the controller reloads its store), so the fire decision and the
1097
+ * subsequent roll-forward both run on the freshest persisted truth.
1098
+ *
972
1099
  * The ticker is controlled: `start` arms a single interval guarded by an
973
1100
  * idempotence check (a second start while running is a no-op, so wiring can
974
1101
  * never leak a duplicate timer), and `stop`/`dispose` clear the timer and
@@ -1043,6 +1170,7 @@ window.__ModuleLoader__.load({
1043
1170
  async tick() {
1044
1171
  if (this.disposed) return;
1045
1172
  if (this.deps.ready !== void 0 && !this.deps.ready()) return;
1173
+ this.deps.refresh?.();
1046
1174
  const now = this.deps.now();
1047
1175
  for (const task of this.deps.tasks()) {
1048
1176
  const schedule = task.schedule;
@@ -1161,6 +1289,7 @@ window.__ModuleLoader__.load({
1161
1289
  task.schedule = normalizeSchedule(row.schedule);
1162
1290
  task.workspaceId = normalizeTargetId(row.workspaceId);
1163
1291
  task.mode = normalizeTargetId(row.mode);
1292
+ task.archivedAt = typeof row.archivedAt === "number" && Number.isFinite(row.archivedAt) ? row.archivedAt : void 0;
1164
1293
  task.permission = isTaskPermission(row.permission) ? row.permission : void 0;
1165
1294
  tasks.push(task);
1166
1295
  }
@@ -1260,6 +1389,10 @@ window.__ModuleLoader__.load({
1260
1389
  "board.search": "筛选任务…",
1261
1390
  "board.empty": "这个状态还没有任务",
1262
1391
  "board.filterAll": "全部",
1392
+ "board.archive": "归档",
1393
+ "board.archiveView": "归档 ({count})",
1394
+ "board.backToBoard": "返回看板",
1395
+ "archive.empty": "没有已归档的任务",
1263
1396
  "board.status": "状态",
1264
1397
  "board.status.backlog": "待规划",
1265
1398
  "board.status.todo": "待办",
@@ -1287,6 +1420,9 @@ window.__ModuleLoader__.load({
1287
1420
  "detail.run": "执行",
1288
1421
  "detail.rerun": "重新执行",
1289
1422
  "detail.delete": "删除",
1423
+ "detail.archive": "归档",
1424
+ "detail.restore": "恢复",
1425
+ "detail.archivedAt": "已归档 · {time}",
1290
1426
  "detail.viewSession": "查看会话",
1291
1427
  "detail.noSession": "暂无会话",
1292
1428
  "detail.executionStarted": "已启动",
@@ -1364,6 +1500,10 @@ window.__ModuleLoader__.load({
1364
1500
  "board.search": "Filter tasks…",
1365
1501
  "board.empty": "No tasks in this column",
1366
1502
  "board.filterAll": "All",
1503
+ "board.archive": "Archive",
1504
+ "board.archiveView": "Archived ({count})",
1505
+ "board.backToBoard": "Back to board",
1506
+ "archive.empty": "No archived tasks",
1367
1507
  "board.status": "Status",
1368
1508
  "board.status.backlog": "Backlog",
1369
1509
  "board.status.todo": "To Do",
@@ -1391,6 +1531,9 @@ window.__ModuleLoader__.load({
1391
1531
  "detail.run": "Run",
1392
1532
  "detail.rerun": "Run Again",
1393
1533
  "detail.delete": "Delete",
1534
+ "detail.archive": "Archive",
1535
+ "detail.restore": "Restore",
1536
+ "detail.archivedAt": "Archived · {time}",
1394
1537
  "detail.viewSession": "View Session",
1395
1538
  "detail.noSession": "No session",
1396
1539
  "detail.executionStarted": "Started",
@@ -1546,6 +1689,27 @@ window.__ModuleLoader__.load({
1546
1689
  "statusDot": "_7D6uKa_statusDot"
1547
1690
  };
1548
1691
  //#endregion
1692
+ //#region src/client/schedule-presets.ts
1693
+ /** Common scheduled-run presets (cron → locale label). */
1694
+ const SCHEDULE_PRESETS = [
1695
+ {
1696
+ cron: "0 9 * * *",
1697
+ label: "detail.schedule.preset.daily9"
1698
+ },
1699
+ {
1700
+ cron: "0 * * * *",
1701
+ label: "detail.schedule.preset.hourly"
1702
+ },
1703
+ {
1704
+ cron: "*/10 * * * *",
1705
+ label: "detail.schedule.preset.tenMin"
1706
+ },
1707
+ {
1708
+ cron: "0 9 * * 1",
1709
+ label: "detail.schedule.preset.weeklyMon9"
1710
+ }
1711
+ ];
1712
+ //#endregion
1549
1713
  //#region src/client/board/NewTaskModal.tsx
1550
1714
  /**
1551
1715
  * New-task modal: title + description + the prompt that execution will send.
@@ -1559,23 +1723,39 @@ window.__ModuleLoader__.load({
1559
1723
  const [workspaceId, setWorkspaceId] = (0, react.useState)("");
1560
1724
  const [mode, setMode] = (0, react.useState)("");
1561
1725
  const [permission, setPermission] = (0, react.useState)("");
1726
+ const [scheduleEnabled, setScheduleEnabled] = (0, react.useState)(false);
1727
+ const [scheduleCron, setScheduleCron] = (0, react.useState)("");
1728
+ const [scheduleError, setScheduleError] = (0, react.useState)(void 0);
1562
1729
  const [error, setError] = (0, react.useState)(void 0);
1563
1730
  const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
1564
1731
  (0, react.useEffect)(() => controller.subscribe(() => setOptions(controller.getSnapshot().executionOptions)), [controller]);
1565
1732
  const submit = () => {
1733
+ if (scheduleEnabled) {
1734
+ const cron = scheduleCron.trim();
1735
+ if (cron === "" || !isValidCron(cron)) {
1736
+ setScheduleError(t("detail.schedule.invalid"));
1737
+ return;
1738
+ }
1739
+ }
1566
1740
  if (controller.createTask({
1567
1741
  title,
1568
1742
  description,
1569
1743
  prompt,
1570
1744
  workspaceId: workspaceId === "" ? void 0 : workspaceId,
1571
1745
  mode: mode === "" ? void 0 : mode,
1572
- permission: permission === "" ? void 0 : permission
1746
+ permission: permission === "" ? void 0 : permission,
1747
+ schedule: scheduleEnabled ? {
1748
+ enabled: true,
1749
+ cron: scheduleCron.trim()
1750
+ } : void 0
1573
1751
  }) === void 0) {
1574
1752
  setError(t("new.required"));
1575
1753
  return;
1576
1754
  }
1577
1755
  onClose();
1578
1756
  };
1757
+ /** Next-run preview for a valid armed cron (creation-time only). */
1758
+ const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now()) : void 0;
1579
1759
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1580
1760
  className: board_module_css_default.modalBackdrop,
1581
1761
  onMouseDown: (event) => {
@@ -1705,6 +1885,67 @@ window.__ModuleLoader__.load({
1705
1885
  }, id))]
1706
1886
  })]
1707
1887
  }),
1888
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1889
+ className: board_module_css_default.detailSection,
1890
+ children: [
1891
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.schedule") }),
1892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1893
+ className: board_module_css_default.scheduleToggle,
1894
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1895
+ type: "checkbox",
1896
+ checked: scheduleEnabled,
1897
+ onChange: (event) => {
1898
+ setScheduleEnabled(event.target.checked);
1899
+ if (!event.target.checked) setScheduleError(void 0);
1900
+ }
1901
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
1902
+ }),
1903
+ scheduleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1905
+ className: board_module_css_default.scheduleRow,
1906
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1907
+ className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
1908
+ value: scheduleCron,
1909
+ placeholder: "0 9 * * *",
1910
+ spellCheck: false,
1911
+ "aria-label": t("detail.schedule.cron"),
1912
+ onChange: (event) => {
1913
+ setScheduleCron(event.target.value);
1914
+ setScheduleError(void 0);
1915
+ }
1916
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1917
+ className: board_module_css_default.schedulePreset,
1918
+ value: "",
1919
+ "aria-label": t("detail.schedule.presets"),
1920
+ onChange: (event) => {
1921
+ if (event.target.value === "") return;
1922
+ setScheduleCron(event.target.value);
1923
+ setScheduleError(void 0);
1924
+ },
1925
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1926
+ value: "",
1927
+ children: [t("detail.schedule.presets"), "…"]
1928
+ }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1929
+ value: preset.cron,
1930
+ children: t(preset.label)
1931
+ }, preset.cron))]
1932
+ })]
1933
+ }),
1934
+ scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1935
+ className: board_module_css_default.formError,
1936
+ children: scheduleError
1937
+ }),
1938
+ scheduleError === void 0 && scheduleNextRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1939
+ className: board_module_css_default.scheduleMeta,
1940
+ children: [
1941
+ t("detail.schedule.nextRun"),
1942
+ " ",
1943
+ new Date(scheduleNextRun).toLocaleString()
1944
+ ]
1945
+ })
1946
+ ] })
1947
+ ]
1948
+ }),
1708
1949
  error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1709
1950
  className: board_module_css_default.formError,
1710
1951
  children: error
@@ -1912,25 +2153,6 @@ window.__ModuleLoader__.load({
1912
2153
  ]
1913
2154
  });
1914
2155
  }
1915
- /** Common scheduled-run presets (cron → locale label). */
1916
- const SCHEDULE_PRESETS = [
1917
- {
1918
- cron: "0 9 * * *",
1919
- label: "detail.schedule.preset.daily9"
1920
- },
1921
- {
1922
- cron: "0 * * * *",
1923
- label: "detail.schedule.preset.hourly"
1924
- },
1925
- {
1926
- cron: "*/10 * * * *",
1927
- label: "detail.schedule.preset.tenMin"
1928
- },
1929
- {
1930
- cron: "0 9 * * 1",
1931
- label: "detail.schedule.preset.weeklyMon9"
1932
- }
1933
- ];
1934
2156
  /** The execution-target editor: workspace / mode / permission pickers. */
1935
2157
  function ExecutionSettingsSection({ controller, task }) {
1936
2158
  const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
@@ -2260,6 +2482,23 @@ window.__ModuleLoader__.load({
2260
2482
  },
2261
2483
  children: current.executions.length === 0 ? t("detail.run") : t("detail.rerun")
2262
2484
  }),
2485
+ current.archivedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2486
+ type: "button",
2487
+ className: board_module_css_default.primaryButton,
2488
+ onClick: () => {
2489
+ controller.restoreTask(current.id);
2490
+ controller.closeTask();
2491
+ },
2492
+ children: t("detail.restore")
2493
+ }) : (current.status === "done" || current.status === "failed") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2494
+ type: "button",
2495
+ className: board_module_css_default.ghostButton,
2496
+ onClick: () => {
2497
+ controller.archiveTask(current.id);
2498
+ controller.closeTask();
2499
+ },
2500
+ children: t("detail.archive")
2501
+ }),
2263
2502
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2264
2503
  type: "button",
2265
2504
  className: board_module_css_default.dangerButton,
@@ -2273,7 +2512,8 @@ window.__ModuleLoader__.load({
2273
2512
  children: [
2274
2513
  t("board.created"),
2275
2514
  " ",
2276
- formatTime(current.createdAt)
2515
+ formatTime(current.createdAt),
2516
+ current.archivedAt !== void 0 && ` · ${t("detail.archivedAt", { time: formatTime(current.archivedAt) })}`
2277
2517
  ]
2278
2518
  })
2279
2519
  ]
@@ -2337,7 +2577,8 @@ window.__ModuleLoader__.load({
2337
2577
  const [filter, setFilter] = (0, react.useState)("");
2338
2578
  const [showNew, setShowNew] = (0, react.useState)(false);
2339
2579
  const selected = selectedTaskOf(snapshot);
2340
- const visible = snapshot.tasks.filter((task) => matchesFilter(task, filter));
2580
+ const archiveView = snapshot.archiveView;
2581
+ const visible = snapshot.tasks.filter((task) => (archiveView ? task.archivedAt !== void 0 : task.archivedAt === void 0) && matchesFilter(task, filter));
2341
2582
  const openTask = (0, react.useCallback)((id) => {
2342
2583
  controller.openTask(id);
2343
2584
  }, [controller]);
@@ -2362,6 +2603,14 @@ window.__ModuleLoader__.load({
2362
2603
  },
2363
2604
  "aria-label": t("board.search")
2364
2605
  }),
2606
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2607
+ type: "button",
2608
+ className: archiveView ? board_module_css_default.primaryButton : board_module_css_default.ghostButton,
2609
+ onClick: () => {
2610
+ controller.toggleArchiveView();
2611
+ },
2612
+ children: archiveView ? t("board.backToBoard") : t("board.archiveView", { count: String(snapshot.tasks.filter((task) => task.archivedAt !== void 0).length) })
2613
+ }),
2365
2614
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2366
2615
  type: "button",
2367
2616
  className: board_module_css_default.primaryButton,
@@ -2382,7 +2631,29 @@ window.__ModuleLoader__.load({
2382
2631
  }),
2383
2632
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2384
2633
  className: board_module_css_default.columns,
2385
- children: COLUMNS.map((column) => {
2634
+ children: archiveView ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2635
+ className: board_module_css_default.column,
2636
+ "data-status": "archived",
2637
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2638
+ className: board_module_css_default.columnHeader,
2639
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2640
+ className: board_module_css_default.columnTitle,
2641
+ children: t("board.archive")
2642
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2643
+ className: board_module_css_default.columnCount,
2644
+ children: visible.length
2645
+ })]
2646
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2647
+ className: board_module_css_default.cards,
2648
+ children: [visible.map((task) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoTaskCard, {
2649
+ task,
2650
+ onOpen: openTask
2651
+ }, task.id)), visible.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2652
+ className: board_module_css_default.columnEmpty,
2653
+ children: t("archive.empty")
2654
+ })]
2655
+ })]
2656
+ }) : COLUMNS.map((column) => {
2386
2657
  const tasks = visible.filter((task) => task.status === column.status);
2387
2658
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2388
2659
  className: board_module_css_default.column,
@@ -2622,7 +2893,7 @@ window.__ModuleLoader__.load({
2622
2893
  }
2623
2894
  //#endregion
2624
2895
  //#region \0dsh-css:packages/dsh-task-board/src/client/settings-card.module.css.mjs
2625
- 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_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_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{transition:none}}";
2896
+ 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_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{transition:none}}";
2626
2897
  const tagId = "@linxin666/dsh-client-ui-task-board/settings-card.module.css";
2627
2898
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2628
2899
  const tag = document.createElement("style");
@@ -2647,6 +2918,7 @@ window.__ModuleLoader__.load({
2647
2918
  "head": "Jh0q7G_head",
2648
2919
  "headText": "Jh0q7G_headText",
2649
2920
  "header": "Jh0q7G_header",
2921
+ "headerStatic": "Jh0q7G_headerStatic",
2650
2922
  "hint": "Jh0q7G_hint",
2651
2923
  "input": "Jh0q7G_input",
2652
2924
  "inputInvalid": "Jh0q7G_inputInvalid",
@@ -2676,24 +2948,42 @@ window.__ModuleLoader__.load({
2676
2948
  * @returns the card, or nothing while the namespace is still loading.
2677
2949
  */
2678
2950
  function PluginSettingsCard(props) {
2679
- const [open, setOpen] = (0, react.useState)(false);
2680
- const { state } = props;
2951
+ const [open, setOpen] = (0, react.useState)(props.defaultOpen ?? true);
2952
+ const { state, alwaysOpen } = props;
2681
2953
  if (!state.available) return null;
2682
2954
  const title = props.t(props.titleKey);
2683
2955
  const description = props.t(props.descriptionKey);
2684
2956
  const blocked = !state.dirty || state.invalid || state.saving;
2685
- const cardClass = open ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
2686
- if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2687
- className: cardClass,
2688
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2689
- type: "button",
2690
- className: settings_card_module_css_default.header,
2691
- "aria-expanded": open,
2692
- "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2693
- onClick: () => {
2694
- setOpen(!open);
2695
- },
2696
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2957
+ const expanded = alwaysOpen === true || open;
2958
+ const cardClass = expanded ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
2959
+ const header = alwaysOpen === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2960
+ className: settings_card_module_css_default.headerStatic,
2961
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2962
+ className: settings_card_module_css_default.headText,
2963
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2964
+ className: settings_card_module_css_default.name,
2965
+ title,
2966
+ children: title
2967
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2968
+ className: settings_card_module_css_default.description,
2969
+ title: description,
2970
+ children: description
2971
+ })]
2972
+ }), state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2973
+ className: settings_card_module_css_default.pending,
2974
+ title: props.t("settings.unsaved"),
2975
+ children: props.t("settings.unsaved")
2976
+ }) : null]
2977
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2978
+ type: "button",
2979
+ className: settings_card_module_css_default.header,
2980
+ "aria-expanded": open,
2981
+ "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2982
+ onClick: () => {
2983
+ setOpen(!open);
2984
+ },
2985
+ children: [
2986
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2697
2987
  className: settings_card_module_css_default.headText,
2698
2988
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2699
2989
  className: settings_card_module_css_default.name,
@@ -2704,7 +2994,13 @@ window.__ModuleLoader__.load({
2704
2994
  title: description,
2705
2995
  children: description
2706
2996
  })]
2707
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2997
+ }),
2998
+ state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2999
+ className: settings_card_module_css_default.pending,
3000
+ title: props.t("settings.unsaved"),
3001
+ children: props.t("settings.unsaved")
3002
+ }) : null,
3003
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2708
3004
  width: "14",
2709
3005
  height: "14",
2710
3006
  viewBox: "0 0 14 14",
@@ -2715,8 +3011,12 @@ window.__ModuleLoader__.load({
2715
3011
  d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
2716
3012
  fill: "currentColor"
2717
3013
  })
2718
- })]
2719
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3014
+ })
3015
+ ]
3016
+ });
3017
+ if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
3018
+ className: cardClass,
3019
+ children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2720
3020
  className: settings_card_module_css_default.body,
2721
3021
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2722
3022
  className: settings_card_module_css_default.notExposed,
@@ -2727,46 +3027,7 @@ window.__ModuleLoader__.load({
2727
3027
  });
2728
3028
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2729
3029
  className: cardClass,
2730
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2731
- type: "button",
2732
- className: settings_card_module_css_default.header,
2733
- "aria-expanded": open,
2734
- "aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
2735
- onClick: () => {
2736
- setOpen(!open);
2737
- },
2738
- children: [
2739
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2740
- className: settings_card_module_css_default.headText,
2741
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2742
- className: settings_card_module_css_default.name,
2743
- title,
2744
- children: title
2745
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2746
- className: settings_card_module_css_default.description,
2747
- title: description,
2748
- children: description
2749
- })]
2750
- }),
2751
- state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2752
- className: settings_card_module_css_default.pending,
2753
- title: props.t("settings.unsaved"),
2754
- children: props.t("settings.unsaved")
2755
- }) : null,
2756
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2757
- width: "14",
2758
- height: "14",
2759
- viewBox: "0 0 14 14",
2760
- fill: "none",
2761
- xmlns: "http://www.w3.org/2000/svg",
2762
- className: open ? `${settings_card_module_css_default.chevron} ${settings_card_module_css_default.chevronOpen}` : settings_card_module_css_default.chevron,
2763
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2764
- d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
2765
- fill: "currentColor"
2766
- })
2767
- })
2768
- ]
2769
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3030
+ children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2770
3031
  className: settings_card_module_css_default.body,
2771
3032
  children: [
2772
3033
  !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -3291,6 +3552,7 @@ window.__ModuleLoader__.load({
3291
3552
  controller.start();
3292
3553
  const scheduler = new SchedulerService({
3293
3554
  tasks: () => controller.getSnapshot().tasks,
3555
+ refresh: () => controller.reloadFromStore(),
3294
3556
  now: () => Date.now(),
3295
3557
  runTask: (id) => controller.runTask(id),
3296
3558
  applySchedule: (id, nextRunAt, lastTriggeredAt) => controller.applyScheduleNextRun(id, nextRunAt, lastTriggeredAt),