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.
- package/Install-Sensorium.ps1 +61 -0
- package/dist/board/api/api.js +61 -0
- package/dist/board/api/client.js +96 -0
- package/dist/board/components/BottomNav.js +32 -0
- package/dist/board/components/Card.js +44 -0
- package/dist/board/components/CommentComposer.js +26 -0
- package/dist/board/components/CommentList.js +39 -0
- package/dist/board/components/Dropdown.js +66 -0
- package/dist/board/components/Lane.js +40 -0
- package/dist/board/components/PriorityChip.js +17 -0
- package/dist/board/components/ProfileSummary.js +39 -0
- package/dist/board/components/StateBanner.js +22 -0
- package/dist/board/components/StatusChip.js +21 -0
- package/dist/board/components/TokenEntry.js +42 -0
- package/dist/board/config.js +56 -0
- package/dist/board/index.html +18 -0
- package/dist/board/main.js +37 -0
- package/dist/board/mock/api.mock.js +193 -0
- package/dist/board/mock/fixtures.js +164 -0
- package/dist/board/schema/policy.js +192 -0
- package/dist/board/state/auth.js +55 -0
- package/dist/board/state/useBoard.js +149 -0
- package/dist/board/state/useBuilder.js +98 -0
- package/dist/board/state/useCard.js +67 -0
- package/dist/board/styles/board.css +1760 -0
- package/dist/board/styles/tokens.css +71 -0
- package/dist/board/vendor/vue.esm-browser.js +9 -0
- package/dist/board/views/App.js +65 -0
- package/dist/board/views/BoardView.js +84 -0
- package/dist/board/views/BuilderView.js +303 -0
- package/dist/board/views/CardView.js +245 -0
- package/dist/board/views/SettingsView.js +145 -0
- package/dist/config/agent-settings.d.ts +2 -0
- package/dist/config/agent-settings.d.ts.map +1 -1
- package/dist/config/agent-settings.js +23 -0
- package/dist/config/agent-settings.js.map +1 -1
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +12 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/misc-settings.d.ts +14 -0
- package/dist/config/misc-settings.d.ts.map +1 -1
- package/dist/config/misc-settings.js +53 -0
- package/dist/config/misc-settings.js.map +1 -1
- package/dist/dashboard/board-static.d.ts +23 -0
- package/dist/dashboard/board-static.d.ts.map +1 -0
- package/dist/dashboard/board-static.js +131 -0
- package/dist/dashboard/board-static.js.map +1 -0
- package/dist/dashboard/routes/board.d.ts +73 -0
- package/dist/dashboard/routes/board.d.ts.map +1 -0
- package/dist/dashboard/routes/board.js +623 -0
- package/dist/dashboard/routes/board.js.map +1 -0
- package/dist/dashboard/routes/claude.d.ts.map +1 -1
- package/dist/dashboard/routes/claude.js +10 -1
- package/dist/dashboard/routes/claude.js.map +1 -1
- package/dist/dashboard/routes/settings.d.ts +2 -0
- package/dist/dashboard/routes/settings.d.ts.map +1 -1
- package/dist/dashboard/routes/settings.js +24 -1
- package/dist/dashboard/routes/settings.js.map +1 -1
- package/dist/dashboard/routes/threads-read.d.ts +7 -3
- package/dist/dashboard/routes/threads-read.d.ts.map +1 -1
- package/dist/dashboard/routes/threads-read.js +19 -4
- package/dist/dashboard/routes/threads-read.js.map +1 -1
- package/dist/dashboard/routes/types.d.ts +6 -0
- package/dist/dashboard/routes/types.d.ts.map +1 -1
- package/dist/dashboard/routes/types.js.map +1 -1
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +42 -1
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/dashboard/spa.html +12 -12
- package/dist/data/board/board-schema.d.ts +22 -0
- package/dist/data/board/board-schema.d.ts.map +1 -0
- package/dist/data/board/board-schema.js +103 -0
- package/dist/data/board/board-schema.js.map +1 -0
- package/dist/data/board/board-types.d.ts +117 -0
- package/dist/data/board/board-types.d.ts.map +1 -0
- package/dist/data/board/board-types.js +9 -0
- package/dist/data/board/board-types.js.map +1 -0
- package/dist/data/board/card.repository.d.ts +64 -0
- package/dist/data/board/card.repository.d.ts.map +1 -0
- package/dist/data/board/card.repository.js +272 -0
- package/dist/data/board/card.repository.js.map +1 -0
- package/dist/data/board/comment.repository.d.ts +12 -0
- package/dist/data/board/comment.repository.d.ts.map +1 -0
- package/dist/data/board/comment.repository.js +31 -0
- package/dist/data/board/comment.repository.js.map +1 -0
- package/dist/data/board/lane.repository.d.ts +18 -0
- package/dist/data/board/lane.repository.d.ts.map +1 -0
- package/dist/data/board/lane.repository.js +44 -0
- package/dist/data/board/lane.repository.js.map +1 -0
- package/dist/data/board/profile-defaults.d.ts +19 -0
- package/dist/data/board/profile-defaults.d.ts.map +1 -0
- package/dist/data/board/profile-defaults.js +118 -0
- package/dist/data/board/profile-defaults.js.map +1 -0
- package/dist/data/memory/migration-runner.d.ts.map +1 -1
- package/dist/data/memory/migration-runner.js +4 -0
- package/dist/data/memory/migration-runner.js.map +1 -1
- package/dist/data/memory/narrative-store.d.ts +14 -0
- package/dist/data/memory/narrative-store.d.ts.map +1 -1
- package/dist/data/memory/narrative-store.js +36 -0
- package/dist/data/memory/narrative-store.js.map +1 -1
- package/dist/data/memory/narrative.d.ts.map +1 -1
- package/dist/data/memory/narrative.js +13 -1
- package/dist/data/memory/narrative.js.map +1 -1
- package/dist/data/memory/schema.d.ts.map +1 -1
- package/dist/data/memory/schema.js +4 -0
- package/dist/data/memory/schema.js.map +1 -1
- package/dist/index.js +84 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/openai/speech.d.ts.map +1 -1
- package/dist/integrations/openai/speech.js +52 -19
- package/dist/integrations/openai/speech.js.map +1 -1
- package/dist/lib/http-retry.d.ts +91 -0
- package/dist/lib/http-retry.d.ts.map +1 -0
- package/dist/lib/http-retry.js +177 -0
- package/dist/lib/http-retry.js.map +1 -0
- package/dist/lib/usage-metering/pricing.js +1 -1
- package/dist/lib/usage-metering/pricing.js.map +1 -1
- package/dist/lib/utils.d.ts +10 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +22 -0
- package/dist/lib/utils.js.map +1 -1
- package/dist/server/http-server.d.ts +3 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +6 -2
- package/dist/server/http-server.js.map +1 -1
- package/dist/services/board/board-signals.d.ts +25 -0
- package/dist/services/board/board-signals.d.ts.map +1 -0
- package/dist/services/board/board-signals.js +83 -0
- package/dist/services/board/board-signals.js.map +1 -0
- package/dist/services/board/card-briefing.d.ts +17 -0
- package/dist/services/board/card-briefing.d.ts.map +1 -0
- package/dist/services/board/card-briefing.js +86 -0
- package/dist/services/board/card-briefing.js.map +1 -0
- package/dist/services/board/orchestrator.service.d.ts +55 -0
- package/dist/services/board/orchestrator.service.d.ts.map +1 -0
- package/dist/services/board/orchestrator.service.js +198 -0
- package/dist/services/board/orchestrator.service.js.map +1 -0
- package/dist/services/board/release-worker.d.ts +32 -0
- package/dist/services/board/release-worker.d.ts.map +1 -0
- package/dist/services/board/release-worker.js +49 -0
- package/dist/services/board/release-worker.js.map +1 -0
- package/dist/services/board/report-capture.d.ts +48 -0
- package/dist/services/board/report-capture.d.ts.map +1 -0
- package/dist/services/board/report-capture.js +172 -0
- package/dist/services/board/report-capture.js.map +1 -0
- package/dist/services/board/worker-spawn.d.ts +30 -0
- package/dist/services/board/worker-spawn.d.ts.map +1 -0
- package/dist/services/board/worker-spawn.js +90 -0
- package/dist/services/board/worker-spawn.js.map +1 -0
- package/dist/services/broker-client.d.ts +30 -0
- package/dist/services/broker-client.d.ts.map +1 -1
- package/dist/services/broker-client.js +23 -0
- package/dist/services/broker-client.js.map +1 -1
- package/dist/services/codex-spawn.service.d.ts +2 -2
- package/dist/services/codex-spawn.service.d.ts.map +1 -1
- package/dist/services/codex-spawn.service.js +54 -54
- package/dist/services/codex-spawn.service.js.map +1 -1
- package/dist/services/http-retry.d.ts +1 -90
- package/dist/services/http-retry.d.ts.map +1 -1
- package/dist/services/http-retry.js +1 -176
- package/dist/services/http-retry.js.map +1 -1
- package/dist/services/mcp-config.service.d.ts.map +1 -1
- package/dist/services/mcp-config.service.js +5 -1
- package/dist/services/mcp-config.service.js.map +1 -1
- package/dist/services/passive-notice.service.d.ts +39 -0
- package/dist/services/passive-notice.service.d.ts.map +1 -0
- package/dist/services/passive-notice.service.js +274 -0
- package/dist/services/passive-notice.service.js.map +1 -0
- package/dist/services/spawn-common.d.ts.map +1 -1
- package/dist/services/spawn-common.js +14 -1
- package/dist/services/spawn-common.js.map +1 -1
- package/dist/services/telegram.d.ts +7 -5
- package/dist/services/telegram.d.ts.map +1 -1
- package/dist/services/telegram.js +18 -13
- package/dist/services/telegram.js.map +1 -1
- package/dist/services/usage/usage-inbox-notifier.d.ts +50 -0
- package/dist/services/usage/usage-inbox-notifier.d.ts.map +1 -0
- package/dist/services/usage/usage-inbox-notifier.js +118 -0
- package/dist/services/usage/usage-inbox-notifier.js.map +1 -0
- package/dist/services/usage/usage-poller.service.d.ts +41 -0
- package/dist/services/usage/usage-poller.service.d.ts.map +1 -0
- package/dist/services/usage/usage-poller.service.js +66 -0
- package/dist/services/usage/usage-poller.service.js.map +1 -0
- package/dist/services/usage/usage-snapshot-provider.d.ts +29 -0
- package/dist/services/usage/usage-snapshot-provider.d.ts.map +1 -0
- package/dist/services/usage/usage-snapshot-provider.js +59 -0
- package/dist/services/usage/usage-snapshot-provider.js.map +1 -0
- package/dist/services/usage/usage-types.d.ts +23 -0
- package/dist/services/usage/usage-types.d.ts.map +1 -0
- package/dist/services/usage/usage-types.js +14 -0
- package/dist/services/usage/usage-types.js.map +1 -0
- package/dist/services/worker-cleanup.service.d.ts +6 -1
- package/dist/services/worker-cleanup.service.d.ts.map +1 -1
- package/dist/services/worker-cleanup.service.js +123 -10
- package/dist/services/worker-cleanup.service.js.map +1 -1
- package/dist/tools/delegate-tool.d.ts.map +1 -1
- package/dist/tools/delegate-tool.js +34 -2
- package/dist/tools/delegate-tool.js.map +1 -1
- package/dist/tools/utility-tools.d.ts.map +1 -1
- package/dist/tools/utility-tools.js +7 -3
- package/dist/tools/utility-tools.js.map +1 -1
- package/dist/tools/wait/consolidation-strategy.d.ts +42 -0
- package/dist/tools/wait/consolidation-strategy.d.ts.map +1 -0
- package/dist/tools/wait/consolidation-strategy.js +50 -0
- package/dist/tools/wait/consolidation-strategy.js.map +1 -0
- package/dist/tools/wait/drive-handler.d.ts +5 -7
- package/dist/tools/wait/drive-handler.d.ts.map +1 -1
- package/dist/tools/wait/drive-handler.js +30 -48
- package/dist/tools/wait/drive-handler.js.map +1 -1
- package/dist/tools/wait/message-delivery.d.ts.map +1 -1
- package/dist/tools/wait/message-delivery.js +13 -0
- package/dist/tools/wait/message-delivery.js.map +1 -1
- package/package.json +2 -2
- package/templates/atomic-build-spec.default.md +57 -0
- package/templates/buildless-vue-preview.default.md +47 -0
- 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
|
+
};
|