Skip to content

Frontend shell: the pristine theater drawn at / (closes #39) - #60

Merged
bgent19 merged 2 commits into
mainfrom
issue-39-frontend-shell
Aug 14, 2026
Merged

Frontend shell: the pristine theater drawn at / (closes #39)#60
bgent19 merged 2 commits into
mainfrom
issue-39-frontend-shell

Conversation

@bgent19

@bgent19 bgent19 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Closes #39.

make api now serves a hand-written SVG theater at /, drawn at the
instructor's authored coordinates, with /docs untouched. Zero dependencies:
no Node, no npm, no build step, no CDN, no web fonts. The assets live inside
the package and are mounted as static files, so pip install gets the frontend
with nothing extra to run — verified by building a wheel and confirming all
seven modules are inside it.

Structure

Seven modules with the ownership the spec fixes. api.js is the only one that
knows an endpoint URL; graph.js builds the DOM once while render.js only
ever sets geometry, text and classes — splitting the two makes build-once
structural, since appending from the render pass would be a visible
import-direction violation rather than an invisible line in a diff. Every
appearance value lives in one :root token block, so retuning for a projector
is an edit to one stylesheet.

Boot wiring is inline in index.html rather than a main.js: the spec fixes
the inventory at seven, and an eighth module would have been the implementer's
choice rather than the spec's.

graph.js freezes four solves against the pristine network, so no mark slides
when a lane is removed: the layered fallback, the directed-pair lookup (reverse
arcs have no id by construction), the sliding lane-anchor solve, and the
crossing solve. Casings are knockouts on the annotation tier only, so the
opening screen carries zero marks.

Verification

The frontend is verified from Python or not at all, and that is a rule rather
than an absence — no test asserts the SVG rendered; the picture is eyeballed,
forever.
tests/test_web_assets.py covers the offline scan, the token subset
and the type floor. tests/test_scenarios.py gains the clearances and a 45°
crossing-angle floor — an angle floor rather than a crossing count, because
the count is a proven global floor that can never fire while the angles are a
windfall protected by nothing.

Each geometry check was confirmed able to go red rather than trusted on a green
run. The angle floor is the one that matters: nudging AIRHEAD-7 by -120,-120
holds every other check green and keeps the crossing count at exactly 8,
while dropping an angle to 42.5° — precisely the invisible regression a count
assertion would never catch.

The anchor solve is mirrored in tests/layout_geometry.py. The two were
compared coordinate-by-coordinate across all three datasets and agree to 6e-14.
That check needs a JS runtime, so it is run by hand rather than added to the
suite; layout_geometry.py documents how.

Two things worth a reviewer's judgement

  • The spec's own numbers conflict. Frontend shell: the pristine theater drawn at / #39 names both a 74 anchor clearance and
    a 120 minimum lane length; those cannot both hold, since a lane needs 2×74 =
    148 to seat an anchor at all. A lane between 120 and 148 would pass the floor
    and then fail the anchor check. I derived the floor from the clearance and
    documented why. Nothing shipped moves — the shortest lane is 242.
  • The ledger rows carry id/from/to. The spec defers "the ledger's content"
    to the panel ticket; I read those three as the row's identity rather than
    its content. Easy to trim.

Both review axes (standards + spec) were run and their findings fixed in the
second commit — the largest being that the opening screen was not actually
pristine, and that the anchor mirror diverged on infeasible lanes in a way that
would have shifted every anchor solved after one.

250 tests pass.

`make api` now serves a hand-written SVG theater at `/`, drawn at the
instructor's authored coordinates, with `/docs` untouched. Zero dependencies:
no Node, no npm, no build step, no CDN, no web fonts. The assets live inside
the package and are mounted as static files, so a `pip install` gets the
frontend with nothing extra to run.

Seven modules with fixed ownership. `api.js` is the only one that knows an
endpoint URL, and `graph.js` builds the DOM once while `render.js` only ever
sets geometry, text and classes -- splitting the two makes build-once
structural, since appending from the render pass would be a visible
import-direction violation. Every appearance value lives in one `:root` token
block, so retuning for a projector is an edit to one stylesheet.

graph.js freezes four solves against the pristine network, so no mark slides
when a lane is removed: the layered fallback for unplaced nodes, the
directed-pair lookup (reverse arcs have no id by construction), the sliding
lane-anchor solve, and the crossing solve. Casings are knockouts on the
annotation tier only, never lane-over-lane, so the opening screen carries zero
marks. Removal is a style rather than a deletion -- a cut lane is struck out,
not deleted, because a lane that vanishes teaches less.

The frontend is verified from Python or not at all, and that is a rule rather
than an absence: no test asserts the SVG rendered, and the picture is eyeballed
forever. tests/test_web_assets.py covers the offline scan, the token subset
(a mistyped var() is dropped at computed-value time and renders plausibly
wrong) and the type floor, with the --type-/--panel- prefix rule.
tests/test_scenarios.py gains the six clearances and a 45-degree crossing-angle
floor -- an angle floor rather than a crossing count, because the count is a
proven global floor that can never fire while the angles are a windfall
protected by nothing.

The anchor solve is mirrored in tests/layout_geometry.py, which is where the
rule is written down. The two were compared coordinate-by-coordinate across all
three datasets and agree to 6e-14; that check needs a JS runtime and so is run
by hand rather than added to this suite.
The opening screen was not pristine. The Theater view switched lane labels on,
and the plate and casing rules named only `.show-flow` -- so the first thing
the room saw was numerals painted over lanes with nothing under them and no
crossing broken. The default view now carries the bare network, and the plate
and casing follow the marks they back rather than one named layer, so either
annotation layer brings its own backing.

The anchor mirror diverged where it mattered least and propagated where it
mattered most: on an infeasible lane JS fell back to the midpoint and pushed
that fallback onto `placed`, which would shift every anchor solved after it
while Python skipped it. The fallback stays -- a numeral in a tight spot beats
a missing one -- but it no longer takes part in the solve, so the two agree
about the whole rest of the diagram. Both sides now say so in words.

MIN_LANE_LENGTH was 120 against an anchor clearance of 74. A lane between 120
and 148 would pass that floor and then fail the anchor check, which is how a
floor comes to be believed and never fires; it is now derived from the
clearance rather than restated beside it. Nothing shipped moves -- the shortest
lane in `data/` is 242.

Also: the terminals derive their x from the authored range instead of two
hardcoded values that were only "the extremes" for today's theater (the
textbook dataset clamps at 972, which the old constant got wrong); `state.dataset`
is set on first load rather than only on a switch, so the field stops lying;
`isRemoved` takes the change map instead of rebuilding it once per lane;
arrowheads moved to the stylesheet, being appearance; the plate's
type-dependent numerals are named, since a projector retune is the expected
edit and would otherwise mis-size every plate silently; and the casing width,
which unavoidably lives in three files, is now pinned by a test.

Dropped: an empty territory tier, an unused `distance` export, and a per-lane
anchor handle nothing read.
@bgent19
bgent19 merged commit 54cbfda into main Aug 14, 2026
1 check passed
@bgent19
bgent19 deleted the issue-39-frontend-shell branch August 14, 2026 22:14
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.

Frontend shell: the pristine theater drawn at /

1 participant