Compare commits
9 Commits
44f7fb8a01
...
421c84ff92
| Author | SHA1 | Date | |
|---|---|---|---|
| 421c84ff92 | |||
| d62c6bf958 | |||
| 99ccc8e249 | |||
| 966dfcb926 | |||
| 99d42f8fbc | |||
| c19e77bcac | |||
| 73d1b62989 | |||
| 8388dd63fe | |||
| 9621ba44bf |
@@ -41,33 +41,34 @@ Phase 3 pages 12 ∥ 13 ∥ 14 ∥ 17, then 15a ∥ 15b ∥ 15c ∥
|
|||||||
Phase 4 polish 18 ∥ 19, then 20
|
Phase 4 polish 18 ∥ 19, then 20
|
||||||
```
|
```
|
||||||
|
|
||||||
| # | Task | Agent | Depends on | Parallel with |
|
| # | Task | Agent | Depends on | Parallel with |
|
||||||
| --- | ----------------------------------------------------------------- | --------------------- | ------------- | ------------- |
|
| --- | ----------------------------------------------------------------- | --------------------- | ------------------ | ------------- |
|
||||||
| 01 | [scaffold + gates](task-01-scaffold.md) | astro-architect | — | — |
|
| 01 | [scaffold + gates](task-01-scaffold.md) | astro-architect | — | — |
|
||||||
| 02 | [design tokens](task-02-tokens.md) | design-system-keeper | 01 | 03, 04 |
|
| 02 | [design tokens](task-02-tokens.md) | design-system-keeper | 01 | 03, 04 |
|
||||||
| 03 | [verification net](task-03-verification-net.md) | verification-engineer | 01 | 02, 04 |
|
| 03 | [verification net](task-03-verification-net.md) | verification-engineer | 01 | 02, 04 |
|
||||||
| 04 | [content schema](task-04-content-schema.md) | content-i18n-migrator | 01 | 02, 03 |
|
| 04 | [content schema](task-04-content-schema.md) | content-i18n-migrator | 01 | 02, 03 |
|
||||||
| 05 | [guide content](task-05-content-guide.md) | content-i18n-migrator | 04 | 06 |
|
| 05 | [guide content](task-05-content-guide.md) | content-i18n-migrator | 04 | 06 |
|
||||||
| 06 | [review-desk content](task-06-content-review.md) | content-i18n-migrator | 04 | 05 |
|
| 06 | [review-desk content](task-06-content-review.md) | content-i18n-migrator | 04 | 05 |
|
||||||
| 07 | [primitives](task-07-primitives.md) | component-builder | 02 | — |
|
| 07 | [primitives](task-07-primitives.md) | component-builder | 02 | — |
|
||||||
| 08 | [route cards](task-08-route-cards.md) | component-builder | 07 | 09, 10, 11 |
|
| 08 | [route cards](task-08-route-cards.md) | component-builder | 07 | 09, 10, 11 |
|
||||||
| 09 | [chapter blocks](task-09-chapter-blocks.md) | component-builder | 07 | 08, 10, 11 |
|
| 09 | [chapter blocks](task-09-chapter-blocks.md) | component-builder | 07 | 08, 10, 11 |
|
||||||
| 10 | [guide blocks](task-10-guide-blocks.md) | component-builder | 07 | 08, 09, 11 |
|
| 10 | [guide blocks](task-10-guide-blocks.md) | component-builder | 07 | 08, 09, 11 |
|
||||||
| 11 | [review-desk blocks](task-11-review-blocks.md) | component-builder | 07 | 08, 09, 10 |
|
| 11 | [review-desk blocks](task-11-review-blocks.md) | component-builder | 07 | 08, 09, 10 |
|
||||||
| 12 | [landing page](task-12-page-landing.md) | page-migrator | 03, 08 | 13, 14, 17 |
|
| 12 | [landing page](task-12-page-landing.md) | page-migrator | 03, 08 | 13, 14, 17 |
|
||||||
| 13 | [chapter pages ×4](task-13-page-chapters.md) | page-migrator | 03, 09 | 12, 14, 17 |
|
| 13 | [chapter pages ×4](task-13-page-chapters.md) | page-migrator | 03, 09 | 12, 14, 17 |
|
||||||
| 14 | [rules page](task-14-page-rules.md) | page-migrator | 03, 09 | 12, 13, 17 |
|
| 14 | [rules page](task-14-page-rules.md) | page-migrator | 03, 09 | 12, 13, 17 |
|
||||||
| 15 | [full guide](task-15-page-full-guide.md) — **split into 15a–15e** | — | — | — |
|
| 15 | [full guide](task-15-page-full-guide.md) — **split into 15a–15e** | — | — | — |
|
||||||
| 15a | [guide selector](task-15a-guide-selector.md) | component-builder | 05, 10 | 15b, 15c |
|
| 05b | [interactiveCopy data](task-05b-guide-interactive-data.md) | content-i18n-migrator | 05 | — |
|
||||||
| 15b | [copy prompt](task-15b-copy-prompt.md) | component-builder | 05 | 15a, 15c |
|
| 15a | [guide selector](task-15a-guide-selector.md) | component-builder | 05, 10 | 15b, 15c |
|
||||||
| 15c | [language toggle](task-15c-language-toggle.md) | content-i18n-migrator | 05 | 15a, 15b |
|
| 15b | [copy prompt](task-15b-copy-prompt.md) | component-builder | 05 | 15a, 15c |
|
||||||
| 15d | [assemble full guide](task-15d-page-full-guide.md) | page-migrator | 10, 13, 15a–c | 16 |
|
| 15c | [language toggle](task-15c-language-toggle.md) | content-i18n-migrator | 05 | 15a, 15b |
|
||||||
| 15e | [retire responsive.css](task-15e-responsive-css.md) | design-system-keeper | 15d, 16 | — |
|
| 15d | [assemble full guide](task-15d-page-full-guide.md) | page-migrator | 05b, 10, 13, 15a–c | 16 |
|
||||||
| 16 | [review desk](task-16-page-review-desk.md) | page-migrator | 06, 11, 13 | 15d |
|
| 15e | [retire responsive.css](task-15e-responsive-css.md) | design-system-keeper | 15d, 16 | — |
|
||||||
| 17 | [hands-on passthrough](task-17-hands-on.md) | astro-architect | 01 | 12, 13, 14 |
|
| 16 | [review desk](task-16-page-review-desk.md) | page-migrator | 06, 11, 13 | 15d |
|
||||||
| 18 | [motion pass](task-18-motion.md) | motion-designer | 15d, 16 | 19 |
|
| 17 | [hands-on passthrough](task-17-hands-on.md) | astro-architect | 01 | 12, 13, 14 |
|
||||||
| 19 | [contract re-point](task-19-verify-repoint.md) | verification-engineer | 15d, 16 | 18 |
|
| 18 | [motion pass](task-18-motion.md) | motion-designer | 15d, 16 | 19 |
|
||||||
| 20 | [cutover + cleanup](task-20-cutover.md) | astro-architect | all | — |
|
| 19 | [contract re-point](task-19-verify-repoint.md) | verification-engineer | 15d, 16 | 18 |
|
||||||
|
| 20 | [cutover + cleanup](task-20-cutover.md) | astro-architect | all | — |
|
||||||
|
|
||||||
Widest parallelism: **four agents** (tasks 08–11, then 12/13/14/17). More than
|
Widest parallelism: **four agents** (tasks 08–11, then 12/13/14/17). More than
|
||||||
that and they start contending on review capacity, not on files.
|
that and they start contending on review capacity, not on files.
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Task 05b — The `interactiveCopy` data
|
||||||
|
|
||||||
|
**Agent**: `content-i18n-migrator` · **Model**: MiniMax-M3 **Depends on**: 05 ·
|
||||||
|
**Blocks**: 15d **Worktree**:
|
||||||
|
`.agents/scripts/worktree.sh start 05b guide-interactive-data`
|
||||||
|
|
||||||
|
## Why this task exists
|
||||||
|
|
||||||
|
It is not in the original plan, and it is the second hole of exactly this kind
|
||||||
|
(see [task 04b](task-04b-chapters-data.md), which filled the empty `chapters`
|
||||||
|
collection).
|
||||||
|
|
||||||
|
Task 05 moved `phases`, `providers`, `efforts`, `skillSources`, `handsOnPrompts`
|
||||||
|
and `skillInstallPrompts` out of `app.js`. It did not move
|
||||||
|
`const interactiveCopy` at `app.js:160`, which holds six more datasets:
|
||||||
|
|
||||||
|
| Key | Feeds | Rendered by |
|
||||||
|
| --------------- | ---------------------- | --------------------- |
|
||||||
|
| `workers` | `#worker-detail` | `renderWorker` |
|
||||||
|
| `trees` | `#tree-detail` | `renderTree` |
|
||||||
|
| `routes` | `#route-detail` | `renderRoute` |
|
||||||
|
| `skillFiles` | `#skill-detail` | `renderSkillFile` |
|
||||||
|
| `skillWorkflow` | `#builder-detail` | `renderSkillWorkflow` |
|
||||||
|
| `commonSkills` | `#common-skill-detail` | `renderCommonSkill` |
|
||||||
|
|
||||||
|
Task 15d stopped before writing a line because of this — six of the nine groups
|
||||||
|
`GuideSelector` drives have no data to read. It was right to stop.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
`src/content/config.ts` (new collections only) and the new `src/content/*/`
|
||||||
|
JSON. **You own `config.ts`** — you are the only agent who may edit it. Do not
|
||||||
|
change an existing collection's schema.
|
||||||
|
|
||||||
|
## The shapes are already written down
|
||||||
|
|
||||||
|
`src/components/islands/GuideSelector.astro` declares a `GuideSelectorData`
|
||||||
|
interface at the top of the file. It is the contract 15d will feed. Match it —
|
||||||
|
if you think a field there is wrong, say so in your report rather than diverging
|
||||||
|
silently.
|
||||||
|
|
||||||
|
Note the non-obvious ones, verified against `app.js`:
|
||||||
|
|
||||||
|
- `trees[id]` has `owner` and `note` localized, but `path` and `command` are
|
||||||
|
**single strings, not localized** — they are shell paths.
|
||||||
|
- `routes[id].score` is a **number**, rendered into `style="--score:N%"`.
|
||||||
|
- `workers[id]` is `{ en: string[], pt: string[] }` — a three-element array
|
||||||
|
(`span`, `strong`, `small`), not an object.
|
||||||
|
- `skillFiles[id]` mixes `icon`/`title` (unlocalized) with `en`/`pt` strings.
|
||||||
|
- `providers[id].tiers` is `[string, string, Localized][]`.
|
||||||
|
|
||||||
|
Read the real `app.js` for each. Do not infer a shape from a sibling.
|
||||||
|
|
||||||
|
## Rules that bite here
|
||||||
|
|
||||||
|
- **Both `en` and `pt` are mandatory on every localized field.** A missing `pt`
|
||||||
|
must fail the build. Never make a field optional to clear a validation error —
|
||||||
|
find the real Portuguese string in `app.js`.
|
||||||
|
- Copy strings **verbatim**. This is a move, not a rewrite. No fixed typos, no
|
||||||
|
improved phrasing, nothing translated that is already translated. Several
|
||||||
|
strings contain inline HTML; keep it exactly as-is.
|
||||||
|
- Prove it. Diff each migrated string against the `app.js` original
|
||||||
|
programmatically and report the result — task 15b did this for the prompt
|
||||||
|
bodies and it is the standard here.
|
||||||
|
- Do not delete `interactiveCopy` from `app.js`. The legacy page still runs on
|
||||||
|
it until task 20 cuts over, and `verify.mjs` still asserts against it.
|
||||||
|
- Do not migrate a page or touch a component. 15d owns that.
|
||||||
|
- Do not edit `verify.mjs`, `tokens.css`, or `astro.config.mjs`.
|
||||||
|
|
||||||
|
## Done when
|
||||||
|
|
||||||
|
- [ ] Six collections defined and exported from `config.ts`
|
||||||
|
- [ ] Entries for every id in `interactiveCopy`, both locales
|
||||||
|
- [ ] Shapes match `GuideSelectorData`, or the divergence is argued in the
|
||||||
|
report
|
||||||
|
- [ ] Every string proven byte-identical to its `app.js` original
|
||||||
|
- [ ] `pnpm run gate` green — the full gate, not `verify` + `audit-ui` alone
|
||||||
|
- [ ] 42 assertions intact
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Task 15d — Assemble /full-guide/
|
# Task 15d — Assemble /full-guide/
|
||||||
|
|
||||||
**Agent**: `page-migrator` · **Model**: **Codex** **Depends on**: 10, 13, 15a,
|
**Agent**: `page-migrator` · **Model**: **Codex** **Depends on**: 05b, 10, 13,
|
||||||
15b, 15c · **Parallel with**: 16 · **Blocks**: 15e, 18, 19 **Worktree**:
|
15a, 15b, 15c · **Parallel with**: 16 · **Blocks**: 15e, 18, 19 **Worktree**:
|
||||||
`.agents/scripts/worktree.sh start 15d page-full-guide`
|
`.agents/scripts/worktree.sh start 15d page-full-guide`
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
@@ -56,3 +56,47 @@ re-points them to output-level checks; you do not.
|
|||||||
- [ ] Screenshots match at 560 / 800 / 1100 / 1600 px
|
- [ ] Screenshots match at 560 / 800 / 1100 / 1600 px
|
||||||
- [ ] `pnpm run gate` green — the full gate, not `verify` + `audit-ui` alone
|
- [ ] `pnpm run gate` green — the full gate, not `verify` + `audit-ui` alone
|
||||||
- [ ] 42 assertions intact
|
- [ ] 42 assertions intact
|
||||||
|
|
||||||
|
## What 15a, 15b and 15c actually shipped
|
||||||
|
|
||||||
|
Read `.agents/context/content-i18n.md` first — it is the language contract and
|
||||||
|
it is binding on this task.
|
||||||
|
|
||||||
|
- **`src/components/islands/GuideSelector.astro`** — one island, all nine
|
||||||
|
groups. Render it as
|
||||||
|
`<GuideSelector rootSelector="#full-guide" data={...} />`. `data` needs
|
||||||
|
`phases`, `workers`, `trees`, `routes`, `providers`, `efforts`, `skillFiles`,
|
||||||
|
`skillWorkflow`, `commonSkills` and a bilingual `labels` object; the
|
||||||
|
`GuideSelectorData` interface at the top of the file is the exact shape. You
|
||||||
|
server-render each group's shell and its initial detail panel, keeping today's
|
||||||
|
`data-*` hooks and `.active` state. Mark each group `role="tablist"` and its
|
||||||
|
controls `role="tab"` or the keyboard handler will not bind.
|
||||||
|
- **`src/components/islands/LanguageToggle.astro`** — render each localized
|
||||||
|
fragment twice, with `data-language-content="en"` or `"pt"` on the outer
|
||||||
|
element. The toggle flips `hidden` on those, sets `<html lang>`, and fires
|
||||||
|
`ai-for-dummies:languagechange` on `window`. It is a plain `.astro` island
|
||||||
|
that defers its own setup with `requestIdleCallback`; **do not** put
|
||||||
|
`client:idle` on it — that directive is framework-components only.
|
||||||
|
- **`src/components/islands/CopyPrompt.astro`** — one per button. Pass
|
||||||
|
`target="prompt-install-skills" | "prompt-basic" | "prompt-skills"`. Render
|
||||||
|
`<p id="copy-status" role="status" aria-live="polite">` exactly once on the
|
||||||
|
page; the island writes into it. Fill the `<pre><code id="prompt-…">` bodies
|
||||||
|
from the `handsOnPrompts` and `skillInstallPrompts` collections — 15b verified
|
||||||
|
those are byte-identical to the legacy `app.js` constants, and the clipboard
|
||||||
|
copies whatever you render, so do not reformat them.
|
||||||
|
- **`src/components/islands/ReadingProgress.astro`** — replaces the legacy
|
||||||
|
`<div class="reading-progress">` at the top of the page.
|
||||||
|
|
||||||
|
Nothing else is missing. If you are about to write an island, you are doing
|
||||||
|
another task's work — report the gap instead.
|
||||||
|
|
||||||
|
## Two things to expect
|
||||||
|
|
||||||
|
- **The snapshot will not match by construction.** Dual-locale rendering emits
|
||||||
|
both languages into the HTML where today's page emits English plus a
|
||||||
|
Portuguese map inside `app.js`. Compare _rendered, language-filtered_ output
|
||||||
|
against today's page, and if `.agents/snapshots/` needs regenerating, say so
|
||||||
|
explicitly in your report with what changed and why — do not quietly rewrite a
|
||||||
|
snapshot to make a diff go away.
|
||||||
|
- **`renderHandsOn` takes no argument** and is not part of GuideSelector's
|
||||||
|
nine-group pattern. It is yours. Its name is asserted by `verify.mjs`.
|
||||||
|
|||||||
@@ -0,0 +1,306 @@
|
|||||||
|
---
|
||||||
|
// SkillPackageExplorer — the four-file skill-package picker. The interactive
|
||||||
|
// version of the `data-package-file` buttons that the vanilla skills page
|
||||||
|
// shipped, rewritten for the Astro chapter surface. The four package entries
|
||||||
|
// mirror skills/app.js so the migration preserves content 1:1; the buttons
|
||||||
|
// carry `data-skill-file` and the click handler swaps the preview panel
|
||||||
|
// contents client-side.
|
||||||
|
//
|
||||||
|
// Page is `client:visible` rather than `client:load`: the picker sits below
|
||||||
|
// the hero and grid; deferring until it scrolls into view keeps initial JS
|
||||||
|
// to zero for the above-the-fold content. The page ships zero JS for the
|
||||||
|
// hero/grid/footer parts — only the picker island hydrates.
|
||||||
|
|
||||||
|
interface PackageFile {
|
||||||
|
id: 'skill' | 'references' | 'scripts' | 'assets';
|
||||||
|
prefix: '├── ' | '└── ';
|
||||||
|
label: string;
|
||||||
|
/** Small caption under the file label, mirrors the vanilla source. */
|
||||||
|
caption: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageFiles: PackageFile[] = [
|
||||||
|
{
|
||||||
|
id: 'skill',
|
||||||
|
prefix: '├── ',
|
||||||
|
label: 'SKILL.md',
|
||||||
|
caption: 'trigger + workflow',
|
||||||
|
title: 'The operating contract',
|
||||||
|
body: 'The one file that should always be loaded. Define the exact trigger, the ordered workflow, safety limits, and the evidence the agent returns.',
|
||||||
|
code: '---\nname: review-ui\ndescription: Review a changed UI for focus, reflow, and motion.\n---\n\n1. Inspect the changed interaction.\n2. Run the UI checks.\n3. Return findings with evidence.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'references',
|
||||||
|
prefix: '├── ',
|
||||||
|
label: 'references/',
|
||||||
|
caption: 'conditional facts',
|
||||||
|
title: 'Facts, only when needed',
|
||||||
|
body: 'Keep conditional detail out of the main instruction. A dialog pattern, framework caveat, or accessibility checklist belongs here when it is not needed for every review.',
|
||||||
|
code: 'references/\n└── accessibility.md\n ├── keyboard interaction patterns\n └── focus and reflow checklist',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'scripts',
|
||||||
|
prefix: '├── ',
|
||||||
|
label: 'scripts/',
|
||||||
|
caption: 'deterministic checks',
|
||||||
|
title: 'Mechanics that should not depend on memory',
|
||||||
|
body: 'Turn deterministic checks into runnable tools. The agent still judges the result, but it should not have to recreate a viewport test or filename rule by hand.',
|
||||||
|
code: 'scripts/\n└── check-reflow.mjs\n └── checks 320px, 1280px, and 4K widths',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'assets',
|
||||||
|
prefix: '└── ',
|
||||||
|
label: 'assets/',
|
||||||
|
caption: 'templates + examples',
|
||||||
|
title: 'Starting material, not hidden instructions',
|
||||||
|
body: 'Use assets for templates and examples a person or agent can copy. Keep them clearly named so package readers can choose the right starting point.',
|
||||||
|
code: 'assets/\n├── review-report.md\n└── focus-test-fixture.html',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="package-workbench" data-package-workbench>
|
||||||
|
<div class="package-tree" role="tablist" aria-label="Files in the review-ui skill package">
|
||||||
|
<p>REVIEW-UI / SKILL PACKAGE</p>
|
||||||
|
{
|
||||||
|
packageFiles.map((file, index) => (
|
||||||
|
<button
|
||||||
|
class:list={[{ active: index === 0 }]}
|
||||||
|
data-skill-file={file.id}
|
||||||
|
role="tab"
|
||||||
|
aria-selected={index === 0 ? 'true' : 'false'}
|
||||||
|
>
|
||||||
|
<code>{`${file.prefix}${file.label}`}</code>
|
||||||
|
<small>{file.caption}</small>
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<article class="package-preview" id="package-preview" aria-live="polite"></article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="application/json" data-skill-files set:html={JSON.stringify(packageFiles)} />
|
||||||
|
|
||||||
|
<script is:inline>
|
||||||
|
(function () {
|
||||||
|
const preview = document.querySelector('#package-preview');
|
||||||
|
const buttons = document.querySelectorAll('[data-skill-file]');
|
||||||
|
const dataNode = document.querySelector('[data-skill-files]');
|
||||||
|
if (!preview || !dataNode) return;
|
||||||
|
const files = JSON.parse(dataNode.textContent || '[]');
|
||||||
|
|
||||||
|
function renderPackage(id) {
|
||||||
|
const item = files.find((entry) => entry.id === id);
|
||||||
|
if (!item) return;
|
||||||
|
preview.classList.remove('is-swapping');
|
||||||
|
void preview.offsetWidth;
|
||||||
|
preview.classList.add('is-swapping');
|
||||||
|
preview.innerHTML =
|
||||||
|
'<span>SELECTED / ' +
|
||||||
|
item.label +
|
||||||
|
'</span>' +
|
||||||
|
'<h3>' +
|
||||||
|
item.title +
|
||||||
|
'</h3>' +
|
||||||
|
'<p>' +
|
||||||
|
item.body +
|
||||||
|
'</p>' +
|
||||||
|
'<pre><code>' +
|
||||||
|
item.code +
|
||||||
|
'</code></pre>';
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
const active = button.dataset.skillFile === id;
|
||||||
|
button.classList.toggle('active', active);
|
||||||
|
button.setAttribute('aria-selected', String(active));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
renderPackage(button.dataset.skillFile);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPackage('skill');
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.package-workbench {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(190px, 0.85fr) minmax(0, 1.3fr);
|
||||||
|
min-width: 0;
|
||||||
|
background: var(--ink);
|
||||||
|
border: 1px solid var(--ink);
|
||||||
|
box-shadow: 10px 10px 0 color-mix(in srgb, var(--gold) 55%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-tree {
|
||||||
|
padding: 22px 16px;
|
||||||
|
/* token-gap: legacy 1px solid #426070 over --ink; no token matches; owner design-system-keeper */
|
||||||
|
border-right: 1px solid #426070;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-tree > p,
|
||||||
|
.package-preview > span {
|
||||||
|
margin: 0 0 14px;
|
||||||
|
color: var(--gold);
|
||||||
|
/* token-gap: source uses 11px monospace; no --step-* covers 11px on this surface; design-system-keeper */
|
||||||
|
font:
|
||||||
|
700 11px / 1.35 ui-monospace,
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-tree button {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 8px;
|
||||||
|
border: 0;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
/* token-gap: legacy #d6e1e4 over --ink tree buttons; no token matches; design-system-keeper */
|
||||||
|
color: #d6e1e4;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background 0.2s ease,
|
||||||
|
border-color 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-tree button:hover,
|
||||||
|
.package-tree button:focus-visible,
|
||||||
|
.package-tree button.active {
|
||||||
|
border-left-color: var(--gold);
|
||||||
|
/* token-gap: legacy #1f3a4b active/hover on --ink tree; no token matches; design-system-keeper */
|
||||||
|
background: #1f3a4b;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-tree button:hover {
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-tree code {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
font:
|
||||||
|
700 13px / 1.4 ui-monospace,
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-tree small {
|
||||||
|
/* token-gap: legacy #aebfc7 muted text on --ink tree; no token matches; design-system-keeper */
|
||||||
|
color: #aebfc7;
|
||||||
|
font:
|
||||||
|
11px / 1.25 Arial,
|
||||||
|
sans-serif;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-preview {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 26px;
|
||||||
|
/* token-gap: legacy #173245 preview surface (between --ink and --blue); no token matches; design-system-keeper */
|
||||||
|
background: #173245;
|
||||||
|
color: var(--paper);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-preview h3 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: clamp(24px, 3vw, 38px);
|
||||||
|
line-height: 1.02;
|
||||||
|
letter-spacing: -0.045em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-preview p {
|
||||||
|
max-width: 52ch;
|
||||||
|
margin: 0;
|
||||||
|
/* token-gap: legacy #d6e1e4 muted text on #173245 preview; no token matches; design-system-keeper */
|
||||||
|
color: #d6e1e4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-preview pre {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 20px 0 0;
|
||||||
|
padding: 15px;
|
||||||
|
overflow: auto;
|
||||||
|
/* token-gap: legacy #466274 rule on preview pre border; no token matches; design-system-keeper */
|
||||||
|
border: 1px solid #466274;
|
||||||
|
/* token-gap: legacy #102837 fill on preview pre background; no token matches; design-system-keeper */
|
||||||
|
background: #102837;
|
||||||
|
/* token-gap: legacy #d6e1e4 code text on #102837; no token matches; design-system-keeper */
|
||||||
|
color: #d6e1e4;
|
||||||
|
font:
|
||||||
|
12px / 1.55 ui-monospace,
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-preview.is-swapping {
|
||||||
|
animation: package-preview-in 0.34s ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes package-preview-in {
|
||||||
|
from {
|
||||||
|
opacity: 0.25;
|
||||||
|
transform: translateY(7px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.package-workbench {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.package-tree {
|
||||||
|
border-right: 0;
|
||||||
|
/* token-gap: legacy #426070 mobile layout rule over --ink; no token matches; design-system-keeper */
|
||||||
|
border-bottom: 1px solid #426070;
|
||||||
|
}
|
||||||
|
.package-preview {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* token-gap: legacy 520px breakpoint from skills/styles.css (mobile phone), not in named set 560/800/1100/1600/2200; design-system-keeper */
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.package-tree {
|
||||||
|
padding: 18px 10px;
|
||||||
|
}
|
||||||
|
.package-tree button {
|
||||||
|
padding: 12px 6px;
|
||||||
|
}
|
||||||
|
.package-tree small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.package-preview {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
.package-preview pre {
|
||||||
|
/* token-gap: legacy 11px mobile font-size from skills/styles.css; no --step-* covers 11px; design-system-keeper */
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
// /agents/ — chapter page. Migrated from agents/index.html in task 13.
|
||||||
|
// Identical URL (/agents/), zero client JS, copy lives in
|
||||||
|
// src/content/chapters/agents.json.
|
||||||
|
|
||||||
|
import { getEntry } from 'astro:content';
|
||||||
|
import ChapterLayout from '../layouts/ChapterLayout.astro';
|
||||||
|
import ChapterHero from '../components/blocks/ChapterHero.astro';
|
||||||
|
|
||||||
|
// Required-field guard. The chapters schema marks section eyebrow /
|
||||||
|
// panelLabel / panelCode / steps / copy as optional because the schema
|
||||||
|
// does not know which page consumes which shape. These four pages do
|
||||||
|
// consume them — fail loudly here rather than rendering a blank section.
|
||||||
|
function requireField<T>(value: T | undefined, name: string): T {
|
||||||
|
if (value === undefined) {
|
||||||
|
throw new Error(`agents chapter: missing required field "${name}"`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = import.meta.env.BASE_URL;
|
||||||
|
const chapter = await getEntry('chapters', 'agents');
|
||||||
|
if (!chapter) {
|
||||||
|
throw new Error('agents chapter: missing collection entry');
|
||||||
|
}
|
||||||
|
const lede = chapter.data.lede.en;
|
||||||
|
const title = chapter.data.title.en;
|
||||||
|
const eyebrow = chapter.data.eyebrow.en;
|
||||||
|
const cards = chapter.data.cards ?? [];
|
||||||
|
const sections = chapter.data.sections ?? [];
|
||||||
|
const treeSection = sections[0];
|
||||||
|
const handoffSection = sections[1];
|
||||||
|
if (!treeSection) {
|
||||||
|
throw new Error('agents chapter: missing sections[0]');
|
||||||
|
}
|
||||||
|
if (!handoffSection) {
|
||||||
|
throw new Error('agents chapter: missing sections[1]');
|
||||||
|
}
|
||||||
|
// Narrow the section fields this page renders. Each `requireField` either
|
||||||
|
// returns a non-null value or throws — TS narrows from `T | undefined` to `T`.
|
||||||
|
const treeEyebrow = requireField(treeSection.eyebrow, 'sections[0].eyebrow');
|
||||||
|
const treePanelLabel = requireField(treeSection.panelLabel, 'sections[0].panelLabel');
|
||||||
|
const treePanelCode = requireField(treeSection.panelCode, 'sections[0].panelCode');
|
||||||
|
const handoffEyebrow = requireField(handoffSection.eyebrow, 'sections[1].eyebrow');
|
||||||
|
const handoffSteps = requireField(handoffSection.steps, 'sections[1].steps');
|
||||||
|
---
|
||||||
|
|
||||||
|
<ChapterLayout title="AI For Dummies — Agents and trees" description={lede.replace(/<[^>]+>/g, '')}>
|
||||||
|
<a slot="top-previous" href={`${base}summary/`}>← ROUTE MAP</a>
|
||||||
|
<span slot="top-center">02 / AGENTS & TREES</span>
|
||||||
|
<a slot="top-next" href={`${base}full-guide/`}>field guide ↗</a>
|
||||||
|
|
||||||
|
<ChapterHero eyebrow={eyebrow}>
|
||||||
|
<span slot="title" set:html={title} />
|
||||||
|
<p>{lede}</p>
|
||||||
|
</ChapterHero>
|
||||||
|
|
||||||
|
<section class="pipeline">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">{treeEyebrow.en}</p>
|
||||||
|
<h2 set:html={treeSection.title.en} />
|
||||||
|
</div>
|
||||||
|
<div class="panel">
|
||||||
|
<strong>{treePanelLabel.en}</strong>
|
||||||
|
<code>{treePanelCode.en}</code>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="grid">
|
||||||
|
{
|
||||||
|
cards.map((card) => (
|
||||||
|
<article class="card">
|
||||||
|
<b>{card.label.en}</b>
|
||||||
|
<h2>{card.title.en}</h2>
|
||||||
|
<p>{card.copy.en}</p>
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="practice">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">{handoffEyebrow.en}</p>
|
||||||
|
<h2 set:html={handoffSection.title.en} />
|
||||||
|
</div>
|
||||||
|
<div class="steps">
|
||||||
|
{
|
||||||
|
handoffSteps.map((step, index) => (
|
||||||
|
<article>
|
||||||
|
<b>{String(index + 1).padStart(2, '0')}</b>
|
||||||
|
<div>
|
||||||
|
<strong>{step.label.en}</strong>
|
||||||
|
<span>{step.copy.en}</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav slot="footer-links" class="links" aria-label="Chapter navigation">
|
||||||
|
<a href={`${base}models/`}>Previous: models →</a>
|
||||||
|
<a href={`${base}rules/`}>Rules case study →</a>
|
||||||
|
<a href={`${base}hands-on/rules/`}>Try the rules lab →</a>
|
||||||
|
</nav>
|
||||||
|
</ChapterLayout>
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
// /models/ — chapter page. Migrated from models/index.html in task 13.
|
||||||
|
// Identical URL (/models/), zero client JS, copy lives in
|
||||||
|
// src/content/chapters/models.json. The grid + panel + steps sections
|
||||||
|
// render the chapter-collections content with bilingual `en` strings.
|
||||||
|
|
||||||
|
import { getEntry } from 'astro:content';
|
||||||
|
import ChapterLayout from '../layouts/ChapterLayout.astro';
|
||||||
|
import ChapterHero from '../components/blocks/ChapterHero.astro';
|
||||||
|
|
||||||
|
// Required-field guard. The chapters schema marks section eyebrow /
|
||||||
|
// panelLabel / panelCode / steps / copy as optional because the schema
|
||||||
|
// does not know which page consumes which shape. These four pages do
|
||||||
|
// consume them — fail loudly here rather than rendering a blank section.
|
||||||
|
function requireField<T>(value: T | undefined, name: string): T {
|
||||||
|
if (value === undefined) {
|
||||||
|
throw new Error(`models chapter: missing required field "${name}"`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = import.meta.env.BASE_URL;
|
||||||
|
const chapter = await getEntry('chapters', 'models');
|
||||||
|
if (!chapter) {
|
||||||
|
throw new Error('models chapter: missing collection entry');
|
||||||
|
}
|
||||||
|
const lede = chapter.data.lede.en;
|
||||||
|
const title = chapter.data.title.en;
|
||||||
|
const eyebrow = chapter.data.eyebrow.en;
|
||||||
|
const cards = chapter.data.cards ?? [];
|
||||||
|
const sections = chapter.data.sections ?? [];
|
||||||
|
// First section carries the routing-rule panel, second carries the steps.
|
||||||
|
const ruleSection = sections[0];
|
||||||
|
const sequenceSection = sections[1];
|
||||||
|
if (!ruleSection) {
|
||||||
|
throw new Error('models chapter: missing sections[0]');
|
||||||
|
}
|
||||||
|
if (!sequenceSection) {
|
||||||
|
throw new Error('models chapter: missing sections[1]');
|
||||||
|
}
|
||||||
|
const ruleEyebrow = requireField(ruleSection.eyebrow, 'sections[0].eyebrow');
|
||||||
|
const rulePanelLabel = requireField(ruleSection.panelLabel, 'sections[0].panelLabel');
|
||||||
|
const rulePanelCode = requireField(ruleSection.panelCode, 'sections[0].panelCode');
|
||||||
|
const sequenceEyebrow = requireField(sequenceSection.eyebrow, 'sections[1].eyebrow');
|
||||||
|
const sequenceSteps = requireField(sequenceSection.steps, 'sections[1].steps');
|
||||||
|
---
|
||||||
|
|
||||||
|
<ChapterLayout title="AI For Dummies — Models" description={lede.replace(/<[^>]+>/g, '')}>
|
||||||
|
<a slot="top-previous" href={`${base}summary/`}>← ROUTE MAP</a>
|
||||||
|
<span slot="top-center">01 / MODELS</span>
|
||||||
|
<a slot="top-next" href={`${base}full-guide/`}>field guide ↗</a>
|
||||||
|
|
||||||
|
<ChapterHero eyebrow={eyebrow}>
|
||||||
|
<span slot="title" set:html={title} />
|
||||||
|
<p>{lede}</p>
|
||||||
|
</ChapterHero>
|
||||||
|
|
||||||
|
<section class="grid">
|
||||||
|
{
|
||||||
|
cards.map((card) => (
|
||||||
|
<article class="card">
|
||||||
|
<b>{card.label.en}</b>
|
||||||
|
<h2>{card.title.en}</h2>
|
||||||
|
<p>{card.copy.en}</p>
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="model">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">{ruleEyebrow.en}</p>
|
||||||
|
<h2 set:html={ruleSection.title.en} />
|
||||||
|
</div>
|
||||||
|
<div class="panel">
|
||||||
|
<strong>{rulePanelLabel.en}</strong>
|
||||||
|
<code>{rulePanelCode.en}</code>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="practice">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">{sequenceEyebrow.en}</p>
|
||||||
|
<h2 set:html={sequenceSection.title.en} />
|
||||||
|
</div>
|
||||||
|
<div class="steps">
|
||||||
|
{
|
||||||
|
sequenceSteps.map((step, index) => (
|
||||||
|
<article>
|
||||||
|
<b>{String(index + 1).padStart(2, '0')}</b>
|
||||||
|
<div>
|
||||||
|
<strong>{step.label.en}</strong>
|
||||||
|
<span>{step.copy.en}</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav slot="footer-links" class="links" aria-label="Chapter navigation">
|
||||||
|
<a href={`${base}agents/`}>Next: agents & trees →</a>
|
||||||
|
<a href={`${base}rules/`}>Rules case study →</a>
|
||||||
|
</nav>
|
||||||
|
</ChapterLayout>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
// /skills/ — chapter page. Migrated from skills/index.html in task 13.
|
||||||
|
// Identical URL (/skills/). The package-anatomy picker is an Astro island
|
||||||
|
// (SkillPackageExplorer) — the only JS the page ships. Copy lives in
|
||||||
|
// src/content/chapters/skills.json.
|
||||||
|
|
||||||
|
import { getEntry } from 'astro:content';
|
||||||
|
import ChapterLayout from '../layouts/ChapterLayout.astro';
|
||||||
|
import ChapterHero from '../components/blocks/ChapterHero.astro';
|
||||||
|
import SkillPackageExplorer from '../components/islands/SkillPackageExplorer.astro';
|
||||||
|
import skillsStylesheet from '../../skills/styles.css?url';
|
||||||
|
|
||||||
|
// Required-field guard. The chapters schema marks section eyebrow /
|
||||||
|
// panelLabel / panelCode / steps / copy as optional because the schema
|
||||||
|
// does not know which page consumes which shape. These four pages do
|
||||||
|
// consume them — fail loudly here rather than rendering a blank section.
|
||||||
|
function requireField<T>(value: T | undefined, name: string): T {
|
||||||
|
if (value === undefined) {
|
||||||
|
throw new Error(`skills chapter: missing required field "${name}"`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = import.meta.env.BASE_URL;
|
||||||
|
const chapter = await getEntry('chapters', 'skills');
|
||||||
|
if (!chapter) {
|
||||||
|
throw new Error('skills chapter: missing collection entry');
|
||||||
|
}
|
||||||
|
const lede = chapter.data.lede.en;
|
||||||
|
const title = chapter.data.title.en;
|
||||||
|
const eyebrow = chapter.data.eyebrow.en;
|
||||||
|
const sections = chapter.data.sections ?? [];
|
||||||
|
const anatomySection = sections[0];
|
||||||
|
const createSection = sections[1];
|
||||||
|
if (!anatomySection) {
|
||||||
|
throw new Error('skills chapter: missing sections[0]');
|
||||||
|
}
|
||||||
|
if (!createSection) {
|
||||||
|
throw new Error('skills chapter: missing sections[1]');
|
||||||
|
}
|
||||||
|
const anatomyEyebrow = requireField(anatomySection.eyebrow, 'sections[0].eyebrow');
|
||||||
|
const anatomyCopy = requireField(anatomySection.copy, 'sections[0].copy');
|
||||||
|
const createEyebrow = requireField(createSection.eyebrow, 'sections[1].eyebrow');
|
||||||
|
const createSteps = requireField(createSection.steps, 'sections[1].steps');
|
||||||
|
---
|
||||||
|
|
||||||
|
<ChapterLayout title="AI For Dummies — Skills" description={lede.replace(/<[^>]+>/g, '')}>
|
||||||
|
<link slot="styles" rel="stylesheet" href={skillsStylesheet} />
|
||||||
|
<a slot="top-previous" href={`${base}summary/`}>← ROUTE MAP</a>
|
||||||
|
<span slot="top-center">03 / SKILLS</span>
|
||||||
|
<a slot="top-next" href={`${base}skills-review/`}>review desk ↗</a>
|
||||||
|
|
||||||
|
<ChapterHero eyebrow={eyebrow}>
|
||||||
|
<span slot="title" set:html={title} />
|
||||||
|
<p set:html={lede} />
|
||||||
|
</ChapterHero>
|
||||||
|
|
||||||
|
<section class="pipeline package-anatomy">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">{anatomyEyebrow.en}</p>
|
||||||
|
<h2 set:html={anatomySection.title.en} />
|
||||||
|
<p class="package-hint">{anatomyCopy.en}</p>
|
||||||
|
</div>
|
||||||
|
<SkillPackageExplorer />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="practice">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">{createEyebrow.en}</p>
|
||||||
|
<h2 set:html={createSection.title.en} />
|
||||||
|
</div>
|
||||||
|
<div class="steps">
|
||||||
|
{
|
||||||
|
createSteps.map((step, index) => (
|
||||||
|
<article>
|
||||||
|
<b>{String(index + 1).padStart(2, '0')}</b>
|
||||||
|
<div>
|
||||||
|
<strong>{step.label.en}</strong>
|
||||||
|
<span>{step.copy.en}</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav slot="footer-links" class="links" aria-label="Chapter navigation">
|
||||||
|
<a href={`${base}agents/`}>Agents & trees →</a>
|
||||||
|
<a href={`${base}rules/`}>Rules case study →</a>
|
||||||
|
<a href={`${base}skills-review/`}>Review submitted skills →</a>
|
||||||
|
<a href={`${base}full-guide/#create-skill`}>Full guide: skill forge →</a>
|
||||||
|
</nav>
|
||||||
|
</ChapterLayout>
|
||||||
Reference in New Issue
Block a user