1
0

fix: make the app boot on an insecure origin, and re-aim the capture harness

Three things, all found by trying to re-shoot the product imagery and failing.

**`crypto.randomUUID` is secure-context only.** `main.ts` called it at module top
level for its three wire identities, so on any origin that is not HTTPS and not
`localhost` the call threw before the scene was built and the app stopped at
"Starting up" with one TypeError and no other symptom. Every way a developer
normally opens this app is a secure context — `vite dev` and `vite preview` serve
localhost, the deployed site is HTTPS — which is why this survived since 3326d2e.
It breaks the brand-capture harness, which serves `dist/` over
`http://tera.lumbridgecorp.com:5210` so the app can read its own hostname and
decide which door it is; it breaks the plain `http://` static host STATIC.md
explicitly invites; and it breaks opening the dev server by LAN IP to try it on a
phone. `src/ids.ts` prefers the platform's `randomUUID` and falls back to
`getRandomValues`, which carries no such restriction. The tests exercise the
fallback specifically, because the happy path was never the broken one.

**`waitForFunction` was ignoring its own timeout.** Playwright's signature is
`(pageFunction, arg, options)` and all four call sites in `shots.mjs` and
`films.mjs` passed `{ timeout: 180_000 }` second, binding it as the predicate's
argument. The wait silently used the 30 s default, which was invisible for as
long as the app booted inside thirty seconds and started failing the moment the
California board grew its relief — with "Timeout 30000ms exceeded" reported
against a line that plainly reads 180_000.

**The office shot list photographed a building that no longer exists.** The
`expect` guard caught it and refused to shoot, which is exactly what it is for:
chapter 0 is "Front Door" now, not "The Floor". But the captions were staler than
the labels — they described forty-eight metres by eighteen, thirty-six seats in
four benches and a fourteen-metre interstitial commons, and Lumbridge HQ is a
live/work studio now. Re-aimed at the buildings that exist: `office-floor` and
`office-desks` at the SF studio (the second specifically at the bench, because the
desk mic and the machine speaker are the new thing there), `office-commons` at the
LA courtyard, `office-hangar` still at Frontier Valley and now honest about being
in development. The four ids are deliberately unchanged: the manifest emits a
`ShotId` union that v4 imports, so renaming one fails v4's typecheck at push.

Also `#onboarding-host` joins the harness's clutter list. The first-run card is
correct behaviour for a real visitor and wrong in a product photograph, and it had
quietly placed itself in the middle of every frame — which is the general hazard
that list exists for, because a shot with a stray card still renders and still
looks deliberate.

