# Playwemo documentation (Markdown) Concatenated source for agents. Prefer individual `.md` URLs when you need one page. --- # api/configure.md # configureDemoWalk ```ts function configureDemoWalk(partial: DemoWalkConfig): void function getDemoWalkConfig(): ResolvedDemoWalkConfig function resetDemoWalkConfig(): void function resolveDemoPacing( pacing?: DemoPacingConfig, options?: { video?: boolean }, ): ResolvedDemoPacing function personaStyle(persona: string): DemoPersonaStyle function personaLabel(persona: string): string ``` ```ts type DemoWalkConfig = { personas?: Record; layout?: DemoWalkLayout; artifactsDir?: string; startPath?: string; pacing?: DemoPacingConfig; }; type DemoPacingConfig = { pace?: 'tight' | 'normal' | 'slow'; chapterMs?: number; pulseMs?: number; afterClickMs?: number; }; ``` Defaults: empty personas, empty layout, empty pacing (`pace: 'normal'` when resolved), `artifactsDir` = `/opt/cursor/artifacts` (`DEFAULT_ARTIFACTS_DIR`), `startPath` = `/`. Pacing is three teaching beats. `pace` scales them (`tight` 0.75, `normal` 1, `slow` 1.4). Explicit `chapterMs` / `pulseMs` / `afterClickMs` win. After-click default is 500ms on video and 700ms on headed stills. Per-call overrides: `demoFocus(..., { chapterMs })`, `demoClick` / `fillField` / `fillShown` / `demoClickAndWaitUrl` `{ pulseMs, afterClickMs }`. `personaStyle` returns `FALLBACK_PERSONA_STYLE` when the id is unknown. --- # api/device.md # Device ```ts type DemoDevice = 'desktop' | 'tablet' | 'mobile'; function resolveDemoDevice(raw: string | undefined): DemoDevice function demoDeviceFromEnv(env: Record): DemoDevice function demoContextOptions(device: DemoDevice): DemoDevicePreset function applyDemoViewport(page: Page, device: DemoDevice): Promise function isDemoFullscreen(env: Record): boolean function isOsWindowFullscreen(env: Record): boolean function presentDemoWindow( page: Page, env?: Record, ): Promise ``` `DEMO_DEVICE_PRESETS` is the viewport table in the [devices guide](/guide/devices). --- # api/index.md # API overview Import from the package root: ```ts import { configureDemoWalk, runDemoWalk, demoFocus, demoClick, } from '@dragonmastery/playwemo'; ``` | Area | Entry points | |---|---| | Config | `configureDemoWalk`, `getDemoWalkConfig`, `resetDemoWalkConfig`, `resolveDemoPacing` | | Walk | `runDemoWalk`, `withDemoPages`, `resumeOnSharedPage`, `demoTimeout` | | Pacing | `demoFocus`, `demoPoint`, `demoClick`, `demoClickAndWaitUrl`, `demoScreenshot`, `fillField`, … | | Overlay | `installDemoOverlay`, `setDemoCaption`, `setDemoPersona`, `DEMO_OVERLAY` | | Cursor | `installDemoCursor`, `moveDemoCursor`, `holdDemoCursorRing`, `releaseDemoCursorRing`, `DEMO_CURSOR_ID` | | Device | `demoContextOptions`, `presentDemoWindow`, `resolveDemoDevice` | | Record | `isDemoVideo`, `isDemoClean`, `prepareDemoWalkRoot`, `stitchDemoWalk`, `copyWalkForChat` | | Transcript | `formatWalkTranscriptText`, `isSecretControl` | | Desktop | `closeStrayBrowserWindows` (optional) | Types: `DemoPersonaId` is `string`. `DemoWalk

