# Rule: Astro

Binding for every `.astro` file.

## Zero JS is the default

A component ships no JavaScript unless it has a `client:*` directive. Seven of
this site's ten pages ship no JS today and must continue to.

- Never add `client:load` without justifying it in the PR description.
- Prefer, in order: no JS → `client:visible` → `client:idle` → `client:load`.
- An island is a **leaf**, not a wrapper. Hydrate the tab panel, not the page.

## Islands in this project

Only these need interactivity. Anything else claiming island status is wrong:

| Island                            | Why                                | Directive        |
| --------------------------------- | ---------------------------------- | ---------------- |
| Guide phase/tab switchers         | click-driven panel swap            | `client:visible` |
| Review desk catalog + file viewer | search, filter, fetch source files | `client:load`    |
| Vote widget                       | talks to `vote-service/`           | `client:visible` |
| Language toggle                   | swaps EN/PT across the page        | `client:idle`    |

## Structure

```astro
---
// 1. imports
// 2. Props interface
// 3. destructure Astro.props
// 4. derived values — no side effects, no fetch in components
---

<!-- markup -->
<style>
  /* component-scoped */
</style>
```

- Typed props always: `interface Props { … }`, then `const { … } = Astro.props`.
- Data loading belongs in `src/content/` collections or the page frontmatter,
  not inside a component.
- No barrel files (`index.ts` re-export hubs). They cost tree-shaking and invite
  cycles.

## Content collections

All copy lives in `src/content/`, typed with a Zod schema in
`src/content/config.ts`. The review desk's `catalog.js` maps onto a collection
almost one-to-one — do that rather than importing a 27 KB JS file.

## Styles

- Component styles go in the component's `<style>` block. Astro scopes them.
- Only tokens and true resets live in global CSS.
- Do not port `responsive.css` verbatim. It is an override layer whose reason
  for existing disappears once layout is componentized. Port what a component
  needs, prove the rest is dead, delete it.

## URLs and the base path

The site is served from `/ai-for-dummies/`. Set `base` in `astro.config.mjs` and
never hand-write an absolute internal path. Use `import.meta.env.BASE_URL`.

Existing routes are load-bearing and must not change, including trailing slashes
and the review desk's query params.

## Never

- No UI framework (React/Vue/Svelte) unless a task brief explicitly calls for
  it. Astro components plus a little vanilla JS cover everything here.
- No CSS framework. This site has a hand-built visual identity — see
  [`theming.md`](theming.md).
- No external runtime requests. Self-host. `audit-ui.mjs` enforces it.
