> ## Documentation Index
> Fetch the complete documentation index at: https://docs.propal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Theming

> Three creation modes, plus apply, detach, named slots, and per-section overrides.

The MCP exposes the same theming model the editor uses — palette + \~3 section
ambiences + typography + radii — through a layered set of tools that match the
agent's intent.

## The three layers

| Layer       | What it controls                                                                     | Where it's stored                         |
| ----------- | ------------------------------------------------------------------------------------ | ----------------------------------------- |
| **Theme**   | Page palette, fonts, radius, plus a list of "section ambiences"                      | `themes` table (per org)                  |
| **Section** | Which ambience this section adopts (`section_style`), plus optional background image | `data-*` attrs on the TipTap section node |
| **Page**    | Fallback when a section opts out (`section_style: 'default'`)                        | The theme's page-level tokens             |

A proposal references a theme via `theme_id`. If absent, it falls back to a
legacy `proposal_style` jsonb on the proposal row.

## Three modes of creating a theme

### 1. Preset (assisted)

Pick from the catalog and snapshot it. Lowest effort — the catalog has \~25
curated palettes (matcha, ocean, ink, midnight, …) covering most use cases.

```
list_design_presets
→ [{ id: 'matcha', sample: { pageBg, accent, sectionBgs }, fonts, radius }, ...]

create_theme_from_preset
  preset_id: 'matcha'
  name: 'Acme matcha'
  overrides: { titleFont: 'satoshi' }     // optional
```

### 2. Smart palette (generated)

Provide a mini-palette, the server generates a full WCAG-compliant theme via
`chroma-js` — including accent solid/subtle variants, badges, and 3 section
ambiences automatically derived from the section backgrounds.

```
generate_theme_from_palette
  name: 'Brand 2026'
  page_bg: '#FAFBFE'
  accent: '#2563EB'
  section_bgs: ['#EEF2FF', '#1E293B', '#D4DCF0']  // optional, 1–3 entries
  title_font: 'inter'
  body_font: 'inter'
  radius: 0.8
  button_radius: 0.5

→ { theme: { id, ... }, warnings: [{ pair, contrast, message }] }
```

The same input can be validated without persisting:

```
validate_palette
  page_bg: '#FAFBFE'
  accent: '#FFFF00'   // bad: low contrast on white
→ { ok: false, warnings: [{ pair: 'accent / page_bg', contrast: 1.07, ... }] }
```

### 3. Advanced (full control)

When the agent needs to set every token explicitly — e.g. matching a brand
guideline that doesn't fit smart-mode generation — use `create_theme` with
the typed `colors` and `section_styles` arrays.

```
create_theme
  name: 'Bespoke 2026'
  title_font: 'plus_jakarta'
  body_font: 'inter'
  radius: 1.2
  button_radius: 0.5
  colors: {
    bg: '#0E0E12',
    headingColor: '#F5F5F7',
    textColor: '#A1A1A6',
    cardBg: '#16161B',
    cardHeadingColor: '#F5F5F7',
    cardTextColor: '#C7C7CC',
    cardBorderColor: '#1E1E24',
    accentColor: '#7B2D3B',
    accentBg: '#2E1318',
    accentContrast: '#FFFFFF',
    accentSolidBg: '#7B2D3B',
    accentSolidText: '#FFFFFF',
    accentSubtleBg: '#2E1318',
    accentSubtleText: '#F5F5F7'
  }
  section_styles: [
    {
      id: 'soft',           // ← optional but recommended
      bg: '#16161B', headingColor: '#F5F5F7', textColor: '#C7C7CC',
      borderColor: '#1E1E24',
      cardBg: '#1E1E24', cardHeadingColor: '#F5F5F7',
      cardTextColor: '#C7C7CC', cardBorderColor: '#2A2A33'
    },
    { id: 'deep', /* ... */ },
    { id: 'highlight', /* ... */ }
  ]
```

Patch later with `update_theme` (full PATCH semantics) or with the more
targeted `update_theme_section_style`:

```
update_theme_section_style
  theme_id: '<uuid>'
  slot_index: 0           // 0-based — replaces section_styles[0]
  tokens: { bg: '...', ... }
```

## Named slot ids — the recommended way

Each entry in `section_styles[]` accepts an optional `id` (1-32 chars,
`[a-zA-Z][a-zA-Z0-9_-]*`). Reserved values: `"default"` and any digit-only
string. Ids must be unique per theme.

When sections reference a slot by id (`section_style: "deep"`), the server
resolves it to the positional index before assembly. **Survives reordering**:
if you later reorganize `section_styles[]`, sections that use named refs keep
pointing at the same ambience. Sections that use positional refs (`"1"`,
`"2"`) silently shift.

The reverse mapping is also done in `get_proposal_tree`: positional refs are
converted back to ids in the response when ids exist.

## Apply / detach

```
apply_theme_to_proposal
  proposal_id: '<uuid>'
  theme_id: '<uuid>'
```

```
detach_theme_from_proposal
  proposal_id: '<uuid>'
  snapshot_style: true      // default true: preserve current rendering
                            // by writing the resolved style to proposal_style
```

If `snapshot_style: false`, the proposal falls back to its legacy
`proposal_style` (or defaults if absent) — useful when you want to "reset" a
proposal to a different visual baseline.

## Read the resolved style

```
get_proposal_resolved_style
  proposal_id: '<uuid>'
→ {
    titleFont, bodyFont,
    radius, buttonRadius,
    colors: { ...IColorTokens },
    sectionStyles: ISectionStyleTokens[],
    // legacy fields surfaced if they apply
  }
```

The resolution chain:

1. If `proposal.theme_id` is set → build the style from the theme row.
2. Else if `proposal.proposal_style` is set → parse it.
3. Else → defaults from `@propal/core/theming`.

## Per-section overrides

Sometimes you want one section to break the theme — e.g. a hero with a
background image and a different ambience. Use `set_proposal_section_attrs`:

```
set_proposal_section_attrs
  proposal_id: '<uuid>'
  index: 0                      // positional, refetch via get_proposal_tree
  attrs: {
    section_style: 'deep',      // named ref → resolved server-side
    background_image: {
      media_id: '<asset uuid>', // must exist in the org's media library
      overlay: true,
      overlay_opacity: 60,
      size: 'cover',
      position_x: 'center',
      position_y: 'top'
    }
  }
```

This is a **patch** — only the keys you provide are merged onto the section's
attrs. Children are untouched.

## Tips

* **Generate first, refine later.** `generate_theme_from_palette` produces
  WCAG-safe results in seconds. Use `update_theme_section_style` to tweak
  individual slots if needed.
* **Always name your slots.** It's a 5-character habit (`id: "deep"`) that
  saves debugging when you reorder slots later.
* **Check the warnings.** `generate_theme_from_palette` returns WCAG warnings
  inline — surface them to the user before persisting if contrasts are weak.
* **Don't mix advanced and smart in one go.** If you start with
  `generate_theme_from_palette` and want to tweak, prefer
  `update_theme_section_style` for surgical changes; full `update_theme`
  PATCH still works but rewrites are noisier.
