Files
ai-for-dummies/plans/astro-refactor/task-15d-page-full-guide.md
T
Marcos Paulo 98ab6db3de
verify-and-publish / gate (push) Successful in 6m14s
verify-and-publish / publish (push) Has been skipped
docs: record why task 15d attempt 1 was rejected
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>
2026-09-05 19:58:51 +00:00

8.8 KiB
Raw Blame History

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:

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