` keys pages by those ids. Teaching stills: pass `shot` on pacing helpers. See [Stills](/guide/stills). --- # api/overlay.md # Overlay ```ts const DEMO_OVERLAY: { rootId: 'e2e-demo-overlay'; personaId: 'e2e-demo-persona'; deviceId: 'e2e-demo-device'; bannerId: 'e2e-demo-banner'; titleId: 'e2e-demo-banner-title'; routeId: 'e2e-demo-banner-route'; noteId: 'e2e-demo-banner-note'; storageKey: 'e2e.demo.caption'; personaKey: 'e2e.demo.persona'; // … }; function installDemoOverlay( context: BrowserContext, persona: string, device?: DemoDevice, ): Promise function setDemoCaption(page: Page, next: Partial): Promise function setDemoPersona(page: Page, persona: string): Promise function paintDemoOverlayInit(args: OverlayInitArgs): void function installDemoCursor(context: BrowserContext): Promise function paintDemoCursorInit(args: { storageKey: string; cursorId: string }): void function moveDemoCursor(page: Page, locator: Locator): Promise function holdDemoCursorRing(page: Page): Promise function releaseDemoCursorRing(page: Page): Promise function hideDemoCursor(page: Page): Promise const DEMO_CURSOR_ID = 'e2e-demo-cursor' const DEMO_CURSOR_RING_ID = 'e2e-demo-cursor-ring' ``` `setDemoCaption` no-ops on `about:blank`. It merges fields and calls `window.__e2ePaintDemoOverlay` when present. `setDemoPersona` writes `e2e.demo.persona` and repaints the chip from the style map in `overlayInitArgs` (every configured actor, not only the lead). `demoFocus` calls both when you pass a bound `walk.page[id]`. `installDemoCursor` runs on both speeds. The pointer stays hidden until `moveDemoCursor`. `holdDemoCursorRing` paints one gold ring for the still; `releaseDemoCursorRing` fades that same ring on headed stills (CI removes it immediately). Video does not hold the ring — it pulses, then clicks when that pulse is done, then pauses. Do not call `pulseDemoCursor` after a hold — that was the double-ring in video. --- # api/pacing.md # Pacing helpers All of these no-op the waits when `E2E_DEMO` is unset, except that clicks and fills still happen immediately and still record transcript events. | Function | Role | |---|---| | `demoFocus(page, step, title, note?, opts?)` | Chapter: transcript + caption + bring-to-front + dwell | | `demoChapter(page, step, title)` | `demoFocus` without a note | | `demoAnnounce(page, note)` | Current-action line only | | `demoDwell(page, ms?)` | Pause (default 2400ms) | | `demoSee(page, ms?)` | Short dwell (default 1200ms) | | `demoScreenshot(page, name)` | Rest-frame viewport PNG (pointer hidden) | | `demoPoint(page, locator, opts?)` | Scroll into view, aim, optional `shot` | | `demoClick(page, locator, opts?)` | Aim, optional `shot`, click | | `demoClickAndWaitUrl(page, locator, url, opts?)` | Same, then `waitForURL` | | `fillField(page, locator, value, opts?)` | Aim, optional `shot`, then type | | `fillIdentityEmail(page, locator, email)` | Sequential keys (80ms) | | `fillTypedValue(locator, value, delay?)` | Click, record, type or `fill` | | `fillShown(page, locator, value, opts?)` | Aim, optional `shot`, then `locator.fill` | `demoClick` options: `{ force?: boolean; waitAfter?: boolean; shot?: string; pulseMs?: number; afterClickMs?: number }`. `fillField` / `fillShown` / `demoClickAndWaitUrl` also take `pulseMs` / `afterClickMs`. `demoFocus` takes `{ chapterMs?: number }` as a fifth argument. App-wide defaults: `configureDemoWalk({ pacing })` — see [configureDemoWalk](/api/configure). `shot` writes a teaching still **after** the pointer and held click ring are on the control, **before** the action — on the stills path. Video skips the hold and the file so the camera does not freeze on a still-pose; keep `shot` on the spec anyway. Each click uses one ring (held, then released — fade on demo, snap-off on CI). On video there is no hold: the ring pulses, then the click lands when that pulse is done. `demoScreenshot` slugs the name (`Vitepress Home` → `vitepress_home.png`) and hides the pointer (rest frame) when it writes a file. Video skips the write. When a stills record dir exists, files land in `/screenshots/`. On headed stills they also copy into `E2E_DEMO_SCREENSHOT_DIR` or `artifactsDir`. --- # api/record.md # Record and stitch Planning (no I/O): ```ts const DEMO_WALK_ROOT = 'test-results/e2e-demo' type DemoWalkKind = 'stills' | 'video' function isDemoVideo(env: Record): boolean function isDemoScreenshotCapture( env?: Record, ): boolean function demoScreenshotAnimations( env: Record, ): 'disabled' | 'allow' function isDemoClean(env?: Record): boolean function demoWalkKind(env: Record): DemoWalkKind function demoWalkKindRoot(env: Record): string function demoVideoStamp(nowMs: number): string function demoVideoDir(env: Record, stamp: string): string function demoChatArtifactPath( env: Record, artifactsDirAvailable: boolean, stamp: string, ): string | null function evenVideoSize(size: DemoVideoSize): DemoVideoSize function demoRecordVideoOption(viewport: DemoVideoSize, dir: string): { dir: string; size: DemoVideoSize } function personaVideoName(persona: string): string function chapterSegments(chapters: readonly DemoChapter[], endMs: number): DemoChapterSegment[] function chapterFileRange( segment: { startMs: number; endMs: number }, startedAt: number, videoOriginMs: number, ): { startMs: number; endMs: number } ``` I/O (needs `ffmpeg` for stitch): ```ts function prepareDemoWalkRoot(env?: NodeJS.ProcessEnv): Promise function ensureDemoVideoDir(dir: string): Promise function savePersonaVideo(video: Video | null, persona: string, dir: string): Promise function stitchDemoWalk(meta: DemoRecordMeta): Promise function writeDemoRecordManifest(meta: DemoRecordMeta, files: Record): Promise function writeDemoLatestPointer( dir: string, env?: Record, ): Promise function copyWalkForChat(walk: string, env?: NodeJS.ProcessEnv, stamp?: string): Promise function writeWalkTranscript(meta: DemoRecordMeta): Promise function copyTranscriptForChat(transcript: string, env?: NodeJS.ProcessEnv, stamp?: string): Promise ``` `CURSOR_ARTIFACTS_DIR` is the default artifacts path. Runtime copies use `getDemoWalkConfig().artifactsDir`. `isDemoScreenshotCapture` is false while `recordVideo` is on — video does not hold the ring, write a still, or call `page.screenshot`. The the click pulse plays, then the click (`pulseDemoCursor`, wait the full pulse, then act). `demoScreenshotAnimations` is `"disabled"` for the stills PNG. `prepareDemoWalkRoot` keeps prior stamps unless `E2E_DEMO_CLEAN=1`. Stamps land under `e2e-demo/stills//` or `e2e-demo/video//` from `isDemoVideo`. Each kind has its own `latest.txt`. Playwright `outputDir` must be `test-results/playwright` (not `test-results`) so a test run does not delete those stamps. --- # api/transcript.md # Transcript ```ts function isDemoTranscript(env: Record): boolean function isDemoTranscriptMarkdown(env: Record): boolean function demoPathFromUrl(url: string): string function isSecretControl(control: string): boolean function redactTranscriptValue(control: string, value: string): string function formatWalkClock(atMs: number): string function formatWalkTranscriptText(doc: DemoTranscriptDoc): string function formatWalkTranscriptMarkdown(doc: DemoTranscriptDoc): string function groupTranscriptChapters(events: readonly DemoTranscriptEvent[]): Array<{ chapter: DemoTranscriptEvent; actions: DemoTranscriptEvent[]; }> function demoChatTranscriptPath( env: Record, artifactsDirAvailable: boolean, stamp: string, ): string | null ``` `formatWalkClock` exists for tooling. The default text handout does **not** print clocks. --- # api/walk.md # runDemoWalk ```ts function isE2eDemo(env?: Record): boolean function demoTimeout(fastMs: number, demoMs?: number): number // demoMs default 900_000 function withDemoPages

( fixtures: { browser: Browser; page: Page }, personas: readonly P[], options?: { device?: DemoDevice }, ): Promise> function runDemoWalk

( fixtures: { browser: Browser; page: Page }, personas: readonly P[], body: (walk: DemoWalk

) => Promise, options?: { device?: DemoDevice }, ): Promise function resumeOnSharedPage( walk: { multi: boolean }, fn: () => Promise, ): Promise ``` ```ts type DemoWalk

= { page: Record; multi: boolean; close: () => Promise; }; ``` `runDemoWalk` always `close()`s in a `finally` (transcript, optional video). `withDemoPages` throws if `personas` is empty. On the fast path it installs the teaching cursor on the shared fixture page (hidden until aimed), sizes the viewport to `options.device` (or `E2E_DEMO_DEVICE`), and brands each `walk.page[id]` so `demoFocus` can update the HUD chip. On the demo path it closes the fixture page and opens one context per persona (overlay + cursor), then `goto(startPath)`. `resumeOnSharedPage` runs `fn` only when `walk.multi` is false. --- # guide/best-practices.md # Best practices A short checklist. The loop itself is [Stills](/guide/stills). ## One spec, two speeds Do not keep a “CI walk” and a “demo walk.” `E2E_DEMO` changes chrome and timing, not the story. | Flag | Use when | |---|---| | unset | Writing the walk, CI, reviewing stills | | `E2E_DEMO=1` | The stills already look good and you need `walk.mp4` | ## `shot` owns the teaching still Pass `shot` on the click, fill, or `demoPoint` the video would click. Capture is after the pointer and ring are on the control, before the action. Rest-frame `demoScreenshot` hides the pointer. Leave `shot` on the video run. Playwemo does not hold the ring or write a still while `recordVideo` is on. On video the ring pulses, then the click, then a short pause. Setup: [Stills](/guide/stills#stills-vs-video) and [Video](/guide/recording#setup-so-the-recording-does-not-freeze). ## Keep assertions on Never skip `expect` to make a recording prettier. ## Configure once Import `configureDemoWalk` from `playwright.config.ts`. Init scripts take arguments; they cannot close over Node state. Set Playwright `outputDir` to `test-results/playwright` so stamps under `test-results/e2e-demo/stills/` and `video/` survive each run. ## Accumulate stamps Leave prior runs on disk. Compare stills across stamps. `E2E_DEMO_CLEAN=1` only when you mean to wipe them. ## Do not minify overlay or cursor inits `addInitScript(fn)` serializes function source. Minify breaks paint. ## Personas stay in the app Ids are strings. Colors, selectors, and `/login` labels belong in the consumer’s `configureDemoWalk`. ## Show the beat, do not describe it In a Cursor Cloud thread, copy stills / `walk.mp4` into `/opt/cursor/artifacts` and inline them. See [Cursor](/guide/cursor). Agents: [For agents](/guide/for-agents). --- # guide/configuration.md # Configuration `configureDemoWalk` merges into a process-wide object. Call it once at startup. `resetDemoWalkConfig` is for unit tests. ## `personas` Map of actor id → chip style. ```ts type DemoPersonaStyle = { bg: string; fg: string; label?: string; // chip text; defaults to the persona id }; ``` Unknown personas use a slate fallback (`FALLBACK_PERSONA_STYLE`). Ids are plain strings — this package does not know `ADMIN` or `PLAYER`. The whole map is passed into the overlay init script so a shared-page `demoFocus` can restyle the chip without closing over Node config. ## `layout` Unused leftover fields. Older configs that still set them continue to merge. | Field | Purpose | |---|---| | `main` | Unused. Kept so older configs still merge. | | `stickyNavs` | Unused. Kept so older configs still merge. | ## `startPath` First URL for each persona window when `E2E_DEMO=1`. Default `/`. ## `artifactsDir` Folder used when copying `walk.mp4` / `transcript.txt` for chat or a PR. Default `/opt/cursor/artifacts` (Cursor Cloud). On a laptop without that directory, copies are skipped unless you set `E2E_DEMO_COPY_WALK`. ## `pacing` Three teaching beats. Optional `pace` (`tight` · `normal` · `slow`) scales all three. Explicit milliseconds win. ```ts configureDemoWalk({ pacing: { pace: 'slow', afterClickMs: 400 }, }); ``` | Beat | Default | Per-call | |---|---|---| | Chapter sit (`demoFocus`) | 2200ms | `{ chapterMs }` | | Click pulse | 450ms | `{ pulseMs }` on click / fill | | After-click linger | 500ms video, 700ms headed stills | `{ afterClickMs }` | `demoDwell(ms)` / `demoSee(ms)` / `{ waitAfter: false }` stay per-call only. Pointer travel and stills pre-hold stay internal. ## Merge rules Later calls **shallow-merge** `personas`, `layout`, and `pacing`. `artifactsDir` and `startPath` replace only when provided. --- # guide/cursor.md # Cursor Cursor Cloud Agents can put Playwemo stills and `walk.mp4` **in the chat**, not only as file paths. The human reviews the beat the same way they would open the storyboard folder. This is the Cloud Agent path. Default `artifactsDir` is `/opt/cursor/artifacts`. A laptop without that folder still writes stamps under `test-results/e2e-demo/` — see [Stills](/guide/stills) and [Video](/guide/recording). ## What Playwemo already copies When `artifactsDir` exists: | Artifact | When it lands there | |---|---| | `walk.mp4` | `E2E_DEMO=1` (unless `E2E_DEMO_VIDEO=0`) | | `transcript.txt` | every `runDemoWalk` (unless `E2E_DEMO_TRANSCRIPT=0`) | | teaching `shot` PNGs | stills run (`E2E_DEMO` unset) | Fast-path stills stay in `test-results/e2e-demo/stills//screenshots/`. Copy the ones you mean to show. Video does not recapture them. Override with `E2E_DEMO_COPY_WALK`, `E2E_DEMO_COPY_TRANSCRIPT`, or `E2E_DEMO_SCREENSHOT_DIR`. See [Environment variables](/guide/environment). ## Show stills in the thread 1. Run the walk (`E2E_DEMO` unset). 2. Read `test-results/e2e-demo/stills/latest.txt`. 3. Copy the teaching PNGs into `/opt/cursor/artifacts/…`. 4. Put **HTML** in the reply. Absolute path. One beat per image. ```bash stamp=$(cat test-results/e2e-demo/stills/latest.txt) mkdir -p /opt/cursor/artifacts/walk-stills cp "$stamp/screenshots/"*.png /opt/cursor/artifacts/walk-stills/ ``` ```html 1 / 4 AUTHOR on writing-a-walk ``` Caption with the step, persona, and route — the same facts as the [transcript](/guide/transcripts). Do not describe the frame instead of showing it. Reading a PNG with a file tool is for the model; the human sees what you put in the reply. Markdown `![](...)` is for this docs site. Cursor chat wants the `` tag. ## Show walk.mp4 in the thread After a headed run, Playwemo copies the stitch when the artifacts dir exists. Override the name with `E2E_DEMO_COPY_WALK`. ```html ``` The file is faststart (`moov` first) so the player can start. A copy that is still writing will not play. ## Rules that bite - Copy first, then tag. `test-results/` is for you; the chat embed uses `/opt/cursor/artifacts`. - Pick the teaching shots. Do not dump every PNG. - Keep `expect`s on. A pretty still that skipped an assertion is not a review. Agents reading this site: start at [For agents](/guide/for-agents). --- # guide/devices.md # Devices and windows Pass `{ device: 'desktop' | 'tablet' | 'mobile' }` to `runDemoWalk` / `withDemoPages`. That sets the viewport and the overlay chip. `E2E_DEMO_DEVICE` is the default when you omit it. Aliases: `phone` → mobile, `ipad` → tablet. ```ts await runDemoWalk({ browser, page }, ['ADMIN'], story, { device: 'mobile' }); ``` `applyDemoViewport(page, device)` resizes an already-open page (the docs walk does this so one spec covers all three). | Mode | Viewport | `isMobile` / `hasTouch` | |---|---|---| | `desktop` (default) | 1920×1080 | no / no | | `tablet` | 768×1024 | yes / yes | | `mobile` | 390×844 | yes / yes | This site’s walk at each preset: ![Desktop overlay](/examples/docs-walk/desktop_01_home_get_started.png) ![Tablet overlay](/examples/docs-walk/tablet_01_home_get_started.png) ![Mobile overlay](/examples/docs-walk/mobile_01_home_get_started.png) Use `demoContextOptions(device)` when you open a context yourself. Do not hardcode a viewport in a new demo context. ## Fullscreen `demoFocus` → `presentDemoWindow` brings the persona forward. OS fullscreen (CDP `Browser.setWindowBounds`) runs only when a real window exists (`--headed` / `HEADED=1` / `E2E_DEMO_HEADED=1`). Headless CDP fullscreen paints the page into a corner of the 1080p video. Leave it off for recordings. `E2E_DEMO_FULLSCREEN=0` keeps the OS window windowed even when headed. ## Leftover Chrome `closeStrayBrowserWindows` closes leftover **Google Chrome** via xdotool/wmctrl. It does **not** close Playwright Chromium. --- # guide/environment.md # Environment variables | Variable | Default | Meaning | |---|---|---| | `E2E_DEMO` | unset | `1` / `true` — multi-window, pacing, video | | `E2E_DEMO_OVERLAY` | on | `0` hides the bottom demo bar on stills and video | | `E2E_DEMO_DEVICE` | `desktop` | One preset. Omit on stills to run desktop + tablet + mobile | | `E2E_DEMO_FULLSCREEN` | on when `E2E_DEMO=1` | `0` windowed; `1` forces on | | `E2E_DEMO_HEADED` | inferred from `--headed` / `HEADED` | OS fullscreen only when a window exists | | `E2E_DEMO_VIDEO` | on when `E2E_DEMO=1` | `0` skips `recordVideo` | | `E2E_DEMO_VIDEO_DIR` | `test-results/e2e-demo//` | Override output folder (`kind` is `stills` or `video`) | | `E2E_DEMO_CLEAN` | unset | `1` / `true` — delete prior `test-results/e2e-demo` stamps before this run | | `E2E_DEMO_COPY_WALK` | `/e2e_demo_walk_.mp4` when that dir exists | Explicit walk copy path | | `E2E_DEMO_COPY_TRANSCRIPT` | sibling `.txt` of the walk copy | Explicit transcript copy path | | `E2E_DEMO_SCREENSHOT_DIR` | `artifactsDir` on `E2E_DEMO=1` | Folder for `demoScreenshot` copies | | `DOCS_E2E_PORT` | `4177` | This repo only — VitePress preview port for `docs:e2e` | | `E2E_DEMO_TRANSCRIPT` | on | `0` skips the handout | | `E2E_DEMO_TRANSCRIPT_MD` | unset | `1` also writes `transcript.md` | `isE2eDemo()`, `isDemoVideo(env)`, `isDemoTranscript(env)`, and `isDemoClean(env)` read these flags. Pass `process.env` from Playwright helpers. Teaching stills (`shot`) write when `E2E_DEMO` is unset (or `E2E_DEMO_VIDEO=0`). Video (`E2E_DEMO=1`) does not hold the ring or write a still. Pulse, then click, then a short pause. See [Stills](/guide/stills) and [Video](/guide/recording). --- # guide/examples.md # Examples This is the **real dogfood walk** of this site (`e2e/docs-walk.spec.ts`): the same steps on **desktop, tablet, and mobile** — stills and one video per viewport. See [Stills](/guide/stills) and [Devices](/guide/devices). Pass `{ device: 'tablet' }` (or `mobile` / `desktop`) into `runDemoWalk`. `E2E_DEMO_DEVICE` is the default when you omit it. Stills and video run all three presets unless that env is set. ## Video `E2E_DEMO=1`. Same walk, one file per viewport. ### Desktop · 1920×1080 ### Tablet · 768×1024 ### Mobile · 390×844 ## Desktop · 1920×1080 Each PNG is a `shot`: pointer on the control, gold ring held, before the click, with the demo bar. `E2E_DEMO_OVERLAY=0` hides the bar. ### Home → Get started ![Pointer on Get started](/examples/docs-walk/desktop_01_home_get_started.png) ### Search ![Pointer on Search](/examples/docs-walk/desktop_02_open_search.png) ![Typing overlay in search](/examples/docs-walk/desktop_03_type_search.png) ![Search results for overlay](/examples/docs-walk/desktop_04_search_results.png) ![Pointer on Overlay and cursor](/examples/docs-walk/desktop_05_search_result_overlay.png) ### Overlay page ![Pointer on the init-script heading](/examples/docs-walk/desktop_06_scroll_init_scripts.png) ### Guide sidebar ![Pointer on Stills](/examples/docs-walk/desktop_07_sidebar_stills.png) ![Pointer on Devices and windows](/examples/docs-walk/desktop_08_sidebar_devices.png) ### Top nav ![Pointer on Recipes](/examples/docs-walk/desktop_09_nav_recipes.png) ![Pointer on Maintainers](/examples/docs-walk/desktop_10_nav_maintainers.png) ![Pointer on Dogfood the docs site](/examples/docs-walk/desktop_10_sidebar_dogfood.png) ![Pointer on API](/examples/docs-walk/desktop_11_nav_api.png) ![Pointer on Pacing helpers](/examples/docs-walk/desktop_12_sidebar_pacing.png) ![Pointer on Guide](/examples/docs-walk/desktop_13_nav_guide.png) ![Pointer on Environment variables](/examples/docs-walk/desktop_14_sidebar_environment.png) ![Pointer on License](/examples/docs-walk/desktop_15_nav_license.png) ## Tablet · 768×1024 Same steps as desktop. Compact chrome opens Menu / hamburger first. ![Tablet — Get started](/examples/docs-walk/tablet_01_home_get_started.png) ![Tablet — Search](/examples/docs-walk/tablet_02_open_search.png) ![Tablet — Type search](/examples/docs-walk/tablet_03_type_search.png) ![Tablet — Search results](/examples/docs-walk/tablet_04_search_results.png) ![Tablet — Overlay result](/examples/docs-walk/tablet_05_search_result_overlay.png) ![Tablet — Init scripts](/examples/docs-walk/tablet_06_scroll_init_scripts.png) ![Tablet — Stills](/examples/docs-walk/tablet_07_sidebar_stills.png) ![Tablet — Devices](/examples/docs-walk/tablet_08_sidebar_devices.png) ![Tablet — Recipes](/examples/docs-walk/tablet_09_nav_recipes.png) ![Tablet — Maintainers](/examples/docs-walk/tablet_10_nav_maintainers.png) ![Tablet — Dogfood](/examples/docs-walk/tablet_10_sidebar_dogfood.png) ![Tablet — API](/examples/docs-walk/tablet_11_nav_api.png) ![Tablet — Pacing](/examples/docs-walk/tablet_12_sidebar_pacing.png) ![Tablet — Guide](/examples/docs-walk/tablet_13_nav_guide.png) ![Tablet — Environment](/examples/docs-walk/tablet_14_sidebar_environment.png) ![Tablet — License](/examples/docs-walk/tablet_15_nav_license.png) ## Mobile · 390×844 Same steps. Narrow HUD (8px inset, no note line). ![Mobile — Get started](/examples/docs-walk/mobile_01_home_get_started.png) ![Mobile — Search](/examples/docs-walk/mobile_02_open_search.png) ![Mobile — Type search](/examples/docs-walk/mobile_03_type_search.png) ![Mobile — Search results](/examples/docs-walk/mobile_04_search_results.png) ![Mobile — Overlay result](/examples/docs-walk/mobile_05_search_result_overlay.png) ![Mobile — Init scripts](/examples/docs-walk/mobile_06_scroll_init_scripts.png) ![Mobile — Stills](/examples/docs-walk/mobile_07_sidebar_stills.png) ![Mobile — Devices](/examples/docs-walk/mobile_08_sidebar_devices.png) ![Mobile — Recipes](/examples/docs-walk/mobile_09_nav_recipes.png) ![Mobile — Maintainers](/examples/docs-walk/mobile_10_nav_maintainers.png) ![Mobile — Dogfood](/examples/docs-walk/mobile_10_sidebar_dogfood.png) ![Mobile — API](/examples/docs-walk/mobile_11_nav_api.png) ![Mobile — Pacing](/examples/docs-walk/mobile_12_sidebar_pacing.png) ![Mobile — Guide](/examples/docs-walk/mobile_13_nav_guide.png) ![Mobile — Environment](/examples/docs-walk/mobile_14_sidebar_environment.png) ![Mobile — License](/examples/docs-walk/mobile_15_nav_license.png) Plain-text handout from the stills run (desktop, tablet, and mobile): [transcript.txt](/examples/docs-walk/transcript.txt). --- # guide/for-agents.md # For agents The HTML site is for humans. The same pages ship as Markdown. Prefer those URLs over scraping. | URL | What it is | |---|---| | [`/llms.txt`](/llms.txt) | Index (title + `.md` link for every page) | | [`/llms-full.txt`](/llms-full.txt) | All pages concatenated | | `/guide/stills.md` | Example: any page path plus `.md` | `Content-Type` is `text/markdown` or `text/plain`. `docs:dev`, `docs:preview`, and the built site all serve them. ## Read in this order 1. [What this is](/guide/) — two speeds, what stays in the app 2. [Getting started](/guide/getting-started) — install + first `shot` 3. [Stills](/guide/stills) — storyboard loop (do this before video) 4. [Examples](/guide/examples) — this site’s own walk as PNGs 5. [Writing a walk](/guide/writing-a-walk) — lift recipe 6. [Cursor](/guide/cursor) — show stills and `walk.mp4` in the thread 7. [API overview](/api/) — exported names 8. [Environment variables](/guide/environment) — flags Then open a single `.md` for the topic you are changing. ## Rules that bite - One spec. `E2E_DEMO` does not fork the story. Keep `expect`s on. - `shot` on the teaching click/fill/point — not a raw Playwright screenshot, not `demoScreenshot` after `demoFocus`. Keep `shot` on the video run; Playwemo does not hold the ring or write a still while `recordVideo` is on ([Video](/guide/recording)). - Do not minify overlay or cursor inits (`addInitScript` serializes function source). - Persona ids are strings. Product selectors live in the consumer’s `configureDemoWalk`. - To show a still or `walk.mp4` in Cursor chat, copy it to `/opt/cursor/artifacts` and emit an `` / `