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. SnabbSajt'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:
| 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 |
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).
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
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:
- 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.
Hittade du inte svaret, eller stämmer något inte? Berätta det för oss.
Senast uppdaterad