Structure · generated surfaces

A pattern from the gf.cx specimen book

Three pages that happened to agree

A page type you have more than one of — three vehicle pages, six dashboards, every product page in a catalogue — has a structure. The question is whether that structure exists anywhere other than the files themselves. If it doesn't, the copies are being kept in sync by whoever last read all of them, and they are already drifting.

Source · Dan, 2026-08-12

"is it part of a vehicle-pattern now?" … "yes publish the pattern and have the generators read from it"

The problem

The gf.cx vehicle pages had a shape. Contractors, then the service log, then what's specific to this vehicle, then cost of ownership, documents, receipts, Q&A, notes. One of the three was called "the target-state" in conversation and the other two were brought to match it by hand.

That arrangement has a specific failure mode, and it isn't that someone forgets. It's that nothing can tell you it happened. Over a single day of edits to these three pages, all of the following were true at once, and every one was found by a human noticing rather than by a check:

The last one is the honest indictment. Two pages were restructured to match the third; the third was never checked against the result. Nobody could have noticed without opening all three and reading them in order, which is exactly the work the pattern was supposed to remove.

The pattern

Write the structure down as data, put it where it can be read, and give it two consumers: the generators that build the pages, and an audit that fails when a page stops matching.

vehicle-page-structure/spec.json      ← authored once, published
  ├── defaults      details_open_on_load, toggle-all placement
  ├── type_scale    section 26px · nested record 20px · caption 11.5px
  ├── order         the canonical section sequence, with slots
  └── guards        which tool checks what

The spec is a file in the pattern's own directory, so patterns.gf.cx/vehicle-page-structure/spec.json serves it to anyone — human or agent — who wants the structure without a checkout. The generators read it from disk, never over the network. That is the same rule the favicon control plane follows: the manifest is authored in the repo, read locally, and published for legibility. A generator that needs the internet to lay out a page is a generator that breaks on a plane.

Relative order, not absolute

The obvious spec is a list of sections in order, and it is the wrong one. Real instances differ: one vehicle has no Q&A section, only one has classic registration, only one has installed accessories. A spec that demanded an exact list would be wrong about every page it described, and a check that is wrong every time gets switched off within a week.

So the spec ranks sections and the audit checks relative order: the sections a page actually has must appear in the canonical sequence. Anything vehicle-specific goes in a declared slot — one after the service log, one after documents — which says "your own content belongs here" without pretending to know what it is.

{ "key": "vehicle-detail", "slot": true,
  "aliases": ["installed accessories", "acquisition gallery", "current condition"],
  "note": "what this particular vehicle has that the others do not" }

Matching a page's heading to a spec key is substring-against-aliases, not equality, because real headings carry counts, dates and vendors — Service invoice #248282 — 22 Jun 2026, Flemington BMW. One trap worth naming: longest alias wins. "Harvest service log" contains "service log", so a first-match scan files it under the wrong section and then reports the right one as missing. The first run of this audit did exactly that.

Prove the guard on the bugs it was built after

A new check that reports "all clean" on its first run has told you nothing. It passes on a conforming page and on a page it cannot parse, and those look identical from the outside.

The test is free if the defects are in version control. Run the audit against the pages as they were before each fix:

$ git show 041177231:pa/vehicles/bmw-2013.html > /tmp/before.html
$ pa_vehicle_spec.py /tmp/before.html

  ⚠ 8 section(s) ship open, spec says all closed — Seeking contractors, …
  ⚠ toggle-all is nested 2 deep inside "Uniden R4 In-car radar / laser
    detector · windshield-mount" — unreachable once that section is closed

Every defect fixed by hand, reproduced from history, naming the exact card the button was buried in. That is the run that earns the green on the current pages. The same discipline caught the companion date-audit shipping a false green — it stripped \xa0 while the pages write a literal   entity, so it parsed zero dates and passed everything.

Reading it from the generators

Publishing the spec is half of it. If the generators still hard-code the type scale, the spec is documentation, and documentation drifts from code silently — which is the original problem wearing a hat.

from pa_vehicle_spec import load_spec

_SPEC = load_spec()
SECTION_LABEL_PX = _SPEC["type_scale"]["section_label_px"]
OPEN_ON_LOAD     = _SPEC["defaults"]["details_open_on_load"]

load_spec() raises rather than falling back to built-in defaults. A silent fallback would reintroduce exactly the drift the file exists to stop, and would do it invisibly.

Two checks make the wiring real. First, regenerate and confirm the output is byte-identical — that proves the spec describes what the pages already are, rather than what you remember them being. Second, and easy to skip: change a spec value and confirm the output moves. Byte-identical output is also what you get from a spec nothing reads.

$ PA_VEHICLE_SPEC=/tmp/spec-with-caption-99.json  regenerate
    font-size: 99px
$ regenerate                     # real spec
    font-size: 11.5px            # …and byte-identical to baseline

What it caught on day one

Writing the spec found a live drift immediately: the LR4's receipts section sat after the harvest log, where the other two pages had it before Q&A. It had been there through every prior fix.

It also found a rotted anchor nobody could have seen. The receipts generator inserted its block before the first <h2 class="qa-section__label"> — an element that no longer exists on any of the three pages, because Q&A became a <details>. The fallback would have dropped the block at the end of the page. It never showed, because the region markers always existed and the replace branch always won. The cold-insert path had been broken for as long as it had been unnecessary. It now anchors on the spec: the first section canonically after receipts.

Changing the order is now a one-line edit

The first real change arrived the next day: the service log should open the page, above "Seeking contractors". It is the vehicle's primary record, and it carries the expand-all control — which a reader wants before scrolling, not after a section they may not care about.

Before the spec, that was an edit to three files and a hope. Now the order lives in one place, the pages are brought to match, and the audit is what says whether they got there. Run it against the spec before the swap and it names both pages and the direction of the fault:

⚠ bmw-2013.html: "Seeking contractors" is out of canonical order
  — spec puts it before "Service log + Expand all", the page has it after
⚠ ford-f250.html: … the page has it after
✓ LR4.html conforms

LR4 passes because it has no contractors section at all — which is the case that the relative-order rule exists for, and the reason an absolute list would have been wrong here. Same run after the spec bump: three greens. A guard that only ever prints green tells you nothing; this one had already told the truth about the pages a minute earlier.

Where it applies

Reusable elements

Reference

Origin
home.gf.cx vehicle pages, 2026-08-12 — after a day of hand-syncing three pages, Dan asked whether the structure was "part of a vehicle-pattern now". It wasn't.
Spec
spec.json — v1.2.0, the machine-readable companion to this page.
Specimen
The control that inherited the wrong voice — the A/B/C that set the toggle-all's type, with ink gaps measured live in the browser.
Reference impl
~/bin/pa_vehicle_spec.py (loader + audit); read by vehicle_receipts_render.py and pa_vehicle_mileage_chart_inject.py. Live at home.gf.cx/vehicles/ford-f250.
Failure it prevents
Instances of a repeated page type drifting apart in ways only a human reading all of them in order could detect — and therefore not detected.
Related patterns
The plate that outgrows its column — one value, published once, consumed by everything downstream. Built-with provenance footer — the same model applied to page provenance.
Build cost
One JSON file, one ~200-line module, three import lines. No runtime cost — nothing ships to the browser.