# FreshVibe CMS

A site-aware, AI-safe, module-scoped CMS layer that runs on top of any
reconstructed static site. Ships as a standalone runtime that any HTML
page can `import`.

> **The canonical FreshVibe pipeline is documented at [`docs/canonical-pipeline.md`](./docs/canonical-pipeline.md). Read this first.** It ties together the 4 shape contracts (vision, FES spec, data model, AI integration) into one source-agnostic flow: FvRE (or VibeCoder) → FES → freshvibe-cms → operator + AI both editing live.

## Architecture — this is intentional

**freshvibe-cms is the manufacturer.** Each consumer site has its own
repo (the consumer site repo) and pulls the CMS via `fvcms-update-local`.

This two-repo split is **not waste and should not be collapsed into a
single repo.** Reasons:

- Many consumer sites will use this CMS. Each one needs the CMS, none
  should fork the CMS, none should own the CMS 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.7.2 ... */`) on every file
  prevents accidental edits at the consumer level. The marker says
  "edit here → go to freshvibe-cms, fix, re-vendor, push."
- The consumer's own code stays clean. The CMS code is identifiable
  on sight.

**The chain (every consumer follows the same pattern):**

1. Edit `freshvibe-cms` source (this repo)
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. Cron on VPS pulls the consumer repo → deployed site has the new CMS

**Reflex for any future Mavis session**: when working on a consumer
site, NEVER edit a file marked `FV-CMS-VENDORED` in place. Edit
freshvibe-cms source, bump, re-vendor, push.

## Status

**v0.7.2 — multi-vendor VPS deploy, sites.json registry, cron-based
auto-deploy on every push to GitHub.**

- Tiny template engine (v0.7.1a): 3 primitives, 8 filters, depth-3 each
- Mobile-aware CSS overrides for trace badges + dock-to-edges
- Two-repo split: this repo (manufacturer) + N consumer sites (v0.7.2)
- VPS deploy is now cron-based: 1 cron line + sites.json + a Python
  script that iterates and deploys. No paramiko file copy.
- Multi-vendor per site supported (a site can vendor multiple repos
  at pinned refs into different subpaths)
- 153 vendored files per consumer, version pinned via FV-CMS-VERSION.txt
  + FV-CMS-MANIFEST.json

## What's new in v0.3

### DOM re-renderer
Every module has a `render(instance, def, skin)` function that patches
the live DOM when config changes. Editing "Learn more" → "Read more"
in a CTA module updates the page text instantly without reload.

### Skins
A skin is `{moduleDefaults: ..., cssTokens: ...}`. Apply one and the
runtime:
1. Sets CSS custom properties on `:root`
2. Overrides module defaults where the instance hasn't explicitly set
   a field (operator overrides win)
3. Triggers a re-render of all modules

Two sample skins:
- **Fresh Classic** (sharp corners, serif headings, forest green)
- **Fresh Soft** (rounded corners, sans-serif, soft blue)

### Group → module toggle
Operator can mark any group as a module (or not) and pick its type.
Auto-detect may come later as a suggestion, never auto-promote.

### Region visualiser
Toggle dashed overlays showing each region's boundary with label +
group-count badge. Hover highlights gold. Click jumps to the region
in the navigator.

### Editor shell
Modal with three tabs: Fields (form), Variants (preset list with Apply
buttons), Raw JSON (live-parsed JSON editor with syntax-error feedback).

## Architecture

```
┌─────────────────────────────────────────────────┐
│           Runtime (universal)                   │
│  Region/Group/Module store, scope resolver,     │
│  re-renderer, skins, visualizer, form editor,   │
│  inline editor, group toggle, scope system.     │
│  → No stack-specific code here.                 │
└─────────────────────────────────────────────────┘
                    ▲
                    │ uses
┌─────────────────────────────────────────────────┐
│           Modules (typed components)            │
│  Heading, CTA, Image, Paragraph, Video,         │
│  Carousel, Menu, Social icons, Accordion,       │
│  Icon list.                                     │
│  → Universal. Stack-agnostic.                   │
└─────────────────────────────────────────────────┘
                    ▲
                    │ matches
┌─────────────────────────────────────────────────┐
│           Detectors (stack-specific)            │
│  elementor.js → Elementor + EAEL                │
│  shopify.js    → Shopify Liquid output          │
│  raw-html.js   → generic semantic HTML          │
│  → Stack-specific knowledge lives ONLY here.    │
└─────────────────────────────────────────────────┘
```

## Layer separation rule

> The runtime never contains the word "Elementor", "WordPress",
> "Shopify", "Webflow", or "Next.js".
> The modules never contain those words either.
> Only the detector knows.

## Data model

See `schemas/data-model.md`.

## Use it

### Step 1: annotate your dist

```bash
node scripts/annotate.js /path/to/your/dist ./annotation.json
```

### Step 2: serve annotation.json alongside your pages

### Step 3: boot the CMS in your page

```html
<script type="module">
  import { bootFreshvibeCms } from './freshvibe-cms/bootstrap.js';
  await bootFreshvibeCms({ annotationUrl: '/annotation.json' });
