# three-musketeers build, adom-gantt table view + DC demo granular plan, 2026-08-07

**Task:** Add a table view (second lens) to adom-gantt with owner/deadline/status columns, per-owner filter, and inline edit, pushed to main; rewrite the DC demo plan data as granular subtasks with owners and deadlines, DoW audience woven in, hiring excluded.
**Stage:** live instance on port 8901 serving `/home/adom/project/dc-demo-gantt/dc-demo.json`; source pushed to wiki main `adom/adom-gantt`.
**Rounds:** 4 (initial build, two fix rounds, one micro-round).
**Models:** Builder Opus, Adversary Fable (fresh context per round), Verifier Sonnet.
**Final version:** 0.8.4, commit `40015e5e8842488a133b448be66118bac93b1e43`. Interim commits: 0.8.0 initial, 0.8.1 `87635bc`, 0.8.2 `80b3e49`, 0.8.3 `f937ea5`.

## What was built

**Table view (0.8.0 to 0.8.4).** A `Gantt | Table` header toggle over the same in-memory items. Columns: Task, Phase, Owner, Start, Deadline, Status, Notes. Sorting on every column (deadline ascending default, blanks last both directions). Filters: owner dropdown (All / Unassigned / distinct owners, persisted), status dropdown, free text, Clear; the legend's phase and Milestone toggles drive both lenses. Inline edit of Owner, Start, Deadline, Status, Notes with Enter/Esc/click-away semantics; edits flow through the existing scheduleSave pipeline into the sidecar and changelog, reflect in the Gantt immediately, survive reload, and are one undo step. Sidecar bumped to v3 (additive owner/status/desc on tasks and milestones; v1/v2 still load). `owner` is a first-class item field carried by /export/json and /export/csv and read back by CSV import. New changelog actions: owner, status, desc, milestone.rename, import.

**Hardening delivered along the way:** stable `#row<n>` keys for id-less rows with an all-or-nothing order application (client and server, textually parallel ladders); crash-proof changelog rendering and a server that survives arbitrary malformed /log posts (validated shapes, 8MB body cap, headersSent guard, uncaughtException backstop); stored-XSS closed at the tooltip and eleven other innerHTML sinks; script-breakout closed in the HTML snapshot export; duplicate-milestone-name resolution; server-owned monotonic rev counter; CSV formula-injection guard and lossless phase-name round-trip; import validation that rejects dateless rows by name; client render that drops undateable rows with a dismissible warning instead of dying; SVG export resilient to the same class; brand fonts actually loading for the first time (adom.inc font URLs were CORS-blocked since forever; now Google Fonts + cdn.fontshare.com with verified ACAO).

**DC demo data (`/home/adom/project/dc-demo-gantt/dc-demo.json`).** 73 items: 6 lanes (Hardware, Test, Logistics, Software & Content, NSF Coordination, Event) with lane leads in the group labels, 58 granular tasks each with a unique id, a single named owner, start, deadline, status, description, and real dependencies; 9 milestones on the plan-of-record backward-scheduled checkpoints. Four DoW-audience tasks woven into Software & Content and NSF Coordination (Dan x3, John x1). The reflow gap from team chat is in as hw-pcb-reflow. Nothing about hiring the NSF Liaison PM. Zero dependency inversions.

## Plan amendments (builder was right where the plan was wrong)

- Legend stays visible in table view and filters it, so the two lenses can never disagree; chart-only controls hide.
- Task/milestone names are not editable in the table (rename-vs-state-key hazard); milestone rename lives in the milestone editor, and was made to actually persist, which it never had.
- Start dates are editable in the table, not just deadlines.
- Undo covers table edits (spec allowed skipping), and this closed a pre-existing milestone-rename undo gap.
- Group rows are not table rows; the phase column carries grouping so sorting keeps meaning.
- Id-less tasks render read-only rather than pretending to persist an unkeyable edit.
- Import rejection over defaulting for dateless rows: an invented date is indistinguishable from a real one once written.
- SVG export names dropped rows in an XML comment rather than skipping silently.
- Fontshare's CSS endpoint loads a stylesheet with zero font-face rules; Satoshi is declared directly against cdn.fontshare.com.

