@linxin666/dsh-client-ui-task-board 0.3.21 → 0.3.23

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.
Files changed (40) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +5 -0
  3. package/README.zh.md +5 -0
  4. package/lib/client.js +723 -29
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2629 -158
  7. package/lib/types/client/board/EditTaskModal.d.ts +7 -1
  8. package/lib/types/client/board/NewTaskModal.d.ts +6 -1
  9. package/lib/types/client/board/TaskBoard.d.ts +10 -1
  10. package/lib/types/client/board/TaskForm.d.ts +18 -0
  11. package/lib/types/client/host-api.d.ts +23 -1
  12. package/lib/types/client/locales.d.ts +36 -0
  13. package/lib/types/core/controller.d.ts +35 -1
  14. package/lib/types/core/tasks.d.ts +60 -0
  15. package/lib/types/core/use-cases/task-update.d.ts +8 -1
  16. package/lib/types/host-ai.d.ts +51 -0
  17. package/lib/types/host-routes.d.ts +11 -1
  18. package/lib/types/index.d.ts +9 -0
  19. package/lib/types/protocol.d.ts +19 -0
  20. package/package.json +1 -1
  21. package/src/client/board/EditTaskModal.tsx +52 -3
  22. package/src/client/board/NewTaskModal.tsx +100 -5
  23. package/src/client/board/TaskBoard.tsx +150 -6
  24. package/src/client/board/TaskCard.tsx +17 -1
  25. package/src/client/board/TaskDetail.tsx +38 -2
  26. package/src/client/board/TaskForm.tsx +85 -0
  27. package/src/client/board.module.css +180 -0
  28. package/src/client/host-api.ts +124 -5
  29. package/src/client/index.ts +7 -0
  30. package/src/client/locales.ts +72 -0
  31. package/src/core/controller.ts +57 -1
  32. package/src/core/store.ts +5 -1
  33. package/src/core/tasks.ts +112 -0
  34. package/src/core/use-cases/task-update.ts +12 -2
  35. package/src/host-ai.ts +152 -0
  36. package/src/host-ledger.ts +25 -3
  37. package/src/host-routes.ts +68 -3
  38. package/src/host-runner.ts +24 -1
  39. package/src/index.ts +26 -1
  40. package/src/protocol.ts +45 -3
