Snabbsite · Docs
For developers

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. Snabbsite's outbound AI integrations MCP surface now lives separately at /api/mcp (API tokens and Clerk OAuth 2.1 are implemented; production OAuth credentials still require owner configuration). It is unrelated to this registry. See MCP tools & scopes.

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 semantic icon key resolved to the approved Tabler icon set
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
availability?"core" (default) or "restricted". Restricted types render everywhere and stay editable, but only users with the advanced-editor capability can add them, and the AI never plans them

There are ~39 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, documents, scroll-tabs, comparison-slider, the last of which is restricted).

Snabbsite does not accept Lucide icon names. If you ask for a Lucide icon, use its meaning to choose the closest Tabler equivalent and store the stable semantic key instead of a library component name.

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 union - convex/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 entry - lib/sections/registry.ts, as above.
  3. The renderer case - components/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

The scaffolder generates all the touchpoints in one step:

bun run section:new <kebab-type> [--restricted]

It inserts the content-union shape, the SECTION_TYPES literal, the registry entry (with availability: "restricted" when flagged), a renderer stub, and the SectionRenderer case via section:new-*-anchor comments. You then shape the typed fields and implement the renderer. The manual steps, for reference:

  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.


Didn't find the answer, or is something wrong here? Tell us.

On this page