# Personalization & optimization

MIDL is **personalization-first**: a page is data, not markup, so the runtime can serve a different
*variant* of a page (or a different *style*) to each visitor, measure what works, and promote the
winners — **with no redeploys**. This is the loop that makes MIDL more than a renderer.

```
author variants ─▶ SERVE (assign per visitor) ─▶ TRACK (impressions + conversions)
       ▲                                                        │
       └──────────────── OPTIMIZE (promote winners) ◀───────────┘
```

## 1. Variants

A stored page (frame) can hold several **content variants**, each with a `traffic_percentage`. The same
idea applies to **design systems** (styles): several competing DESIGN.md themes can run at once. You
author variants once; you don't redeploy to change the split.

## 2. Serve — deterministic, sticky, per-visitor

On every request the server picks one variant **deterministically** from a hash of
`(visitor key + frame id)` mapped over the variants' cumulative weights:

- **Sticky** — the same visitor always sees the same variant for a page, so the experience is stable
  (and conversions attribute cleanly).
- **Weighted** — across visitors the split matches each variant's `traffic_percentage`; change a weight
  and assignment deterministically re-shuffles.
- **Edge-safe** — pure hashing (no `Math.random`, no Node crypto), so it runs on the Cloudflare edge.

**Design systems** are assigned the same way but **site-wide** (one style per visitor for the whole
site), on an independent seed so a visitor's style and content assignments don't correlate.

### Targeting (who sees what)

A variant can carry a **targeting rule** and take precedence over the weighted pool. Rules match on the
visitor context the edge derives per request:

- `geo` — country (the trusted edge geo, not a spoofable header)
- `device` — mobile / desktop
- `returning` — first-time vs returning visitor
- `referrer` — where they came from

Targeted visitors get the matching variant; everyone else falls back to the weighted/sticky pool. The
served-page cache is segmented by the resolved segment, so personalization is never cached away.

## 3. Track

Each render emits an **impression** (`frame_id` + `variant_id` + visitor context) off the hot path, and
the rendered output is tagged (`data-midl-frame` / `data-midl-variant`) so clicks and conversions
**attribute to the variant that was actually served**. Style impressions are tracked separately by
design-system id.

## 4. Optimize — promote winners automatically

A scheduled **optimizer Worker** reads the telemetry and, per variant, computes the conversion +
engagement rate, the sample size, and a **statistical confidence** (95% level). Once a variant clears a
minimum sample (so you're not acting on noise) and is a confident winner, the optimizer **promotes it** —
shifting traffic toward it by adjusting the weights the serve step reads. The loop then repeats with the
new split. No human redeploy; the site self-improves on real outcomes.

## How you use it

- **Author variants** — via the authenticated write API (`@wisepunk/sdk` → `frames.create` /
  `frames.update`) or the admin Studio. Add content variants (or competing design systems) and set each
  one's `traffic_percentage`.
- **Target** — attach a `geo`/`device`/`returning`/`referrer` rule to a variant for context-driven
  selection.
- **Let it run** — the optimizer promotes winners on a schedule; watch the admin dashboards for
  per-variant performance and confidence.
- **Machine-readable** — a page's served variant + extracted facts are available at
  `/render/<slug>.midl.json` and `/render/<slug>.facts.json` for agents and crawlers.

Because selection happens at render time over stored data, you change *who sees what* by editing data —
never by shipping code.
