Three strands of work on the chapter surface, all reading from the same
constraint: this site ships no runtime dependencies, so every effect below
is native CSS or an .astro component.
Motion (src/styles/motion.css, DrawRule.astro). A scroll-driven layer of
reveals, hero parallax, section depth, and a hairline that paints itself
along its path, scrubbed by `animation-timeline: view()` — 0KB against
lottie-web's ~60KB gzipped on the main thread. Every scrubbed rule sits
inside `prefers-reduced-motion: no-preference` and `@supports`, so a
Firefox reader or an opted-out one gets the complete static page rather
than one with holes in it. Ranges key to `cover 40%`-`cover 75%` where the
motion is meant to be watched: `view()` ranges key to first visibility,
which on a 2600px page is long before anyone is reading the section.
`.agents/skills/motion/references/scroll-driven.md` records the technique
and the two ways `pathLength` normalisation was broken while building it.
Diagrams (StepFlow.astro, WorktreeMap.astro). The `.steps` stack on
/skills/, /models/, and /agents/ becomes a numbered flow with connectors,
and /agents/ grows the worktree map it was describing in prose — reusing
the `trees` collection rather than a second set of strings. The map's
static variant is gated one class deeper than full-guide's page styles, so
the interactive copy renders byte-identical. Connectors are pseudo-elements,
not SVG: a stretched path desynchronises its own dash pattern, and a
straight line does not need one. Mermaid was considered and rejected at
~1MB of runtime.
Retrieval practice (/rules/, /skills/). A "check yourself" section of
native `<details>` question/answer pairs plus a citation row, both bilingual
through the existing `data-copy` toggle, and both JavaScript-free.
Two audit blind spots surfaced and are closed rather than worked around:
`build.inlineStylesheets: 'never'`, because Astro inlined sheets under ~4kB
and audit-ui.mjs reads its colour and size baseline from dist/_astro/*.css;
and `--columns` declared in the grid components, because an element-level
custom property is not a declaration the audit can resolve.
legacy/styles/skills.css is renamed and imported for its side effect. Inside
a *page*, `?url` resolves to that page's own CSS chunk whatever file it
names, so the link pointed at the wrong asset and the sheet was emitted but
never loaded — the package preview had been rendering unstyled and
overflowing since the Astro cutover.
verify.mjs gains 5 assertions for the recall sections and their Portuguese
copy: 89 now, against the 84 baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes the Go source, Dockerfile, go.mod, and Kubernetes manifests. The
deployed service is untouched and the review desk still calls it over
window.SKILLS_REVIEW_VOTE_API; only the source leaves.
The runbook does not leave. vote-service/README.md moves to
docs/vote-service.md, because it carries the parts that are hard to
rediscover: why the ingress overwrites X-Forwarded-For and Caddy stamps
X-Client-IP instead, why the image is side-loaded into containerd rather
than pulled, and why the PVC pins the Deployment to one node.
This drops verify.mjs from 84 assertions to 83. The removed one read
vote-service/main.go for X-Forwarded-For and 'one active vote per skill'
-- the review desk's only anti-abuse control -- and there is no file left
to read. It is the first assertion this repository has ever lost.
Rather than lower the gate's floor and leave a bare number behind,
gate.sh now subtracts the number of entries in
.agents/context/assertion-removals.md from the baseline. A removal costs
a written reason in a tracked file, in the same commit, as a visible
diff. Tested at 82 assertions: still refused.
Also drops the 22 MB of PNG baselines under .agents/snapshots/before/ and
before-reduced-motion/. They pictured the hand-written site, which no
longer exists; visual-regression.mjs has no compare mode to diff them
against; and they are recoverable from d88d8b8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
`styles.css` line 1 carried a malformed rule for the life of the site:
@font-face{font-family:Manrope;src:url('https://fonts.googleapis.com/css2?...')}
`src:` in an @font-face must point at a font binary. That URL returns a CSS
stylesheet, so no browser could ever load a face from it. Every
`font-family:Manrope,Arial,sans-serif` fell through to Arial, and 'DM Mono' was
never declared as a family at all, so it fell through to generic monospace. The
intended typography has never once been seen.
Task 02 spotted this and was told to default to deleting the dead rule and
declaring the stacks that actually render. It recorded that decision, deferred
the deletion to "future component tasks", and nothing picked it up. The human
has now chosen the other branch: the real fonts.
Self-hosted rather than linked from fonts.googleapis.com because
scripts/audit-ui.mjs rejects any external <link>/<script>, and because the site
is presented in workshop rooms with unreliable networks. Latin and latin-ext
subsets only — the site is EN and PT-BR, so the cyrillic, greek and vietnamese
subsets Google also serves are dropped. Manrope ships as one variable file
covering 400-800. 89 KB total across six faces, all SIL OFL.
One public/fonts/fonts.css serves both trees, with relative url()s that each
consumer resolves against that file's own location: BaseLayout.astro links it
for Astro pages, the legacy root styles.css @imports it.
This changes how every page renders. That is the point, and it is the one
sanctioned visual change in the migration — screenshots taken before today show
Arial and are no longer a valid baseline. The three governing documents that
said "do not add a webfont" are updated so the next design-system-keeper does
not undo this.
Adds .stylelintignore, mirroring .prettierignore's legacy list for the same
reason: staging the minified styles.css to change one declaration produced ~180
declaration-block-single-line-max-declarations errors and blocked the commit.
public/fonts/fonts.css is deliberately excluded from that ignore list.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Document the client-side, dual-rendered locale contract and dispatch a narrow language-change event for guide selector panels.\n\nDo not assemble the full-guide page or change its content collections; task 15d owns that integration.
Ten git worktrees each carried their own 225 MB node_modules (1.1 GB across
five) and paid 11s per `npm ci`. pnpm hardlinks from a shared store: the same
five worktrees cost ~250 MB total, and a fresh install is 4s.
What changed beyond the mechanical rename:
- `overrides` moved to `pnpm-workspace.yaml`. pnpm 11 does not read the `pnpm`
field in package.json *or* npm's top-level `overrides`, and it fails silently
— the vite/defu/language-server pins would have quietly stopped applying.
- Build scripts are blocked by default in pnpm; esbuild and sharp are allowed
explicitly via `allowBuilds` (renamed from `onlyBuiltDependencies` in 11).
- `packageManager` + `engines` pin the toolchain.
- gate.sh rejects a package-lock.json/yarn.lock/bun.lock outright, so an agent
running `npm install` out of habit fails loudly instead of building a second,
divergent dependency tree.
- CI bootstraps pnpm with `npm install --global pnpm@11.25.0` rather than
corepack (unbundled as of Node 25) or pnpm/action-setup (this self-hosted
act-runner has never run a job; fetching a third-party action is not
something to discover on the first one).
Two pre-existing CI bugs fixed while in the file:
- the gate installed with `npm install --package-lock=false`, which discarded
the lockfile the previous session had just fixed.
- the visual-regression step imported `playwright`, which is not a dependency,
and `visual-regression.mjs` has no compare mode anyway — in CI it overwrote
its own baselines and passed unconditionally. Removed with a comment; it
comes back when it can diff.
The `publish` job is now manual (`workflow_dispatch`). During the migration
dist/ holds three HTML files against the live pages branch's ten, so publishing
on every push to main would take the site down to a stub. Restore at task 20.
HANDOVER.md's incident log still says npm where it describes what happened at
the time; that is history, not a missed rename.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the agent-facing workspace and a 20-task plan for migrating the site
to Astro. Nothing here implements the refactor; these are briefs, rules and
templates that the task agents read.
- .agents/ holds context, rules, checklists, skills, specialist agents,
component/page/config templates and gate scripts. It is vendor-neutral so
MiniMax, Gemini and Codex can all read it; CLAUDE.md just points at
AGENTS.md.
- .husky/ plus .lintstagedrc.json wire the three gate tiers. gate.sh locks on
the shared git-common-dir so parallel worktrees serialise, and guards the
assertion count in scripts/verify.mjs against a coverage drop.
- plans/astro-refactor/ carries the phase graph, per-task briefs and the
model-routing recommendation.
These files must be tracked before fanning out: a worktree only checks out
tracked files, so an untracked plan is invisible to every agent working in one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>