package/lib/client.js CHANGED
@@ -7,7 +7,58 @@ window.__ModuleLoader__.load({
7
7
  let react = require("react");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
9
  let react_dom_client = require("react-dom/client");
10
- //#region src/core/tasks.ts
10
+ /**
11
+ * Repair a persisted tag list: keep the well-formed entries, trim, drop
12
+ * blanks and repeats, cap the count, and collapse a blank prompt line to
13
+ * "display-only". Returns undefined when nothing usable remains, so the caller
14
+ * clears the field instead of storing an empty array.
15
+ */
16
+ function normalizeTags(value) {
17
+ if (!Array.isArray(value)) return void 0;
18
+ const tags = [];
19
+ const seen = /* @__PURE__ */ new Set();
20
+ for (const entry of value) {
21
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
22
+ const row = entry;
23
+ if (typeof row.name !== "string") continue;
24
+ const name = row.name.trim();
25
+ if (name === "" || name.length > 32 || seen.has(name)) continue;
26
+ const raw = typeof row.promptPrefix === "string" ? row.promptPrefix.trim() : "";
27
+ const promptPrefix = raw === "" ? void 0 : raw.slice(0, 200);
28
+ seen.add(name);
29
+ tags.push(promptPrefix === void 0 ? { name } : {
30
+ name,
31
+ promptPrefix
32
+ });
33
+ if (tags.length >= 8) break;
34
+ }
35
+ return tags.length === 0 ? void 0 : tags;
36
+ }
37
+ /**
38
+ * Stable palette slot (0..5) for a tag name. The same label always lands on the
39
+ * same tone, so a badge needs no stored colour and two tasks sharing a label
40
+ * cannot disagree about it.
41
+ */
42
+ function tagTone(name) {
43
+ let hash = 0;
44
+ for (const char of name) hash = hash * 31 + (char.codePointAt(0) ?? 0) >>> 0;
45
+ return hash % 6;
46
+ }
47
+ /**
48
+ * Union of the labels already carried by `tasks`, first occurrence wins (the
49
+ * oldest task's hint is the one offered). Feeds the editor's name datalist and
50
+ * the board's tag filter, so a label defined once can be reused everywhere.
51
+ */
52
+ function collectKnownTags(tasks) {
53
+ const known = [];
54
+ const seen = /* @__PURE__ */ new Set();
55
+ for (const task of tasks) for (const tag of task.tags ?? []) {
56
+ if (seen.has(tag.name)) continue;
57
+ seen.add(tag.name);
58
+ known.push(tag);
59
+ }
60
+ return known;
61
+ }
11
62
  /**
12
63
  * Statuses a task may be archived from: every status but `running`, whose
13
64
  * execution the runner still owns until it settles. A settled-only gate made
@@ -92,6 +143,7 @@ window.__ModuleLoader__.load({
92
143
  }
93
144
  /** Create a task from user input. */
94
145
  function createTask(input, now, id) {
146
+ const tags = normalizeTags(input.tags);
95
147
  return {
96
148
  id,
97
149
  title: input.title.trim(),
@@ -110,7 +162,8 @@ window.__ModuleLoader__.load({
110
162
  ...input.handover === void 0 ? {} : { handover: {
111
163
  ...input.handover,
112
164
  bundledAt: now
113
- } }
165
+ } },
166
+ ...tags === void 0 ? {} : { tags }
114
167
  };
115
168
  }
116
169
  /** Clone a task with an updated status and a fresh updatedAt. */
@@ -523,7 +576,7 @@ window.__ModuleLoader__.load({
523
576
  function applyUpdateTask(tasks, id, patch, now) {
524
577
  return tasks.map((task) => {
525
578
  if (task.id !== id) return task;
526
- const { freeze: freezePatch, handover: handoverPatch, ...rest } = patch;
579
+ const { freeze: freezePatch, handover: handoverPatch, tags: tagsPatch, ...rest } = patch;
527
580
  const workspaceId = "workspaceId" in patch ? normalizeTargetId(patch.workspaceId) : void 0;
528
581
  const mode = "mode" in patch ? normalizeTargetId(patch.mode) : void 0;
529
582
  const permission = "permission" in patch ? normalizePermission(task.permission, patch.permission) : void 0;
@@ -543,6 +596,7 @@ window.__ModuleLoader__.load({
543
596
  ...handoverPatch,
544
597
  bundledAt: now
545
598
  };
599
+ if ("tags" in patch) next.tags = tagsPatch == null ? void 0 : normalizeTags(tagsPatch);
546
600
  if ("permission" in patch && patch.permission !== void 0 && patch.permission !== task.permission || "handover" in patch) next.permissionConfirmedAt = void 0;
547
601
  if ("reuseSession" in patch) next.reuseSession = patch.reuseSession === true ? true : void 0;
548
602
  if (workspaceId !== void 0 || "workspaceId" in patch) next.workspaceId = workspaceId;
@@ -590,6 +644,7 @@ window.__ModuleLoader__.load({
590
644
  presets: [],
591
645
  models: []
592
646
  };
647
+ workspaceCreator;
593
648
  listeners = /* @__PURE__ */ new Set();
594
649
  disposers = [];
595
650
  now;
@@ -633,6 +688,8 @@ window.__ModuleLoader__.load({
633
688
  selectedTaskId: this.selectedTaskId,
634
689
  executionOptions: this.executionOptions,
635
690
  pendingTaskIds: [...this.pendingTaskIds],
691
+ ...this.workspaceCreator === void 0 ? {} : { canCreateWorkspace: true },
692
+ ...typeof this.deps.transport?.parseDraft === "function" ? { canParseTask: true } : {},
636
693
  ...this.transportError === void 0 ? {} : { transportError: this.transportError },
637
694
  ...this.hostState === void 0 ? {} : { host: this.hostState }
638
695
  };
@@ -735,6 +792,33 @@ window.__ModuleLoader__.load({
735
792
  };
736
793
  this.notify();
737
794
  }
795
+ /** Wire (or clear) the runtime's project registration face (issue #1536). */
796
+ setWorkspaceCreator(creator) {
797
+ this.workspaceCreator = creator;
798
+ this.notify();
799
+ }
800
+ /**
801
+ * Register an existing host directory as a DSH project, exactly as the GUI's
802
+ * own "add project" does; the runtime's failure message surfaces unchanged.
803
+ */
804
+ async createWorkspace(path) {
805
+ if (this.workspaceCreator === void 0) throw new Error("workspace creation is unavailable");
806
+ return await this.workspaceCreator(path);
807
+ }
808
+ /** Whether this deployment can parse pasted text into task fields (issue #1540). */
809
+ canParseTask() {
810
+ return typeof this.deps.transport?.parseDraft === "function";
811
+ }
812
+ /**
813
+ * Parse pasted text into task fields through the Host. The transport already
814
+ * phrases every failure for the user, so its message surfaces unchanged.
815
+ */
816
+ async parseTaskDraft(request, signal) {
817
+ const transport = this.deps.transport;
818
+ const parse = transport?.parseDraft;
819
+ if (transport === void 0 || parse === void 0) throw new Error("task parsing is unavailable");
820
+ return await parse.call(transport, request, signal);
821
+ }
738
822
  moveTask(id, status) {
739
823
  if (this.deps.transport !== void 0) {
740
824
  this.commitRemote({
@@ -1404,6 +1488,7 @@ window.__ModuleLoader__.load({
1404
1488
  task.reuseSession = row.reuseSession === true ? true : void 0;
1405
1489
  task.freeze = normalizeFreeze(row.freeze);
1406
1490
  task.handover = normalizeHandover(row.handover);
1491
+ task.tags = normalizeTags(row.tags);
1407
1492
  task.permissionConfirmedAt = typeof row.permissionConfirmedAt === "number" && Number.isFinite(row.permissionConfirmedAt) ? row.permissionConfirmedAt : void 0;
1408
1493
  tasks.push(task);
1409
1494
  }
@@ -1518,6 +1603,30 @@ window.__ModuleLoader__.load({
1518
1603
  "board.updated": "更新于",
1519
1604
  "board.created": "创建于",
1520
1605
  "board.hostError": "Host 操作失败:{error}",
1606
+ "board.hostError.notMounted": "任务看板的后台接口没有挂载:宿主可能没有加载该插件,也可能后台数据正被另一个 DSH 实例占用。重启 DSH 服务后再试",
1607
+ "board.hostError.unauthorized": "登录状态已失效,请刷新页面后重试",
1608
+ "board.hostError.locked": "任务看板的后台数据被占用:{detail}",
1609
+ "board.hostError.timeout": "后台 {seconds} 秒内没有响应,请稍后重试",
1610
+ "board.hostError.unreachable": "连接不上本机 DSH 服务,请确认它仍在运行",
1611
+ "board.hostError.unexpected": "后台返回了无法识别的响应(HTTP {status})",
1612
+ "board.project": "项目",
1613
+ "board.projectAll": "全部项目",
1614
+ "board.projectNew": "新建项目…",
1615
+ "board.projectNewPath": "项目目录(宿主上的绝对路径)",
1616
+ "board.projectNewPathPlaceholder": "例如 C:\\work\\my-project 或 /home/me/project",
1617
+ "board.projectCreate": "创建项目",
1618
+ "board.projectCreateFailed": "创建项目失败:{error}",
1619
+ "new.aiParse": "AI 解析",
1620
+ "new.aiParseHint": "把从别处复制的一段话粘进来,让模型整理成标题、描述和执行 Prompt;解析结果只填进下面的表单,你可以先改再创建。",
1621
+ "new.aiParsePlaceholder": "粘贴要变成任务的内容…",
1622
+ "new.aiParseModel": "解析用模型",
1623
+ "new.aiParseRun": "解析并填入",
1624
+ "new.aiParseCancel": "取消解析",
1625
+ "new.aiParseEmpty": "先粘贴一段内容再解析",
1626
+ "new.aiParseNoModel": "这个部署还没有可用的模型,请先在 DSH 里配置模型提供商,或换一个模型再试",
1627
+ "new.aiParseUnavailable": "后台没有解析接口可用,重启 DSH 服务后再试",
1628
+ "new.aiParseTimeout": "模型 {seconds} 秒内没有返回结果,请重试或换一个更快的模型",
1629
+ "new.aiParseFailed": "解析失败:{error}",
1521
1630
  "board.retryHost": "重试连接 Host",
1522
1631
  "board.hostMeta": "Host 时区 {timeZone} · revision {revision}",
1523
1632
  "new.title": "标题",
@@ -1544,11 +1653,23 @@ window.__ModuleLoader__.load({
1544
1653
  "detail.handover": "交接包",
1545
1654
  "detail.handover.bundledAt": "打包于 {time}",
1546
1655
  "detail.handover.references": "引用",
1656
+ "new.tags": "标签(可选)",
1657
+ "new.tagsHint": "标签用于卡片徽章与顶栏筛选;填了「执行提示」的行会在每次执行前注入 Prompt,留空则只作展示。",
1658
+ "new.tagName": "标签名",
1659
+ "new.tagNamePlaceholder": "例如:工作",
1660
+ "new.tagPrompt": "执行提示(可选)",
1661
+ "new.tagPromptPlaceholder": "例如:产物归档到 02-工作/",
1662
+ "new.tagAdd": "添加标签",
1663
+ "new.tagRemove": "移除标签 {name}",
1664
+ "board.tagFilter": "按标签筛选",
1665
+ "board.tagFilterClear": "清除标签筛选",
1666
+ "board.tagEmpty": "没有同时带这些标签的任务",
1547
1667
  "detail.permissionPending": "权限待确认:本卡有效权限({permission})高于会话默认,手动执行与定时调度都会被拒绝,需人工确认后才能运行。",
1548
1668
  "detail.permissionConfirm": "确认权限绑定",
1549
1669
  "detail.permissionConfirmed": "已确认权限绑定 · {time}",
1550
1670
  "detail.title": "任务详情",
1551
1671
  "detail.edit": "编辑",
1672
+ "detail.editTags": "编辑标签",
1552
1673
  "detail.duplicate": "复制为新任务",
1553
1674
  "detail.duplicateAndEdit": "修改并新建副本",
1554
1675
  "detail.close": "关闭",
@@ -1670,6 +1791,30 @@ window.__ModuleLoader__.load({
1670
1791
  "board.updated": "Updated",
1671
1792
  "board.created": "Created",
1672
1793
  "board.hostError": "Host action failed: {error}",
1794
+ "board.hostError.notMounted": "The task board Host API is not mounted: the Host may not have loaded the plugin, or another DSH instance holds the ledger. Restart the DSH service and try again",
1795
+ "board.hostError.unauthorized": "This session is no longer signed in; reload the page and try again",
1796
+ "board.hostError.locked": "The task board ledger is locked: {detail}",
1797
+ "board.hostError.timeout": "The Host did not answer within {seconds}s; try again shortly",
1798
+ "board.hostError.unreachable": "Cannot reach the local DSH service; make sure it is still running",
1799
+ "board.hostError.unexpected": "The Host returned an unrecognized response (HTTP {status})",
1800
+ "board.project": "Project",
1801
+ "board.projectAll": "All projects",
1802
+ "board.projectNew": "New project…",
1803
+ "board.projectNewPath": "Project directory (absolute path on the Host)",
1804
+ "board.projectNewPathPlaceholder": "e.g. C:\\work\\my-project or /home/me/project",
1805
+ "board.projectCreate": "Create project",
1806
+ "board.projectCreateFailed": "Could not create the project: {error}",
1807
+ "new.aiParse": "Parse with AI",
1808
+ "new.aiParseHint": "Paste text copied from somewhere else and let a model turn it into a title, description, and run prompt; the result only fills the form below, so you can edit it before creating the task.",
1809
+ "new.aiParsePlaceholder": "Paste the content to turn into a task…",
1810
+ "new.aiParseModel": "Model used for parsing",
1811
+ "new.aiParseRun": "Parse and fill",
1812
+ "new.aiParseCancel": "Cancel parsing",
1813
+ "new.aiParseEmpty": "Paste something to parse first",
1814
+ "new.aiParseNoModel": "This deployment has no model available; configure a model provider in DSH or pick another model",
1815
+ "new.aiParseUnavailable": "The Host has no parse endpoint; restart the DSH service and try again",
1816
+ "new.aiParseTimeout": "The model did not answer within {seconds}s; try again or pick a faster model",
1817
+ "new.aiParseFailed": "Parsing failed: {error}",
1673
1818
  "board.retryHost": "Retry Host connection",
1674
1819
  "board.hostMeta": "Host time zone {timeZone} · revision {revision}",
1675
1820
  "new.title": "Title",
@@ -1696,11 +1841,23 @@ window.__ModuleLoader__.load({
1696
1841
  "detail.handover": "Handover bundle",
1697
1842
  "detail.handover.bundledAt": "Bundled at {time}",
1698
1843
  "detail.handover.references": "References",
1844
+ "new.tags": "Tags (optional)",
1845
+ "new.tagsHint": "Tags drive the card badges and the board filter; a row with an execution hint is injected ahead of the prompt on every run, an empty one is display-only.",
1846
+ "new.tagName": "Tag name",
1847
+ "new.tagNamePlaceholder": "e.g. Work",
1848
+ "new.tagPrompt": "Execution hint (optional)",
1849
+ "new.tagPromptPlaceholder": "e.g. Archive artefacts under 02-work/",
1850
+ "new.tagAdd": "Add tag",
1851
+ "new.tagRemove": "Remove tag {name}",
1852
+ "board.tagFilter": "Filter by tag",
1853
+ "board.tagFilterClear": "Clear tag filter",
1854
+ "board.tagEmpty": "No task carries all of these tags",
1699
1855
  "detail.permissionPending": "Permission pending confirmation: this card's effective permission ({permission}) is above the session default; manual runs and cron are refused until a human confirms.",
1700
1856
  "detail.permissionConfirm": "Confirm permission binding",
1701
1857
  "detail.permissionConfirmed": "Permission confirmed · {time}",
1702
1858
  "detail.title": "Task Detail",
1703
1859
  "detail.edit": "Edit",
1860
+ "detail.editTags": "Edit tags",
1704
1861
  "detail.duplicate": "Duplicate Task",
1705
1862
  "detail.duplicateAndEdit": "Edit as New Copy",
1706
1863
  "detail.close": "Close",
@@ -1823,7 +1980,7 @@ window.__ModuleLoader__.load({
1823
1980
  }
1824
1981
  //#endregion
1825
1982
  //#region \0dsh-css:packages/dsh-task-board/src/client/board.module.css.mjs
1826
- const css$1 = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-taskboard-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0;container:_7D6uKa_task-board-view/inline-size}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-dsh-taskboard-view]{display:block}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-taskboard-view]),html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-taskboard-view]){display:none!important}._7D6uKa_entry{box-sizing:border-box;width:100%;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 10px;font-size:13px;display:flex}._7D6uKa_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}._7D6uKa_entryIcon{flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:inline-flex}._7D6uKa_entryIcon svg{width:18px;height:18px;display:block}._7D6uKa_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entry,[data-sidebar-collapsed] ._7D6uKa_entry{border-radius:50%;justify-content:center;width:36px;height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryLabel,[data-sidebar-collapsed] ._7D6uKa_entryLabel{display:none}._7D6uKa_board{box-sizing:border-box;background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:12px;padding:14px 16px 16px;display:flex}._7D6uKa_boardHeader{flex:none;align-items:center;gap:10px;display:flex}._7D6uKa_boardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}._7D6uKa_backButton{align-items:center;gap:4px;display:inline-flex}._7D6uKa_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}._7D6uKa_search::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_columns{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;grid-auto-columns:minmax(220px,1fr);grid-auto-flow:column;gap:12px;min-height:0;padding-bottom:6px;display:grid;overflow:auto hidden}._7D6uKa_columns::-webkit-scrollbar{height:10px}._7D6uKa_columns::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid #0000;border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-border-l4);background-clip:content-box}._7D6uKa_column{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}._7D6uKa_columnHeader{flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}._7D6uKa_columnTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}._7D6uKa_columnCount{min-width:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:1px 8px;font-size:12px}._7D6uKa_statusDot{border-radius:50%;flex:none;width:8px;height:8px}._7D6uKa_statusDot[data-status=backlog]{background:var(--dsw-alias-label-tertiary)}._7D6uKa_statusDot[data-status=todo]{background:var(--dsw-alias-state-business-primary)}._7D6uKa_statusDot[data-status=running]{background:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusDot[data-status=done]{background:var(--dsw-alias-state-success-primary)}._7D6uKa_statusDot[data-status=failed]{background:var(--dsw-alias-state-error-primary)}._7D6uKa_cards{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}._7D6uKa_columnEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}._7D6uKa_card{text-align:left;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;font-family:inherit;transition:box-shadow .12s,border-color .12s,transform .12s;display:flex}._7D6uKa_card:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}._7D6uKa_card[data-status=running]{border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}._7D6uKa_cardExcerpt{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}._7D6uKa_cardMeta{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}._7D6uKa_cardTime{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._7D6uKa_cardSchedule{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:2px 6px;font-size:12px;line-height:1}._7D6uKa_cardRun{flex:none}._7D6uKa_cardRun[data-result=failed]{color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardRun[data-result=succeeded]{color:var(--dsw-alias-state-success-primary)}._7D6uKa_cardSession{color:var(--dsw-alias-state-business-primary);flex:none}._7D6uKa_cardRunningLabel{color:var(--dsw-alias-state-warn-primary);font-size:11px}._7D6uKa_cardSpinner{border:2px solid var(--dsw-alias-state-warn-primary);border-top-color:#0000;border-radius:50%;flex:none;width:10px;height:10px;animation:.8s linear infinite _7D6uKa_dshTbSpin}@keyframes _7D6uKa_dshTbSpin{to{transform:rotate(360deg)}}._7D6uKa_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._7D6uKa_primaryButton:disabled{opacity:.5;cursor:default}._7D6uKa_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._7D6uKa_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7D6uKa_ghostButton:disabled{opacity:.45;cursor:default}._7D6uKa_dangerButton{color:#fff;background:var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_dangerButton:hover:not(:disabled){filter:brightness(1.08)}._7D6uKa_dangerButton:active:not(:disabled){filter:brightness(.94)}._7D6uKa_dangerButton:disabled{opacity:.5;cursor:default}._7D6uKa_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}._7D6uKa_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}._7D6uKa_linkButton:hover{text-decoration:underline}._7D6uKa_modalBackdrop{z-index:1300;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._7D6uKa_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}._7D6uKa_modalTitle{margin:0;font-size:15px;font-weight:700}._7D6uKa_confirmMessage{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.5}._7D6uKa_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}._7D6uKa_field{flex-direction:column;gap:5px;display:flex}._7D6uKa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._7D6uKa_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_select{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;max-width:100%;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._7D6uKa_detail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(640px,100vw - 48px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_detailHeader{border-bottom:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:14px 18px;display:flex}._7D6uKa_detailTitle{overflow-wrap:anywhere;flex:1;margin:0;font-size:15px;font-weight:700}._7D6uKa_statusBadge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:2px 10px;font-size:12px}._7D6uKa_statusBadge[data-status=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusBadge[data-status=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_statusBadge[data-status=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_detailBody{flex-direction:column;flex:1;gap:16px;padding:14px 18px;display:flex;overflow-y:auto}._7D6uKa_detailSection{flex-direction:column;gap:6px;display:flex}._7D6uKa_detailSection h4{color:var(--dsw-alias-label-tertiary);text-transform:none;margin:0;font-size:12px;font-weight:700}._7D6uKa_detailText{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.55}._7D6uKa_scheduleToggle{color:var(--dsw-alias-label-primary);cursor:pointer;user-select:none;align-items:center;gap:8px;font-size:13px;display:flex}._7D6uKa_scheduleToggle input{accent-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleRow{align-items:center;gap:8px;display:flex}._7D6uKa_scheduleInput{min-width:0;font-family:var(--dsw-font-markdown-code-block-small);flex:1;font-size:12.5px}._7D6uKa_scheduleInputInvalid,._7D6uKa_scheduleInputInvalid:focus{border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_schedulePreset{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:none;padding:7px 8px;font-size:12.5px}._7D6uKa_scheduleMeta{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px}._7D6uKa_promptBlock{font-size:12.5px;line-height:1.5;font-family:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;max-height:240px;margin:0;padding:10px 12px;overflow-y:auto}._7D6uKa_executionList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_executionRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:8px 10px;display:flex}._7D6uKa_executionBadge{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:600}._7D6uKa_executionBadge[data-result=succeeded]{color:var(--dsw-alias-state-success-primary);background:0 0}._7D6uKa_executionBadge[data-result=failed]{color:var(--dsw-alias-state-error-primary);background:0 0}._7D6uKa_executionBadge[data-result=cancelled]{color:var(--dsw-alias-label-tertiary);background:0 0}._7D6uKa_executionTimes{color:var(--dsw-alias-label-secondary);font-size:12px}._7D6uKa_executionError{width:100%;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px}._7D6uKa_moveRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_detailFooter{border-top:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:12px 18px;display:flex}._7D6uKa_detailMeta{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}@container _7D6uKa_task-board-view (width<=768px){._7D6uKa_board{gap:10px;padding:10px}._7D6uKa_boardHeader{flex-wrap:wrap;align-items:center;gap:8px}._7D6uKa_backButton{flex:none;order:1}._7D6uKa_boardTitle{flex:auto;order:2}._7D6uKa_boardHeader>._7D6uKa_detailMeta{flex:1 0 100%;order:3;margin-left:0}._7D6uKa_search{flex:1 0 100%;order:4;min-width:0}._7D6uKa_boardHeader>button:not(._7D6uKa_backButton){flex:1 1 0;order:5;min-width:0}._7D6uKa_columns{scroll-snap-type:inline mandatory;scrollbar-width:none;-webkit-overflow-scrolling:touch;grid-auto-columns:86cqw;gap:10px;padding-inline:2px 14cqw;scroll-padding-inline:2px}._7D6uKa_columns::-webkit-scrollbar{display:none}._7D6uKa_column{scroll-snap-align:start;scroll-snap-stop:always}}@container _7D6uKa_task-board-view (width<=720px){._7D6uKa_boardHeader>._7D6uKa_detailMeta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@container _7D6uKa_task-board-view (width<=600px){._7D6uKa_board{padding-inline:8px}}@media (width<=768px){[data-dsh-taskboard-view]{height:100dvh}._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle{min-height:44px}._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset{box-sizing:border-box;font-size:16px}._7D6uKa_modalBackdrop{justify-content:stretch;align-items:stretch;width:100vw;height:100dvh}._7D6uKa_modal,._7D6uKa_detail{box-sizing:border-box;border:0;border-radius:0;width:100vw;height:100dvh;max-height:none}._7D6uKa_modal{padding-top:max(16px, env(safe-area-inset-top));padding-right:max(16px, env(safe-area-inset-right));padding-bottom:max(16px, env(safe-area-inset-bottom));padding-left:max(16px, env(safe-area-inset-left))}._7D6uKa_modalFooter{z-index:1;background:var(--dsw-alias-bg-base);flex-wrap:wrap;padding-top:8px;position:sticky;bottom:0}._7D6uKa_modalFooter>button{flex:120px}._7D6uKa_detailHeader{padding-top:max(12px, env(safe-area-inset-top));padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailTitle{min-width:0}._7D6uKa_detailBody{overscroll-behavior-y:contain;padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left))}._7D6uKa_detailFooter{padding-right:max(14px, env(safe-area-inset-right));padding-bottom:max(12px, env(safe-area-inset-bottom));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailFooter>button{flex:96px}._7D6uKa_detailFooter>._7D6uKa_detailMeta{text-align:end;flex:1 0 100%;margin-left:0}._7D6uKa_scheduleRow{flex-direction:column;align-items:stretch}._7D6uKa_schedulePreset{width:100%}}._7D6uKa_entry:focus-visible,._7D6uKa_card:focus-visible,._7D6uKa_primaryButton:focus-visible,._7D6uKa_ghostButton:focus-visible,._7D6uKa_dangerButton:focus-visible,._7D6uKa_iconButton:focus-visible,._7D6uKa_linkButton:focus-visible,._7D6uKa_search:focus-visible,._7D6uKa_input:focus-visible,._7D6uKa_select:focus-visible,._7D6uKa_schedulePreset:focus-visible,._7D6uKa_scheduleToggle input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._7D6uKa_entry,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:background-color .12s,color .12s,border-color .12s,outline-color .12s,box-shadow .12s,transform .12s}._7D6uKa_card:active{box-shadow:var(--dsw-shadow-lv1);transform:translateY(0)}._7D6uKa_entry:active,._7D6uKa_primaryButton:active:not(:disabled),._7D6uKa_ghostButton:active:not(:disabled),._7D6uKa_dangerButton:active:not(:disabled),._7D6uKa_iconButton:active:not(:disabled),._7D6uKa_linkButton:active:not(:disabled){transform:translateY(1px)}._7D6uKa_entry[data-active]:hover{background:var(--dsw-specific-sidebar-nav-item-active)}._7D6uKa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton:hover:not(:disabled){text-decoration:underline}._7D6uKa_iconButton:disabled,._7D6uKa_linkButton:disabled{opacity:.45;cursor:default}._7D6uKa_search:focus,._7D6uKa_select:focus,._7D6uKa_schedulePreset:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleToggle input{margin:0}@media (prefers-reduced-motion:reduce){._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:none}._7D6uKa_cardSpinner{animation:none}}";
1983
+ const css$1 = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-taskboard-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0;container:_7D6uKa_task-board-view/inline-size}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-dsh-taskboard-view]{display:block}html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [data-pane=conversation]>:not([data-dsh-taskboard-view]),html[data-dsh-taskboard-active]:not([data-dsh-ssh-active]) [class*=centerCol]>:not([data-dsh-taskboard-view]){display:none!important}._7D6uKa_entry{box-sizing:border-box;width:100%;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 10px;font-size:13px;display:flex}._7D6uKa_entry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_entry[data-active]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:600}._7D6uKa_entryIcon{flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:inline-flex}._7D6uKa_entryIcon svg{width:18px;height:18px;display:block}._7D6uKa_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entry,[data-sidebar-collapsed] ._7D6uKa_entry{border-radius:50%;justify-content:center;width:36px;height:36px;margin:0 auto 12px;padding:0}[data-dsh-frame][data-sidebar-collapsed] ._7D6uKa_entryLabel,[data-sidebar-collapsed] ._7D6uKa_entryLabel{display:none}._7D6uKa_board{box-sizing:border-box;background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:12px;padding:14px 16px 16px;display:flex}._7D6uKa_boardHeader{flex:none;align-items:center;gap:10px;display:flex}._7D6uKa_boardTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;margin:0;font-size:16px;font-weight:700}._7D6uKa_backButton{align-items:center;gap:4px;display:inline-flex}._7D6uKa_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}._7D6uKa_search::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_columns{overscroll-behavior-inline:contain;scrollbar-color:var(--dsw-alias-border-l3) var(--dsw-alias-interactive-bg-hover);scrollbar-width:thin;flex:1;grid-auto-columns:minmax(220px,1fr);grid-auto-flow:column;gap:12px;min-height:0;padding-bottom:6px;display:grid;overflow:auto hidden}._7D6uKa_columns::-webkit-scrollbar{height:10px}._7D6uKa_columns::-webkit-scrollbar-track{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l3);background-clip:content-box;border:2px solid #0000;border-radius:999px}._7D6uKa_columns::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-border-l4);background-clip:content-box}._7D6uKa_column{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;flex-direction:column;min-height:0;display:flex;overflow:hidden}._7D6uKa_columnHeader{flex:none;align-items:center;gap:6px;padding:10px 12px;display:flex}._7D6uKa_columnTitle{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:13px;font-weight:700;overflow:hidden}._7D6uKa_columnCount{min-width:0;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:1px 8px;font-size:12px}._7D6uKa_statusDot{border-radius:50%;flex:none;width:8px;height:8px}._7D6uKa_statusDot[data-status=backlog]{background:var(--dsw-alias-label-tertiary)}._7D6uKa_statusDot[data-status=todo]{background:var(--dsw-alias-state-business-primary)}._7D6uKa_statusDot[data-status=running]{background:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusDot[data-status=done]{background:var(--dsw-alias-state-success-primary)}._7D6uKa_statusDot[data-status=failed]{background:var(--dsw-alias-state-error-primary)}._7D6uKa_cards{flex-direction:column;flex:1;gap:8px;min-height:0;padding:2px 8px 10px;display:flex;overflow-y:auto}._7D6uKa_columnEmpty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:24px 8px;font-size:12px}._7D6uKa_card{text-align:left;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;font-family:inherit;transition:box-shadow .12s,border-color .12s,transform .12s;display:flex}._7D6uKa_card:hover{box-shadow:var(--dsw-shadow-lv2);border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}._7D6uKa_card[data-status=running]{border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_cardTitle{-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}._7D6uKa_cardExcerpt{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}._7D6uKa_cardMeta{color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}._7D6uKa_cardTime{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}._7D6uKa_cardSchedule{white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;flex:none;padding:2px 6px;font-size:12px;line-height:1}._7D6uKa_cardRun{flex:none}._7D6uKa_cardRun[data-result=failed]{color:var(--dsw-alias-state-error-primary)}._7D6uKa_cardRun[data-result=succeeded]{color:var(--dsw-alias-state-success-primary)}._7D6uKa_cardSession{color:var(--dsw-alias-state-business-primary);flex:none}._7D6uKa_cardRunningLabel{color:var(--dsw-alias-state-warn-primary);font-size:11px}._7D6uKa_cardSpinner{border:2px solid var(--dsw-alias-state-warn-primary);border-top-color:#0000;border-radius:50%;flex:none;width:10px;height:10px;animation:.8s linear infinite _7D6uKa_dshTbSpin}@keyframes _7D6uKa_dshTbSpin{to{transform:rotate(360deg)}}._7D6uKa_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}._7D6uKa_primaryButton:disabled{opacity:.5;cursor:default}._7D6uKa_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}._7D6uKa_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7D6uKa_ghostButton:disabled{opacity:.45;cursor:default}._7D6uKa_dangerButton{color:#fff;background:var(--dsw-alias-state-error-primary);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}._7D6uKa_dangerButton:hover:not(:disabled){filter:brightness(1.08)}._7D6uKa_dangerButton:active:not(:disabled){filter:brightness(.94)}._7D6uKa_dangerButton:disabled{opacity:.5;cursor:default}._7D6uKa_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}._7D6uKa_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}._7D6uKa_linkButton:hover{text-decoration:underline}._7D6uKa_modalBackdrop{z-index:1300;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._7D6uKa_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(520px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}._7D6uKa_modalTitle{margin:0;font-size:15px;font-weight:700}._7D6uKa_confirmMessage{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.5}._7D6uKa_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}._7D6uKa_field{flex-direction:column;gap:5px;display:flex}._7D6uKa_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}._7D6uKa_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_select{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;max-width:100%;padding:7px 10px;font-family:inherit;font-size:13px}._7D6uKa_input::placeholder{color:var(--dsw-alias-label-tertiary)}._7D6uKa_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}._7D6uKa_detail{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(640px,100vw - 48px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;display:flex;overflow:hidden}._7D6uKa_detailHeader{border-bottom:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:14px 18px;display:flex}._7D6uKa_detailTitle{overflow-wrap:anywhere;flex:1;margin:0;font-size:15px;font-weight:700}._7D6uKa_statusBadge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:2px 10px;font-size:12px}._7D6uKa_statusBadge[data-status=running]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}._7D6uKa_statusBadge[data-status=done]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}._7D6uKa_statusBadge[data-status=failed]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_detailBody{flex-direction:column;flex:1;gap:16px;padding:14px 18px;display:flex;overflow-y:auto}._7D6uKa_detailSection{flex-direction:column;gap:6px;display:flex}._7D6uKa_detailSection h4{color:var(--dsw-alias-label-tertiary);text-transform:none;margin:0;font-size:12px;font-weight:700}._7D6uKa_detailText{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:13px;line-height:1.55}._7D6uKa_scheduleToggle{color:var(--dsw-alias-label-primary);cursor:pointer;user-select:none;align-items:center;gap:8px;font-size:13px;display:flex}._7D6uKa_scheduleToggle input{accent-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleRow{align-items:center;gap:8px;display:flex}._7D6uKa_scheduleInput{min-width:0;font-family:var(--dsw-font-markdown-code-block-small);flex:1;font-size:12.5px}._7D6uKa_scheduleInputInvalid,._7D6uKa_scheduleInputInvalid:focus{border-color:var(--dsw-alias-state-error-primary)}._7D6uKa_schedulePreset{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:none;padding:7px 8px;font-size:12.5px}._7D6uKa_scheduleMeta{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px}._7D6uKa_promptBlock{font-size:12.5px;line-height:1.5;font-family:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-markdown-code-block);border:1px solid var(--dsw-alias-border-l1);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:8px;max-height:240px;margin:0;padding:10px 12px;overflow-y:auto}._7D6uKa_executionList{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}._7D6uKa_executionRow{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-wrap:wrap;align-items:center;gap:10px;padding:8px 10px;display:flex}._7D6uKa_executionBadge{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:600}._7D6uKa_executionBadge[data-result=succeeded]{color:var(--dsw-alias-state-success-primary);background:0 0}._7D6uKa_executionBadge[data-result=failed]{color:var(--dsw-alias-state-error-primary);background:0 0}._7D6uKa_executionBadge[data-result=cancelled]{color:var(--dsw-alias-label-tertiary);background:0 0}._7D6uKa_executionTimes{color:var(--dsw-alias-label-secondary);font-size:12px}._7D6uKa_executionError{width:100%;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:12px}._7D6uKa_moveRow{flex-wrap:wrap;gap:8px;display:flex}._7D6uKa_detailFooter{border-top:1px solid var(--dsw-alias-separator-primary);flex:none;align-items:center;gap:10px;padding:12px 18px;display:flex}._7D6uKa_detailMeta{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}@container _7D6uKa_task-board-view (width<=768px){._7D6uKa_board{gap:10px;padding:10px}._7D6uKa_boardHeader{flex-wrap:wrap;align-items:center;gap:8px}._7D6uKa_backButton{flex:none;order:1}._7D6uKa_boardTitle{flex:auto;order:2}._7D6uKa_boardHeader>._7D6uKa_detailMeta{flex:1 0 100%;order:3;margin-left:0}._7D6uKa_search{flex:1 0 100%;order:4;min-width:0}._7D6uKa_boardHeader>button:not(._7D6uKa_backButton){flex:1 1 0;order:5;min-width:0}._7D6uKa_columns{scroll-snap-type:inline mandatory;scrollbar-width:none;-webkit-overflow-scrolling:touch;grid-auto-columns:86cqw;gap:10px;padding-inline:2px 14cqw;scroll-padding-inline:2px}._7D6uKa_columns::-webkit-scrollbar{display:none}._7D6uKa_column{scroll-snap-align:start;scroll-snap-stop:always}}@container _7D6uKa_task-board-view (width<=720px){._7D6uKa_boardHeader>._7D6uKa_detailMeta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@container _7D6uKa_task-board-view (width<=600px){._7D6uKa_board{padding-inline:8px}}@media (width<=768px){[data-dsh-taskboard-view]{height:100dvh}._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle{min-height:44px}._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset{box-sizing:border-box;font-size:16px}._7D6uKa_modalBackdrop{justify-content:stretch;align-items:stretch;width:100vw;height:100dvh}._7D6uKa_modal,._7D6uKa_detail{box-sizing:border-box;border:0;border-radius:0;width:100vw;height:100dvh;max-height:none}._7D6uKa_modal{padding-top:max(16px, env(safe-area-inset-top));padding-right:max(16px, env(safe-area-inset-right));padding-bottom:max(16px, env(safe-area-inset-bottom));padding-left:max(16px, env(safe-area-inset-left))}._7D6uKa_modalFooter{z-index:1;background:var(--dsw-alias-bg-base);flex-wrap:wrap;padding-top:8px;position:sticky;bottom:0}._7D6uKa_modalFooter>button{flex:120px}._7D6uKa_detailHeader{padding-top:max(12px, env(safe-area-inset-top));padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailTitle{min-width:0}._7D6uKa_detailBody{overscroll-behavior-y:contain;padding-right:max(14px, env(safe-area-inset-right));padding-left:max(14px, env(safe-area-inset-left))}._7D6uKa_detailFooter{padding-right:max(14px, env(safe-area-inset-right));padding-bottom:max(12px, env(safe-area-inset-bottom));padding-left:max(14px, env(safe-area-inset-left));flex-wrap:wrap}._7D6uKa_detailFooter>button{flex:96px}._7D6uKa_detailFooter>._7D6uKa_detailMeta{text-align:end;flex:1 0 100%;margin-left:0}._7D6uKa_scheduleRow{flex-direction:column;align-items:stretch}._7D6uKa_schedulePreset{width:100%}}._7D6uKa_entry:focus-visible,._7D6uKa_card:focus-visible,._7D6uKa_primaryButton:focus-visible,._7D6uKa_ghostButton:focus-visible,._7D6uKa_dangerButton:focus-visible,._7D6uKa_iconButton:focus-visible,._7D6uKa_linkButton:focus-visible,._7D6uKa_search:focus-visible,._7D6uKa_input:focus-visible,._7D6uKa_select:focus-visible,._7D6uKa_schedulePreset:focus-visible,._7D6uKa_scheduleToggle input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._7D6uKa_entry,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:background-color .12s,color .12s,border-color .12s,outline-color .12s,box-shadow .12s,transform .12s}._7D6uKa_card:active{box-shadow:var(--dsw-shadow-lv1);transform:translateY(0)}._7D6uKa_entry:active,._7D6uKa_primaryButton:active:not(:disabled),._7D6uKa_ghostButton:active:not(:disabled),._7D6uKa_dangerButton:active:not(:disabled),._7D6uKa_iconButton:active:not(:disabled),._7D6uKa_linkButton:active:not(:disabled){transform:translateY(1px)}._7D6uKa_entry[data-active]:hover{background:var(--dsw-specific-sidebar-nav-item-active)}._7D6uKa_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._7D6uKa_linkButton:hover:not(:disabled){text-decoration:underline}._7D6uKa_iconButton:disabled,._7D6uKa_linkButton:disabled{opacity:.45;cursor:default}._7D6uKa_search:focus,._7D6uKa_select:focus,._7D6uKa_schedulePreset:focus{border-color:var(--dsw-alias-state-business-primary)}._7D6uKa_scheduleToggle input{margin:0}@media (prefers-reduced-motion:reduce){._7D6uKa_entry,._7D6uKa_card,._7D6uKa_primaryButton,._7D6uKa_ghostButton,._7D6uKa_dangerButton,._7D6uKa_iconButton,._7D6uKa_linkButton,._7D6uKa_search,._7D6uKa_input,._7D6uKa_select,._7D6uKa_schedulePreset,._7D6uKa_scheduleToggle input{transition:none}._7D6uKa_cardSpinner{animation:none}}._7D6uKa_cardTags{flex-wrap:wrap;gap:4px;display:flex}._7D6uKa_cardTag{border:1px solid var(--dsh-task-tag-border);background:var(--dsh-task-tag-fill);max-width:100%;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;border-radius:999px;padding:0 7px;font-size:10px;line-height:16px;overflow:hidden}._7D6uKa_tagFilter{flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px;display:flex}._7D6uKa_tagFilterLabel{color:var(--dsw-alias-label-tertiary);font-size:11px}._7D6uKa_tagChip{border:1px solid var(--dsh-task-tag-border);color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:999px;padding:1px 9px;font-family:inherit;font-size:11px;line-height:18px}._7D6uKa_tagChip[data-active=true]{background:var(--dsh-task-tag-fill);color:var(--dsw-alias-label-primary)}._7D6uKa_cardTag[data-tag-tone=\"0\"],._7D6uKa_tagChip[data-tag-tone=\"0\"]{--dsh-task-tag-fill:#4e93e82e;--dsh-task-tag-border:#4e93e866}._7D6uKa_cardTag[data-tag-tone=\"1\"],._7D6uKa_tagChip[data-tag-tone=\"1\"]{--dsh-task-tag-fill:#2ea36a2e;--dsh-task-tag-border:#2ea36a66}._7D6uKa_cardTag[data-tag-tone=\"2\"],._7D6uKa_tagChip[data-tag-tone=\"2\"]{--dsh-task-tag-fill:#d08a2a2e;--dsh-task-tag-border:#d08a2a66}._7D6uKa_cardTag[data-tag-tone=\"3\"],._7D6uKa_tagChip[data-tag-tone=\"3\"]{--dsh-task-tag-fill:#b456c82e;--dsh-task-tag-border:#b456c866}._7D6uKa_cardTag[data-tag-tone=\"4\"],._7D6uKa_tagChip[data-tag-tone=\"4\"]{--dsh-task-tag-fill:#cf5f7a2e;--dsh-task-tag-border:#cf5f7a66}._7D6uKa_cardTag[data-tag-tone=\"5\"],._7D6uKa_tagChip[data-tag-tone=\"5\"]{--dsh-task-tag-fill:#4a9fb52e;--dsh-task-tag-border:#4a9fb566}._7D6uKa_fieldHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.4}._7D6uKa_tagRow{align-items:center;gap:6px;display:flex}._7D6uKa_tagRow ._7D6uKa_input{flex:1 1 0;min-width:0}._7D6uKa_tagRow ._7D6uKa_ghostButton{flex:none}._7D6uKa_tagAddButton{align-self:flex-start}._7D6uKa_projectFilter{flex:none;align-items:center;gap:6px;display:flex}._7D6uKa_projectFilterLabel{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}._7D6uKa_projectDialog{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;flex:none;gap:8px;margin-bottom:8px;padding:10px 12px;display:flex}._7D6uKa_projectDialogActions{justify-content:flex-end;gap:8px;display:flex}._7D6uKa_aiParse{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}._7D6uKa_aiParseRow{align-items:center;gap:8px;display:flex}._7D6uKa_aiParseRow ._7D6uKa_select{flex:1 1 0;min-width:0}._7D6uKa_aiParseRow ._7D6uKa_ghostButton,._7D6uKa_aiParseRow ._7D6uKa_primaryButton{flex:none}";
1827
1984
  const tagId$1 = "@linxin666/dsh-client-ui-task-board/packages/dsh-task-board/src/client/board.module.css";
1828
1985
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1829
1986
  const tag = document.createElement("style");
@@ -1833,6 +1990,8 @@ window.__ModuleLoader__.load({
1833
1990
  document.head.appendChild(tag);
1834
1991
  }
1835
1992
  var board_module_css_default = {
1993
+ "aiParse": "_7D6uKa_aiParse",
1994
+ "aiParseRow": "_7D6uKa_aiParseRow",
1836
1995
  "backButton": "_7D6uKa_backButton",
1837
1996
  "board": "_7D6uKa_board",
1838
1997
  "boardHeader": "_7D6uKa_boardHeader",
@@ -1845,6 +2004,8 @@ window.__ModuleLoader__.load({
1845
2004
  "cardSchedule": "_7D6uKa_cardSchedule",
1846
2005
  "cardSession": "_7D6uKa_cardSession",
1847
2006
  "cardSpinner": "_7D6uKa_cardSpinner",
2007
+ "cardTag": "_7D6uKa_cardTag",
2008
+ "cardTags": "_7D6uKa_cardTags",
1848
2009
  "cardTime": "_7D6uKa_cardTime",
1849
2010
  "cardTitle": "_7D6uKa_cardTitle",
1850
2011
  "cards": "_7D6uKa_cards",
@@ -1874,6 +2035,7 @@ window.__ModuleLoader__.load({
1874
2035
  "executionRow": "_7D6uKa_executionRow",
1875
2036
  "executionTimes": "_7D6uKa_executionTimes",
1876
2037
  "field": "_7D6uKa_field",
2038
+ "fieldHint": "_7D6uKa_fieldHint",
1877
2039
  "fieldLabel": "_7D6uKa_fieldLabel",
1878
2040
  "formError": "_7D6uKa_formError",
1879
2041
  "ghostButton": "_7D6uKa_ghostButton",
@@ -1886,6 +2048,10 @@ window.__ModuleLoader__.load({
1886
2048
  "modalTitle": "_7D6uKa_modalTitle",
1887
2049
  "moveRow": "_7D6uKa_moveRow",
1888
2050
  "primaryButton": "_7D6uKa_primaryButton",
2051
+ "projectDialog": "_7D6uKa_projectDialog",
2052
+ "projectDialogActions": "_7D6uKa_projectDialogActions",
2053
+ "projectFilter": "_7D6uKa_projectFilter",
2054
+ "projectFilterLabel": "_7D6uKa_projectFilterLabel",
1889
2055
  "promptBlock": "_7D6uKa_promptBlock",
1890
2056
  "scheduleInput": "_7D6uKa_scheduleInput",
1891
2057
  "scheduleInputInvalid": "_7D6uKa_scheduleInputInvalid",
@@ -1897,6 +2063,11 @@ window.__ModuleLoader__.load({
1897
2063
  "select": "_7D6uKa_select",
1898
2064
  "statusBadge": "_7D6uKa_statusBadge",
1899
2065
  "statusDot": "_7D6uKa_statusDot",
2066
+ "tagAddButton": "_7D6uKa_tagAddButton",
2067
+ "tagChip": "_7D6uKa_tagChip",
2068
+ "tagFilter": "_7D6uKa_tagFilter",
2069
+ "tagFilterLabel": "_7D6uKa_tagFilterLabel",
2070
+ "tagRow": "_7D6uKa_tagRow",
1900
2071
  "task-board-view": "_7D6uKa_task-board-view"
1901
2072
  };
1902
2073
  //#endregion
@@ -1922,6 +2093,8 @@ window.__ModuleLoader__.load({
1922
2093
  ];
1923
2094
  //#endregion
1924
2095
  //#region src/client/board/TaskForm.tsx
2096
+ /** DOM id shared by the tag-name inputs and their datalist (one board at a time). */
2097
+ const TAG_NAME_LIST_ID = "dsh-task-board-tag-names";
1925
2098
  /** Modal overlay: closes on backdrop press, submits through the form. */
1926
2099
  function ModalShell({ ariaLabel, title, error, pending, submitLabel, onSubmit, onClose, children }) {
1927
2100
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -2009,6 +2182,94 @@ window.__ModuleLoader__.load({
2009
2182
  })
2010
2183
  ] });
2011
2184
  }
2185
+ /**
2186
+ * Task labels (issue #1521): one row per label holding the badge name and an
2187
+ * optional execution hint. The name inputs offer the labels already used on the
2188
+ * board through a datalist, and picking one adopts its hint when the row has
2189
+ * none — so a business line is defined once and reused by every later task.
2190
+ */
2191
+ function TaskTagFields({ tags, knownTags, onChange }) {
2192
+ const update = (index, patch) => {
2193
+ onChange(tags.map((tag, position) => position === index ? {
2194
+ ...tag,
2195
+ ...patch
2196
+ } : tag));
2197
+ };
2198
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2199
+ className: board_module_css_default.field,
2200
+ children: [
2201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2202
+ className: board_module_css_default.fieldLabel,
2203
+ children: t("new.tags")
2204
+ }),
2205
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2206
+ className: board_module_css_default.fieldHint,
2207
+ children: t("new.tagsHint")
2208
+ }),
2209
+ tags.map((tag, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2210
+ className: board_module_css_default.tagRow,
2211
+ children: [
2212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2213
+ className: board_module_css_default.input,
2214
+ list: TAG_NAME_LIST_ID,
2215
+ value: tag.name,
2216
+ maxLength: 32,
2217
+ placeholder: t("new.tagNamePlaceholder"),
2218
+ "aria-label": t("new.tagName"),
2219
+ onChange: (event) => {
2220
+ const name = event.target.value;
2221
+ const known = knownTags.find((candidate) => candidate.name === name);
2222
+ const adoptsHint = known?.promptPrefix !== void 0 && (tag.promptPrefix ?? "").trim() === "";
2223
+ update(index, adoptsHint ? {
2224
+ name,
2225
+ promptPrefix: known.promptPrefix
2226
+ } : { name });
2227
+ }
2228
+ }),
2229
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2230
+ className: board_module_css_default.input,
2231
+ value: tag.promptPrefix ?? "",
2232
+ maxLength: 200,
2233
+ placeholder: t("new.tagPromptPlaceholder"),
2234
+ "aria-label": t("new.tagPrompt"),
2235
+ onChange: (event) => {
2236
+ update(index, { promptPrefix: event.target.value });
2237
+ }
2238
+ }),
2239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2240
+ type: "button",
2241
+ className: board_module_css_default.ghostButton,
2242
+ "aria-label": t("new.tagRemove", { name: tag.name }),
2243
+ onClick: () => {
2244
+ onChange(tags.filter((_, position) => position !== index));
2245
+ },
2246
+ children: "×"
2247
+ })
2248
+ ]
2249
+ }, index)),
2250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("datalist", {
2251
+ id: TAG_NAME_LIST_ID,
2252
+ children: knownTags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: tag.name }, tag.name))
2253
+ }),
2254
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2255
+ type: "button",
2256
+ className: board_module_css_default.ghostButton + " " + board_module_css_default.tagAddButton,
2257
+ disabled: tags.length >= 8,
2258
+ onClick: () => {
2259
+ onChange([...tags, { name: "" }]);
2260
+ },
2261
+ children: ["+ ", t("new.tagAdd")]
2262
+ })
2263
+ ]
2264
+ });
2265
+ }
2266
+ /**
2267
+ * Clean a tag list via normalizeTags: trim, drop blanks and duplicates, cap
2268
+ * lengths and count, so the wire always carries a valid tag list.
2269
+ */
2270
+ function cleanTags(tags) {
2271
+ return normalizeTags(tags) ?? [];
2272
+ }
2012
2273
  //#endregion
