<feed xmlns='http://www.w3.org/2005/Atom'>
<title>claudomator.git/web/test/stories-board.test.mjs, branch fix/dockersandbox-gitpush-host-security</title>
<subtitle>claudomator — task automation server
</subtitle>
<id>https://git.terst.org/claudomator.git/atom?h=fix%2Fdockersandbox-gitpush-host-security</id>
<link rel='self' href='https://git.terst.org/claudomator.git/atom?h=fix%2Fdockersandbox-gitpush-host-security'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/'/>
<updated>2026-07-04T05:31:42+00:00</updated>
<entry>
<title>feat(web): add Stories tab -- Kanban board, epic swimlanes, story-detail DAG (Phase 9a)</title>
<updated>2026-07-04T05:31:42+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-04T05:31:42+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=d105eca610b0e737c3313e4978d6a917b4f55d10'/>
<id>urn:sha1:d105eca610b0e737c3313e4978d6a917b4f55d10</id>
<content type='text'>
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
  -&gt; BACKLOG -&gt; PRIORITIZED -&gt; IN_PROGRESS -&gt; VALIDATING/REVIEW_READY -&gt; 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-&gt;4-Evaluator-&gt;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-&gt;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 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
</feed>
