Skip to content

Serve the Pocket Planetarium from the vendored runtime (RFC 0003) - #6

Merged
Protonmatter merged 4 commits into
mainfrom
topic/beta-vendored-runtime
Aug 18, 2026
Merged

Serve the Pocket Planetarium from the vendored runtime (RFC 0003)#6
Protonmatter merged 4 commits into
mainfrom
topic/beta-vendored-runtime

Conversation

@Protonmatter

Copy link
Copy Markdown
Owner

Implements RFC 0003 and resolves deviation D-1.

What

pocket-planetarium.html was the only page on the site that needed the network to boot. Its runtime, a separate copy at beta/pocket-planetarium/support.js, loaded React, ReactDOM and Babel from unpkg while every other page loaded the same three bundles from vendor/. This points it at vendor/.

The bundles are the same bytes

Bundle Version in file Beta's pin
vendor/react.production.min.js 18.3.1 react@18.3.1
vendor/react-dom.production.min.js 18.3.1-next-f1338f8080-20240426, the real 18.3.1 UMD build react-dom@18.3.1
vendor/babel.min.js 7.29.0 @babel/standalone@7.29.0

The decisive check is not the version strings: the SHA-384 of each vendored file already equalled the SRI constant the beta declared. A SHA-384 match is byte equality, so the vendored copies are the unpkg artefacts.

The SRI constants are therefore unchanged. Only the three URL strings move. Rewriting six digests by hand to arrive at the same values would only create a chance to typo one, and a wrong digest renders the page blank with no console error and an HTTP 200. The two runtime copies are now byte-identical.

Closing the gap that let D-1 hide

SITE-001 is enforced by reading <script src> out of the HTML. pocket-planetarium.html declares exactly one script, a local reference, so it passed SITE-001 statically. The remote URLs were string constants inside that .js file, where nothing looked.

Deleting D-1 without closing that hole would leave it open for the next copy of the runtime. So:

  • SITE-019 (new): every copy of the runtime resolves its bundle URLs to vendor/ rather than a remote origin.
  • SITE-016 (amended): now covers every copy of the runtime, not support.js alone.

Both were confirmed to fail red on the old code before implementing.

Verification

  • npm test: 22/22 validation, traceability OK, 109/109 browser, 0 failures.
  • The page joins PAGES, so it is now covered by SITE-006, SITE-007, SITE-008, SITE-017 and SITE-018. All five pass.
  • Offline proof: with every non-local request blocked, the page boots fully. React 18.3.1, Babel 7.29.0, 1,639 characters rendered, zero page errors. The only blocked request is the site-wide Google Fonts stylesheet. Before this change it fetched three unpkg URLs.

Also included

A fix for the flaky DECK-012 test. It held the unawaited page.evaluate() promise that installs the slidechange listener, then pressed ArrowRight, so the keypress raced the round trip that registers the listener. Under parallel workers the press sometimes won and the promise never settled, failing on the 30s timeout. It looked like a deck bug and was not one. Stressed at --repeat-each=20 --workers=6: before, 4 of 60 runs failed; after, 60 of 60 pass. It was the only instance of that pattern in the suite.

A stale reference. tests/regression/invariants.spec.js cited "deviation (D-1)" for the template-placeholder issue, which is D-2. Already wrong, and deleting D-1 made it point at nothing.

BETA-005's rationale in landing.spec.md justified its SHOULD level by the unpkg dependency. I corrected the reasoning but left the level alone, since raising it needs a viewport test that does not exist.

Notes for review

  • D-2 keeps its number. Reusing identifiers would falsify the CHANGELOG.
  • CDN_DEPENDENT_PAGES is removed rather than emptied, as nothing imported it.

Protonmatter added 2 commits August 18, 2026 07:11
The beta's copy of the runtime loaded React, ReactDOM and Babel from unpkg
while every other page loaded the same three bundles from vendor/. That made
pocket-planetarium.html the only page needing the network to boot, kept it
outside SITE-001 and SITE-008, and kept it out of the regression suite.