## Adversary findings resolved

Round 1 (16 findings, 2 Critical): milestone rename silently deleted the row on reload; one malformed changelog event killed the server permanently and re-killed it on restart; stored XSS via Notes; `</script>` breakout in /export/html; duplicate milestone names misrouting edits; no-id sidecar entries corrupting group rows; client-forgeable rev counter; phantom reorder instead of rename events; CSV formula injection; CSV round-trip destroying phases; unescaped first-paint labels; unbounded request bodies; dropped group-owner labels and 18 dependency date inversions in the data (adversary flagged 8; builder's sweep found 10 more). All fixed in 0.8.1, none rebutted, each reproduced before fixing.

Round 2 (verifier, behavioral): Gantt tooltip stale after reload (plus a sibling gap on diamond-style milestones the builder found); changelog able to claim an edit the closed tab never persisted. Fixed in 0.8.2 with a pagehide/visibilitychange flush via sendBeacon; /log shape validation and a loud import changelog event taken as minors.

Round 3 (dry-well adversary): server export path missing the stable-key ladder (exports disagreed with the screen after rename+reorder, owner silently dropped) with the root client/server key asymmetry; dateless rows bricking the chart; changelog markdown line spoofing via embedded newlines; fonts never loading. Fixed in 0.8.3.

Round 4 (micro): /export/svg 500 on hand-corrupted dateless rows, plus two same-class defects (dateless phase throw, negative width on fully dateless data). Fixed in 0.8.4; normal-path SVGs byte-identical pre/post.

## Behavioral verification (Sonnet verifier, headless Chrome, screenshots)

| Flow | Result |
|---|---|
| Gantt render: 58 tasks, 9 milestones, 6 lanes, lane leads, arrows, today line | PASS |
| Milestone tag pairwise non-overlap | PASS (0 collisions) |
| Table toggle, 67 rows, correct columns, no "undefined" | PASS |
| Owner/status/text filters, clear, sort | PASS |
| Inline edit: commit, cancel, reload persistence, changelog, undo | PASS |
| Milestone rename round-trip, no row loss, rename event logged | PASS |
| XSS probes: table, tooltip, exported snapshot (re-opened standalone) | PASS |
| Malformed /log posts: server survives, md renders | PASS |
| CSV export/import round-trip: owners, phase names, literal payloads | PASS |
| Data sanity as served: owners, date window, deps, DoW, no hiring | PASS |
| Computed-style looks: fonts, dark tokens, row heights, no overflow | PASS |
| Round-2 fixes re-verified: tooltip freshness both milestone styles, flush inside debounce, single-event logging | PASS |
| Round-3/4 fixes re-verified by the finding adversary: all DEAD at root | PASS |

## Open items / human calls

- No human eye has seen the UI: every check was headless (pup bridge down, adom-desktop-puppeteer-bridge#33). Spacing, colour, and the newly-loading typography deserve a glance.
- No package release was cut: main has 0.8.4 but `adom-wiki pkg install` still delivers the last released version. Cut a release when ready.
- Recommendation for the platform: serve `Access-Control-Allow-Origin` on adom.inc/fonts so every Adom app's font-face rules work; the CORS block was silently defeating them all.
- MS Project XML and Jira CSV imports do not carry owner (no unambiguous single-owner field in those formats); CSV and both exports do.
- No batch reassign in the table (chart's batch editor remains date-only).
- /log remains unauthenticated by design; rev framing can no longer be forged, but posted event text is trusted prose.
- Live instance state and changelog were reset to pristine twice during the build after verified-net-zero inspection of test noise; the plan data file itself was never touched by tests.
