diff --git a/src/components/islands/SkillPackageExplorer.astro b/src/components/islands/SkillPackageExplorer.astro new file mode 100644 index 0000000..8552734 --- /dev/null +++ b/src/components/islands/SkillPackageExplorer.astro @@ -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', + }, +]; +--- + +
+
+

REVIEW-UI / SKILL PACKAGE

+ { + packageFiles.map((file, index) => ( + + )) + } +
+
+
+ + + + diff --git a/src/pages/agents.astro b/src/pages/agents.astro new file mode 100644 index 0000000..00d207a --- /dev/null +++ b/src/pages/agents.astro @@ -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(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'); +--- + +]+>/g, '')}> + ← ROUTE MAP + 02 / AGENTS & TREES + field guide ↗ + + + +

{lede}

+
+ +
+
+

{treeEyebrow.en}

+

+

+
+ {treePanelLabel.en} + {treePanelCode.en} +
+
+ +
+ { + cards.map((card) => ( +
+ {card.label.en} +

{card.title.en}

+

{card.copy.en}

+
+ )) + } +
+ +
+
+

{handoffEyebrow.en}

+

+

+
+ { + handoffSteps.map((step, index) => ( +
+ {String(index + 1).padStart(2, '0')} +
+ {step.label.en} + {step.copy.en} +
+
+ )) + } +
+
+ + +
diff --git a/src/pages/models.astro b/src/pages/models.astro new file mode 100644 index 0000000..041166f --- /dev/null +++ b/src/pages/models.astro @@ -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(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'); +--- + +]+>/g, '')}> + ← ROUTE MAP + 01 / MODELS + field guide ↗ + + + +

{lede}

+
+ +
+ { + cards.map((card) => ( +
+ {card.label.en} +

{card.title.en}

+

{card.copy.en}

+
+ )) + } +
+ +
+
+

{ruleEyebrow.en}

+

+

+
+ {rulePanelLabel.en} + {rulePanelCode.en} +
+
+ +
+
+

{sequenceEyebrow.en}

+

+

+
+ { + sequenceSteps.map((step, index) => ( +
+ {String(index + 1).padStart(2, '0')} +
+ {step.label.en} + {step.copy.en} +
+
+ )) + } +
+
+ + +
diff --git a/src/pages/skills.astro b/src/pages/skills.astro new file mode 100644 index 0000000..9e9518b --- /dev/null +++ b/src/pages/skills.astro @@ -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(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'); +--- + +]+>/g, '')}> + + ← ROUTE MAP + 03 / SKILLS + review desk ↗ + + + +

+ + +

+
+

{anatomyEyebrow.en}

+

+

{anatomyCopy.en}

+

+ +
+ +
+
+

{createEyebrow.en}

+

+

+
+ { + createSteps.map((step, index) => ( +
+ {String(index + 1).padStart(2, '0')} +
+ {step.label.en} + {step.copy.en} +
+
+ )) + } +
+
+ + +