---
name: section-builder
description: Use when adding a new section to the visual editor (a new file in admin/app/_lib/sections/definitions/ or its renderer). Walks the 8-step registration checklist and the hard render-pipeline rules. Auto-invokes when Claude is creating or editing files under app/_lib/sections/definitions/, app/(guest)/_components/sections/renderers/, or new section CSS.
allowed-tools: Read, Glob, Grep, Edit, Write, Bash
---

You are adding a new section to the visual editor. This module has
strict architectural rules — violating any of them breaks render
correctness, editor preview, or publish flow.

# Single source of truth

Read these BEFORE writing any code:

  **`admin/app/_lib/sections/CLAUDE.md`** — full pattern + render
  pipeline + color + typography rules

  **`admin/app/_lib/sections/INVARIANTS.md`** — 15 hard rules for
  the section system (resolve→validate→render order, slots-only
  element placement, preset changeStrategy discipline, etc.)

These docs are the contract. This skill is the playbook.

# Pick a reference section to copy

```bash
ls admin/app/_lib/sections/definitions/
```

Pick one with similar shape (free vs locked, single-preset vs
multi-preset, blocks vs standalone). Common references:
- `accordion.ts` — free section, multi-preset
- A locked section under `definitions/` matching `scope: "locked"` —
  for platform-owned sections bound to a specific page

Read the reference end-to-end. Copy structurally — don't approximate.

# The 8-step registration checklist

Per `_lib/sections/CLAUDE.md` "Registration checklist":

## 1. Create the definition file

Path: `admin/app/_lib/sections/definitions/<section-id>.ts`

Required exports / structure (verify against `_lib/sections/types.ts`
for the current SectionDefinition shape):

```typescript
import { registerSectionDefinition } from "../registry";

const mySection: SectionDefinition = {
  id: "kebab-case-id",            // unique, kebab-case
  version: "1.0.0",
  name: "Svensk UI-etikett",      // Swedish per project convention
  description: "kort beskrivning för section picker",
  category: "hero" | "navigation" | "content" | "media" | "utility",
  tags: ["searchable", "keywords"],
  thumbnail: "",
  scope: "free",                  // or "locked" + lockedTo: <PageId>

  settingsSchema: [...],          // section-level controls
  settingDefaults: {...},

  presets: [...],                 // min 1, first is default
  createDefault: () => ({...}),
};

registerSectionDefinition(mySection);
```

## 2. Call registerSectionDefinition

LAST line of the definition file. Without this call the section
never reaches the registry — silent failure.

## 3. Create the renderer

Path: `admin/app/(guest)/_components/sections/renderers/<SectionName>Renderer.tsx`

```tsx
"use client";  // REQUIRED — renderers use hooks

import "./section-name-renderer.css";
import type { SectionRendererProps } from "@/app/_lib/sections/types";

export function MySectionRenderer(props: SectionRendererProps) {
  const { section, settings, presetSettings, blocks } = props;

  // Render using CSS variables ONLY — no hardcoded colors
  // Read from settings/presetSettings/blocks as fully-resolved
  // No fallback logic — invalid data never reaches you
  return <section className={`s-mysection ${preset.cssClass}`}>...</section>;
}
```

## 4. Create the CSS file

Path: same dir as renderer, kebab-case.
Class naming: BEM with `s-{section-id}` prefix.
Preset class: `s-{section-id}--{preset-key}` (matches
`SectionPreset.cssClass`).

## 5. Import the CSS in the renderer

Top of the renderer .tsx file. Without the import the section
renders unstyled.

## 6. CSS variables — zero hardcoded colors

Per `_lib/sections/CLAUDE.md` "Color scheme rules":

  --background, --text, --button-bg, --button-fg,
  --outline-btn, --outline-btn-label

Derived colors via `color-mix()`:

```css
border-color: color-mix(in srgb, var(--text) 12%, transparent);
background: color-mix(in srgb, var(--text) 4%, var(--background));
```

If you find yourself writing `#0075DE` or any hex literal in the
section CSS, stop and use the appropriate scheme variable.

## 7. Typography via clamp() and font variables

```css
font-size: clamp(1.5rem, 1.25rem + 1vw, 2rem);
font-family: var(--font-heading);  /* or --font-body, --font-button */
color: var(--text);
```

No fixed-px headings. No hardcoded font names.

## 8. Test in the editor

Restart the dev server using the project's strict 4-step procedure
(per `admin/CLAUDE.md` "Development"):

```bash
fuser -k 3000/tcp 3001/tcp 2>&1 || true
fuser 3000/tcp 2>&1 || echo "(3000 free)"   # MUST be free before continuing
fuser 3001/tcp 2>&1 || echo "(3001 free)"   # MUST be free before continuing
rm -rf admin/.next
cd admin && npm run dev
```

(The `/restart-dev` slash command codifies this if it's installed —
prefer it when available.)

Then in the browser:
1. Open the editor
2. Add the new section from the picker
3. Confirm preview renders correctly
4. Edit each settingsSchema field — verify changes apply
5. If multi-preset, switch presets — verify changeStrategy behaves
6. Save draft → publish → confirm guest-side render

# Hard rules from INVARIANTS.md (do NOT violate)

These are non-negotiable per `_lib/sections/INVARIANTS.md`:

1. **resolve → validate → render**, always in that order
2. **Renderers contain NO fallback logic** — no `?? "default"`,
   no `|| fallbackColor`. If a renderer needs a fallback, the
   resolve step is broken.
3. **Invalid data never reaches a renderer** — `resolveSection()`
   returns `null` for invalid sections.
4. **Actions only valid where `supportsAction: true`**.
5. **Content lives in `settings`. Behaviour lives in `action`.**
   Never put URLs/modal IDs in settings; never put text/colors in
   action.
6. **Slots are the only structure for element placement.** Renderers
   access `slots.media.elements`, `slots.content.elements` — never
   iterate "all elements".
7. **Preset switching goes through `changeStrategy`** — never mutate
   blocks directly when changing presets.
8. **All definitions carry semver versions** —
   `ElementDefinition.version`, `BlockTypeDefinition.version`,
   `SectionPreset.version`, `SectionDefinition.version`.
9. **Element library stays small** — six types only:
   `heading`, `text`, `button`, `image`, `divider`, `icon`. New
   types require strong justification.
10. **Build few, build well** — target 3-5 section families first;
    each must work end-to-end (editor, validation, render, preset
    switch, publish).

# Failure modes to avoid

- **Skipping the reference read.** Always copy from an existing
  definition. Approximation produces broken sections.
- **Hardcoded colors.** Even one hex literal breaks the color
  scheme contract.
- **Forgetting `registerSectionDefinition()`.** Silent failure —
  section never appears in the picker.
- **`"use client"` missing on the renderer.** Hooks won't run; the
  section breaks at runtime.
- **Missing CSS import in the renderer.** Section renders unstyled
  but the editor preview "works" — easy to ship.
- **Skipping editor verification.** TypeScript happy ≠ section
  works. Always exercise the section in the editor before
  committing.

# Composition with other layers

When the section ships:
- `code-reviewer` agent will audit Component reuse + CSS reuse
  rules (universal)
- `invariant-auditor` against `_lib/sections/CLAUDE.md` will audit
  the section-specific invariants
- The eslint hook will fire on the .ts and .tsx files

Run `/audit-pr` before push to get all three in one shot.
