Sajt · Docs

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 (see prd.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:

FieldPurpose
typeThe discriminant ("hero", "services", …)
labelPlain-language { sv, en } name shown in the editor
whenToUseOne-line { sv, en } "when to use this block" guidance
categoryAdd-section grouping: intro / services / trust / content / contact / structure
iconA lucide icon name
variantsThe allow-listed layout variants for this type
defaultVariantThe variant a new section starts with
defaultToneThe tone a new section starts with
allowedTonesThe 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 default

ARRAY_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:

  1. The content unionconvex/model/sections.ts holds a discriminated v.union keyed by type. Convex validates every write, so malformed content can never reach a snapshot. No raw HTML or raw URLs are stored — links are typed target unions and icons are an allow-listed enum.
  2. The registry entrylib/sections/registry.ts, as above.
  3. The renderer casecomponents/site-sections/SectionRenderer.tsx maps { 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

  1. Add the type's v.object({ type: v.literal("x"), … }) to the union in convex/model/sections.ts and the literal to SECTION_TYPES.
  2. Add a registry entry in lib/sections/registry.ts (label, whenToUse, category, icon, variants, defaultVariant, defaultTone, allowedTones, defaultContent).
  3. Create components/site-sections/x/X.tsx taking SectionComponentProps<"x">, styling only via --site-* CSS variables.
  4. Add a case "x": to SectionRenderer.tsx. The switch is exhaustive, so TypeScript errors until you do.
  5. Run bunx convex dev --once to regenerate types, then bun 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.

On this page