---
name: autokap-preset
description: >
  Generate AutoKap capture programs — deterministic opcode sequences for automated
  screenshot, clip, and video capture of web apps. Use when: creating or updating presets,
  adding data-ak attributes for capture automation, or debugging failed capture programs.
metadata:
  author: AutoKap
  version: 2.3.0
---

> Packaging note: the canonical AutoKap preset skill is a modular bundle for Codex (`SKILL.md` + companion reference files). This single-file export is generated from that same source so all agents stay in parity.

# AutoKap Preset Creation Skill

## Mental Model

AutoKap is **not** a freeform runtime navigation agent. Your job is to inspect the user's codebase, add stable selectors where needed, and generate a **deterministic program** (a JSON sequence of browser opcodes) that the AutoKap CLI executes locally via Playwright.

Normal navigation and interaction stay deterministic. Runtime AI is limited to narrow fallback tasks such as overlay dismissal fallback, capture verification, alt text generation, and the last-resort healer. Do **not** design flows that depend on an LLM improvising navigation at runtime.

This installed skill is the **source of truth** for the AutoKap contract: opcode schema, login rules, variant handling, persistence, and validation. The copied prompt from the AutoKap dashboard is only the **preset-specific brief** (project URL, variants, template goal, mock data guidance, etc.).

## When To Use This Skill

- User wants to capture screenshots, clips, or videos of their web app
- User asks to create or update an AutoKap preset
- User needs `data-ak` attributes added to UI elements for capture
- User is debugging a failed capture program

## Before Generating Anything

1. **Inspect the codebase first** — Read the relevant routes, layouts, auth checks, theme/locale system, and the components you may need to tag. Do not start by guessing opcodes.
2. **Decide whether auth is required** — If any target route is protected, the program must begin with the canonical login flow. Never assume the user is already logged in.
3. **Understand how locale and theme really work** — Prefer storage-based `SET_LOCALE` / `SET_THEME` with `"$variant"` when the app supports it. If the app uses URL-based locale routing, reflect that in `NAVIGATE`.
4. **Understand the UI states** — Know which conditions make modals, dropdowns, tabs, tables, empty states, or dashboards appear before you write selectors or opcodes.
5. **Use planning mode if the assistant supports it** — Make a short plan before editing code or generating the program.
6. **Ask clarifying questions when navigation is ambiguous** — If the codebase or prompt does not clearly reveal how to reach a state, ask instead of inventing UI details.
7. **Do not break the one-shot flow** — When API access is available, persist the preset so the user can run `autokap run <preset-id>` immediately. Manual JSON output is a fallback, not the default.

## Quick Workflow

1. **Understand the capture goal** — What pages/states should be captured? Screenshot, clip, or interactive demo? Which viewports, locales, themes, and mockups matter?
2. **Inspect the implementation** — Confirm routes, auth, theme, locale, and any dynamic UI state in the codebase.
3. **Add `data-ak` attributes** — Tag every element the opcodes must interact with using stable selectors.
4. **Choose media mode and variants** — Set `mediaMode` and define the exact viewport/locale/theme combinations.
5. **Generate the `ExecutionProgram`** — Write deterministic opcodes and explicit postconditions.
6. **Persist the preset** — Prefer creating/updating it via the AutoKap API so the program lives inside `config.program`.
7. **Validate it** — Run the recommended checks and at least one real `autokap run <preset-id>` before considering the work done.

## Bundled References

Load these only when the request actually needs them:

