# Migration: Adopting the Local CMS Module

**Audience:** app maintainers who have vendored `freshvibe-cms` and want to override per-app config without forking the package.

**What it is:** the Local CMS Module is a per-app `/cms-local/` directory that the vendored CMS reads at boot time. It contains 6 JSON files + optional CSS + optional JS. The loader deep-merges them with the vendored defaults. Missing files are silently skipped (vendored default used).

**What you get:**
- Per-app theme (light/dark tokens, fonts)
- Per-app panel layout (default size, snap threshold)
- Per-app role + permission rules
- Per-app context rules (which chrome shows where)
- Per-app AI config (model, system prompt, tool allowlist)
- Per-app dock config (edge widths, mobile behavior)

**What you DON'T get:** the Local CMS Module is **read-only** from the CMS side. The vendored CMS never writes to `/cms-local/`. It's your config to own.

---

## Step 0 — Confirm the vendored CMS version

The Local CMS Module loader ships in freshvibe-cms v1.2.0+. Check your vendored copy's version:

```bash
cat FV-CMS-VERSION.txt
# Expected: 1.2.0 or higher
```

If your version is older, run `fvcms-update` first. (See `docs/DEPLOY.md`.)

---

## Step 1 — Create the `/cms-local/` directory

In your app's deployed dist (or wherever the chrome loader resolves relative to):

```
your-app/
└── cms-local/
    ├── cms-theme.json
    ├── cms-panel-layout.json
    ├── cms-permissions.json
    ├── cms-context-rules.json
    ├── cms-ai-config.json
    ├── cms-dock-config.json
    ├── cms-overrides.css   (optional)
    └── cms-overrides.js    (optional, not yet implemented in v1)
```

You can start with **all 6 files empty** (with just the `schema` field). The loader will pick them up and find that the merge is a no-op, so nothing changes.

---

## Step 2 — Add the `schema` field to each JSON file

Each file MUST have a `schema` field as its first key. This is the loader's version handshake (per Pact §3.18.3).

```json
{
  "schema": "fvcms.cms-local.theme.001",
  "tokens": {}
}
```

The 6 schema IDs are:
- `fvcms.cms-local.theme.001`
- `fvcms.cms-local.panel-layout.001`
- `fvcms.cms-local.permissions.001`
- `fvcms.cms-local.context-rules.001`
- `fvcms.cms-local.ai.001`
- `fvcms.cms-local.dock.001`

---

## Step 3 — Override what you need

Each file's content shape is defined by its schema in `freshvibe-cms/schemas/cms-local.*.schema.json`. The loader does a **deep-merge** with the vendored defaults (except for arrays, which replace). Read the schemas for the full shape; here are the most common overrides:

### Theme — change the accent color

```json
{
  "schema": "fvcms.cms-local.theme.001",
  "tokens": {
    "fvcms-accent": "#1e88e5",
    "fvcms-accent-fg": "#ffffff"
  }
}
```

### Dock — force mobile to dock at the bottom

```json
{
  "schema": "fvcms.cms-local.dock.001",
  "defaultEdge": "left",
  "mobile": {
    "dockAt": "bottom",
    "width": "100vw"
  }
}
```

### AI — use a different model

```json
{
  "schema": "fvcms.cms-local.ai.001",
  "model": {
    "provider": "openai",
    "name": "gpt-4o-mini",
    "temperature": 0.3
  }
}
```

### Permissions — add a new role

```json
{
  "schema": "fvcms.cms-local.permissions.001",
  "roles": {
    "editor": { "label": "Editor", "color": "#1e88e5" }
  },
  "rules": [
    { "role": "editor", "action": "edit", "target": "*", "effect": "allow" }
  ]
}
```

---

## Step 4 — Verify the diff

Run the vendored CMS's diff script to confirm your files parse and validate:

```bash
node node_modules/@freshvibe/cms/scripts/cms-local-diff.mjs ./cms-local/ ./node_modules/@freshvibe/cms/app-fragments/cms-local-defaults/
```

Output:
```
Local CMS Module diff
  Local:    ./cms-local/
  Defaults: ./node_modules/@freshvibe/cms/app-fragments/cms-local-defaults/

  ✓ cms-theme.json: schema=fvcms.cms-local.theme.001, 2 top-level keys overridden
  ✓ cms-panel-layout.json: schema=fvcms.cms-local.panel-layout.001, 0 top-level keys overridden
  ...

OK
```

---

## Step 5 — Test the chrome

Boot your app, open the dev panel, confirm the chrome behaves as expected:
- Theme tokens applied (panel background, accent color)
- Panel default size matches your override
- Dock behaviour on mobile matches your `mobile.dockAt`

If something's wrong, check the browser console — the loader emits `[fvcms-local] <reason>` warnings for any issue.

---

## Fallback behaviour (Pact §3.18.2)

The loader is **fail-safe**:
- File missing → silent. Vendored default used.
- Network error → log warning, vendored default used. Page doesn't block.
- Parse error → log warning, vendored default used.
- Schema version newer than loader knows → forward compat, file skipped, warning logged.
- Schema version older → file used, warning logged (you should upgrade).

Your page NEVER fails to boot because of a `/cms-local/` problem.

---

## Rollback

To roll back a single override: delete the file. The vendored default applies.
To roll back the entire module: delete the `/cms-local/` directory. The vendored defaults apply for everything.

There is no "disable" flag. The Local CMS Module is opt-in by virtue of the directory existing.

---

## See also

- `app-pact.md` §3.18 — Local CMS Module contract
- `app-pact.md` invariants I-017, I-018, I-019
- `schemas/cms-local.*.schema.json` — the canonical schemas
- `runtime/cms-local-loader.js` — the loader source
- `tests/cms-local-loader.mjs` — 12 test cases for the loader
- `scripts/cms-local-diff.mjs` — the diff script
