sensorium-mcp 3.1.2 → 3.1.3-board.2

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 (217) hide show
  1. package/Install-Sensorium.ps1 +61 -0
  2. package/dist/board/api/api.js +61 -0
  3. package/dist/board/api/client.js +96 -0
  4. package/dist/board/components/BottomNav.js +32 -0
  5. package/dist/board/components/Card.js +44 -0
  6. package/dist/board/components/CommentComposer.js +26 -0
  7. package/dist/board/components/CommentList.js +39 -0
  8. package/dist/board/components/Dropdown.js +66 -0
  9. package/dist/board/components/Lane.js +40 -0
  10. package/dist/board/components/PriorityChip.js +17 -0
  11. package/dist/board/components/ProfileSummary.js +39 -0
  12. package/dist/board/components/StateBanner.js +22 -0
  13. package/dist/board/components/StatusChip.js +21 -0
  14. package/dist/board/components/TokenEntry.js +42 -0
  15. package/dist/board/config.js +56 -0
  16. package/dist/board/index.html +18 -0
  17. package/dist/board/main.js +37 -0
  18. package/dist/board/mock/api.mock.js +193 -0
  19. package/dist/board/mock/fixtures.js +164 -0
  20. package/dist/board/schema/policy.js +192 -0
  21. package/dist/board/state/auth.js +55 -0
  22. package/dist/board/state/useBoard.js +149 -0
  23. package/dist/board/state/useBuilder.js +98 -0
  24. package/dist/board/state/useCard.js +67 -0
  25. package/dist/board/styles/board.css +1760 -0
  26. package/dist/board/styles/tokens.css +71 -0
  27. package/dist/board/vendor/vue.esm-browser.js +9 -0
  28. package/dist/board/views/App.js +65 -0
  29. package/dist/board/views/BoardView.js +84 -0
  30. package/dist/board/views/BuilderView.js +303 -0
  31. package/dist/board/views/CardView.js +245 -0
  32. package/dist/board/views/SettingsView.js +145 -0
  33. package/dist/config/agent-settings.d.ts +2 -0
  34. package/dist/config/agent-settings.d.ts.map +1 -1
  35. package/dist/config/agent-settings.js +23 -0
  36. package/dist/config/agent-settings.js.map +1 -1
  37. package/dist/config/index.d.ts +1 -0
  38. package/dist/config/index.d.ts.map +1 -1
  39. package/dist/config/index.js +12 -0
  40. package/dist/config/index.js.map +1 -1
  41. package/dist/config/misc-settings.d.ts +14 -0
  42. package/dist/config/misc-settings.d.ts.map +1 -1
  43. package/dist/config/misc-settings.js +53 -0
  44. package/dist/config/misc-settings.js.map +1 -1
  45. package/dist/dashboard/board-static.d.ts +23 -0
  46. package/dist/dashboard/board-static.d.ts.map +1 -0
  47. package/dist/dashboard/board-static.js +131 -0
  48. package/dist/dashboard/board-static.js.map +1 -0
  49. package/dist/dashboard/routes/board.d.ts +73 -0
  50. package/dist/dashboard/routes/board.d.ts.map +1 -0
  51. package/dist/dashboard/routes/board.js +623 -0
  52. package/dist/dashboard/routes/board.js.map +1 -0
  53. package/dist/dashboard/routes/claude.d.ts.map +1 -1
  54. package/dist/dashboard/routes/claude.js +10 -1
  55. package/dist/dashboard/routes/claude.js.map +1 -1
  56. package/dist/dashboard/routes/settings.d.ts +2 -0
  57. package/dist/dashboard/routes/settings.d.ts.map +1 -1
  58. package/dist/dashboard/routes/settings.js +24 -1
  59. package/dist/dashboard/routes/settings.js.map +1 -1
  60. package/dist/dashboard/routes/threads-read.d.ts +7 -3
  61. package/dist/dashboard/routes/threads-read.d.ts.map +1 -1
  62. package/dist/dashboard/routes/threads-read.js +19 -4
  63. package/dist/dashboard/routes/threads-read.js.map +1 -1
  64. package/dist/dashboard/routes/types.d.ts +6 -0
  65. package/dist/dashboard/routes/types.d.ts.map +1 -1
  66. package/dist/dashboard/routes/types.js.map +1 -1
  67. package/dist/dashboard/routes.d.ts.map +1 -1
  68. package/dist/dashboard/routes.js +42 -1
  69. package/dist/dashboard/routes.js.map +1 -1
  70. package/dist/dashboard/spa.html +12 -12
  71. package/dist/data/board/board-schema.d.ts +22 -0
  72. package/dist/data/board/board-schema.d.ts.map +1 -0
  73. package/dist/data/board/board-schema.js +103 -0
  74. package/dist/data/board/board-schema.js.map +1 -0
  75. package/dist/data/board/board-types.d.ts +117 -0
  76. package/dist/data/board/board-types.d.ts.map +1 -0
  77. package/dist/data/board/board-types.js +9 -0
  78. package/dist/data/board/board-types.js.map +1 -0
  79. package/dist/data/board/card.repository.d.ts +64 -0
  80. package/dist/data/board/card.repository.d.ts.map +1 -0
  81. package/dist/data/board/card.repository.js +272 -0
  82. package/dist/data/board/card.repository.js.map +1 -0
  83. package/dist/data/board/comment.repository.d.ts +12 -0
  84. package/dist/data/board/comment.repository.d.ts.map +1 -0
  85. package/dist/data/board/comment.repository.js +31 -0
  86. package/dist/data/board/comment.repository.js.map +1 -0
  87. package/dist/data/board/lane.repository.d.ts +18 -0
  88. package/dist/data/board/lane.repository.d.ts.map +1 -0
  89. package/dist/data/board/lane.repository.js +44 -0
  90. package/dist/data/board/lane.repository.js.map +1 -0
  91. package/dist/data/board/profile-defaults.d.ts +19 -0
  92. package/dist/data/board/profile-defaults.d.ts.map +1 -0
  93. package/dist/data/board/profile-defaults.js +118 -0
  94. package/dist/data/board/profile-defaults.js.map +1 -0
  95. package/dist/data/memory/migration-runner.d.ts.map +1 -1
  96. package/dist/data/memory/migration-runner.js +4 -0
  97. package/dist/data/memory/migration-runner.js.map +1 -1
  98. package/dist/data/memory/narrative-store.d.ts +14 -0
  99. package/dist/data/memory/narrative-store.d.ts.map +1 -1
  100. package/dist/data/memory/narrative-store.js +36 -0
  101. package/dist/data/memory/narrative-store.js.map +1 -1
  102. package/dist/data/memory/narrative.d.ts.map +1 -1
  103. package/dist/data/memory/narrative.js +13 -1
  104. package/dist/data/memory/narrative.js.map +1 -1
  105. package/dist/data/memory/schema.d.ts.map +1 -1
  106. package/dist/data/memory/schema.js +4 -0
  107. package/dist/data/memory/schema.js.map +1 -1
  108. package/dist/index.js +84 -2
  109. package/dist/index.js.map +1 -1
  110. package/dist/integrations/openai/speech.d.ts.map +1 -1
  111. package/dist/integrations/openai/speech.js +52 -19
  112. package/dist/integrations/openai/speech.js.map +1 -1
  113. package/dist/lib/http-retry.d.ts +91 -0
  114. package/dist/lib/http-retry.d.ts.map +1 -0
  115. package/dist/lib/http-retry.js +177 -0
  116. package/dist/lib/http-retry.js.map +1 -0
  117. package/dist/lib/usage-metering/pricing.js +1 -1
  118. package/dist/lib/usage-metering/pricing.js.map +1 -1
  119. package/dist/lib/utils.d.ts +10 -0
  120. package/dist/lib/utils.d.ts.map +1 -1
  121. package/dist/lib/utils.js +22 -0
  122. package/dist/lib/utils.js.map +1 -1
  123. package/dist/server/http-server.d.ts +3 -1
  124. package/dist/server/http-server.d.ts.map +1 -1
  125. package/dist/server/http-server.js +6 -2
  126. package/dist/server/http-server.js.map +1 -1
  127. package/dist/services/board/board-signals.d.ts +25 -0
  128. package/dist/services/board/board-signals.d.ts.map +1 -0
  129. package/dist/services/board/board-signals.js +83 -0
  130. package/dist/services/board/board-signals.js.map +1 -0
  131. package/dist/services/board/card-briefing.d.ts +17 -0
  132. package/dist/services/board/card-briefing.d.ts.map +1 -0
  133. package/dist/services/board/card-briefing.js +86 -0
  134. package/dist/services/board/card-briefing.js.map +1 -0
  135. package/dist/services/board/orchestrator.service.d.ts +55 -0
  136. package/dist/services/board/orchestrator.service.d.ts.map +1 -0
  137. package/dist/services/board/orchestrator.service.js +198 -0
  138. package/dist/services/board/orchestrator.service.js.map +1 -0
  139. package/dist/services/board/release-worker.d.ts +32 -0
  140. package/dist/services/board/release-worker.d.ts.map +1 -0
  141. package/dist/services/board/release-worker.js +49 -0
  142. package/dist/services/board/release-worker.js.map +1 -0
  143. package/dist/services/board/report-capture.d.ts +48 -0
  144. package/dist/services/board/report-capture.d.ts.map +1 -0
  145. package/dist/services/board/report-capture.js +172 -0
  146. package/dist/services/board/report-capture.js.map +1 -0
  147. package/dist/services/board/worker-spawn.d.ts +30 -0
  148. package/dist/services/board/worker-spawn.d.ts.map +1 -0
  149. package/dist/services/board/worker-spawn.js +90 -0
  150. package/dist/services/board/worker-spawn.js.map +1 -0
  151. package/dist/services/broker-client.d.ts +30 -0
  152. package/dist/services/broker-client.d.ts.map +1 -1
  153. package/dist/services/broker-client.js +23 -0
  154. package/dist/services/broker-client.js.map +1 -1
  155. package/dist/services/codex-spawn.service.d.ts +2 -2
  156. package/dist/services/codex-spawn.service.d.ts.map +1 -1
  157. package/dist/services/codex-spawn.service.js +54 -54
  158. package/dist/services/codex-spawn.service.js.map +1 -1
  159. package/dist/services/http-retry.d.ts +1 -90
  160. package/dist/services/http-retry.d.ts.map +1 -1
  161. package/dist/services/http-retry.js +1 -176
  162. package/dist/services/http-retry.js.map +1 -1
  163. package/dist/services/mcp-config.service.d.ts.map +1 -1
  164. package/dist/services/mcp-config.service.js +5 -1
  165. package/dist/services/mcp-config.service.js.map +1 -1
  166. package/dist/services/passive-notice.service.d.ts +39 -0
  167. package/dist/services/passive-notice.service.d.ts.map +1 -0
  168. package/dist/services/passive-notice.service.js +274 -0
  169. package/dist/services/passive-notice.service.js.map +1 -0
  170. package/dist/services/spawn-common.d.ts.map +1 -1
  171. package/dist/services/spawn-common.js +14 -1
  172. package/dist/services/spawn-common.js.map +1 -1
  173. package/dist/services/telegram.d.ts +7 -5
  174. package/dist/services/telegram.d.ts.map +1 -1
  175. package/dist/services/telegram.js +18 -13
  176. package/dist/services/telegram.js.map +1 -1
  177. package/dist/services/usage/usage-inbox-notifier.d.ts +50 -0
  178. package/dist/services/usage/usage-inbox-notifier.d.ts.map +1 -0
  179. package/dist/services/usage/usage-inbox-notifier.js +118 -0
  180. package/dist/services/usage/usage-inbox-notifier.js.map +1 -0
  181. package/dist/services/usage/usage-poller.service.d.ts +41 -0
  182. package/dist/services/usage/usage-poller.service.d.ts.map +1 -0
  183. package/dist/services/usage/usage-poller.service.js +66 -0
  184. package/dist/services/usage/usage-poller.service.js.map +1 -0
  185. package/dist/services/usage/usage-snapshot-provider.d.ts +29 -0
  186. package/dist/services/usage/usage-snapshot-provider.d.ts.map +1 -0
  187. package/dist/services/usage/usage-snapshot-provider.js +59 -0
  188. package/dist/services/usage/usage-snapshot-provider.js.map +1 -0
  189. package/dist/services/usage/usage-types.d.ts +23 -0
  190. package/dist/services/usage/usage-types.d.ts.map +1 -0
  191. package/dist/services/usage/usage-types.js +14 -0
  192. package/dist/services/usage/usage-types.js.map +1 -0
  193. package/dist/services/worker-cleanup.service.d.ts +6 -1
  194. package/dist/services/worker-cleanup.service.d.ts.map +1 -1
  195. package/dist/services/worker-cleanup.service.js +123 -10
  196. package/dist/services/worker-cleanup.service.js.map +1 -1
  197. package/dist/tools/delegate-tool.d.ts.map +1 -1
  198. package/dist/tools/delegate-tool.js +34 -2
  199. package/dist/tools/delegate-tool.js.map +1 -1
  200. package/dist/tools/utility-tools.d.ts.map +1 -1
  201. package/dist/tools/utility-tools.js +7 -3
  202. package/dist/tools/utility-tools.js.map +1 -1
  203. package/dist/tools/wait/consolidation-strategy.d.ts +42 -0
  204. package/dist/tools/wait/consolidation-strategy.d.ts.map +1 -0
  205. package/dist/tools/wait/consolidation-strategy.js +50 -0
  206. package/dist/tools/wait/consolidation-strategy.js.map +1 -0
  207. package/dist/tools/wait/drive-handler.d.ts +5 -7
  208. package/dist/tools/wait/drive-handler.d.ts.map +1 -1
  209. package/dist/tools/wait/drive-handler.js +30 -48
  210. package/dist/tools/wait/drive-handler.js.map +1 -1
  211. package/dist/tools/wait/message-delivery.d.ts.map +1 -1
  212. package/dist/tools/wait/message-delivery.js +13 -0
  213. package/dist/tools/wait/message-delivery.js.map +1 -1
  214. package/package.json +2 -2
  215. package/templates/atomic-build-spec.default.md +57 -0
  216. package/templates/buildless-vue-preview.default.md +47 -0
  217. package/templates/phased-build.default.md +59 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Board-front runtime config. ❌ FROZEN SEAM — do NOT edit during UI iteration.
