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

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
@@ -58,6 +58,10 @@ window.__ModuleLoader__.load({
58
58
  function isTaskStatus(value) {
59
59
  return typeof value === "string" && ALL_STATUSES.includes(value);
60
60
  }
61
+ /** Whether a manual move target is allowed from the given status. */
62
+ function canMoveManually(from, to) {
63
+ return from !== "running" && MANUAL_STATUSES.includes(to);
64
+ }
61
65
  /** Normalize one optional execution-target string: trim; blank collapses to undefined. */
62
66
  function normalizeTargetId(value) {
63
67
  const trimmed = value?.trim();
@@ -527,10 +531,6 @@ window.__ModuleLoader__.load({
527
531
  const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
528
532
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
529
533
  }
530
- /** Read the current selection off a session-list snapshot (structural). */
531
- function currentOf(sessions) {
532
- return sessions.list.getSnapshot().current;
533
- }
534
534
  function messageOf(error) {
535
535
  return error instanceof Error ? error.message : String(error);
536
536
  }
@@ -611,12 +611,10 @@ window.__ModuleLoader__.load({
611
611
  }
612
612
  openBoard() {
613
613
  if (this.boardOpen) return;
614
- this.lastCurrent = currentOf(this.deps.sessions);
615
614
  this.boardOpen = true;
616
615
  this.notify();
617
616
  }
618
617
  closeBoard() {
619
- if (!this.boardOpen) return;
620
618
  this.boardOpen = false;
621
619
  this.notify();
622
620
  }
@@ -804,6 +802,7 @@ window.__ModuleLoader__.load({
804
802
  * @param sessionId - the execution session to open.
805
803
  */
806
804
  openSession(sessionId) {
805
+ this.closeBoard();
807
806
  this.deps.sessions.open(sessionId);
808
807
  }
809
808
  /**
@@ -831,14 +830,17 @@ window.__ModuleLoader__.load({
831
830
  taskId: id
832
831
  }, id);
833
832
  }
834
- /** Close the board when the user navigates to another session. */
835
- onSessionsChanged() {
836
- if (!this.boardOpen) return;
837
- const current = currentOf(this.deps.sessions);
838
- if (current !== this.lastCurrent) this.closeBoard();
839
- this.lastCurrent = current;
840
- }
841
- lastCurrent = void 0;
833
+ /**
834
+ * Session-list notifications fire for all kinds of incidental churn
835
+ * (background navigation, the Host runner creating and selecting a fresh
836
+ * execution session, settlement, other plugins), so closing on `current`
837
+ * changes would evict the board without the user asking. The board closes
838
+ * only on explicit user navigation: a sidebar session/workspace row click
839
+ * (board-mount onClickSidebarRow) or the board's own actions
840
+ * (openSession / close). Keeping the hook preserves the subscription
841
+ * contract for future listeners.
842
+ */
843
+ onSessionsChanged() {}
842
844
  persistAndNotify() {
843
845
  if (this.deps.transport === void 0) this.deps.store.save(this.tasks);
844
846
  this.notify();
@@ -1496,6 +1498,95 @@ window.__ModuleLoader__.load({
1496
1498
  }
1497
1499
  ];
1498
1500
  //#endregion
1501
+ //#region src/client/board/TaskForm.tsx
1502
+ /** Modal overlay: closes on backdrop press, submits through the form. */
1503
+ function ModalShell({ ariaLabel, title, error, pending, submitLabel, onSubmit, onClose, children }) {
1504
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1505
+ className: board_module_css_default.modalBackdrop,
1506
+ onMouseDown: (event) => {
1507
+ if (event.target === event.currentTarget) onClose();
1508
+ },
1509
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
1510
+ className: board_module_css_default.modal,
1511
+ role: "dialog",
1512
+ "aria-label": ariaLabel,
1513
+ onSubmit: (event) => {
1514
+ event.preventDefault();
1515
+ onSubmit();
1516
+ },
1517
+ children: [
1518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1519
+ className: board_module_css_default.modalTitle,
1520
+ children: title
1521
+ }),
1522
+ children,
1523
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1524
+ className: board_module_css_default.formError,
1525
+ children: error
1526
+ }),
1527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
1528
+ className: board_module_css_default.modalFooter,
1529
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1530
+ type: "button",
1531
+ className: board_module_css_default.ghostButton,
1532
+ onClick: onClose,
1533
+ children: t("new.cancel")
1534
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1535
+ type: "submit",
1536
+ className: board_module_css_default.primaryButton,
1537
+ disabled: pending,
1538
+ children: submitLabel
1539
+ })]
1540
+ })
1541
+ ]
1542
+ })
1543
+ });
1544
+ }
1545
+ /** Title + description + prompt fields shared by the new and edit task forms. */
1546
+ function TaskContentFields({ title, description, prompt, onTitleChange, onDescriptionChange, onPromptChange }) {
1547
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1548
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1549
+ className: board_module_css_default.field,
1550
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1551
+ className: board_module_css_default.fieldLabel,
1552
+ children: t("new.title")
1553
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1554
+ className: board_module_css_default.input,
1555
+ value: title,
1556
+ autoFocus: true,
1557
+ placeholder: t("new.titlePlaceholder"),
1558
+ onChange: (event) => onTitleChange(event.target.value)
1559
+ })]
1560
+ }),
1561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1562
+ className: board_module_css_default.field,
1563
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1564
+ className: board_module_css_default.fieldLabel,
1565
+ children: t("new.description")
1566
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1567
+ className: board_module_css_default.input,
1568
+ rows: 3,
1569
+ value: description,
1570
+ placeholder: t("new.descriptionPlaceholder"),
1571
+ onChange: (event) => onDescriptionChange(event.target.value)
1572
+ })]
1573
+ }),
1574
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1575
+ className: board_module_css_default.field,
1576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1577
+ className: board_module_css_default.fieldLabel,
1578
+ children: t("new.prompt")
1579
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1580
+ className: board_module_css_default.input,
1581
+ rows: 4,
1582
+ value: prompt,
1583
+ placeholder: t("new.promptPlaceholder"),
1584
+ onChange: (event) => onPromptChange(event.target.value)
1585
+ })]
1586
+ })
1587
+ ] });
1588
+ }
1589
+ //#endregion
1499
1590
  //#region src/client/board/NewTaskModal.tsx