2013
2274
  //#region src/client/board/NewTaskModal.tsx
2014
2275
  /**
@@ -2016,12 +2277,12 @@ window.__ModuleLoader__.load({
2016
2277
  * Creates through the Host and closes only after the Host confirms it.
2017
2278
  */
2018
2279
  /** New-task form overlay. */
2019
- function NewTaskModal({ controller, onClose, initialTask, onDuplicateSuccess }) {
2280
+ function NewTaskModal({ controller, onClose, initialTask, defaultWorkspaceId, onDuplicateSuccess }) {
2020
2281
  const isDuplicate = initialTask !== void 0;
2021
2282
  const [title, setTitle] = (0, react.useState)(initialTask?.title ?? "");
2022
2283
  const [description, setDescription] = (0, react.useState)(initialTask?.description ?? "");
2023
2284
  const [prompt, setPrompt] = (0, react.useState)(initialTask?.prompt ?? "");
2024
- const [workspaceId, setWorkspaceId] = (0, react.useState)(initialTask?.workspaceId ?? "");
2285
+ const [workspaceId, setWorkspaceId] = (0, react.useState)(initialTask?.workspaceId ?? defaultWorkspaceId ?? "");
2025
2286
  const [mode, setMode] = (0, react.useState)(initialTask?.mode ?? "");
2026
2287
  const [permission, setPermission] = (0, react.useState)(initialTask?.permission ?? "");
2027
2288
  const [model, setModel] = (0, react.useState)(initialTask?.model ?? "");
@@ -2032,11 +2293,47 @@ window.__ModuleLoader__.load({
2032
2293
  const [freezeText, setFreezeText] = (0, react.useState)("");
2033
2294
  const [freezeError, setFreezeError] = (0, react.useState)(void 0);
2034
2295
  const [handoverText, setHandoverText] = (0, react.useState)(initialTask?.handover?.references !== void 0 ? initialTask.handover.references.join("\n") : "");
2296
+ const [tags, setTags] = (0, react.useState)(initialTask?.tags ?? []);
2035
2297
  const [archiveOriginal, setArchiveOriginal] = (0, react.useState)(true);
2036
2298
  const [error, setError] = (0, react.useState)(void 0);
2037
2299
  const [pending, setPending] = (0, react.useState)(false);
2038
2300
  const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
2301
+ const [canParse] = (0, react.useState)(controller.getSnapshot().canParseTask === true);
2302
+ const [parseText, setParseText] = (0, react.useState)("");
2303
+ const [parseModel, setParseModel] = (0, react.useState)("");
2304
+ const [parsePending, setParsePending] = (0, react.useState)(false);
2305
+ const [parseError, setParseError] = (0, react.useState)(void 0);
2306
+ const parseAbort = (0, react.useRef)(void 0);
2307
+ const parseModels = options.models ?? [];
2039
2308
  (0, react.useEffect)(() => controller.subscribe(() => setOptions(controller.getSnapshot().executionOptions)), [controller]);
2309
+ (0, react.useEffect)(() => {
2310
+ if (parseModel === "" && parseModels.length > 0) setParseModel(parseModels[0].id);
2311
+ }, [parseModel, options.models]);
2312
+ const runParse = async () => {
2313
+ const text = parseText.trim();
2314
+ if (text === "") {
2315
+ setParseError(t("new.aiParseEmpty"));
2316
+ return;
2317
+ }
2318
+ const abort = new AbortController();
2319
+ parseAbort.current = abort;
2320
+ setParsePending(true);
2321
+ setParseError(void 0);
2322
+ try {
2323
+ const draft = await controller.parseTaskDraft({
2324
+ text,
2325
+ ...parseModel === "" ? {} : { model: parseModel }
2326
+ }, abort.signal);
2327
+ setTitle(draft.title);
2328
+ setDescription(draft.description);
2329
+ setPrompt(draft.prompt);
2330
+ } catch (parseFailure) {
2331
+ if (!abort.signal.aborted) setParseError(parseFailure instanceof Error ? parseFailure.message : String(parseFailure));
2332
+ } finally {
2333
+ parseAbort.current = void 0;
2334
+ setParsePending(false);
2335
+ }
2336
+ };
2040
2337
  const submit = async () => {
2041
2338
  if (scheduleEnabled) {
2042
2339
  const cron = scheduleCron.trim();
@@ -2064,6 +2361,7 @@ window.__ModuleLoader__.load({
2064
2361
  mode: mode === "" ? void 0 : mode,
2065
2362
  permission: permission === "" ? void 0 : permission
2066
2363
  };
2364
+ const tagList = cleanTags(tags);
2067
2365
  setPending(true);
2068
2366
  if (await controller.createTaskConfirmed({
2069
2367
  title,
@@ -2076,6 +2374,7 @@ window.__ModuleLoader__.load({
2076
2374
  permission: permission === "" ? void 0 : permission,
2077
2375
  model: model === "" ? void 0 : model,
2078
2376
  ...reuseSession ? { reuseSession: true } : {},
2377
+ ...tagList.length > 0 ? { tags: tagList } : {},
2079
2378
  schedule: scheduleEnabled ? {
2080
2379
  enabled: true,
2081
2380
  cron: scheduleCron.trim()
@@ -2103,6 +2402,65 @@ window.__ModuleLoader__.load({
2103
2402
  },
2104
2403
  onClose,
2105
2404
  children: [
2405
+ canParse && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2406
+ className: board_module_css_default.aiParse,
2407
+ "data-dsh-part": "ai-parse",
2408
+ children: [
2409
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2410
+ className: board_module_css_default.fieldLabel,
2411
+ children: t("new.aiParse")
2412
+ }),
2413
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2414
+ className: board_module_css_default.fieldHint,
2415
+ children: t("new.aiParseHint")
2416
+ }),
2417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
2418
+ className: board_module_css_default.input,
2419
+ rows: 3,
2420
+ value: parseText,
2421
+ placeholder: t("new.aiParsePlaceholder"),
2422
+ spellCheck: false,
2423
+ onChange: (event) => {
2424
+ setParseText(event.target.value);
2425
+ setParseError(void 0);
2426
+ }
2427
+ }),
2428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2429
+ className: board_module_css_default.aiParseRow,
2430
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
2431
+ className: board_module_css_default.select,
2432
+ value: parseModel,
2433
+ "aria-label": t("new.aiParseModel"),
2434
+ onChange: (event) => {
2435
+ setParseModel(event.target.value);
2436
+ },
2437
+ children: parseModels.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2438
+ value: option.id,
2439
+ children: option.name ?? option.id
2440
+ }, option.id))
2441
+ }), parsePending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2442
+ type: "button",
2443
+ className: board_module_css_default.ghostButton,
2444
+ onClick: () => {
2445
+ parseAbort.current?.abort();
2446
+ },
2447
+ children: t("new.aiParseCancel")
2448
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2449
+ type: "button",
2450
+ className: board_module_css_default.primaryButton,
2451
+ disabled: parseText.trim() === "",
2452
+ onClick: () => {
2453
+ runParse();
2454
+ },
2455
+ children: t("new.aiParseRun")
2456
+ })]
2457
+ }),
2458
+ parseError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2459
+ className: board_module_css_default.formError,
2460
+ children: parseError
2461
+ })
2462
+ ]
2463
+ }),
2106
2464
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
2107
2465
  title,
