refactor: retire the hand-written site

Deletes the pre-Astro pages, scripts, and stylesheets that the migration
replaced, and moves the ones it did not replace out of the way.

Deleted (32 files): app.js, responsive.css, landing.css, rules/app.js,
rules/styles.css, skills/app.js, the ten route index.html files, and the
root hands-on/ copy, which is byte-identical to public/hands-on/ -- the
one the build actually ships.

Moved to legacy/ (12 files): styles.css, full-guide/audit.css,
chapters.css, skills/styles.css, skills-review/styles.css,
skills-review/change-lens.css, and the skills-review/app.js module graph.
These are not dead. The Astro pages import them and the build fails
without them, which the plan had not accounted for. They go to legacy/
rather than src/ because check-tokens.mjs sweeps src, and these files are
full of raw hex and unnamed breakpoints: moving one into src/ should mean
migrating it to tokens in the same change, not adding a scan exclusion.
The prettier, stylelint, and eslint ignore lists that already named these
files at their old paths now name legacy/ instead.

verify.mjs no longer reads app.js. The 102 Portuguese strings were
extracted from its translations.pt object before deletion into
.agents/snapshots/full-guide-pt.json -- a legacy capture, not a snapshot
of the Astro build, so the assertion still compares against an
independent source. The brace-matching helper's assertion is replaced by
one that rejects an empty snapshot entry, without which trimming the
snapshot would make the presence check pass vacuously. Count stays at 84.

audit-ui.mjs reads the ten pages from dist/ and resolves Astro's
base-absolute hrefs against it.

Before deleting anything, rendered-text-diff was run across all ten
routes plus both Portuguese pages: every one at parity, 0 missing and 0
extra. That comparison is not repeatable once the legacy files are gone.
computed-style-diff on /full-guide/ stays at 32 differences, so the moves
are style-neutral.

Docs updated to match: README, AGENTS.md, GATES.md, the architecture
context, the operations guide's lab instructions, and the three skills
that told you to serve the vanilla site.

Publishing is not part of this commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Marcos Paulo
2026-09-06 08:30:27 +00:00
parent 12c32d2fd7
commit 580293867d
57 changed files with 367 additions and 1287 deletions
+20 -13
View File
@@ -1,18 +1,20 @@
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { dirname, join } from 'node:path';
const base = '/ai-for-dummies/';
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
// The legacy hand-written pages are gone; the built output is the site now.
const pages = [
'index.html',
'full-guide/index.html',
'summary/index.html',
'models/index.html',
'agents/index.html',
'skills/index.html',
'rules/index.html',
'skills-review/index.html',
'hands-on/starter/index.html',
'hands-on/rules/index.html',
'dist/index.html',
'dist/full-guide/index.html',
'dist/summary/index.html',
'dist/models/index.html',
'dist/agents/index.html',
'dist/skills/index.html',
'dist/rules/index.html',
'dist/skills-review/index.html',
'dist/hands-on/starter/index.html',
'dist/hands-on/rules/index.html',
];
const walk = (path) =>
readdirSync(path).flatMap((name) => {
@@ -49,8 +51,13 @@ for (const [kind, values] of Object.entries(baselineValues)) {
const missing = values.filter((value) => !builtValues[kind].has(value));
if (missing.length) throw new Error(`built CSS lost ${kind}: ${missing.join(', ')}`);
}
// Legacy fixtures still ship directly. Resolve every var() from the stylesheets
// that page actually links; Astro's tokens cannot mask a broken standalone page.
// The hands-on labs still ship their own stylesheet rather than going through
// Astro. Resolve every var() from the stylesheets each page actually links, so
// Astro's tokens cannot mask a broken standalone page.
// Astro emits base-absolute hrefs (`/ai-for-dummies/_astro/x.css`); those are
// rooted at `dist/`, not at the page's own directory.
const resolveHref = (page, href) =>
href.startsWith(base) ? join('dist', href.slice(base.length)) : join(dirname(page), href);
for (const page of pages) {
const html = read(page);
const linked = [...html.matchAll(/<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"/gi)].map(
@@ -58,7 +65,7 @@ for (const page of pages) {
);
const styles = linked
.filter((href) => !/^https?:/i.test(href))
.map((href) => read(join(dirname(page), href)));
.map((href) => read(resolveHref(page, href)));
const defined = new Set(styles.flatMap((css) => [...definitions(css)]));
const unresolved = new Set(
styles.flatMap((css) =>
+10 -26
View File
@@ -46,7 +46,7 @@ const source = {
read('src/content/providers/claude.json'),
read('src/content/providers/gemini.json'),
].join('\n'),
starter: read('hands-on/starter/app.js'),
starter: read('public/hands-on/starter/app.js'),
voteService: read('vote-service/main.go'),
ndoReview: read('src/content/reviews/ndo-repro.md'),
};
@@ -73,32 +73,16 @@ if (rendered(html.starter) !== snapshot('hands-on/starter.txt'))
if (rendered(html.labRules) !== snapshot('hands-on/rules.txt'))
throw new Error('rules lab rendered-text snapshot changed');
const braceMatch = (source, open) => {
let depth = 0;
let quote = '';
let escaped = false;
for (let index = open; index < source.length; index += 1) {
const character = source[index];
if (quote) {
if (escaped) escaped = false;
else if (character === '\\') escaped = true;
else if (character === quote) quote = '';
continue;
}
if (character === "'" || character === '"' || character === '`') quote = character;
else if (character === '{') depth += 1;
else if (character === '}' && --depth === 0) return index;
}
throw new Error('could not brace-match legacy Portuguese translations');
};
const legacyGuide = read('app.js');
const translationsStart = legacyGuide.indexOf('const translations =');
const objectStart = legacyGuide.indexOf('{', translationsStart);
const objectEnd = braceMatch(legacyGuide, objectStart);
const translations = Function(`return (${legacyGuide.slice(objectStart, objectEnd + 1)})`)();
const portuguese = Object.values(translations.pt).map(rendered);
// The 102 Portuguese strings were extracted verbatim from the legacy
// `app.js` `translations.pt` object at cutover, before that file was deleted.
// This is a legacy capture, not a snapshot of the Astro build: it still asserts
// against an independent source, which is the whole point of the check.
const portuguese = JSON.parse(read('.agents/snapshots/full-guide-pt.json')).map(rendered);
if (portuguese.length !== 102)
throw new Error('full-guide Portuguese source no longer has 102 translated strings');
throw new Error('full-guide Portuguese snapshot no longer has 102 translated strings');
// Without this, trimming the snapshot would make the check below pass vacuously.
if (portuguese.some((value) => !value.trim()))
throw new Error('full-guide Portuguese snapshot has an empty entry');
if (!portuguese.every((value) => rendered(html.guide).includes(value)))
throw new Error('built full-guide lost a Portuguese translation');