feat: separate the ambient field and Graph Playground - #1
Merged
Conversation
Two graph languages, deliberately opposed. The ambient field is a live force-directed graph: nodes drift under repulsion and edge springs, the topology rewires itself, and semantic pulses travel it. Most arrive; about one in four stops mid-edge, cuts, and marks its downstream path. That is the world before GraphABI. Every product surface is the opposite by design - precise, causal, and it comes to rest. Canvas 2D with a hand-written simulation rather than a WebGL library: the interesting work is the force model, not the rasteriser, and Three.js would have cost ~600 KB against a documented performance budget for nothing. Also fixes real defects found while rebuilding: - light mode was broken. The header kept a hardcoded dark background, the wordmark 404'd on a missing logo-light.svg, and code text rendered near white on white. The palette is now two designed themes, not an inversion. - the brand lockup is an inline mark plus HTML text, so it is theme-aware, costs no request, and no longer depends on Inter being installed. - blast-radius edges stacked a green background under red dashes during the baseline phase. Edge state is now one `background`, so the replay cannot assert two states on one rail. - architecture and report artwork were hardcoded-dark images that inverted wrongly in light mode and scaled their labels below legibility on mobile. Both are now components built from the same primitives as the page. - the pulse now rests at a tokenised distance short of the cut, in both the horizontal and vertical topologies, instead of colliding with it. Spacing is on a strict 8px scale, mobile is a rethought vertical topology rather than rotated desktop, and reduced motion renders every state the animation would have revealed.
Auditing the palette turned up contrast failures that the previous single status colour could not avoid: --subtle failed on every dark surface, and red text on the breaking tint landed at 4.06:1 against a 4.5:1 requirement. Status colours are now split by role. --pass / --fail / --unknown drive strokes, rails, node borders and the break mark, where 3:1 applies because they are non-text UI. --pass-text / --fail-text / --unknown-text drive anything set as small text, and clear 4.5:1 on every surface it can land on including the status tints. The break mark keeps its full-strength red, so the one high-saturation focal point in a graph scene is unchanged. Also gives the horizontally scrollable code regions keyboard access, which only affected narrow viewports where they actually overflow. axe: 0 violations at 1440 and 390, light and dark. Lighthouse: performance 99, accessibility 100, best practices 100, SEO 100.
The method rail is an edge, so step 03 now actually interrupts it: the rail opens a gap and the canonical two-stroke mark sits inside it, instead of a single slash floating unattached in the middle of a cell. The closing call to action is centred. It was a 14ch headline pinned left against roughly sixty percent dead space, and the honest fix is composition rather than inventing decoration to fill it. Also re-measures the segmented thumb and the field's obstacle rects once fonts settle, since a late Inter swap changes both the label widths the thumb is sized from and the text boxes the field routes around. Measured: no frame over 20ms at 1440, 1920, or 390 with 4x CPU throttling.
The traversal rail and its section node were hidden until an IntersectionObserver revealed them, so with scripting off every section eyebrow lost its node and the rails never drew. The rail is structure, not motion: it now renders by default, and only the arrival animation is withheld, gated on a class the document sets for itself. Lighthouse 100 across performance, accessibility, best practices and SEO. CLS 0, TBT 0 ms, 29 KB gzipped for the whole page.
The baseline no longer resolves every edge at once. One pulse walks the graph hop by hop, each rail filling green behind it, so the pass is shown being earned rather than asserted. The candidate then leaves on the same path and never arrives. Adds a drain beat between the two runs. The graph previously cut from fully resolved to empty in a single frame, which read as a glitch instead of a second run starting clean. Fixes the blast propagation, which was firing all at once: .is-playing had narrowed the node transition list to opacity and transform, so border and background snapped and the stagger delay had nothing to act on. Affected nodes were lighting up ahead of the edges meant to be carrying the blast to them. The blast now travels outward, edges leading nodes by one beat.
Nodes carry a depth value that drives radius, alpha, edge weight and drift distance, so the field reads as a volume rather than a flat wireframe. The graph now parts around the cursor, near nodes moving further than far ones, which is what sells the depth. Coarse pointers are excluded: there is nothing hovering to react to, and a touch would drag the graph around under the user's own finger. Still no frame over 20ms at 1440, 1920, or 390 with 4x CPU throttling.
The dialog's transform origin is computed from the trigger's position, so the panel unfolds out of that node instead of arriving from nowhere. Falls back to a centred scale when reduced motion is set or there is no trigger.
An audit across twelve widths from 320 to 1920 found system labels rendering at 10px and the method-rail numerals at 9px, both below the 12px label spec. Section-level labels now sit at the full 12px; the densest in-panel labels at 11px, where 12px would bloat the instrument panels. Two consequences fixed with it: the step circles grew to keep the numerals centred, so the rail that threads them moved a pixel; and the pipeline's contract labels now wrap instead of running off a 320px viewport, which the larger type exposed. No horizontal overflow and no text under 11px at any of the twelve widths.
The roadmap was the one section still speaking list instead of graph. Each planned item now sits behind a hollow node on a dashed stub: an edge that does not reach. Hovering completes it in amber, the unknown colour, because that is exactly the claim being made. Planned is now visible before the label says so.
The panel was mostly empty space around a thin graph. Both nodes now carry their actual content: the producer's recorded output and the consumer's contract, side by side. Switching to the candidate changes exactly one number, from 1 to 0, and colours it, so the claim the section makes is visible in the graph before the check is even run. The edge now names the contract being evaluated rather than repeating the consumer node's label, and long contract identifiers wrap instead of running off a narrow viewport.
parvbansal11
marked this pull request as ready for review
August 4, 2026 18:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Outcome
Rebuilds the public site around two graph languages that never blur together:
The primary explanation remains exact: the schema passes, semantics fail at researcher to verifier, the witness is candidate-003, and verifier, decision_maker, and publisher are affected.
What changed after the exploration
Final screenshots
Measured
The site remains static, framework-free, font-free, analytics-free, and usable without JavaScript. The PR-media orphan branch is retained only for immutable review evidence so deployment does not ship multi-megabyte full-page screenshots.