igneum/tools/site/sheet-test.mjs
igneum-labs 7eed16a29a Pre-public scrub, the text pass (7 October 2026, 19:5x UK): no founder name, personal login, earlier business or personal address in any tracked text file, and a gate check that keeps it so
The sweep (main's item 1): 199 tracked text files, 783 lines. The founder's full name, first name and possessive become "the founder" (sentence starts capitalised); the lowercase operating-system user name in WSL paths and commands becomes <user>; the second owner login becomes "the second owner login"; the three earlier businesses and the two other brands become "the other business", "the earlier entity", "the earlier business" and "another brand"; the Chrome profile rule names the igneum.network profile, not the profile's label. The standing commit login igneum-labs is not a founder term here: the fresh-repository step renames it in the history (docs/plans/history-rewrite.md, tools/repo/fresh-repo.sh).

The patterns never appear in plain text in the tree (a plaintext list would be the hit): tools/ci/founder-strings.b64 (perl regex, tab, a sample per row) is read by tools/ci/founder-strings-check.sh (every tracked text file, perl, known-failed first: the self-test plants each row's sample in a fixture and the hit must name the file), by tools/community/discord-hooks.mjs (the guard's founder and business rows; the test takes its fixtures from the samples) and by tools/repo/fresh-repo.sh (the business names of the rewrite rules). site/forbidden-strings.txt carries the same patterns as b64: lines, decoded case-insensitive by site/scrub.mjs and tools/ci/launch-gates-check.mjs (whose fixture now plants an encoded made-up name). The check runs in the gate's tree checks on every merge.

Not in this commit, by main's word: the 105 commit messages and 40 personal-identity commits that need the history rewrite (listed, not run), and the secrets found by gitleaks over the history (reported with owners).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 18:39:50 +00:00

67 lines
4.9 KiB
JavaScript

#!/usr/bin/env node
// The phone menu opens and is seen (site-ui-5, 7 October 2026, the founder's "mobile nav does not work"): at 390 px every served
// page's burger opens the sheet, and the sheet's first link is on screen and on top (a hit-test at its centre returns that
// link, not the page beneath). The failed case this guards: the sheet was position:fixed inside the bar, whose
// backdrop-filter is a containing block for fixed descendants, so top and bottom resolved against the 60 px bar and the
// sheet opened with zero height while aria-expanded said true.
//
// node tools/site/sheet-test.mjs [site-dir] every page; exit 1 listing each page whose sheet is not seen
// node tools/site/sheet-test.mjs --self-test [dir] known-failed first: a page with the sheet forced to zero height is
// caught, then the real pages pass
//
// Needs a browser: Playwright from this tree's node_modules, $IGNEUM_PLAYWRIGHT_DIR, or /srv/builds/_bin/overlap on a build
// box (the overlap sweep's install). Without one it says so and exits 0 (CI installs Playwright; the box has it; the Mac
// renders nothing). The site is served by tools/site/serve.mjs on a port of its own with /api answering nothing.
import { readdirSync, existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawn } from 'node:child_process';
import http from 'node:http';
const here = dirname(fileURLToPath(import.meta.url));
const args = process.argv.slice(2);
const selfTest = args.includes('--self-test');
const site = args.filter(a => !a.startsWith('--'))[0] || join(here, '..', '..', 'site');
function playwright() {
const req = createRequire(import.meta.url);
for (const root of [join(here, '..', '..'), process.env.IGNEUM_PLAYWRIGHT_DIR, '/srv/builds/_bin/overlap'].filter(Boolean)) {
try { return createRequire(join(root, 'package.json'))('playwright'); } catch (e) { /* next */ }
}
try { return req('playwright'); } catch (e) { return null; }
}
const pw = playwright();
if (!pw) { console.log('sheet-test: no Playwright here (the box and CI have it); nothing checked'); process.exit(0); }
const port = 4700 + Math.floor(Math.random() * 300);
const server = spawn(process.execPath, [join(here, 'serve.mjs'), String(port), site, 'http://127.0.0.1:1'], { stdio: 'ignore' });
await new Promise((res, rej) => { let n = 0; const t = setInterval(() => { http.get(`http://127.0.0.1:${port}/`, r => { clearInterval(t); res(); }).on('error', () => { if (++n > 80) { clearInterval(t); rej(new Error('serve.mjs did not answer')); } }); }, 100); });
const pages = readdirSync(site).filter(f => f.endsWith('.html')).map(f => f.replace(/\.html$/, '')).map(n => n === 'index' ? '/' : '/' + n);
const browser = await pw.chromium.launch({ headless: true, args: ['--no-sandbox'] });
async function check(route, breakIt) {
const ctx = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 1, reducedMotion: 'reduce' });
const page = await ctx.newPage();
await page.goto(`http://127.0.0.1:${port}${route}`, { waitUntil: 'load' });
if (breakIt) await page.addStyleTag({ content: '.nav .nav-sheet{height:0!important}' });
await page.click('#nav-burger');
const r = await page.evaluate(() => {
const b = document.getElementById('nav-burger'), s = document.getElementById('nav-sheet'), a = s && s.querySelector('a');
if (!b || !s || !a) return { ok: false, why: 'no burger, sheet or first link' };
const box = a.getBoundingClientRect(), vis = box.width > 0 && box.height > 0 && box.top >= 0 && box.bottom <= innerHeight && box.left >= 0 && box.right <= innerWidth;
const hit = document.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2), onTop = !!hit && (hit === a || a.contains(hit));
return { ok: b.getAttribute('aria-expanded') === 'true' && !s.hidden && vis && onTop, why: `aria-expanded ${b.getAttribute('aria-expanded')}, hidden ${s.hidden}, link ${Math.round(box.width)}x${Math.round(box.height)} at ${Math.round(box.top)}, on top ${onTop}` };
});
await ctx.close();
return r;
}
let bad = [];
try {
if (selfTest) {
const broken = await check('/', true);
if (broken.ok) { console.error('sheet-test self-test: a sheet forced to zero height passed'); process.exitCode = 1; }
else console.log('sheet-test self-test: the zero-height sheet is caught (' + broken.why + ')');
}
if (!process.exitCode) {
for (const route of pages) { const r = await check(route, false); if (!r.ok) bad.push(`${route}: ${r.why}`); }
if (bad.length) { console.error('sheet-test: ' + bad.length + ' pages whose phone menu is not seen\n' + bad.join('\n')); process.exitCode = 1; }
else console.log(`sheet-test: ${pages.length} pages open the phone menu at 390 px with the first link on screen and on top`);
}
} finally { await browser.close(); server.kill(); }