`bay-relief`'s daylight frame moves from 07:40 to 09:10. At 07:40 the marine layer
buried the heightfield the shot exists to demonstrate; the version currently on the
site is almost entirely white. Its `note` moves with it, since the note names the
hour and a stale one describes light that is not in the picture.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 00:03:20 -07:00
parent 8fb85cd2e5
commit 3c4649d078
7 changed files with 199 additions and 34 deletions
+1
View File
@@ -393,6 +393,7 @@ async function film(browser, spec, dir) {
() =>
document.getElementById("boot")?.hidden === true &&
document.querySelectorAll("#chapters .chapter").length > 0,
undefined,
{ timeout: 180_000 },
);
await page.waitForTimeout(4000);
+11 -3
View File
@@ -178,12 +178,20 @@ export function clockShim(iso) {
* would be a picture of a permission nobody browsing the site actually has.
* - `#rail` is the keyboard hint strip, which is advice for a person holding a
* keyboard, not information about the city.
* - `#onboarding-host` is the first-run card. It is *correct* — a real visitor
* on a first visit does see it — but it is teaching the reader how to fly a
* board they are looking at a photograph of, and it covers the bottom-left
* quarter of the frame. It is also the newest thing here, which is the
* general hazard this list exists for: every card added to the interface is
* in the product imagery by default, and silently, because the shot still
* renders and still looks deliberate. Anything mounted over the canvas that
* is not the world or an instrument reading it belongs in this list.
*/
export const FURNITURE = {
/** Never wanted in a photograph: the boot curtain and the phone's panel scrim. */
transient: ["#boot", "#scrim"],
BARE: ["#panel", "#panel-toggle", "#corner", "#rail", "#source", "#tier"],
CLUTTER: ["#rail", "#tier", "#source", "#panel-toggle"],
transient: ["#boot", "#scrim", "#onboarding-host"],
BARE: ["#panel", "#panel-toggle", "#corner", "#rail", "#source", "#tier", "#onboarding-host"],
CLUTTER: ["#rail", "#tier", "#source", "#panel-toggle", "#onboarding-host"],
};
/** Hide selectors, and wait a beat for the layout to settle before shooting. */
+30 -27
View File
@@ -85,9 +85,9 @@ const SHOTS = [
caption:
"Everything else in the engine stands on this heightfield: 83 named hills, four octaves of value noise, and overlapping peaks combined as tallest-plus-35%-of-the-rest — the one rule that makes a run of summits read as a ridge instead of stacking into a fictional mountain.",
day: {
at: "2026-08-06T07:40:00-07:00",
note: "Twenty to eight, sun fifteen degrees up: low enough that the hills rake shadow across the flats and the relief reads as relief.",
alt: "The San Francisco Bay Area seen from above in low morning light, the peninsula and the East Bay hills raking shadows across the water.",
at: "2026-08-06T09:10:00-07:00",
note: "Ten past nine, sun thirty degrees up: high enough that the marine layer has burned back off the bay, low enough that the East Bay hills still rake shadow west across the flats.",
alt: "The San Francisco Bay Area seen from above in mid-morning light, the peninsula and the East Bay hills casting shadows west, scattered cloud over the water.",
},
night: {
at: "2026-08-06T21:35:00-07:00",
@@ -178,58 +178,59 @@ const SHOTS = [
{
id: "office-floor",
door: "office",
chapter: 0,
expect: "The Floor",
place: "Lumbridge HQ",
chapter: 1,
expect: "Studio",
place: "SF HQ · Studio",
caption:
"The same renderer from inside: forty-eight metres by eighteen, two storeys held a long way apart, glazed along the north edge and open to the roof at the east end. An Office is one building's interior and a Space is a room within it — and this cannot share a THREE.Scene with the city, because San Francisco puts one scene unit at about 94 metres and this is one unit to the metre.",
"The same renderer from inside, at one unit to the metre — which is why this cannot share a THREE.Scene with the city, where San Francisco puts one unit at about 94. A live/work studio rather than a floorplate: the desk, the bench and the domestic half are one room, and an Office is one building's interior while a Space is a room within it.",
day: {
at: "2026-08-06T11:20:00-07:00",
note: "Twenty past eleven in the morning.",
alt: "A whole office interior rendered in 3D as an open-topped model, walls, desks and meeting rooms laid out across one floor, with its plan drawn beside it.",
note: "Twenty past eleven, daylight through the glazing — the sun is the real one over this coordinate, and the pack says where on the earth it stands and which way it is turned.",
alt: "A studio interior rendered in 3D as an open-topped model: desks with monitors, a sofa and rug, shelving and planting, with its floor plan drawn beside it.",
},
night: {
at: "2026-08-06T21:30:00-07:00",
note: "Half past nine. The building has switched its own lights on, because the sun it is following has gone down over the real San Francisco — and a fitting brightens as somebody walks under it.",
alt: "The same office model at night, lit from within by its ceiling fittings against a dark ground.",
alt: "The same studio at night, lit from within by its ceiling fittings against a dark ground.",
},
},
{
id: "office-desks",
door: "office",
chapter: 2,
expect: "Desks",
place: "Lumbridge HQ",
expect: "Agent Bench",
place: "SF HQ · Studio",
caption:
"Thirty-six seats in four benches, and how many of them are taken depends on the hour you are reading this. Stepping in here does not throw the city away the city scene is retained and paused rather than disposed, because rebuilding the Bay's heightfield costs about 2.3 seconds and nobody should pay that to walk back out of a door.",
"The bench, and the hardware on it. A microphone on the desk and a speaker on the machine are declared by the pack and simulated deterministically behind an adapter a real device API can occupy later — power, gain, level, volume, all readable by anyone, all honestly labelled as demonstration data. Stepping in here does not throw the city away: the city scene is retained and paused rather than disposed, because rebuilding the Bay's heightfield costs about 2.3 seconds.",
day: {
at: "2026-08-06T11:20:00-07:00",
note: "Twenty past eleven, daylight through the glazing on the north edge.",
alt: "A closer view inside the office: rows of desks with chairs and monitors, people seated at them, bookshelves and planting behind a partition.",
note: "Twenty past eleven, daylight across the bench.",
alt: "A close view of a two-place desk bench in a studio: monitors, chairs, a desk microphone and a speaker, with planting behind.",
},
night: {
at: "2026-08-06T21:30:00-07:00",
note: "Half past nine, and the windows have stopped being the light source. The fittings have taken over, and two humanoids are walking the floor.",
alt: "The same desks at night, lit by the office's own lighting with darkness beyond the windows.",
note: "Half past nine, and the windows have stopped being the light source. The fittings have taken over.",
alt: "The same bench at night, lit by the studio's own lighting with darkness beyond the windows.",
},
},
{
id: "office-commons",
door: "office",
chapter: 5,
expect: "Commons",
place: "Lumbridge HQ",
office: "LA HQ",
chapter: 6,
expect: "Courtyard",
place: "LA HQ · Office, Arts District",
caption:
"Fourteen metres of floor open all the way to the roof, with a gallery cutting across one side of it. The two storeys are held apart by a fourteen-metre interstitial — not architecture, a deliberate exaggeration of the service void, so that the space between the slabs is somewhere things can hang. Everything you can see is composed at runtime from cached unit primitives; no mesh in this repo was ever a file.",
"A second studio, eight hundred kilometres south, and the argument for the format being a format. A courtyard block square to the 1781 pueblo grid rather than to the compass — which is the whole reason a pack carries a heading, because the heading decides which walls the sun comes through. Same engine, same clock, same sun; nothing here was ever a mesh file.",
day: {
at: "2026-08-06T11:20:00-07:00",
note: "Twenty past eleven, and the sky through the glazing is the real sky over this coordinate.",
alt: "A tall interior atrium seen from floor level: pale terrazzo, trees, lounge seating, with a balcony crossing high above and daylight beyond the glass.",
note: "Twenty past eleven, and the courtyard is taking the sun the way a courtyard at 36 degrees off north does.",
alt: "An open courtyard inside a two-storey block rendered in 3D: planters, a parasol, tables and chairs, glazed offices around the edges.",
},
night: {
at: "2026-08-06T21:30:00-07:00",
note: "Half past nine, with the pendants hanging nineteen metres into the void.",
alt: "The same atrium at night, lit by pendants high in the volume.",
note: "Half past nine, and the courtyard is lit from the rooms around it rather than from above.",
alt: "The same courtyard at night, lit from the glazed rooms around its edges.",
},
},
{
@@ -240,7 +241,7 @@ const SHOTS = [
expect: "Hangar",
place: "Frontier Valley, Alameda Point",
caption:
"A second building, and the argument for the format being a format: one room, fifty-four metres by thirty, nine to the trusses, in a hangar on the old naval air station across the estuary. Same engine, same clock, same sun — and it feels nothing like the tower, because an office pack now says where on the earth it stands and how it is turned, and four metres above an airfield is not a hundred and eighty-eight above Transbay.",
"A third pack, still in development, and the clearest statement of what an office pack is: one room, fifty-four metres by thirty, nine to the trusses, in a hangar on the old naval air station across the estuary. It feels nothing like either studio because four metres above an airfield is not a hundred and eighty-eight above Transbay, and the pack is what says so.",
day: {
at: "2026-08-06T11:20:00-07:00",
note: "Twenty past eleven. North light through the clerestory, which is the light a shed actually wants.",
@@ -413,6 +414,7 @@ async function shoot(browser, shot, frame) {
() =>
document.getElementById("boot")?.hidden === true &&
document.querySelectorAll("#chapters .chapter").length > 0,
undefined,
{ timeout: 180_000 },
);
// Boot hiding means the scene exists, not that it has drawn a full frame.
@@ -447,7 +449,8 @@ async function shoot(browser, shot, frame) {
() =>
document.getElementById("boot")?.hidden === true &&
document.querySelectorAll("#chapters .chapter").length > 0,
{ timeout: 180_000 },
undefined,
{ timeout: 180_000 },
);
await page.waitForTimeout(preview ? 3000 : 6000);
}