@tenonhq/dovetail-dashboard 0.0.29 → 0.0.31

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/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog — @tenonhq/dovetail-dashboard
2
+
3
+ ## Unreleased — Phase E (claude-plans v2 bidirectional surfaces)
4
+
5
+ - **New dependency** `@tenonhq/dovetail-claude-plans` (workspace) — write routes call directly into the package's storage layer so the state machine, conflict rule, and dispatch-token lifecycle are enforced in exactly one place.
6
+ - **Three new POST routes** under `/api/claude-plans/:slug/`:
7
+ - `answers` → `recordAnswer()`. Forces `answered_by: "dashboard"` by default.
8
+ - `stage` → `setStage()`. Forces `source: "dashboard"` so the conflict rule (design doc §4) treats operator moves as authoritative.
9
+ - `dispatch` → `dispatchStage()`. Pass-through; dashboard sends dry-run first, then confirm+token.
10
+ - **Typed error envelope** — every error response carries `{ error: <code>, name: <ClassName>, message }`. HTTP status reflects the failure mode: 400 (validation / no-token), 404 (plan-not-found), 409 (illegal transition / conflict), 410 (stale token), 424 (missing agent), 500 (spawn / internal).
11
+ - **Stage Map strip** on the plan detail page — 10 stage pills with current-stage highlight, click-to-set_stage, per-stage Dispatch (▶) button, and a ⚠ marker for stages whose agent hasn't shipped yet (test-first, test-reality).
12
+ - **Dispatch dialog** — opens on Dispatch click, POSTs dry-run first, renders the resolved command + cwd, then the operator confirms to fire a live dispatch (which consumes the cached `set_stage` token).
13
+ - **Questions & Answers tab** — lists `plan.questions`, lets the operator answer each one (free-form input + clickable suggested options); SSE plan upsert re-renders the tab automatically.
14
+ - **Per-plan token cache** — the most recent `set_stage` response is held client-side per plan so the Dispatch button has a token to consume. Cache is cleared on plan switch.
15
+ - **Rate limited** — all three new routes share `claudePlansLimiter` (60 req / 15 min / IP), same as the existing destructive delete route.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-dashboard",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "description": "Update Set Dashboard for Dovetail",
5
5
  "main": "server.js",
6
6
  "scripts": {
@@ -9,6 +9,7 @@
9
9
  "postpublish": "npm run version:bump"
10
10
  },
