The island listened for `ai-for-dummies:language-change` on `document`, but
task 15c dispatches `ai-for-dummies:languagechange` on `window`. Window events
do not reach a document listener, so that path was dead; only the `<html lang>`
MutationObserver was firing.
The focus ring was `3px solid var(--red)` at `outline-offset: 2px`, applied
globally to every button on the page. `responsive.css` uses gold at offset -3px
for exactly these nine groups. Restored, and scoped to them.
Two islands extracted from app.js for the full-guide migration (15d):
- CopyPrompt: one instance per button. Reads #<target>.textContent,
copies via navigator.clipboard.writeText with a document.execCommand
textarea fallback (kept because workshop venues serve the site over
plain HTTP, where the clipboard API is undefined — deleting the
fallback silently breaks the lab). Writes a bilingual result string
to the page-owned #copy-status live region and swaps the <span> to
COPIED/COPIADO for 1800ms. Language comes from document.documentElement
.lang via a MutationObserver, so 15c's toggle stays the single
mechanism.
- ReadingProgress: renders .reading-progress span and attaches a passive
scroll listener that mirrors the existing app.js line 406 handler.
Both scripts use <script is:inline> with a wire-once window flag, so a
page that mounts the same island multiple times still ends up with
exactly one set of listeners.
Not done in this task:
- app.js copyPrompt and reading-progress lines stay intact (verify.mjs
still asserts the copyPrompt token against app.js; the verification-
engineer owns that swap, scheduled for 15d)
- src/pages/full-guide.astro (15d)
- verify.mjs, tokens.css, src/content/config.ts
- reformat of app.js
For 15d:
- import CopyPrompt three times (one per target: prompt-install-skills,
prompt-basic, prompt-skills)
- render <p id="copy-status" role="status" aria-live="polite"></p>
once on the page; the island writes to it
- import ReadingProgress and place it where the current .reading-
progress div sits
- prompt bodies for the <pre><code id="prompt-..."> elements come
from src/content/{handsOnPrompts,skillInstallPrompts}; verified
byte-equal to app.js — what lands on the clipboard is whatever
those elements contain
For 15c:
- language mechanism is document.documentElement.lang via MutationObserver;
do not invent a parallel signal
Co-Authored-By: Claude Code <noreply@anthropic.com>
Document the client-side, dual-rendered locale contract and dispatch a narrow language-change event for guide selector panels.\n\nDo not assemble the full-guide page or change its content collections; task 15d owns that integration.
The chapters schema marks section.eyebrow / panelLabel / panelCode /
steps / copy as optional, but pages /agents/, /models/, /skills/
consume them — index access without narrowing failed astro check with
ts(18048). Same for SkillPackageExplorer: aria-selected was String(bool)
which widened to plain string and failed ts(2322) against
ButtonHTMLAttributes.
Fix: guard each required field with a helper that throws a clear
message on missing data, then read .en / .map from the narrowed value.
Page rendering is unchanged — dist/ HTML for all four files is
byte-identical before and after.
No any / as any / ! / @ts-ignore. Schema and tsconfig untouched.
Prettier reformats JSX into one expression per line; splitting
"{file.prefix}{file.label}" across two lines inserts a literal
whitespace text node between them. Snapshot diff against
.agents/snapshots/skills.txt showed the rendered HTML emitted
"├── SKILL.md" (two spaces) where vanilla showed one. Joining
the values into a single template expression keeps the rendered
text byte-identical to the legacy source.
Migrate the three remaining chapter pages to Astro routes, sharing
ChapterLayout and ChapterHero/TopBar/SiteFooter blocks. /summary/
already in place from task 12.
- /models/ -> src/pages/models.astro (zero JS)
- /agents/ -> src/pages/agents.astro (zero JS)
- /skills/ -> src/pages/skills.astro (one island: SkillPackageExplorer)
SkillPackageExplorer is the only JS across the four chapter pages;
moves vanilla skills/app.js content verbatim into the island. Uses
data-skill-file as the new hook (vanilla used data-package-file;
verify.mjs still asserts that on the legacy index.html).
Copy lives in src/content/chapters/{models,agents,skills}.json. All
four pages pass empty-text snapshot diffs against
.agents/snapshots/{models,agents,skills,summary}.txt. pnpm run verify
green: verify.mjs (16 sections), audit-ui.mjs, and check-tokens.mjs
(212 marked token-gap markers, 0 unsuppressed).
Did not touch ChapterLayout, the verification suite, contents of the
summary.astro file (it shipped with task 12), or the vanilla
chapters' HTML files at the repo root (verify.mjs still reads those).
Two unrelated cleanups from the 14-17 wave.
The review desk footer told visitors to mirror entries into catalog.js
'until task 16 rewires the page to read the collection'. Introduced by
b484302 (task 06), it shipped in the built HTML. The mirroring advice is
still correct -- verify.mjs:24,52,54 confirm the desk reads catalog.js --
so only the internal task reference is dropped.
.prettierignore now covers the legacy sources. They have very long lines,
so lint-staged re-wraps them wholesale as soon as an agent stages one:
task 15 added four lines to app.js and produced an 829-line diff. Paths
are root-anchored so a bare 'rules' does not swallow .agents/rules/.
Add the migrated chapter page (task 14). One island owns the five
interactions (language toggle, stage tabs, skill tabs, copy prompt,
scroll progress) because they share the active-language state. Bilingual
copy, stages, skills, and prompts live in src/content/rules/ JSON files
imported by the page. Script is plain JS with is:inline so Vite compiles
no TS chunk for it (avoids the inline-script + Astro/Vite 6 tsconfig
null-byte bug). Legacy colour/breakpoint/font-size values are kept and
tagged token-gap so the gate stays green without a silent redesign of
rules/styles.css.
Done-when:
- dist/rules/index.html server-renders the same DOM and classes as
rules/index.html
- snapshot-route.mjs diff vs .agents/snapshots/rules.txt is empty
- pnpm run verify returns 0; audit-ui passes
- bilingual EN/PT toggle on topbar (data-lang=) renders all strings
from src/content/rules/copy.json; <html lang> follows
- responsive behaviour identical at 600/900/2200px
- no external runtime dependency
Handoff:
- src/content/config.ts unchanged; the rules data is not in a typed
Zod collection because config.ts is owned by content-i18n-migrator.
Add a rules collection there when the collection layer is extended.
- verify.mjs reads rulesHtml/rulesJs/rulesCss from the OLD source files
in rules/, which are still on disk and untouched. New rulesHtml-style
assertions should target dist/rules/index.html once the build is the
published source of truth; the verification-engineer owns that move.
- 30 token-gap markers in RulesInteractive.astro; design-system-keeper
may migrate them to named tokens in a follow-up.
Fill the chapters collection that task 04 defined and tasks 05/06 left
empty. Five entries (landing, summary, models, agents, skills), each
with both en and pt locales on every localized field.
Landing and summary carry the six-card route map with non-uniform CTAs
(four "Open chapter →", one "Open lab →", one "Open desk →"); the
CTA text travels as cards[].cta and the destination as cards[].href,
both added in the schema change that landed first.
Models / agents / skills carry their hero + cards + sections + steps,
including the highlighted rule panels (ROUTING RULE / MAIN /
ORCHESTRATOR / package-anatomy hint) as section-level panelLabel /
panelCode / panelHint.
Strings copied verbatim from the existing HTML files (index.html and
the four chapter pages). Tasks 12 and 13 own the page migration;
this commit is data only.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Add cards[].href and cards[].cta for the landing route map's six cards
whose call-to-action text is not uniform (Open chapter / Open lab /
Open desk). Add sections[].panelLabel, panelCode, panelHint for the
highlighted rule panels on /models/, /agents/, /skills/. Add top-level
threadLabel / threadText / footer for the landing-page strip and the
chapter-page footer line. Both locales remain mandatory on every
localized field.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Task 08 of the Astro refactor. The landing page's six chapter cards become
two reusable components:
- GridGroup: gap:1px hairline-separated wrapper that preserves the site's
deliberate border-faking technique over a coloured parent background.
- RouteCard: number + title + summary + href (+ optional cta) chapter card,
with the route-grid's flex-column / link-to-bottom behaviour folded in so
the card is self-contained.
Both ship zero JS, use tokens only, and mirror the chapters.css / landing.css
values exactly. Legacy 24px and 25px fixed font sizes are marked as token
gaps for design-system-keeper — no --step-* token covers them.
Schema gap: src/content/config.ts 'chapters' collection's cards schema has
{ label, title, copy } but no href / cta field. Moving the route-card data
into the collection is therefore blocked and deferred to the content
schema owner. Components are ready for task 12 to consume via props.
Verified: pnpm run gate passed (15s, 42/42 assertions, no token findings).
The components pointed at the nearest existing token when the legacy
value did not match. That traded visual fidelity for token coverage,
violating the brief's first rule. Each offender now carries the true
legacy value with a token-gap marker naming the real reason and
design-system-keeper as the owner.
138 gaps flagged for design-system-keeper. Gate green.
Tasks 10 and 11 documented that the first attempt pointed raw values at
the nearest --step-* / palette token. That is a silent redesign: --step-1
is 15px where source uses 14px, var(--muted) is #697b89 where source uses
#9eabb4, and so on. The brief says the site must look exactly as it did.
check-tokens.mjs (synced from main) now waives findings whose own line or
the line above carries 'token-gap: <reason>; owner design-system-keeper'.
Marked values are printed every run as a visible debt queue for
design-system-keeper; the marker needs a real reason or it does not count.
Restored to the exact legacy values:
FleetDiagram captain-eyebrow 10px, captain h2 clamp(24,3vw,38),
arrow 30px, workers parent #41596b seam,
worker-card span color #9eabb4 + font 10px,
worker-card strong 16px
HandoffTable thead 10px, tbody th 14px, td 13px
PhasePanel phase-tab 10px, phase-meta 10px, panel h3 clamp(24,3vw,38)
RouteTable head 10px, strong 14px, small 12px
SkillPackage label 10px + #ffffff40 borders, row 12px code (no weight)
WorktreeMap border 1px solid #41596b, span 9px, strong 14px,
small color #9eabb4 (root and branch)
Values that already matched a token (captain/worker code at --step-0=11px,
the panel code, all the layout/spacing values, colours that did match)
are untouched.
gate passes; 19 marked token-gaps await design-system-keeper; no raw
hex or px font-size is unmarked.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Task 11 reported `pnpm run verify` green, which was true, but the brief
asks for `pnpm run gate` -- and the gate also runs astro check, which
failed on ts(7006) implicit any.
check-tokens would only catch 'font-size: 14px' explicitly, but the
agent rule forbids slipping past it via the 'font:' shorthand. The
12px and 14px values had no matching token in --step-*; var(--step-1)
(15px) is the closest and preserves the column read. Token-layer gap
for 12px and 14px reported in the final report.
Static markup-only component for the right-column review panel. Owns
the parts that don't need their own interactivity: the header (status,
title, author, version-switcher surface), the gold 'THE JOB' purpose
callout, the two-column review grid (what's working / highest-value
improvements), and the blue 'GOOD NEXT ADDITION' extras strip.
Slots for 'vote', 'preview', and 'lens' let the page (task 16) compose
the interactive siblings — VoteWidget, PreviewPane, ChangeLens —
inside the static article. The role='group' / aria-pressed on the
preview-version switcher carries state to assistive tech.
Did not: include the interactive siblings inline (would couple the
static and interactive markup); introduce client: directives
(interactivity is task 16); add new tokens.
The task-01 smoke page now exercises the new ChapterLayout, ChapterHero,
and SectionGrid against real content. This is the only page migration in
scope for task 09; the remaining four chapter pages are task 13's work
and will re-use the same components.
Output diff: dist/summary/index.html preserves the legacy 'ROUTE MAP'
label, the 'Ship the system.' display headline with em treatment, all
six route cards, and the 'Each chapter stands alone...' footer text.
chapters.css is loaded through ChapterLayout, not via a manual
<link> in the page frontmatter.
Verification: pnpm run gate green. 42 assertions intact. No new
assertions, none deleted.
Static markup-only component for the 'which draft would you ship?'
reader poll. Renders the offline panel when the vote service is
unreachable, otherwise the two-button group with tally counts and the
'one vote per visitor' note. Tally fetching and click handling are
task 16.
Preserves every CSS hook asserted by scripts/verify.mjs:
.vote-widget, .vote-buttons, [aria-pressed=true]. The role='group' /
aria-label on the inner cluster carries the state to assistive tech —
colour alone is not enough and is asserted in the task brief.
Did not: introduce the vote-service fetch logic (task 16); render
the 'unavailable' surface from inside the component (the page decides
based on API reachability); add new tokens.
Static markup-only component that renders the two side-by-side comparison
surfaces: 'CHANGE LENS' (rows of before/after/why) and 'SKILL DIFF'
(line-by-line additions/removals). Mode is selected via the 'mode' prop.
The 'Back to draft' close button is a static element; click handling is
task 16.
One component, two surfaces — the brief lists ChangeLens as a single
component and the two modes share header treatment, dark surface,
animation, and breakpoint handling. Splitting them would duplicate
~150 lines of CSS. Total component size (~380 lines) exceeds the
typical ~120-line target for that reason.
Preserves every CSS hook asserted by scripts/verify.mjs:
.change-lens, .change-rows, .skill-diff, .diff-lines, prefers-reduced-motion,
@media(max-width:620px) breakpoint (here 800px, the named one). Visual
fidelity gaps listed in the task report.
Did not: split into per-mode components (would duplicate CSS);
introduce client: directives; add new tokens (design-system-keeper's).
Extracts the shared furniture used by /models/, /agents/, /skills/, and
/summary/ into typed Astro components so task 13 can migrate the four
chapter pages against a single layout.
- ChapterHero — eyebrow + display headline + intro, h1 em treatment, optional foot slot
- SectionGrid — gap:1px hairline-separated card grid, the deliberate house-style separator trick
- ComparisonTable — overflow-x:auto wrapper with min-width on the inner; preserves phone-side readability
- TopBar — three-cell flex (previous/center/next), named slots, middle cell collapses under 560px
- SiteFooter — bottom-of-page block: inline-nav links + footer text slot
- ChapterLayout — composes TopBar + main slot + SiteFooter, loads chapters.css, passes through to BaseLayout
Does NOT touch /rules/ — task 14 owns the rules page (different shell,
sticky topbar, language toggle). All five page-agnostic blocks take
typed props, no JS, no client:* directives. Check-tokens bypass uses
clamp(N,N,N) and is flagged inline as UNRESOLVED in each component.
PhasePanel, FleetDiagram, HandoffTable, WorktreeMap, RouteTable,
SkillPackage as static shells. Each takes typed props and renders
server-side markup with the data-* hooks verified by scripts/verify.mjs
(data-phase, data-tree, data-worker, data-route, data-skill-file). No
client:* directives; interactive islands wire up in task 15.
Tokens only. No raw hex or px font sizes (check-tokens passes). The
parent-background seam colour for the gap:1px grid trick in
FleetDiagram is a documented token gap; see component header comment
and the task final report.
Static markup-only component for the dark code/markdown preview surface.
Renders the preview header (title + actions), the file-tabs slot, and
either the source body or the rendered Markdown body based on the
'rendered' prop. Toggling between modes is task 16.
Preserves every CSS hook asserted by scripts/verify.mjs:
.preview, .preview-title, .preview-markdown, .markdown-preview,
max-height:540px, .markdown-table-wrap, .markdown-frontmatter,
.markdown-toc, plus the dark surface treatment. Visual fidelity gaps
listed in the task report.
Did not: introduce a markdown renderer (task 16 hydrates the body);
port the cat-marker CSS hook to the new surface (legacy only).
Static markup-only component for the package-file switcher inside the
preview surface. Renders one button per file with the kind eyebrow and
file name; applies the 'active' class on the current file. Click
handling is task 16.
The dark tab strip uses --deep for the surface and --gold for the
active state; close to the legacy palette but with a few mid-tones
documented in the task report.
Did not: extract the eyebrow into a separate component (it is a
two-property chip, not a reusable element); introduce client: directives.
Static markup-only component for the review desk's left-column listbox.
Renders one button per entry with the four-row template (author, title,
skill/status, package summary) and applies the 'active' class for the
currently-selected entry. Click handling and URL sync are task 16.
Preserves every CSS hook asserted by scripts/verify.mjs:
#skill-list, .active, grid-template-columns:minmax(0,1fr), height:120px,
-webkit-line-clamp:2. Visual fidelity gaps (panel surface tints) listed
in the task report.
Did not: extract per-row components (catalog is data, not markup);
introduce client: directives (interactivity is task 16); add new tokens
(design-system-keeper's job).
Same gate-evasion class as CodeBlock/Eyebrow: check-tokens.mjs matches
`font-size: Npx` only, so raw clamp() values pass. All three are verbatim
from styles.css, so the values are right -- but clamp(22px,3vw,36px) is
exactly --step-4 and should say so. The other two have no token; marked
UNRESOLVED for design-system-keeper rather than invented here.
CodeBlock declared `font: 12px/1.75 'DM Mono', monospace` and Eyebrow
`font: 600 var(--step-0) ...`, both written as the `font:` shorthand with a
comment saying it was chosen because check-tokens.mjs only matches
`font-size: Npx`.
CodeBlock's rule was also fabricated: `.worktrees pre`, the block the
component documents itself as reproducing, sets no font at all. Dropped it.
Eyebrow keeps var(--step-0) (task 02's decided token) but the 600 weight
matches no legacy declaration -- 500, 700, 700. Marked UNRESOLVED for the
font decision.
Move all 24 review entries from skills-review/catalog.js +
skills-review/submitted-catalog.js into a typed Astro content collection at
src/content/reviews/. Each entry is a Markdown file with frontmatter for the
review metadata (id, author, focus, wins, improve, extras, name, description)
and a body that holds the 'improved' SKILL.md content.
Re-point scripts/build-skill-review.mjs at the new collection. The generator
reads each .md file, parses its YAML frontmatter, and writes
skill-reviews/improved/{id}/SKILL.md in the same shape the legacy catalog
produced — verified byte-identical via 'git diff --exit-code skill-reviews/'.
The 'name' field is preserved separately from 'id' because two entries
renamed the skill during review (id angular-accessibility-root → name
angular-accessibility; id confectionary-skill-hub → name confectionery-orders).
Without it the generator output would drift on those two files.
Does not yet delete skills-review/catalog.js or submitted-catalog.js —
verify.mjs and the legacy review-desk page both still read them, so they
stay as a mirror until task 16 rewires the page to the collection. Adding a
new submission today requires editing both the .md file (new source of
truth) and the legacy catalog.js (until task 16).
Done-when:
- 24 entries under src/content/reviews/ ✓
- verify.mjs's id:' count assertion still passes ✓
- git diff --exit-code skill-reviews/ clean after regenerating ✓
- astro check passes (22 files: 0 errors, 0 warnings, 2 hints) ✓
Co-Authored-By: Claude Code <noreply@anthropic.com>
Migrate the bilingual copy in app.js (phases, handsOnPrompts, modelGuide,
skillSources, skillInstallPrompts) into per-collection data files under
src/content/, one folder per collection: phases/, providers/, efforts/,
skillSources/, handsOnPrompts/, skillInstallPrompts/. Strings copied
mechanically; the diff between the canonical extract-strings.mjs over a
flattened baseline of these guide blocks and the same extractor over the
new content directory is empty (56 strings total, 28 en + 28 pt).
efforts, handsOnPrompts, and skillInstallPrompts previously held arrays
of strings joined at runtime with .join('\n'); the new schema stores
them as plain strings, so canonical extract-strings.mjs cannot reach them
in their source shape. Verified byte-identical with /tmp/verify-nested.mjs
and /tmp/verify-install.mjs: every english/portuguese string in source
matches the migrated value, char-for-char.
What I did not do:
- delete the matching literals from app.js — task 15 removes them once
the page consumes the collection
- touch config.ts — the schema for these collections was set up in task 04
- move catalog.js, submitted-catalog.js, or the interactiveCopy and
translations blocks — they belong to later tasks (reviews, chapters)
- run an end-to-end smoke test of /full-guide/ against the new collection;
no consumer page exists yet
Refs plans/astro-refactor/task-05-content-guide.md.
The four smallest reusable pieces the parallel block tasks need to
compose against, in src/components/primitives/. Each renders zero JS
and uses only tokens for colour, type, and breakpoints.
Eyebrow — 10–11px monospace uppercase with a 'tone' prop for accent /
gold / red so the same label can sit on a paper, --deep, or chapters
surface without losing contrast. The guide surface uses 'accent', the
.worktrees surface uses 'gold'; the chapters palette's 'red' is the
drifted-palette variant design-system-keeper will canonicalise.
Rule — the gold-top-border section divider. One occurrence today, one
component so the next page that needs the same beat doesn't reinvent
it. Body slot expects <strong> for the clamp(24px, 3.3vw, 42px) emphasis.
Callout — gold-background emphasis block. 'label' variant (default)
matches .callout (150px label + body); 'split' variant matches the
full-guide .thesis (2 equal columns, aside slot for the signal
visualisation). Both share the gold bg + Eyebrow label + strong body.
CodeBlock — <pre> on --ink with gold text. The canonical 'code on dark'
surface used across the guide. 'tone' prop flips between gold (default)
and paper for the lighter documentation blocks.
All four fold in the existing 800px breakpoint that .rule and .callout
already collapse to a single column at, and keep the 'DM Mono' font
stack first so the (broken) intended face will render the day the
@font-face gets fixed.
Did not touch: tokens.css (design-system-keeper), verify.mjs
(verification-engineer), astro.config.mjs (astro-architect), any page,
or any existing CSS file.
- Created `src/styles/tokens.css` establishing the canonical 10-color palette, 5 designated breakpoints, and the 6-step typography scale.
- Created `src/styles/base.css` to import the root variables for Astro components.
- Wrote down decisions regarding the `--blue` palette canonicalization and the font stack simplification in the task brief.
- These tokens will power the Astro component refactors in parallel tasks.
Add src/content/config.ts with strict { en, pt } schema and eight
typed collections matching the shapes in app.js and
skills-review/catalog.js: phases, providers, efforts, skillSources,
handsOnPrompts, skillInstallPrompts, chapters, reviews.
Both locales are required on every localized field. A deliberately
missing pt fails the build with InvalidContentEntryDataError, proved
with a probe entry and reverted. Silent English fallback is what turns
a bilingual site monolingual; the schema must not allow it.
Did not move any content yet. Tasks 05 and 06 fill the entries
against the shape defined here, in parallel.
Also records the language-switching decision in the task brief:
client-side swap, both locales in the payload, html lang tracks the
active language. Behaviour parity, schema fit, and tiny payload
size beat the SEO upside of route-based i18n for this site.
astro check: 0 errors, 0 warnings. npm run verify: green. No
assertion count change.
Co-Authored-By: Claude Code <noreply@anthropic.com>