1500
1591
  /**
1501
1592
  * New-task modal: title + description + the prompt that execution will send.
@@ -1545,216 +1636,155 @@ window.__ModuleLoader__.load({
1545
1636
  };
1546
1637
  /** Next-run preview for a valid armed cron (creation-time only). */
1547
1638
  const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now()) : void 0;
1548
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1549
- className: board_module_css_default.modalBackdrop,
1550
- onMouseDown: (event) => {
1551
- if (event.target === event.currentTarget) onClose();
1639
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ModalShell, {
1640
+ ariaLabel: t("board.new"),
1641
+ title: t("board.new"),
1642
+ error,
1643
+ pending,
1644
+ submitLabel: t("new.submit"),
1645
+ onSubmit: () => {
1646
+ submit();
1552
1647
  },
1553
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
1554
- className: board_module_css_default.modal,
1555
- role: "dialog",
1556
- "aria-label": t("board.new"),
1557
- onSubmit: (event) => {
1558
- event.preventDefault();
1559
- submit();
1560
- },
1561
- children: [
1562
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1563
- className: board_module_css_default.modalTitle,
1564
- children: t("board.new")
1565
- }),
1566
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1567
- className: board_module_css_default.field,
1568
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1569
- className: board_module_css_default.fieldLabel,
1570
- children: t("new.title")
1571
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1572
- className: board_module_css_default.input,
1573
- value: title,
1574
- autoFocus: true,
1575
- placeholder: t("new.titlePlaceholder"),
1576
- onChange: (event) => {
1577
- setTitle(event.target.value);
1578
- setError(void 0);
1579
- }
1580
- })]
1581
- }),
1582
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1583
- className: board_module_css_default.field,
1584
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1585
- className: board_module_css_default.fieldLabel,
1586
- children: t("new.description")
1587
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1588
- className: board_module_css_default.input,
1589
- rows: 3,
1590
- value: description,
1591
- placeholder: t("new.descriptionPlaceholder"),
1592
- onChange: (event) => {
1593
- setDescription(event.target.value);
1594
- }
1595
- })]
1596
- }),
1597
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1598
- className: board_module_css_default.field,
1599
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1600
- className: board_module_css_default.fieldLabel,
1601
- children: t("new.prompt")
1602
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1603
- className: board_module_css_default.input,
1604
- rows: 4,
1605
- value: prompt,
1606
- placeholder: t("new.promptPlaceholder"),
1607
- onChange: (event) => {
1608
- setPrompt(event.target.value);
1609
- }
1610
- })]
1611
- }),
1612
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1613
- className: board_module_css_default.field,
1614
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1615
- className: board_module_css_default.fieldLabel,
1616
- children: t("new.workspace")
1617
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1618
- className: board_module_css_default.select,
1619
- value: workspaceId,
1620
- onChange: (event) => {
1621
- setWorkspaceId(event.target.value);
1622
- },
1623
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1624
- value: "",
1625
- children: t("exec.workspace.recent")
1626
- }), options.workspaces.map((workspace) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1627
- value: workspace.workspaceId,
1628
- children: workspace.title
1629
- }, workspace.workspaceId))]
1630
- })]
1631
- }),
1632
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1633
- className: board_module_css_default.field,
1634
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1635
- className: board_module_css_default.fieldLabel,
1636
- children: t("new.mode")
1637
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1638
- className: board_module_css_default.select,
1639
- value: mode,
1640
- onChange: (event) => {
1641
- setMode(event.target.value);
1642
- },
1643
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1644
- value: "",
1645
- children: t("exec.mode.default")
1646
- }), options.presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1647
- value: preset.id,
1648
- disabled: preset.broken !== void 0,
1649
- children: [
1650
- preset.name ?? preset.id,
1651
- preset.isDefault ? t("exec.mode.defaultSuffix") : "",
1652
- preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
1653
- ]
1654
- }, preset.id))]
1655
- })]
1656
- }),
1657
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1658
- className: board_module_css_default.field,
1659
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1660
- className: board_module_css_default.fieldLabel,
1661
- children: t("new.permission")
1662
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1663
- className: board_module_css_default.select,
1664
- value: permission,
1665
- onChange: (event) => {
1666
- setPermission(event.target.value);
1667
- },
1668
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1669
- value: "",
1670
- children: t("exec.permission.default")
1671
- }), TASK_PERMISSIONS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1672
- value: id,
1673
- children: t(`exec.permission.${id}`)
1674
- }, id))]
1675
- })]
1676
- }),
1677
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1678
- className: board_module_css_default.detailSection,
1679
- children: [
1680
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.schedule") }),
1681
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1682
- className: board_module_css_default.scheduleToggle,
1648
+ onClose,
1649
+ children: [
1650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
1651
+ title,
1652
+ description,
1653
+ prompt,
1654
+ onTitleChange: (value) => {
1655
+ setTitle(value);
1656
+ setError(void 0);
1657
+ },
1658
+ onDescriptionChange: setDescription,
1659
+ onPromptChange: setPrompt
1660
+ }),
1661
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1662
+ className: board_module_css_default.field,
1663
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1664
+ className: board_module_css_default.fieldLabel,
1665
+ children: t("new.workspace")
1666
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1667
+ className: board_module_css_default.select,
1668
+ value: workspaceId,
1669
+ onChange: (event) => {
1670
+ setWorkspaceId(event.target.value);
1671
+ },
1672
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1673
+ value: "",
1674
+ children: t("exec.workspace.recent")
1675
+ }), options.workspaces.map((workspace) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1676
+ value: workspace.workspaceId,
1677
+ children: workspace.title
1678
+ }, workspace.workspaceId))]
1679
+ })]
1680
+ }),
1681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1682
+ className: board_module_css_default.field,
1683
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1684
+ className: board_module_css_default.fieldLabel,
1685
+ children: t("new.mode")
1686
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1687
+ className: board_module_css_default.select,
1688
+ value: mode,
1689
+ onChange: (event) => {
1690
+ setMode(event.target.value);
1691
+ },
1692
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1693
+ value: "",
1694
+ children: t("exec.mode.default")
1695
+ }), options.presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1696
+ value: preset.id,
1697
+ disabled: preset.broken !== void 0,
1698
+ children: [
1699
+ preset.name ?? preset.id,
1700
+ preset.isDefault ? t("exec.mode.defaultSuffix") : "",
1701
+ preset.broken !== void 0 ? t("exec.mode.brokenSuffix") : ""
1702
+ ]
1703
+ }, preset.id))]
1704
+ })]
1705
+ }),
1706
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1707
+ className: board_module_css_default.field,
1708
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1709
+ className: board_module_css_default.fieldLabel,
1710
+ children: t("new.permission")
1711
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1712
+ className: board_module_css_default.select,
1713
+ value: permission,
1714
+ onChange: (event) => {
1715
+ setPermission(event.target.value);
1716
+ },
1717
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1718
+ value: "",
1719
+ children: t("exec.permission.default")
1720
+ }), TASK_PERMISSIONS.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1721
+ value: id,
1722
+ children: t(`exec.permission.${id}`)
1723
+ }, id))]
1724
+ })]
1725
+ }),
1726
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1727
+ className: board_module_css_default.detailSection,
1728
+ children: [
1729
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("detail.schedule") }),
1730
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1731
+ className: board_module_css_default.scheduleToggle,
1732
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1733
+ type: "checkbox",
1734
+ checked: scheduleEnabled,
1735
+ onChange: (event) => {
1736
+ setScheduleEnabled(event.target.checked);
1737
+ if (!event.target.checked) setScheduleError(void 0);
1738
+ }
1739
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
1740
+ }),
1741
+ scheduleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1742
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1743
+ className: board_module_css_default.scheduleRow,
1683
1744
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1684
- type: "checkbox",
1685
- checked: scheduleEnabled,
1745
+ className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
1746
+ value: scheduleCron,
1747
+ placeholder: "0 9 * * *",
1748
+ spellCheck: false,
1749
+ "aria-label": t("detail.schedule.cron"),
1686
1750
  onChange: (event) => {
1687
- setScheduleEnabled(event.target.checked);
1688
- if (!event.target.checked) setScheduleError(void 0);
1751
+ setScheduleCron(event.target.value);
1752
+ setScheduleError(void 0);
1689
1753
  }
