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,84 @@
1
+ /**
2
+ * Board / kanban screen — ✅ EDITABLE.
3
+ *
4
+ * Dispatch design: transparent swipeable columns (dot + name + count pill),
5
+ * floating cards, accent "+ New card" pill. All data + actions come from
6
+ * useBoard() — no fetch here.
7
+ */
8
+
9
+ import { computed, inject, onMounted, onUnmounted, ref } from "../vendor/vue.esm-browser.js";
10
+ import { useBoard } from "../state/useBoard.js";
11
+ import Lane from "../components/Lane.js";
12
+ import StateBanner from "../components/StateBanner.js";
13
+ import Dropdown from "../components/Dropdown.js";
14
+
15
+ const COLUMN_LABELS = {
16
+ backlog: "Backlog",
17
+ in_progress: "In progress",
18
+ decision_needed: "Decision needed",
19
+ blocked: "Blocked",
20
+ done: "Done",
21
+ };
22
+
23
+ export default {
24
+ name: "BoardView",
25
+ components: { Lane, StateBanner, Dropdown },
26
+ setup() {
27
+ const router = inject("router");
28
+ const board = useBoard();
29
+ const laneSel = ref(board.laneFilter.value);
30
+
31
+ onMounted(() => board.start());
32
+ onUnmounted(() => board.stop());
33
+
34
+ function open(card) {
35
+ router.navigate(`/card/${card.cardId}`);
36
+ }
37
+ const laneOptions = computed(() => [
38
+ { value: null, label: "All lanes" },
39
+ ...board.lanes.value.map((l) => ({ value: l.threadId, label: l.name })),
40
+ ]);
41
+ function onLaneChange(v) {
42
+ laneSel.value = v;
43
+ board.actions.setLaneFilter(v);
44
+ }
45
+ function onArchivedChange(e) {
46
+ board.actions.setIncludeArchived(e.target.checked);
47
+ }
48
+
49
+ return { router, board, laneSel, laneOptions, open, onLaneChange, onArchivedChange, labels: COLUMN_LABELS };
50
+ },
51
+ template: `
52
+ <section class="board">
53
+ <div class="board__toolbar">
54
+ <Dropdown class="dropdown--sm" :model-value="laneSel" :options="laneOptions"
55
+ placeholder="All lanes" @update:model-value="onLaneChange" />
56
+ <label class="field field--check">
57
+ <input type="checkbox" :checked="board.includeArchived.value" @change="onArchivedChange" />
58
+ <span class="field__label">Archived</span>
59
+ </label>
60
+ <button class="btn btn--primary board__new" type="button" @click="router.navigate('/new')">
61
+ + New card
62
+ </button>
63
+ </div>
64
+
65
+ <StateBanner
66
+ :loading="board.loading.value"
67
+ :error="board.error.value"
68
+ @retry="board.actions.refresh()"
69
+ />
70
+
71
+ <div class="board__columns" role="list">
72
+ <Lane
73
+ v-for="status in board.statuses"
74
+ :key="status"
75
+ :title="labels[status] || status"
76
+ :status="status"
77
+ :cards="board.byStatus.value[status]"
78
+ :lane-name="board.laneName"
79
+ @open="open"
80
+ />
81
+ </div>
82
+ </section>
83
+ `,
84
+ };
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Card builder — ✅ EDITABLE. Step-by-step wizard (full concept pass).
3
+ *
4
+ * Step 1 "What": title / goal / acceptance / priority / type / lane.
5
+ * Step 2 "How": rendered GENERICALLY from the policy schema — the view knows
6
+ * only the widget *types* (select / segmented / chips / toggle / phases); every
7
+ * option list, preset and phase preset lives in the editable data file
8
+ * schema/policy.js and reaches here through useBuilder() (frozen seam). A
9
+ * designer tunes the controls by editing that data file, no view change needed.
10
+ *
11
+ * Preset picker on top; collapsible read-only "Compiled policy" preview below,
12
+ * derived live from the schema + current values.
13
+ *
14
+ * Only the WHAT fields + type/lane/priority submit through useBuilder(). The HOW
15
+ * values are local builder state (compiled into the preview); the seam wiring to
16
+ * persist them is a P1+ extension.
17
+ */
18
+
19
+ import { computed, inject, onMounted, reactive, ref } from "../vendor/vue.esm-browser.js";
20
+ import { useBuilder } from "../state/useBuilder.js";
21
+ import StateBanner from "../components/StateBanner.js";
22
+ import Dropdown from "../components/Dropdown.js";
23
+
24
+ const PRIORITIES = ["Low", "Medium", "High", "Urgent"];
25
+
26
+ /** Deep-ish clone of a schema default (handles chips arrays + phases pairs). */
27
+ function cloneVal(v) {
28
+ return Array.isArray(v) ? v.map((x) => (Array.isArray(x) ? [...x] : x)) : v;
29
+ }
30
+
31
+ export default {
32
+ name: "BuilderView",
33
+ components: { StateBanner, Dropdown },
34
+ setup() {
35
+ const router = inject("router");
36
+ const b = useBuilder();
37
+
38
+ const step = ref(1);
39
+ const preset = ref(null);
40
+ const previewOpen = ref(false);
41
+ const newPhase = ref("");
42
+ // HOW values, keyed by schema field. Seeded from schema defaults once the
43
+ // schema arrives (load() below); reactive so v-model tracks every key.
44
+ const how = reactive({});
45
+
46
+ function seedDefaults() {
47
+ for (const f of b.policySchema.value) {
48
+ if (!(f.key in how)) how[f.key] = cloneVal(f.default);
49
+ }
50
+ }
51
+
52
+ onMounted(async () => {
53
+ await b.load();
54
+ seedDefaults();
55
+ });
56
+
57
+ const laneOptions = computed(() =>
58
+ b.lanes.value.map((l) => ({ value: l.threadId, label: l.name }))
59
+ );
60
+
61
+ function applyPreset(p) {
62
+ preset.value = p.name;
63
+ if (p.type) b.draft.type = p.type;
64
+ for (const f of b.policySchema.value) {
65
+ const v = p.values && f.key in p.values ? p.values[f.key] : f.default;
66
+ how[f.key] = cloneVal(v);
67
+ }
68
+ }
69
+
70
+ // Generic widget handlers — all clear the active preset on edit.
71
+ function setValue(key, val) {
72
+ how[key] = val;
73
+ preset.value = null;
74
+ }
75
+ function toggleBool(key) {
76
+ how[key] = !how[key];
77
+ preset.value = null;
78
+ }
79
+ function toggleChip(key, opt) {
80
+ const arr = how[key] || (how[key] = []);
81
+ const i = arr.indexOf(opt);
82
+ i >= 0 ? arr.splice(i, 1) : arr.push(opt);
83
+ preset.value = null;
84
+ }
85
+ function addPhase(key) {
86
+ const t = newPhase.value.trim();
87
+ if (!t) return;
88
+ (how[key] || (how[key] = [])).push([t, false]);
89
+ newPhase.value = "";
90
+ preset.value = null;
91
+ }
92
+ function removePhase(key, i) {
93
+ how[key].splice(i, 1);
94
+ preset.value = null;
95
+ }
96
+ function flipPhaseReview(key, i) {
97
+ how[key][i][1] = !how[key][i][1];
98
+ preset.value = null;
99
+ }
100
+
101
+ function formatValue(f, v) {
102
+ switch (f.widget) {
103
+ case "chips":
104
+ return v && v.length ? v.join(", ") : "none";
105
+ case "toggle":
106
+ // Schema may supply richer phrasing; fall back to plain on/off.
107
+ return v ? (f.onText || "on") : (f.offText || "off");
108
+ case "phases":
109
+ return v && v.length
110
+ ? v.map((p, i) => i + 1 + ". " + p[0] + (p[1] ? " (review)" : "")).join(" ")
111
+ : "single pass";
112
+ default:
113
+ return v == null || v === "" ? "—" : String(v);
114
+ }
115
+ }
116
+
117
+ const compiled = computed(() => {
118
+ const lines = [
119
+ ["TASK", b.draft.title || "(untitled)"],
120
+ ["TYPE", b.draft.type + " · " + (PRIORITIES[b.draft.priority] || "") + " priority"],
121
+ ];
122
+ for (const f of b.policySchema.value) {
123
+ lines.push([f.label.toUpperCase(), formatValue(f, how[f.key])]);
124
+ }
125
+ return lines;
126
+ });
127
+
128
+ async function next() {
129
+ if (step.value === 1) {
130
+ step.value = 2;
131
+ return;
132
+ }
133
+ const card = await b.submit();
134
+ if (card) router.navigate(`/card/${card.cardId}`);
135
+ }
136
+
137
+ return {
138
+ router, b, step, preset, previewOpen, newPhase, how, compiled, PRIORITIES, laneOptions,
139
+ applyPreset, setValue, toggleBool, toggleChip, addPhase, removePhase, flipPhaseReview, next,
140
+ };
141
+ },
142
+ template: `
143
+ <section class="builder">
144
+ <header class="builder__head">
145
+ <div class="builder__headrow">
146
+ <div>
147
+ <h1 class="builder__title">New card</h1>
148
+ <p class="builder__hint mono-label">STEP {{ step }} OF 2 · {{ step === 1 ? 'WHAT' : 'HOW' }}</p>
149
+ </div>
150
+ <div class="builder__dots">
151
+ <span class="builder__dotstep is-on"></span>
152
+ <span class="builder__dotstep" :class="{ 'is-on': step === 2 }"></span>
153
+ </div>
154
+ </div>
155
+ </header>
156
+
157
+ <StateBanner :error="b.error.value" />
158
+
159
+ <div class="presets">
160
+ <p class="mono-label presets__label">START FROM A PRESET</p>
161
+ <div class="presets__row">
162
+ <button
163
+ v-for="p in b.presets.value"
164
+ :key="p.name"
165
+ type="button"
166
+ class="presets__chip"
167
+ :class="{ 'is-active': preset === p.name }"
168
+ @click="applyPreset(p)"
169
+ >{{ p.name }}</button>
170
+ </div>
171
+ </div>
172
+
173
+ <form class="form" @submit.prevent="next">
174
+ <template v-if="step === 1">
175
+ <label class="field">
176
+ <span class="field__label">Title *</span>
177
+ <input class="input" v-model="b.draft.title" placeholder="e.g. Migrate auth to passkeys" />
178
+ </label>
179
+
180
+ <label class="field">
181
+ <span class="field__label">Goal</span>
182
+ <textarea class="input input--area" v-model="b.draft.description" rows="3"
183
+ placeholder="What should the worker achieve?"></textarea>
184
+ </label>
185
+
186
+ <label class="field">
187
+ <span class="field__label">Acceptance <span class="field__hint">— how we know it's done</span></span>
188
+ <textarea class="input input--area" v-model="b.draft.acceptance" rows="3"
189
+ placeholder="All tests pass, no breaking API changes…"></textarea>
190
+ </label>
191
+
192
+ <div class="field">
193
+ <span class="field__label">Priority</span>
194
+ <div class="seg" role="radiogroup" aria-label="Priority">
195
+ <button v-for="(p, i) in PRIORITIES" :key="p" type="button" class="seg__btn"
196
+ :class="b.draft.priority === i ? 'is-active-' + i : ''"
197
+ role="radio" :aria-checked="b.draft.priority === i"
198
+ @click="b.draft.priority = i">{{ p }}</button>
199
+ </div>
200
+ </div>
201
+
202
+ <div class="form__row">
203
+ <div class="field">
204
+ <span class="field__label">Type</span>
205
+ <Dropdown :model-value="b.draft.type" :options="b.types.value"
206
+ @update:model-value="v => b.draft.type = v" />
207
+ </div>
208
+ <div class="field">
209
+ <span class="field__label">Lane *</span>
210
+ <Dropdown :model-value="b.draft.laneThreadId" :options="laneOptions"
211
+ placeholder="Pick a lane" @update:model-value="v => b.draft.laneThreadId = v" />
212
+ </div>
213
+ </div>
214
+ </template>
215
+
216
+ <template v-else>
217
+ <template v-for="f in b.policySchema.value" :key="f.key">
218
+ <!-- select -->
219
+ <div v-if="f.widget === 'select'" class="field">
220
+ <span class="field__label">{{ f.label }}<span v-if="f.desc" class="field__hint"> — {{ f.desc }}</span></span>
221
+ <Dropdown :model-value="how[f.key]" :options="f.options"
222
+ @update:model-value="v => setValue(f.key, v)" />
223
+ </div>
224
+
225
+ <!-- segmented -->
226
+ <div v-else-if="f.widget === 'segmented'" class="field">
227
+ <span class="field__label">{{ f.label }}<span v-if="f.desc" class="field__hint"> — {{ f.desc }}</span></span>
228
+ <div class="seg" role="radiogroup" :aria-label="f.label">
229
+ <button v-for="o in f.options" :key="o" type="button" class="seg__btn"
230
+ :class="{ 'is-selected': how[f.key] === o }"
231
+ role="radio" :aria-checked="how[f.key] === o"
232
+ @click="setValue(f.key, o)">{{ o }}</button>
233
+ </div>
234
+ </div>
235
+
236
+ <!-- chips (multi-select) -->
237
+ <div v-else-if="f.widget === 'chips'" class="field">
238
+ <span class="field__label">{{ f.label }}<span v-if="f.desc" class="field__hint"> — {{ f.desc }}</span></span>
239
+ <div class="skillchips">
240
+ <button v-for="o in f.options" :key="o" type="button" class="skillchips__chip"
241
+ :class="{ 'is-on': (how[f.key] || []).includes(o) }"
242
+ @click="toggleChip(f.key, o)">{{ (how[f.key] || []).includes(o) ? '✓ ' : '' }}{{ o }}</button>
243
+ </div>
244
+ </div>
245
+
246
+ <!-- toggle -->
247
+ <div v-else-if="f.widget === 'toggle'" class="switchlist">
248
+ <button type="button" class="switchrow" @click="toggleBool(f.key)">
249
+ <span class="switchrow__text">
250
+ <span class="switchrow__label">{{ f.label }}</span>
251
+ <span v-if="f.desc" class="switchrow__desc">{{ f.desc }}</span>
252
+ </span>
253
+ <span class="switch" :class="{ 'is-on': how[f.key] }"><span class="switch__knob"></span></span>
254
+ </button>
255
+ </div>
256
+
257
+ <!-- phases (ordered list with per-phase review) -->
258
+ <div v-else-if="f.widget === 'phases'" class="field">
259
+ <span class="field__label">{{ f.label }}<span v-if="f.desc" class="field__hint"> — {{ f.desc }}</span></span>
260
+ <div class="phases">
261
+ <div v-for="(ph, i) in (how[f.key] || [])" :key="i" class="phases__row">
262
+ <span class="phases__num">{{ i + 1 }}</span>
263
+ <span class="phases__name">{{ ph[0] }}</span>
264
+ <button type="button" class="phases__review" :class="{ 'is-on': ph[1] }"
265
+ @click="flipPhaseReview(f.key, i)">{{ ph[1] ? 'REVIEW ✓' : 'REVIEW' }}</button>
266
+ <button type="button" class="phases__remove" aria-label="Remove phase" @click="removePhase(f.key, i)">×</button>
267
+ </div>
268
+ <div class="phases__add">
269
+ <input class="input" v-model="newPhase" placeholder="Add a phase…" @keydown.enter.prevent="addPhase(f.key)" />
270
+ <button type="button" class="btn phases__addbtn" @click="addPhase(f.key)">Add</button>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ </template>
275
+ </template>
276
+
277
+ <div v-if="step === 2" class="preview" :class="{ 'is-open': previewOpen }">
278
+ <button type="button" class="preview__toggle" @click="previewOpen = !previewOpen">
279
+ <span class="preview__title">
280
+ <span class="preview__label">COMPILED POLICY</span>
281
+ <span class="preview__meta">read-only · live</span>
282
+ </span>
283
+ <span class="preview__chevron">{{ previewOpen ? '▲' : '▼' }}</span>
284
+ </button>
285
+ <div v-if="previewOpen" class="preview__body">
286
+ <div v-for="([k, v]) in compiled" :key="k" class="preview__line">
287
+ <span class="preview__key">{{ k }}</span>
288
+ <span class="preview__val">{{ v }}</span>
289
+ </div>
290
+ </div>
291
+ </div>
292
+
293
+ <div class="actions builder__actions">
294
+ <button v-if="step === 2" class="btn" type="button" @click="step = 1">Back</button>
295
+ <button class="btn btn--primary builder__cta" type="submit" :disabled="b.submitting.value">
296
+ {{ step === 1 ? 'Next → How' : (b.submitting.value ? 'Creating…' : 'Create card') }}
297
+ </button>
298
+ <button class="btn btn--ghost" type="button" @click="router.navigate('/board')">Cancel</button>
299
+ </div>
300
+ </form>
301
+ </section>
302
+ `,
303
+ };
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Card detail screen — ✅ EDITABLE.
3
+ *
4
+ * Full concept pass (all phases): worker status strip (running/started/elapsed),
5
+ * Decision-needed block with option buttons, Blocked banner, assistant log panel
6
+ * (static sample lines — P3 wires real streaming), activity feed. Everything
7
+ * data-bound flows through useCard(); the log lines and decision options are
8
+ * static mock UI per the brief.
9
+ */
10
+
11
+ import { computed, inject, onMounted, ref } from "../vendor/vue.esm-browser.js";
12
+ import { useCard } from "../state/useCard.js";
13
+ import StatusChip from "../components/StatusChip.js";
14
+ import PriorityChip from "../components/PriorityChip.js";
15
+ import ProfileSummary from "../components/ProfileSummary.js";
16
+ import CommentList from "../components/CommentList.js";
17
+ import CommentComposer from "../components/CommentComposer.js";
18
+ import StateBanner from "../components/StateBanner.js";
19
+
20
+ /** Static sample assistant log (P3 replaces with real streaming). */
21
+ const SAMPLE_LOG = [
22
+ { kind: "cmd", text: "$ git checkout -b task/worker-run" },
23
+ { kind: "info", text: "Reading project files (14 relevant)" },
24
+ { kind: "info", text: "Drafting plan — 3 phases, tests after each" },
25
+ { kind: "ok", text: "✓ Phase 1 complete, 34 tests green (8.2s)" },
26
+ { kind: "cmd", text: "$ npm test --silent" },
27
+ { kind: "warn", text: "⚠ 2 flaky timing tests — retrying" },
28
+ { kind: "ok", text: "✓ Retry clean, proceeding to phase 2" },
29
+ { kind: "info", text: "Instrumenting spawn barrier for repro" },
30
+ ];
31
+
32
+ const DECISION_OPTIONS = [
33
+ "Go with the recommendation",
34
+ "Choose the cheaper alternative",
35
+ "Pause — let's discuss",
36
+ ];
37
+
38
+ export default {
39
+ name: "CardView",
40
+ components: {
41
+ StatusChip,
42
+ PriorityChip,
43
+ ProfileSummary,
44
+ CommentList,
45
+ CommentComposer,
46
+ StateBanner,
47
+ },
48
+ props: { cardId: { type: Number, required: true } },
49
+ setup(props) {
50
+ const router = inject("router");
51
+ const { card, comments, running, loading, error, load, actions } = useCard(props.cardId);
52
+ onMounted(load);
53
+
54
+ const logOpen = ref(true);
55
+ const answered = ref(false);
56
+ const ownAnswer = ref("");
57
+
58
+ const showLog = computed(() => {
59
+ const c = card.value;
60
+ return c && (c.status === "in_progress" || c.status === "decision_needed" || c.status === "blocked");
61
+ });
62
+ const logStatus = computed(() => {
63
+ const c = card.value;
64
+ if (!c) return "";
65
+ if (c.status === "in_progress") return running.value === false ? "IDLE" : "LIVE";
66
+ if (c.status === "blocked") return "HALTED";
67
+ return "PAUSED";
68
+ });
69
+ const decisionQuestion = computed(() => {
70
+ const list = comments.value.filter((c) => c.author === "worker");
71
+ return list.length
72
+ ? list[list.length - 1].content
73
+ : "The worker needs an operator decision to continue.";
74
+ });
75
+ const workerStrip = computed(() => {
76
+ const c = card.value;
77
+ if (!c || !c.startedAt) return null;
78
+ const start = new Date(c.startedAt);
79
+ const end = c.finishedAt ? new Date(c.finishedAt) : new Date();
80
+ const mins = Math.max(0, Math.round((end - start) / 60000));
81
+ const elapsed = mins >= 60 ? Math.floor(mins / 60) + "h " + (mins % 60) + "m" : mins + "m";
82
+ return {
83
+ state:
84
+ c.status === "in_progress"
85
+ ? running.value === false
86
+ ? "idle"
87
+ : "running"
88
+ : c.status === "done" && c.finishedAt
89
+ ? "finished"
90
+ : "paused",
91
+ started: start.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
92
+ elapsed,
93
+ thread: c.workerThreadId,
94
+ };
95
+ });
96
+
97
+ async function move(status) {
98
+ await actions.move(status);
99
+ }
100
+ async function stop() {
101
+ await actions.stop();
102
+ }
103
+ async function archive() {
104
+ await actions.archive();
105
+ router.navigate("/board");
106
+ }
107
+ async function remove() {
108
+ await actions.remove();
109
+ router.navigate("/board");
110
+ }
111
+ async function addComment(text) {
112
+ await actions.comment(text);
113
+ }
114
+ async function answer(label) {
115
+ answered.value = true;
116
+ await actions.comment("Decision: " + label);
117
+ }
118
+ async function answerOwn() {
119
+ const t = ownAnswer.value.trim();
120
+ if (!t) return;
121
+ answered.value = true;
122
+ await actions.comment("Decision: " + t);
123
+ }
124
+
125
+ return {
126
+ router, card, comments, running, loading, error, actions,
127
+ move, stop, archive, remove, addComment,
128
+ logOpen, showLog, logStatus, SAMPLE_LOG,
129
+ decisionQuestion, DECISION_OPTIONS, answer, answered, ownAnswer, answerOwn,
130
+ workerStrip,
131
+ };
132
+ },
133
+ template: `
134
+ <section class="card-detail">
135
+ <div class="card-detail__nav">
136
+ <button class="btn btn--back" type="button" aria-label="Back to board" @click="router.navigate('/board')">←</button>
137
+ <span class="card-detail__id">CARD DSP-{{ cardId }}</span>
138
+ </div>
139
+
140
+ <StateBanner :loading="loading" :error="error" @retry="actions.reload()" />
141
+
142
+ <template v-if="card">
143
+ <header class="card-detail__head">
144
+ <h1 class="card-detail__title">{{ card.title }}</h1>
145
+ <div class="chips">
146
+ <PriorityChip :priority="card.priority" />
147
+ <StatusChip :status="card.status" />
148
+ <span class="chip chip--muted">{{ card.type }}</span>
149
+ </div>
150
+ </header>
151
+
152
+ <div v-if="workerStrip" class="worker-strip" :class="'worker-strip--' + workerStrip.state">
153
+ <span class="worker-strip__state">
154
+ <span v-if="workerStrip.state === 'running'" class="dot dot--pulse"></span>
155
+ {{ workerStrip.state }}
156
+ </span>
157
+ <span class="worker-strip__item">started {{ workerStrip.started }}</span>
158
+ <span class="worker-strip__item">elapsed {{ workerStrip.elapsed }}</span>
159
+ <span v-if="workerStrip.thread" class="worker-strip__item">thread {{ workerStrip.thread }}</span>
160
+ </div>
161
+
162
+ <div v-if="card.status === 'decision_needed'" class="decision">
163
+ <div class="decision__head">
164
+ <span class="dot dot--pulse"></span>
165
+ <span class="decision__label">WORKER NEEDS A DECISION</span>
166
+ </div>
167
+ <p class="decision__question">{{ decisionQuestion }}</p>
168
+ <div class="decision__options">
169
+ <button
170
+ v-for="opt in DECISION_OPTIONS"
171
+ :key="opt"
172
+ type="button"
173
+ class="decision__option"
174
+ :disabled="answered"
175
+ @click="answer(opt)"
176
+ >{{ opt }}</button>
177
+ </div>
178
+ <div class="decision__own">
179
+ <input class="input" v-model="ownAnswer" placeholder="Your variant…" :disabled="answered"
180
+ @keydown.enter.prevent="answerOwn" />
181
+ <button type="button" class="decision__own-send" :disabled="answered || !ownAnswer.trim()"
182
+ @click="answerOwn">Send</button>
183
+ </div>
184
+ <p v-if="answered" class="decision__sent">Answer sent — worker resuming.</p>
185
+ </div>
186
+
187
+ <div v-if="card.blockedReason" class="notice notice--warn">
188
+ <span><strong>Waiting on:</strong> {{ card.blockedReason }}</span>
189
+ </div>
190
+
191
+ <div v-if="showLog" class="log">
192
+ <button type="button" class="log__toggle" @click="logOpen = !logOpen">
193
+ <span class="log__title">
194
+ <span class="mono-label">AGENT LOG</span>
195
+ <span class="log__status" :class="'log__status--' + logStatus.toLowerCase()">
196
+ <span v-if="logStatus === 'LIVE'" class="dot dot--pulse"></span>{{ logStatus }}
197
+ </span>
198
+ </span>
199
+ <span class="log__chevron">{{ logOpen ? '▲' : '▼' }}</span>
200
+ </button>
201
+ <div v-if="logOpen" class="log__body">
202
+ <p v-for="(ln, i) in SAMPLE_LOG" :key="i" class="log__line" :class="'log__line--' + ln.kind">{{ ln.text }}</p>
203
+ <p v-if="logStatus === 'LIVE'" class="log__line log__line--cursor">▋</p>
204
+ </div>
205
+ </div>
206
+
207
+ <section class="section">
208
+ <h2 class="section__label">Description</h2>
209
+ <p class="prose">{{ card.description || '—' }}</p>
210
+ </section>
211
+
212
+ <section class="section">
213
+ <h2 class="section__label">Acceptance</h2>
214
+ <p class="prose">{{ card.acceptance || '—' }}</p>
215
+ </section>
216
+
217
+ <section class="section">
218
+ <h2 class="section__label">Execution profile</h2>
219
+ <ProfileSummary :profile="card.profile" />
220
+ </section>
221
+
222
+ <section class="section section--plain">
223
+ <h2 class="section__label">Activity</h2>
224
+ <CommentList :comments="comments" />
225
+ <CommentComposer @submit="addComment" />
226
+ </section>
227
+
228
+ <div class="card-detail__actions">
229
+ <button class="btn" type="button" v-if="card.status !== 'backlog'" @click="move('backlog')">
230
+ Move to Backlog
231
+ </button>
232
+ <button class="btn" type="button" v-if="card.status !== 'done'" @click="move('done')">
233
+ Mark Done
234
+ </button>
235
+ <button class="btn btn--danger" type="button" v-if="card.status === 'in_progress'" @click="stop">
236
+ Stop
237
+ </button>
238
+ <span class="card-detail__actions-spacer"></span>
239
+ <button class="btn" type="button" @click="archive">Archive</button>
240
+ <button class="btn btn--danger" type="button" @click="remove">Delete</button>
241
+ </div>
242
+ </template>
243
+ </section>
244
+ `,
245
+ };