# 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"`, ``. 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 ``. `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 ``, 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 `

` exactly once on the page; the island writes into it. Fill the `

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