summaryrefslogtreecommitdiff
path: root/web/index.html
diff options
context:
space:
mode:
authorClaude Sonnet 5 <noreply@anthropic.com>2026-07-04 05:31:42 +0000
committerClaude Sonnet 5 <noreply@anthropic.com>2026-07-04 05:31:42 +0000
commitd105eca610b0e737c3313e4978d6a917b4f55d10 (patch)
tree2ad75a33abd45ebb8225dcbee01a2d0a8d7a21ed /web/index.html
parent8cb291ad7cdb317ff80947278ee055b1a4925b41 (diff)
feat(web): add Stories tab -- Kanban board, epic swimlanes, story-detail DAG (Phase 9a)
First UI work on top of the harness's 8 backend phases -- pure web/* frontend, no backend changes, keeping the existing embedded vanilla-JS convention (no build step, no framework). - Kanban board: columns spanning the full story lifecycle (DISCOVERY/FRAMING -> BACKLOG -> PRIORITIZED -> IN_PROGRESS -> VALIDATING/REVIEW_READY -> DONE), cards showing priority/acceptance-criteria count/eval-verdict progress. Drag-and-drop reorders priority within a column (persisted via a partial PUT /api/stories/{id}); cross-column drag is disabled outright (a dragover handler only preventDefault()s when the dragged card's own status already maps to that column) rather than allowed-then-snapped-back, since the latter would visibly misplace a card for several seconds until the next poll -- reads as a bug, not a feature. - Epic swimlanes: stories grouped client-side by epic_id (incl. an "Unassigned" lane) with a DONE/total progress meter per epic, built from data the board already fetches (avoids N+1 GET /api/epics/{id}/stories calls for a result that's a strict subset of the already-loaded list). - Story detail modal: metadata/spec/acceptance criteria, a BFS-layered plain- SVG DAG of the story's task-tree (rows = BFS depth from root_task_id over both parent_task_id and depends_on edges, solid vs. dashed strokes distinguishing the two edge kinds), an event timeline rendering eval_verdict/arbitration_decided/human_accepted/retro_captured/ epic_proposed with human-readable text, and an Accept button wired to POST /api/stories/{id}/accept when status is REVIEW_READY. - DAG node coloring reuses the app's existing --state-* CSS custom properties (the dataviz skill's "reserved status palette" pattern, already used elsewhere in the app) rather than introducing a new palette. Running the skill's own palette validator against that pre-existing 9-color set fails its CVD/lightness checks (a pre-existing condition, out of scope to fix here since those colors are used elsewhere in the app) -- mitigated per the skill's documented remedy for a failing palette: every node always carries a text label (name + role + state) and a full legend, so identity never depends on color alone; edge kind is encoded by stroke style, not color. Verified with a real running server and a real headless-browser session (Chromium via playwright-core, offline-cached), not just code review: seeded real data (an epic, 11 stories across every lifecycle status, a 6-node Builder->4-Evaluator->Arbitration task tree, 4 story events) via the live REST API plus two throwaway fixture scripts for the two write paths the API doesn't expose (task depends_on, direct event writes), then confirmed via screenshots/DOM assertions: correct column placement for every status, working swimlane grouping/progress meter, correct 6-node/8-edge DAG render, working event timeline, a real Accept-button API call flipping REVIEW_READY->DONE, real drag-and-drop priority persistence (re-fetched to confirm), and a genuine cross-column-drag no-op. Caught and fixed a real bug during this process: DAG node-label truncation now measures getComputedTextLength() and binary-searches the longest fit, instead of a fixed character-count budget that collapsed every "Eval: evaluator_X" label to "Eval:..." (only one space in the string). go build ./... clean; node --test web/test/*.mjs -- 275 tests pass (19 new). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
Diffstat (limited to 'web/index.html')
-rw-r--r--web/index.html21
1 files changed, 21 insertions, 0 deletions
diff --git a/web/index.html b/web/index.html
index 5aa7b44..523edd9 100644
--- a/web/index.html
+++ b/web/index.html
@@ -44,6 +44,7 @@
<button class="tab" data-tab="interrupted" title="Interrupted">⚠️<span class="tab-count-badge" hidden></span></button>
<button class="tab" data-tab="ready" title="Ready">✅<span class="tab-count-badge" hidden></span></button>
<button class="tab" data-tab="running" title="Running">▶️<span class="tab-count-badge" hidden></span></button>
+ <button class="tab" data-tab="stories" title="Stories">📋</button>
<button class="tab" data-tab="drops" title="Drops">📁</button>
<button class="tab" data-tab="stats" title="Stats">📊</button>
<button class="tab" data-tab="settings" title="Settings">⚙️</button>
@@ -65,6 +66,17 @@
<div class="running-current"></div>
<div class="running-history"></div>
</div>
+ <div data-panel="stories" hidden>
+ <div class="stories-toolbar">
+ <div class="view-toggle" role="tablist" aria-label="Stories view">
+ <button class="view-toggle-btn active" data-view="board" type="button">Board</button>
+ <button class="view-toggle-btn" data-view="epics" type="button">Epics</button>
+ </div>
+ </div>
+ <div class="stories-view-container">
+ <div class="task-empty">Loading…</div>
+ </div>
+ </div>
<div data-panel="drops" hidden>
<div class="drops-panel"></div>
</div>
@@ -96,6 +108,15 @@
</div>
</dialog>
+ <!-- Story detail modal -->
+ <dialog id="story-modal">
+ <div class="story-modal-header">
+ <h2 id="story-modal-title">Story</h2>
+ <button id="btn-close-story-modal" class="btn-close-panel" aria-label="Close">&#x2715;</button>
+ </div>
+ <div id="story-modal-body" class="story-modal-body"></div>
+ </dialog>
+
<script type="module" src="app.js"></script>
</body>
</html>