docs: add .agents workspace and the Astro refactor plan
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>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# Context: architecture, current and target
|
||||
|
||||
## Current (no build step)
|
||||
|
||||
Ten hand-written HTML pages, each linking its own CSS and one ES module:
|
||||
|
||||
| Route | Page | Script | Stylesheets |
|
||||
| --- | --- | --- | --- |
|
||||
| `/` | `index.html` | — | `chapters.css`, `landing.css` |
|
||||
| `/full-guide/` | `full-guide/index.html` | `app.js` (50 KB) | `styles.css`, `responsive.css`, `audit.css` |
|
||||
| `/summary/` | `summary/index.html` | — | `chapters.css` |
|
||||
| `/models/` | `models/index.html` | — | `chapters.css` |
|
||||
| `/agents/` | `agents/index.html` | — | `chapters.css` |
|
||||
| `/skills/` | `skills/index.html` | `skills/app.js` | `skills/styles.css` |
|
||||
| `/rules/` | `rules/index.html` | `rules/app.js` | `rules/styles.css` |
|
||||
| `/skills-review/` | `skills-review/index.html` | `skills-review/app.js` | `skills-review/styles.css`, `change-lens.css` |
|
||||
| `/hands-on/starter/` | lab fixture | own | own |
|
||||
| `/hands-on/rules/` | lab fixture | own | own |
|
||||
|
||||
Weight is concentrated: `app.js` 50 KB, `responsive.css` 30 KB,
|
||||
`skills-review/catalog.js` 27 KB, `skills-review/submitted-catalog.js` 18 KB.
|
||||
|
||||
### What each big file actually is
|
||||
|
||||
- **`app.js`** — not really application code. It is a **bilingual content
|
||||
database** (`phases`, `handsOnPrompts`, `modelGuide`, `skillSources`,
|
||||
`skillInstallPrompts`, each keyed `{en, pt}`) plus ~12 small `render*`
|
||||
functions that swap `innerHTML` on tab clicks. ~50 `en:` keys. The content
|
||||
should become data; only the tab behaviour is interactive.
|
||||
- **`responsive.css`** — a 30 KB append-only layer of overrides bolted on top of
|
||||
`styles.css`. Expect large parts to be dead once layout moves into components.
|
||||
Do not port it verbatim.
|
||||
- **`skills-review/catalog.js`** — the real data model of the review desk: one
|
||||
entry per submitted skill with `id`, `author`, `title`, `status`, `focus`,
|
||||
`wins[]`, `improve[]`, `extras`, `improved` (full markdown). 24 entries across
|
||||
`catalog.js` + `submitted-catalog.js`. This is already a content collection in
|
||||
all but name.
|
||||
- **`skills-review/files.js` / `submitted-files.js`** — generated file manifests.
|
||||
- **`vote.js`** — the vote widget island; talks to `vote-service/`.
|
||||
|
||||
## Target (Astro)
|
||||
|
||||
```
|
||||
src/
|
||||
content/ catalog entries, chapter copy, EN/PT strings (typed collections)
|
||||
layouts/ BaseLayout, ChapterLayout, GuideLayout
|
||||
components/ .astro by default; islands only where marked
|
||||
styles/ tokens.css, base.css, then per-component styles
|
||||
pages/ routes mirroring today's URLs exactly
|
||||
public/
|
||||
hands-on/ lab fixtures copied verbatim, never processed
|
||||
```
|
||||
|
||||
### Non-negotiables for the target
|
||||
|
||||
- **URLs do not change.** `/full-guide/`, `/skills-review/`, `/hands-on/starter/`
|
||||
and the rest must resolve exactly as they do now, trailing slash included.
|
||||
Existing links (including `docs/`, SilverBullet, and shared URLs with
|
||||
`?author=…&skill=…&view=…` query params) must keep working.
|
||||
- **Zero JS by default.** Seven of the ten pages ship no JavaScript today.
|
||||
They must still ship none. Islands are opt-in, per component, and justified.
|
||||
- **`hands-on/` stays vanilla.** It goes in `public/` untouched. It is a lab
|
||||
fixture, not a component.
|
||||
- **No external runtime requests.** `audit-ui.mjs` enforces this and it is part
|
||||
of the site's thesis. Self-host anything you add.
|
||||
- **The review desk's query-param deep links keep working** — `?author=`,
|
||||
`?skill=`, `?view=`, `?file=`, `?compare=`, `?render=`. They are documented in
|
||||
the page footer and shared externally.
|
||||
|
||||
## Companion service
|
||||
|
||||
`vote-service/` is a Go API on its own Kubernetes deploy cycle, reached by the
|
||||
review desk over `window.SKILLS_REVIEW_VOTE_API`. The refactor does not touch
|
||||
it. Keep the global, or replace it with a build-time `PUBLIC_VOTE_API` env var —
|
||||
but if you do, update `vote-service/README.md` in the same change.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Context: the design system (as it actually is)
|
||||
|
||||
Read this before touching CSS. Everything here was extracted from the current
|
||||
files, not assumed.
|
||||
|
||||
## There is no single palette. There are three.
|
||||
|
||||
The same semantic names carry different values depending on which stylesheet
|
||||
loaded them:
|
||||
|
||||
| Token | `styles.css`, `rules/styles.css` | `chapters.css`, `skills-review/styles.css` | `hands-on/*/styles.css` |
|
||||
| --- | --- | --- | --- |
|
||||
| `--paper` | `#f5f4f1` | `#f6f3ed` | `#f4f3ef` |
|
||||
| `--ink` | `#172f42` | `#122534` | `#173044` |
|
||||
| `--muted` | `#697b89` | `#65717a` | `#687d8c` |
|
||||
| `--line` | `#d8dee2` | `#d0d5d2` | `#d5dde1` |
|
||||
| `--blue` | `#527f9f` | `#215675` | `#5683a1` |
|
||||
| `--gold` | `#efc76b` | `#ebbf58` | `#efc86d` |
|
||||
| `--accent` | `#7c78a8` | — | — |
|
||||
| `--deep` | `#102536` | — | — |
|
||||
| `--red` | — | `#a7483f` (chapters only) | — |
|
||||
| `--violet` | — | `#6b668f` (review desk only) | — |
|
||||
|
||||
Most deltas are a few units per channel — drift, not intent. `--blue` is the
|
||||
exception: `#527f9f` vs `#215675` is a visible difference and may be deliberate.
|
||||
|
||||
**Decision required before any component work** (task 02). Options:
|
||||
|
||||
- **Canonicalize** to one palette. Recommended. The sub-perceptual deltas
|
||||
collapse; only `--blue` needs a human's eye on a before/after screenshot.
|
||||
- **Keep three named surfaces** (`--surface-guide`, `--surface-chapter`,
|
||||
`--surface-lab`) if the drift turns out to be intentional per section.
|
||||
|
||||
Do not "just pick one" silently in the middle of another task. This is its own
|
||||
reviewed change with visual diffs attached.
|
||||
|
||||
## The typography you see is not the typography that was written
|
||||
|
||||
`styles.css` line 1:
|
||||
|
||||
```css
|
||||
@font-face{font-family:Manrope;src:url('https://fonts.googleapis.com/css2?family=DM+Mono&family=Manrope:wght@400;600;700;800&display=swap')}
|
||||
```
|
||||
|
||||
`src:` points at a **CSS stylesheet**, not a font file. No browser can load a
|
||||
font from that, so:
|
||||
|
||||
- every `font-family:Manrope,Arial,sans-serif` renders as **Arial**
|
||||
- every `font:… 'DM Mono',monospace` renders as the **generic monospace** face
|
||||
- there are no `@font-face` blocks anywhere else and zero font files in the repo
|
||||
- `scripts/audit-ui.mjs` only rejects external `<link>`/`<script>` tags, so this
|
||||
slipped through the "dependency-free" audit
|
||||
|
||||
**This is a trap for the refactor.** Self-hosting Manrope and DM Mono in Astro
|
||||
is the obvious "fix" — and it would change how every page looks, violating
|
||||
"maintain the same styles". Treat it as an explicit product decision:
|
||||
|
||||
- **Keep current rendering**: delete the dead `@font-face`, replace the font
|
||||
stacks with what actually renders today (`Arial, sans-serif` /
|
||||
`ui-monospace, monospace`). Zero visual change. Honest CSS.
|
||||
- **Adopt the intended fonts**: self-host the woff2 files in `public/fonts/`,
|
||||
add real `@font-face` with `font-display:swap`. Better-looking, but it is a
|
||||
redesign and needs sign-off plus fresh screenshots.
|
||||
|
||||
Default to the first unless a human says otherwise.
|
||||
|
||||
## Type scale
|
||||
|
||||
`Georgia, serif` is used deliberately for emphasis (`h1 em`, `.hero em`) and is
|
||||
real — it is a system font, so it does render. Keep it.
|
||||
|
||||
Sizes are all `clamp()`, roughly:
|
||||
|
||||
| Role | Value |
|
||||
| --- | --- |
|
||||
| Display / `h1` | `clamp(56px,9vw,126px)` |
|
||||
| Section `h2` | `clamp(36px,5vw,65px)` |
|
||||
| Sub-head | `clamp(24px,3vw,38px)` |
|
||||
| Pull-quote | `clamp(22px,3vw,36px)` |
|
||||
| Body | `15px/1.6` … `18px` |
|
||||
| Eyebrow / label | `10–11px` monospace, `letter-spacing:.08–.1em`, uppercase |
|
||||
|
||||
There are 14+ distinct clamp triples doing near-identical jobs. Collapse to a
|
||||
named scale (`--step-0` … `--step-6`) during tokenization; the visual result
|
||||
should be unchanged within a pixel or two at common viewports.
|
||||
|
||||
## Breakpoints
|
||||
|
||||
Sixteen distinct max-widths are in use: 420, 520, 530, 560, 600, 620, 720, 800,
|
||||
850, 880, 900, 1000, 1050, 1100 — plus `min-width:1600px` and `min-width:2200px`.
|
||||
|
||||
Collapse to a named set (suggested: 560 / 800 / 1100 / 1600 / 2200) and prove
|
||||
equivalence with screenshots at the *old* breakpoint values, since that is
|
||||
where regressions will hide.
|
||||
|
||||
`@media(prefers-reduced-motion:reduce)` is already respected in several
|
||||
stylesheets. Keep it — see [`../rules/animation.md`](../rules/animation.md).
|
||||
|
||||
## House style worth preserving
|
||||
|
||||
The visual identity is editorial-print: flat colour blocks, hairline `1px`
|
||||
rules, uppercase monospace eyebrows with wide tracking, very tight negative
|
||||
letter-spacing on display type (`-.06em` … `-.08em`), grid layouts with `gap:1px`
|
||||
over a background colour to fake borders, and near-zero border-radius.
|
||||
|
||||
That last trick (`gap:1px` + parent background) is used everywhere. It is
|
||||
intentional. Do not replace it with `border`.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Context: publishing, and what a build step changes
|
||||
|
||||
## How it works today
|
||||
|
||||
The Gitea Pages Server serves the **`pages` branch tree directly**. There is no
|
||||
build. `main` and `pages` end up with byte-identical trees; `pages` exists only
|
||||
because the Pages Server publishes a branch, not a directory.
|
||||
|
||||
Live at `https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/`.
|
||||
|
||||
Full procedure, including the fact that `merge --ff-only main` fails (the
|
||||
histories diverged), is in [`../../docs/operations-guide.md`](../../docs/operations-guide.md).
|
||||
|
||||
## What Astro changes
|
||||
|
||||
Astro emits `dist/`. The Pages Server cannot run a build, so **something has to
|
||||
put built output on `pages`**. Pick one, deliberately, in task 01:
|
||||
|
||||
| Option | How | Cost |
|
||||
| --- | --- | --- |
|
||||
| **A. Build locally, commit `dist/` to `pages`** | `npm run build`, copy `dist/*` into the `pages` worktree, commit | `pages` stops being "the exact source". Diffs become unreadable. Publishing depends on one workstation. Simple, no new infra. |
|
||||
| **B. Gitea Actions builds and pushes `pages`** | workflow on `main` → `npm ci && npm run build` → force-push `dist/` to `pages` | `pages` becomes a machine-owned branch (force-push is fine *because* nothing else writes it). Needs the act-runner to be healthy. **Recommended.** |
|
||||
| **C. Serve from a container instead** | drop Pages Server for an nginx pod behind the existing ingress | most control, most infra, changes the URL story |
|
||||
|
||||
**Recommended: B**, with A as the documented manual fallback for when the runner
|
||||
is down. Note the known failure mode: this Gitea's act-runner registration lives
|
||||
in an `emptyDir`, so a pod restart silently kills CI until re-registered. The
|
||||
runbook must say "if the site stopped updating, check the runner first".
|
||||
|
||||
Whichever you pick, `docs/operations-guide.md` must be rewritten in the same
|
||||
task — it currently promises `pages` is "the exact published source", and that
|
||||
stops being true under A and B.
|
||||
|
||||
## Base path
|
||||
|
||||
The site is served from a **subdirectory**: `/ai-for-dummies/`. Astro needs
|
||||
`base: '/ai-for-dummies'` in `astro.config.mjs`, and every internal link must go
|
||||
through `import.meta.env.BASE_URL` or Astro's `<a href={...}>` helpers rather
|
||||
than a hand-written absolute `/models/`.
|
||||
|
||||
This is the single most likely source of "works locally, 404s in production" in
|
||||
this migration. Verify it on the real host, not just `npm run preview`.
|
||||
|
||||
## Verification before you call it published
|
||||
|
||||
```bash
|
||||
curl -sS -o /dev/null -w '%{http_code}\n' \
|
||||
"https://netcracker.pages.marcospaulo.dev.br/ai-for-dummies/?v=$(git rev-parse --short HEAD)"
|
||||
```
|
||||
|
||||
The `?v=` cache-buster matters: the Pages Server caches, and a stale 200 looks
|
||||
exactly like a successful deploy. Check one nested route
|
||||
(`/ai-for-dummies/skills-review/`) and one static asset too — the base-path bug
|
||||
shows up on assets first.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Context: the verification contract
|
||||
|
||||
`scripts/verify.mjs` is 13 KB, 42 `throw new Error` sites, 16 checkpoints. It
|
||||
reads 26 source files and asserts that specific **string tokens** appear in
|
||||
them — `data-phase="plan"`, `renderTree`, `.change-lens`,
|
||||
`styles.css?v=20260904-vote-widget`, and so on.
|
||||
|
||||
## Why this matters more than it looks
|
||||
|
||||
These assertions are the only thing standing between this site and silent
|
||||
content loss during a large refactor. They are also **all going to break**,
|
||||
because they assert against files that will stop existing.
|
||||
|
||||
The failure mode to guard against: an agent runs `npm run verify`, sees red,
|
||||
and "fixes" it by deleting the assertion. The suite goes green and the site
|
||||
loses a section. **Deleting an assertion is a change that requires review, the
|
||||
same as deleting a feature.**
|
||||
|
||||
## How the contract must evolve
|
||||
|
||||
Three kinds of assertion, three different fates:
|
||||
|
||||
| Kind | Example | Fate |
|
||||
| --- | --- | --- |
|
||||
| **Content presence** | `'data-phase="plan"'` in `full-guide/index.html` | Re-point at built output (`dist/`) — the token should survive rendering. If it does not, the component dropped content. |
|
||||
| **Implementation detail** | `'const phases'`, `'renderTree'` in `app.js` | Obsolete. Replace with an assertion about *behaviour or output*, never delete outright. |
|
||||
| **Cache-busting version** | `'app.js?v=20260904-vote-widget'` | Obsolete — Astro hashes assets. Replace with "the built HTML references a hashed asset". |
|
||||
|
||||
**Rule: the assertion count must not fall.** Every removed token is replaced by
|
||||
one that pins the same user-visible fact against the new architecture. The
|
||||
verification engineer owns this and is the only role allowed to reduce coverage,
|
||||
with a written reason per removal.
|
||||
|
||||
## The stronger check to add
|
||||
|
||||
Token-matching is brittle. During the migration, add a **rendered-output diff**:
|
||||
snapshot the current site's DOM text content per route, then assert the Astro
|
||||
build produces the same text. That catches dropped paragraphs the way token
|
||||
matching cannot.
|
||||
|
||||
```bash
|
||||
# before migrating a page, from the vanilla site:
|
||||
node .agents/scripts/snapshot-route.mjs /models/ > .agents/snapshots/models.txt
|
||||
# after: same script against dist/, diff must be empty (or reviewed)
|
||||
```
|
||||
|
||||
See [`../skills/verify-contract/SKILL.md`](../skills/verify-contract/SKILL.md).
|
||||
|
||||
## Also in the suite
|
||||
|
||||
`scripts/audit-ui.mjs` asserts every page has a viewport meta and **no external
|
||||
`<script>`/`<link>`**. Keep it and extend it: it currently misses external URLs
|
||||
inside CSS (`@font-face src`, `@import`, `url()`), which is exactly how the
|
||||
broken Google Fonts request in `styles.css:1` got in.
|
||||
Reference in New Issue
Block a user