Skip to content

feat: live architecture view in the dashboard - #47

Open
Robobc wants to merge 2 commits into
mainfrom
feat/architecture-graph
Open

feat: live architecture view in the dashboard#47
Robobc wants to merge 2 commits into
mainfrom
feat/architecture-graph

Conversation

@Robobc

@Robobc Robobc commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What

Adds an Architecture tab that draws the deployed platform live, from the same status.json the Monitor tab already reads. Retires the Approach tab.

Changes

  • dashboard/public/graph.js + graph.css (new) — the view. Plain DOM and inline SVG, zero dependencies, because the dashboard has to keep working over python3 -m http.server with no build step. Layout is deterministic (columns are layers, rows ordered by a single barycentre pass), so the same status.json always draws the same picture and a narrated demo stays true tomorrow. Drag a card and it stays where you drop it, including across polls; scroll to zoom, drag the background to pan.
  • dashboard/monitor.py — emits a deployment block (strategy, role, polled/platform/workload accounts) so the view can be honest about multi-account deployments. Also stops calling sts:GetCallerIdentity twice per poll, and degrades to the full stack list when the contract cannot resolve a footprint instead of blinding the dashboard.
  • dashboard/public/index.html — Architecture tab wired in; Approach tab removed, Monitor is now the default. Drops 371 lines.
  • dashboard/approach-tab.html (new) — the Approach tab, parked. Note it lives in dashboard/, not dashboard/public/, so the static server cannot reach it. Header comment explains how to put it back.
  • tests/test_dashboard.py — 7 tests pinning the view's invariants.

Three deliberate constraints

state is read, never recomputed. monitor.py owns the one CloudFormation-status classifier. A second one in the browser was the exact bug #46 removed, so the view reads the emitted state and degrades a missing one to not-deployed rather than re-deriving it from the raw status string.

A dashboard sees exactly one account. In a federated deployment the far side is not observable from here, so it is drawn unobserved — dashed, no state colour — and never counted as deployed or failed. Guessing would be worse than admitting it.

Observability is a per-card badge, not edges. Five stacks ship logs and traces to the observability stack; drawn as edges that is a fan-in straight through the middle of the picture and was the worst source of crossings. TOPOLOGY keeps the real dependency because it mirrors app.py, and the filter lives at draw time, so the map and the picture disagree in exactly one obvious place.

Verified

  • 33 headless assertions against the real status.json and a federated fixture: every edge is a cubic bezier with no NaN, five edges survive the in-scope topology filter, out-of-scope stacks land in the bottom tray and carry no edges, badges appear only on deployed stacks shipping to a deployed observability stack, the VPC box stays hidden until the networking stack is actually deployed, a dragged card survives the next poll while an untouched one re-snaps to its slot, and the federated fixture yields two account boxes with exactly one trust edge.
  • A real bug found that way. buildModel guarded status.json by truthiness only, so a malformed file with stacks as a string passed s && s.stacks and Object.keys() then walked its characters — one card per letter. The try/catch above it could not help, because nothing throws. Both blocks are shape-checked now, and the second commit pins it: mutating either check out turns CI red.
  • Live against a deployed platform: reads 6 deployed + 4 out of scope, matching deploy.sh verify.
  • 156 tests pass; ruff check and ruff format --check clean.

Review notes

  • graph.js carries long comments explaining the choices most likely to get "simplified" later — why DOM instead of <canvas> (a stack name is text: crisp at any zoom, selectable, reaches the accessibility tree), why there is no animation loop at all (every animation is CSS @keyframes, so idle CPU is zero and prefers-reduced-motion switches the lot off in one block), and why the layout is one barycentre pass rather than full Sugiyama.
  • Known rough edge: the drawing is 1330px wide, so on a narrow window auto-fit drops to about k=0.65 and 12px type renders around 8px. The lever is the 130px gutters, but they are also what makes the edges read as curves. Left as-is deliberately; zoom and fit controls are bottom-left.

Robobc added 2 commits August 25, 2026 23:42
Adds an Architecture tab that draws the deployed platform from the same
status.json the Monitor tab reads, and retires the Approach tab.

The view is plain DOM + inline SVG with no dependencies, because the dashboard
has to keep working over `python3 -m http.server` with no build step. Layout is
deterministic (columns are layers, rows ordered by one barycentre pass), so the
same status.json always draws the same picture and a narrated demo stays true.

Three deliberate honesty constraints:

- `state` is READ from monitor.py, never recomputed. monitor.py owns the one
  CloudFormation-status classifier; a second one in the UI was the bug the
  previous change removed.
- A dashboard sees exactly ONE account. monitor.py now emits a `deployment`
  block (strategy, role, polled/platform/workload accounts) and the far side of
  a federated deployment is drawn "not observed" rather than given a guessed
  status. It is never counted as deployed or failed.
- Observability is a per-card badge, not edges. TOPOLOGY keeps the real
  dependency (it mirrors app.py) and the filter lives at draw time, so the map
  and the picture disagree in one obvious place. Five lines converging on one
  card was the worst source of crossings.

monitor.py also stops calling get_caller_identity() twice per poll and degrades
to the full stack list when the contract cannot resolve a footprint, instead of
blinding the dashboard.

The Approach tab moves to dashboard/approach-tab.html — parked outside public/
so the static server cannot reach it, with instructions for putting it back.
Monitor becomes the default tab. index.html drops 371 lines.

Verified:
- 33 headless assertions (DOM shim, real status.json + a federated fixture):
  every edge is a cubic bezier with no NaN, 5 edges survive the in-scope
  topology filter, out-of-scope stacks land in the tray and carry no edges,
  badges appear only on deployed stacks shipping to a deployed observability
  stack, the VPC box stays hidden until networking is deployed, a dragged card
  survives the next poll while an untouched one re-snaps, and the federated
  fixture yields two account boxes with one trust edge.
- Fixed while testing: buildModel guarded status.json by truthiness only, so a
  malformed file with a string `stacks` passed the guard and Object.keys() walked
  its characters — one card per letter. The try/catch above could not help
  because nothing throws. Shape-checked now; mutating the guard back out makes
  the probe fail.
- Live against the test rig (066523631817): reads 6 deployed + 4 out of scope,
  matching `deploy.sh verify`.
- 155 tests pass; ruff clean on monitor.py.
The truthiness guard that let a string `stacks` through (one card per
letter, and update()'s try/catch cannot catch it because nothing throws)
now fails CI if it regresses. Mutating either check out makes it red.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant