Compare commits
2 Commits
aefbd206f9
...
a1eb1e79ea
| Author | SHA1 | Date | |
|---|---|---|---|
| a1eb1e79ea | |||
| c2b35d5355 |
@@ -0,0 +1,92 @@
|
|||||||
|
#!/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
|
||||||
|
//
|
||||||
|
// 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 { chromium } from 'playwright';
|
||||||
|
|
||||||
|
const route = process.argv[2];
|
||||||
|
if (!route) {
|
||||||
|
console.error('usage: rendered-text-diff.mjs <route> e.g. full-guide');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 });
|
||||||
|
|
||||||
|
const serve = (dir, port) =>
|
||||||
|
spawn('python3', ['-m', 'http.server', String(port), '-d', dir], { stdio: 'ignore' });
|
||||||
|
const servers = [serve('.', 4197), serve(staging, 4196)];
|
||||||
|
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 } });
|
||||||
|
await page.goto(url, { waitUntil: 'load' });
|
||||||
|
// The islands hydrate and render their initial panel on load; without this
|
||||||
|
// every panel's copy reads as missing.
|
||||||
|
await page.waitForTimeout(1200);
|
||||||
|
const spans = await page.evaluate(visibleText);
|
||||||
|
await page.close();
|
||||||
|
return spans;
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacy = await grab(`http://localhost:4197/${route}/index.html`);
|
||||||
|
const astro = await grab(`http://localhost:4196/ai-for-dummies/${route}/`);
|
||||||
|
await browser.close();
|
||||||
|
|
||||||
|
const rendered = new Set(astro);
|
||||||
|
const missing = legacy.filter((span) => !rendered.has(span));
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`legacy ${legacy.length} spans · astro ${astro.length} spans · missing ${missing.length}`,
|
||||||
|
);
|
||||||
|
for (const span of missing) console.log(` - ${span}`);
|
||||||
|
process.exitCode = missing.length === 0 ? 0 : 1;
|
||||||
|
} finally {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# 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
|
||||||
@@ -1064,6 +1064,22 @@ const base = import.meta.env.BASE_URL;
|
|||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
<!-- Legacy full-guide/index.html closes with this section after `.sources`.
|
||||||
|
Task 15d dropped it, and nothing caught that: verify.mjs reads the
|
||||||
|
legacy file, so its chapter-route assertion kept passing. It has no
|
||||||
|
`translations.pt` entry, so it is English-only on the live site too. -->
|
||||||
|
<section class="chapter-route">
|
||||||
|
<div class="section-label">
|
||||||
|
<span>Navigate by idea</span><span>short chapters / one system</span>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Prefer a focused chapter? Start with the <a href={`${base}summary/`}>route map</a>, then
|
||||||
|
jump directly to <a href={`${base}models/`}>models</a>, <a href={`${base}agents/`}
|
||||||
|
>agents and worktrees</a
|
||||||
|
>, <a href={`${base}skills/`}>skill creation</a>, <a href={`${base}rules/`}>rules</a>, or
|
||||||
|
the <a href={`${base}skills-review/`}>skills review desk</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<GuideSelector rootSelector="#full-guide" data={selectorData} />
|
<GuideSelector rootSelector="#full-guide" data={selectorData} />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user