Date screenshots by commit, so a fresh clone can pass check:shots
Some checks are pending
build-gate / build (push) Waiting to run

check:shots compared mtimes, and a checkout writes files in arbitrary order:
every clone failed, pristine main included (11 shots "2s behind"). Committed,
unmodified files are now dated by their last commit; files with local edits,
or no git at all, still use mtime.

With the honest clock the same 11 shots really were stale (3h to 31h behind
their bundles), so they are retaken here with ONLY=. Emoji now render: the
shot machine has an emoji font. Negative-tested: a byte appended to the
doxa-simple-map bundle fails exactly the three cap2 shots.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 18:44:53 +00:00
parent 29dfef1fc4
commit 4f689a8fb1
12 changed files with 31 additions and 5 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 KiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 925 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -25,10 +25,19 @@
* Shots whose path is not a /doxa-maps/ leaf (the synthetic iframe host) are REPORTED as skipped
* rather than dropped — an unchecked shot must be visible, not invisible.
*
* NEGATIVE-TESTED: `touch public/doxa-maps/doxa-simple-map/doxa-simple-map.js` makes this FAIL
* naming the three cap2-* shots; re-running readme-shots.mjs returns it to PASS.
* WHICH CLOCK: GIT, NOT THE FILESYSTEM, for any file that is committed and unmodified.
* This compared mtimes, and a clone (or a new worktree) writes files in whatever order git checks
* them out — so every fresh checkout failed, pristine main included (11 shots "2s behind" their
* bundles). A gate that cannot pass on a clone teaches the recipient to ignore it. A committed,
* clean file is dated by its last commit; a file with local edits (or no git at all, e.g. a
* tarball) falls back to its mtime, because then the working copy IS the newest version.
*
* NEGATIVE-TESTED: appending a byte to public/doxa-maps/doxa-simple-map/doxa-simple-map.js makes
* this FAIL naming the three cap2-* shots (a dirty file is dated by mtime); reverting it returns it
* to PASS. A bare `touch` no longer fails it — a touch changes no content, so nothing is stale.
*/
import { readFileSync, existsSync, statSync } from 'node:fs'
import { execFileSync } from 'node:child_process'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
@ -42,6 +51,23 @@ if (!existsSync(SHOTS_SRC) || !existsSync(OUT)) {
process.exit(0)
}
// Dirty = modified or untracked under the two trees compared. null = git unavailable -> mtimes only.
const git = (...a) => execFileSync('git', ['-C', ROOT, ...a], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
let dirty = null
try {
dirty = new Set(git('status', '--porcelain', '--', 'public/doxa-maps', 'documentation/readme-images')
.split('\n').filter(Boolean).map((l) => resolve(ROOT, l.slice(3).replace(/^"|"$/g, ''))))
} catch { /* not a git checkout */ }
function stamp(file) {
if (dirty && !dirty.has(file)) {
try {
const ct = git('log', '-1', '--format=%ct', '--', file).trim()
if (ct) return { ms: Number(ct) * 1000, by: 'commit' }
} catch { /* fall through */ }
}
return { ms: statSync(file).mtimeMs, by: 'mtime' }
}
// Parse `await shot('name', '/path/...')` — the generator is the source of truth for its own map.
const src = readFileSync(SHOTS_SRC, 'utf8')
const shots = [...src.matchAll(/shot\(\s*'([^']+)'\s*,\s*'([^']*)'/g)].map(m => ({ name: m[1], path: m[2] }))
@ -60,10 +86,10 @@ for (const { name, path } of shots) {
if (!existsSync(bundle)) { skipped.push(`${name} (bundle ${m[1]} not built)`); continue }
checked++
const bMs = statSync(bundle).mtimeMs, pMs = statSync(png).mtimeMs
if (bMs > pMs) {
const b = stamp(bundle), p = stamp(png)
if (b.ms > p.ms) {
stale.push(`${name}.png is OLDER than the bundle it shows (${m[1]}) — ` +
`${Math.round((bMs - pMs) / 1000)}s behind`)
`${Math.round((b.ms - p.ms) / 1000)}s behind (bundle by ${b.by}, png by ${p.by})`)
}
}