1690
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
1691
- }),
1692
- scheduleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1693
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1694
- className: board_module_css_default.scheduleRow,
1695
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1696
- className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
1697
- value: scheduleCron,
1698
- placeholder: "0 9 * * *",
1699
- spellCheck: false,
1700
- "aria-label": t("detail.schedule.cron"),
1701
- onChange: (event) => {
1702
- setScheduleCron(event.target.value);
1703
- setScheduleError(void 0);
1704
- }
1705
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1706
- className: board_module_css_default.schedulePreset,
1754
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1755
+ className: board_module_css_default.schedulePreset,
1756
+ value: "",
1757
+ "aria-label": t("detail.schedule.presets"),
1758
+ onChange: (event) => {
1759
+ if (event.target.value === "") return;
1760
+ setScheduleCron(event.target.value);
1761
+ setScheduleError(void 0);
1762
+ },
1763
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1707
1764
  value: "",
1708
- "aria-label": t("detail.schedule.presets"),
1709
- onChange: (event) => {
1710
- if (event.target.value === "") return;
1711
- setScheduleCron(event.target.value);
1712
- setScheduleError(void 0);
1713
- },
1714
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
1715
- value: "",
1716
- children: [t("detail.schedule.presets"), "…"]
1717
- }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1718
- value: preset.cron,
1719
- children: t(preset.label)
1720
- }, preset.cron))]
1721
- })]
1722
- }),
1723
- scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1724
- className: board_module_css_default.formError,
1725
- children: scheduleError
1726
- }),
1727
- scheduleError === void 0 && scheduleNextRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1728
- className: board_module_css_default.scheduleMeta,
1729
- children: [
1730
- t("detail.schedule.nextRun"),
1731
- " ",
1732
- new Date(scheduleNextRun).toLocaleString()
1733
- ]
1734
- })
1735
- ] })
1736
- ]
1737
- }),
1738
- error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1739
- className: board_module_css_default.formError,
1740
- children: error
1741
- }),
1742
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
1743
- className: board_module_css_default.modalFooter,
1744
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1745
- type: "button",
1746
- className: board_module_css_default.ghostButton,
1747
- onClick: onClose,
1748
- children: t("new.cancel")
1749
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1750
- type: "submit",
1751
- className: board_module_css_default.primaryButton,
1752
- disabled: pending,
1753
- children: t("new.submit")
1754
- })]
1755
- })
1756
- ]
1757
- })
1765
+ children: [t("detail.schedule.presets"), "…"]
1766
+ }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1767
+ value: preset.cron,
1768
+ children: t(preset.label)
1769
+ }, preset.cron))]
1770
+ })]
1771
+ }),
1772
+ scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1773
+ className: board_module_css_default.formError,
1774
+ children: scheduleError
1775
+ }),
1776
+ scheduleError === void 0 && scheduleNextRun !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1777
+ className: board_module_css_default.scheduleMeta,
1778
+ children: [
1779
+ t("detail.schedule.nextRun"),
1780
+ " ",
1781
+ new Date(scheduleNextRun).toLocaleString()
1782
+ ]
1783
+ })
1784
+ ] })
1785
+ ]
1786
+ })
1787
+ ]
1758
1788
  });
