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.