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
package/Install-Sensorium.ps1
CHANGED
|
@@ -41,11 +41,25 @@ $ConfigFile = Join-Path $DataDir "install.config.json"
|
|
|
41
41
|
$StartupDir = [Environment]::GetFolderPath("Startup")
|
|
42
42
|
$StartupLauncher = Join-Path $StartupDir "SensoriumSupervisor.cmd"
|
|
43
43
|
|
|
44
|
+
# Isolated MCP runtime: a PRIVATE npm cache + a repo-independent cwd for the MCP,
|
|
45
|
+
# both living under $DataDir (never a dev checkout). This severs the shared-cache /
|
|
46
|
+
# shared-cwd coupling that lets a worker's `npm ci`/build in a dev repo interfere
|
|
47
|
+
# with (and kill) the live MCP — WITHOUT pinning the version, so `npx ...@latest`
|
|
48
|
+
# still auto-updates the MCP on each restart. npx also stages its packages in
|
|
49
|
+
# <cache>/_npx, so a private npm cache isolates npx resolution too.
|
|
50
|
+
$McpRuntimeRoot = Join-Path $DataDir "mcp-runtime"
|
|
51
|
+
$McpRuntimeCache = Join-Path $McpRuntimeRoot "npm-cache"
|
|
52
|
+
|
|
44
53
|
# ── Config File ──────────────────────────────────────────────────────────────
|
|
45
54
|
$Defaults = @{
|
|
46
55
|
SecureVaultProfile = "SENSORIUM"
|
|
47
56
|
UpdateMode = "production"
|
|
48
57
|
MCPStartCommand = "npx -y sensorium-mcp@latest"
|
|
58
|
+
# Opt-in runtime isolation. When "true", the MCP keeps its `npx ...@latest`
|
|
59
|
+
# start command (so auto-update still works) but is given its OWN npm cache
|
|
60
|
+
# ($McpRuntimeCache) and a repo-independent cwd ($McpRuntimeRoot), decoupled
|
|
61
|
+
# from any dev checkout's shared npm cache / cwd. "false" = legacy behaviour.
|
|
62
|
+
IsolateMcpRuntime = "false"
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
function Load-Config {
|
|
@@ -78,6 +92,7 @@ $Config = Load-Config
|
|
|
78
92
|
$SecureVaultProfile = $Config["SecureVaultProfile"]
|
|
79
93
|
$UpdateMode = $Config["UpdateMode"]
|
|
80
94
|
$EffectiveMCPStartCommand = $Config["MCPStartCommand"]
|
|
95
|
+
$IsolateMcpRuntime = $Config["IsolateMcpRuntime"] -eq "true"
|
|
81
96
|
|
|
82
97
|
# Resolve the supervisor release channel from the same tag the node server
|
|
83
98
|
# follows: the "@<tag>" in MCPStartCommand. Only @refactor uses the branch
|
|
@@ -306,10 +321,44 @@ function Test-SecureVaultAvailable {
|
|
|
306
321
|
catch { return $false }
|
|
307
322
|
}
|
|
308
323
|
|
|
324
|
+
function Initialize-IsolatedMcpRuntime {
|
|
325
|
+
<#
|
|
326
|
+
Prepares the PRIVATE MCP runtime area: a dedicated npm cache and a
|
|
327
|
+
repo-independent cwd, both under $McpRuntimeRoot. It does NOT install or pin
|
|
328
|
+
any package — the MCP keeps its `npx ...@latest` start command so auto-update
|
|
329
|
+
still works; isolation comes purely from the private cache + cwd handed to the
|
|
330
|
+
supervisor (and thus to npx) via Start-Supervisor. Idempotent; only creates dirs.
|
|
331
|
+
#>
|
|
332
|
+
Write-Host "Isolated MCP runtime enabled — preparing private cache + cwd..." -ForegroundColor Cyan
|
|
333
|
+
Write-Host " Runtime cwd : $McpRuntimeRoot"
|
|
334
|
+
Write-Host " npm cache : $McpRuntimeCache (npx stages under $McpRuntimeCache\_npx)"
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
New-Item -ItemType Directory -Path $McpRuntimeRoot -Force | Out-Null
|
|
338
|
+
New-Item -ItemType Directory -Path $McpRuntimeCache -Force | Out-Null
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
Write-Host "[WARN] Could not create isolated runtime dirs: $($_.Exception.Message) — keeping legacy behaviour." -ForegroundColor Yellow
|
|
342
|
+
return $false
|
|
343
|
+
}
|
|
344
|
+
return $true
|
|
345
|
+
}
|
|
346
|
+
|
|
309
347
|
function Start-Supervisor {
|
|
310
348
|
$env:SUPERVISOR_UPDATE_MODE = $UpdateMode
|
|
311
349
|
$env:MCP_START_COMMAND = $EffectiveMCPStartCommand
|
|
312
350
|
|
|
351
|
+
# In isolated mode, hand the supervisor a private npm cache and a repo-independent
|
|
352
|
+
# cwd. The supervisor inherits its whole environment and passes it to the MCP, so
|
|
353
|
+
# NPM_CONFIG_CACHE reaches the `npx ...@latest` child automatically (isolating both
|
|
354
|
+
# the npm cache and npx's <cache>\_npx staging); MCP_WORKING_DIR is consumed by the
|
|
355
|
+
# supervisor to set the MCP's spawn cwd. The `@latest` command is unchanged, so
|
|
356
|
+
# auto-update keeps working — only WHERE npx resolves/caches changes.
|
|
357
|
+
if ($IsolateMcpRuntime) {
|
|
358
|
+
$env:NPM_CONFIG_CACHE = $McpRuntimeCache
|
|
359
|
+
$env:MCP_WORKING_DIR = $McpRuntimeRoot
|
|
360
|
+
}
|
|
361
|
+
|
|
313
362
|
$useSecureVault = (-not [string]::IsNullOrWhiteSpace($SecureVaultProfile)) -and (Test-SecureVaultAvailable)
|
|
314
363
|
|
|
315
364
|
if ($useSecureVault) {
|
|
@@ -414,6 +463,7 @@ else {
|
|
|
414
463
|
Write-Host "SecureVault profile: $(if ([string]::IsNullOrWhiteSpace($SecureVaultProfile)) { '<disabled>' } else { $SecureVaultProfile })"
|
|
415
464
|
Write-Host "Watcher mode: $UpdateMode"
|
|
416
465
|
Write-Host "MCP start command: $EffectiveMCPStartCommand"
|
|
466
|
+
Write-Host "MCP runtime isolation: $(if ($IsolateMcpRuntime) { 'ENABLED' } else { 'disabled (legacy npx)' })"
|
|
417
467
|
Write-Host "Binary : $Binary"
|
|
418
468
|
Write-Host ""
|
|
419
469
|
|
|
@@ -428,6 +478,17 @@ New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
|
|
|
428
478
|
# Step 3 — fetch release metadata and download only when needed
|
|
429
479
|
Sync-ReleaseBinary
|
|
430
480
|
|
|
481
|
+
# Step 3b — prepare the isolated MCP runtime area (opt-in): a private npm cache +
|
|
482
|
+
# repo-independent cwd. The `npx ...@latest` command is unchanged (auto-update
|
|
483
|
+
# preserved). On any failure this disables the isolation env in Start-Supervisor,
|
|
484
|
+
# so the install degrades safely to legacy behaviour.
|
|
485
|
+
if ($IsolateMcpRuntime) {
|
|
486
|
+
if (-not (Initialize-IsolatedMcpRuntime)) {
|
|
487
|
+
$IsolateMcpRuntime = $false
|
|
488
|
+
Write-Host "[WARN] Isolation disabled for this run — using the shared npm cache / inherited cwd." -ForegroundColor Yellow
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
431
492
|
# Step 4 — install launcher and start
|
|
432
493
|
Install-StartupLauncher
|
|
433
494
|
Start-Supervisor
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-adapter switch. ❌ FROZEN SEAM — the ONLY seam is data.
|
|
3
|
+
*
|
|
4
|
+
* MODE decides the implementation ONCE, lazily:
|
|
5
|
+
* - preview → mock/api.mock.js is DYNAMICALLY imported (so not a single mock
|
|
6
|
+
* byte is loaded in production).
|
|
7
|
+
* - production → api/client.js (real fetch).
|
|
8
|
+
*
|
|
9
|
+
* Every method returns a Promise and forwards to the resolved adapter, so callers
|
|
10
|
+
* (state/ composables) never know or care which one is live.
|
|
11
|
+
*
|
|
12
|
+
* @typedef {import('./contract.js').BoardApi} BoardApi
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { MODE } from "../config.js";
|
|
16
|
+
|
|
17
|
+
let implPromise = null;
|
|
18
|
+
|
|
19
|
+
/** @returns {Promise<BoardApi>} */
|
|
20
|
+
function load() {
|
|
21
|
+
if (!implPromise) {
|
|
22
|
+
implPromise =
|
|
23
|
+
MODE === "preview"
|
|
24
|
+
? import("../mock/api.mock.js").then((m) => m.mockApi)
|
|
25
|
+
: import("./client.js").then((m) => m.client);
|
|
26
|
+
}
|
|
27
|
+
return implPromise;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const METHODS = [
|
|
31
|
+
"listCards",
|
|
32
|
+
"getCard",
|
|
33
|
+
"createCard",
|
|
34
|
+
"updateCard",
|
|
35
|
+
"deleteCard",
|
|
36
|
+
"stopCard",
|
|
37
|
+
"addComment",
|
|
38
|
+
"listLanes",
|
|
39
|
+
"getProfiles",
|
|
40
|
+
"threadRunning",
|
|
41
|
+
// Card-builder "How" schema + presets. Preview is served by mock/api.mock.js
|
|
42
|
+
// (from the editable schema/policy.js). TODO(P1): api/client.js must implement
|
|
43
|
+
// getPolicySchema()/getPresets() against the real board API before production
|
|
44
|
+
// uses the schema-driven builder; until then production calls reject and
|
|
45
|
+
// useBuilder() degrades to a schema-less builder (no crash).
|
|
46
|
+
"getPolicySchema",
|
|
47
|
+
"getPresets",
|
|
48
|
+
// Lane management (Settings). Preview served by the mock; TODO(P1): implement
|
|
49
|
+
// in api/client.js before production exposes lane CRUD.
|
|
50
|
+
"createLane",
|
|
51
|
+
"updateLane",
|
|
52
|
+
"deleteLane",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/** @type {BoardApi} */
|
|
56
|
+
const api = /** @type {any} */ ({});
|
|
57
|
+
for (const name of METHODS) {
|
|
58
|
+
api[name] = (...args) => load().then((impl) => impl[name](...args));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { api };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production data adapter. ❌ FROZEN SEAM.
|
|
3
|
+
*
|
|
4
|
+
* Real `fetch` to the board API. Root-relative `/api/...` paths (never a host or
|
|
5
|
+
* port) so the app works unchanged behind the Cloudflare Tunnel. Bearer token is
|
|
6
|
+
* injected from state/auth.js. This is the ONLY module that talks to the network.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import('./contract.js').BoardApi} BoardApi
|
|
9
|
+
* @typedef {import('./contract.js').BoardCard} BoardCard
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { API_BASE } from "../config.js";
|
|
13
|
+
import { getToken } from "../state/auth.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} method
|
|
17
|
+
* @param {string} path
|
|
18
|
+
* @param {unknown} [body]
|
|
19
|
+
*/
|
|
20
|
+
async function req(method, path, body) {
|
|
21
|
+
const headers = {};
|
|
22
|
+
const token = getToken();
|
|
23
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
24
|
+
if (body !== undefined) headers["Content-Type"] = "application/json";
|
|
25
|
+
|
|
26
|
+
const res = await fetch(`${API_BASE}${path}`, {
|
|
27
|
+
method,
|
|
28
|
+
headers,
|
|
29
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
let data = null;
|
|
34
|
+
if (text) {
|
|
35
|
+
try {
|
|
36
|
+
data = JSON.parse(text);
|
|
37
|
+
} catch {
|
|
38
|
+
data = { error: text };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const msg = (data && data.error) || `${res.status} ${res.statusText}`;
|
|
43
|
+
const err = new Error(msg);
|
|
44
|
+
err.status = res.status;
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @type {BoardApi} */
|
|
51
|
+
export const client = {
|
|
52
|
+
listCards({ includeArchived = false, lane } = {}) {
|
|
53
|
+
const p = new URLSearchParams();
|
|
54
|
+
if (includeArchived) p.set("includeArchived", "1");
|
|
55
|
+
if (lane != null) p.set("lane", String(lane));
|
|
56
|
+
const qs = p.toString();
|
|
57
|
+
return req("GET", `/api/board/cards${qs ? `?${qs}` : ""}`).then((d) => d.cards);
|
|
58
|
+
},
|
|
59
|
+
getCard(id) {
|
|
60
|
+
return req("GET", `/api/board/cards/${id}`);
|
|
61
|
+
},
|
|
62
|
+
createCard(card) {
|
|
63
|
+
return req("POST", "/api/board/cards", card).then((d) => d.card);
|
|
64
|
+
},
|
|
65
|
+
updateCard(id, patch) {
|
|
66
|
+
return req("PATCH", `/api/board/cards/${id}`, patch).then((d) => d.card);
|
|
67
|
+
},
|
|
68
|
+
deleteCard(id) {
|
|
69
|
+
return req("DELETE", `/api/board/cards/${id}`).then(() => undefined);
|
|
70
|
+
},
|
|
71
|
+
stopCard(id) {
|
|
72
|
+
return req("POST", `/api/board/cards/${id}/stop`, {}).then((d) => d.card);
|
|
73
|
+
},
|
|
74
|
+
addComment(id, content) {
|
|
75
|
+
return req("POST", `/api/board/cards/${id}/comments`, { content }).then((d) => d.comment);
|
|
76
|
+
},
|
|
77
|
+
listLanes() {
|
|
78
|
+
return req("GET", "/api/board/lanes").then((d) => d.lanes);
|
|
79
|
+
},
|
|
80
|
+
getProfiles() {
|
|
81
|
+
return req("GET", "/api/board/profiles").then((d) => d.profiles);
|
|
82
|
+
},
|
|
83
|
+
threadRunning(threadId) {
|
|
84
|
+
return req("GET", `/api/threads/${threadId}/running`).then((d) => !!d.running);
|
|
85
|
+
},
|
|
86
|
+
// TODO(P1): serve the card-builder "How" schema/presets from the real board
|
|
87
|
+
// API (e.g. GET /api/board/policy). Until then these reject explicitly so the
|
|
88
|
+
// production failure is legible; useBuilder() swallows the rejection and falls
|
|
89
|
+
// back to a schema-less builder (no crash).
|
|
90
|
+
getPolicySchema() {
|
|
91
|
+
return Promise.reject(new Error("getPolicySchema not implemented — P1"));
|
|
92
|
+
},
|
|
93
|
+
getPresets() {
|
|
94
|
+
return Promise.reject(new Error("getPresets not implemented — P1"));
|
|
95
|
+
},
|
|
96
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bottom navigation — ✅ EDITABLE. Pure: `active` in, `navigate` out.
|
|
3
|
+
* Thumb-reach primary nav (board / new / settings).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
name: "BottomNav",
|
|
8
|
+
props: { active: { type: String, default: "board" } },
|
|
9
|
+
emits: ["navigate"],
|
|
10
|
+
setup(props, { emit }) {
|
|
11
|
+
const items = [
|
|
12
|
+
{ name: "board", to: "/board", label: "Board", icon: "▦" },
|
|
13
|
+
{ name: "settings", to: "/settings", label: "Settings", icon: "⚙" },
|
|
14
|
+
];
|
|
15
|
+
return { items, emit };
|
|
16
|
+
},
|
|
17
|
+
template: `
|
|
18
|
+
<nav class="bottom-nav">
|
|
19
|
+
<button
|
|
20
|
+
v-for="it in items"
|
|
21
|
+
:key="it.name"
|
|
22
|
+
type="button"
|
|
23
|
+
class="bottom-nav__item"
|
|
24
|
+
:class="{ 'is-active': active === it.name }"
|
|
25
|
+
@click="emit('navigate', it.to)"
|
|
26
|
+
>
|
|
27
|
+
<span class="bottom-nav__icon" aria-hidden="true">{{ it.icon }}</span>
|
|
28
|
+
<span class="bottom-nav__label">{{ it.label }}</span>
|
|
29
|
+
</button>
|
|
30
|
+
</nav>
|
|
31
|
+
`,
|
|
32
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card tile — ✅ EDITABLE. Pure: `card` + `laneName` in, `open` out.
|
|
3
|
+
* Dispatch design: floating dark card — title, then chip row with priority,
|
|
4
|
+
* type, live worker pulse / blocked / archived.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import StatusChip from "./StatusChip.js";
|
|
8
|
+
import PriorityChip from "./PriorityChip.js";
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
name: "Card",
|
|
12
|
+
components: { StatusChip, PriorityChip },
|
|
13
|
+
props: {
|
|
14
|
+
card: { type: Object, required: true },
|
|
15
|
+
laneName: { type: Function, default: (id) => `Thread ${id}` },
|
|
16
|
+
},
|
|
17
|
+
emits: ["open"],
|
|
18
|
+
setup(props, { emit }) {
|
|
19
|
+
function elapsed() {
|
|
20
|
+
if (!props.card.startedAt) return "";
|
|
21
|
+
const mins = Math.max(0, Math.round((Date.now() - new Date(props.card.startedAt)) / 60000));
|
|
22
|
+
return mins >= 60 ? Math.floor(mins / 60) + "h " + (mins % 60) + "m" : mins + "m";
|
|
23
|
+
}
|
|
24
|
+
return { emit, elapsed };
|
|
25
|
+
},
|
|
26
|
+
template: `
|
|
27
|
+
<article class="card" role="listitem" tabindex="0"
|
|
28
|
+
@click="emit('open', card)"
|
|
29
|
+
@keydown.enter="emit('open', card)">
|
|
30
|
+
<h3 class="card__title">{{ card.title }}</h3>
|
|
31
|
+
<p v-if="card.description" class="card__desc">{{ card.description }}</p>
|
|
32
|
+
<div class="card__foot">
|
|
33
|
+
<PriorityChip :priority="card.priority" />
|
|
34
|
+
<span class="chip chip--muted">{{ card.type }}</span>
|
|
35
|
+
<span v-if="card.status === 'in_progress'" class="chip chip--live">
|
|
36
|
+
<span class="dot dot--pulse"></span>working<template v-if="card.startedAt"> · {{ elapsed() }}</template>
|
|
37
|
+
</span>
|
|
38
|
+
<span v-else-if="card.blockedReason" class="chip chip--warn">blocked</span>
|
|
39
|
+
<span v-if="card.archivedAt" class="chip chip--muted">archived</span>
|
|
40
|
+
<span class="card__lane">{{ laneName(card.laneThreadId) }}</span>
|
|
41
|
+
</div>
|
|
42
|
+
</article>
|
|
43
|
+
`,
|
|
44
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment composer — ✅ EDITABLE. Pure: emits `submit(text)`; clears on send.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ref } from "../vendor/vue.esm-browser.js";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: "CommentComposer",
|
|
9
|
+
emits: ["submit"],
|
|
10
|
+
setup(props, { emit }) {
|
|
11
|
+
const text = ref("");
|
|
12
|
+
function send() {
|
|
13
|
+
const t = text.value.trim();
|
|
14
|
+
if (!t) return;
|
|
15
|
+
emit("submit", t);
|
|
16
|
+
text.value = "";
|
|
17
|
+
}
|
|
18
|
+
return { text, send };
|
|
19
|
+
},
|
|
20
|
+
template: `
|
|
21
|
+
<form class="composer" @submit.prevent="send">
|
|
22
|
+
<textarea class="input input--area" v-model="text" rows="2" placeholder="Add a comment…"></textarea>
|
|
23
|
+
<button class="btn btn--primary" type="submit" :disabled="!text.trim()">Comment</button>
|
|
24
|
+
</form>
|
|
25
|
+
`,
|
|
26
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment history — ✅ EDITABLE. Pure: `comments` in.
|
|
3
|
+
* Dispatch design: activity feed — avatar tile + floating bubble per entry.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const AVATARS = { worker: "⚙", operator: "Y", board: "◆" };
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: "CommentList",
|
|
10
|
+
props: { comments: { type: Array, default: () => [] } },
|
|
11
|
+
setup() {
|
|
12
|
+
function when(iso) {
|
|
13
|
+
try {
|
|
14
|
+
return new Date(iso).toLocaleString();
|
|
15
|
+
} catch {
|
|
16
|
+
return iso;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function avatar(author) {
|
|
20
|
+
return AVATARS[author] || "•";
|
|
21
|
+
}
|
|
22
|
+
return { when, avatar };
|
|
23
|
+
},
|
|
24
|
+
template: `
|
|
25
|
+
<ul class="comments">
|
|
26
|
+
<li v-for="c in comments" :key="c.id" class="comment" :class="'comment--' + c.author">
|
|
27
|
+
<span class="comment__avatar" aria-hidden="true">{{ avatar(c.author) }}</span>
|
|
28
|
+
<div class="comment__bubble">
|
|
29
|
+
<div class="comment__meta">
|
|
30
|
+
<span class="comment__author">{{ c.author }}</span>
|
|
31
|
+
<time class="comment__time">{{ when(c.createdAt) }}</time>
|
|
32
|
+
</div>
|
|
33
|
+
<p class="comment__body">{{ c.content }}</p>
|
|
34
|
+
</div>
|
|
35
|
+
</li>
|
|
36
|
+
<li v-if="!comments.length" class="comment comment--empty">No comments yet.</li>
|
|
37
|
+
</ul>
|
|
38
|
+
`,
|
|
39
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom dropdown — ✅ EDITABLE. Replaces native <select> (default styles are
|
|
3
|
+
* not overridable cross-platform). Pure: modelValue in, update:modelValue out.
|
|
4
|
+
* Options: array of strings or {value,label}.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computed, onMounted, onUnmounted, ref } from "../vendor/vue.esm-browser.js";
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: "Dropdown",
|
|
11
|
+
props: {
|
|
12
|
+
modelValue: { default: null },
|
|
13
|
+
options: { type: Array, default: () => [] },
|
|
14
|
+
placeholder: { type: String, default: "Select…" },
|
|
15
|
+
},
|
|
16
|
+
emits: ["update:modelValue"],
|
|
17
|
+
setup(props, { emit }) {
|
|
18
|
+
const open = ref(false);
|
|
19
|
+
const rootEl = ref(null);
|
|
20
|
+
const norm = computed(() =>
|
|
21
|
+
props.options.map((o) => (o && typeof o === "object" ? o : { value: o, label: String(o) }))
|
|
22
|
+
);
|
|
23
|
+
const current = computed(() => norm.value.find((o) => o.value === props.modelValue));
|
|
24
|
+
function pick(o) {
|
|
25
|
+
emit("update:modelValue", o.value);
|
|
26
|
+
open.value = false;
|
|
27
|
+
}
|
|
28
|
+
function onDoc(e) {
|
|
29
|
+
if (rootEl.value && !rootEl.value.contains(e.target)) open.value = false;
|
|
30
|
+
}
|
|
31
|
+
function onKey(e) {
|
|
32
|
+
if (e.key === "Escape") open.value = false;
|
|
33
|
+
}
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
document.addEventListener("click", onDoc, true);
|
|
36
|
+
document.addEventListener("keydown", onKey);
|
|
37
|
+
});
|
|
38
|
+
onUnmounted(() => {
|
|
39
|
+
document.removeEventListener("click", onDoc, true);
|
|
40
|
+
document.removeEventListener("keydown", onKey);
|
|
41
|
+
});
|
|
42
|
+
return { open, rootEl, norm, current, pick };
|
|
43
|
+
},
|
|
44
|
+
template: `
|
|
45
|
+
<div class="dropdown" ref="rootEl">
|
|
46
|
+
<button type="button" class="dropdown__btn" :class="{ 'is-open': open }"
|
|
47
|
+
aria-haspopup="listbox" :aria-expanded="open" @click="open = !open">
|
|
48
|
+
<span class="dropdown__value" :class="{ 'is-placeholder': !current }">
|
|
49
|
+
{{ current ? current.label : placeholder }}
|
|
50
|
+
</span>
|
|
51
|
+
<span class="dropdown__chevron" aria-hidden="true">▾</span>
|
|
52
|
+
</button>
|
|
53
|
+
<ul v-if="open" class="dropdown__menu" role="listbox">
|
|
54
|
+
<li v-for="o in norm" :key="String(o.value)">
|
|
55
|
+
<button type="button" class="dropdown__opt" role="option"
|
|
56
|
+
:aria-selected="o.value === modelValue"
|
|
57
|
+
:class="{ 'is-selected': o.value === modelValue }"
|
|
58
|
+
@click="pick(o)">
|
|
59
|
+
<span class="dropdown__optlabel">{{ o.label }}</span>
|
|
60
|
+
<span v-if="o.value === modelValue" class="dropdown__check">✓</span>
|
|
61
|
+
</button>
|
|
62
|
+
</li>
|
|
63
|
+
</ul>
|
|
64
|
+
</div>
|
|
65
|
+
`,
|
|
66
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kanban column — ✅ EDITABLE. Pure: title/status/cards/laneName in, `open` out.
|
|
3
|
+
* Dispatch design: transparent column, colored status dot + name + count pill.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import Card from "./Card.js";
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: "Lane",
|
|
10
|
+
components: { Card },
|
|
11
|
+
props: {
|
|
12
|
+
title: { type: String, required: true },
|
|
13
|
+
status: { type: String, required: true },
|
|
14
|
+
cards: { type: Array, default: () => [] },
|
|
15
|
+
laneName: { type: Function, default: (id) => `Thread ${id}` },
|
|
16
|
+
},
|
|
17
|
+
emits: ["open"],
|
|
18
|
+
setup(props, { emit }) {
|
|
19
|
+
return { emit };
|
|
20
|
+
},
|
|
21
|
+
template: `
|
|
22
|
+
<section class="lane" :class="'lane--' + status" role="listitem">
|
|
23
|
+
<header class="lane__head">
|
|
24
|
+
<span class="lane__dot"></span>
|
|
25
|
+
<h2 class="lane__title">{{ title }}</h2>
|
|
26
|
+
<span class="lane__count">{{ cards.length }}</span>
|
|
27
|
+
</header>
|
|
28
|
+
<div class="lane__body">
|
|
29
|
+
<Card
|
|
30
|
+
v-for="c in cards"
|
|
31
|
+
:key="c.cardId"
|
|
32
|
+
:card="c"
|
|
33
|
+
:lane-name="laneName"
|
|
34
|
+
@open="emit('open', $event)"
|
|
35
|
+
/>
|
|
36
|
+
<p v-if="!cards.length" class="lane__empty">Nothing here</p>
|
|
37
|
+
</div>
|
|
38
|
+
</section>
|
|
39
|
+
`,
|
|
40
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Priority chip — ✅ EDITABLE. Pure: `priority` (int) in.
|
|
3
|
+
* Higher int = higher urgency (matches dispatch ORDER BY priority DESC).
|
|
4
|
+
* Dispatch design: tinted pill — Low / Medium / High / Urgent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const LABELS = ["Low", "Medium", "High", "Urgent"];
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: "PriorityChip",
|
|
11
|
+
props: { priority: { type: Number, default: 0 } },
|
|
12
|
+
setup(props) {
|
|
13
|
+
const level = () => Math.max(0, Math.min(3, props.priority));
|
|
14
|
+
return { level, LABELS };
|
|
15
|
+
},
|
|
16
|
+
template: `<span class="chip chip--prio" :class="'chip--prio-' + level()">{{ LABELS[level()] }}</span>`,
|
|
17
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution-profile summary — ✅ EDITABLE. Pure: `profile` in.
|
|
3
|
+
* Renders the set flags as readable chips; nothing set → a muted note.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { computed } from "../vendor/vue.esm-browser.js";
|
|
7
|
+
|
|
8
|
+
const FLAG_LABELS = {
|
|
9
|
+
createBranch: "branch",
|
|
10
|
+
runTests: "tests",
|
|
11
|
+
build: "build",
|
|
12
|
+
openPr: "PR",
|
|
13
|
+
push: "push",
|
|
14
|
+
expertReview: "expert review",
|
|
15
|
+
operatorReview: "operator review",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
name: "ProfileSummary",
|
|
20
|
+
props: { profile: { type: Object, default: () => ({}) } },
|
|
21
|
+
setup(props) {
|
|
22
|
+
const items = computed(() => {
|
|
23
|
+
const p = props.profile || {};
|
|
24
|
+
const out = [];
|
|
25
|
+
if (p.agentType) out.push(p.agentType);
|
|
26
|
+
for (const [k, label] of Object.entries(FLAG_LABELS)) if (p[k]) out.push(label);
|
|
27
|
+
if (p.ciCheck && p.ciCheck !== "none") out.push("ci:" + p.ciCheck);
|
|
28
|
+
if (Array.isArray(p.skills) && p.skills.length) out.push(p.skills.length + " skill(s)");
|
|
29
|
+
return out;
|
|
30
|
+
});
|
|
31
|
+
return { items };
|
|
32
|
+
},
|
|
33
|
+
template: `
|
|
34
|
+
<div class="profile">
|
|
35
|
+
<span v-for="(it, i) in items" :key="i" class="chip chip--live">{{ it }}</span>
|
|
36
|
+
<span v-if="!items.length" class="profile__empty">Default profile (no flags set).</span>
|
|
37
|
+
</div>
|
|
38
|
+
`,
|
|
39
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading / error banner — ✅ EDITABLE. Pure: `loading`/`error` in, `retry` out.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: "StateBanner",
|
|
7
|
+
props: {
|
|
8
|
+
loading: { type: Boolean, default: false },
|
|
9
|
+
error: { type: [String, null], default: null },
|
|
10
|
+
},
|
|
11
|
+
emits: ["retry"],
|
|
12
|
+
setup(props, { emit }) {
|
|
13
|
+
return { emit };
|
|
14
|
+
},
|
|
15
|
+
template: `
|
|
16
|
+
<div v-if="error" class="notice notice--error">
|
|
17
|
+
<span>{{ error }}</span>
|
|
18
|
+
<button class="btn btn--ghost btn--sm" type="button" @click="emit('retry')">Retry</button>
|
|
19
|
+
</div>
|
|
20
|
+
<div v-else-if="loading" class="notice notice--info">Loading…</div>
|
|
21
|
+
`,
|
|
22
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status chip — ✅ EDITABLE. Pure: `status` in.
|
|
3
|
+
* Dispatch design: tinted pill in the status accent color.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const LABELS = {
|
|
7
|
+
backlog: "Backlog",
|
|
8
|
+
in_progress: "In progress",
|
|
9
|
+
decision_needed: "Decision needed",
|
|
10
|
+
blocked: "Blocked",
|
|
11
|
+
done: "Done",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
name: "StatusChip",
|
|
16
|
+
props: { status: { type: String, required: true } },
|
|
17
|
+
setup(props) {
|
|
18
|
+
return { LABELS };
|
|
19
|
+
},
|
|
20
|
+
template: `<span class="chip chip--status" :class="'chip--status-' + status">{{ LABELS[status] || status }}</span>`,
|
|
21
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-entry gate — ✅ EDITABLE. Shown in production when no token is stored.
|
|
3
|
+
* Uses useAuth() to persist; no fetch here.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ref } from "../vendor/vue.esm-browser.js";
|
|
7
|
+
import { useAuth } from "../state/auth.js";
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: "TokenEntry",
|
|
11
|
+
setup() {
|
|
12
|
+
const auth = useAuth();
|
|
13
|
+
const value = ref("");
|
|
14
|
+
function save() {
|
|
15
|
+
if (value.value.trim()) auth.setToken(value.value.trim());
|
|
16
|
+
}
|
|
17
|
+
return { value, save };
|
|
18
|
+
},
|
|
19
|
+
template: `
|
|
20
|
+
<section class="token-gate">
|
|
21
|
+
<div class="token-gate__card">
|
|
22
|
+
<div class="token-gate__logo"><span class="app-bar__dot"></span></div>
|
|
23
|
+
<h1 class="token-gate__title">Dispatch</h1>
|
|
24
|
+
<p class="token-gate__sub">Enter the board API token to continue.</p>
|
|
25
|
+
<form @submit.prevent="save">
|
|
26
|
+
<input
|
|
27
|
+
class="input"
|
|
28
|
+
type="password"
|
|
29
|
+
v-model="value"
|
|
30
|
+
placeholder="Bearer token"
|
|
31
|
+
autocomplete="off"
|
|
32
|
+
autofocus
|
|
33
|
+
/>
|
|
34
|
+
<button class="btn btn--primary token-gate__btn" type="submit" :disabled="!value.trim()">
|
|
35
|
+
Continue
|
|
36
|
+
</button>
|
|
37
|
+
</form>
|
|
38
|
+
<p class="token-gate__hint">The token is stored only in this browser.</p>
|
|
39
|
+
</div>
|
|
40
|
+
</section>
|
|
41
|
+
`,
|
|
42
|
+
};
|