98ab6db3de
The attempt passed the gate and dropped Portuguese from the largest page on a bilingual site: it scraped the legacy full-guide <main> at build time with `?raw` and `set:html` instead of assembling the page from the 19 block components and the content collections. That also couples the new page to a file task 20 deletes. Adds the constraint explicitly, lists the blocks by name so the next run does not have to discover them, and adds two done-when boxes the gate cannot check for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
182 lines
8.8 KiB
Markdown
182 lines
8.8 KiB
Markdown
# Task 15d — Assemble /full-guide/
|
||
|
||
**Agent**: `page-migrator` · **Model**: **Codex** **Depends on**: 05b, 10, 13,
|
||
15a, 15b, 15c · **Parallel with**: 16 · **Blocks**: 15e, 18, 19 **Worktree**:
|
||
`.agents/scripts/worktree.sh start 15d page-full-guide`
|
||
|
||
## Goal
|
||
|
||
`src/pages/full-guide.astro`. 22 KB of HTML, bilingual throughout, everything
|
||
interactive already built by 15a/15b/15c and every block already built by
|
||
task 10. **This task is assembly.** If you find yourself writing a new island,
|
||
stop — it belongs to one of the earlier briefs and you should report the gap
|
||
instead.
|
||
|
||
Read the reports from 15a, 15b and 15c first. They state their props and the
|
||
language contract.
|
||
|
||
## Islands and nothing else
|
||
|
||
| Island | Directive | From |
|
||
| ------------------------------- | ---------------- | ---- |
|
||
| Guide selector (one, ×9 groups) | `client:visible` | 15a |
|
||
| Copy-prompt buttons + progress | `client:visible` | 15b |
|
||
| Language toggle | `client:idle` | 15c |
|
||
|
||
Everything else is server-rendered.
|
||
|
||
## Asserted by verify.mjs — all must survive
|
||
|
||
`const phases`, `const handsOnPrompts`, `const modelGuide`,
|
||
`const skillSources`, `const skillInstallPrompts`, `render('plan')`,
|
||
`renderTree`, `renderWorker`, `renderRoute`, `renderModelProvider`,
|
||
`renderEffort`, `renderSkillFile`, `renderSkillWorkflow`, `renderCommonSkill`,
|
||
`renderHandsOn`, `copyPrompt`, plus
|
||
`data-copy-target="prompt-install-skills|prompt-basic|prompt-skills"`,
|
||
`hands-on/starter/`, `additional-reading.md`, `role="tablist"`, `<table>`.
|
||
|
||
Implementation-detail assertions, deliberately. **Never delete one.** Task 19
|
||
re-points them to output-level checks; you do not.
|
||
|
||
## Watch for
|
||
|
||
- `hands-on/starter/` is a **lab fixture**. Link to it, ship it as a static
|
||
asset, do not componentize it. Same for `hands-on/rules/`.
|
||
- `renderHandsOn` takes no argument — it is not part of 15a's selector pattern.
|
||
Check whether 15a covered it; if not, it is yours, and say so in the report.
|
||
- Do not delete `responsive.css` here. That is 15e, and it needs screenshots.
|
||
|
||
## Done when
|
||
|
||
- [ ] Snapshot diff against `.agents/snapshots/` empty
|
||
- [ ] Every interaction works: all nine selector groups, both languages, all
|
||
three copy buttons
|
||
- [ ] Keyboard: arrows move between tabs; focus visible throughout
|
||
- [ ] JS payload **smaller** than today's 50 KB — content is static now
|
||
- [ ] Screenshots match at 560 / 800 / 1100 / 1600 px
|
||
- [ ] `pnpm run gate` green — the full gate, not `verify` + `audit-ui` alone
|
||
- [ ] 42 assertions intact
|
||
|
||
## What 15a, 15b and 15c actually shipped
|
||
|
||
Read `.agents/context/content-i18n.md` first — it is the language contract and
|
||
it is binding on this task.
|
||
|
||
- **`src/components/islands/GuideSelector.astro`** — one island, all nine
|
||
groups. Render it as
|
||
`<GuideSelector rootSelector="#full-guide" data={...} />`. `data` needs
|
||
`phases`, `workers`, `trees`, `routes`, `providers`, `efforts`, `skillFiles`,
|
||
`skillWorkflow`, `commonSkills` and a bilingual `labels` object; the
|
||
`GuideSelectorData` interface at the top of the file is the exact shape. You
|
||
server-render each group's shell and its initial detail panel, keeping today's
|
||
`data-*` hooks and `.active` state. Mark each group `role="tablist"` and its
|
||
controls `role="tab"` or the keyboard handler will not bind.
|
||
- **`src/components/islands/LanguageToggle.astro`** — render each localized
|
||
fragment twice, with `data-language-content="en"` or `"pt"` on the outer
|
||
element. The toggle flips `hidden` on those, sets `<html lang>`, and fires
|
||
`ai-for-dummies:languagechange` on `window`. It is a plain `.astro` island
|
||
that defers its own setup with `requestIdleCallback`; **do not** put
|
||
`client:idle` on it — that directive is framework-components only.
|
||
- **`src/components/islands/CopyPrompt.astro`** — one per button. Pass
|
||
`target="prompt-install-skills" | "prompt-basic" | "prompt-skills"`. Render
|
||
`<p id="copy-status" role="status" aria-live="polite">` exactly once on the
|
||
page; the island writes into it. Fill the `<pre><code id="prompt-…">` bodies
|
||
from the `handsOnPrompts` and `skillInstallPrompts` collections — 15b verified
|
||
those are byte-identical to the legacy `app.js` constants, and the clipboard
|
||
copies whatever you render, so do not reformat them.
|
||
- **`src/components/islands/ReadingProgress.astro`** — replaces the legacy
|
||
`<div class="reading-progress">` at the top of the page.
|
||
|
||
Nothing else is missing. If you are about to write an island, you are doing
|
||
another task's work — report the gap instead.
|
||
|
||
## Two things to expect
|
||
|
||
- **The snapshot will not match by construction.** Dual-locale rendering emits
|
||
both languages into the HTML where today's page emits English plus a
|
||
Portuguese map inside `app.js`. Compare _rendered, language-filtered_ output
|
||
against today's page, and if `.agents/snapshots/` needs regenerating, say so
|
||
explicitly in your report with what changed and why — do not quietly rewrite a
|
||
snapshot to make a diff go away.
|
||
- **`renderHandsOn` takes no argument** and is not part of GuideSelector's
|
||
nine-group pattern. It is yours. Its name is asserted by `verify.mjs`.
|
||
|
||
## The data exists now (task 05b)
|
||
|
||
Your first attempt stopped here, correctly: six of the nine selector groups had
|
||
no collection to read. Task 05b fixed that. `src/content/` now carries `workers`
|
||
(3), `trees` (4), `routes` (4), `skillFiles` (4), `skillWorkflow` (5) and
|
||
`commonSkills` (7) alongside the six task 05 already migrated. Every string was
|
||
verified byte-identical to its `app.js` original — 221 values, zero mismatches —
|
||
so read them as authoritative and do not re-derive from `app.js`.
|
||
|
||
Two things 05b decided that you should know:
|
||
|
||
- `trees[id].status` is in the schema although `GuideSelectorData` omits it.
|
||
`index.html` renders it as the dot colour on each tree node. The island does
|
||
not consume it; the page may.
|
||
- `commonSkills[id].source` is embedded per entry rather than joined from
|
||
`skillSources[id].url` at render time. Both carry the same URL.
|
||
|
||
## The one piece still missing: `labels`
|
||
|
||
`GuideSelectorData.labels` is not a collection. Those strings — `OWNER` /
|
||
`RESPONSÁVEL`, `REASONING LOAD` / `CARGA DE RACIOCÍNIO`, `context: isolated` /
|
||
`contexto: isolado` and the rest — are still hard-coded bilingual literals
|
||
inside the `render*` functions in `app.js`. 05b deliberately left them, because
|
||
they are page-chrome rather than content.
|
||
|
||
They are yours. Lift them verbatim — same rule as everything else, both locales
|
||
mandatory, no retranslation, copy the exact strings out of `app.js`. Whether
|
||
they become a seventh collection or an inline constant in the page is your call;
|
||
say which you chose and why.
|
||
|
||
## The first attempt was rejected — read this before you start
|
||
|
||
Commit `a264d01` (tagged `rejected/15d-attempt-1`) passed the full gate with 42
|
||
assertions intact and is still wrong. It did this:
|
||
|
||
```js
|
||
import legacyGuide from '../../full-guide/index.html?raw';
|
||
let guideMarkup = legacyGuide.match(/<main>[\s\S]*<\/main>/)?.[0] ?? '';
|
||
```
|
||
|
||
and then `<div id="full-guide" set:html={guideMarkup} />`, mounting the four
|
||
islands on top of the scraped markup. Three things that breaks:
|
||
|
||
1. **Portuguese is gone.** The page contains zero `data-language-content`
|
||
attributes and zero `.pt` reads — every server-rendered detail panel
|
||
hard-codes `.en`. The legacy `<main>` is English-only; today's Portuguese
|
||
comes from `app.js`, which the Astro page does not load. The language
|
||
contract in `.agents/context/content-i18n.md` is binding and this violated
|
||
it. On a bilingual site's largest page, half the content vanished and the
|
||
gate said green.
|
||
2. **Zero of task 10's block components are used.** All 19 exist in
|
||
`src/components/blocks/`. See the list below.
|
||
3. **It couples the new page to the file task 20 deletes.** `/full-guide/` would
|
||
break the moment the legacy tree goes.
|
||
|
||
**You may not read `full-guide/index.html` at build time.** Read it to learn
|
||
what to build; do not import it, scrape it, or `set:html` it. The page's markup
|
||
comes from components and content collections.
|
||
|
||
### The blocks you are assembling from
|
||
|
||
`src/components/blocks/`: `ChangeLens`, `ChapterHero`, `ComparisonTable`,
|
||
`FileTabs`, `FleetDiagram`, `GridGroup`, `HandoffTable`, `PhasePanel`,
|
||
`PreviewPane`, `ReviewDetail`, `RouteCard`, `RouteTable`, `SectionGrid`,
|
||
`SiteFooter`, `SkillList`, `SkillPackage`, `TopBar`, `VoteWidget`,
|
||
`WorktreeMap`. Plus `src/components/primitives/`. Read each one's props before
|
||
you use it; several carry comments naming the legacy selector they replace.
|
||
|
||
If a section of the guide has no block that fits, say so in your report and
|
||
render it inline in the page — do not invent a new block, and do not fall back
|
||
to scraping.
|
||
|
||
### Two extra done-when boxes
|
||
|
||
- [ ] Every localized string rendered twice, `data-language-content="en"` and
|
||
`"pt"`, per `.agents/context/content-i18n.md`
|
||
- [ ] Zero imports of any file under `full-guide/`, and no `set:html` of legacy
|
||
markup
|