- **Opcode parameters** — [OPCODE-REFERENCE.md](#reference-opcode-reference)
- **Interactive demos** — [references/interactive-demo.md](#reference-interactive-demo-workflow)
- **Mock data** — [references/mock-data.md](#reference-mock-data-injection)
- **Complete examples** — [references/examples.md](#reference-complete-examples)

Keep the core `SKILL.md` for the non-negotiable contract. Reach for the
references only after you know which mode or advanced feature the user needs.

## Adding `data-ak` Attributes

For every element you interact with in the opcodes, add a `data-ak="descriptive-name"` attribute:

```jsx
// Before
<button onClick={handleLogin}>Sign in</button>
<input type="email" placeholder="Email" />

// After
<button data-ak="login-btn" onClick={handleLogin}>Sign in</button>
<input data-ak="login-email" type="email" placeholder="Email" />
```

**Rules:**
- Use kebab-case: `login-btn`, `pricing-table`, `theme-toggle`
- Add ONLY to elements that opcodes interact with
- For dynamic lists, add `data-ak` to the container AND item template:
  ```jsx
  <ul data-ak="project-list">
    {projects.map(p => <li data-ak="project-item" key={p.id}>{p.name}</li>)}
  </ul>
  ```
  Target: `[data-ak="project-list"] [data-ak="project-item"]:nth-child(2)`
- Zero runtime cost and no security implications

## ExecutionProgram Schema

```typescript
interface ExecutionProgram {
  presetId: string;         // Unique slug (e.g. "homepage-hero")
  programVersion: number;   // Always 1 for new programs
  mediaMode: 'screenshot' | 'clip';
  baseUrl: string;          // Root URL of the application
  variants: VariantSpec[];  // Viewport/locale/theme combinations
  preconditions: {
    // Auth bootstrap auto-adapts to whatever is configured on the preset.
    // If the preset has stored auth cookies, the runtime injects them into
    // the browser context. If it has stored credentials (email/password), they
    // are substituted into {{email}}/{{password}}/{{loginUrl}} placeholders
    // inside opcodes — the program performs the UI login itself.
    // If neither is set, the run is anonymous. Both can coexist.
    credentialsId?: string; // Optional metadata pointer
  };
  steps: ExecutionOpcode[];
  artifactPlan: {
    mediaMode: 'screenshot' | 'clip';
    cursorTheme?: 'minimal' | 'macos' | 'windows'; // Clip only. Default: 'minimal'
    format?: {
      clipFormat?: 'gif' | 'mp4' | 'both';      // Default: 'gif'
      screenshotFormat?: 'png' | 'jpeg';          // Default: 'png'
    };
    applyMockup?: boolean;     // Apply device frame mockup
    applyStatusBar?: boolean;  // Add status bar to mockup
  };
  compileFingerprint: string;    // e.g. "v1-homepage"
  variantFingerprint?: string;   // "langs:en,fr|themes:dark,light"
  compiledAt: string;            // ISO timestamp
  compiledWith?: string;         // "ai-assistant"
}

interface VariantSpec {
  id: string;                // e.g. "desktop-en-light"
  viewport: { width: number; height: number };
  deviceScaleFactor?: number;
  locale?: string;           // BCP-47 (e.g. "en", "fr")
  theme?: 'light' | 'dark';
  targetId?: string;         // Stable preset target id (e.g. "desktop")
  targetLabel?: string;      // Human-readable label (e.g. "Desktop")
  deviceFrame?: string;      // e.g. "iphone-16-pro" for mockup
}
```

## Opcode Quick Reference

24 opcodes available. For full parameter documentation, see [OPCODE-REFERENCE.md](#reference-opcode-reference).

| Kind | Selector? | Key Params | Typical Postcondition | Notes |
|------|-----------|-----------|----------------------|-------|
| `NAVIGATE` | no | `url` | `route_matches` | Always first step |
| `DISMISS_OVERLAYS` | no | — | `overlay_dismissed` | Always after NAVIGATE |
| `CLICK` | yes | `button?` | `element_visible` / `route_matches` | Postcondition = what CHANGED |
| `TYPE` | yes | `text`, `clearFirst` | `any_change` | `{{email}}` / `{{password}}` for creds |
| `PRESS_KEY` | no | `key` | `any_change` | `"Enter"`, `"Escape"`, `"Tab"`, etc. |
| `WAIT_FOR` | yes* | `state` | `element_visible` | `"visible"` or `"attached"` (DOM only) |
| `SCROLL` | no | `direction`, `targetSelector?`, `amount?` | `element_visible` | Use `targetSelector` for precise scroll |
| `HOVER` | yes | — | `element_visible` | Capture immediately after for hover state |
| `SELECT_OPTION` | yes | `optionLabel` / `optionValue` / `optionIndex` | `text_contains` | **Native `<select>` only.** Custom dropdowns: use CLICK sequence |
| `CHECK` | yes | `checked` | `always` | Idempotent. Safer than CLICK for checkboxes |
| `DOUBLE_CLICK` | yes | — | `element_visible` / `any_change` | Inline editing, text selection |
| `SET_LOCALE` | no | `locale`, `method`, `storageHints?` | `always` | Use `"$variant"`. Prefer `method: "storage"` |
| `SET_THEME` | no | `theme`, `method`, `storageHints?` | `always` | Use `"$variant"`. Prefer `method: "storage"` |
| `ASSERT_ROUTE` | no | `urlPattern` | `route_matches` | Validation checkpoint |
| `ASSERT_SURFACE` | no | `selectors[]`, `matchAll` | `always` | Validation checkpoint |
| `CAPTURE_SCREENSHOT` | no | `captureId`, `captureName`, `elementSelector?` | `always` | `elementSelector` for element-level crop |
| `CAPTURE_DOM` | no | `stateName`, `selector?` | `always` | Interactive Demo state — see [Interactive Demo Workflow](#interactive-demo-workflow). `mediaMode: "dom"` only. Add `selector` to capture a focused subtree instead of the whole page. |
| `CAPTURE_FRAGMENT` | yes | `fragmentName`, `parentState`, `selector`, `variantName?`, `triggerSelector?`, `mountStrategy?` | `always` | Interactive Demo fragment (modal/popover/dropdown/local subtree) — see [Fragments](#fragments-and-local-interactions). Mounted on top of `parentState` by the player. Capture the same fragment under multiple `variantName`s to enable in-place swap (e.g. background colour change). |
| `BEGIN_CLIP` | no | `clipId`, `clipName` | `always` | Start recording |
| `END_CLIP` | no | `clipId`, `clipName` | `always` | Stop recording. Same `clipId` as BEGIN_CLIP |
| `CLONE_ELEMENT` | yes | `sourceSelector`, `containerSelector`, `count` | `always` | **Non-blocking.** Duplicate a template element N times |
| `INJECT_MOCK_DATA` | yes | `groupName` + clone fields and/or trigger fields | `always` | **Non-blocking.** Apply both clone (instant visual) AND trigger (survives re-renders) whenever possible |
| `REMOVE_ELEMENT` | yes | `selector` | `always` | **Non-blocking.** Remove empty-state placeholders / unwanted nodes |
| `SET_ATTRIBUTE` | yes | `attribute`, `value` | `always` | **Non-blocking.** Set attribute (e.g. `src` on an `<img>`) |

*WAIT_FOR requires `selector` or semantic `target`.

## Postcondition Types

| Type | Fields | When to use |
|------|--------|-------------|
| `route_matches` | `pattern` | After NAVIGATE, CLICK that changes page |
| `element_visible` | `selector`, `waitMs?` | After CLICK/HOVER that opens modal/dropdown/tooltip |
| `element_absent` | `selector` | After CLICK that closes modal |
| `text_contains` | `selector`, `text` | After TYPE, SELECT_OPTION |
| `overlay_dismissed` | — | After DISMISS_OVERLAYS |
| `screenshot_stable` | `threshold?` (0-1), `waitMs?` | Before CAPTURE_SCREENSHOT when page has animations |
| `any_change` | — | After TYPE, PRESS_KEY, DOUBLE_CLICK (soft check) |
| `always` | — | CAPTURE_SCREENSHOT, SET_LOCALE, SET_THEME, CHECK, BEGIN/END_CLIP |

## What Presets Can and Cannot Do

### Capabilities

- **Multi-viewport**: desktop, tablet, mobile in a single program
- **Multi-locale**: switch languages via SET_LOCALE with `"$variant"` placeholder
- **Multi-theme**: switch light/dark via SET_THEME with `"$variant"` placeholder
- **Authenticated flows**: login via TYPE with `{{email}}`/`{{password}}` credential placeholders
- **Element-level capture**: crop screenshot to a specific component with `elementSelector`
- **Clips**: record interactions as GIF, MP4, or both via BEGIN_CLIP/END_CLIP
- **Device mockups**: wrap screenshots in device frames (`applyMockup: true`)
- **Status bars**: add device status bar to mockups (`applyStatusBar: true`)
- **Hover states**: capture tooltips, dropdown menus via HOVER + CAPTURE_SCREENSHOT
- **Scroll-to-element**: scroll specific sections into view with SCROLL
- **Right-click menus**: context menus via CLICK with `button: "right"`
- **Native selects**: option selection via SELECT_OPTION
- **Checkboxes/toggles**: idempotent state control via CHECK

### Limitations

- **No file uploads**: native OS file dialogs cannot be automated
- **No complex drag-and-drop**: simple clicks only, no drag gestures
- **No multi-tab workflows**: single page context per execution
- **No native OS dialogs**: print, save-as, permission prompts are inaccessible
- **No CAPTCHAs**: cannot solve CAPTCHAs or bot detection challenges
- **No real-time dynamic data**: data that changes between runs produces inconsistent captures
- **No cross-origin iframes**: cannot interact with content from different origins
- **No browser extensions**: extension UIs are not accessible via Playwright

## Anti-Patterns

| Do NOT | Do instead |
|--------|-----------|
| Use SELECT_OPTION for custom dropdowns (Radix, Headless UI, MUI) | CLICK to open + WAIT_FOR options + CLICK the option |
| Use CLICK for checkboxes | CHECK (idempotent, sets state directly) |
| Omit postconditions | Every opcode needs a postcondition describing the expected result |
| Hardcode locale/theme values in SET_LOCALE/SET_THEME | Use `"$variant"` — runtime substitutes the current variant's value |
| Use `method: "ui_interaction"` for locale/theme | Use `method: "storage"` — instant, reliable, no UI dependency |
| Tell the user to save a local `program.json` file | Persist the preset via API; only output full JSON as a fallback if the API call fails |
| Guess CSS selectors | Add `data-ak` attributes to the code first |
| Skip WAIT_FOR after page transitions | Add WAIT_FOR with `waitMs: 10000` after login, navigation, modal open |
| Use NAVIGATE or SCROLL inside a clip to reach a clickable target | CLICK the link/button — cursor animates to it naturally |
| Skip `cursorTheme` in clip `artifactPlan` | Set `cursorTheme: "macos"` or `"windows"` for polished recordings |

## Clip Workflow

For recording video clips of interactions:

1. Set `mediaMode: "clip"` on the program
2. Set `artifactPlan.format.clipFormat` to `"gif"`, `"mp4"`, or `"both"`
3. Set `artifactPlan.cursorTheme` to `"minimal"`, `"macos"`, or `"windows"` (default: `"minimal"`)
4. Put navigation and setup steps **before** BEGIN_CLIP (they won't be recorded)
5. Place `BEGIN_CLIP` with a `clipId` and `clipName` to start recording
6. All interaction steps between BEGIN_CLIP and END_CLIP are recorded
7. Place `END_CLIP` with the **same `clipId`** to stop recording

### Cursor animation (automatic)

During clip recording, the runtime automatically animates a visible cursor with **cubic Bézier curves** for every CLICK, HOVER, TYPE, CHECK, DOUBLE_CLICK, and SCROLL opcode. The cursor moves smoothly (350–900 ms depending on distance), with randomized control points and micro-jitter to look human. You do **not** need to script cursor movement yourself — just emit the right interaction opcodes and the runtime handles the animation.

### Designing human-like clip programs

Because the cursor is visible during recording, **how** you reach a target matters as much as reaching it:

| Do | Don't | Why |
|---|---|---|
| **CLICK** on a nav link / anchor to scroll to a section | SCROLL blindly then capture | The cursor animates to the link before clicking — the viewer sees intentional navigation |
| **CLICK** on an in-page link to change route | NAVIGATE to the new URL mid-clip | NAVIGATE is an instant browser jump with no cursor motion — it looks like a cut, not a flow |
| **HOVER** → pause → **CLICK** for important interactions | CLICK without HOVER | HOVER gives the viewer time to see where the cursor is heading and creates anticipation |
| Keep the clip short and focused (5–15 interactions) | Record an entire user journey in one clip | Long clips lose the viewer. Split into multiple clips if needed |
| Place WAIT_FOR after CLICK that triggers a route change | Immediately interact after navigation | Gives the page time to render and the viewer time to register the new state |
| Add a `screenshot_stable` WAIT_FOR before BEGIN_CLIP when DISMISS_OVERLAYS precedes it | Start recording immediately after DISMISS_OVERLAYS | Overlay dismiss animations (fade-out, slide) bleed into the first frames of the clip |

**Rule of thumb:** inside a clip, never use NAVIGATE or SCROLL to reach content that the user would normally reach by clicking a link or button. Use CLICK on that element instead — the cursor animation makes the transition visible and human-like.

SCROLL is still appropriate for:
- Scrolling down a long page to reveal below-the-fold content when there is no clickable anchor
- Scrolling inside a container or list

### Clean start: wait for visual stability before recording

When `DISMISS_OVERLAYS` runs shortly before `BEGIN_CLIP`, overlay dismiss animations (fade-out, slide-away) can bleed into the first frames of the clip. Add a `screenshot_stable` WAIT_FOR between them so the page is visually settled before recording begins:

```json
{ "kind": "WAIT_FOR", "description": "Wait for page to be visually stable", "selector": "[data-ak=\"main-content\"]", "state": "visible", "postcondition": { "type": "screenshot_stable", "threshold": 0.01, "waitMs": 3000 }, "recovery": { "retries": 0, "useSelectorMemory": false, "useAltInteraction": false, "allowReload": false, "allowHealer": false }, "timeoutMs": 5000, "maxFailures": 1 }
```

Place this step **after** DISMISS_OVERLAYS + any content WAIT_FOR, and **immediately before** BEGIN_CLIP.

```json
{
  "steps": [
    { "kind": "NAVIGATE", "url": "https://app.example.com", "..." : "..." },
    { "kind": "DISMISS_OVERLAYS", "..." : "..." },
    { "kind": "WAIT_FOR", "selector": "[data-ak=\"dashboard\"]", "state": "visible", "..." : "..." },
    { "kind": "WAIT_FOR", "description": "Let overlays finish animating", "selector": "[data-ak=\"dashboard\"]", "state": "visible", "postcondition": { "type": "screenshot_stable", "threshold": 0.01, "waitMs": 3000 }, "..." : "..." },
    { "kind": "BEGIN_CLIP", "clipId": "add-project", "clipName": "Add project", "postcondition": { "type": "always" }, "..." : "..." },
    { "kind": "CLICK", "selector": "[data-ak=\"add-project-btn\"]", "..." : "..." },
    { "kind": "TYPE", "selector": "[data-ak=\"project-name\"]", "text": "My Project", "clearFirst": true, "..." : "..." },
    { "kind": "CLICK", "selector": "[data-ak=\"save-btn\"]", "..." : "..." },
    { "kind": "END_CLIP", "clipId": "add-project", "clipName": "Add project", "postcondition": { "type": "always" }, "..." : "..." }
  ]
}
```

## Interactive Demo Workflow

Interactive demos are advanced and should only be used when the user wants a
clickable DOM-based experience, not static screenshots or a clip.

Key rules:

- center the capture around the feature loop, not the whole app
- use `CAPTURE_DOM` for base states
- use `CAPTURE_FRAGMENT` for local overlays and subtree swaps
- add authored markers such as `data-ak-interact`, `data-ak-fragment`,
  `data-ak-model`, and `data-ak-template`
- prefer fragments / bindings / model-driven reconstruction before custom
  interaction `code`

Read the full reference before generating an interactive demo:

- [references/interactive-demo.md](#reference-interactive-demo-workflow)

## Recovery System Overview

When an opcode fails, AutoKap tries 5 recovery strategies in order:

1. **Deterministic retry** — Same opcode, fresh page observation. Exponential backoff (500ms, 1000ms...).
2. **Selector memory** — Tries known-good selectors from previous successful runs stored in Supabase.
3. **Alternative interaction** — Keyboard (Tab+Enter), JS dispatch, coordinate-based click.
4. **Targeted reload** — Reloads the page and retries. **Loses UI state** (open modals, form data).
5. **LLM Healer** — AI analyzes the page screenshot and AKTree, then rewrites the failing opcode. Max 3 invocations per run.

**Guidance:** Keep `allowReload: false` for most steps (it loses state). Set `allowReload: true` only for the first NAVIGATE or after full page transitions where UI state is expendable.

## SET_LOCALE / SET_THEME: Method Selection

**Investigate the app's i18n/theme mechanism first** by reading configuration files:

| Framework | Opcode | Method | Storage | Key |
|-----------|--------|--------|---------|-----|
| next-intl | SET_LOCALE | `storage` | `cookie` | `NEXT_LOCALE` |
| i18next / react-intl | SET_LOCALE | `storage` | `localStorage` | `i18nextLng` |
| URL-based (e.g. `/fr/page`) | NAVIGATE | — | — | Use locale in URL path |
| Browser-level only | SET_LOCALE | `browser_context` | — | — |
| next-themes | SET_THEME | `storage` | `localStorage` | `theme` |
| CSS `prefers-color-scheme` | SET_THEME | `color_scheme` | — | — |

Place SET_LOCALE/SET_THEME **after NAVIGATE + DISMISS_OVERLAYS** but **before WAIT_FOR or CAPTURE_SCREENSHOT**. The runtime reloads the page after applying storage-based changes.

If the preset has only one locale and one theme, these opcodes are unnecessary.

## Mock Data Injection

Use mock data only when the capture would otherwise look empty or broken.

Core rules:

- wire both delivery mechanisms whenever possible: clone + hidden trigger/input
- every variable UI element must become a slot
- add `data-ak` markers to the template, container, variable children, and
  hidden receivers
- declare `mockDataInjection.groups` at the top level of the preset config
- place `INJECT_MOCK_DATA` after `WAIT_FOR` and before `CAPTURE_SCREENSHOT`
- keep the opcode non-blocking with `postcondition: { type: "always" }`

Read the full reference before adding mock data:

- [references/mock-data.md](#reference-mock-data-injection)

## Critical Rules

1. **Always start with `NAVIGATE` + `DISMISS_OVERLAYS`**
2. **Every CLICK/TYPE uses a `data-ak` selector** — add the attribute to the code first
3. **Never guess selectors** — if the element doesn't have a stable selector, add `data-ak` to it
4. **CLICK postconditions describe the result**, not the action (what changed, not what was clicked)
5. **Add `WAIT_FOR` after page transitions** (login, route change, modal open)
6. **Set `waitMs: 10000`** on postconditions involving async transitions
7. **Persist the preset via the API — the program lives INSIDE `config.program`**, not in a separate file. After generating the program JSON, POST `/api/v1/presets` with a body whose `config` contains `program: { ...the full ExecutionProgram... }` PLUS any `interactiveDemo` / `mockDataInjection` blocks. The user MUST be able to run `autokap run <preset-id>` afterwards with no `--program` flag and no extra files. **Never tell the user to save the JSON to a local file**, never suggest `--program ./program.json` as the normal run path. The CLI fetches the program from the server.
8. **Use `captureId`/`clipId`** on CAPTURE_SCREENSHOT/BEGIN_CLIP for Studio and dev links to work. Always set `"devLinksEnabled": true` in the preset config when creating via API so endpoints are visible on the dashboard
9. **Mock data opcodes are non-blocking** — they log a warning and continue if selectors miss; always pair them with `recovery: { retries: 0, ... }` and `postcondition: { type: "always" }`
10. **Hardcode the login URL — never use `{{loginUrl}}`.** The login URL is just another navigation. You generate the entire program, so you know which path the app's login lives at — write it directly (`https://app.example.com/login`, `http://localhost:3000/login`, etc.). The `{{loginUrl}}` placeholder is **deprecated** and produces broken navigations when the user hasn't filled the optional `loginUrl` credential field. `{{email}}` and `{{password}}` placeholders for `TYPE` opcodes are still correct and required (those are sensitive secrets the user fills in).
11. **Capturing an auth-protected route requires a login flow at the START of the program.** Do NOT assume the user is "already logged in". Before any opcode that hits a route behind authentication, emit the canonical login sequence (see "Login flow" below). Skipping this is the #1 cause of "preset runs but never reaches the dashboard" failures.

## Login flow

If **any** of the captures or DOM states in the program lives on an auth-protected route, the program MUST start with this canonical login sequence — placed BEFORE the first capture/state-bearing NAVIGATE.

```json
{ "kind": "NAVIGATE", "description": "Open login page", "url": "https://app.example.com/login", "postcondition": { "type": "route_matches", "pattern": "/login" }, "recovery": { "retries": 2, "useSelectorMemory": true, "useAltInteraction": true, "allowReload": true, "allowHealer": true }, "timeoutMs": 20000, "maxFailures": 3 },
{ "kind": "DISMISS_OVERLAYS", "description": "Dismiss any overlays on login", "postcondition": { "type": "overlay_dismissed" }, "recovery": { "retries": 2, "useSelectorMemory": true, "useAltInteraction": true, "allowReload": false, "allowHealer": true }, "timeoutMs": 5000, "maxFailures": 3 },
{ "kind": "WAIT_FOR", "description": "Wait for the login email input", "selector": "[data-ak=\"login-email\"]", "state": "visible", "postcondition": { "type": "element_visible", "selector": "[data-ak=\"login-email\"]", "waitMs": 5000 }, "recovery": { "retries": 2, "useSelectorMemory": true, "useAltInteraction": true, "allowReload": false, "allowHealer": true }, "timeoutMs": 10000, "maxFailures": 3 },
{ "kind": "TYPE", "description": "Enter login email", "selector": "[data-ak=\"login-email\"]", "text": "{{email}}", "clearFirst": true, "postcondition": { "type": "any_change" }, "recovery": { "retries": 2, "useSelectorMemory": true, "useAltInteraction": true, "allowReload": false, "allowHealer": true }, "timeoutMs": 10000, "maxFailures": 3 },
{ "kind": "TYPE", "description": "Enter login password", "selector": "[data-ak=\"login-password\"]", "text": "{{password}}", "clearFirst": true, "postcondition": { "type": "any_change" }, "recovery": { "retries": 2, "useSelectorMemory": true, "useAltInteraction": true, "allowReload": false, "allowHealer": true }, "timeoutMs": 10000, "maxFailures": 3 },
{ "kind": "CLICK", "description": "Submit the login form", "selector": "[data-ak=\"login-submit\"]", "postcondition": { "type": "route_matches", "pattern": "/dashboard**", "waitMs": 10000 }, "recovery": { "retries": 2, "useSelectorMemory": true, "useAltInteraction": true, "allowReload": false, "allowHealer": true }, "timeoutMs": 15000, "maxFailures": 3 }
```

### Decision rule: do I need a login flow?

Walk through the routes you're about to capture:

| Route shape | Login flow needed? |
|---|---|
| `/`, `/pricing`, `/blog/*`, `/docs/*` (marketing / public) | **No** |
| `/dashboard`, `/projects/*`, `/settings`, `/admin/*` (anything behind a guard) | **Yes** |
| `/login`, `/signup`, `/forgot-password` (the auth pages themselves) | **No** (you're already there) |
| Mixed (some public, some private) | **Yes** if at least one is private — emit the login sequence first, then NAVIGATE to each route |

When in doubt: **inspect the route's component file** for auth checks (`useSession`, `redirect()`, middleware, layout-level auth wrapper, server-side `getServerSession`, etc.). If you find any, emit the login flow.

### Anti-patterns

| Don't | Why |
|---|---|
| `{ "kind": "NAVIGATE", "url": "{{loginUrl}}" }` | Deprecated placeholder. The user often leaves `loginUrl` empty (it's optional), and the substitution produces an empty string that crashes Playwright. Hardcode the URL. |
| Skip the login flow because "the user runs locally and is already logged in" | Playwright launches a fresh browser context with no cookies. Sessions never carry over. |
| Capture a private route with no preceding login sequence | You'll capture the login screen (or a 401 page) instead of the actual dashboard. |
| Place login steps in the middle of the program after a public capture | The first NAVIGATE to a private route fails with a 401/redirect before login runs. Login goes FIRST. |
| Use `{{loginUrl}}` in any example or doc you write into chat | Reinforces the broken pattern. Always hardcode. |

## How to persist the preset

The user expects a **one-shot flow**: copy prompt → paste → assistant does the work → user runs `autokap run <preset-id>`. **Never break this flow.**

Use the AutoKap CLI commands to create, update, and query presets. The CLI reads the stored API key from `~/.autokap/config.json` automatically — **do NOT read the key yourself or build raw HTTP requests**.

### Step 1: Write the config JSON to a temp file

Build the full config object (with `program`, `pages`, and optionally `interactiveDemo`/`mockDataInjection`) and write it to a temporary file:

```bash
cat > /tmp/autokap-preset.json << 'EOF'
{
  "captureMode": "screenshot",
  "devLinksEnabled": true,
  "pages": [
    { "id": "dashboard", "name": "Dashboard" },
    { "id": "settings", "name": "Settings" }
  ],
  "program": {
    "presetId": "my-preset",
    "programVersion": 1,
    "mediaMode": "screenshot",
    "baseUrl": "https://app.example.com",
    "variants": [ { "id": "desktop-en-light", "viewport": { "width": 1440, "height": 900 }, "locale": "en", "theme": "light", "targetId": "desktop", "targetLabel": "Desktop" } ],
    "preconditions": {},
    "steps": [ { "kind": "NAVIGATE", "..." : "..." } ],
    "artifactPlan": { "mediaMode": "screenshot" },
    "compileFingerprint": "v1-my-preset",
    "compiledAt": "2026-04-11T00:00:00.000Z",
    "compiledWith": "ai-assistant"
  }
}
EOF
```

#### Config requirements

- **`pages`** — one entry per `CAPTURE_SCREENSHOT` opcode (`{ "id": "<captureId>", "name": "<captureName>" }`). Drives endpoint (dev link) creation. **Screenshot presets only.**
- **`clips`** — one entry per `BEGIN_CLIP`/`END_CLIP` pair (`{ "id": "<clipId>", "name": "<clipName>" }`). Drives endpoint and Studio slot creation. **Clip presets only.** Do NOT put clip entries in `pages` — the server treats `pages` as screenshots.
- **`program`** — the full `ExecutionProgram` with `steps`, `variants`, `artifactPlan`, etc.
- **`captureMode`** — `"screenshot"`, `"clip"`, or `"interactive_demo"`.
- **`devLinksEnabled: true`** — enables endpoints on the dashboard.

The server auto-syncs `langs`, `themes`, `targets`, and `viewports` from `program.variants` — you do NOT need to set these manually.

### Step 2: Create the preset

```bash
npx autokap@latest preset create \
  --project <project-id> \
  --name "My preset" \
  --config /tmp/autokap-preset.json
```

The command outputs **only the preset ID** to stdout. Capture it for the next steps:

```bash
PRESET_ID=$(npx autokap@latest preset create --project <project-id> --name "My preset" --config /tmp/autokap-preset.json)
```

### Step 3: Run the preset

```bash
npx autokap@latest run $PRESET_ID
```

### Step 4: Fetch dev link endpoints

```bash
npx autokap@latest endpoints list --preset $PRESET_ID
```

Outputs JSON with endpoint IDs and public asset URLs:
```json
[
  { "id": "abc123...", "capture_name": "dashboard", "label": "Dashboard", "url": "https://app.example.com/api/v1/assets/abc123..." }
]
```

### Updating an existing preset

```bash
npx autokap@latest preset update <preset-id> --config /tmp/autokap-preset.json
```

Optional `--name` and `--description` flags to update metadata.

### Deleting a preset

```bash
npx autokap@latest preset delete <preset-id>
```

### Anti-patterns (NEVER do these)

| Don't | Why |
|---|---|
| Read `~/.autokap/config.json` and make raw `fetch`/`curl` calls | The CLI handles auth automatically. Raw HTTP calls are fragile and expose the key. |
| Tell the user to save the program to a local JSON and pass `--program` | Defeats the one-shot flow. The user expected the AI to handle persistence. |
| Create the preset without `config.program` | The CLI then has nothing to run; the dashboard shows "No capture program". |
| Create the preset without `config.pages` (for screenshot presets) | No dev link endpoints will be created. |
| Put clip entries in `config.pages` instead of `config.clips` | The server treats `pages` as screenshots. Clips won't appear in Studio or get endpoints. |
| Create a clip preset without `config.clips` | No clip endpoints or Studio slots will be created. |
| Output the program as a code block "for the user to copy" | Forces a manual step. Use the CLI instead. |

### Fallback (when the CLI is genuinely unavailable)

If the CLI command fails, output the full config JSON as a code block and tell the user to import it via the dashboard. Make it clear this is a recovery path, not the normal one.

## Running

```bash
# Run a preset (the program lives in preset.config.program — no --program flag):
autokap run <preset-id>

# Watch the browser execute:
autokap run <preset-id> --headed

# Save the artifacts to a local directory in addition to uploading them:
autokap run <preset-id> --output ./screenshots
```

## Complete Examples

Use the examples reference for ready-made shapes of:

- anonymous screenshot presets
- authenticated screenshot presets
- clip presets
- interactive demo presets
- mock-data-enabled presets

Read:

- [references/examples.md](#reference-complete-examples)


---

## Reference: Opcode Reference
Source path in the Codex bundle: `OPCODE-REFERENCE.md`

Detailed parameter documentation for all 22 opcodes. For workflow, rules, and examples, see [SKILL.md](#autokap-preset-creation-skill).

## Common Fields (all opcodes)

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `kind` | string | yes | — | Opcode type (see sections below) |
| `description` | string | yes | — | Human-readable description of this step |
| `postcondition` | PostconditionSpec | yes | — | Condition that must hold after execution |
| `recovery` | RecoveryPolicy | yes | — | Recovery behavior on failure |
| `timeoutMs` | number | yes | 15000 | Max time (ms) including recovery |
| `maxFailures` | number | yes | 3 | Max recovery attempts before fatal |

### RecoveryPolicy

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `retries` | number (0-10) | 2 | Deterministic retry attempts |
| `useSelectorMemory` | boolean | true | Try known-good selectors from previous runs |
| `useAltInteraction` | boolean | true | Try keyboard/JS/coordinate click |
| `allowReload` | boolean | false | Reload page and retry. **Use sparingly** — loses UI state (open modals, form data). Only safe after full page transitions. |
| `allowHealer` | boolean | true | Allow LLM to rewrite the failing opcode as last resort |

### SemanticTarget (optional on interaction opcodes)

Fallback when CSS selector fails. The runtime resolves via Playwright semantic locators, then AKTree fuzzy matching.

| Field | Type | Description |
|-------|------|-------------|
| `text` | string | Visible text content (substring match by default) |
| `role` | string | ARIA role: `"button"`, `"link"`, `"textbox"`, `"checkbox"`, `"tab"`, `"menuitem"` |
| `label` | string | `aria-label`, associated `<label>` text, or `title` attribute |
| `near` | string | Nearby text for disambiguation (e.g. `"in the pricing section"`) |
| `placeholder` | string | Input placeholder text |
| `exact` | boolean | If `true`, match text exactly. Default: `false` (substring) |

### PostconditionSpec

| Field | Type | Used by | Description |
|-------|------|---------|-------------|
| `type` | string | all | One of the 8 postcondition types (see SKILL.md) |
| `pattern` | string | `route_matches` | URL glob pattern (`*` = segment, `**` = any path) |
| `selector` | string | `element_visible`, `element_absent`, `text_contains` | CSS selector to check |
| `text` | string | `text_contains` | Expected text substring |
| `threshold` | number (0-1) | `screenshot_stable` | Pixel diff tolerance. Default: `0.01` |
| `waitMs` | number | all except `always` | Max polling time (ms). Default: `5000` |

---

## NAVIGATE

Navigate to a URL.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `url` | string | yes | Full URL to navigate to |

**Can:** Load any URL, follow redirects, handle SPA route changes.
**Cannot:** Open new tabs/windows. Use CLICK with `button: "middle"` for that.

```json
{ "kind": "NAVIGATE", "url": "https://app.example.com/pricing", "postcondition": { "type": "route_matches", "pattern": "/pricing" } }
```

## DISMISS_OVERLAYS

Automatically dismisses cookie banners, newsletter popups, chat widgets, and age gates. Multi-pass: adapter dismissal, Escape key, close-button clicks (multilingual patterns).

No additional parameters.

**Can:** Handle common overlays in any language (en, fr, de, es, it patterns).
**Cannot:** Dismiss overlays that require form input (e.g. age verification with date entry) or CAPTCHAs.

```json
{ "kind": "DISMISS_OVERLAYS", "postcondition": { "type": "overlay_dismissed" } }
```

## CLICK

Click an element.

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `selector` | string | yes | — | CSS selector (use `data-ak` attributes) |
| `target` | SemanticTarget | no | — | Fallback for recovery chain |
| `button` | `"right"` \| `"middle"` | no | left | `"right"` = context menu, `"middle"` = new tab |
| `fingerprint` | string | no | — | AKTree fingerprint for fuzzy matching |
| `selectorAlternates` | string[] | no | — | Alternative selectors tried in order |

**Can:** Left/right/middle click, trigger navigation, open modals, toggle dropdowns.
**Cannot:** Drag-and-drop, long-press, touch gestures.

```json
{ "kind": "CLICK", "selector": "[data-ak=\"pricing-btn\"]", "postcondition": { "type": "element_visible", "selector": "[data-ak=\"pricing-modal\"]", "waitMs": 5000 } }
```

## TYPE

Type text into a form field.

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `selector` | string | yes | — | CSS selector of the input/textarea |
| `text` | string | yes | — | Text to type. Use `{{email}}` / `{{password}}` for credentials |
| `clearFirst` | boolean | yes | true | Clear existing value before typing |
| `target` | SemanticTarget | no | — | Fallback for recovery chain |
| `fingerprint` | string | no | — | AKTree fingerprint |
| `selectorAlternates` | string[] | no | — | Alternative selectors |

**Can:** Type into text inputs, textareas, contenteditable elements. Clear existing values. Use credential placeholders.
**Cannot:** Type into file inputs (use file upload instead). Paste from clipboard.

```json
{ "kind": "TYPE", "selector": "[data-ak=\"search-input\"]", "text": "dashboard", "clearFirst": true, "postcondition": { "type": "any_change" } }
```

## PRESS_KEY

Press a keyboard key.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `key` | string | yes | Key name: `"Enter"`, `"Escape"`, `"Tab"`, `"ArrowDown"`, `"Backspace"`, etc. |

**Can:** Submit forms (Enter), close modals (Escape), navigate focus (Tab), trigger keyboard shortcuts.
**Cannot:** Key combinations (Ctrl+C, Cmd+V). For shortcuts, use the individual key name that Playwright supports.

```json
{ "kind": "PRESS_KEY", "key": "Enter", "postcondition": { "type": "any_change" } }
```

## WAIT_FOR

Wait for an element to appear.

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `selector` | string | no* | — | CSS selector to wait for |
| `target` | SemanticTarget | no* | — | Semantic target to wait for |
| `state` | `"visible"` \| `"attached"` | yes | — | `"visible"` = rendered and visible. `"attached"` = in DOM but may be hidden |

*At least one of `selector` or `target` is required.

**Can:** Wait for page content to load, modals to appear, dynamic elements to render.
**Cannot:** Wait for network requests or API responses directly. Use `state: "attached"` for elements that exist in DOM but are initially hidden (e.g. lazy-loaded tabs).

```json
{ "kind": "WAIT_FOR", "selector": "[data-ak=\"dashboard\"]", "state": "visible", "postcondition": { "type": "element_visible", "selector": "[data-ak=\"dashboard\"]", "waitMs": 10000 } }
```

## SCROLL

Scroll the page or scroll an element into view.

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `direction` | `"up"` \| `"down"` \| `"left"` \| `"right"` | yes | — | Scroll direction |
| `amount` | number | no | viewport height | Pixels to scroll |
| `targetSelector` | string | no | — | CSS selector to scroll into view |
| `target` | SemanticTarget | no | — | Semantic target to scroll into view |

**Can:** Scroll page in any direction, scroll specific element into view, scroll by pixel amount.
**Cannot:** Scroll inside a specific scrollable container (always scrolls the page). For nested scrollable areas, use CLICK to focus the container first.

**Tip:** When `targetSelector` is provided, the runtime scrolls the element into view regardless of `direction`/`amount`. Use `direction` + `amount` for blind scrolling, `targetSelector` for precise positioning.

```json
{ "kind": "SCROLL", "direction": "down", "targetSelector": "[data-ak=\"pricing\"]", "postcondition": { "type": "element_visible", "selector": "[data-ak=\"pricing\"]" } }
```

## HOVER

Hover over an element.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `selector` | string | yes | CSS selector |
| `target` | SemanticTarget | no | Fallback for recovery chain |
| `fingerprint` | string | no | AKTree fingerprint |
| `selectorAlternates` | string[] | no | Alternative selectors |

**Can:** Trigger tooltips, dropdown menus, CSS `:hover` states, hover cards.
**Cannot:** Hover state persists only until the next interaction. If you need to capture a hover state, place CAPTURE_SCREENSHOT immediately after HOVER.

```json
{ "kind": "HOVER", "selector": "[data-ak=\"nav-products\"]", "postcondition": { "type": "element_visible", "selector": "[data-ak=\"products-dropdown\"]", "waitMs": 3000 } }
```

## SELECT_OPTION

Select an option in a **native `<select>` element**.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `selector` | string | yes | CSS selector of the `<select>` element |
| `optionLabel` | string | no* | Select by visible label text |
| `optionValue` | string | no* | Select by `value` attribute |
| `optionIndex` | number | no* | Select by zero-based index |
| `target` | SemanticTarget | no | Fallback for recovery chain |
| `fingerprint` | string | no | AKTree fingerprint |
| `selectorAlternates` | string[] | no | Alternative selectors |

*Exactly one of `optionLabel`, `optionValue`, or `optionIndex` must be provided. Prefer `optionLabel` for readability.

**Can:** Select from native `<select>` dropdowns.
**Cannot:** Select from custom dropdowns (Radix Select, Headless UI Listbox, MUI Select, cmdk). For custom dropdowns, use a CLICK + WAIT_FOR + CLICK sequence: click to open, wait for options, click the option.

```json
{ "kind": "SELECT_OPTION", "selector": "[data-ak=\"country-select\"]", "optionLabel": "France", "postcondition": { "type": "text_contains", "selector": "[data-ak=\"country-select\"]", "text": "France" } }
```

## CHECK

Set a checkbox or toggle to a specific state.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `selector` | string | yes | CSS selector of the checkbox/toggle |
| `checked` | boolean | yes | `true` = checked, `false` = unchecked |
| `target` | SemanticTarget | no | Fallback for recovery chain |
| `fingerprint` | string | no | AKTree fingerprint |
| `selectorAlternates` | string[] | no | Alternative selectors |

**Can:** Idempotently set checkbox state. Safer than CLICK because it sets the desired state directly without toggling.
**Cannot:** Interact with custom toggle components that don't use native checkbox semantics. For those, use CLICK.

```json
{ "kind": "CHECK", "selector": "[data-ak=\"terms-checkbox\"]", "checked": true, "postcondition": { "type": "always" } }
```

## DOUBLE_CLICK

Double-click an element.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `selector` | string | yes | CSS selector |
| `target` | SemanticTarget | no | Fallback for recovery chain |
| `fingerprint` | string | no | AKTree fingerprint |
| `selectorAlternates` | string[] | no | Alternative selectors |

**Can:** Trigger inline editing, text selection, and any UI that responds to `dblclick`.
**Cannot:** Triple-click (select paragraph). Use TYPE with `clearFirst: true` to replace full text content instead.

```json
{ "kind": "DOUBLE_CLICK", "selector": "[data-ak=\"editable-title\"]", "postcondition": { "type": "element_visible", "selector": "[data-ak=\"title-editor\"]", "waitMs": 3000 } }
```

## SET_LOCALE

Set the application's locale/language for the current variant.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `locale` | string | yes | BCP-47 locale (e.g. `"fr"`, `"en-US"`). Use `"$variant"` for runtime substitution |
| `method` | `"browser_context"` \| `"ui_interaction"` \| `"storage"` | yes | How to apply the locale |
| `selector` | string | no | Element to click (for `ui_interaction` only) |
| `storageHints` | StorageHint[] | no | Storage writes (for `storage` only) |

**StorageHint:** `{ "storage": "localStorage" | "sessionStorage" | "cookie", "key": string, "value": string }`

**Can:** Switch locale via storage writes (instant, reliable), browser context, or UI click.
**Cannot:** Handle URL-based routing (e.g. `/fr/page`). For URL-based i18n, use NAVIGATE with the locale path.

**Method decision tree:**
- **next-intl** -> `method: "storage"`, `storage: "cookie"`, key: `"NEXT_LOCALE"`
- **i18next / react-intl** -> `method: "storage"`, `storage: "localStorage"`, key: `"i18nextLng"`
- **URL-based** (e.g. `/fr/page`) -> Use NAVIGATE instead
- **Browser-level only** -> `method: "browser_context"`

```json
{ "kind": "SET_LOCALE", "locale": "$variant", "method": "storage", "storageHints": [{ "storage": "cookie", "key": "NEXT_LOCALE", "value": "$variant" }], "postcondition": { "type": "always" } }
```

## SET_THEME

Set the application's color theme for the current variant.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `theme` | `"light"` \| `"dark"` \| `"$variant"` | yes | Target theme. Use `"$variant"` for runtime substitution |
| `method` | `"color_scheme"` \| `"ui_interaction"` \| `"storage"` | yes | How to apply the theme |
| `selector` | string | no | Element to click (for `ui_interaction` only) |
| `storageHints` | StorageHint[] | no | Storage writes (for `storage` only) |

**Can:** Switch theme via storage writes (instant, reliable), CSS `prefers-color-scheme`, or UI click.
**Cannot:** Handle complex theme systems with more than light/dark (e.g. "system", "sepia"). For those, use `method: "storage"` with the exact value the app expects.

**Method decision tree:**
- **next-themes** -> `method: "storage"`, `storage: "localStorage"`, key: `"theme"`
- **CSS only** (reads `prefers-color-scheme`) -> `method: "color_scheme"`
- **Custom storage** -> `method: "storage"` with app-specific key

```json
{ "kind": "SET_THEME", "theme": "$variant", "method": "storage", "storageHints": [{ "storage": "localStorage", "key": "theme", "value": "$variant" }], "postcondition": { "type": "always" } }
```

## ASSERT_ROUTE

Assert the current URL matches a pattern. Pure validation, no navigation.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `urlPattern` | string | yes | URL glob pattern (`*` = segment, `**` = any path) |

**Can:** Validate the browser is on the expected page. Useful as a checkpoint before capture.
**Cannot:** Navigate or change the URL. Use NAVIGATE for that.

```json
{ "kind": "ASSERT_ROUTE", "urlPattern": "/dashboard/**", "postcondition": { "type": "route_matches", "pattern": "/dashboard/**" } }
```

## ASSERT_SURFACE

Assert that specific elements are visible on the page. Pure validation.

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `selectors` | string[] | yes | — | CSS selectors that must be visible |
| `matchAll` | boolean | yes | true | `true` = ALL must match, `false` = ANY one suffices |

**Can:** Validate page state before capture. Confirm multiple elements are rendered.
**Cannot:** Interact with elements or wait for them. Use WAIT_FOR for dynamic elements.

```json
{ "kind": "ASSERT_SURFACE", "selectors": ["[data-ak=\"header\"]", "[data-ak=\"sidebar\"]"], "matchAll": true, "postcondition": { "type": "always" } }
```

## CAPTURE_SCREENSHOT

Capture a screenshot of the viewport or a specific element.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `captureId` | string | no | Stable identifier for Studio/dev links. Should match preset page/element id |
| `captureName` | string | no | Human-readable label shown in Studio |
| `elementSelector` | string | no | CSS selector for element-level capture (crops to element bounds) |

**Can:** Full-page viewport capture, element-level capture (cropped), LLM verification (detects blank/error/loading/overlay states), alt text generation, favicon extraction.
**Cannot:** Capture content below the fold in a single shot (use SCROLL first). Capture cross-origin iframe content.

**Tip:** Without `elementSelector`, captures the full viewport. With `elementSelector`, captures only that element's bounding box — useful for component-level screenshots.

```json
{ "kind": "CAPTURE_SCREENSHOT", "captureId": "dashboard-main", "captureName": "Dashboard", "elementSelector": "[data-ak=\"main-content\"]", "postcondition": { "type": "always" } }
```

## BEGIN_CLIP

Start recording a video clip. All interactions between BEGIN_CLIP and END_CLIP are recorded.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `clipId` | string | no | Stable identifier for Studio/dev links. Should match preset clip id |
| `clipName` | string | no | Human-readable label shown in Studio |

**Can:** Record viewport interactions as video.
**Cannot:** Record element-level video (always full viewport). Record audio.

```json
{ "kind": "BEGIN_CLIP", "clipId": "open-modal-flow", "clipName": "Open modal", "postcondition": { "type": "always" } }
```

## END_CLIP

Stop recording the video clip. Must pair with a prior BEGIN_CLIP.

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `clipId` | string | no | Must match the BEGIN_CLIP's `clipId` |
| `clipName` | string | no | Must match the BEGIN_CLIP's `clipName` |

**Can:** Finalize recording and produce GIF/MP4 artifact.
**Cannot:** Trim or edit the recording. The full BEGIN_CLIP to END_CLIP duration is captured.

```json
{ "kind": "END_CLIP", "clipId": "open-modal-flow", "clipName": "Open modal", "postcondition": { "type": "always" } }
```

## CLONE_ELEMENT

Duplicate a template element N times into a container. **Non-blocking** — if a selector misses, the opcode is skipped and the run continues.

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `sourceSelector` | string | yes | — | CSS selector of the element to clone |
| `containerSelector` | string | yes | — | CSS selector of the container that receives the clones |
| `count` | number (1-500) | yes | — | Number of clones to produce |
| `removeSource` | boolean | no | false | If true, remove the template after cloning (template must be a child of the container) |

**Can:** Duplicate any DOM node including its descendants and attributes. Optionally remove the source after cloning.
**Cannot:** Clone content from cross-origin iframes. Clone the shadow tree of Web Components (only the host is cloned).

**Soft-failure semantics:** Throws if `sourceSelector` or `containerSelector` is missing. The runner converts the throw into `status: "skipped"` and the variant continues.

```json
{ "kind": "CLONE_ELEMENT", "description": "Clone customer row 5 times", "sourceSelector": "[data-ak=\"customer-row\"]", "containerSelector": "[data-ak=\"customers-tbody\"]", "count": 5, "postcondition": { "type": "always" }, "recovery": { "retries": 0, "useSelectorMemory": false, "useAltInteraction": false, "allowReload": false, "allowHealer": false }, "timeoutMs": 5000, "maxFailures": 1 }
```

## INJECT_MOCK_DATA

Orchestrator opcode that fills a fillable region with mock data. Carries fields for **two complementary delivery mechanisms** and applies BOTH whenever they are wired:

- **Clone**: clones a template DOM element N times into a container and writes each slot value into the cloned descendants. Provides instant visual feedback even before any state-driven re-render. Works for any container whose children are pure markup the runtime can mutate.
- **Trigger**: writes the JSON-encoded `defaultValues` array into a hidden `<input>` the user's app exposed, then clicks a hidden trigger element via `element.click()`. The user's `onClick` handler reads the input, parses the JSON, and re-renders the widget through normal React/Vue/Svelte state updates. Survives any future re-renders that would clobber the cloned DOM, and works for libraries that own their DOM (Chart.js, Recharts, D3).

The two mechanisms are independent and additive. **Wire both whenever possible** — clone for instant paint, trigger to survive re-renders. The runtime applies clone first, then trigger. The opcode is recorded as `applied` if AT LEAST ONE mechanism succeeds.

The named group is looked up from `ExecutionProgram.mockDataGroups` (compiled from `PresetConfig.mockDataInjection.groups`). **Non-blocking** — if both mechanisms fail (or neither was wired), the opcode is recorded as `mockDataGroupResults[groupName] = "skipped"` and the run continues.

### Common params

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `groupName` | string | yes | Name of the MockDataGroup to use (must exist in the preset's `mockDataInjection.groups`) |

### Clone params (optional but typically populated)

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `containerSelector` | string | conditional | — | CSS selector of the container that holds the cloned items. Required if any other clone field is present. |
| `templateSelector` | string | no | first child of container | CSS selector of the element to clone (defaults to `${containerSelector} > *:first-child`) |
| `count` | number (1-500) | no | `defaultValues.length` | Override the row count |
| `removeTemplate` | boolean | no | `false` | If true, remove the template after cloning (use to replace empty-state placeholders) |
| `slotMappings` | array of `{ slot, selector, attribute? }` | conditional | — | Maps each slot name to a target inside a cloned item. Required if `containerSelector` is present. |

**`slotMappings` entry:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `slot` | string | yes | Name of a slot from the MockDataGroup |
| `selector` | string | yes | CSS selector RELATIVE to the cloned item |
| `attribute` | string | no | If present, write to `setAttribute(name, value)`. If absent, write to `textContent`. |

The runtime tags each clone with `data-ak-mock-clone="<i>"` so slot selectors are addressed via the tag rather than `nth-child` math — robust against non-template siblings (placeholders, headers).

### Trigger params (optional but typically populated)

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `inputSelector` | string | conditional | CSS selector of the hidden `<input>` / `<textarea>` / `<select>` that receives the JSON payload. Required if `triggerSelector` is present. Use `[data-ak-fill-input="<group>"]`. |
| `triggerSelector` | string | conditional | CSS selector of the hidden trigger element AutoKap clicks programmatically. Required if `inputSelector` is present. Use `[data-ak-fill-trigger="<group>"]`. |

The runtime serializes `defaultValues` as a JSON array (`JSON.stringify(group.defaultValues)`) and writes it into the input via the React-aware native value setter, then dispatches `input` and `change` events, then calls `triggerElement.click()` directly. The user's `onClick` handler is responsible for parsing and applying the data.

### Validation

At least one mechanism must be complete (clone = `containerSelector` + `slotMappings`; trigger = `inputSelector` + `triggerSelector`). Partial mechanisms are rejected at compile time.

### Capabilities

**Can:**
- Apply both mechanisms additively for the same group, in a single opcode.
- Populate tables, lists, card grids, badge rows, and any container with text or image-source values via the clone mechanism.
- Re-render charts, maps, calendars, and any programmatic widget via the trigger mechanism, by asking the user's app to handle the seed data through normal state updates.

**Cannot:**
- Trigger mechanism cannot work without app-side cooperation (the assistant must add a hidden trigger + input + `onClick` handler to the user's source code). The handler must JSON.parse the input value and validate the shape defensively.
- Clone mechanism cannot persist DOM mutations into React/Vue/Svelte state — on the next render the framework reconciles its virtual tree against the real DOM and overwrites the cloned children. (This is exactly why you should also wire the trigger mechanism — it routes the same data through state, so the next render preserves it.)

### Soft-failure semantics

Returns failure if the group is missing, has no `defaultValues`, the opcode declares neither mechanism, or both wired mechanisms fail at runtime. The runner converts failure into `status: "skipped"` and records `mockDataGroupResults[groupName] = "skipped"`. If at least one mechanism succeeds, the group is recorded as `"applied"` even if the other failed.

### Example — Both mechanisms (recommended)

```json
{
  "kind": "INJECT_MOCK_DATA",
  "description": "Populate preset cards with mock data",
  "groupName": "preset-cards",

  "containerSelector": "[data-ak=\"preset-card-grid\"]",
  "templateSelector": "[data-ak=\"preset-card-grid\"] > [data-ak=\"preset-card\"]",
  "removeTemplate": true,
  "slotMappings": [
    { "slot": "title", "selector": "[data-ak=\"preset-card-title\"]" },
    { "slot": "lastCapture", "selector": "[data-ak=\"preset-card-last-capture\"]" },
    { "slot": "variantCount", "selector": "[data-ak=\"preset-card-variant-count\"]" },
    { "slot": "creditCost", "selector": "[data-ak=\"preset-card-credit-cost\"]" },
    { "slot": "typeBadge", "selector": "[data-ak=\"preset-card-type-badge\"]" }
  ],

  "inputSelector": "[data-ak-fill-input=\"preset-cards\"]",
  "triggerSelector": "[data-ak-fill-trigger=\"preset-cards\"]",

  "postcondition": { "type": "always" },
  "recovery": { "retries": 0, "useSelectorMemory": false, "useAltInteraction": false, "allowReload": false, "allowHealer": false },
  "timeoutMs": 5000,
  "maxFailures": 1
}
```

### Example — Clone-only (static HTML, no app state)

```json
{
  "kind": "INJECT_MOCK_DATA",
  "description": "Populate static customers table",
  "groupName": "customers-table",
  "containerSelector": "[data-ak=\"customers-tbody\"]",
  "templateSelector": "[data-ak=\"customers-tbody\"] > [data-ak=\"customer-row\"]",
  "removeTemplate": true,
  "slotMappings": [
    { "slot": "name", "selector": "[data-ak=\"customer-name\"]" },
    { "slot": "avatar", "selector": "[data-ak=\"customer-avatar\"]", "attribute": "src" }
  ],
  "postcondition": { "type": "always" },
  "recovery": { "retries": 0, "useSelectorMemory": false, "useAltInteraction": false, "allowReload": false, "allowHealer": false },
  "timeoutMs": 5000,
  "maxFailures": 1
}
```

### Example — Trigger-only (pure D3 widget, no DOM template to clone)

```json
{
  "kind": "INJECT_MOCK_DATA",
  "description": "Seed revenue chart with mock data",
  "groupName": "revenue-chart",
  "inputSelector": "[data-ak-fill-input=\"revenue-chart\"]",
  "triggerSelector": "[data-ak-fill-trigger=\"revenue-chart\"]",
  "postcondition": { "type": "always" },
  "recovery": { "retries": 0, "useSelectorMemory": false, "useAltInteraction": false, "allowReload": false, "allowHealer": false },
  "timeoutMs": 5000,
  "maxFailures": 1
}
```

## REMOVE_ELEMENT

Remove all elements matching a CSS selector. **Non-blocking.**

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `selector` | string | yes | CSS selector — every match is removed |

**Can:** Strip empty-state illustrations, debug banners, "powered by" footers, or any visual noise that would distract from the screenshot.
**Cannot:** Remove an element from a parent that re-renders it via React state — the next render will recreate it.

**Soft-failure semantics:** Throws if the selector matches zero elements. The runner converts the throw into `status: "skipped"`.

```json
{ "kind": "REMOVE_ELEMENT", "description": "Remove empty-state illustration", "selector": "[data-ak=\"empty-state\"]", "postcondition": { "type": "always" }, "recovery": { "retries": 0, "useSelectorMemory": false, "useAltInteraction": false, "allowReload": false, "allowHealer": false }, "timeoutMs": 3000, "maxFailures": 1 }
```

## SET_ATTRIBUTE

Set an attribute on the first matching element. **Non-blocking.**

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `selector` | string | yes | CSS selector of the target element (first match only) |
| `attribute` | string | yes | Attribute name (e.g. `"src"`, `"href"`, `"data-state"`) |
| `value` | string | yes | Attribute value |

**Can:** Swap an `<img src>`, change a `data-*` flag to trigger CSS state, set `aria-*` for a11y previews, or override `href` on a link.
**Cannot:** Trigger React state updates — only the DOM attribute changes. Replace `style` reliably (prefer setting a `class` or `data-*` and letting CSS handle the visual).

**Soft-failure semantics:** Throws if the selector is unmatched. The runner converts the throw into `status: "skipped"`.

```json
{ "kind": "SET_ATTRIBUTE", "description": "Override hero image src", "selector": "[data-ak=\"hero-image\"]", "attribute": "src", "value": "https://picsum.photos/1200/600", "postcondition": { "type": "always" }, "recovery": { "retries": 0, "useSelectorMemory": false, "useAltInteraction": false, "allowReload": false, "allowHealer": false }, "timeoutMs": 3000, "maxFailures": 1 }
```

---

## Reference: Interactive Demo Workflow
Source path in the Codex bundle: `references/interactive-demo.md`

Use this reference when the user wants an embeddable product demo driven by
captured DOM states plus deterministic local interactions, not just static
screenshots or clips.

## When to use interactive demos

Choose this mode when:

- the output should feel clickable and alive on a landing page or docs page
- the user wants to recreate a focused product loop, not the entire app
- 2-6 distinct base states are enough and the rest can be reconstructed locally
- the main value comes from local UI reactions such as switching tabs, opening
  a panel, swapping a preview, or editing small bits of content

Prefer plain screenshots or clips when the user only needs static visuals or a
linear recording.

## Core mental model

For interactive demos, the captured DOM is only the substrate. The quality of
the result depends on the interaction model:

1. what the viewer can click or edit
2. which parts of the UI react immediately
3. which changes need a new captured state vs local reconstruction

Do not start from "capture every page". Start from the feature loop the user
wants to showcase, then capture only what is needed to support that loop.

## What to capture vs what to reconstruct

Use this decision rule:

- whole page or route changes: create another `CAPTURE_DOM` base state
- one focused shell matters more than the whole page: use `CAPTURE_DOM` with a
  `selector`
- modal / popover / dropdown / sheet: use `CAPTURE_FRAGMENT`
- local text / class / visible-state changes: use player API or bindings
- repeated editable content: use templates / repeaters / model bindings
- complex app-specific logic: use custom interaction `code` as the last resort

Default bias:

- fewer base states
- more local reconstruction
- a small number of meaningful viewer interactions
- focus on the central product experience

## Required authored markers

### Viewer interaction markers

Add `data-ak-interact="<name>"` to every element the viewer should operate in
the published demo.

```tsx
<button data-ak-interact="open-chat">Open chat</button>
<button data-ak-interact="settings-link">Settings</button>
```

Use kebab-case names and keep the marker set intentionally small.

### Fragment markers

Add `data-ak-fragment="<name>"` on the root of every modal/popover/dropdown
subtree that should be captured separately.

Use `data-ak-mount` when mounting behavior matters:

- `inline`
- `overlay`
- `portal:<target-name>`

Add `data-ak-mount-target="<target-name>"` when a portal mount location is
required.

### Model / behavior markers

Prefer authored markers before custom code:

- `data-ak-model="<path>"`
- `data-ak-bind="..."`
- `data-ak-action="..."`
- `data-ak-template="<name>"`
- `data-ak-behavior="tabs|popover|slider|..."` for standard widget hints

These give the player enough structure to replay many standard UI behaviors
without bespoke JavaScript.

## Capture flow

### Base states

Set `mediaMode: "dom"` and `artifactPlan: { "mediaMode": "dom" }`, then use
normal deterministic opcodes to reach each captured state.

Use `CAPTURE_DOM` for:

- the full page
- or a focused shell via `selector`

Good focused-shell selectors:

- `[data-ak="studio-shell"]`
- `[data-ak="pricing-calculator"]`
- `[data-ak="dashboard-hero"]`

Avoid targeting tiny leaf nodes.

### Fragments

Use `CAPTURE_FRAGMENT` when only a local subtree changes.

Preferred pattern:

1. use visible UI to open the fragment
2. `WAIT_FOR` the fragment root
3. capture it with `CAPTURE_FRAGMENT`

Fallback pattern:

Add a hidden fragment trigger button when the fragment is not reachable through
visible UI.

## Fragment variants

Capture the same fragment under multiple `variantName`s when an interaction only
swaps a subtree in place, for example:

- preview background changes
- before / after toggle
- local layout switch

Rules:

- capture the baseline first
- keep variant names kebab-case
- keep the same fragment root selector across variants
- use variants only for structurally related subtree swaps

## Interaction script

The interaction script lives in `config.interactiveDemo.script`.

Each interaction has only four required fields:

- `state`
- `selector`
- `trigger`
- `code`

Canonical shape:

```json
{
  "state": "dashboard",
  "selector": "[data-ak-interact='open-chat']",
  "trigger": "click",
  "code": "await api.navigate('dashboard-chat-open', 'fade');"
}
```

Use `[data-ak-interact='...']` selectors by default.

Supported triggers:

- `click`
- `hover`
- `submit`

## Player API

The interaction `code` receives:

- `doc`
- `iframe`
- `api`
- `state`
- `target`

Prefer the player API before raw DOM mutation:

- `api.navigate(...)`
- `api.delay(...)`
- `api.query(...)`
- `api.setText(...)`
- `api.toggleClass(...)`
- `api.setValue(...)`
- `api.show(...)` / `api.hide(...)`
- `api.model.*`
- `api.template.*`
- `api.fragment.show / hide / swap / toggle / current / variants`

Use raw DOM access only when the built-in helpers and declarative markers are
not expressive enough.

## Common failure modes

- treating a custom dropdown like a native `<select>`
- not preserving active-state markers such as `aria-selected` or `data-state`
- capturing the trigger but not the controlled surface
- using unstable selectors like utility classes or text selectors
- forgetting that portal UI lives outside the main subtree
- capturing before the controlled DOM has actually settled
- trying to reconstruct canvas/WebGL internals directly from DOM capture
- over-capturing every page instead of focusing on the feature loop

## Preview and publish

After a run, the user previews the demo at:

`/projects/<projectId>/presets/<presetId>/preview`

The public URL is:

`/demo/<presetId>`

The embed snippet is:

```html
<div data-ak-demo="<presetId>"></div>
<script src="https://autokap.app/api/v1/loader.js" defer></script>
```

---

## Reference: Mock Data Injection
Source path in the Codex bundle: `references/mock-data.md`

Use this reference when the user enables mock data generation or when a target
capture would otherwise show empty tables, empty lists, empty charts, or other
unhelpful placeholder states.

## When to use it

Use mock data when:

- the page would otherwise render an empty state
- the marketing value depends on populated cards, rows, charts, or metrics
- the user explicitly enabled "Generate mock data"

Do not use it just because the feature exists. If the real page already renders
stable, attractive data, prefer the real state.

## Core model

Mock data injection has two complementary delivery mechanisms and the runtime
applies both when they are wired:

### Clone

Clone a DOM template element, then write slot values into the cloned
descendants.

Best for:

- tables
- card grids
- simple lists
- badge rows

### Trigger

Write JSON into a hidden input and click a hidden button the app exposes. The
component reads the payload and re-renders through normal app state.

Best for:

- charts
- calendars
- maps
- library-owned DOM
- anything likely to re-render and overwrite a clone

## Default rule

For modern React/Vue/Svelte apps, wire **both** clone and trigger whenever
possible.

Use only one mechanism when:

- the page is pure server-rendered HTML with no client-side state: clone-only
- the widget has no useful DOM template to clone: trigger-only

## Critical slot rule

Every variable UI element must be represented as a slot.

Commonly missed slots:

- dates
- counts
- status badges
- labels that look static
- type icons or icon variants
- prices
- ratings
- role / owner / modified-at metadata
- chart legends and series labels

Rule of thumb:

Walk through each JSX expression, ternary, and conditional class. If that value
could differ across items, it should be a slot.

## Step 1 — add authored markers

Tag:

- the clone template
- the clone container
- every variable child node
- the hidden trigger input/button pair

Example:

```tsx
<div data-ak="preset-card-grid">
  {cards.map((card) => (
    <article key={card.id} data-ak="preset-card">
      <h3 data-ak="preset-card-title">{card.name}</h3>
      <span data-ak="preset-card-type">{card.mediaMode}</span>
      <span data-ak="preset-card-variant-count">{card.variantCount}</span>
    </article>
  ))}
</div>

<input type="hidden" data-ak-fill-input="preset-cards" />
<button
  type="button"
  style={{ display: "none" }}
  data-ak-fill-trigger="preset-cards"
  onClick={(e) => {
    const input = e.currentTarget.previousElementSibling as HTMLInputElement;
    try {
      const parsed = JSON.parse(input.value);
      if (Array.isArray(parsed)) setCards(parsed);
    } catch {
      // ignore malformed payloads
    }
  }}
/>
```

## Step 2 — declare `mockDataInjection.groups`

Declare groups at the top level of the preset config, not inside `program`.

Each group needs:

- `name`
- `description`
- `slots[]`
- `defaultValues[]`

Keep values realistic for the user's domain. Seed 5-10 rows when that makes
sense.

## Step 3 — emit `INJECT_MOCK_DATA`

Place each `INJECT_MOCK_DATA` opcode:

- after the relevant `WAIT_FOR`
- before the relevant `CAPTURE_SCREENSHOT`

Wire both clone and trigger fields whenever possible:

- `containerSelector`
- `templateSelector`
- `slotMappings[]`
- `inputSelector`
- `triggerSelector`

Always keep the opcode non-blocking:

```json
{
  "postcondition": { "type": "always" },
  "recovery": {
    "retries": 0,
    "useSelectorMemory": false,
    "useAltInteraction": false,
    "allowReload": false,
    "allowHealer": false
  }
}
```

## Implementation rules

- `slotMappings[].selector` is relative to the cloned template item
- `removeTemplate: true` when the original template should disappear
- `replaceExisting: true` on the group when placeholder content should be wiped
- use `attribute` only when the value belongs in an attribute such as `src`
- a group counts as applied if at least one delivery mechanism succeeds

## When not to use `INJECT_MOCK_DATA`

Use lower-level DOM mutation opcodes instead when the change is narrow:

- `CLONE_ELEMENT`
- `REMOVE_ELEMENT`
- `SET_ATTRIBUTE`

Reserve `INJECT_MOCK_DATA` for real structured seed-data scenarios.

---

## Reference: Complete Examples
Source path in the Codex bundle: `references/examples.md`

Use these examples as structural templates only. Adapt routes, selectors,
variants, auth handling, and persistence to the user's actual codebase.

## Example 1 — Anonymous screenshot preset

Good for:

- marketing homepage
- pricing page
- public docs landing page

Pattern:

1. `NAVIGATE`
2. `DISMISS_OVERLAYS`
3. optional `SET_LOCALE`
4. optional `SET_THEME`
5. `WAIT_FOR`
6. `CAPTURE_SCREENSHOT`

## Example 2 — Authenticated screenshot preset

Good for:

- dashboards
- settings pages
- project lists behind auth

Pattern:

1. hardcode the real login URL
2. `NAVIGATE` to login
3. `DISMISS_OVERLAYS`
4. `WAIT_FOR` login fields
5. `TYPE` `{{email}}`
6. `TYPE` `{{password}}`
7. `CLICK` submit
8. `WAIT_FOR` the protected surface
9. capture the full page or an `elementSelector`

Never use `{{loginUrl}}`.

## Example 3 — Clip preset

Good for:

- short product walkthroughs
- showing a lightweight interaction
- demo GIFs / MP4s

Pattern:

1. perform setup before recording
2. `BEGIN_CLIP`
3. run only the user-visible interaction sequence
4. `END_CLIP`

Prefer short, deterministic flows.

## Example 4 — Interactive demo preset

Good for:

- embeddable product demos
- focused feature loops
- local UI swaps and fragment-driven demos

Pattern:

1. `mediaMode: "dom"`
2. capture a small number of base states with `CAPTURE_DOM`
3. capture overlays/local subtrees with `CAPTURE_FRAGMENT`
4. wire `interactiveDemo.script`
5. use fragments, model bindings, and small `code` blocks for reconstruction

## Example 5 — Mock data preset

Good for:

- empty dashboards
- empty card grids
- empty tables
- charts that need seeded values for a compelling screenshot

Pattern:

1. add `data-ak` markers to the template and every variable child
2. add hidden trigger/input receivers when the app can re-render the widget
3. declare `mockDataInjection.groups`
4. emit `INJECT_MOCK_DATA` before the screenshot

## Final reminder

These examples are reference shapes, not recipes to copy blindly.

Before using one:

- inspect the codebase
- verify auth requirements
- verify locale/theme handling
- add authored selectors
- persist the preset via API so the user can run `autokap run <preset-id>`