c19e77bcac
The chapters schema marks section.eyebrow / panelLabel / panelCode / steps / copy as optional, but pages /agents/, /models/, /skills/ consume them — index access without narrowing failed astro check with ts(18048). Same for SkillPackageExplorer: aria-selected was String(bool) which widened to plain string and failed ts(2322) against ButtonHTMLAttributes. Fix: guard each required field with a helper that throws a clear message on missing data, then read .en / .map from the narrowed value. Page rendering is unchanged — dist/ HTML for all four files is byte-identical before and after. No any / as any / ! / @ts-ignore. Schema and tsconfig untouched.
107 lines
3.6 KiB
TypeScript
107 lines
3.6 KiB
TypeScript
---
|
|
// /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>
|