# Context: architecture

## Current (Astro, static output)

Ten routes, one `src/pages/` entry each, built to `dist/`:

| Route                | Page                            | Islands                                         |
| -------------------- | ------------------------------- | ----------------------------------------------- |
| `/`                  | `src/pages/index.astro`         | —                                               |
| `/full-guide/`       | `src/pages/full-guide.astro`    | `GuideSelector`, `LanguageToggle`, `CopyPrompt` |
| `/summary/`          | `src/pages/summary.astro`       | —                                               |
| `/models/`           | `src/pages/models.astro`        | —                                               |
| `/agents/`           | `src/pages/agents.astro`        | —                                               |
| `/skills/`           | `src/pages/skills.astro`        | `SkillPackageExplorer`                          |
| `/rules/`            | `src/pages/rules.astro`         | `RulesInteractive`                              |
| `/skills-review/`    | `src/pages/skills-review.astro` | `legacy/skills-review/app.js`                   |
| `/hands-on/starter/` | `public/` lab fixture           | own                                             |
| `/hands-on/rules/`   | `public/` lab fixture           | own                                             |

## What is still unmigrated

`legacy/` holds the parts the migration did not componentize. They are not dead
files — the pages listed above import them, and the build fails without them.

- **`legacy/styles/guide.css`** (was `styles.css`) — the editorial visual
  system, imported by `full-guide.astro`.
- **`legacy/styles/audit.css`** (was `full-guide/audit.css`) — responsive audit
  overrides, imported by `full-guide.astro`.
- **`legacy/styles/chapters.css`** — imported by `ChapterLayout.astro`.
- **`legacy/styles/skills-chapter.css`**, **`skills-review.css`**,
  **`change-lens.css`** — imported by their respective pages.
  `skills-chapter.css` is imported for its side effect, not through the `?url` +
  `<link slot="styles">` pattern the layout uses: inside a **page**, `?url` on a
  stylesheet resolves to that page's own CSS chunk rather than to the imported
  file, so the link points at the wrong asset and the sheet is emitted but never
  loaded. It was named `skills.css` and silently unloaded that way until
  2026-09-06.
- **`legacy/skills-review/`** — `app.js` and the module graph under it
  (`catalog.js`, `submitted-catalog.js`, `files.js`, `submitted-files.js`,
  `vote.js`). `catalog.js` + `submitted-catalog.js` are the review desk's real
  data model, 24 entries; they are a content collection in all but name.

These sit outside `src/` deliberately: `check-tokens.mjs` sweeps `src`, and
these files are full of raw hex and unnamed breakpoints. Moving one into `src/`
means migrating it to tokens in the same change, not adding an exclusion.

`responsive.css`, `landing.css`, `app.js`, `rules/app.js`, `rules/styles.css`,
and `skills/app.js` were deleted at cutover: their content lives in components.

## Layout

```
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

- **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. They must
  still ship none. Islands are opt-in, per component, and justified.
- **`hands-on/` stays vanilla.** It lives 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

The vote API is a Go service on its own Kubernetes deploy cycle, reached by the
review desk over `window.SKILLS_REVIEW_VOTE_API`. Its source left this
repository on 2026-09-06; the deployed service is unchanged, and the review desk
still calls it. Keep the global, or replace it with a build-time
`PUBLIC_VOTE_API` env var — but if you do, update the service's own README in
the same change.

Its one-vote-per-IP assertion left `verify.mjs` with it. See
[`assertion-removals.md`](assertion-removals.md).
