Attempt 2 got the structure right and said plainly it had only done the hero, stat and thesis regions in both locales. The remaining static prose is the 102-entry `translations.pt` selector map in app.js, which is what gives today's /full-guide/ its Portuguese. Also records that the 55 `.en` reads in the selector detail panels are correct and must not be changed: GuideSelector re-renders those per locale on languagechange, so the server render only needs the initial locale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 KiB
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 forhands-on/rules/.renderHandsOntakes 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.csshere. 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 gategreen — the full gate, notverify+audit-uialone- 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={...} />.dataneedsphases,workers,trees,routes,providers,efforts,skillFiles,skillWorkflow,commonSkillsand a bilinguallabelsobject; theGuideSelectorDatainterface at the top of the file is the exact shape. You server-render each group's shell and its initial detail panel, keeping today'sdata-*hooks and.activestate. Mark each grouprole="tablist"and its controlsrole="tab"or the keyboard handler will not bind.src/components/islands/LanguageToggle.astro— render each localized fragment twice, withdata-language-content="en"or"pt"on the outer element. The toggle flipshiddenon those, sets<html lang>, and firesai-for-dummies:languagechangeonwindow. It is a plain.astroisland that defers its own setup withrequestIdleCallback; do not putclient:idleon it — that directive is framework-components only.src/components/islands/CopyPrompt.astro— one per button. Passtarget="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 thehandsOnPromptsandskillInstallPromptscollections — 15b verified those are byte-identical to the legacyapp.jsconstants, 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. renderHandsOntakes no argument and is not part of GuideSelector's nine-group pattern. It is yours. Its name is asserted byverify.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].statusis in the schema althoughGuideSelectorDataomits it.index.htmlrenders it as the dot colour on each tree node. The island does not consume it; the page may.commonSkills[id].sourceis embedded per entry rather than joined fromskillSources[id].urlat 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:
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:
- Portuguese is gone. The page contains zero
data-language-contentattributes and zero.ptreads — every server-rendered detail panel hard-codes.en. The legacy<main>is English-only; today's Portuguese comes fromapp.js, which the Astro page does not load. The language contract in.agents/context/content-i18n.mdis binding and this violated it. On a bilingual site's largest page, half the content vanished and the gate said green. - Zero of task 10's block components are used. All 19 exist in
src/components/blocks/. See the list below. - 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 noset:htmlof legacy markup
Attempt 2: structure accepted, bilingual work unfinished
Commit 677c511 is the right shape and is the base to build on — no legacy
import, no set:html, six blocks used (FleetDiagram, HandoffTable,
PhasePanel, RouteTable, SkillPackage, WorktreeMap), one file changed,
verify.mjs untouched, 42 assertions, gate green, JS 47,079 B against the
legacy 50,338 B. Its report was honest about what it did not finish. Finish it.
What is already correct — do not "fix" it. The page has 55 .en reads and
zero .pt reads in the selector detail panels. That is right.
GuideSelector.astro re-renders every panel with [locale] on
ai-for-dummies:languagechange, so the server-rendered panel only has to match
the initial locale. Leave those alone.
What is missing: the static prose. Legacy app.js holds translations.pt —
a map of 102 CSS-selector → Portuguese-string entries, starting at
.chapter-links a:nth-child(1). applyLanguage('pt') walks it and calls
setText(selector, value); switching back replays the captured originals.
That map is the full-guide page's static Portuguese, and it is the authoritative
source for this work.
The page currently carries 3 data-language-content pairs (hero, stat,
thesis). The other ~99 strings have no Portuguese counterpart anywhere in the
Astro output, so /full-guide/ renders English-only for everything the selector
islands do not own.
Render each of those 102 strings twice per .agents/context/content-i18n.md:
the English exactly as it appears in full-guide/index.html today, the
Portuguese exactly as it appears in translations.pt. Verbatim both ways — no
retranslation, no rephrasing, no fixing what looks like a typo.
A selector in the map that targets an element the blocks now render means the pair belongs inside that block's slot content, not bolted on afterwards. If a block gives you no way to pass both locales, say so in the report and name the block — do not work around it by duplicating the block.
Done when, for this pass
- All 102
translations.ptentries have a rendered Portuguese counterpart - Every localized static string wrapped in
data-language-content="en"/"pt"pairs - No
.ptreads added to the nine selector detail panels - Gate green, 42 assertions,
verify.mjsuntouched - Report lists any
translations.ptselector you could not place, and why