3
+ *
4
+ * MODE selects the data adapter downstream (see api/api.js):
5
+ * 'production' → real fetch to the board API with the Bearer token.
6
+ * 'preview' → mock adapter over fixtures (Claude Design sandbox); auth is a
7
+ * no-op so EVERY screen renders with zero real network calls.
8
+ */
9
+
10
+ /**
11
+ * Canonical card statuses — mirror of src/data/board/board-types.ts `CardStatus`
12
+ * plus the P2 `decision_needed` value the kanban reserves a column for now so the
13
+ * skeleton lane exists before the backend migration lands.
14
+ */
15
+ export const CARD_STATUSES = ["backlog", "in_progress", "decision_needed", "blocked", "done"];
16
+
17
+ /** Board poll cadence while the browser tab is visible (ms). */
18
+ export const POLL_INTERVAL_MS = 5000;
19
+
20
+ /**
21
+ * All API calls are same-origin, ROOT-RELATIVE (`/api/...`) — safe behind any
22
+ * reverse proxy / Cloudflare Tunnel. No host or port is ever hardcoded.
23
+ */
24
+ export const API_BASE = "";
25
+
26
+ function readOverride() {
27
+ try {
28
+ const q = new URLSearchParams(location.search);
29
+ const qMode = q.get("mode") ?? (q.has("preview") ? "preview" : null);
30
+ if (qMode === "preview" || qMode === "production") return qMode;
31
+ const stored = localStorage.getItem("BOARD_MODE");
32
+ if (stored === "preview" || stored === "production") return stored;
33
+ } catch {
34
+ /* sandbox without URL/localStorage — fall through to auto-detect */
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * Auto-detect the run mode.
41
+ * 1. explicit `?mode=` / `?preview` / localStorage override wins (the design
42
+ * tool can force either without editing this file).
43
+ * 2. non-http(s) origin (file: / blob: / data:) → sandbox → preview.
44
+ * 3. http(s) served under `/board` by our server → production.
45
+ * 4. anything else (an artifact host serving at `/`) → preview.
46
+ */
47
+ export function detectMode() {
48
+ const override = readOverride();
49
+ if (override) return override;
50
+ const proto = typeof location !== "undefined" ? location.protocol : "file:";
51
+ if (proto !== "http:" && proto !== "https:") return "preview";
52
+ const path = (typeof location !== "undefined" && location.pathname) || "/";
53
+ return path.startsWith("/board") ? "production" : "preview";
54
+ }
55
+
56
+ export const MODE = detectMode();
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <meta name="color-scheme" content="dark" />
7
+ <meta name="theme-color" content="#15141d" />
8
+ <title>Dispatch — Agent Task Board</title>
9
+ <!-- ✅ styles/ is editable by Claude Design. -->
10
+ <link rel="stylesheet" href="./styles/tokens.css" />
11
+ <link rel="stylesheet" href="./styles/board.css" />
12
+ </head>
13
+ <body>
14
+ <div id="app"></div>
15
+ <!-- ❌ main.js is the frozen entry/router. -->
16
+ <script type="module" src="./main.js"></script>
17
+ </body>
18
+ </html>
@@ -0,0 +1,37 @@
1
+ /**
2
+ * App entry + hash router. ❌ FROZEN SEAM (entry/router wiring only).
3
+ *
4
+ * Provides a minimal reactive router to the app under the inject key 'router'.
5
+ * Views consume it via `inject('router')` and never import this file:
6
+ * router.current = { name, params, path } // name ∈ board|card|new|settings
7
+ * router.navigate('/card/12') // sets location.hash
8
+ *
9
+ * A hash router (not history routing) is deliberate: the same file set runs from
10
+ * file:// in the design sandbox AND under /board in production with no server-side
11
+ * route table for client paths.
12
+ */
13
+
14
+ import { createApp, reactive } from "./vendor/vue.esm-browser.js";
15
+ import App from "./views/App.js";
16
+
17
+ function parseHash() {
18
+ const raw = (location.hash || "#/board").replace(/^#/, "");
19
+ const path = raw.split("?")[0];
20
+ const parts = path.split("/").filter(Boolean);
21
+ if (parts[0] === "card" && parts[1]) return { name: "card", params: { id: Number(parts[1]) }, path };
22
+ if (parts[0] === "new") return { name: "new", params: {}, path };
23
+ if (parts[0] === "settings") return { name: "settings", params: {}, path };
24
+ return { name: "board", params: {}, path: "/board" };
25
+ }
26
+
27
+ const router = reactive({ current: parseHash() });
28
+ router.navigate = (to) => {
29
+ location.hash = to.startsWith("#") ? to : `#${to}`;
30
+ };
31
+ window.addEventListener("hashchange", () => {
32
+ router.current = parseHash();
33
+ });
34
+
35
+ const app = createApp(App);
36
+ app.provide("router", router);
37
+ app.mount("#app");
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Preview data adapter. ❌ FROZEN SEAM. Dynamically imported ONLY when
3
+ * MODE==='preview' (api/api.js) — no mock byte ships to production.
4
+ *
5
+ * An in-memory store over fixtures that implements the full BoardApi surface,
6
+ * including mutations, so create / move / stop / comment / archive / delete all
7
+ * work standalone in the design sandbox with ZERO real network calls.
8
+ *
9
+ * Extra design affordances (query string, preview only):
10
+ * ?mock=empty → the board renders its empty state.
11
+ * ?mock=error → list calls reject so the error state is visible.
12
+ *
13
+ * @typedef {import('../api/contract.js').BoardApi} BoardApi
14
+ */
15
+
16
+ import { fixtures } from "./fixtures.js";
17
+ import { POLICY_SCHEMA, PRESETS } from "../schema/policy.js";
18
+
19
+ const clone = (x) => JSON.parse(JSON.stringify(x));
20
+
21
+ const scenario = (() => {
22
+ try {
23
+ return new URLSearchParams(location.search).get("mock") || "default";
24
+ } catch {
25
+ return "default";
26
+ }
27
+ })();
28
+
29
+ const db = {
30
+ lanes: clone(fixtures.lanes),
31
+ cards: scenario === "empty" ? [] : clone(fixtures.cards),
32
+ comments: clone(fixtures.comments),
33
+ profiles: clone(fixtures.profiles),
34
+ seq: 1000,
35
+ };
36
+
37
+ const delay = (v) => new Promise((r) => setTimeout(() => r(v), 120));
38
+ const nowIso = () => new Date().toISOString();
39
+ const find = (id) => db.cards.find((c) => c.cardId === id);
40
+
41
+ function guardScenario() {
42
+ if (scenario === "error") throw new Error("Simulated preview error (?mock=error)");
43
+ }
44
+
45
+ /** @type {BoardApi} */
46
+ export const mockApi = {
47
+ async listCards({ includeArchived = false, lane } = {}) {
48
+ await delay();
49
+ guardScenario();
50
+ let cards = db.cards.filter((c) => (includeArchived ? true : !c.archivedAt));
51
+ if (lane != null) cards = cards.filter((c) => c.laneThreadId === lane);
52
+ return clone(cards);
53
+ },
54
+
55
+ async getCard(id) {
56
+ await delay();
57
+ const card = find(id);
58
+ if (!card) throw new Error(`Card ${id} not found`);
59
+ return clone({ card, comments: db.comments[id] || [] });
60
+ },
61
+
62
+ async createCard(input) {
63
+ await delay();
64
+ if (!input.title || !input.title.trim()) throw new Error("'title' is required");
65
+ if (input.laneThreadId == null) throw new Error("integer 'laneThreadId' is required");
66
+ const id = ++db.seq;
67
+ const card = {
68
+ cardId: id,
69
+ title: input.title.trim(),
70
+ description: input.description || "",
71
+ acceptance: input.acceptance || "",
72
+ type: input.type || "chore",
73
+ laneThreadId: input.laneThreadId,
74
+ priority: input.priority || 0,
75
+ status: "backlog",
76
+ profile: input.profile || db.profiles[input.type || "chore"] || {},
77
+ escalate: input.escalate === true,
78
+ recurDelayMin: input.recurDelayMin ?? null,
79
+ recurGroupId: null,
80
+ nextSpawnAt: null,
81
+ workerThreadId: null,
82
+ blockedReason: null,
83
+ archivedAt: null,
84
+ createdAt: nowIso(),
85
+ updatedAt: nowIso(),
86
+ startedAt: null,
87
+ finishedAt: null,
88
+ };
89
+ db.cards.push(card);
90
+ return clone(card);
91
+ },
92
+
93
+ async updateCard(id, patch) {
94
+ await delay();
95
+ const card = find(id);
96
+ if (!card) throw new Error(`Card ${id} not found`);
97
+ if ("archivedAt" in patch) card.archivedAt = patch.archivedAt === null ? null : nowIso();
98
+ for (const k of ["title", "description", "acceptance", "type", "laneThreadId", "priority", "escalate", "status"]) {
99
+ if (k in patch && patch[k] !== undefined) card[k] = patch[k];
100
+ }
101
+ if (patch.status === "backlog") {
102
+ card.workerThreadId = null;
103
+ card.blockedReason = null;
104
+ } else if (patch.status === "done") {
105
+ card.finishedAt = nowIso();
106
+ card.workerThreadId = null;
107
+ }
108
+ card.updatedAt = nowIso();
109
+ return clone(card);
110
+ },
111
+
112
+ async deleteCard(id) {
113
+ await delay();
114
+ const i = db.cards.findIndex((c) => c.cardId === id);
115
+ if (i >= 0) db.cards.splice(i, 1);
116
+ delete db.comments[id];
117
+ },
118
+
119
+ async stopCard(id) {
120
+ await delay();
121
+ const card = find(id);
122
+ if (!card) throw new Error(`Card ${id} not found`);
123
+ card.status = "blocked";
124
+ card.blockedReason = "Stopped by operator";
125
+ card.workerThreadId = null;
126
+ card.updatedAt = nowIso();
127
+ return clone(card);
128
+ },
129
+
130
+ async addComment(id, content) {
131
+ await delay();
132
+ const card = find(id);
133
+ if (!card) throw new Error(`Card ${id} not found`);
134
+ const comment = { id: ++db.seq, cardId: id, author: "operator", content, createdAt: nowIso() };
135
+ (db.comments[id] ||= []).push(comment);
136
+ return clone(comment);
137
+ },
138
+
139
+ async listLanes() {
140
+ await delay();
141
+ return clone(db.lanes);
142
+ },
143
+
144
+ async getProfiles() {
145
+ await delay();
146
+ return clone(db.profiles);
147
+ },
148
+
149
+ async threadRunning(threadId) {
150
+ await delay();
151
+ // A worker referenced by an in_progress fixture card is "running".
152
+ return db.cards.some((c) => c.status === "in_progress" && c.workerThreadId === threadId);
153
+ },
154
+
155
+ // Card-builder "How" step schema + presets. Sourced from the EDITABLE
156
+ // design-layer file schema/policy.js so a designer can tune the controls and
157
+ // see them live in preview. (Production wiring is a P1 TODO in api/client.js.)
158
+ async getPolicySchema() {
159
+ await delay();
160
+ return clone(POLICY_SCHEMA);
161
+ },
162
+
163
+ async getPresets() {
164
+ await delay();
165
+ return clone(PRESETS);
166
+ },
167
+
168
+ // Lane management. Delete is refused while any card (incl. archived) is on
169
+ // the lane — mirrors the real board rule.
170
+ async createLane(name) {
171
+ await delay();
172
+ if (!name || !name.trim()) throw new Error("'name' is required");
173
+ const lane = { threadId: ++db.seq, name: name.trim(), connected: false, maxConcurrent: 1 };
174
+ db.lanes.push(lane);
175
+ return clone(lane);
176
+ },
177
+
178
+ async updateLane(id, patch) {
179
+ await delay();
180
+ const lane = db.lanes.find((l) => l.threadId === id);
181
+ if (!lane) throw new Error(`Lane ${id} not found`);
182
+ if (patch.name != null && String(patch.name).trim()) lane.name = String(patch.name).trim();
183
+ return clone(lane);
184
+ },
185
+
186
+ async deleteLane(id) {
187
+ await delay();
188
+ const used = db.cards.filter((c) => c.laneThreadId === id).length;
189
+ if (used) throw new Error(`Lane has ${used} card(s) — move or delete them first`);
190
+ const i = db.lanes.findIndex((l) => l.threadId === id);
191
+ if (i >= 0) db.lanes.splice(i, 1);
192
+ },
193
+ };
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Preview fixtures. ❌ FROZEN DATA (not UI) — fixture changes go through a coding
3
+ * worker so preview stays honest to the real API shapes. Card objects match
4
+ * src/data/board/board-types.ts `BoardCard` exactly; lanes match the GET
5
+ * /api/board/lanes decorated shape (`name` added).
6
+ *
7
+ * Covers EVERY kanban column so all screens render standalone from mocks:
8
+ * backlog, in_progress (with worker), decision_needed, blocked, done + one
9
+ * archived card (visible only with "include archived").
10
+ */
11
+
12
+ const T0 = "2026-08-08T09:00:00.000Z";
13
+ const T1 = "2026-08-08T12:30:00.000Z";
14
+ const T2 = "2026-08-08T14:05:00.000Z";
15
+
16
+ /** @param {Partial<import('../api/contract.js').BoardCard>} o */
17
+ function card(o) {
18
+ return {
19
+ cardId: 0,
20
+ title: "",
21
+ description: "",
22
+ acceptance: "",
23
+ type: "chore",
24
+ laneThreadId: 101,
25
+ priority: 0,
26
+ status: "backlog",
27
+ profile: {},
28
+ escalate: false,
29
+ recurDelayMin: null,
30
+ recurGroupId: null,
31
+ nextSpawnAt: null,
32
+ workerThreadId: null,
33
+ blockedReason: null,
34
+ archivedAt: null,
35
+ createdAt: T0,
36
+ updatedAt: T0,
37
+ startedAt: null,
38
+ finishedAt: null,
39
+ ...o,
40
+ };
41
+ }
42
+
43
+ export const fixtures = {
44
+ lanes: [
45
+ { threadId: 101, connected: true, maxConcurrent: 2, createdAt: T0, name: "Frontend Lane" },
46
+ { threadId: 202, connected: true, maxConcurrent: 1, createdAt: T0, name: "Backend Lane" },
47
+ { threadId: 303, connected: false, maxConcurrent: 1, createdAt: T0, name: "Research Lane" },
48
+ ],
49
+
50
+ cards: [
51
+ card({
52
+ cardId: 1,
53
+ title: "Add dark-mode toggle to settings",
54
+ description: "Persist the choice in localStorage and honour prefers-color-scheme on first load.",
55
+ acceptance: "Toggle flips theme instantly; choice survives reload; no FOUC.",
56
+ type: "feature",
57
+ laneThreadId: 101,
58
+ priority: 2,
59
+ status: "backlog",
60
+ profile: { agentType: "claude", createBranch: true, runTests: true, openPr: true, expertReview: true, push: true, ciCheck: "github" },
61
+ }),
62
+ card({
63
+ cardId: 2,
64
+ title: "Bump eslint to v10 and fix new violations",
65
+ description: "Upgrade eslint + config, resolve the handful of new lint errors.",
66
+ acceptance: "npm run lint passes clean on CI.",
67
+ type: "tech-debt",
68
+ laneThreadId: 202,
69
+ priority: 0,
70
+ status: "backlog",
71
+ profile: { agentType: "claude", createBranch: true, runTests: true },
72
+ }),
73
+ card({
74
+ cardId: 3,
75
+ title: "Investigate intermittent worker hang on spawn",
76
+ description: "A worker occasionally never emits STARTED. Reproduce, find the root cause, propose a fix.",
77
+ acceptance: "Root cause identified with evidence; fix plan attached as a comment.",
78
+ type: "bug",
79
+ laneThreadId: 202,
80
+ priority: 3,
81
+ status: "in_progress",
82
+ workerThreadId: 9901,
83
+ startedAt: T1,
84
+ profile: { agentType: "claude", createBranch: true, runTests: true, expertReview: true },
85
+ }),
86
+ card({
87
+ cardId: 4,
88
+ title: "Choose analytics vendor for usage metrics",
89
+ description: "Compare 3 options against privacy + cost constraints. Needs an operator call before build.",
90
+ acceptance: "A vendor is chosen and rationale recorded.",
91
+ type: "research",
92
+ laneThreadId: 303,
93
+ priority: 1,
94
+ status: "decision_needed",
95
+ startedAt: T1,
96
+ finishedAt: T2,
97
+ profile: { agentType: "claude" },
98
+ }),
99
+ card({
100
+ cardId: 5,
101
+ title: "Migrate payments module to new SDK",
102
+ description: "Blocked on an upstream dependency that has not shipped the 4.x release yet.",
103
+ acceptance: "All payment flows pass integration tests on the new SDK.",
104
+ type: "feature",
105
+ laneThreadId: 202,
106
+ priority: 2,
107
+ status: "blocked",
108
+ blockedReason: "Upstream SDK 4.x not released; waiting on vendor.",
109
+ profile: { agentType: "claude", createBranch: true, runTests: true, openPr: true },
110
+ }),
111
+ card({
112
+ cardId: 6,
113
+ title: "Document the board-front isolation contract",
114
+ description: "Write the CLAUDE.md guardrail describing editable vs frozen folders.",
115
+ acceptance: "Guardrail file present and accurate.",
116
+ type: "chore",
117
+ laneThreadId: 101,
118
+ priority: 0,
119
+ status: "done",
120
+ startedAt: T0,
121
+ finishedAt: T1,
122
+ profile: { agentType: "claude" },
123
+ }),
124
+ card({
125
+ cardId: 7,
126
+ title: "Old spike: prototype swipe gestures",
127
+ description: "Archived experiment kept for reference.",
128
+ acceptance: "n/a",
129
+ type: "research",
130
+ laneThreadId: 101,
131
+ priority: 0,
132
+ status: "done",
133
+ startedAt: T0,
134
+ finishedAt: T0,
135
+ archivedAt: T1,
136
+ profile: {},
137
+ }),
138
+ ],
139
+
140
+ /** Keyed by cardId. */
141
+ comments: {
142
+ 3: [
143
+ { id: 1, cardId: 3, author: "board", content: "Card dispatched to worker thread 9901.", createdAt: T1 },
144
+ { id: 2, cardId: 3, author: "worker", content: "Reproduced once in 40 runs. Adding instrumentation around the spawn barrier.", createdAt: T2 },
145
+ ],
146
+ 4: [
147
+ { id: 3, cardId: 4, author: "worker", content: "Shortlisted 3 vendors; two meet the privacy bar. Need an operator decision on cost tier.", createdAt: T2 },
148
+ ],
149
+ 5: [
150
+ { id: 4, cardId: 5, author: "operator", content: "Parking this until the SDK ships.", createdAt: T1 },
151
+ ],
152
+ 6: [{ id: 5, cardId: 6, author: "operator", content: "Looks good — merged.", createdAt: T1 }],
153
+ },
154
+
155
+ /** Mirrors GET /api/board/profiles (effective per-type templates). */
156
+ profiles: {
157
+ bug: { createBranch: true, runTests: true, openPr: true, expertReview: true, push: true, ciCheck: "github", workerSplit: "single" },
158
+ feature: { createBranch: true, runTests: true, build: true, openPr: true, expertReview: true, push: true, ciCheck: "github", workerSplit: "single" },
159
+ "tech-debt": { createBranch: true, runTests: true, openPr: true, expertReview: false, push: true, ciCheck: "github", workerSplit: "single" },
160
+ chore: { createBranch: false, runTests: false, openPr: false, expertReview: false, push: false, ciCheck: "none", workerSplit: "single" },
161
+ research: { createBranch: false, runTests: false, openPr: false, expertReview: false, push: false, ciCheck: "none", workerSplit: "single" },
162
+ "recurring-ops": { createBranch: false, runTests: false, openPr: false, expertReview: false, push: false, ciCheck: "none", workerSplit: "single" },
163
+ },
164
+ };
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Card-builder "How" policy schema. ✅ EDITABLE design-layer data.
3
+ *
4
+ * This is the single source of truth for the builder Step 2 ("How") controls.
5
+ * A designer (Claude Design) edits the option lists / presets / phases here and
6
+ * sees them live in the buildless preview — no build step, no code change in the
7
+ * view. views/BuilderView.js renders this GENERICALLY: only the widget *types*
8
+ * below are hardcoded (in the view's dispatch), never the options themselves.
9
+ *
10
+ * It flows to the view through the frozen seam (mock/api.mock.js → useBuilder()),
11
+ * so this file is design-tier just like views/ and components/ — NOT frozen.
12
+ *
13
+ * ── Widget vocabulary (the only hardcoded part) ──────────────────────────────
14
+ * "select" — single choice from `options` (dropdown)
15
+ * "segmented" — single choice from `options` (button group)
16
+ * "chips" — multi-select subset of `options` (value is an array)
17
+ * "toggle" — boolean on/off switch
18
+ * "phases" — ordered list of [name, reviewFlag] pairs (editable in place)
19
+ *
20
+ * ── Field shape ──────────────────────────────────────────────────────────────
21
+ * { key, widget, label, desc?, options?, default }
22
+ * - key unique id; also the value key on the compiled policy
23
+ * - widget one of the vocabulary above
24
+ * - label shown to the operator; also the compiled-policy row heading
25
+ * - desc optional helper line (rendered under the label / switch)
26
+ * - options choices for select / segmented / chips (ignored otherwise)
27
+ * - default initial value (chips → array, phases → array of [name, bool])
28
+ */
29
+
30
+ /** @typedef {"select"|"segmented"|"chips"|"toggle"|"phases"} PolicyWidget */
31
+
32
+ /**
33
+ * Ordered list of How-step fields. Reorder / add / edit freely — the view
34
+ * iterates this array in order and dispatches on `widget`.
35
+ * @type {Array<{key:string, widget:PolicyWidget, label:string, desc?:string, options?:string[], default:*}>}
36
+ */
37
+ export const POLICY_SCHEMA = [
38
+ {
39
+ key: "provider",
40
+ widget: "segmented",
41
+ label: "Provider",
42
+ options: ["Claude", "Copilot", "Codex"],
43
+ default: "Claude",
44
+ },
45
+ {
46
+ key: "model",
47
+ widget: "select",
48
+ label: "Worker model",
49
+ options: ["Claude Haiku · fast", "Claude Sonnet · balanced", "Claude Opus · deep"],
50
+ default: "Claude Sonnet · balanced",
51
+ },
52
+ {
53
+ key: "skills",
54
+ widget: "chips",
55
+ label: "Skills",
56
+ options: ["Git", "Tests", "Web search", "Docs", "Database", "Design review"],
57
+ default: ["Git"],
58
+ },
59
+ {
60
+ key: "runIn",
61
+ widget: "select",
62
+ label: "Run in",
63
+ desc: "Where the worker executes",
64
+ // Base run targets; existing threads are appended by the seam in P1.
65
+ options: ["New worker", "New persistent thread"],
66
+ default: "New worker",
67
+ },
68
+ {
69
+ key: "worktree",
70
+ widget: "toggle",
71
+ label: "Run in a git worktree",
72
+ desc: "Isolated branch — nothing touches main until you merge",
73
+ default: true,
74
+ // Compiled-policy phrasing (optional; falls back to "on"/"off").
75
+ onText: "git worktree, merge on approval",
76
+ offText: "direct on main",
77
+ },
78
+ {
79
+ key: "review",
80
+ widget: "toggle",
81
+ label: "Expert code review",
82
+ desc: "A second model reviews the diff before completion",
83
+ default: false,
84
+ onText: "expert pass before completion",
85
+ offText: "off",
86
+ },
87
+ {
88
+ key: "verifyDone",
89
+ widget: "toggle",
90
+ label: "Verify on done",
91
+ desc: "Check the acceptance criteria before moving to Done (else an ephemeral check)",
92
+ default: true,
93
+ onText: "verify acceptance → move to Done → notify you",
94
+ offText: "off",
95
+ },
96
+ {
97
+ key: "phases",
98
+ widget: "phases",
99
+ label: "Phases",
100
+ desc: "run in order",
101
+ default: [["Plan", true], ["Implement", false]],
102
+ },
103
+ ];
104
+
105
+ /**
106
+ * Presets seed the whole How step (and the card `type`) in one click.
107
+ * `values` is keyed by POLICY_SCHEMA field `key`; any key omitted falls back to
108
+ * that field's `default`. Editing a control after applying a preset clears it.
109
+ *
110
+ * "Research" demonstrates a phase-less run (`phases: []` → "single pass").
111
+ * "Full delivery" carries the canonical
112
+ * Analysis → Plan → Implementation → Expert code review → Commit pipeline.
113
+ * @type {Array<{name:string, type?:string, values:Object}>}
114
+ */
115
+ export const PRESETS = [
116
+ {
117
+ name: "Bug fix",
118
+ type: "bug",
119
+ values: {
120
+ provider: "Claude",
121
+ model: "Claude Sonnet · balanced",
122
+ skills: ["Git", "Tests"],
123
+ runIn: "New worker",
124
+ worktree: true,
125
+ review: true,
126
+ verifyDone: true,
127
+ phases: [["Reproduce", false], ["Fix root cause", true], ["Verify", false]],
128
+ },
129
+ },
130
+ {
131
+ name: "Feature",
132
+ type: "feature",
133
+ values: {
134
+ provider: "Claude",
135
+ model: "Claude Opus · deep",
136
+ skills: ["Git", "Tests", "Docs"],
137
+ runIn: "New worker",
138
+ worktree: true,
139
+ review: true,
140
+ verifyDone: true,
141
+ phases: [["Plan", true], ["Implement", false], ["Test & document", true]],
142
+ },
143
+ },
144
+ {
145
+ name: "Full delivery",
146
+ type: "feature",
147
+ values: {
148
+ provider: "Claude",
149
+ model: "Claude Opus · deep",
150
+ skills: ["Git", "Tests", "Docs"],
151
+ runIn: "New persistent thread",
152
+ worktree: true,
153
+ review: true,
154
+ verifyDone: true,
155
+ phases: [
156
+ ["Analysis", false],
157
+ ["Plan", true],
158
+ ["Implementation", false],
159
+ ["Expert code review", true],
160
+ ["Commit", false],
161
+ ],
162
+ },
163
+ },
164
+ {
165
+ name: "Research",
166
+ type: "research",
167
+ values: {
168
+ provider: "Claude",
169
+ model: "Claude Sonnet · balanced",
170
+ skills: ["Web search", "Docs"],
171
+ runIn: "New worker",
172
+ worktree: false,
173
+ review: false,
174
+ verifyDone: false,
175
+ phases: [],
176
+ },
177
+ },
178
+ {
179
+ name: "Tech debt",
180
+ type: "tech-debt",
181
+ values: {
182
+ provider: "Codex",
183
+ model: "Claude Sonnet · balanced",
184
+ skills: ["Git", "Tests"],
185
+ runIn: "New worker",
186
+ worktree: true,
187
+ review: false,
188
+ verifyDone: true,
189
+ phases: [["Map current structure", false], ["Migrate incrementally", true]],
190
+ },
191
+ },
192
+ ];