2108
2466
  description,
@@ -2114,6 +2472,11 @@ window.__ModuleLoader__.load({
2114
2472
  onDescriptionChange: setDescription,
2115
2473
  onPromptChange: setPrompt
2116
2474
  }),
2475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskTagFields, {
2476
+ tags,
2477
+ knownTags: collectKnownTags(controller.getSnapshot().tasks),
2478
+ onChange: setTags
2479
+ }),
2117
2480
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2118
2481
  className: board_module_css_default.field,
2119
2482
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -2390,6 +2753,17 @@ window.__ModuleLoader__.load({
2390
2753
  className: board_module_css_default.cardTitle,
2391
2754
  children: task.title
2392
2755
  }),
2756
+ task.tags !== void 0 && task.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2757
+ className: board_module_css_default.cardTags,
2758
+ children: task.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2759
+ className: board_module_css_default.cardTag,
2760
+ "data-tag-tone": tagTone(tag.name),
2761
+ "data-dsh-part": "tag-badge",
2762
+ "data-tag-hint": tag.promptPrefix === void 0 ? void 0 : tag.promptPrefix,
2763
+ title: tag.promptPrefix === void 0 ? tag.name : tag.promptPrefix,
2764
+ children: tag.name
2765
+ }, tag.name))
2766
+ }),
2393
2767
  task.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2394