1759
1789
  }
1760
1790
  //#endregion
@@ -1799,17 +1829,22 @@ window.__ModuleLoader__.load({
1799
1829
  if (timeZone !== void 0) return formatHostTimestamp(ms, timeZone);
1800
1830
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
1801
1831
  }
1802
- /** One card in a column. */
1803
1832
  function TaskCardInner({ task, pending, timeZone, onClick }) {
1804
1833
  const latest = task.executions[task.executions.length - 1];
1805
1834
  const runs = task.executions.length;
1806
1835
  const archived = task.archivedAt !== void 0;
1836
+ const isDraggable = !archived && task.status !== "running" && !pending;
1807
1837
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1808
1838
  type: "button",
1809
1839
  className: board_module_css_default.card,
1810
1840
  "data-status": archived ? "archived" : task.status,
1811
1841
  "data-dsh-part": "card",
1812
1842
  "data-pending": pending || void 0,
1843
+ draggable: isDraggable,
1844
+ onDragStart: isDraggable ? (event) => {
1845
+ event.dataTransfer.setData("text/plain", task.id);
1846
+ event.dataTransfer.effectAllowed = "move";
1847
+ } : void 0,
1813
1848
  onClick,
1814
1849
  title: task.description !== "" ? task.description : task.title,
1815
1850
  children: [
@@ -1946,89 +1981,26 @@ window.__ModuleLoader__.load({
1946
1981
  setPending(false);
1947
1982
  setError(controller.getSnapshot().transportError ?? t("new.required"));
1948
1983
  };
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();
1984
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalShell, {
1985
+ ariaLabel: t("edit.title"),
1986
+ title: t("edit.title"),
1987
+ error,
1988
+ pending,
1989
+ submitLabel: t("edit.save"),
1990
+ onSubmit: () => {
1991
+ submit();
1953
1992
  },
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();
1993
+ onClose,
1994
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
1995
+ title,
1996
+ description,
1997
+ prompt,
1998
+ onTitleChange: (value) => {
1999
+ setTitle(value);
2000
+ setError(void 0);
1961
2001
  },
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
- ]
2002
+ onDescriptionChange: setDescription,
2003
+ onPromptChange: setPrompt
2032
2004
  })
2033
2005
  });
