--- name: cloud-screencast description: >- Record a clean product demo video of a web app on a disposable cloud VM — provision a GCE instance, install Xvfb + Chrome + ffmpeg + real fonts, deploy the app, drive the UI deterministically with Playwright over CDP while x11grab records, then cut the raw take into a social-ready clip with speed ramps and burned-in captions. Use when asked to record a demo / screencast / promo video, produce a GIF or clip of a UI, or capture a reproducible app walkthrough — and especially when the local machine is unsuitable (personal bookmarks and profile chrome in frame, wrong window size, the app must start from a pristine state, or the recording would tie up the user's desktop for minutes). allowed-tools: - Read - Write - Edit - Bash --- # cloud-screencast — reproducible UI demo videos on a throwaway VM Records a web UI on a headless cloud box instead of the user's desktop. You get a pristine app state, a chosen viewport, no personal data in frame, and a scripted take you can re-run until it's right — none of which is true of a hand-recorded local screen capture. > **Cost + lifecycle:** this creates a billable VM. Always tell the user it exists, > and **delete it** when the recording is downloaded (§8). An e2-standard-4 left > running is roughly $95/month. --- ## Config Fill once per project, then run. ``` PROJECT = ZONE = VM = APP_REPO = APP_START = APP_URL = SECRETS = ``` --- ## 1. Pick the geometry first Everything downstream depends on this, and getting it wrong means re-recording. Chrome's `--force-device-scale-factor=N` divides the X display into CSS pixels: ``` CSS viewport = Xvfb resolution / DSF ``` Record at **2× the delivery resolution** and downscale in post — supersampling is what makes small UI text look sharp in an H.264 clip. | Delivery | Xvfb | DSF | CSS viewport | Notes | |---|---|---|---|---| | 1920×1080 | 2880×1620 | 2 | 1440×810 | good default, 16:9 | | 1920×1080 | 2560×1440 | 2 | 1280×720 | tighter; short UIs get clipped | | 1080×1080 | 2160×2160 | 2 | 1080×1080 | square, better mobile in-feed | **Do not just use 1280×720.** A real user's browser window is far taller than 720 CSS px, so a 720-tall viewport clips panels that never clip in real life — and the clipped part is usually the payoff (the final state, the confirm button). Probe the actual content height before committing (§4) and go taller if it overflows. Keep integer DSF values. Fractional scale factors make Chrome's text rendering noticeably softer. ## 2. Provision ```bash gcloud compute instances create --project= --zone= \ --machine-type=e2-standard-4 \ --image-family=ubuntu-2404-lts-amd64 --image-project=ubuntu-os-cloud \ --boot-disk-size=50GB --boot-disk-type=pd-balanced \ --labels=purpose=demo-recording ``` 4 vCPU is the floor — x11grab at 2880×1620/30fps plus Chrome plus the app will saturate 2 cores. Copy `reference/setup.sh` over and run it: it installs Xvfb, ffmpeg, Chrome, xdotool, ImageMagick, Node, and — critically — **fonts**. > **Fonts are the whole ballgame for "does this look right".** Most web UIs specify > `-apple-system, BlinkMacSystemFont, "SF Pro Text"` and `ui-monospace, "SF Mono", > Menlo`. None of those exist on Linux, so you get DejaVu fallback and the app looks > subtly wrong in a way reviewers notice but can't name. Install `fonts-inter` > (a near-exact SF Pro substitute, and often already in the CSS fallback chain), > `fonts-jetbrains-mono`, `fonts-noto-color-emoji` (UIs lean on ❤️ 🌙 ★ far more than > you expect), and `fonts-noto-cjk`. Then `fc-cache -f`. **`gcloud compute ssh` is flaky under load.** It intermittently dies with `RemoteDisconnected`. Fall back to plain SSH against the external IP with the key gcloud already provisioned: ```bash ssh -i ~/.ssh/google_compute_engine -o StrictHostKeyChecking=no @ ``` ## 3. Deploy the app + secrets Prefer building from source at the version you want to show — a published package often lags the current version. **Never put secrets in instance metadata or in a command line.** Metadata is readable by anyone with project viewer, and argv shows up in `ps` and shell history. Pipe them over SSH's stdin instead: ```bash grep -E '^(API_KEY|BASE_URL)=' | \ ssh ... "mkdir -p ~/.app && cat > ~/.app/config.env && chmod 600 ~/.app/config.env" ``` **Snapshot the pristine state before the app ever runs.** First-run flows — onboarding, setup wizards, a birth ritual — happen exactly once, and you will need three or four takes to get one good one: ```bash cp -r ~/.app ~/.app-pristine # BEFORE first launch # each retake starts with: rm -rf ~/.app && cp -r ~/.app-pristine ~/.app ``` ## 4. Bring the stack up `reference/stack.sh` does Xvfb → app → Chrome, idempotently. Chrome flags that matter for a clean frame: ``` --kiosk no tabs, no URL bar, no bookmarks --hide-scrollbars scrollbars read as clutter on video --force-device-scale-factor see §1 --lang=en-US UIs branch on navigator.language; pin it --remote-debugging-port=9222 so Playwright can attach --user-data-dir=/tmp/... fresh profile, no first-run bubbles --disable-features=Translate,TranslateUI,AutofillServerCommunication,MediaRouter ``` Also `xsetroot -solid ''` so any gap looks deliberate, and `xset -dpms s off s noblank` so the screen never blanks mid-take. Then **probe before recording** — check the content actually fits: ```js await page.evaluate(() => ({ viewport: [innerWidth, innerHeight], dpr: devicePixelRatio, overflow: document.querySelector('').scrollHeight, clientH: document.querySelector('').clientHeight, })) ``` If `scrollHeight > clientHeight` on something that shouldn't scroll, go back to §1. ## 5. Drive it Attach to the running Chrome rather than letting Playwright launch its own — the browser then survives a script crash, so a failed drive doesn't cost you the whole recording: ```js const b = await chromium.connectOverCDP('http://127.0.0.1:9222'); const page = b.contexts()[0].pages()[0]; ``` Use `playwright-core` (no bundled browser download). Rules that make takes usable: - **Print a `MARK