2768
  className: board_module_css_default.cardExcerpt,
2395
2769
  children: task.description
@@ -2505,6 +2879,7 @@ window.__ModuleLoader__.load({
2505
2879
  const [title, setTitle] = (0, react.useState)(task.title);
2506
2880
  const [description, setDescription] = (0, react.useState)(task.description);
2507
2881
  const [prompt, setPrompt] = (0, react.useState)(task.prompt);
2882
+ const [tags, setTags] = (0, react.useState)(task.tags ?? []);
2508
2883
  const [error, setError] = (0, react.useState)(void 0);
2509
2884
  const [pending, setPending] = (0, react.useState)(false);
2510
2885
  const submit = async () => {
@@ -2513,18 +2888,21 @@ window.__ModuleLoader__.load({
2513
2888
  return;
2514
2889
  }
2515
2890
  setPending(true);
2516
- if (await controller.updateTask(task.id, {
2891
+ const tagList = cleanTags(tags);
2892
+ const patch = {
2517
2893
  title,
2518
2894
  description,
2519
- prompt
2520
- })) {
2895
+ prompt,
2896
+ ...tagList.length > 0 ? { tags: tagList } : task.tags === void 0 ? {} : { tags: null }
2897
+ };
2898
+ if (await controller.updateTask(task.id, patch)) {
2521
2899
  onClose();
2522
2900
  return;
2523
2901
  }
2524
2902
  setPending(false);
2525
2903
  setError(controller.getSnapshot().transportError ?? t("new.required"));
2526
2904
  };
2527
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalShell, {
2905
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ModalShell, {
2528
2906
  ariaLabel: t("edit.title"),
2529
2907
  title: t("edit.title"),
2530
2908
  error,
@@ -2534,7 +2912,7 @@ window.__ModuleLoader__.load({
2534
2912
  submit();
2535
2913
  },
2536
2914
  onClose,
2537
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
2915
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskContentFields, {
2538
2916
  title,
2539
2917
  description,
2540
2918
  prompt,
@@ -2544,6 +2922,43 @@ window.__ModuleLoader__.load({
2544
2922
  },
2545
2923
  onDescriptionChange: setDescription,
2546
2924
  onPromptChange: setPrompt
2925
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskTagFields, {
2926
+ tags,
2927
+ knownTags: collectKnownTags(controller.getSnapshot().tasks),
2928
+ onChange: setTags
2929
+ })]
2930
+ });
2931
+ }
2932
+ /** Edit-tags modal: edit labels only, shown for tasks after first execution. */
2933
+ function EditTagsModal({ controller, task, onClose }) {
2934
+ const [tags, setTags] = (0, react.useState)(task.tags ?? []);
2935
+ const [error, setError] = (0, react.useState)(void 0);
2936
+ const [pending, setPending] = (0, react.useState)(false);
2937
+ const submit = async () => {
2938
+ setPending(true);
2939
+ const tagList = cleanTags(tags);
2940
+ const patch = { tags: tagList.length > 0 ? tagList : null };
2941
+ if (await controller.updateTask(task.id, patch)) {
2942
+ onClose();
2943
+ return;
2944
+ }
2945
+ setPending(false);
2946
+ setError(controller.getSnapshot().transportError ?? t("new.required"));
2947
+ };
2948
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalShell, {
2949
+ ariaLabel: t("detail.editTags"),
2950
+ title: t("detail.editTags"),
2951
+ error,
2952
+ pending,
2953
+ submitLabel: t("edit.save"),
2954
+ onSubmit: () => {
2955
+ submit();
2956
+ },
2957
+ onClose,
2958
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TaskTagFields, {
2959
+ tags,
2960
+ knownTags: collectKnownTags(controller.getSnapshot().tasks),
2961
+ onChange: setTags
2547
2962
  })
2548
2963
  });
2549
2964
  }
@@ -2878,6 +3293,7 @@ window.__ModuleLoader__.load({
2878
3293
  function TaskDetail({ controller, task }) {
2879
3294
  const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
2880
3295
  const [showEdit, setShowEdit] = (0, react.useState)(false);
3296
+ const [showEditTags, setShowEditTags] = (0, react.useState)(false);
2881
3297
  const [showDuplicate, setShowDuplicate] = (0, react.useState)(false);
2882
3298
  const [latest, setLatest] = (0, react.useState)(task);
2883
3299
  (0, react.useEffect)(() => {
@@ -2885,6 +3301,7 @@ window.__ModuleLoader__.load({
2885
3301
  }, [task]);
2886
3302
  (0, react.useEffect)(() => {
2887
3303
  setShowEdit(false);
3304
+ setShowEditTags(false);
2888
3305
  setShowDuplicate(false);
2889
3306
  }, [task.id]);
2890
3307
  const current = latest;
@@ -2954,6 +3371,21 @@ window.__ModuleLoader__.load({
2954
3371
  children: current.description !== "" ? current.description : "—"
2955
3372
  })]
2956
3373
  }),
3374
+ current.tags !== void 0 && current.tags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3375
+ className: board_module_css_default.detailSection,
3376
+ "data-dsh-part": "tags",
3377
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("new.tags") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3378
+ className: board_module_css_default.cardTags,
3379
+ children: current.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3380
+ className: board_module_css_default.cardTag,
3381
+ "data-tag-tone": tagTone(tag.name),
3382
+ "data-dsh-part": "tag-badge",
3383
+ "data-tag-hint": tag.promptPrefix === void 0 ? void 0 : tag.promptPrefix,
3384
+ title: tag.promptPrefix === void 0 ? tag.name : tag.promptPrefix,
3385
+ children: tag.name
3386
+ }, tag.name))
3387
+ })]
3388
+ }),
2957
3389
  current.freeze !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2958