The vendored files are byte-for-byte the artefacts those URLs serve: React
18.3.1, ReactDOM 18.3.1 and @babel/standalone 7.29.0, each with a SHA-384
digest already equal to the SRI constant the beta declared. So only the three
URL constants change; the digests stay as they were. Rewriting them would have
been the risk, since a wrong digest renders the page blank with no error.

Both runtime copies are now byte-identical.

Also closes the gap that let the deviation hide. SITE-001 reads <script src>
out of the HTML, and the beta's one script tag is a local reference, so the
remote URLs sitting in variables inside that file passed the static gate. New
SITE-019 reads the runtime sources themselves, and SITE-016 now covers every
copy of the runtime rather than support.js alone.

- docs/rfcs/0003-vendored-runtime-for-the-beta.md: the proposal
- docs/specs/site.spec.md: add SITE-019, amend SITE-016, delete deviation D-1
- tests/validation/documents.test.js: cover both requirements
- tests/support/pages.js: page moves into PAGES, CDN_DEPENDENT_PAGES retired
- tests/regression/invariants.spec.js: cite D-2, not the deleted D-1
- README, CHANGELOG, SDLC, landing.spec: drop the stale CDN exception
The test held the unawaited page.evaluate() promise that installs the
slidechange listener, then pressed ArrowRight. Installing the listener needs a
round trip into the page, so the keypress raced it. Under parallel workers the
press sometimes won: the event fired with nobody listening, the promise never
settled, and the test failed on the 30s timeout. It looked like a deck bug and
was not one.

Awaiting the install, then reading the result back afterwards, removes the race.

Stressed at --repeat-each=20 --workers=6: before, 4 of 60 runs failed; after,
60 of 60 pass.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

…ild hazard

Three points raised reviewing this branch.

Hash each vendored bundle once rather than once per runtime. The digests do not
depend on which runtime declares them, so computing them inside the runtime loop
re-hashed 3MB of babel.min.js for every entry in RUNTIMES.

Derive VENDORED from BUNDLE_FILES. They listed the same three paths
independently, so a future rename could update one and leave SITE-009 checking a
different set of files from SITE-016 and SITE-019, silently.

