feat: serve and document flashtrace JSON Schemas#8
Conversation
Copies schemas/** from the tool repo to /schemas/**, byte for byte, and generates a /docs/schemas/<name>/ page per schema from the schema itself. Both come from the same release checkout that /docs/ is built from. The files are machine-consumed, so they never touch the markdown pipeline: the strings inside them ($id, $ref, $schema) are identifiers, not links. latest.json is a real byte copy of the highest version, not a redirect - GitHub Pages has no server-side redirects and a meta-refresh means nothing to a JSON fetch. Its $id keeps pointing at the canonical version, so a consumer that vendors the file still knows which version it holds. The highest version is picked by parsing vN numerically, so v10 beats v9. Version files are re-copied on every build rather than treated as final: schemaVersion only moves on breaking changes, so v1.json keeps receiving additive updates within version 1. The renderer is deliberately strict - an unrecognized keyword fails the build naming its JSON pointer, because a page that silently omits a constraint is worse than a missing page. It resolves local $defs pointers to on-page anchors and walks the top-level allOf alongside the base schema by instance location, so the rich-mode conditionals land on the fields they constrain (forwards at the top level, wantedBy on the item definition) instead of being dropped. schemas/ is absent until the release that introduces it, so a missing folder only warns and the rest of the site still builds; once it exists, anything wrong with its contents is fatal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stacking one section per version does not scale - by v3 the page is mostly history and the TOC lists three copies of every definition. A picker at the top defaults to the latest and switches on demand. Every version still ships in the page and only its display is toggled, so #v1 and #v1-item keep resolving, in-page $ref links keep working, and the browser's own find still hits what is on screen. Degrades cleanly. The .js class now lands on <html> in the pre-paint theme script, and the latest version is marked current in the markup - so the page paints on the right version rather than flashing all of them and collapsing to one. With scripting off the picker is styled away entirely and every version renders stacked, newest first, with all anchors reachable. A select rather than tabs: version counts only grow, and a picker stays one line at ten versions. It is rendered only when there is more than one version to choose between. The TOC rail follows the same data-version filter, and the flow marker now skips sections with no box - a display:none section measures as a zero rect at the top of the document and would drag the marker up there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update: version picker replaces the stacked sections
Every version still ships in the page — only its display is toggled — so No-JS behaviour is deliberate, not incidental. The One non-obvious interaction: the TOC flow marker caches heading rects, and a Verified in a headless browserAgainst a two-version fixture (a synthetic v2, since only v1 exists upstream), 16 checks — default is latest; picker swaps section, TOC and hash; Heads-up: the schema changed upstream mid-review
The renderer handles it correctly — the page's field list matches the new The conditional ( Worth a sanity check from your side that collapsing the modes was intended and not an in-progress state, since it changes what |
It sat below the intro prose, so on a narrow viewport you scrolled past version-independent text before discovering the page could be switched. The right rail was the other candidate and is the wrong home: .toc is display:none under 1100px, which would strand the only way to change version on every tablet and phone. Duplicating the control would mean two selects with one id to keep in sync. On the title line it is always present, reads as "this page is showing version N", and sits above everything it governs. The boxed panel competed with the h1, so it is now just a label and a select, wrapping under the heading when the two no longer fit. Option labels shorten to v10 (latest) to fit beside the heading; they match the anchors, and the section heading below still spells out "Version 10". Long canonical URLs get overflow-wrap: anywhere. They are unbreakable strings that pushed the page into a horizontal scroll on a phone - the page now overflows exactly as much as every other docs page, which is to say only from the pre-existing topbar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A schema is now identified by (directory, format) rather than assumed to be
JSON, so schemas/{name}/v{N}.{format} reads off both: schemas/some-name/v1.json
is titled "Some Name JSON", and a future schemas/report/v1.xml would be
"Report XML" beside it rather than colliding with the JSON page.
The version filename regex captures the extension instead of hardcoding
.json, and the latest alias follows it - latest.xml for xml versions, one per
format. Serving stays format-agnostic: every file is copied verbatim whatever
its extension.
Rendering is not format-agnostic, because schema-doc.mjs reads JSON Schema.
An unrenderable format is still published and warned about rather than
failing the build - the file is served correctly, only its page is missing,
and blocking a release deploy over that is the worse trade. That differs from
the strictness inside a page, where an unknown keyword still aborts: there
the risk is a page that is silently wrong, not merely absent.
Doc pages still live at /docs/schemas/<name>/, which has no room for two
renderable formats of one schema. That is unreachable while only JSON
renders, so it is a guarded error rather than an invented URL scheme.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/docs/schemas/report-json/ rather than /docs/schemas/report/, so two formats of one schema get a page each instead of one overwriting the other. The served path is deliberately not slugged: /schemas/report/v1.json keeps mirroring the tool repo's directory layout exactly, because that URL is the schema's $id and a consumer fetches it by that identifier. Only the docs page - a site concern, not an artifact - takes the qualified slug. This also retires the collision guard added with the previous commit. Pairing a directory with its format is injective: a format matches [A-Za-z0-9]+ and so cannot contain the dash that joins them, meaning two schemas can never land on one slug. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Split into two PRs so the serving half can merge without waiting on a review of the renderer:
Together they build a byte-identical |
Serves the machine-readable JSON Schemas the tool repo publishes under
schemas/, and generates a human-readable page per schema from those files. Both are built from the same release checkout/docs/already comes from, so they cannot disagree about which version the site describes.What lands
/schemas/report/v1.jsonschemas/report/v1.json/schemas/report/latest.json/docs/schemas/report/#vNsection per versionDecisions worth reviewing
The schemas never touch the markdown pipeline. They are copied with
cpSyncand served as-is. The strings inside them ($id,$ref,$schema) are identifiers, not navigation, and the docs link rewriter would corrupt them.latest.jsonis a real file, and its$idis not rewritten. It keeps"$id": ".../schemas/report/v1.json". JSON Schema allows the retrieval URI to differ from$id, which is the point: a copy fetched fromlatest.jsonmust still identify the version it actually is, or every vendored copy would claim to be current. A redirect was not an option — GitHub Pages has no server-side redirects, and a meta-refresh means nothing to a JSON fetch.The renderer fails loudly. An unrecognized keyword aborts the build naming its JSON pointer (
#/$defs/item/properties/tags/maxItems) rather than rendering a page that silently drops a constraint. ExtendingKNOWN_KEYWORDSin src/schema-doc.mjs means deciding how the new constraint displays — it should not be widened just to get a build green.Conditionals are resolved, not dropped. The top-level
allOfis walked alongside the base schema by instance location, sothen.required: ["forwards"]annotates the top-level table andthen.properties.items.items.required: ["wantedBy"]resolves through the element$refand annotates theitemdefinition. Both render asRequired when mode is "rich"instead of a misleadingOptional.Version files are re-copied every build.
schemaVersiononly moves on breaking changes, sov1.jsonis not write-once — it keeps receiving additive updates within version 1. Nothing skips a version file for already existing.A missing
schemas/only warns. It is absent until the release that introduces it, and CI builds against the latest published release — so hard-failing would break CI and deploys today for a folder that upstream has not shipped yet. Once the folder exists, anything wrong inside it is fatal.Verified locally
Built against a checkout that has
schemas/and one that does not./schemas/report/v1.jsonis byte-identical to the upstream file (cmp)/schemas/report/latest.jsonis byte-identical tov1.json(cmp)$idin both readshttps://flashtrace.github.io/schemas/report/v1.json/docs/schemas/report/#v1matches the schema'srequiredarray, withforwardscorrectly shown as rich-mode-conditional#anchorresolves to a heading that exists$defs/ in a property / in athen-branch each report their pointer; unresolvable and remote$reffail;allOfwithoutif/thenfails;v10sorts abovev9; an upstreamlatest.jsonand malformed JSON are both rejectedStill needs checking after the first deploy
Two response-header requirements cannot be verified before the site is live, and neither is controllable from this repo — GitHub Pages has no
_headerssupport:Expecting
200,Content-Type: application/json, andAccess-Control-Allow-Origin: *. Pages is expected to do both by default, but that is worth confirming rather than assuming — third-party validators fetching the schema by$iddepend on the CORS header. If either is wrong we need a different hosting path for/schemas/, so it is worth checking promptly.Sequencing
schemas/is not upstream yet, so this merges safely now and stays dormant: the build warns and skips. The schemas appear on the site only after a flashtrace release containing them — a merge to the tool repo's main is not enough, since both CI and deploy build from a release tag.🤖 Generated with Claude Code