3390
  className: board_module_css_default.detailSection,
2959
3391
  "data-dsh-part": "freeze",
@@ -3120,6 +3552,15 @@ window.__ModuleLoader__.load({
3120
3552
  },
3121
3553
  children: t("detail.edit")
3122
3554
  }),
3555
+ !archived && !canEditTaskContent(current) && current.status !== "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3556
+ type: "button",
3557
+ className: board_module_css_default.ghostButton,
3558
+ disabled: pending,
3559
+ onClick: () => {
3560
+ setShowEditTags(true);
3561
+ },
3562
+ children: t("detail.editTags")
3563
+ }),
3123
3564
  !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3124
3565
  type: "button",
3125
3566
  className: board_module_css_default.ghostButton,
@@ -3200,6 +3641,13 @@ window.__ModuleLoader__.load({
3200
3641
  setShowEdit(false);
3201
3642
  }
3202
3643
  }),
3644
+ showEditTags && !archived && current.status !== "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditTagsModal, {
3645
+ controller,
3646
+ task: current,
3647
+ onClose: () => {
3648
+ setShowEditTags(false);
3649
+ }
3650
+ }),
3203
3651
  showDuplicate && !archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewTaskModal, {
3204
3652
  controller,
3205
3653
  initialTask: current,
@@ -3214,22 +3662,30 @@ window.__ModuleLoader__.load({
3214
3662
  ]
3215
3663
  });