</script>
```

That gives you:
- Region overlays (click "Show regions" in dev panel)
- Module navigator (region → group → module buttons)
- Inline editing on heading text (click to edit, blur to save)
- Skin picker (live preview)
- Group → module toggle
- Click a module button → modal editor (Fields / Variants / Raw JSON)

## Module library

| ID | Label | Elementor widgets mapped |
|---|---|---|
| `M-heading` | Heading | heading, animated-headline |
| `M-cta` | CTA button | button, eael-creative-button |
| `M-image` | Image | image, eael-image |
| `M-paragraph` | Paragraph | text-editor |
| `M-video` | Video | video, eael-video |
| `M-carousel` | Carousel | eael-post-carousel, eael-team-member-carousel, eael-stacked-cards |
| `M-menu` | Menu | eael-simple-menu |
| `M-social-icons` | Social icons | social-icons |
| `M-accordion` | Accordion | eael-adv-accordion, eael-toggle |
| `M-icon-list` | Icon list | icon-list |

## Skins

| ID | Label | Style |
|---|---|---|
| `skin-fresh-classic` | Fresh Classic | Sharp corners, serif, forest green |
| `skin-fresh-soft` | Fresh Soft | Rounded corners, sans-serif, soft blue |

## Repo layout

```
freshvibe-cms/
├── README.md                    # this file
├── schemas/
│   └── data-model.md
├── modules/                     # universal module library (10)
├── detectors/                   # stack-specific (elementor.js)
├── runtime/
│   ├── index.js                 # public API
│   ├── store.js                 # IndexedDB-backed store
│   ├── scope.js                 # scope resolver + ops policy
│   ├── load-annotation.js       # JSON loader
│   ├── form-editor.js           # generic form editor
│   ├── editor-shell.js          # improved modal shell v0.3
│   ├── inline-editor.js         # inline text edit
│   ├── renderer.js              # DOM re-renderer v0.3
│   ├── skin.js                  # skin system v0.3
│   ├── visualizer.js            # region overlays v0.3
│   ├── group-toggle.js          # group→module toggle v0.3
│   └── styles.css
├── skins/                       # sample skins v0.3
│   ├── index.js
│   ├── fresh-classic.js
│   └── fresh-soft.js
├── scripts/
│   └── annotate.js              # dist → annotation.json
├── bootstrap.js                 # entry point
├── annotation.json              # generated for a consumer site's dist
└── tests/
    └── smoke.mjs                # 49 tests
```

## Roadmap

- [x] **v0.1** — foundation: data model + 3 modules + Elementor detector stub
- [x] **v0.2** — annotate + edit what's there (10 modules, annotation
       script, form editor, inline editor, bootstrap)
- [x] **v0.3** — skins + group toggle + DOM re-renderer + editor shell
- [ ] **v1.0** — tests + docs + AI integration spec
- [ ] **v2.0 (next-phase consumer work)** — auth + gating + progress + quiz module

## Future wiring: how a consumer pulls in this repo

This package ships as a runtime that any HTML page can `import`. It
lives in its own repo (`avidtech6/freshvibe-cms`) and is consumed by
any number of consumer sites. Each consumer pulls a specific pinned
version via `fvcms-update-local`.

**The recommended wiring for a consumer:**

1. The consumer repo adds this repo as a vendored sub-tree, pinned
   to a specific `FV-CMS-VERSION` (e.g. v0.7.2) in its own
   `FV-CMS-VERSION.txt`.
2. The consumer's build script (or `fvcms-update-local`) copies the
   CMS files into the consumer's source tree at the same paths they
   live in here (`runtime/`, `app-fragments/`, `bootstrap.js`, etc.).
3. To bump: edit here, bump version, push. Then in the consumer
   sandbox run `fvcms-update-local`, commit, push. The consumer's
   VPS cron deploys the new bundle.

**Why this pattern (not a monorepo, not npm):**
- Each consumer is a self-contained repo. One consumer breaking
  doesn't break the others.
- Version pinning per consumer is explicit. A consumer can be at
  v0.7.2 while a newer consumer is at v0.8.0.
- No monorepo tooling tax (Lerna, Nx, Turborepo). Each repo is plain git.
- No npm publish / install overhead. Vendored files in the consumer
  repo = one less moving part.

**The cross-repo contract:** a consumer is responsible for pulling
this repo, copying the runtime files into its own source tree, and
stamping the `FV-CMS-VENDORED` marker on each file. The CMS exposes
a stable ES module API in `runtime/index.js` — that's the only
surface a consumer depends on.

**When the consumer is a desktop app (submodule alternative):**
If the consumer is a desktop app that wants git-submodule semantics
instead of vendoring, the consumer can add this repo as a submodule
at `packages/freshvibe-cms/`, pin to a specific commit hash, and
update the pointer when bumping. The vendored approach is preferred
for static sites; submodules are for desktop apps where the
reproducible-build property matters more.

## License

Operator-internal.