From 1c019da3fb166d87e499793c15bf8d4893cb98b6 Mon Sep 17 00:00:00 2001 From: zm2231 <25645999+zm2231@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:22:16 -0400 Subject: [PATCH] Add /flex skill: launch videos from real captured footage /flex is /brag with one law added and enforced: the product footage is real. It drives the running product on its real surface (browser, CLI, TUI, desktop, library, or API), captures actual interactions, and refuses to reconstruct the UI in CSS. It reuses brag's assets and tone system via sibling `../brag/` paths. Adds seven honesty gates (real-footage, drift, happy-path, secrets, muted, CTA, fidelity), a retina-capture discipline, and a clean-by-construction workflow that keeps private data off the screen before the shutter instead of blurring it after. DOM text substitution is bounded to non-claim-bearing content and must be disclosed to viewers when it changes what they see. --- skills/flex/SKILL.md | 196 ++++++++++++++++++++++++++++++ skills/flex/references/capture.md | 121 ++++++++++++++++++ skills/flex/references/gates.md | 142 ++++++++++++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 skills/flex/SKILL.md create mode 100644 skills/flex/references/capture.md create mode 100644 skills/flex/references/gates.md diff --git a/skills/flex/SKILL.md b/skills/flex/SKILL.md new file mode 100644 index 0000000..da18dde --- /dev/null +++ b/skills/flex/SKILL.md @@ -0,0 +1,196 @@ +--- +name: flex +description: Turn a project you can actually run into a short launch video built from REAL captured footage of the running product, not a CSS recreation of it. Use when someone says "/flex", "make a launch video from the real app", "brag but with real footage", or is about to post a demo video to Twitter/X, LinkedIn, or a community. Drives the live product on its real surface (browser, terminal, or desktop app), records real interactions, and refuses to fake the product. Requires the app to be runnable; use /brag instead when it is not. +--- + +# /flex + +You built it. Now show it. The actual thing. + +`/flex` is `/brag` with one law added and enforced: **the product footage is real.** No reconstructed dashboards, no CSS mockups of your own UI, no "close enough" screenshots hand-built from reading the source. The single exception is `--allow-recreation`, off by default, and it does not waive the law so much as force it into the open: every reconstructed frame it permits must be declared in the plan (`flex-plan.md`), where the gates can check it, and recorded again in the honesty report. Silent recreation never happens. + +## Why this exists + +`/brag` reads project code and rebuilds the UI in CSS. That is a deliberate, reasonable tradeoff: it works on any project, including one you cannot run. It also has a failure mode that gets worse over time. + +A reconstructed UI drifts. The real product ships a "Stop" button and the video's mock has two buttons. The real status line says "Running locally" and the mock says "Daemon online". Nobody set out to lie; the mock was built from an older reading of the code and the product moved. The result is a launch video whose product shots are subtly, checkably wrong. + +Developers are the one audience that catches this instantly, and a demo video is aimed straight at them. A mocked UI reads as vaporware even when the product is real and good. **If you can run the thing, film the thing.** + +Use `/brag` when the project genuinely cannot be run (a concept, a library with no UI, a dead prototype). Use `/flex` when it can. + +## What this skill does + +1. **Establishes the app is runnable and runs it.** Hard gate. No app, no flex. +2. Inspects the project (reuses `/brag`'s rubric). +3. Plans the concept and storyboard (reuses `/brag`'s planning + tone system). +4. **Captures real footage of the real product**, driving real interactions. +5. Composes with Hyperframes, using the captured footage as the product scenes. +6. Runs the honesty gates against a candidate render, then finalizes and delivers with share copy and a CTA. + +Steps 2, 3, 5 borrow `/brag` wholesale. Steps 1, 4, and 6 are what `/flex` adds. + +## Dependencies + +`/flex` is co-bundled with `/brag` in this plugin (both live under `skills/`), and it reuses brag's 16MB of assets and its tone system rather than duplicating them. Paths below are relative to this skill's directory, so `../brag/` resolves to the sibling brag skill. + +| Need | Path | +|---|---| +| Music, SFX, cue presets | `../brag/assets/` | +| Tone presets | `../brag/references/tones.md` | +| Audio direction | `../brag/references/audio.md` | +| Project inspection rubric | `../brag/references/step-1-inspect.md` | +| Planning + storyboard | `../brag/references/step-2-plan.md` | +| Composition rules | `../brag/references/step-3-compose.md` | +| Render + deliver | `../brag/references/step-4-deliver.md` | + +Installing this plugin installs brag alongside flex, so the sibling paths above always resolve. If you run flex as a standalone user skill instead, point these paths at wherever brag is installed. + +### Reading the borrowed brag references + +The brag references were written for standalone `/brag` and were not edited for this fork. Translate four things as you read them, so a fresh agent does not follow brag's literal instructions off a cliff: + +- **Provenance (the load-bearing one).** brag assumes a CSS recreation of the product is acceptable, so its references tell you to "recreate the dashboard", "recreate a UI element in HTML", "simulate a swipe", and to show "at least one real element". In `/flex` that assumption is inverted and this is the whole point of the skill: wherever a borrowed reference says to recreate, simulate, or mock a product surface or interaction, you **capture it for real instead** (Step 3 and the real-footage gate). "At least one real element" becomes "every product frame is real". The only recreated frame allowed is one you deliberately declare under `--allow-recreation`. If you catch yourself building a scene in CSS because a borrowed reference told you to, stop: that is the exact failure `/flex` exists to prevent. +- **Paths.** Where a brag reference writes `~/.claude/skills/brag/...` or `skills/brag/...` (the asset roots in `audio.md`, `step-3-compose.md`), read it as `../brag/...`, the sibling brag skill in this plugin. If you installed flex standalone, substitute wherever brag actually lives. The brag references never point at the right place on their own. +- **Output directory.** brag writes to `brag-output/`. flex writes to **``**, which defaults to `flex-output-/` (create it in Step 2 and reuse it for every artifact). Wherever a brag reference says `brag-output/`, read `/`. +- **Filenames.** brag's `brag-plan.md`, `brag.mp4`, and `brag.jpg` are flex's `flex-plan.md`, `flex.mp4`, and `flex.jpg`. `composition/`, `composition-brief.md`, and `share-copy.txt` keep their names. When a borrowed reference names a `brag-*` file, write the `flex-*` equivalent. + +## Invocation + +``` +/flex +/flex --tone polished +/flex --url http://localhost:3000 +/flex --format square --duration 20 +``` + +| Option | Values | Default | +|---|---|---| +| `--url` | where the running app lives | auto-detect | +| `--tone` | any `/brag` preset or freeform | inferred | +| `--format` | `landscape`, `vertical`, `square` | `landscape` | +| `--duration` | seconds | auto (15–25s) | +| `--no-music` / `--no-sfx` | flag | music & SFX on; pass a flag to disable that layer | +| `--allow-recreation` | flag | **off** | + +`--allow-recreation` is the escape hatch. It permits a reconstructed scene, and in exchange the run is required to declare every reconstructed frame in `flex-plan.md` (Step 2), which every downstream gate checks and the honesty report records. There is no silent recreation. + +--- + +## Step 0: Prove the app runs + +**Read:** [references/capture.md](references/capture.md) → "Getting the app up" + +Find how this project runs and run it. Confirm the real product responds on whatever surface it has. Not every project is a web app; capture.md's "Getting the app up" table maps each shape to what you capture: a web app serves a URL, a CLI or TUI runs in a terminal, a desktop app opens a window, a library installs and runs, an API answers real requests. + +**Gate:** The actual product is running and you have seen it on its real surface (loaded the URL, run the command, opened the window). If you cannot get it running, **stop and say so.** Do not proceed to build a video of a product you never saw. Offer `/brag` instead, and name the reason. + +This gate is the whole point of the skill. Do not soften it. + +--- + +## Step 1: Inspect + +**Read:** `../brag/references/step-1-inspect.md` + +Same rubric as `/brag`. You now have an advantage it lacks: you can *look* at the running product instead of inferring it from source. Use that. Where the code and the running app disagree, **the running app wins.** + +**Gate:** You can answer all 9 rubric questions, and you have seen every screen you intend to show. + +--- + +## Step 2: Plan and storyboard + +**Read:** `../brag/references/step-2-plan.md` +**Read:** `../brag/references/tones.md` + +Pick `` now (default `flex-output-/`, e.g. `flex-output-2026-07-13-052656/`), `mkdir -p` it, and use it for every artifact from here on. Write `/flex-plan.md`. All of `/brag`'s creative laws apply (short, readable, specific, hook-first, no SaaS language). + +Two additional planning rules: + +**Storyboard the interactions, not the screenshots.** A real capture can *do* things. Plan the product beats as verbs: select the conversation, type the instruction, send it, watch the handoff land. Static screens are a waste of a running app. + +**Budget the product more time than the claims.** Sum your abstract/title scenes and your product scenes. Product must be the larger number. If it isn't, cut a claim. + +**Declare any recreation up front.** If you invoked `--allow-recreation`, list every scene you intend to reconstruct in `flex-plan.md` now, with the reason it cannot be captured. This declaration is the authoritative record the capture, composition, and honesty gates all check, so it has to exist before you compose. A recreated scene that is not in the plan is an undeclared recreation, which fails the real-footage gate. + +**Gate:** `flex-plan.md` exists. Scene durations sum to 15–25s. Product scene time > abstract scene time. Every intended recreation is declared in it. + +--- + +## Step 3: Capture the real product + +**Read:** [references/capture.md](references/capture.md) + +Drive the live app and record it. Real cursor, real typing, real state changes, real latency. + +Two disciplines from capture.md decide whether the footage is usable, and both are easy to get wrong: + +- **Capture at retina.** `surf screenshot` downscales to 1200px by default, and upscaled UI looks fake. Capture with `emulate.viewport --scale 2` and `screenshot --full` so the frame is denser than the slot it fills. This is enforced by the fidelity gate. +- **Clean by construction.** Get private data off the screen before the shutter, not after. Seed innocuous real content, alias real identifiers with the product's own feature, or sanitize live text at the source with a disclosed `surf js` DOM edit. Blurring after the fact wrecks the demo and is the last resort, never the first. + +**Gate:** Every product scene in the storyboard has a real captured asset backing it, captured at full fidelity, with no private data on screen. Any scene without a real asset is recaptured or cut. `--allow-recreation` is the only exception, and it forces a declaration in `flex-plan.md`. + +--- + +## Step 4: Compose + +**Read:** The `hyperframes` skill (all rules apply) +**Read:** `../brag/references/step-3-compose.md` +**Read:** `../brag/references/audio.md` + +Compose in `/composition/`. The captured footage goes in `composition/assets/capture/` and is composited as real media. Motion, type, and transitions are yours; **the pixels of the product are not yours to invent.** + +Frame the capture (device chrome, scale, crop, masking) freely. Do not repaint it. + +**Gate:** `npx hyperframes lint` passes with zero errors. Every product scene references a file under `assets/capture/`, unless it is a scene declared under `--allow-recreation` in `flex-plan.md`. + +--- + +## Step 5: Run the honesty gates + +**Read:** [references/gates.md](references/gates.md) + +Seven gates, each a real failure mode I have watched ship. Most run on the composition and the captured frames; three (the muted gate, the CTA gate, and the rendered-frame half of the secrets gate) need a rendered video or the share copy. So Step 5 straddles a **candidate render**: run every gate you can on the composition first, produce a candidate `flex.mp4` and a draft `share-copy.txt`, then run the three that need them. Nothing is delivered until all seven resolve. + +1. **Real-footage gate.** *(composition)* Every product frame traces to a capture, or is a scene declared under `--allow-recreation` in `flex-plan.md`. +2. **Drift gate.** *(composition)* Nothing on screen contradicts the running app. +3. **Happy-path gate.** *(composition)* Every flow shown works on a fresh install, with no undocumented setup step. +4. **Secrets gate.** *(captured frames, then the candidate render)* No token, key, QR code, private URL, or PII in any captured frame, and none the composition introduced once rendered. Prefer clean-by-construction and disclosed text substitution (never fabricated rows/counts/state) over blur. +5. **Muted gate.** *(candidate render)* Watch it with the sound off; the story still lands. +6. **CTA gate.** *(draft share copy)* The share copy tells people where to get it. +7. **Fidelity gate.** *(captured frames)* Every captured product frame is at least as dense as the slot it fills. No upscaling. A scene declared under `--allow-recreation` in `flex-plan.md` is exempt; the real-footage gate governs it. + +Gates 1–3, the captured-frame half of 4, and 7 run before you render. Then produce the candidate render (`npx hyperframes render`) and draft `share-copy.txt`, and finish 5, 6, and the rendered-frame half of 4 against them. + +**Gate:** Secrets, real-footage, and drift **hard-block** and must pass. A secrets failure on a captured frame is fixed before the candidate render; one the composition introduced is fixed and the candidate re-rendered before Step 6. A real-footage failure means an undeclared recreation, and reporting it does not clear it, that is the silent-recreation the skill exists to stop; fix it (real capture, a declared recreation in `flex-plan.md`, or cut the scene). The remaining gates (happy-path, fidelity, muted, CTA) are reported to the user before delivery; a reported failure there is the user's decision to accept, not a silent pass. + +--- + +## Step 6: Finalize and deliver + +**Read:** `../brag/references/step-4-deliver.md` + +The candidate render from Step 5 becomes the final `/flex.mp4` once its gates pass; re-render only if a reported gate prompted a change to the composition. Pick a real best frame to `/flex.jpg` and finalize `/share-copy.txt`. + +Then write `/honesty-report.md`: the seven gates, pass or fail, every reconstructed frame if `--allow-recreation` was used, and every frame whose text was substituted at the source. This file is the receipt. It is what lets someone else trust the video without re-auditing it. When a substitution materially changes what a viewer believes they are seeing, the disclosure also ships with the video or the share copy, not only in this private report. + +**Gate:** `flex.mp4` exists. `honesty-report.md` exists and every gate is accounted for. + +--- + +## The laws + +Everything in `/brag`'s creative laws, plus: + +**Film it, don't paint it.** If the product can be run, its pixels come from the product. + +**The running app is the source of truth.** Not the source code, not last week's screenshot, not your memory of the UI. + +**Never demo a path that needs an unmentioned step.** If your flow only works after a setup command the video never shows, you are filming a lie with real pixels. Show the step or cut the flow. + +**A demo is a promise.** Someone will install this and try to reproduce exactly what they saw. Make that possible. + +**Capture dense, not soft.** A blurry product shot reads as fake even when it is real. Film at retina and let the frame be denser than the slot it fills. + +**Construct clean, do not blur clean.** Get private data off the screen before you capture. Blur is what a broken demo looks like. diff --git a/skills/flex/references/capture.md b/skills/flex/references/capture.md new file mode 100644 index 0000000..5416e7c --- /dev/null +++ b/skills/flex/references/capture.md @@ -0,0 +1,121 @@ +# Capturing the real product + +The product footage is the reason this skill exists. Everything here serves one outcome: the pixels of the product in the final video came out of the product. + +## Getting the app up + +Find how the project runs before you decide it cannot. + +Look, in order, at: `package.json` scripts (`dev`, `start`, `preview`), `Makefile` targets, `docker-compose.yml`, `Procfile`, a `justfile`, the README's "Getting started" or "Usage" section, and any `.env.example` that hints at required config. + +Some projects are not a web app. Adapt rather than give up: + +| Project shape | What you capture | +|---|---| +| Web app / dashboard | Drive a browser at its URL | +| CLI | Record a real terminal session | +| TUI | Record the terminal, real keystrokes | +| Desktop app | Screen-record the real window | +| Library with no UI | Real terminal: install it, use it, show the output | +| API | Real requests and real responses, in a terminal or a client | + +A library with no interface still has a real surface: the install, the import, the call, the output. Film that. "There's no UI" is not a reason to invent one. + +**If you truly cannot run it, stop.** Say which step failed and why (missing credentials, an unavailable service, a broken build). Offer `/brag`, which is designed for exactly this case. Do not quietly fall back to recreating the UI. A silent fallback is the failure this skill exists to prevent. + +## Driving a web app + +`surf` (Chrome automation) is the primary tool. Check `surf --help`; the useful verbs are `window.new`, `navigate`, `click`, `type`, `screenshot`, `record`, `read`, `emulate.device`, `wait`. + +Work in an **isolated window** so you never disturb the user's real browsing: + +```bash +surf window.new "http://localhost:3000" # returns a window id +surf --window-id screenshot --output /abs/path/frame.png +``` + +Notes learned the hard way: + +- The screenshot flag is `--output`, and it wants an **absolute path**. +- Read the accessibility tree (`surf --window-id read --compact`) to get stable element refs before clicking. Do not guess selectors. +- Close the window when you are finished (`surf window.close `). Leave the user's browser as you found it. +- For a mobile beat, `emulate.device "iPhone 14"` and capture the same flow again. Reset the emulation afterward. + +Playwright or Puppeteer are fine substitutes if `surf` is unavailable. The tool does not matter. The realness does. + +`surf --window-id ` scopes every call to your isolated window. Always pass it, especially if another agent may be driving surf at the same time. Two more verbs beyond the basics earn their keep: `emulate.viewport` (set size and device scale, below) and `js` (run JavaScript in the page, used for clean-by-construction below). + +## Capture at retina + +This is the single biggest quality mistake, and it is invisible until the video is rendered. `surf screenshot` **downscales to `--max-size 1200` by default.** A 1200px capture placed in a browser mock that fills a 1920x1080 frame gets upscaled, and upscaled UI reads as soft, cheap, and fake, which is the exact impression a launch video cannot afford. + +Capture denser than the slot the image will fill, never sparser: + +```bash +surf --window-id emulate.viewport --width 1600 --height 900 --scale 2 # retina desktop +surf --window-id screenshot --full --output /abs/path/frame.png # --full skips the 1200px downscale +``` + +`--scale 2` renders the page at 2x device pixels, and `--full` keeps the native resolution. A 1600x900 viewport at scale 2 yields a 3200x1800 PNG, which stays crisp when composited. For a phone beat, `emulate.viewport --width 400 --height 860 --scale 3 --mobile`. Reset the viewport before you close the window. + +Aspect ratio matters more than absolute size: match the capture's aspect to the slot it fills in the composition, then make it dense. A frame at the right aspect and 2-3x density drops into an existing composition and simply renders sharp. + +The fidelity gate (see gates.md) checks exactly this: no product frame may be captured at fewer pixels than the region it occupies. + +## Capture interactions, not screenshots + +A running app can *do* things. A storyboard that only needs static screens is wasting it, and static screens are also what a recreation is good at, so you have given up your only advantage. + +For each product beat, capture the **verb**: + +- Not "the inbox" but *selecting a conversation and watching the pane fill*. +- Not "the composer" but *typing an instruction and pressing send*. +- Not "the handoff panel" but *a handoff arriving in the other agent*. + +`surf record` produces an animated capture of a sequence. Prefer it for any beat with motion. Fall back to a burst of stills only when a real recording is not possible, and note it. + +**Let real latency show.** A spinner that resolves, a status that flips from queued to delivered, a row that appears. These are the moments that prove the thing is alive. A recreation cannot fake the feeling of a real state change, which is exactly why it is worth filming. + +## Clean by construction + +An empty app films badly, a fake-looking app films worse, and an app full of the operator's private data cannot ship at all. The move that solves all three is the same: **curate a clean, real, representative state before you capture**, so there is nothing to hide and nothing to blur. + +This is the most important lesson in this skill. The instinct when a frame contains something private is to blur or crop it. Blur wrecks the demo. A launch video whose hero screen is a grey smear tells the viewer the product has nothing to show. Do not get into that position. Get the private data off the screen *before* the shutter, not after. + +How to construct clean state, in order of preference: + +1. **Seed innocuous real content.** Drive the product to produce a genuine but harmless session, record, or job. A real coding agent asked to "rank three chess openings for a beginner" produces a real transcript with zero private paths. The content is real; it just is not sensitive. +2. **Alias real identifiers using the product's own feature.** If the product can rename or alias sessions, users, or records for display, use that. The pixels stay the product's; the private name never renders. This is not repainting, it is using a shipped feature. +3. **Substitute live sensitive text at the source with `surf js`.** When a view shows real data you cannot re-seed (a live queue, an activity log), replace the offending **text** before capturing. Edit text nodes that already exist on screen; do not inject new rows or alter any claim-bearing state: + + ```bash + surf --window-id js "(function(){ \ + for(var i=1;i<99999;i++){clearInterval(i);} /* best-effort: kills simple interval polls that would overwrite your edits; misses high timer IDs, requestAnimationFrame, workers, and websocket-driven updates, so re-read the DOM after and confirm the text held before you capture */ \ + document.querySelectorAll('.message-body').forEach(function(el){el.textContent='clean demo text';}); \ + return document.querySelectorAll('.message-body').length; })()" + ``` + + The `js` verb evaluates the code as a single **expression**, so statement forms like top-level `const` and `return` throw; wrap the work in an IIFE (verified against `surf` 2.8.0). Read the app's own source or the live DOM to get the real class names. The timer sweep is blunt: it can also freeze a live interaction you meant to film (a spinner, a status flip, a row that lands). Reserve it for a frame you capture as a still right after sanitizing. If the beat needs a real state change on camera, target the specific poll handle instead of sweeping every ID, and after any sweep exercise the planned interaction to confirm the live transition still fires, not only that the substituted text held. **Substitute only non-claim-bearing text** (a message body, a private name) with clean equivalents. **Do not fabricate claim-bearing state:** never inject a row, bump a count, flip a status, or paint an interaction result the product did not actually produce. Those are claims a viewer reads as product behavior, and a DOM edit that manufactures them is the exact lie this skill exists to prevent. If a beat needs more or different content, produce it by *using the product* (option 1), not by injecting DOM. **Disclose every substitution in the honesty report; and when a substitution materially changes what a viewer believes they are seeing, disclose it to the viewer too** (an on-screen note or a line in the share copy), not only in the private receipt. + +Use the product's own commands and interfaces to build state. Do not hand-edit the database. If the *structure* on screen could not have arisen from using the product, you are faking it; if only the *text* is demo content and you disclosed it, you are staging a demo, which is what a demo is. + +## What you may and may not change + +**You may:** frame, crop, scale, mask, add device chrome, slow down, speed up, cut, and hold. You may put a real screenshot inside a drawn laptop or phone. You may zoom into a corner. + +**You may not:** repaint UI, retype labels, recolor status, redraw a button, "clean up" a value, inject a row, bump a count, flip a status the product did not produce, or composite a widget that the app does not render. The product's pixels are not yours. + +If a real frame contains something that must not ship (a token, a customer name, a private path), the fix ranks in this order: **construct clean state and re-capture** (best, see "Clean by construction"), **sanitize the offending text at the source** with the product's aliasing or a disclosed `surf js` edit, then **crop it out**, then **cut the shot**. Blurring or masking the content is the last resort, not the first, because it is the move that makes the demo look broken. You still never repaint pixels to fake a feature or a state the product cannot produce. + +## Save captures where the composition can find them + +``` +/composition/assets/capture/ + 01-overview.png + 02-select-conversation.gif + 03-send-instruction.gif + 04-handoff-lands.png + 05-mobile.png +``` + +Name them for the **beat**, not the screen. The name should say what is happening. Every product scene in the composition must reference a file in this directory, and the real-footage gate checks exactly that. The one exception is a scene declared under `--allow-recreation` in `flex-plan.md`, which has no captured file by definition; everything else without a real asset is recaptured or cut. diff --git a/skills/flex/references/gates.md b/skills/flex/references/gates.md new file mode 100644 index 0000000..d4e33eb --- /dev/null +++ b/skills/flex/references/gates.md @@ -0,0 +1,142 @@ +# The honesty gates + +Seven checks, run before you deliver. Most run on the composition and the captured frames before you render; three (muted, CTA, and the rendered-frame half of secrets) run against a candidate render and a draft share copy. Each one is a real failure mode that has shipped in a real launch video. Report the result of every gate to the user, then write them to `honesty-report.md`. + +A gate that "probably passes" has not been run. + +--- + +## 1. Real-footage gate + +**Check:** Every product frame in the composition traces back to a file in `assets/capture/`, or is a scene explicitly declared under `--allow-recreation` in `flex-plan.md`. + +Grep the composition for image and video references. Any product surface drawn with divs, CSS, and hand-typed labels is a violation, unless it is a declared `--allow-recreation` scene named in `flex-plan.md`. Recreation with no declaration is exactly the failure this gate exists to catch. + +```bash +ls composition/assets/capture/ # should not be empty +grep -o 'assets/capture/[^"]*' composition/index.html | sort -u +``` + +**The tell:** `assets/` contains only `music/` and `sfx/`. If there is no capture directory, no product was filmed, and the video is a drawing of the product. + +**Fail action:** Capture the missing beats. If a beat genuinely cannot be captured, cut it or invoke `--allow-recreation` and declare it in `flex-plan.md`. This gate hard-blocks the render: an undeclared recreation stops the run. Reporting the failure does not clear it, and you do not proceed to Step 6 until every product frame is a real capture or a declared recreation. + +--- + +## 2. Drift gate + +**Check:** Nothing on screen contradicts the running app. + +Even a real capture can drift: a screenshot from three weeks ago, a beta build, a feature flag that is off in production. Open the running app **now**, side by side with the composition, and compare every product frame. + +Look specifically at: status text, button sets, nav labels, counts, and version strings. These are what people zoom in on. + +**Real example:** a video's mock showed a status of "Daemon online" and two buttons. The shipped product said "Running locally" and had three. Nobody lied. The mock was built from an older read of the code, and the product moved. That is exactly how drift arrives. + +**Fail action:** Recapture. A stale real screenshot is still a false claim. + +--- + +## 3. Happy-path gate + +**Check:** Every flow shown works on a fresh install, with no undocumented setup step. + +This is the subtlest gate and the most damaging to fail, because the footage is real and the video is still misleading. + +Ask, for each product beat: **would this work for someone who just installed it and followed the README?** If it only works because of a setup command the video never shows, a service you happened to have running, or a bridge you enabled last Tuesday, then you are filming a lie with real pixels. + +**Real example:** a demo showed an agent handoff landing live in a desktop app. Genuine footage. But that write path only works if the app was launched through a specific bridge command. A viewer who installs, opens the app normally, and reproduces the video exactly gets a failed send. + +**Fail action:** Either show the setup step in the video, or cut the flow, or fix the product so the demoed path is the default. Documenting an aspirational happy path is fine **only** if the product will ship that way before the video posts. If it will, say so, and hold the video until it does. + +--- + +## 4. Secrets gate + +**Check:** No token, API key, QR code, private URL, tailnet or internal hostname, customer name, email, or file path that reveals something private, in any frame. + +This gate never gets waived. Real footage means real data, and the failure is unrecoverable once posted. + +Scan every captured frame and every rendered frame. Zoom in. Check: address bars, terminal scrollback, settings screens, notification toasts, browser tabs, window titles. + +**QR codes are the sharpest edge.** A QR encoding an access token is a credential rendered in a format your eye does not read as one. Anyone who pauses the video owns it. A settings screen that displays a QR by default will leak it to every viewer. + +**Real example:** a dashboard's settings screen rendered a live access-token QR by default. A demo video of that screen would have handed full control of the user's agents to the entire audience. + +**Fail action, in order:** (1) construct clean state and re-capture (see capture.md "Clean by construction"); (2) substitute the offending **text only** at the source, via the product's own aliasing or a `surf js` DOM edit (never inject rows or alter counts/status), **disclose that edit in the honesty report**, and surface the disclosure to viewers when it changes what they believe they see; (3) crop it out; (4) cut the shot. **Blurring or masking is the last resort, not the first**, because it is the move that makes the hero shot look broken, which is its own kind of failure. **Never repaint pixels to fake a feature or state.** And never deliver past this gate: a secrets failure on a captured frame is fixed before the candidate render, and one that appears only in the rendered composition is fixed and the candidate re-rendered. Fix it first. + +--- + +## 5. Muted gate + +**Check:** The video lands with the sound off. + +Twitter, LinkedIn, and most feeds autoplay muted. Most viewers will never hear the music bed, the beat grid, or the SFX. That effort still pays off in an embed or a community post, but the feed is muted by default. + +Watch the render with the sound off. Does the story still land? Is every claim carried by something visible? If a beat only works because of an audio hit, that beat does not work. + +**Fail action:** Move the meaning into the frame. Audio supports; it never carries. + +--- + +## 6. CTA gate + +**Check:** The share copy tells people where to get it. + +Good copy that ends without a link is a conversion thrown away. The post is the moment someone decides to try it, and they cannot. + +Share copy needs: the hook, what it is, and **where to get it** (repo URL, install command, or site). If the license or platform constrains who can use it, say so plainly rather than letting someone discover it after cloning. + +**Also check hook coherence.** The first line of the share copy and the first line of the video should be the *same line*. Two different hooks compete with each other; one hook, stated twice, compounds. + +**Real example:** a launch post opened "Your agents are already working. They just have no idea what the others are doing," while the video opened "Your agents are working. Just not together." Both are good. Together they are two openings fighting for the same two seconds. + +**Fail action:** Add the CTA. Pick one hook. + +--- + +## 7. Fidelity gate + +**Check:** Every captured product frame is at least as dense as the region it fills on screen. No upscaling. A scene declared under `--allow-recreation` in `flex-plan.md` is not a capture, so it is exempt here and is governed by the real-footage gate instead. + +This is the gate for the failure that is invisible until render: a soft, low-resolution product shot. It happens by default, because `surf screenshot` downscales to 1200px unless told otherwise, and a 1200px image stretched across a 1920x1080 frame is blurry. Soft UI reads as fake, which defeats the entire point of filming the real thing. + +For each product capture, compare its pixel dimensions to the pixels it occupies in the composition. A frame that fills a 1450px-wide browser mock must be at least 1450px wide, and 2-3x that is better. See capture.md "Capture at retina". + +```bash +# a frame that fills most of a 1920-wide comp should be ~3000px+ wide +ffprobe -v error -select_streams v -show_entries stream=width,height -of csv=s=x:p=0 frame.png +``` + +**Fail action:** Re-capture with `emulate.viewport --scale 2` and `screenshot --full`. Do not upscale the existing PNG; upscaling adds pixels, not detail. + +--- + +## Writing the report + +`honesty-report.md`, in the output directory: + +```markdown +# Honesty report + +| Gate | Result | Note | +|---|---|---| +| Real footage | PASS | 5 beats, all from assets/capture/ | +| Drift | PASS | compared against localhost:7412 at 05:31 | +| Happy path | FAIL | handoff beat needs `app launch bridge` first; see below | +| Secrets | PASS | settings screen cut; it renders a token QR | +| Muted | PASS | every claim is on screen | +| CTA | PASS | repo link + install line in share copy | +| Fidelity | PASS | frames captured at 3200x1800, drop into a 1450px slot | + +## Reconstructed frames +None. (Or: list every one, with the reason.) + +## Sanitized frames +None. (Or: list each frame where DOM text was edited via `surf js` or the product's aliasing, what was replaced, and why. The UI, feature, counts, and state stay real; only the disclosed text is demo content. If a substitution materially changed what a viewer believes they are seeing, note the viewer-facing disclosure that ships with the video or share copy.) + +## Open risks +The handoff beat documents the intended default, which ships in v0.2. +``` + +The report is the receipt. It is what lets someone trust the video without re-auditing it, and it is what stops *you* from talking yourself past a gate at 2am.