diff --git a/.agents/scripts/rendered-text-diff.mjs b/.agents/scripts/rendered-text-diff.mjs new file mode 100644 index 0000000..6e239e4 --- /dev/null +++ b/.agents/scripts/rendered-text-diff.mjs @@ -0,0 +1,181 @@ +#!/usr/bin/env node +// Compare the *rendered* text of a legacy page against its Astro replacement. +// +// node .agents/scripts/rendered-text-diff.mjs full-guide +// node .agents/scripts/rendered-text-diff.mjs full-guide --pt +// +// Why this exists: scripts/verify.mjs reads the legacy files, so a migrated +// page can drop half its content and still pass the gate. Task 15d shipped +// /full-guide/ missing 86 rendered spans -- the entire verification section, +// the hands-on exercise brief, and both "Clone from Gitea" links -- and every +// check was green. +// +// Static HTML comparison is useless here: the guide's tab panels are injected +// by an island at runtime, so half the legacy page's markup has no static +// counterpart. This walks the live DOM instead and skips anything the browser +// is not painting -- which also drops the hidden Portuguese half of each +// bilingual pair, so the two sides line up. +// +// Requires playwright (devDependency) and two static servers; it starts both. +import { spawn } from 'node:child_process'; +import { cpSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { createServer } from 'node:net'; +import { chromium } from 'playwright'; + +// `index` is the landing page: it lives at the repository root, not in a +// directory of its own, so it needs a different path on the legacy side. +const route = process.argv[2]; +if (!route) { + console.error('usage: rendered-text-diff.mjs [--pt] e.g. full-guide, or index'); + process.exit(2); +} + +// `--pt` clicks the language toggle on both pages first. English parity is +// only half the contract: a page can render every English string and still +// leave a restored block untranslated, because the Portuguese half is a +// separate set of nodes. Only /full-guide/ and /rules/ have a toggle. +const portuguese = process.argv.includes('--pt'); +const legacyPath = route === 'index' ? 'index.html' : `${route}/index.html`; +const astroPath = route === 'index' ? '' : `${route}/`; + +// The built site expects to be served under the configured base path. +const staging = mkdtempSync(join(tmpdir(), 'af-rtd-')); +cpSync('dist', join(staging, 'ai-for-dummies'), { recursive: true }); + +// Ask the kernel for a free port rather than pinning one. Back-to-back runs +// used to collide: the previous run's server was still holding the fixed port +// while its staging directory had already been deleted, so every page came +// back as a 404 and the diff reported the whole route missing. +const freePort = () => + new Promise((resolve, reject) => { + const probe = createServer(); + probe.on('error', reject); + probe.listen(0, '127.0.0.1', () => { + const { port } = probe.address(); + probe.close(() => resolve(port)); + }); + }); + +const legacyPort = await freePort(); +const astroPort = await freePort(); + +const serve = (dir, port) => + spawn('python3', ['-m', 'http.server', String(port), '-d', dir], { stdio: 'ignore' }); +const servers = [serve('.', legacyPort), serve(staging, astroPort)]; +const stop = () => { + servers.forEach((s) => s.kill()); + rmSync(staging, { recursive: true, force: true }); +}; + +// Visible text nodes, in document order, whitespace collapsed. +const visibleText = () => { + const out = []; + const walk = (node) => { + for (const child of node.childNodes) { + if (child.nodeType === Node.TEXT_NODE) { + const text = child.textContent.replace(/\s+/g, ' ').trim(); + if (text) out.push(text); + continue; + } + if (child.nodeType !== Node.ELEMENT_NODE) continue; + if (child.tagName === 'SCRIPT' || child.tagName === 'STYLE') continue; + const style = getComputedStyle(child); + if (child.hidden || style.display === 'none' || style.visibility === 'hidden') continue; + walk(child); + } + }; + walk(document.body); + return out; +}; + +try { + await new Promise((r) => setTimeout(r, 1500)); + const browser = await chromium.launch(); + const grab = async (url) => { + const page = await browser.newPage({ viewport: { width: 1400, height: 1000 } }); + const response = await page.goto(url, { waitUntil: 'load' }); + // A 404 renders as four spans of python's error page and the diff then + // reports the entire route as missing, which reads exactly like a real + // regression. Fail loudly instead. + if (!response || !response.ok()) { + throw new Error(`${url} returned ${response ? response.status() : 'no response'}`); + } + // The islands hydrate and render their initial panel on load; without this + // every panel's copy reads as missing. + await page.waitForTimeout(1200); + if (portuguese) { + const toggle = await page.$('[data-lang="pt"]'); + if (!toggle) throw new Error(`no language toggle on ${url}`); + await toggle.click(); + await page.waitForTimeout(1200); + } + // Islands hydrate at their own pace, and the language toggle repaints in + // more than one frame. A single read after a fixed wait is flaky, so read + // until two consecutive reads agree. + let spans = await page.evaluate(visibleText); + for (let i = 0; i < 10; i += 1) { + await page.waitForTimeout(300); + const next = await page.evaluate(visibleText); + if (next.length === spans.length && next.every((span, j) => span === spans[j])) { + spans = next; + break; + } + spans = next; + } + await page.close(); + return spans; + }; + + const legacy = await grab(`http://localhost:${legacyPort}/${legacyPath}`); + const astro = await grab(`http://localhost:${astroPort}/ai-for-dummies/${astroPath}`); + await browser.close(); + + // Count occurrences, not membership. A set comparison reports zero when a + // string the legacy page paints four times is painted three times here -- + // exactly the kind of near-miss that got past the earlier checks. + const tally = (spans) => { + const counts = new Map(); + for (const span of spans) counts.set(span, (counts.get(span) || 0) + 1); + return counts; + }; + + const legacyCounts = tally(legacy); + const astroCounts = tally(astro); + + const missing = []; + for (const [span, count] of legacyCounts) { + const short = count - (astroCounts.get(span) || 0); + for (let i = 0; i < short; i += 1) missing.push(span); + } + + // Both directions. A string the Astro page paints and the legacy page does + // not is just as wrong: it means a translation was invented, or an English + // string was left standing where the legacy page swaps it. + const extra = []; + for (const [span, count] of astroCounts) { + const over = count - (legacyCounts.get(span) || 0); + for (let i = 0; i < over; i += 1) extra.push(span); + } + + // Order counts too. Both pages can paint the same strings while a block + // sits in the wrong place -- the Portuguese eyebrow, or a reordered card + // deck -- and a count-only comparison calls that clean. + const firstOutOfOrder = legacy.findIndex((span, i) => astro[i] !== span); + + const mode = portuguese ? 'pt' : 'en'; + console.log( + `${mode} · legacy ${legacy.length} spans · astro ${astro.length} spans · missing ${missing.length} · extra ${extra.length}`, + ); + for (const span of missing) console.log(` - ${span}`); + for (const span of extra) console.log(` + ${span}`); + if (firstOutOfOrder !== -1) { + console.log(` order diverges at span ${firstOutOfOrder}`); + console.log(` legacy: ${legacy[firstOutOfOrder]}`); + console.log(` astro: ${astro[firstOutOfOrder]}`); + } + process.exitCode = missing.length === 0 && extra.length === 0 && firstOutOfOrder === -1 ? 0 : 1; +} finally { + stop(); +} diff --git a/plans/astro-refactor/task-15f-full-guide-restore.md b/plans/astro-refactor/task-15f-full-guide-restore.md new file mode 100644 index 0000000..6575b52 --- /dev/null +++ b/plans/astro-refactor/task-15f-full-guide-restore.md @@ -0,0 +1,293 @@ +# Task 15f — Restore the content 15d dropped from /full-guide/ + +**Agent**: `page-migrator` · **Model**: Codex **Depends on**: 15d **Blocks**: +19, 20 **Worktree**: `.agents/scripts/worktree.sh start 15f full-guide-restore` + +## Goal + +`/full-guide/` is missing about a fifth of the page. Put it back, in the right +place, bilingual where the legacy page is bilingual. + +Task 15d assembled the page and passed every check. It also dropped 87 rendered +text spans. One of them — the `.chapter-route` section, the only route out of +the guide to the summary, models, agents and skills chapters — has already been +restored (`c2b35d5`). The remaining 86 are your task. + +Nothing caught this. `scripts/verify.mjs` reads `full-guide/index.html`, the +legacy file, which still has every one of these sections. The gate was green the +whole time. Task 19 is blocked on you: it re-pointed its assertions at `dist/` +and they now fail, correctly, on exactly this content. + +## How to see the gap + +``` +pnpm run build +node .agents/scripts/rendered-text-diff.mjs full-guide +``` + +It walks the live DOM of both pages and prints the text the legacy page paints +and the Astro page does not. It skips hidden nodes, so the Portuguese half of +each bilingual pair does not register as a difference, and it waits for the +islands to hydrate, so the tab panels' injected copy counts as present. + +**This script reaching zero is the task.** Do not edit it to make it pass. + +## What is missing + +Whole blocks, not scattered strings. By CSS class, present in +`full-guide/index.html` and absent from `dist/full-guide/index.html`: + +`verify-intro`, `verify-cta`, `verify-cta-grid`, `verify-card`, +`verify-card-source`, `verify-antipatterns`, `ap-grid`, `comparison-strip`, +`exercise-brief`, `builder-intro`, `builder-loop`, `builder-artifact`, +`artifact-head`, `artifact-command`, `starter-link-group`, `starter-link-source` + +That covers, at minimum: the three-layer verification section and its code +lines, the "four ways a green report is false" grid, the two hands-on lab cards +with both "Clone from Gitea →" links, the exercise brief (stack / dependencies / +files), the four-row comparison strip, and the skill-forge output package tree +with its validate and after-real-use panels. + +The exact 86 spans, in document order: + +``` + - default start + - name: review-ui · check focus, mobile, reduced motion · run verification · return evidence + - The skill forge + - Teach the decision. + - Keep the context + - light. + - Do not package everything you know. Capture the non-obvious choices that repeatedly improve an outcome, then prove the skill changes behavior. + - Observe + - find repeated friction + - Define trigger + - route precisely + - Choose anatomy + - only needed files + - Write guidance + - decisions, not trivia + - Validate + - test real behavior + - OUTPUT / SKILL PACKAGE + - review-ui/ + - ├── SKILL.md + - ├── agents/ + - │ └── openai.yaml + - ├── references/ + - │ └── accessibility.md + - └── scripts/ + - └── verify.mjs + - VALIDATE + - quick_validate.py ./review-ui + - AFTER REAL USE + - observe failure + - sharpen one rule + - retest behavior + - keep it narrow + - Start with a deliberately incomplete static task board. Run one prompt as written, reset, then run the skill-enabled version. Compare diff size, verification evidence, and unnecessary complexity. + - Clone from Gitea → + - Clone from Gitea → + - THE MISSING FEATURE + - Add All / Open / Done filters that survive reload and browser navigation. + - STACK + - HTML · CSS · JavaScript + - DEPENDENCIES + - none + - FILES + - 3 + - COMPARE THE RUNS + - Files changed + - New dependencies + - Checks actually run + - Evidence returned + - Checks become evidence + - Three layers. + - Run each one alone. + - Run a gate on its own line, print its exit code, attach the output. The result is the deliverable. + - Format, lint, type-check. Fast and scoped to one file. Run on every save. + - pnpm lint; echo "lint=$?" pnpm typecheck; echo "typecheck=$?" + - pnpm test; echo "test=$?" cd services/api && go test ./... + - Drive the actual UI, API, or browser. Slower and flakier — only this catches mobile overflow and a missing 404. + - pnpm check:ui; echo "ui=$?" TURBO_FORCE=true pnpm e2e + - FOUR WAYS A GREEN REPORT IS FALSE + - 1 + - Pipe a gate + - tail, grep, or head hide the real exit code — a pipeline returns the last command's status. + - 2 + - Swallow a rejection + - A silent + - .catch(() => {}) + - hides a panic, an upstream limit, or a partial failure. + - 3 + - Trust the cache + - Turbo caches results. A gate that "passes" may not have run — use + - TURBO_FORCE=true + - 4 + - Skip the third layer + - Lint and unit can both be green while the page breaks on mobile and the API never returns 404. + - RUN IT YOURSELF · two labs, under 10 minutes each + - Path A · verification lab + - Fill the four-row comparison strip on the starter. Run A naively, Run B with + - $gate-discipline + - and + - $webapp-testing + - Clone ↗ + - git.marcospaulo.dev.br/.../src/branch/pages/hands-on/starter + - Path B · rules lab + - Toggle every rule off, run the prompt. Toggle every rule on, run it again. Compare diff size, gate invocations, and the names of checks the agent names back. + - Clone ↗ + - git.marcospaulo.dev.br/.../src/branch/pages/hands-on/rules +``` + +## Method + +1. Read the legacy source for each block out of `full-guide/index.html`. Copy + the strings; do not retype them. Several contain box-drawing characters + (`├──`, `└──`), `·` separators, and `$`-prefixed skill names. +2. Place each block where the legacy page has it — the section order is part of + the argument the page is making. +3. Bilingual pairs follow `.agents/rules/content-i18n.md`: render the fragment + twice, `data-language-content="en"` visible and `data-language-content="pt"` + hidden. **Check `translations.pt` in `app.js` before assuming a block is + bilingual.** Several of these are English-only on the live site — + `.chapter-route` was — and inventing Portuguese for them is a regression in + the other direction. +4. Reuse the existing blocks in `src/components/blocks/`. If a block does not + exist, this is assembly work that 15d should have done and you may write the + markup inline in the page, as 15d did elsewhere. Do not write a new island. + +## Do not + +- Do not touch `full-guide/index.html`, `app.js`, or any other legacy file. +- Do not weaken or delete an assertion in `scripts/verify.mjs`. +- Do not reformat files you are not restoring content into. 15d ran prettier + across the whole repo on one attempt and it had to be reverted. + +## Done when + +- [ ] `node .agents/scripts/rendered-text-diff.mjs full-guide` reports 0 missing +- [ ] Every restored bilingual block has both `en` and `pt`; every English-only + block is English-only in `translations.pt` too, and you say which is which +- [ ] Section order matches the legacy page +- [ ] `pnpm run gate` green + +## Attempt 1: English is exact, Portuguese is not + +`8f82304`, tagged `rejected/15f-attempt-1`. Keep it and continue from it — the +English restoration is correct and complete: + +``` +en · legacy 432 spans · astro 432 spans · missing 0 · extra 0 +``` + +Every block is back, in the legacy order, with no invented content. That half of +the task is done. + +The Portuguese half was not checked, because the tool could not check it when +you started. `rendered-text-diff.mjs` now takes `--pt`: it clicks the language +toggle on both pages before reading, and it reports **both** directions — a +string the Astro page paints and the legacy page does not is as wrong as one it +drops. On your branch: + +``` +pt · legacy 431 spans · astro 431 spans · missing 27 · extra 26 +``` + +The totals match because nothing is structurally missing. The content is English +where the legacy page shows Portuguese. + +### Two separate causes — fix both + +**1. The blocks you restored are English-only under PT.** "Create a skill", +"INSTALL PACK", "Hands-on", "Tiny Tasks lab", "Good prompt", both long lab +prompts, the two `Clone from Gitea →` links and the rest of the hands-on and +skill-forge copy. The Portuguese for these is in `translations.pt` in `app.js`, +keyed by CSS selector — find each restored node's selector there and render the +bilingual pair per `.agents/rules/content-i18n.md`. + +Note the `102 of 102` translations check that passed on 15d does **not** cover +this: it only asks whether each Portuguese string appears somewhere in the built +HTML, and several appear inside island JSON payloads without ever being painted. +`--pt` is the real check. + +**2. Pre-existing over-translation in the hero, from 15d, not from you.** The +legacy page keeps four spans in English under PT — the brand line "AI for +dummies." and the "THINK" / "MAKE" labels. The Astro page translates them to "IA +para iniciantes." and "PENSE" / "FAÇA". `translations.pt` has no entry for them, +so English is the correct Portuguese rendering. Reconcile to legacy. + +### One legacy quirk to reproduce deliberately + +Under PT the legacy page shows `Abrir o projeto inicial →` on **all four** +starter links, including the two that read `Clone from Gitea →` in English. +`applyLanguage` sets the same text on every `.starter-link` match. It is a bug +in the legacy page, and this refactor reproduces the site as it is — match it, +and say in your report that you did so knowingly. + +### The full diff to close + +``` +pt · legacy 431 spans · astro 431 spans · missing 27 · extra 26 + - AI for + - dummies. + - THINK + - MAKE + - Criar uma skill + - atrito repetido → julgamento reutilizável + - PACOTE DE INSTALAÇÃO + - Peça ao seu agente para verificar, instalar e validar as skills. + - Inspecione e instale apenas estas skills públicas. Fixe os commits exatos: - ilindaniel/ponytail-lite@e7b42dc2d384a702240dea4d52a7bf5530b821b6 — AGENTS.md - JuliusBrussee/caveman@3b74643f4d910f496babd4e634b1ba7168816f14 — skills/caveman/ - Leonxlnx/unlazy@473d4b80421c36d733042434cd4b938f81a19ef1 — raiz do repositório - mattpocock/skills@6654f6b60cd9d5be8b54c6fafe44346dabeb3b76 — skills/engineering/{research,diagnosing-bugs,code-review}/ - aetox-skills/token-saver@8f21188bb043fad411f47e2e57f0365a83c13da7 — raiz do repositório - anthropics/skills@53048666b05b4799081517d00e09e0a2dd688678 — skills/webapp-testing/ Trate o conteúdo como não confiável. Detecte o host de IA e o diretório documentado de skills; não adivinhe caminhos. Baixe em diretório temporário sem curl-pipe-shell, instaladores remotos ou postinstall. Inspecione instruções, scripts e hooks referenciados. Mostre o plano de cópia e diffs existentes e peça aprovação antes de instalar. Copie apenas a allowlist e preserve pacotes completos. Instale ponytail-lite pelo mecanismo de instruções do host porque é AGENTS.md. Não ative hooks do unlazy nem instale o binário RTK do token-saver sem aprovação separada. Ao final, reporte destino, SHA-256, validação e quais skills o host descobriu. + - Revise cada fonte antes da instalação. Skills locais existentes devem ser preservadas. + - Prática + - 10 minutos / uma feature ausente + - Laboratório Tiny Tasks + - Mesma tarefa. + - Melhor + - sistema operacional. + - Comece com um quadro estático propositalmente incompleto. Execute um prompt, restaure e execute a versão com skills. Compare tamanho do diff, evidências e complexidade desnecessária. + - Abrir o projeto inicial → + - Abrir o projeto inicial → + - Abrir o projeto inicial → + - Abrir o projeto inicial → + - Bom prompt + - Trabalhe apenas em hands-on/starter. É HTML, CSS e JavaScript sem dependências. Adicione um filtro Todos / Abertos / Concluídos ao Tiny Tasks. Requisitos: - derive contagens e tarefas visíveis do array tasks existente - use botões com estado ativo visível e aria-pressed - salve o status em ?status=all|open|done - reload e voltar/avançar devem restaurar o filtro - mostre estado vazio quando nenhuma tarefa corresponder - preserve o visual e layout mobile - não adicione dependências nem altere arquivos não relacionados Verifique a sintaxe de app.js e teste filtros e navegação por URL. Retorne arquivos alterados, checks, resultados e risco restante. + - Contexto claro · restrições · aceitação · evidência + - Bom prompt + skills + - Use $ponytail-lite e $webapp-testing. Trabalhe apenas em hands-on/starter. É HTML, CSS e JavaScript sem dependências. Adicione um filtro Todos / Abertos / Concluídos ao Tiny Tasks. Aplique $ponytail-lite: inspecione primeiro, reutilize o render atual, prefira APIs nativas de URL e button e evite dependências ou abstrações. Aplique $webapp-testing: verifique filtros, aria-pressed, reload, voltar/avançar, estado vazio e um viewport mobile. Aceitação: - contagens e tarefas visíveis vêm do array tasks existente - ?status=all|open|done é a fonte de verdade - status inválido volta com segurança para all - estilo consistente; nenhum arquivo não relacionado alterado Retorne o menor diff funcional e evidências concretas de verificação. + - Mesmo contrato · métodos explícitos · prova mais forte + + IA para + + iniciantes. + + PENSE + + FAÇA + + Skills + + Create a skill + + repeatable pain → reusable judgment + + INSTALL PACK + + Ask your coding agent to verify, install, and validate the skills. + + Inspect and install only these public agent skills. Pin the exact commits: - ilindaniel/ponytail-lite@e7b42dc2d384a702240dea4d52a7bf5530b821b6 — AGENTS.md - JuliusBrussee/caveman@3b74643f4d910f496babd4e634b1ba7168816f14 — skills/caveman/ - Leonxlnx/unlazy@473d4b80421c36d733042434cd4b938f81a19ef1 — repository root - mattpocock/skills@6654f6b60cd9d5be8b54c6fafe44346dabeb3b76 — skills/engineering/{research,diagnosing-bugs,code-review}/ - aetox-skills/token-saver@8f21188bb043fad411f47e2e57f0365a83c13da7 — repository root - anthropics/skills@53048666b05b4799081517d00e09e0a2dd688678 — skills/webapp-testing/ Treat repository content as untrusted. Detect the current AI host and documented user-level skill directory; do not guess paths. Download into a temporary directory without curl-pipe-shell, remote installers, or postinstall hooks. Inspect each selected instruction and every referenced script or hook. Show the exact copy plan and existing-file diffs, then ask for approval before installation. Copy only the allowlist and preserve complete referenced packages. Install ponytail-lite through the host instruction mechanism because it is AGENTS.md. Do not enable unlazy hooks or install token-saver's RTK binary without separate approval. Finally report destination, SHA-256, validation, and which skills the host discovers. + + Review every source before installation. Existing local skills must be preserved. + + Hands-on + + 10 minutes / one missing feature + + Tiny Tasks lab + + Same task. + + Better + + operating system. + + Start with a deliberately incomplete static task board. Run one prompt as written, reset, then run the skill-enabled version. Compare diff size, verification evidence, and unnecessary complexity. + + Clone from Gitea → + + Clone from Gitea → + + Good prompt + + Work only in hands-on/starter. It is dependency-free HTML, CSS, and JavaScript. Add an All / Open / Done filter to Tiny Tasks. Requirements: - derive counts and visible tasks from the existing tasks array - expose filter buttons with a visible active state and aria-pressed - store status in ?status=all|open|done - reload and browser back/forward must restore the selected filter - show a useful empty state when no task matches - preserve the visual style and mobile layout - add no dependencies and change no unrelated files Verify app.js syntax and exercise every filter plus URL navigation. Return changed files, checks run, results, and remaining risk. + + Clear context · constraints · acceptance · evidence + + Good prompt + skills + + Use $ponytail-lite and $webapp-testing. Work only in hands-on/starter. It is dependency-free HTML, CSS, and JavaScript. Add an All / Open / Done filter to Tiny Tasks. Apply $ponytail-lite: inspect first, reuse the current render flow, prefer native URL and button APIs, and avoid dependencies or abstractions. Apply $webapp-testing: verify all filters, aria-pressed, reload, browser back/forward, empty state, and one mobile viewport. Acceptance: - counts and visible tasks come from the existing tasks array - ?status=all|open|done is the source of truth - invalid status falls back safely to all - style remains consistent; unrelated files remain untouched Return the smallest working diff and concrete verification evidence. + + Same contract · explicit working methods · stronger proof +``` + +## Done when (attempt 2) + +- [ ] `node .agents/scripts/rendered-text-diff.mjs full-guide` → + `missing 0 · extra 0` +- [ ] `node .agents/scripts/rendered-text-diff.mjs full-guide --pt` → + `missing 0 · extra 0` +- [ ] No legacy file touched; no assertion in `scripts/verify.mjs` touched +- [ ] `pnpm run gate` green diff --git a/src/components/islands/CopyPrompt.astro b/src/components/islands/CopyPrompt.astro index 5d49f74..be52ea4 100644 --- a/src/components/islands/CopyPrompt.astro +++ b/src/components/islands/CopyPrompt.astro @@ -121,7 +121,8 @@ const { target } = Astro.props; if (!id) return; const target = document.querySelector('#' + id); if (!target) return; - const value = target.textContent || ''; + const visible = target.querySelector(':scope > [data-language-content]:not([hidden])'); + const value = visible?.textContent || target.textContent || ''; if ( typeof navigator !== 'undefined' && diff --git a/src/components/islands/RulesInteractive.astro b/src/components/islands/RulesInteractive.astro index 79c23c7..b3f09b3 100644 --- a/src/components/islands/RulesInteractive.astro +++ b/src/components/islands/RulesInteractive.astro @@ -403,8 +403,13 @@ fix(api): scope session query select('[data-skill]', id, 'skill'); } + // Keys whose copy carries markup, so the swap must write innerHTML. + // `skillsText` was missing while the markup renders it with `set:html`: + // the island rewrote it as text on load and every visitor saw a literal + // `.agents/skills/` in the paragraph. const HTML_KEYS = { heroTitle: 1, + skillsText: 1, pipelineTitle: 1, skillsTitle: 1, ratchetTitle: 1, diff --git a/src/content/rules/copy.json b/src/content/rules/copy.json index 0be3b6d..6c01e92 100644 --- a/src/content/rules/copy.json +++ b/src/content/rules/copy.json @@ -56,7 +56,6 @@ "skillReadSkill": "READ SKILL ↗", "skillReadSkillPt": "LER SKILL ↗", "skillTriggerLabel": "TRIGGER", - "skillTriggerLabelPt": "GATILHO", "copyStatus": "Prompt copied.", "copyStatusPt": "Prompt copiado.", "copyFallback": "Select the text manually.", @@ -118,8 +117,7 @@ "stageOpenSourcePt": "ABRIR FONTE ↗", "skillReadSkill": "LER SKILL ↗", "skillReadSkillPt": "LER SKILL ↗", - "skillTriggerLabel": "TRIGGER", - "skillTriggerLabelPt": "GATILHO", + "skillTriggerLabel": "GATILHO", "copyStatus": "Prompt copied.", "copyStatusPt": "Prompt copiado.", "copyFallback": "Select the text manually.", diff --git a/src/pages/full-guide.astro b/src/pages/full-guide.astro index e133917..514f7e2 100644 --- a/src/pages/full-guide.astro +++ b/src/pages/full-guide.astro @@ -55,6 +55,9 @@ const efforts = toRecord(effortEntries); const skillFiles = toRecord(skillFileEntries); const skillWorkflow = toRecord(workflowEntries); const commonSkills = toRecord(commonSkillEntries); +const sortedCommonSkills = Object.values(commonSkills).sort((a, b) => + a.number.localeCompare(b.number), +); const handsOn = promptEntries.find(({ data }) => data.id === 'tiny-tasks')?.data; const install = installEntries.find(({ data }) => data.id === 'install')?.data; if (!handsOn || !install) throw new Error('full-guide content collections are incomplete'); @@ -82,6 +85,31 @@ const labels = { source: { en: 'GITHUB SOURCE ↗', pt: 'FONTE NO GITHUB ↗' }, }, }; +const builderSteps: Record< + string, + { title: { en: string; pt: string }; tagline: { en: string; pt: string } } +> = { + observe: { + title: { en: 'Observe', pt: 'Observar' }, + tagline: { en: 'find repeated friction', pt: 'encontre atrito repetido' }, + }, + trigger: { + title: { en: 'Define trigger', pt: 'Definir gatilho' }, + tagline: { en: 'route precisely', pt: 'roteie com precisão' }, + }, + scaffold: { + title: { en: 'Choose anatomy', pt: 'Escolher anatomia' }, + tagline: { en: 'only needed files', pt: 'apenas arquivos necessários' }, + }, + write: { + title: { en: 'Write guidance', pt: 'Escrever orientação' }, + tagline: { en: 'decisions, not trivia', pt: 'decisões, não trivialidades' }, + }, + validate: { + title: { en: 'Validate', pt: 'Validar' }, + tagline: { en: 'test real behavior', pt: 'teste comportamento real' }, + }, +}; const selectorData = { phases, workers, @@ -235,7 +263,7 @@ const base = import.meta.env.BASE_URL; @@ -418,7 +446,8 @@ const base = import.meta.env.BASE_URL; data-language-content="pt" hidden>Clique em uma fase.
Veja a passagem. -