11
11
  "dependencies": {
12
+ "@tenonhq/dovetail-claude-plans": "*",
12
13
  "axios": "^1.5.1",
13
14
  "axios-cookiejar-support": "^4.0.7",
14
15
  "chokidar": "^3.6.0",
@@ -1311,3 +1311,218 @@
1311
1311
  .cp-topic-chip--active .cp-topic-chip-count {
1312
1312
  background: color-mix(in srgb, var(--accent-fg) 20%, transparent);
1313
1313
  }
1314
+
1315
+ /* Merged-PR filter toggle + per-row PR state dots */
1316
+ .cp-rail-filters {
1317
+ padding: 8px 0 4px;
1318
+ }
1319
+ .cp-pr-toggle {
1320
+ display: inline-flex;
1321
+ align-items: center;
1322
+ gap: 6px;
1323
+ padding: 4px 12px;
1324
+ background: var(--surface);
1325
+ color: var(--fg);
1326
+ border: 1px solid var(--border);
1327
+ border-radius: 999px;
1328
+ font-family: inherit;
1329
+ font-size: 12px;
1330
+ font-weight: 500;
1331
+ cursor: pointer;
1332
+ transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
1333
+ }
1334
+ .cp-pr-toggle:hover {
1335
+ background: color-mix(in srgb, var(--accent) 10%, var(--surface));
1336
+ border-color: var(--accent);
1337
+ }
1338
+ .cp-pr-toggle--active {
1339
+ background: var(--accent);
1340
+ color: var(--accent-fg);
1341
+ border-color: var(--accent);
1342
+ }
1343
+
1344
+ .cp-pr-dot {
1345
+ display: inline-block;
1346
+ width: 8px;
1347
+ height: 8px;
1348
+ border-radius: 50%;
1349
+ flex: 0 0 auto;
1350
+ margin: 0 4px;
1351
+ background: var(--fg-muted);
1352
+ }
1353
+ .cp-pr-dot--merged { background: var(--success-700); }
1354
+ .cp-pr-dot--open { background: var(--warning-700); }
1355
+ .cp-pr-dot--closed { background: var(--danger); }
1356
+ .cp-pr-dot--unknown { background: var(--fg-muted); }
1357
+
1358
+ /* ===========================================================================
1359
+ * v2 — Stage Map, Questions tab, Dispatch dialog (Phase E)
1360
+ * Lightweight additions on top of the existing token system.
1361
+ * ========================================================================= */
1362
+
1363
+ .cp-stage-map {
1364
+ display: flex;
1365
+ flex-wrap: wrap;
1366
+ gap: 6px;
1367
+ padding: 12px 0;
1368
+ margin-bottom: 12px;
1369
+ border-bottom: 1px solid var(--border, #e5e7eb);
1370
+ }
1371
+ .cp-stage {
1372
+ position: relative;
1373
+ display: inline-flex;
1374
+ align-items: center;
1375
+ gap: 6px;
1376
+ padding: 6px 10px;
1377
+ border: 1px solid var(--border, #d1d5db);
1378
+ border-radius: 999px;
1379
+ background: var(--bg-elevated, #fff);
1380
+ font-size: 12px;
1381
+ font-weight: 500;
1382
+ cursor: pointer;
1383
+ user-select: none;
1384
+ }
1385
+ .cp-stage:hover { background: var(--bg-hover, #f3f4f6); }
1386
+ .cp-stage--current {
1387
+ background: var(--accent-bg, #2563eb);
1388
+ color: var(--accent-fg, #fff);
1389
+ border-color: var(--accent-bg, #2563eb);
1390
+ }
1391
+ .cp-stage--illegal {
1392
+ opacity: 0.4;
1393
+ cursor: not-allowed;
1394
+ }
1395
+ .cp-stage--missing-agent::after {
1396
+ content: "⚠";
1397
+ font-size: 11px;
1398
+ margin-left: 4px;
1399
+ }
1400
+ .cp-stage-dispatch {
1401
+ margin-left: 4px;
1402
+ padding: 0 6px;
1403
+ font-size: 11px;
1404
+ background: transparent;
1405
+ border: 1px solid currentColor;
1406
+ border-radius: 4px;
1407
+ cursor: pointer;
1408
+ color: inherit;
1409
+ }
1410
+ .cp-stage-dispatch:hover { background: color-mix(in srgb, currentColor 12%, transparent); }
1411
+
1412
+ .cp-question {
1413
+ padding: 12px;
1414
+ border: 1px solid var(--border, #e5e7eb);
1415
+ border-radius: 8px;
1416
+ margin-bottom: 12px;
1417
+ background: var(--bg-elevated, #fff);
1418
+ }
1419
+ .cp-question--answered { opacity: 0.78; }
1420
+ .cp-question-header {
1421
+ display: flex;
1422
+ gap: 8px;
1423
+ align-items: center;
1424
+ margin-bottom: 6px;
1425
+ font-size: 11px;
1426
+ text-transform: uppercase;
1427
+ letter-spacing: 0.06em;
1428
+ color: var(--text-muted, #6b7280);
1429
+ }
1430
+ .cp-question-stage {
1431
+ padding: 2px 6px;
1432
+ border-radius: 4px;
1433
+ background: color-mix(in srgb, currentColor 10%, transparent);
1434
+ }
1435
+ .cp-question-q { font-size: 14px; margin-bottom: 10px; font-weight: 500; }
1436
+ .cp-question-options {
1437
+ display: flex; flex-wrap: wrap; gap: 6px;
1438
+ margin-bottom: 10px;
1439
+ }
1440
+ .cp-question-option {
1441
+ padding: 4px 8px;
1442
+ border: 1px solid var(--border, #d1d5db);
1443
+ border-radius: 4px;
1444
+ background: var(--bg-elevated, #fff);
1445
+ font-size: 12px;
1446
+ cursor: pointer;
1447
+ }
1448
+ .cp-question-option:hover { background: var(--bg-hover, #f3f4f6); }
1449
+ .cp-question-answer-form { display: flex; gap: 6px; }
1450
+ .cp-question-answer-input {
1451
+ flex: 1;
1452
+ padding: 6px 8px;
1453
+ border: 1px solid var(--border, #d1d5db);
1454
+ border-radius: 4px;
1455
+ font: inherit;
1456
+ }
1457
+ .cp-question-answer-submit {
1458
+ padding: 6px 12px;
1459
+ border: 1px solid var(--accent-bg, #2563eb);
1460
+ background: var(--accent-bg, #2563eb);
1461
+ color: var(--accent-fg, #fff);
1462
+ border-radius: 4px;
1463
+ cursor: pointer;
1464
+ }
1465
+ .cp-question-recorded {
1466
+ font-size: 13px;
1467
+ padding: 8px 10px;
1468
+ background: color-mix(in srgb, var(--accent-bg, #2563eb) 10%, transparent);
1469
+ border-radius: 4px;
1470
+ }
1471
+ .cp-question-recorded-meta {
1472
+ font-size: 11px;
1473
+ color: var(--text-muted, #6b7280);
1474
+ margin-top: 4px;
1475
+ }
1476
+
1477
+ .cp-dispatch-dialog {
1478
+ position: fixed;
1479
+ inset: 0;
1480
+ background: rgba(0,0,0,0.4);
1481
+ display: flex;
1482
+ align-items: center;
1483
+ justify-content: center;
1484
+ z-index: 1000;
1485
+ }
1486
+ .cp-dispatch-dialog-inner {
1487
+ background: var(--bg-elevated, #fff);
1488
+ border-radius: 8px;
1489
+ padding: 20px;
1490
+ max-width: 640px;
1491
+ width: 90%;
1492
+ font-size: 14px;
1493
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
1494
+ }
1495
+ .cp-dispatch-dialog h3 { margin-top: 0; }
1496
+ .cp-dispatch-dialog pre {
1497
+ background: var(--bg, #f9fafb);
1498
+ border-radius: 4px;
1499
+ padding: 10px;
1500
+ overflow-x: auto;
1501
+ font-size: 12px;
1502
+ }
1503
+ .cp-dispatch-dialog-actions {
1504
+ display: flex;
1505
+ gap: 8px;
1506
+ margin-top: 14px;
1507
+ justify-content: flex-end;
1508
+ }
1509
+ .cp-dispatch-dialog-actions button {
1510
+ padding: 6px 14px;
1511
+ border: 1px solid var(--border, #d1d5db);
1512
+ border-radius: 4px;
1513
+ background: var(--bg-elevated, #fff);
1514
+ cursor: pointer;
1515
+ }
1516
+ .cp-dispatch-dialog-actions button.cp-dispatch-confirm {
1517
+ background: var(--accent-bg, #2563eb);
1518
+ color: var(--accent-fg, #fff);
1519
+ border-color: var(--accent-bg, #2563eb);
1520
+ }
1521
+ .cp-dispatch-error {
1522
+ color: #b91c1c;
1523
+ background: #fee2e2;
1524
+ padding: 8px 10px;
1525
+ border-radius: 4px;
1526
+ margin-top: 10px;
1527
+ font-size: 13px;
1528
+ }
@@ -84,6 +84,15 @@
84
84
  hidden
85
85
  >×</button>
86
86
  </div>
87
+ <div class="cp-rail-filters">
88
+ <button
89
+ type="button"
90
+ class="cp-pr-toggle"
91
+ id="cp-merged-toggle"
92
+ aria-pressed="false"
93
+ title="Show only plans whose linked PR is merged"
94
+ >Merged PRs only</button>
95
+ </div>
87
96
  <div class="cp-rail-empty" id="cp-rail-empty">
88
97
  Waiting for plans. Run <code>push_plan</code> from a Claude Code
89
98
  session to populate this list.
@@ -104,6 +113,9 @@
104
113
  <span class="cp-status-pill" id="cp-detail-status"></span>
105
114
  <span class="cp-stamp" id="cp-detail-stamp"></span>
106
115
  </header>
116
+ <!-- v2 Stage Map: 10-stage pipeline strip; clicking a stage triggers
117
+ POST /stage (subject to state-machine + conflict rules). -->
118
+ <div class="cp-stage-map" id="cp-stage-map" hidden></div>
107
119
  <nav class="cp-tabs">
108
120
  <button class="cp-tab active" data-tab="plan">Plan</button>
109
121
  <button class="cp-tab" data-tab="prompts">
@@ -112,10 +124,14 @@
112
124
  <button class="cp-tab" data-tab="artifacts">
113
125
  Artifacts <span class="cp-tab-count" id="cp-artifact-count">0</span>
114
126
  </button>
127
+ <button class="cp-tab" data-tab="questions">
128
+ Q&amp;A <span class="cp-tab-count" id="cp-question-count">0</span>
129
+ </button>
115
130
  </nav>
116
131
  <div class="cp-tab-panel" id="cp-tab-plan"></div>
117
132
  <div class="cp-tab-panel" id="cp-tab-prompts" hidden></div>
118
133
  <div class="cp-tab-panel" id="cp-tab-artifacts" hidden></div>
134
+ <div class="cp-tab-panel" id="cp-tab-questions" hidden></div>
119
135
  </article>
120
136
  </section>
121
137
  </main>