Record the rebuild hazard in the RFC. Both runtimes are marked "GENERATED from
dc-runtime/src/*.ts - do not edit" and dc-runtime is not in this repository, so
SITE-019 now enforces a hand-edit that a legitimate rebuild would undo, with no
way to fix it here. The root support.js already carried vendor/ paths under the
same header, so this is existing practice rather than something new, but it was
not written down and the honest fix is upstream.
The RFC described the problem and named the upstream fix but left it with
nowhere to go. Issue #9 tracks it.
Protonmatter added a commit that referenced this pull request Aug 18, 2026
Independent of #6 and #7. Targets `main` directly.

## What

`SITE-013` asks every page to declare a meta description. **Two of eight
did.** It was the last requirement in the specification with no test,
and a test written against the old state would have failed on six pages,
which is presumably why nobody wrote one.

| Page | Before | After |
| --- | --- | --- |
| `index.html`, `beta.html` | had one | unchanged |
| `solar-system.dc.html` | none | 157 chars |
| `junocam-explorer.dc.html` | none | 137 chars |
| `junocam-deck.dc.html` | none | 132 chars |
| `pocket-planetarium.html` | none | 132 chars |
| `the-moons.dc.html` | none | 127 chars |
| `junocam-poster.dc.html` | none | 118 chars |

The copy is drawn from what the README already says each page contains,
so it describes the real thing rather than inventing a pitch.

## The test

Checks presence, and rejects anything under 40 characters. A description
short enough to be a placeholder passes a presence check while telling a
search result or a shared link nothing. The shortest genuine one here is
118 characters, so the floor has plenty of room and is not tuned to the
current content.

Confirmed it fails when a description is removed, naming the offending
page.

## Checked the descriptions actually reach the browser

The `.dc.html` pages hold their head content in a `<helmet>` block that
the runtime rewrites into the document on boot, so a static `<meta>` in
the head could plausibly have been stripped or duplicated. It is not:
exactly one survives on each page, with the right content.

```
/solar-system.dc.html    {"count":1,"content":"Thirteen slides on the Sun, the eight planets and "}
/junocam-poster.dc.html  {"count":1,"content":"A printable 18 by 24 inch sheet on JunoCam at Jupi"}
/pocket-planetarium.html {"count":1,"content":"The Pocket Planetarium prototype: a scroll-driven "}
```

## Result

- `npm test`: 22/22 validation, traceability OK, 104/104 browser, 0
failures
- `SITE-013` no longer appears in the traceability report as untested

With #7 merged, the report will have **no uncovered requirements at any
level**.

## The level stays SHOULD

Every page now satisfies it and it is covered, so it is no longer an
unkept promise. Raising it to MUST would change what the gate enforces,
which [RFC
0004](docs/rfcs/0004-enforcing-the-phone-viewport-requirement.md)
establishes needs its own proposal. Say the word and I will write it.
@Protonmatter
Protonmatter merged commit 886e91a into main Aug 18, 2026
4 of 6 checks passed
Protonmatter added a commit that referenced this pull request Aug 18, 2026
Follow-up to #6. **Stacked on `topic/beta-vendored-runtime`**, since it
edits the same `BETA-005` paragraph #6 touches. GitHub will retarget
this to `main` when #6 merges.

## Why now

`BETA-005` requires the Pocket Planetarium to render its own content at
an iPhone-sized viewport. It was advisory only because the prototype
loaded its runtime from unpkg, and a mandatory requirement would have
made the build gate depend on that service being reachable. [RFC
0003](docs/rfcs/0003-vendored-runtime-for-the-beta.md) removed the
dependency, so the absence of a test was the only thing still holding
the level down.

## The test

Opens the prototype at 390x844 and asserts it:

1. renders its own content, not just a booted shell
2. draws nothing wider than the screen it is on
3. does not scroll sideways

Assertions 2 and 3 are what catch a broken layout. On a desktop the
prototype draws a 402x874 device frame on a backdrop; below 560px it
drops the frame and becomes the device. A frame left at its fixed width
on a 390px screen spills past the edge, and **no existing test ran at a
phone viewport**, so nothing would have caught that.

## Both assertions verified against a broken build

A passing test proves little on its own. Both were run against a build
with `window.matchMedia` stubbed to report no match, so the frame keeps
its desktop width:

| | adapts correctly | mode defeated |
| --- | --- | --- |
| elements wider than viewport | 0 | **40** |
| document scrolls sideways | no | **yes** |

Both assertions fail in the broken state, which is the only evidence
either is worth keeping.

An earlier revision of this PR asserted on the host element's width and
claimed that was the discriminating check. It was not: the host is a
full-width backdrop, so its width tracks the viewport in both modes and
stayed at 390px while the layout was broken. The justification given for
it, that the assertion failed at a 600px viewport, was also bogus, since
it failed only because the viewport was wider than the hardcoded 390.
Corrected in `7717226`.

## Result

- `npm test`: 22/22 validation, traceability OK, **110/110** browser, 0
failures
- `npm run test:trace` no longer lists `BETA-005` as untested. Only
`SITE-013` (SHOULD) remains.

## On process

Treated as routine under [RFC 0001](docs/rfcs/0001-rfc-process.md),
which lists "a test added for behaviour that is already specified" as
routine. The requirement text is unchanged; only its level moves, and
the reason it was advisory was recorded as void when RFC 0003 landed.

Raising a level does tighten what CI enforces, so if you read that as
substantive I will write it up as RFC 0004 instead.

## Also

`tests/functional/landing.spec.js` opened with a comment saying its
pages need no readiness handling because they use no runtime. That is no
longer true of this one test, so the comment now says so.
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