fix: restore the full-guide content task 15d dropped
Merges refactor/task-15f-full-guide-restore. Task 15d shipped /full-guide/ missing about a fifth of its rendered content -- the three-layer verification section, the "four ways a green report is false" grid, the comparison strip, the exercise brief, both Gitea links, and the skill-forge package tree -- and every check stayed green, because scripts/verify.mjs reads the legacy file. This restores all of it and closes the Portuguese half, which English parity had hidden. The rendered-text diff now compares occurrence counts and document order rather than set membership, which is what caught the last two defects. /full-guide/ is en 432/432 and pt 431/431, missing 0, extra 0, order clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ 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
|
||||
@@ -43,9 +44,26 @@ const astroPath = route === 'index' ? '' : `${route}/`;
|
||||
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('.', 4197), serve(staging, 4196)];
|
||||
const servers = [serve('.', legacyPort), serve(staging, astroPort)];
|
||||
const stop = () => {
|
||||
servers.forEach((s) => s.kill());
|
||||
rmSync(staging, { recursive: true, force: true });
|
||||
@@ -77,7 +95,13 @@ try {
|
||||
const browser = await chromium.launch();
|
||||
const grab = async (url) => {
|
||||
const page = await browser.newPage({ viewport: { width: 1400, height: 1000 } });
|
||||
await page.goto(url, { waitUntil: 'load' });
|
||||
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);
|
||||
@@ -87,23 +111,58 @@ try {
|
||||
await toggle.click();
|
||||
await page.waitForTimeout(1200);
|
||||
}
|
||||
const spans = await page.evaluate(visibleText);
|
||||
// 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:4197/${legacyPath}`);
|
||||
const astro = await grab(`http://localhost:4196/ai-for-dummies/${astroPath}`);
|
||||
const legacy = await grab(`http://localhost:${legacyPort}/${legacyPath}`);
|
||||
const astro = await grab(`http://localhost:${astroPort}/ai-for-dummies/${astroPath}`);
|
||||
await browser.close();
|
||||
|
||||
const rendered = new Set(astro);
|
||||
const missing = legacy.filter((span) => !rendered.has(span));
|
||||
// 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 legacySpans = new Set(legacy);
|
||||
const extra = astro.filter((span) => !legacySpans.has(span));
|
||||
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(
|
||||
@@ -111,7 +170,12 @@ try {
|
||||
);
|
||||
for (const span of missing) console.log(` - ${span}`);
|
||||
for (const span of extra) console.log(` + ${span}`);
|
||||
process.exitCode = missing.length === 0 && extra.length === 0 ? 0 : 1;
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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' &&
|
||||
|
||||
+372
-39
@@ -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;
|
||||
<section class="hero" id="top-pt" data-language-content="pt" hidden>
|
||||
<div>
|
||||
<p class="eyebrow">Uma apresentação para quem entrega software</p><h1>
|
||||
IA para<br /><em>iniciantes.</em>
|
||||
AI for<br /><em>dummies.</em>
|
||||
</h1><p class="lede">
|
||||
Você não precisa de um exército de modelos. Precisa de um sistema: uma mente para
|
||||
enquadrar o trabalho, várias mãos para executá-lo e uma fronteira clara entre cada tarefa.
|
||||
@@ -307,7 +335,7 @@ const base = import.meta.env.BASE_URL;
|
||||
>Modelo forte para ambiguidade.<br />Modelo leve para trabalho delimitado.</strong
|
||||
>
|
||||
</div><div class="signal" aria-hidden="true">
|
||||
<b>PENSE</b><i></i><i></i><i></i><b>FAÇA</b>
|
||||
<b>THINK</b><i></i><i></i><i></i><b>MAKE</b>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -418,7 +446,8 @@ const base = import.meta.env.BASE_URL;
|
||||
data-language-content="pt"
|
||||
hidden>Clique em uma fase.<br /><em>Veja a passagem.</em></span
|
||||
>
|
||||
</h2><p>
|
||||
</h2><!-- The legacy PT render paints this paragraph twice; preserve that output. -->
|
||||
<p>
|
||||
<span data-language-content="en"
|
||||
>Delegation means moving one bounded task into a smaller context—not giving away
|
||||
responsibility.</span
|
||||
@@ -690,7 +719,16 @@ const base = import.meta.env.BASE_URL;
|
||||
></small
|
||||
></button
|
||||
><button class="active" data-effort="medium" role="tab" aria-selected="true"
|
||||
>MEDIUM</button
|
||||
><b
|
||||
><span data-language-content="en">MEDIUM</span><span data-language-content="pt" hidden
|
||||
>MÉDIO</span
|
||||
></b
|
||||
><small
|
||||
><span data-language-content="en">default start</span><span
|
||||
data-language-content="pt"
|
||||
hidden>ponto inicial</span
|
||||
></small
|
||||
></button
|
||||
><button data-effort="high" role="tab" aria-selected="false"
|
||||
><b
|
||||
><span data-language-content="en">HIGH</span><span data-language-content="pt" hidden
|
||||
@@ -731,8 +769,15 @@ const base = import.meta.env.BASE_URL;
|
||||
<section class="skills" id="skills">
|
||||
<div>
|
||||
<p class="eyebrow">
|
||||
{
|
||||
/* Under Portuguese the legacy page paints the long skill sentence
|
||||
here, not "Skills": its `.skills > div:first-child > p` selector
|
||||
also matches this eyebrow and overwrites it. Reproduced on
|
||||
purpose -- see .agents/rules/content-i18n.md. */
|
||||
}
|
||||
<span data-language-content="en">Skills</span><span data-language-content="pt" hidden
|
||||
>Skills</span
|
||||
>Uma skill é um procedimento reutilizável. Ela pode carregar instruções, referências,
|
||||
scripts e assets. Não é memória mágica e não substitui critérios de aceitação.</span
|
||||
>
|
||||
</p><h2>
|
||||
<span data-language-content="en">Write the right way<br /><em>once.</em></span><span
|
||||
@@ -778,16 +823,51 @@ const base = import.meta.env.BASE_URL;
|
||||
>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div><pre><code>name: review-ui · check focus, mobile, reduced motion · run verification · return evidence</code></pre>
|
||||
</section>
|
||||
|
||||
<section class="skill-builder" id="create-skill">
|
||||
<div class="section-label">
|
||||
<span>Create a skill</span><span>repeatable pain → reusable judgment</span>
|
||||
<span
|
||||
><span data-language-content="en">Create a skill</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Criar uma skill</span
|
||||
></span
|
||||
><span
|
||||
><span data-language-content="en">repeatable pain → reusable judgment</span><span
|
||||
data-language-content="pt"
|
||||
hidden>atrito repetido → julgamento reutilizável</span
|
||||
></span
|
||||
>
|
||||
</div><div class="builder-intro">
|
||||
<div>
|
||||
<p class="eyebrow">
|
||||
<span data-language-content="en">The skill forge</span><span
|
||||
data-language-content="pt"
|
||||
hidden>A forja de skills</span
|
||||
>
|
||||
</p><h2>
|
||||
<span data-language-content="en"
|
||||
>Teach the decision.<br />Keep the context <em>light.</em></span
|
||||
><span data-language-content="pt" hidden
|
||||
>Ensine a decisão.<br />Mantenha o contexto <em>leve.</em></span
|
||||
>
|
||||
</h2>
|
||||
</div><p>
|
||||
<span data-language-content="en"
|
||||
>Do not package everything you know. Capture the non-obvious choices that repeatedly
|
||||
improve an outcome, then prove the skill changes behavior.</span
|
||||
><span data-language-content="pt" hidden
|
||||
>Não empacote tudo o que você sabe. Capture as escolhas não óbvias que melhoram
|
||||
resultados repetidamente e prove que a skill muda o comportamento.</span
|
||||
>
|
||||
</p>
|
||||
</div><div class="builder-workbench">
|
||||
<nav class="builder-steps" role="tablist" aria-label="Skill creation workflow">
|
||||
{
|
||||
Object.values(skillWorkflow).map((step) => (
|
||||
['observe', 'trigger', 'scaffold', 'write', 'validate'].map((id) => {
|
||||
const step = skillWorkflow[id];
|
||||
return (
|
||||
<button
|
||||
class:list={{ active: step.id === 'observe' }}
|
||||
data-skill-step={step.id}
|
||||
@@ -796,10 +876,28 @@ const base = import.meta.env.BASE_URL;
|
||||
>
|
||||
<>
|
||||
<b>{step.number}</b>
|
||||
<span>{step.title.en}</span>
|
||||
<>
|
||||
<span>
|
||||
<>
|
||||
<span data-language-content="en">{builderSteps[step.id].title.en}</span>
|
||||
<span data-language-content="pt" hidden>
|
||||
{builderSteps[step.id].title.pt}
|
||||
</span>
|
||||
</>
|
||||
</span>
|
||||
<small>
|
||||
<>
|
||||
<span data-language-content="en">{builderSteps[step.id].tagline.en}</span>
|
||||
<span data-language-content="pt" hidden>
|
||||
{builderSteps[step.id].tagline.pt}
|
||||
</span>
|
||||
</>
|
||||
</small>
|
||||
</>
|
||||
</>
|
||||
</button>
|
||||
))
|
||||
);
|
||||
})
|
||||
}
|
||||
</nav><article class="builder-detail" id="builder-detail" aria-live="polite">
|
||||
<header>
|
||||
@@ -819,7 +917,39 @@ const base = import.meta.env.BASE_URL;
|
||||
>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</article><aside class="builder-artifact">
|
||||
<div class="artifact-head">
|
||||
<span
|
||||
><span data-language-content="en">OUTPUT / SKILL PACKAGE</span><span
|
||||
data-language-content="pt"
|
||||
hidden>SAÍDA / PACOTE DE SKILL</span
|
||||
></span
|
||||
><i></i>
|
||||
</div><pre
|
||||
aria-label="Example skill structure"><code>review-ui/<br />├── SKILL.md<br />├── agents/<br />│ └── openai.yaml<br />├── references/<br />│ └── accessibility.md<br />└── scripts/<br /> └── verify.mjs</code></pre><div
|
||||
class="artifact-command"
|
||||
>
|
||||
<span
|
||||
><span data-language-content="en">VALIDATE</span><span
|
||||
data-language-content="pt"
|
||||
hidden>VALIDAR</span
|
||||
></span
|
||||
><code>quick_validate.py ./review-ui</code>
|
||||
</div>
|
||||
</aside>
|
||||
</div><div class="builder-loop">
|
||||
<span
|
||||
><span data-language-content="en">AFTER REAL USE</span><span
|
||||
data-language-content="pt"
|
||||
hidden>APÓS USO REAL</span
|
||||
></span
|
||||
><div data-language-content="en">
|
||||
<b>observe failure</b><i>→</i><b>sharpen one rule</b><i>→</i><b>retest behavior</b><i>→</i
|
||||
><b>keep it narrow</b>
|
||||
</div><div data-language-content="pt" hidden>
|
||||
<b>observar falha</b><i>→</i><b>refinar uma regra</b><i>→</i><b>retestar comportamento</b
|
||||
><i>→</i><b>manter estreita</b>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -866,7 +996,10 @@ const base = import.meta.env.BASE_URL;
|
||||
</div><div class="skill-deck">
|
||||
<div class="skill-index" role="tablist" aria-label="Common agent skills">
|
||||
{
|
||||
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) => (
|
||||
<button
|
||||
class:list={{ active: skill.id === 'ponytail' }}
|
||||
data-common-skill={skill.id}
|
||||
@@ -934,53 +1067,184 @@ const base = import.meta.env.BASE_URL;
|
||||
</div><article class="install-skills">
|
||||
<header>
|
||||
<div>
|
||||
<span>INSTALL PACK</span><strong
|
||||
>Ask your coding agent to verify, install, and validate the skills.</strong
|
||||
<span
|
||||
><span data-language-content="en">INSTALL PACK</span><span
|
||||
data-language-content="pt"
|
||||
hidden>PACOTE DE INSTALAÇÃO</span
|
||||
></span
|
||||
><strong
|
||||
><span data-language-content="en"
|
||||
>Ask your coding agent to verify, install, and validate the skills.</span
|
||||
><span data-language-content="pt" hidden
|
||||
>Peça ao seu agente para verificar, instalar e validar as skills.</span
|
||||
></strong
|
||||
>
|
||||
</div><CopyPrompt target="prompt-install-skills" />
|
||||
</header><pre><code id="prompt-install-skills">{install.en}</code></pre><footer>
|
||||
Review every source before installation. Existing local skills must be preserved.
|
||||
</header><pre><code id="prompt-install-skills"><span data-language-content="en">{install.en}</span><span data-language-content="pt" hidden>{install.pt}</span></code></pre><footer
|
||||
>
|
||||
<span data-language-content="en"
|
||||
>Review every source before installation. Existing local skills must be preserved.</span
|
||||
><span data-language-content="pt" hidden
|
||||
>Revise cada fonte antes da instalação. Skills locais existentes devem ser preservadas.</span
|
||||
>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="hands-on" id="hands-on">
|
||||
<div class="section-label">
|
||||
<span>Hands-on</span><span>10 minutes / one missing feature</span>
|
||||
<span
|
||||
><span data-language-content="en">Hands-on</span><span data-language-content="pt" hidden
|
||||
>Prática</span
|
||||
></span
|
||||
><span
|
||||
><span data-language-content="en">10 minutes / one missing feature</span><span
|
||||
data-language-content="pt"
|
||||
hidden>10 minutos / uma feature ausente</span
|
||||
></span
|
||||
>
|
||||
</div><div class="hands-intro">
|
||||
<div>
|
||||
<p class="eyebrow">Tiny Tasks lab</p><h2>
|
||||
Same task.<br />Better <em>operating system.</em>
|
||||
<p class="eyebrow">
|
||||
<span data-language-content="en">Tiny Tasks lab</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Laboratório Tiny Tasks</span
|
||||
>
|
||||
</p><h2>
|
||||
<span data-language-content="en">Same task.<br />Better <em>operating system.</em></span
|
||||
><span data-language-content="pt" hidden
|
||||
>Mesma tarefa.<br />Melhor <em>sistema operacional.</em></span
|
||||
>
|
||||
</h2>
|
||||
</div><div>
|
||||
<p>
|
||||
<p data-language-content="en">
|
||||
Start with a deliberately incomplete static task board. Run one prompt as written,
|
||||
reset, then run the skill-enabled version.
|
||||
reset, then run the skill-enabled version. Compare diff size, verification evidence, and
|
||||
unnecessary complexity.
|
||||
</p><p data-language-content="pt" hidden>
|
||||
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.
|
||||
</p><div class="starter-links">
|
||||
<a href={`${base}hands-on/starter/`} class="starter-link">Open the starter →</a><a
|
||||
href={`${base}hands-on/rules/`}
|
||||
class="starter-link">Open the rules lab →</a
|
||||
<div class="starter-link-group">
|
||||
<a href={`${base}hands-on/starter/`} class="starter-link"
|
||||
><span data-language-content="en">Open the starter →</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Abrir o projeto inicial →</span
|
||||
></a
|
||||
><a
|
||||
href="https://git.marcospaulo.dev.br/netcracker/ai-for-dummies/src/branch/pages/hands-on/starter"
|
||||
class="starter-link starter-link-source"
|
||||
><span data-language-content="en">Clone from Gitea →</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Abrir o projeto inicial →</span
|
||||
></a
|
||||
>
|
||||
</div><div class="starter-link-group">
|
||||
<a href={`${base}hands-on/rules/`} class="starter-link"
|
||||
><span data-language-content="en">Open the rules lab →</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Abrir o projeto inicial →</span
|
||||
></a
|
||||
><a
|
||||
href="https://git.marcospaulo.dev.br/netcracker/ai-for-dummies/src/branch/pages/hands-on/rules"
|
||||
class="starter-link starter-link-source"
|
||||
><span data-language-content="en">Clone from Gitea →</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Abrir o projeto inicial →</span
|
||||
></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><div class="exercise-brief">
|
||||
<span
|
||||
><span data-language-content="en">THE MISSING FEATURE</span><span
|
||||
data-language-content="pt"
|
||||
hidden>A FEATURE AUSENTE</span
|
||||
></span
|
||||
><strong
|
||||
><span data-language-content="en"
|
||||
>Add All / Open / Done filters that survive reload and browser navigation.</span
|
||||
><span data-language-content="pt" hidden
|
||||
>Adicione filtros Todos / Abertos / Concluídos que sobrevivem reload e navegação.</span
|
||||
></strong
|
||||
><div>
|
||||
<span data-language-content="en"
|
||||
><b>STACK</b> HTML · CSS · JavaScript <b>DEPENDENCIES</b> none <b>FILES</b> 3</span
|
||||
><span data-language-content="pt" hidden
|
||||
><b>STACK</b> HTML · CSS · JavaScript <b>DEPENDÊNCIAS</b> nenhuma <b>ARQUIVOS</b> 3</span
|
||||
>
|
||||
</div>
|
||||
</div><div class="prompt-compare">
|
||||
<article class="prompt-card">
|
||||
<header>
|
||||
<div><span>RUN A</span><strong>Good prompt</strong></div><CopyPrompt
|
||||
target="prompt-basic"
|
||||
/>
|
||||
</header><pre><code id="prompt-basic">{handsOn.en.basic}</code></pre><footer>
|
||||
Clear context · constraints · acceptance · evidence
|
||||
<div>
|
||||
<span>RUN A</span><strong
|
||||
><span data-language-content="en">Good prompt</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Bom prompt</span
|
||||
></strong
|
||||
>
|
||||
</div><CopyPrompt target="prompt-basic" />
|
||||
</header><pre><code id="prompt-basic"><span data-language-content="en">{handsOn.en.basic}</span><span data-language-content="pt" hidden>{handsOn.pt.basic}</span></code></pre><footer
|
||||
>
|
||||
<span data-language-content="en"
|
||||
>Clear context · constraints · acceptance · evidence</span
|
||||
><span data-language-content="pt" hidden
|
||||
>Contexto claro · restrições · aceitação · evidência</span
|
||||
>
|
||||
</footer>
|
||||
</article><article class="prompt-card enhanced">
|
||||
<header>
|
||||
<div><span>RUN B</span><strong>Good prompt + skills</strong></div><CopyPrompt
|
||||
target="prompt-skills"
|
||||
/>
|
||||
</header><pre><code id="prompt-skills">{handsOn.en.skills}</code></pre><footer>
|
||||
Same contract · explicit working methods · stronger proof
|
||||
<div>
|
||||
<span>RUN B</span><strong
|
||||
><span data-language-content="en">Good prompt + skills</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Bom prompt + skills</span
|
||||
></strong
|
||||
>
|
||||
</div><CopyPrompt target="prompt-skills" />
|
||||
</header><pre><code id="prompt-skills"><span data-language-content="en">{handsOn.en.skills}</span><span data-language-content="pt" hidden>{handsOn.pt.skills}</span></code></pre><footer
|
||||
>
|
||||
<span data-language-content="en"
|
||||
>Same contract · explicit working methods · stronger proof</span
|
||||
><span data-language-content="pt" hidden
|
||||
>Mesmo contrato · métodos explícitos · prova mais forte</span
|
||||
>
|
||||
</footer>
|
||||
</article>
|
||||
</div><div class="comparison-strip">
|
||||
<span
|
||||
><span data-language-content="en">COMPARE THE RUNS</span><span
|
||||
data-language-content="pt"
|
||||
hidden>COMPARE AS EXECUÇÕES</span
|
||||
></span
|
||||
><div>
|
||||
<b>01</b>
|
||||
<span data-language-content="en">Files changed</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Arquivos alterados</span
|
||||
>
|
||||
</div><div>
|
||||
<b>02</b>
|
||||
<span data-language-content="en">New dependencies</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Novas dependências</span
|
||||
>
|
||||
</div><div>
|
||||
<b>03</b>
|
||||
<span data-language-content="en">Checks actually run</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Checks executados</span
|
||||
>
|
||||
</div><div>
|
||||
<b>04</b>
|
||||
<span data-language-content="en">Evidence returned</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Evidências retornadas</span
|
||||
>
|
||||
</div>
|
||||
</div><p class="copy-status" id="copy-status" role="status" aria-live="polite"></p>
|
||||
</section>
|
||||
|
||||
@@ -1017,21 +1281,90 @@ const base = import.meta.env.BASE_URL;
|
||||
<section class="verification" id="verification">
|
||||
<div class="section-label">
|
||||
<span>Verification</span><span>run each gate separately</span>
|
||||
</div><div class="verify-intro">
|
||||
<div>
|
||||
<p class="eyebrow">Checks become evidence</p><h2>
|
||||
Three layers.<br />Run each one alone.
|
||||
</h2>
|
||||
</div><p>
|
||||
Run a gate on its own line, print its exit code, attach the output. The result is the
|
||||
deliverable.
|
||||
</p>
|
||||
</div><div class="verify-layers">
|
||||
<article>
|
||||
<span>01 · STATIC</span><h3>Lint and types</h3><p>
|
||||
Format, lint, type-check. Fast and scoped to one file.
|
||||
</p><code>pnpm lint; echo "lint=$?"</code>
|
||||
Format, lint, type-check. Fast and scoped to one file. Run on every save.
|
||||
</p><code>pnpm lint; echo "lint=$?" pnpm typecheck; echo "typecheck=$?"</code>
|
||||
</article><article>
|
||||
<span>02 · BEHAVIOR</span><h3>Unit and contract</h3><p>
|
||||
Tests that repeat. Run before claiming done.
|
||||
</p><code>pnpm test; echo "test=$?"</code>
|
||||
</p><code>pnpm test; echo "test=$?" cd services/api && go test ./...</code>
|
||||
</article><article>
|
||||
<span>03 · INTEGRATION</span><h3>Real UI and API</h3><p>
|
||||
Drive the actual UI, API, or browser.
|
||||
</p><code>pnpm check:ui; echo "ui=$?"</code>
|
||||
Drive the actual UI, API, or browser. Slower and flakier — only this catches mobile
|
||||
overflow and a missing 404.
|
||||
</p><code>pnpm check:ui; echo "ui=$?" TURBO_FORCE=true pnpm e2e</code>
|
||||
</article>
|
||||
</div><div class="verify-antipatterns">
|
||||
<span>FOUR WAYS A GREEN REPORT IS FALSE</span><div class="ap-grid">
|
||||
<article>
|
||||
<b>1</b><div>
|
||||
<strong>Pipe a gate</strong><p>
|
||||
tail, grep, or head hide the real exit code — a pipeline returns the last command's
|
||||
status.
|
||||
</p>
|
||||
</div>
|
||||
</article><article>
|
||||
<b>2</b><div>
|
||||
<strong>Swallow a rejection</strong><p>
|
||||
A silent <code>{'.catch(() => {})'}</code> hides a panic, an upstream limit, or a partial
|
||||
failure.
|
||||
</p>
|
||||
</div>
|
||||
</article><article>
|
||||
<b>3</b><div>
|
||||
<strong>Trust the cache</strong><p>
|
||||
Turbo caches results. A gate that "passes" may not have run — use <code
|
||||
>TURBO_FORCE=true</code
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</article><article>
|
||||
<b>4</b><div>
|
||||
<strong>Skip the third layer</strong><p>
|
||||
Lint and unit can both be green while the page breaks on mobile and the API never
|
||||
returns 404.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div><article class="verify-cta">
|
||||
<span>RUN IT YOURSELF · two labs, under 10 minutes each</span><div class="verify-cta-grid">
|
||||
<a href={`${base}hands-on/starter/`} class="verify-card"
|
||||
><strong>Path A · verification lab</strong><p>
|
||||
Fill the four-row comparison strip on the starter. Run A naively, Run B with <code
|
||||
>$gate-discipline</code
|
||||
> and <code>$webapp-testing</code>.
|
||||
</p><small>Open the starter →</small><small class="verify-card-source"
|
||||
>Clone ↗ <span>git.marcospaulo.dev.br/.../src/branch/pages/hands-on/starter</span
|
||||
></small
|
||||
></a
|
||||
><a href={`${base}hands-on/rules/`} class="verify-card"
|
||||
><strong>Path B · rules lab</strong><p>
|
||||
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.
|
||||
</p><small
|
||||
><span data-language-content="en">Open the rules lab →</span><span
|
||||
data-language-content="pt"
|
||||
hidden>Open the rules lab →</span
|
||||
></small
|
||||
><small class="verify-card-source"
|
||||
>Clone ↗ <span>git.marcospaulo.dev.br/.../src/branch/pages/hands-on/rules</span
|
||||
></small
|
||||
></a
|
||||
>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<section class="sources">
|
||||
<div class="section-label">
|
||||
|
||||
Reference in New Issue
Block a user