typecheck
"The template must compile under TypeScript."
Venue: check — The template must compile under TypeScript.
The template’s TypeScript must compile cleanly. The check resolves typescript from your
workspace’s node_modules — the compiler that runs is the one you installed, at the
version you pinned, against your own tsconfig.json.
If typescript is not installed, that is itself the diagnostic (with the fix), not a crash.
Reason
Section titled “Reason”The schema system is the section contract, and it is enforced through the type system.
SectionProps<typeof schema> is not a convenience — it is the mechanism that keeps
section.ts and Renderer.tsx in agreement: a slot you add to the schema and forget to
render, a slot you render under the wrong name, an optional slot you treat as always
present, a slot whose type you assume wrong. Each of those is a compile error rather than a
surprise on a stranger’s published page.
A template that does not typecheck has opted out of the only thing checking that the two files still describe the same section.
Fix the type error. If the compiler itself is missing, install it and point your
tsconfig.json at the kit’s base config, which sets the module resolution and JSX settings
the kit’s types expect:
{ "extends": "@homepages/template-kit/tsconfig", "include": ["sections", "theme.ts"] }npm install --save-dev typescriptBefore
Section titled “Before”year_built is a number slot, so SectionProps resolves it to number | null —
a number slot’s canonical blank IS null — and the Renderer treats it as a number:
import { bindSlots, Section, Slot } from "@homepages/template-kit";
import { schema } from "./section.js";import type { Props } from "./section.js";
export function Renderer({ slots }: Props) { const slot = bindSlots(schema, slots);
return ( <Section> {/* error TS18047: 'slots.year_built' is possibly 'null'. */} <Slot.Number slot={slot.year_built} as="p">Built {slots.year_built.toFixed(0)}</Slot.Number> </Section> );}That is not a pedantic complaint. A thin listing really does arrive with no year, and this line really does throw on it.
import { bindSlots, defineSchema, number, Section, Slot } from "@homepages/template-kit";import type { SectionProps } from "@homepages/template-kit";
const schema = defineSchema({ label: "Facts", anchor: "facts", slots: { year_built: number({ label: "Year built" }) },});
type Props = SectionProps<typeof schema>;
export function Renderer({ slots }: Props) { const slot = bindSlots(schema, slots);
return ( <Section> <Slot.Number slot={slot.year_built} as="p" format={(year) => `Built ${year.toFixed(0)}`} /> </Section> );}A format callback is typed (value: NonNullable<…>) => ReactNode and is only ever called
on a non-blank value, so year is a number inside it and the .toFixed compiles. An
absent year renders the marked empty <p>; branch on the binding’s present if you want
something else there.
There is no schema declaration that narrows the type instead. Every slot is nullable, because a renderer receives an empty slot whatever the schema says.
See also
Section titled “See also”Renderer.tsx— howSectionPropsresolves each slot to its runtime type.props-from-schema— the rule that stops a hand-writtenPropsfrom disabling this inference in the first place.render-invariant— the same class of bug caught at render time, for the cases the types cannot see.