+ +

Delegation means moving one bounded task into a smaller context—not giving away responsibility.MEDIUMdefault start - )) + ['observe', 'trigger', 'scaffold', 'write', 'validate'].map((id) => { + const step = skillWorkflow[id]; + return ( + + ); + }) }

@@ -819,7 +917,39 @@ const base = import.meta.env.BASE_URL; > -
+ +
+ AFTER REAL USE
+ observe failuresharpen one ruleretest behaviorkeep it narrow +
@@ -866,7 +996,10 @@ const base = import.meta.env.BASE_URL;
{ - Object.values(commonSkills).map((skill) => ( + /* getCollection's order is not the deck's order. Sort by the + card number so the tabs stay 01..07 as the legacy page has + them, instead of shifting between builds. */ + sortedCommonSkills.map((skill) => (
+ FOUR WAYS A GREEN REPORT IS FALSE
+
+ 1
+ Pipe a gate

+ tail, grep, or head hide the real exit code — a pipeline returns the last command's + status. +

+
+
+ 2
+ Swallow a rejection

+ A silent {'.catch(() => {})'} hides a panic, an upstream limit, or a partial + failure. +

+
+
+ 3
+ Trust the cache

+ Turbo caches results. A gate that "passes" may not have run — use TURBO_FORCE=true. +

+
+
+ 4
+ Skip the third layer

+ Lint and unit can both be green while the page breaks on mobile and the API never + returns 404. +

+
+
+
+
+ RUN IT YOURSELF · two labs, under 10 minutes each +
+ +
+ +

+ Prefer a focused chapter? Start with the route map, then + jump directly to models, agents and worktrees, skill creation, rules, or + the skills review desk. +

+