/* [RFC-0102] Canonical SectionHeader contract — structured heading (string or tone-segmented array), optional subheading, independent alignment, optional section number, optional level override. Do not declare body content (RFC-0103) or CTA shape (RFC-0104). Do not couple header alignment to body alignment (each is independent). RFC-0133: backfilled MODULE_MAP and CHANGE_SUMMARY markers for compass.validate compliance. RFC-1196: step 1 — sectionHeadingId() resolver + sectionId prop */ import { z } from "zod"; import { horizontalAlignSchema } from "./horizontal-align.ts"; import { effectStackSchema } from "./effects.ts"; export const headingToneSchema = z.enum(["default", "primary", "accent", "muted", "inverse"]); export type HeadingTone = z.infer; export const headingSegmentSchema = z .object({ text: z.string().min(1), tone: headingToneSchema.optional(), }) .strict(); export type HeadingSegment = z.infer; /** * RFC-0102 — HeadingContent. Either a plain string (entire title renders in * tone "default") or an array of tone-segmented runs that the renderer wraps * in . */ export const headingContentSchema = z.union([ z.string().min(1), z.array(headingSegmentSchema).min(1), ]); export type HeadingContent = z.infer; export const sectionHeaderLevelSchema = z.union([z.literal(1), z.literal(2)]); export type SectionHeaderLevel = z.infer; export const sectionHeaderSchema = z .object({ /** Resolved upstream by blocks-renderer. */ sectionNumber: z.string().optional(), /** Suppress the section number even if blocks-renderer supplied one. */ hideSectionNumber: z.boolean().optional(), heading: headingContentSchema, /** RFC-0567 — short contextual label rendered above the heading. */ eyebrow: z.string().optional(), /** One short paragraph under the title. Longer copy belongs in body.kind: paragraphs (RFC-0103). */ subheading: z.string().optional(), /** Independent header alignment; default "left". */ align: horizontalAlignSchema.optional(), /** h1 for hero archetype, h2 otherwise. Default 2. */ level: sectionHeaderLevelSchema.optional(), /** Used as aria-labelledby target by . */ id: z.string().optional(), /** * RFC-1196 — per-instance anchor id resolved via resolveSectionAnchor * (= blockId, RFC-0914). Uniqueness fallback for heading ids when * `sectionNumber` is empty — consumed by sectionHeadingId(). */ sectionId: z.string().optional(), /** * RFC-0151 — resolved typographic effect stack for the `heading` target. * Section composites pass resolveEffectsForTarget(effects, "heading"). * When absent or empty, the header renders exactly as before. */ headingEffects: effectStackSchema.optional(), /** * RFC-0151 — resolved typographic effect stack for the `subheading` target. */ subheadingEffects: effectStackSchema.optional(), }) .strict(); export type SectionHeaderProps = z.infer;