Fix five defects found by running Motion rather than reading it
CI / verify (push) Successful in 7m21s
CI / publish (push) Has been skipped

The markdown parser was in the eager entry chunk. `manualChunks` in its
object form does not leave an unlisted vendor package to Vite's async
splitting, so react-markdown was hoisted into the entry even though its only
importers are lazy routes — 327.70 kB gzip against a 314 kB baseline, on the
one download every route pays for. Naming it as its own chunk puts it back
behind the Motion pages and takes the entry to 282.21 kB, below where it was
before Motion existed.

The starter library could never be improved. Seeding was insert-only, so a
deployment seeded in August was frozen on August's wording for ever with no
upgrade path short of editing production rows by hand — for a feature whose
entire premise is that the library gets better. A second run now refreshes a
starter row, but only while it is still ours: `is_system`, `usage_count = 0`
and no owner. That is the same condition §7a already enforces on the API, so
a template an engagement was cut from is left alone and reported by name
rather than silently overwritten.

The refresh was not idempotent, and the seed lied about it. `jsonb` does not
preserve key order — Postgres sorts keys by length then bytewise — so
comparing `JSON.stringify(stored)` against `JSON.stringify(authored)` marked
every template as changed on every run, and the seed rewrote nine rows each
time while reporting itself clean. Comparison is now canonical. Found by
running the seed three times and reading the counts.

`motion-overflow-check.mjs` measured less than it claimed. It seeded
`pig.sidebar` and `pig.piggy.dock`, neither of which anything reads (the keys
are `pig.sidebarOpen` and `pig.piggyDockOpen`), so the layout it pinned was
whatever the last run left. Its dark pass set `colorScheme` only, and the
appearance preference is stored server-side and adopted after hydration, so
the dark pass measured the light palette a beat after first paint. It now
rewrites the profile response as `screenshots.mjs` does, asserts the rendered
`data-theme`, and fails a page that renders almost no text — a page that
throws inside its own body otherwise measures zero overflow and passes.

The stage rail rendered "1 templates", in the visible label and in every
aria-label. Singular and plural are now both passed.

Also normalised `artifact` to `artefact` in the seeded prose, which had
drifted American in the playbook. The `artifacts` field key is untouched:
FieldsView reads it, and already labels it in British.
This commit is contained in:
2026-08-19 00:07:08 -07:00
parent 15c72ade1c
commit 2f32186d22
12 changed files with 187 additions and 38 deletions
+41 -6
View File
@@ -49,12 +49,31 @@ for (const theme of ['light', 'dark']) {
isMobile: vp.name === 'mobile',
hasTouch: vp.name === 'mobile',
});
// The sidebar and the dock are per-device states in localStorage; left to
// whatever a human last set, the measurement is not reproducible.
/*
* The sidebar and the dock are per-device states in localStorage; left to
* whatever a human last set, the measurement is not reproducible. The key
* names are `pig.sidebarOpen` and `pig.piggyDockOpen` — an earlier version
* of this file invented `pig.sidebar` and `pig.piggy.dock`, which set two
* keys nothing reads and left the real state untouched, so the run looked
* pinned and was not.
*/
await ctx.addInitScript(`
localStorage.setItem('pig.sidebar', 'expanded');
localStorage.setItem('pig.piggy.dock', 'closed');
localStorage.setItem('pig.sidebarOpen', 'true');
localStorage.setItem('pig.piggyDockOpen', 'false');
localStorage.setItem('pig.themeMode', '${theme}');
`);
/*
* colorScheme alone only sets prefers-color-scheme, and the appearance
* preference is stored SERVER-side and adopted after hydration — so the
* dark pass rendered light a beat after first paint and measured the wrong
* theme. Rewriting the profile response is what screenshots.mjs does, and
* for the same reason.
*/
await ctx.route('**/api/me/profile', async (route) => {
const response = await route.fetch();
const body = await response.json().catch(() => ({}));
await route.fulfill({ json: { ...body, themeMode: theme } });
});
for (const route of ROUTES) {
const page = await ctx.newPage();
@@ -90,11 +109,27 @@ for (const theme of ['light', 'dark']) {
.split(' ')
.slice(0, 4)
.join('.')}`);
return { overflow, wide, text: document.body.innerText.slice(0, 120) };
return {
overflow,
wide,
theme: doc.getAttribute('data-theme'),
chars: document.body.innerText.trim().length,
text: document.body.innerText.slice(0, 120),
};
});
const label = `${route.slug} ${vp.name} ${theme}`;
if (measured.overflow !== 0) {
if (measured.theme !== theme) {
// Without this the dark pass silently measured the light palette, and a
// dark-only overflow — a wider border, a different font fallback —
// would have gone unseen while the run reported ok.
failures++;
console.log(`FAIL ${label}: rendered data-theme=${measured.theme}, not ${theme}`);
} else if (measured.chars < 120) {
// A page that throws inside its own body still measures zero overflow.
failures++;
console.log(`FAIL ${label}: rendered only ${measured.chars} characters — the page is blank`);
} else if (measured.overflow !== 0) {
failures++;
console.log(`FAIL ${label}: overflows by ${measured.overflow}px — ${measured.wide.join(', ')}`);
} else if (problems.length) {