Section registry
The registry as the single source of truth for section labels, variants, tones, and default content — and how to add a section type.
This page is about the section registry, not MCP. The "registry" here is the section registry (
lib/sections/registry.ts) — a plain TypeScript data structure — not a Model Context Protocol server, tool registry, or plugin host. It replaces an earlier, mistaken "MCP server" page. Sajt does not expose an MCP server today; an outbound "AI integrations" MCP server is a separate, planned capability (seeprd.md) and is unrelated to this registry.
What the registry is
lib/sections/registry.ts (SECTION_REGISTRY) is the single source of truth
for everything the editor and generator need to know about a section type. Each
entry (SectionDef) carries:
| Field | Purpose |
|---|---|
type | The discriminant ("hero", "services", …) |
label | Plain-language { sv, en } name shown in the editor |
whenToUse | One-line { sv, en } "when to use this block" guidance |
category | Add-section grouping: intro / services / trust / content / contact / structure |
icon | A lucide icon name |
variants | The allow-listed layout variants for this type |
defaultVariant | The variant a new section starts with |
defaultTone | The tone a new section starts with |
allowedTones | The tones the editor offers for this type (subset of light / clear / dark) |
defaultContent(lang) | A factory returning a valid SectionContent for a new section |
There are ~33 section types (hero, services, service-detail, about, team, testimonials, gallery, before-after, pricing, faq, process, service-areas, contact, opening-hours, location, certifications, social-proof, instagram, cta-band, booking, lead-form, footer, legal, logos, highlights, bento, banner, video, comparison, newsletter, statement, rich-text, image).
Variants and tones are validated server-side
Variants in the registry are the allow-list, and convex/sections.ts enforces
them on every write, so a tampered client can never store an unknown variant or a
tone outside allowedTones. The helpers:
isValidVariant(type, variant); // variant ∈ registry variants for `type`
isValidTone(type, tone); // tone ∈ allowedTones for `type`
resolveTone(type, stored); // stored tone, else the defaultARRAY_DEFAULTS / arrayDefaultFor(type, field, lang) supply default items so
the canvas can add a service / FAQ / step without a dialog. Only text-bearing
arrays are listed — image arrays (gallery / instagram images, before-after pairs)
grow by uploading, since an empty assetRef is invalid. ARRAY_ITEM_MAX caps
list length at 24.
Three structures, one section type
A section type lives in three places that must agree:
- The content union —
convex/model/sections.tsholds a discriminatedv.unionkeyed bytype. Convex validates every write, so malformed content can never reach a snapshot. No raw HTML or raw URLs are stored — links are typedtargetunions and icons are an allow-listed enum. - The registry entry —
lib/sections/registry.ts, as above. - The renderer case —
components/site-sections/SectionRenderer.tsxmaps{ type, variant, tone, content }→ a React component. This one shared renderer drives the editor canvas,/preview,/p/<token>, and the public/site, which is what guarantees preview == production. Never fork it. See the editor & publishing model.
How to add a section type
- Add the type's
v.object({ type: v.literal("x"), … })to the union inconvex/model/sections.tsand the literal toSECTION_TYPES. - Add a registry entry in
lib/sections/registry.ts(label,whenToUse, category, icon,variants,defaultVariant,defaultTone,allowedTones,defaultContent). - Create
components/site-sections/x/X.tsxtakingSectionComponentProps<"x">, styling only via--site-*CSS variables. - Add a
case "x":toSectionRenderer.tsx. The switch is exhaustive, so TypeScript errors until you do. - Run
bunx convex dev --onceto regenerate types, thenbun run test && bun run lint.
Because the editor's side panel is generated schema-driven from a section's
content (lib/editor/extractFields.ts + imageSlotsFor), a new section type
gets an editor form for free — no bespoke form per type.
whenToUse doubles as the LLM block-selector spec
The whenToUse field is shown under the label in the add-section picker and
is the exact specification an LLM block-selector reads when choosing which blocks
fit a given business (aiSelectBlocks.selectHomeBlocks). Writing a precise,
plain-language whenToUse therefore improves both the human picker and the AI's
block selection. The AI may reorder or hide blocks but never invents a block type
outside the union — its output is coerced back to the sectionType literal set.
See the AI system.