2034
2006
  }
@@ -2669,10 +2641,22 @@ window.__ModuleLoader__.load({
2669
2641
  })]
2670
2642
  }) : COLUMNS.map((column) => {
2671
2643
  const tasks = visible.filter((task) => task.status === column.status);
2644
+ const isManualDropTarget = column.status === "backlog" || column.status === "todo";
2672
2645
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2673
2646
  className: board_module_css_default.column,
2674
2647
  "data-status": column.status,
2675
2648
  "data-dsh-part": "column",
2649
+ onDragOver: isManualDropTarget ? (event) => {
2650
+ event.preventDefault();
2651
+ event.dataTransfer.dropEffect = "move";
2652
+ } : void 0,
2653
+ onDrop: isManualDropTarget ? (event) => {
2654
+ event.preventDefault();
2655
+ const taskId = event.dataTransfer.getData("text/plain");
2656
+ if (!taskId) return;
2657
+ const dropped = snapshot.tasks.find((t) => t.id === taskId);
2658
+ if (dropped && canMoveManually(dropped.status, column.status) && dropped.status !== column.status) controller.moveTask(taskId, column.status);
2659
+ } : void 0,
2676
2660
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2677
2661
  className: board_module_css_default.columnHeader,
2678
2662
  children: [
@@ -2755,7 +2739,13 @@ window.__ModuleLoader__.load({
2755
2739
  let root;
2756
2740
  let container;
2757
2741
  const ensure = () => {
2758
- if (container !== void 0) return;
2742
+ if (container !== void 0) {
2743
+ if (container.isConnected) return;
2744
+ root?.unmount();
2745
+ root = void 0;
2746
+ container.remove();
2747
+ container = void 0;
2748
+ }
2759
2749
  const column = conversationColumn();
2760
2750
  if (column === void 0) return;
2761
2751
  container = document.createElement("div");
@@ -3854,7 +3844,7 @@ window.__ModuleLoader__.load({
3854
3844
  /** The building package's version, when the bundle carries it. */
3855
3845
  function bakedVersion() {
3856
3846
  try {
3857
- return "0.3.4";
3847
+ return "0.3.6";
3858
3848
  } catch {
3859
3849
  return;
3860
3850
  }