3216
3664
  }
3217
- //#endregion
3218
- //#region src/client/board/TaskBoard.tsx
3219
- /**
3220
- * Board view: the multi-column kanban that replaces the middle column while
3221
- * active. Cards open the task detail (never execute directly); the header
3222
- * offers filter, new-task, and a back-to-chat escape.
3223
- */
3224
- /** Case-insensitive title/description/freeze-snapshot match. */
3665
+ /** Case-insensitive title/description/tag/freeze-snapshot match. */
3225
3666
  function matchesFilter(task, filter) {
3226
3667
  if (filter.trim() === "") return true;
3227
3668
  const needle = filter.trim().toLowerCase();
3228
- const haystacks = [task.title, task.description];
3669
+ const haystacks = [
3670
+ task.title,
3671
+ task.description,
3672
+ ...(task.tags ?? []).map((tag) => tag.name)
3673
+ ];
3229
3674
  if (task.freeze !== void 0) haystacks.push(task.freeze.goal, task.freeze.progress, task.freeze.next);
3230
3675
  return haystacks.some((text) => text.toLowerCase().includes(needle));
3231
3676
  }
3232
3677
  /**
3678
+ * Whether a task carries every selected label (issue #1521). Multi-select is
3679
+ * conjunctive: adding a label narrows the board instead of widening it, which
3680
+ * is the only reading that keeps "工作" selected from dragging unrelated cards
3681
+ * back in when a second label is added.
3682
+ */
3683
+ function matchesTagFilter(task, selected) {
3684
+ if (selected.length === 0) return true;
3685
+ const names = new Set((task.tags ?? []).map((tag) => tag.name));
3686
+ return selected.every((name) => names.has(name));
3687
+ }
3688
+ /**
3233
3689
  * Memoized per-card adapter: with a stable `onOpen` from the board and an
3234
3690
  * immutable task record (only the changed card gets a new object ref), a card
3235
3691
  * re-renders only when its own task changes — not when a sibling card status,
@@ -3250,10 +3706,38 @@ window.__ModuleLoader__.load({
3250
3706
  const [snapshot, setSnapshot] = (0, react.useState)(controller.getSnapshot());
3251
3707
  (0, react.useEffect)(() => controller.subscribe(() => setSnapshot(controller.getSnapshot())), [controller]);
3252
3708
  const [filter, setFilter] = (0, react.useState)("");
3709
+ const [tagFilter, setTagFilter] = (0, react.useState)([]);
3253
3710
  const [showNew, setShowNew] = (0, react.useState)(false);
3711
+ const [projectId, setProjectId] = (0, react.useState)("");
3712
+ const [showNewProject, setShowNewProject] = (0, react.useState)(false);
3713
+ const [newProjectPath, setNewProjectPath] = (0, react.useState)("");
3714
+ const [newProjectError, setNewProjectError] = (0, react.useState)(void 0);
3715
+ const [newProjectPending, setNewProjectPending] = (0, react.useState)(false);
3254
3716
  const selected = selectedTaskOf(snapshot);
3255
3717
  const archiveView = snapshot.archiveView;
3256
- const visible = snapshot.tasks.filter((task) => (archiveView ? task.archivedAt !== void 0 : task.archivedAt === void 0) && matchesFilter(task, filter));
3718
+ const knownTags = collectKnownTags(snapshot.tasks);
3719
+ const visible = snapshot.tasks.filter((task) => (archiveView ? task.archivedAt !== void 0 : task.archivedAt === void 0) && (projectId === "" || task.workspaceId === projectId) && matchesFilter(task, filter) && matchesTagFilter(task, tagFilter));
3720
+ const projects = snapshot.executionOptions.workspaces;
3721
+ const canCreateProject = snapshot.canCreateWorkspace === true;
3722
+ const submitNewProject = async () => {
3723
+ const path = newProjectPath.trim();
3724
+ if (path === "") return;
3725
+ setNewProjectPending(true);
3726
+ setNewProjectError(void 0);
3727
+ try {
3728
+ const created = await controller.createWorkspace(path);
3729
+ setProjectId(created.workspaceId);
3730
+ setShowNewProject(false);
3731
+ setNewProjectPath("");
3732
+ } catch (error) {
3733
+ setNewProjectError(error instanceof Error ? error.message : String(error));
3734
+ } finally {
3735
+ setNewProjectPending(false);
3736
+ }
3737
+ };
3738
+ const toggleTag = (0, react.useCallback)((name) => {
3739
+ setTagFilter((current) => current.includes(name) ? current.filter((entry) => entry !== name) : [...current, name]);
3740
+ }, []);
3257
3741
  const openTask = (0, react.useCallback)((id) => {
3258
3742
  controller.openTask(id);
3259
3743
  }, [controller]);
@@ -3289,6 +3773,41 @@ window.__ModuleLoader__.load({
3289
3773
  timeZone: snapshot.host.scheduler.timeZone
3290
3774
  })
3291
3775
  }),
3776
+ (projects.length > 0 || canCreateProject) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3777
+ className: board_module_css_default.projectFilter,
3778
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3779
+ className: board_module_css_default.projectFilterLabel,
3780
+ children: t("board.project")
3781
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3782
+ className: board_module_css_default.select,
3783
+ "data-dsh-part": "project-filter",
3784
+ value: projectId,
3785
+ "aria-label": t("board.project"),
3786
+ onChange: (event) => {
3787
+ const value = event.target.value;
3788
+ if (value === "__dsh_new_project__") {
3789
+ setNewProjectError(void 0);
3790
+ setShowNewProject(true);
3791
+ return;
3792
+ }
3793
+ setProjectId(value);
3794
+ },
3795
+ children: [
3796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3797
+ value: "",
3798
+ children: t("board.projectAll")
3799
+ }),
3800
+ projects.map((project) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3801
+ value: project.workspaceId,
3802
+ children: project.title
3803
+ }, project.workspaceId)),
3804
+ canCreateProject && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3805
+ value: "__dsh_new_project__",
3806
+ children: t("board.projectNew")
3807
+ })
3808
+ ]
3809
+ })]
3810
+ }),
3292
3811
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3293
3812
  className: board_module_css_default.search,
3294
3813
  type: "search",
@@ -3317,6 +3836,86 @@ window.__ModuleLoader__.load({
3317
3836
  })
3318
3837
  ]
3319
3838
  }),
3839
+ showNewProject && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3840
+ className: board_module_css_default.projectDialog,
3841
+ "data-dsh-part": "project-dialog",
3842
+ children: [
3843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3844
+ className: board_module_css_default.field,
3845
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3846
+ className: board_module_css_default.fieldLabel,
3847
+ children: t("board.projectNewPath")
3848
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3849
+ className: board_module_css_default.input,
3850
+ value: newProjectPath,
3851
+ placeholder: t("board.projectNewPathPlaceholder"),
3852
+ spellCheck: false,
3853
+ onChange: (event) => {
3854
+ setNewProjectPath(event.target.value);
3855
+ setNewProjectError(void 0);
3856
+ }
3857
+ })]
3858
+ }),
3859
+ newProjectError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3860
+ className: board_module_css_default.formError,
3861
+ children: t("board.projectCreateFailed", { error: newProjectError })
3862
+ }),
3863
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3864
+ className: board_module_css_default.projectDialogActions,
3865
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3866
+ type: "button",
3867
+ className: board_module_css_default.ghostButton,
3868
+ onClick: () => {
3869
+ setShowNewProject(false);
3870
+ setNewProjectError(void 0);
3871
+ },
3872
+ children: t("new.cancel")
3873
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3874
+ type: "button",
3875
+ className: board_module_css_default.primaryButton,
3876
+ disabled: newProjectPending || newProjectPath.trim() === "",
3877
+ onClick: () => {
3878
+ submitNewProject();
3879
+ },
3880
+ children: t("board.projectCreate")
3881
+ })]
3882
+ })
3883
+ ]
3884
+ }),
3885
+ !archiveView && knownTags.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3886
+ className: board_module_css_default.tagFilter,
3887
+ "data-dsh-part": "tag-filter",
3888
+ children: [
3889
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3890
+ className: board_module_css_default.tagFilterLabel,
3891
+ children: t("board.tagFilter")
3892
+ }),
3893
+ knownTags.map((tag) => {
3894
+ const active = tagFilter.includes(tag.name);
3895
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3896
+ type: "button",
3897
+ className: board_module_css_default.tagChip,
3898
+ "data-dsh-part": "tag-chip",
3899
+ "data-tag-tone": tagTone(tag.name),
3900
+ "data-active": active ? "true" : void 0,
3901
+ "aria-pressed": active,
3902
+ title: tag.promptPrefix === void 0 ? tag.name : tag.promptPrefix,
3903
+ onClick: () => {
3904
+ toggleTag(tag.name);
3905
+ },
3906
+ children: tag.name
3907
+ }, tag.name);
3908
+ }),
3909
+ tagFilter.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3910
+ type: "button",
3911
+ className: board_module_css_default.linkButton,
3912
+ onClick: () => {
3913
+ setTagFilter([]);
3914
+ },
3915
+ children: t("board.tagFilterClear")
3916
+ })
3917
+ ]
3918
+ }),
3320
3919
  snapshot.transportError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3321
3920
  className: board_module_css_default.formError,
3322
3921
  children: [
@@ -3356,7 +3955,7 @@ window.__ModuleLoader__.load({
3356
3955
  onOpen: openTask
3357
3956
  }, task.id)), visible.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3358
3957
  className: board_module_css_default.columnEmpty,
3359
- children: t("archive.empty")
3958
+ children: tagFilter.length > 0 ? t("board.tagEmpty") : t("archive.empty")
3360
3959
  })]
3361
3960
  })]
3362
3961
  }) : COLUMNS.map((column) => {
@@ -3403,7 +4002,7 @@ window.__ModuleLoader__.load({
3403
4002
  onOpen: openTask
3404
4003
  }, task.id)), tasks.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3405
4004
  className: board_module_css_default.columnEmpty,
3406
- children: t("board.empty")
4005
+ children: tagFilter.length > 0 ? t("board.tagEmpty") : t("board.empty")
3407
4006
  })]
3408
4007
  })]
3409
4008
  }, column.status);
@@ -3415,6 +4014,7 @@ window.__ModuleLoader__.load({
3415
4014
  }),
3416
4015
  showNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewTaskModal, {
3417
4016
  controller,
4017
+ ...projectId === "" ? {} : { defaultWorkspaceId: projectId },
3418
4018
  onClose: () => {
3419
4019
  setShowNew(false);
3420
4020
  }
@@ -4636,19 +5236,64 @@ window.__ModuleLoader__.load({
4636
5236
  //#endregion
4637
5237
  //#region src/protocol.ts
4638
5238
  const TASK_BOARD_API_PREFIX = "/api/task-board";
5239
+ /** Whether a decoded reply carries the three draft strings the form accepts. */
5240
+ function isTaskParseDraft(value) {
5241
+ if (typeof value !== "object" || value === null) return false;
5242
+ const record = value;
5243
+ return typeof record.title === "string" && typeof record.description === "string" && typeof record.prompt === "string";
5244
+ }
4639
5245
  //#endregion
