---
id: fvcms.canonical-pipeline.001
freshvibe_way_version: v8
date: 2026-07-31
last_rewritten: 2026-07-31 (per operator directive, source-agnostic spec unifying the 4 existing shape docs)
subsystem: docs.canonical-pipeline
related:
  - docs/fv-cms-pipeline-vision.md (the operator's spoken vision, 12-step cycle)
  - docs/fes-spec-contract.md (the FES module shape contract, AI-facing)
  - docs/fvcms-data-model-contract.md (the FvCMS runtime data shape, AI-facing)
  - docs/ai-integration.md (how AI reimagine integrates, parked Phase 4)
  - app-pact/app-pact.md (the package constitution, 12 rules)
  - app-pact/invariants.md (10 testable invariants)
---

# The FreshVibe Canonical Pipeline

**One spec, source-agnostic, that ties together every piece of the FreshVibe ecosystem.**

If you're an AI (or a human) picking up any of the FreshVibe repos, READ THIS DOC FIRST. It tells you:
1. What the pipeline does (one paragraph, no jargon)
2. The 5 stages every site flows through (URL → live editable site)
3. The 2 entry points (FvRE for existing sites, VibeCoder for greenfield)
4. The shared middle (gallery + WCP + AI reimagine, used by both entries)
5. The shared end (freshvibe-cms, the editor chrome that handles any site)
6. Where each piece lives (which repo, which branch, which doc has the shape rules)
7. The world-class criteria (what "good" looks like at every stage)
8. The decision logic (when to use existing / build new / AI compose)

The 4 existing docs (vision, FES spec, data model, AI integration) are the **shape contracts** for the data that flows through this pipeline. This doc is the **flow contract** — it tells you what reads what, what writes what, in what order. If a future change breaks the flow, this doc is the spec it has to satisfy.

---

## What the pipeline does (one paragraph)

Take any website (typically WordPress, but any page-builder works) — or a blank canvas — and turn it into a static site that the operator can edit live in the browser. The static site is fast, portable, plain HTML. The editing chrome overlays it. The operator clicks on any region or module, edits inline, saves — the page updates with no reload. The operator can also ask an AI agent to "reimagine S2" or "add 3 book cards" — the AI proposes, the operator approves, both paths write to the same store. **The store is the source of truth, the page is its render, the chrome is the editing surface.** Manual and AI are always both available, both write to the same place, both respect the same scope.

---

## The 5 stages every site flows through

```
[ ENTRY 1: FvRE ]       [ ENTRY 2: VibeCoder ]       (any source)
        ↓                          ↓
   [ STAGE 1: EXTRACT ]   [ STAGE 1: COMPOSE ]
        ↓                          ↓
        └──────────┬───────────────┘
                   ↓
          [ STAGE 2: FES — make canonical ]
                   ↓
          [ STAGE 3: DEPLOY — ship to consumer ]
                   ↓
          [ STAGE 4: EDIT — operator + AI both ]
```

### Stage 1 (entry-specific) — extract from a real site OR compose from blank canvas

**Entry 1: FvRE (`avidtech6/freshvibe-reconstruction-engine` on `main` @ `bc1501c`)**
- Input: a live URL (or `.wpress` file as a fallback for sites that block crawlers)
- Process: navigate the site like a browser, scan every page, extract every region + widget + its data-settings
- Output: static dist + `annotation.json` (regions + modules + their data-settings) + `cms-baseline.json` (the content model the freshvibe-cms admin bootstraps from)
- The dist preserves the **original framework tags** (`data-widget_type="eael-post-carousel.default"`, `data-settings="{...}"`, `data-id="..."` from Elementor / EAEL / Bricks / etc.) — these tags are the signal FES uses
- Boundary: FvRE stops at the dist + annotation + baseline. It does NOT know about the editor chrome. It is a sealed subprocess per Studio Pact Fragment 1.

**Entry 2: VibeCoder (`avidtech6/vibecoder-standalone` — future adoption of the canonical-pipeline spec)**
- Input: a blank canvas + a user describing what they want
- Process: compose from the same WCP primitives + canonical modules + gallery, in the same shape FvRE produces
- Output: the same shape FvRE produces — static dist + annotation.json + cms-baseline.json
- The `feat/vibecoder-rerender-watcher` branch in freshvibe-cms closes the round-trip: VibeCoder composes, FES renders editable, the same chrome handles both

**Why two entries, one output shape:** freshvibe-cms does not care whether the source was a crawled site or a blank canvas. The chrome is the same. The store is the same. The 12-step operator cycle is the same. The only thing that differs is how the dist + annotation + baseline were produced.

### Stage 2 — FES makes it canonical (the brain in the middle)

FES = FreshVibe Extracted Spec. It is the dispatcher that turns widget configs (from FvRE) or composed configs (from VibeCoder) into canonical FreshVibe-native modules that the chrome can render + edit.

**What FES does at this stage:**
1. For each widget in the dist, look it up in the gallery registry (`avidtech6/fv-module-gallery`)
2. **Decision logic** (the gallery's role):
   - **Match found** → use the existing canonical module. No new build needed.
   - **No match, widget is a known archetype** (e.g. `eael-post-carousel` = carousel) → use a canonical module with WCP primitives that fill the gaps. The spec is `docs/fes-spec-contract.md`.
   - **No match, widget is new** → build a new canonical module. The FES worker (`docs/_code-references/fes_worker.py`) generates the module spec; the GLM-templates library drafts the boilerplate. The new module joins the gallery so future crawls find it.
   - **AI reimagine requested** → the AI proposes a new arrangement, scope-limited to a region OR a page OR a content type, always with a diff the operator can review. AI never silently applies.
3. Output: a fully-canonicalized `annotation.json` + an updated `cms-baseline.json` that lists the canonical modules the site uses

**The FES spec contract** (`docs/fes-spec-contract.md`, 483 lines): the shape of every module spec — `moduleType`, `fieldSchema`, `inspector: {Settings, Style, Advanced}`, `wcpPrimitivesUsed`, etc. The contract is locked; any module that breaks it is wrong.

**The data model contract** (`docs/fvcms-data-model-contract.md`): the shape of the runtime store — pages, regions, groups, module instances, module definitions, skins, scopes. The runtime is the source of truth for what the operator can edit.

**The FES worker** (`docs/_code-references/fes_worker.py`): the canonical Python implementation of the dispatcher. The WCP catalog (20 primitives — see `docs/fes-spec-shapes.md`) is the vocabulary the worker uses to compose modules.

### Stage 3 — Deploy to the consumer

Every site that adopts freshvibe-cms follows the same chain:

1. Edit `avidtech6/freshvibe-cms` source (this repo, the manufacturer)
2. Bump `FV-CMS-VERSION.txt` + `FV-CMS-MANIFEST.json`
3. Commit + push this repo
4. In the consumer's sandbox: `node scripts/fvcms-update-local.mjs --target=<consumer>`
5. Commit + push the consumer repo
6. VPS cron pulls the consumer repo → site has the new CMS

The two-repo split is **not waste and should not be collapsed** (per the README). Reasons:
- Many consumer sites will use this CMS. Each one needs the CMS, none should fork it, none should own the source.
- Versioning: each consumer pins to a specific `FV-CMS-VERSION`. One site can be at v0.7.2 while another is at v0.8.0 — independently.
- Vendored marker (`/* FV-CMS-VENDORED v0.x.y — DO NOT EDIT IN PLACE. ... */`) on every file prevents accidental edits at the consumer level.

The VPS deploy is now cron-based (since 2026-07-30 21:00 BST). The pattern is `sites.json` + a single Python script + a 2-min cron line. Adding a new site = 1 entry in `sites.json`. See `scripts/DEPLOY.md`.

### Stage 4 — Edit (the operator's day-to-day)

The site loads in the browser with a `{ }` Dev button in the corner. Pressing it enters **CMS overlay mode**:

1. Yellow region tags float on each top-level region. Click → region panel (background, gradient, padding, columns).
2. Purple widget sub-tags float inside the selected region. Click → module panel (the form fields the FES spec contract generated for that module).
3. **Manual edit**: change a field, save, the DOM patches in place with no reload. The store + scope system in `runtime/store.js` and `runtime/scope.js` enforce field-level write protection.
4. **AI reimagine**: type "reimagine S2" (or any natural-language request). The AI agent:
   - Parses the request to infer scope (which region / module / page / content type)
   - Reads the same store the manual path reads
   - Proposes a change, shows a diff the operator can review
   - On operator approval, writes through the same validation
   - **Never** silently applies. **Always** scoped. **Always** diffed.

The 12-step operator cycle is documented in `docs/fv-cms-pipeline-vision.md` — that doc is the user's-eye view of what the chrome does. This doc is the architecture view of what produces it.

**Stage 4 amendment (2026-07-31, per oscar-website-mavis b224):** Stage 4 also covers **creating new regions and modules** when the operator wants to expand a page. The flow is:

- **Create new region**: press `+ Region` in the dock → pick a region kind (section, container, column) → FES scaffolds the new region from WCP primitives → region panel opens with the new region's settings → operator can immediately drop widgets into it.
- **Create new module**: drag a widget from the dock (or right-click → "Insert widget here") → pick a widget type from the gallery catalog (or a WCP fallback) → module is inserted at the cursor position, the module panel opens for editing.

The page builder (Stage 7 in the oscar-website-mavis's split) is what powers these affordances. This doc doesn't define how — it just notes that Stage 4 is where the operator encounters them. See `docs/canonical-pipeline.md` addendum 1 (the oscar-website-mavis b222 addendum) for the full page builder architecture.

---

## The 3 shared layers (used by both entries, used at every site)

### Layer A — The gallery (`avidtech6/fv-module-gallery`)

The registry of canonical FreshVibe-native modules. **27 modules + 1 bundle today**, growing.

**The gallery's role in the pipeline:**
- FES consults the gallery to decide "match found, use this" vs "no match, build from WCP" vs "no match, generate new"
- New modules built by the FES worker join the gallery as canonical
- VibeCoder composes from the gallery (same as FES, just at a different entry point)

**Module structure** (the 3-rule shape per `app-pact.md` §3.3 + §3.9):
- Canonical `module_id` (kebab-case, fixed) — `heading`, `paragraph`, `button`, `carousel`, `cta`, `testimonial`, `cta-box`, `accordion`, `icon-list`, `info-box`, `menu`, `social-icons`, `video`, `contact-form`, `breadcrumb`
- User-facing instance name in `displayLabel` (operator can rename per instance)
- Module owns data + renderer. Widget owns editing UI. (Per §3.10)
- Cross-module imports go through `index.js` only. Private impl stays in `runtime.js`.

### Layer B — WCP (Widget Composition Primitives)

The 20 primitives documented in `docs/fes-spec-shapes.md`. They're the building blocks the FES worker uses to compose modules. Examples: `field-string`, `field-color`, `field-number`, `field-select`, `field-media`, `repeater`, `tabs`, `slider`, `switch`, `dropdown`, `tags`, `pills`, `responsive`, `dimension`, `shadow-multi`, `motion-entrance`, `motion-grid`, `motion-trigger`, `backdrop-filter`, `ai-assist`.

WCP is the vocabulary. Modules are the sentences. Pages are the paragraphs. The site is the document.

### Layer C — The store + scope system (the runtime)

`runtime/store.js` + `runtime/scope.js`. The IndexedDB-backed reactive store that holds every editable thing on the page. The scope resolver that decides what the operator (or AI) can write to at any given field.

**The store is the source of truth.** The DOM is its render. The chrome is the editing surface. Manual and AI both write to it. Idempotent save means same edit → same DOM. Undo is "replay the last N edits in reverse."

---

## The world-class criteria (what "good" looks like at every stage)

These are the criteria FES applies when filling gaps. They're not exhaustive — they're the baseline.

| Stage | "World-class" means |
|---|---|
| **Stage 1 (extract/compose)** | The dist is pixel-perfect to the source. The annotation is complete (every region, every module, every data-settings). The cms-baseline.json is enough for the admin to bootstrap without manual setup. The original framework tags are preserved (FES needs them). |
| **Stage 2 (FES)** | Every widget matches a gallery module OR a WCP-primitive composition. Gaps are filled (slider with 1 image gets a 6+ image editor with reorder, effects, etc.). The 3-tab inspector (Settings / Style / Advanced) is generated for every module. A11y is built in (keyboard nav, ARIA labels, focus management). Mobile is first-class (touch targets ≥ 44px, no hover-only UX). |
| **Stage 3 (deploy)** | The dist is fast (sub-second FCP). The vendored marker is on every CMS file. The deploy is automatic (cron + sites.json, not SSH). The chain is verifiable (`fvcms-version-matrix.mjs` shows source = consumer = deployed). |
| **Stage 4 (edit)** | Click → panel opens in <100ms. Edits apply live, no reload. Multiple panels can be open (multi-panel per edge). Mobile shows one panel at a time but chips stay in the dock. AI reimagine always proposes a diff, never silently applies. Undo is always available. |

**World-class criteria — Stage 1, 2, 4 amendments (2026-07-31, per oscar-website-mavis b224):**

- **Scope discipline (Stage 1 + Stage 2 + Stage 4):** every stage declares its scope before doing work. FES declares "this widget, this region, this site". VibeCoder declares "this intent, this app, these types". The AI agent declares "this scope, this kind, this count" (per `app-pact.md` §3.12). No silent scope expansion. No "while I'm here, let me also fix this other thing."
- **Manual-always (Stage 4):** the operator can do ANY editing task manually via the chrome. The AI is a co-editor, not a replacement. The chrome always exposes the manual path, even when the AI affordance is open. If the AI breaks, the operator can still ship. (Per `app-pact.md` §3.11.)

A11y, performance, mobile, theme support — these are the four dimensions every world-class widget must satisfy. They're not optional.

---

## The decision logic (when to use existing / build new / AI compose)

This is the rule the FES worker applies per widget, in order:

1. **Exact match in gallery?** (widget type → module_id, with same data-settings shape) → use the gallery module, no changes
2. **Family match in gallery?** (widget type → module_id, even if data-settings shape differs) → use the gallery module, fill gaps with WCP primitives to match the original's behavior
3. **Known archetype, no gallery match?** (e.g. an unfamiliar page builder's carousel) → compose a new module from WCP primitives that delivers the same UX. Add to gallery as a new canonical module.
4. **Unknown widget, no archetype?** → generate a new module spec via the FES worker. AI proposes shape, GLM drafts boilerplate, Mavis reviews. Add to gallery.
5. **AI reimagine requested by the operator?** → scope first (region / module / page / post-type / all), then propose, then diff, then apply on approval. AI is never the only path to do anything.

The decision is logged in `metadata.provenance` on every module spec, so future sessions can audit "why was this widget rendered this way."

---

## Where each piece lives (for an AI picking this up cold)

| Layer | Repo | Branch | Key files |
|---|---|---|---|
| **FvRE (engine)** | `avidtech6/freshvibe-reconstruction-engine` | `main` | `packages/cli/`, `packages/extraction/wp-adapter/`, `pact/CLAUDE.md` (locked rules) |
| **freshvibe-cms (chrome)** | `avidtech6/freshvibe-cms` | `main` | `app-pact/app-pact.md` (constitution), `runtime/` (engine), `app-fragments/editor-*/` (chrome fragments), `modules/` (17 canonical layouts), `detectors/` (framework adapters), `docs/` (shape contracts) |
| **Gallery (module registry)** | `avidtech6/fv-module-gallery` | `main` | 27 modules + 1 bundle, growing as FES worker generates new ones |
| **VibeCoder (greenfield entry)** | `avidtech6/vibecoder-standalone` | (future) | Will adopt this spec as its canonical pipeline contract |
| **VPS deploy** | (operator's IONOS VPS at 185.249.73.178) | n/a | `/etc/freshvibe/sites.json` + cron + Python script |
| **The bridge** | both | n/a | `cms-baseline.json` — produced by FvRE, read by freshvibe-cms admin |

---

## What the operator's pipeline vision (the 12-step cycle) maps to in this spec

| Operator's step (from `docs/fv-cms-pipeline-vision.md`) | This spec's stage |
|---|---|
| 1. Operator gives Mavis a URL | Entry 1 (FvRE) input |
| 2. Mavis runs FvRE on the live site | Stage 1 extract |
| 3. FvRE produces static dist + annotation.json | Stage 1 output |
| 4. The dist preserves the original framework tags | Stage 1 invariant |
| 5. FES takes the annotation + dist and produces canonical modules | Stage 2 (FES) |
| 6. The dist is shipped to a subdomain | Stage 3 (deploy) |
| 7. The site loads with a `{ }` Dev button | Stage 4 entry |
| 8. Pressing the Dev button enters CMS overlay mode | Stage 4 chrome activation |
| 9. Region tags + widget sub-tags appear | Stage 4 chrome fragments (outline, navigator) |
| 10. Click region → region panel opens | Stage 4 region editor |
| 11. Click widget → module panel opens | Stage 4 module widget (per §3.10) |
| 12. Edits apply live, no reload, DOM patches in place | Stage 4 store + renderer invariant |

The 12-step cycle is the **user-facing** view. This spec is the **architecture** view. Same flow, different lens.

---

## The 4 signs of "the pipeline is broken"

If any of these happen, the pipeline is broken and the change that introduced them is wrong:

1. **A consumer site edits a file with `FV-CMS-VENDORED` marker in place.** The fix is to revert the edit, fix in source, re-vendor, re-deploy. Drift = bad.
2. **A widget config doesn't match any gallery module AND doesn't get a WCP composition.** The fix is to extend the FES spec contract or add a new gallery module. No silent fallback.
3. **An AI reimagine silently applies a change without showing a diff.** The fix is to enforce the propose-then-approve invariant at the dispatcher. AI never silent-applies.
4. **A stage's output doesn't satisfy the next stage's input shape.** E.g. FvRE produces a dist without framework tags → FES can't read it. Or FES produces a module spec that breaks the FES spec contract → the chrome can't render it. The fix is to add an invariant test between stages.

If the broken state is in source, fix in source. If it's in the spec, fix the spec. **Never** patch a deployed consumer to work around a source-level bug.

---

## What this spec does NOT define

- **The AI reimagine UX in detail** (Parked Phase 4 work). This spec says "AI proposes, operator approves, scope is explicit." The detail of the natural-language parsing, the diff format, the scope inference rules — those are spec work for when Phase 4 starts.
- **The page builder layer** (Parked Phase 2 work). This spec says "Stage 4 edits existing regions and modules." Adding NEW regions by drag-to-add, dropping new modules from a palette, layout templates — those are spec work for when Phase 2 starts.
- **The full CMS admin** (Parked Phase 3 work). This spec says "cms-baseline.json is the bridge from FvRE to freshvibe-cms." The detail of the admin UI for content types, queries, templates, records, media, taxonomies, navigation, settings — those are spec work for when Phase 3 starts.
- **The FES composer's "match found / no match" report format**. This spec says the decision is logged in `metadata.provenance`. The exact shape of the report is a detail for the FES worker implementation.

When the parked work starts, this spec is the surface those features must satisfy. If a new feature wants to break this spec, it must update this spec first.

---

## Reflexes for any future Mavis session

If you're a fresh Mavis session picking this up:
1. **Read this doc first.** Then read the 4 shape contracts it references (vision, FES spec, data model, AI integration). Then read the app-pact constitution. That's the full picture.
2. **Don't make a change that breaks a stage's output shape.** If you do, update the spec. Don't work around it in code.
3. **Don't make a change to the chrome without bumping `FV-CMS-VERSION.txt` + `FV-CMS-MANIFEST.json`.** The version is the contract with the consumers.
4. **Don't add a widget to a consumer site directly.** Add it to freshvibe-cms, bump the version, re-vendor.
5. **Don't rebuild the AI reimagine in a one-off way.** It must always propose, always scope, always diff, always write through the same store.
6. **Don't collapse the two-repo split.** freshvibe-cms is the manufacturer, consumers vendor. Don't fork.
7. **Don't collapse the two entry points.** FvRE and VibeCoder both produce the same output shape. The chrome doesn't care which one made it.
8. **When in doubt, ask the operator.** This spec is operator-locked, not auto-generated. Major changes need operator signoff.

---

## Provenance

- Spec written 2026-07-31 12:25 BST by Hopfan App Mavis (session 425731789295699)
- Per operator directive 2026-07-31 12:24 BST: "just do it I have explained already"
- Per the cross-thread agreement with oscar-website-mavis (id 21), bulletin b221: Hopfan takes A (this spec) + C (Phase 8 CMS admin), oscar-website-mavis takes B (Phase 7 page builder), we sync via HQ bulletins
- Per the FES merge audit (2026-07-31 12:15 BST, bulletin b219, /workspace/operator-panel/audits/2026-07-31-freshvibe-cms-merge-audit.md): the FES chrome is already on main, the 4 spec docs are already on main, what was missing was this single canonical pipeline spec
- Per the operator's earlier directives (FvRE bundles, freshvibe-cms vision, the Hopfan test plan v2): the input to this spec

This spec is **locked at v1.0.0** by Hopfan App Mavis and oscar-website-mavis on 2026-07-31. Major changes need both signoffs + operator signoff.