4640
5246
  //#region src/client/host-api.ts
4641
5247
  const IMPORT_MARKER = "dsh.taskBoard.v2.hostImported";
4642
5248
  const SOURCE_KEY = "dsh.taskBoard.v2.sourceId";
4643
5249
  const IMPORT_REQUEST_KEY = "dsh.taskBoard.v2.importRequestId";
4644
5250
  const REQUEST_TIMEOUT_MS = 15e3;
5251
+ /** Re-notify the panel at most this often while the event stream stays broken. */
5252
+ const STREAM_ERROR_NOTIFY_MS = 15e3;
5253
+ /** Mirrors the Host's own parse budget; only used to phrase the timeout. */
5254
+ const TASK_PARSE_TIMEOUT_SECONDS = 45;
5255
+ /** Transport failure carrying a stable class next to its user-facing message. */
5256
+ var HostApiError = class extends Error {
5257
+ failure;
5258
+ status;
5259
+ constructor(failure, message, status) {
5260
+ super(message);
5261
+ this.failure = failure;
5262
+ this.status = status;
5263
+ this.name = "HostApiError";
5264
+ }
5265
+ };
4645
5266
  function uuid() {
4646
5267
  return globalThis.crypto?.randomUUID?.() ?? `browser-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
4647
5268
  }
5269
+ /**
5270
+ * Read one Host response without ever handing a non-JSON body to JSON.parse:
5271
+ * the core webserver answers an unmounted `/api/*` path with the plain text
5272
+ * "not found", which used to surface as a JavaScript parse error in the panel.
5273
+ */
4648
5274
  async function readJson(response) {
4649
- const body = await response.json();
4650
- if (!response.ok) throw new Error(body.error ?? `task-board request failed: ${response.status}`);
4651
- return body;
5275
+ const text = await response.text();
5276
+ let parsed;
5277
+ let readable = false;
5278
+ if (text.trim() !== "") try {
5279
+ parsed = JSON.parse(text);
5280
+ readable = true;
5281
+ } catch {
5282
+ readable = false;
5283
+ }
5284
+ const hostError = readable && typeof parsed === "object" && parsed !== null && typeof parsed.error === "string" ? parsed.error : void 0;
5285
+ if (response.ok) {
5286
+ if (!readable) throw new HostApiError("unexpected", t("board.hostError.unexpected", { status: String(response.status) }), response.status);
5287
+ return parsed;
5288
+ }
5289
+ if (hostError !== void 0) {
5290
+ if (hostError === "forbidden") throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
5291
+ if (response.status === 503 || /lock/i.test(hostError)) throw new HostApiError("locked", t("board.hostError.locked", { detail: hostError }), response.status);
5292
+ throw new HostApiError("rejected", hostError, response.status);
5293
+ }
5294
+ if (response.status === 404) throw new HostApiError("not-mounted", t("board.hostError.notMounted"), 404);
5295
+ if (response.status === 401 || response.status === 403) throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
5296
+ throw new HostApiError("unexpected", t("board.hostError.unexpected", { status: String(response.status) }), response.status);
4652
5297
  }
4653
5298
  var HttpTaskBoardHostTransport = class {
4654
5299
  storage;
@@ -4708,14 +5353,54 @@ window.__ModuleLoader__.load({
4708
5353
  signal: controller.signal
4709
5354
  }));
4710
5355
  } catch (error) {
4711
- if (controller.signal.aborted) throw new Error(`task-board Host request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s`);
4712
- throw error;
5356
+ if (error instanceof HostApiError) throw error;
5357
+ if (controller.signal.aborted) throw new HostApiError("timeout", t("board.hostError.timeout", { seconds: String(REQUEST_TIMEOUT_MS / 1e3) }));
5358
+ throw new HostApiError("unreachable", t("board.hostError.unreachable"));
4713
5359
  } finally {
4714
5360
  globalThis.clearTimeout(timeout);
4715
5361
  }
4716
5362
  }
5363
+ /**
5364
+ * Ask the Host to turn pasted text into task fields. The route answers a
5365
+ * typed failure (no model, timeout, unparseable reply) already phrased for
5366
+ * the form, so the UI never renders a raw status code (issue #1540).
5367
+ */
5368
+ async parseDraft(request, signal) {
5369
+ let response;
5370
+ try {
5371
+ response = await fetch(`${TASK_BOARD_API_PREFIX}/parse`, {
5372
+ method: "POST",
5373
+ headers: { "content-type": "application/json" },
5374
+ body: JSON.stringify(request),
5375
+ ...signal === void 0 ? {} : { signal }
5376
+ });
5377
+ } catch {
5378
+ throw new HostApiError("unreachable", t("board.hostError.unreachable"));
5379
+ }
5380
+ const text = await response.text();
5381
+ let parsed;
5382
+ let readable = false;
5383
+ if (text.trim() !== "") try {
5384
+ parsed = JSON.parse(text);
5385
+ readable = true;
5386
+ } catch {
5387
+ readable = false;
5388
+ }
5389
+ const record = readable && typeof parsed === "object" && parsed !== null ? parsed : void 0;
5390
+ if (response.ok) {
5391
+ if (record !== void 0 && isTaskParseDraft(record.draft)) return record.draft;
5392
+ throw new HostApiError("unexpected", t("new.aiParseFailed", { error: t("board.hostError.unexpected", { status: String(response.status) }) }), response.status);
5393
+ }
5394
+ if (response.status === 404) throw new HostApiError("not-mounted", t("new.aiParseUnavailable"), 404);
5395
+ if (response.status === 401 || response.status === 403) throw new HostApiError("unauthorized", t("board.hostError.unauthorized"), response.status);
5396
+ const code = typeof record?.code === "string" ? record.code : void 0;
5397
+ if (code === "no-model") throw new HostApiError("rejected", t("new.aiParseNoModel"), response.status);
5398
+ if (code === "timeout") throw new HostApiError("timeout", t("new.aiParseTimeout", { seconds: String(TASK_PARSE_TIMEOUT_SECONDS) }), response.status);
5399
+ throw new HostApiError("rejected", t("new.aiParseFailed", { error: typeof record?.error === "string" && record.error !== "" ? record.error : String(response.status) }), response.status);
5400
+ }
4717
5401
  subscribe(listener) {
4718
5402
  const events = new EventSource(`${TASK_BOARD_API_PREFIX}/events`);
5403
+ let lastStreamErrorNotify = 0;
4719
5404
  events.onmessage = (message) => {
4720
5405
  try {
4721
5406
  const parsed = JSON.parse(message.data);
@@ -4725,6 +5410,12 @@ window.__ModuleLoader__.load({
4725
5410
  listener();
4726
5411
  }
4727
5412
  };
5413
+ events.onerror = () => {
5414
+ const now = Date.now();
5415
+ if (now - lastStreamErrorNotify < STREAM_ERROR_NOTIFY_MS) return;
5416
+ lastStreamErrorNotify = now;
5417
+ listener();
5418
+ };
4728
5419
  const onVisible = () => {
4729
5420
  if (document.visibilityState === "visible") listener();
4730
5421
  };
@@ -4743,7 +5434,7 @@ window.__ModuleLoader__.load({
4743
5434
  /** The building package's version, when the bundle carries it. */
4744
5435
  function bakedVersion() {
4745
5436
  try {
4746
- return "0.3.21";
5437
+ return "0.3.23";
4747
5438
  } catch {
4748
5439
  return;
4749
5440
  }
@@ -4894,6 +5585,9 @@ window.__ModuleLoader__.load({
4894
5585
  };
4895
5586
  pushWorkspaceOptions();
4896
5587
  disposers.push(workspaces.list.subscribe(pushWorkspaceOptions));
5588
+ controller.setWorkspaceCreator(async (path) => {
5589
+ return { workspaceId: (await workspaces.create({ path })).workspaceId };
5590
+ });
4897
5591
  const pushModelOptions = async () => {
4898
5592
  try {
4899
5593
  let models = [];