# Project export: Cutscene

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Most screen recorders save pixels. Cutscene saves the page structure too, so zooms land on the real element and a stale demo fails like a failing test.
- Devpost: https://devpost.com/software/cutscene-n40yek
- GitHub: https://github.com/macayu17/Cutscene
- Demo: https://cutscene.ayushh.in/
- Video: https://www.youtube.com/embed/akee4mF3XBQ?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — macayu17 (179 commits)

## Devpost submission (written by the team)

### Inspiration

I kept re-recording the same README GIF. The product would change, the GIF would go stale, and nobody noticed until someone opened an issue about it. Screen recorders store a demo as pixels. The editor then has to guess what mattered. It guesses the zoom target from a click coordinate and the section boundary from a pause. The guess is often wrong, and it always looks like a guess. The recording is the problem, not the editor. If the recorder captures the page structure at the same time as the pixels, it does not have to guess anything. It knows the click landed on the "Create report" button and it knows that button's exact bounds.

### What it does

Cutscene records a Chrome tab and the DOM events behind it into one bundle: a WebM file, a versioned JSONL trace, and a metadata file. The trace holds clicks, inputs, navigation, scrolling, viewport changes, ranked locators, element bounds, and clock sync markers. From that single capture you get: Zooms that frame the recorded element instead of the cursor position. A 1080p MP4, a README GIF with one global palette, a 9:16 crop, step docs, cropped screenshots, per-step GIFs, captions, and a Playwright flow skeleton. An interactive demo. It is a static ZIP that pauses the video at every recorded click and waits for you to click the real element. No backend and no sign-in. A recording quality report. Every element you touched already carries its role, accessible name, and ranked locators, so the report flags interactions on elements with no accessible name and steps whose best locator is one that an ordinary edit breaks. Regeneration. A demo.yml file declares the trace, a base URL, and the outputs to maintain. The runner replays the stored locators against a current build and rebuilds the outputs. Input values and sensitive elements are masked at capture time, before anything is written to disk. How I built it Five packages. The extension is Manifest V3 and deliberately thin. It uses chrome.tabCapture for video, MediaRecorder for encoding, and a content script for the trace. There is no product logic in it, because the extension is the only part I cannot easily change later. packages/trace holds the real work: the schema, ranked locator generation, privacy masking, clock fitting, coordinate math, zoom generation, quality analysis, and drift healing. It has no DOM dependency and both the extension and the editor import it. The editor is React, TypeScript, Vite, and Zustand, with plain CSS tokens. Rendering runs on ffmpeg.wasm, so nothing leaves the machine. The runner validates demo.yml, replays in Chromium, and writes drift and staleness reports. The whole project ran off a phase-gated PRD. Each phase had numeric exit criteria and could not start until the previous phase reported its numbers. Phase 0 was allowed to fail. Challenges I ran into Three clocks. The video clock, the DOM event clock, and the recorder's own clock do not start together. A zoom that fires two frames late reads as a mistake even when the element is correct. Sync markers and a shared media clock model got the acceptance run to a mean timing error of 0.258 frame across ten sampled zooms, with a worst case of 0.422. Failing honestly. Regeneration is only useful if a replay against a changed build fails instead of clicking the wrong thing. Locators are captured as a ranked list. If the best one stops resolving and a weaker one still finds the element, the step is marked drifted. If nothing resolves, it is orphaned, the replay stops, and the command exits 1 with the declared outputs untouched. Knowing when not to repair. I added a --heal flag that promotes the locator that actually resolved and writes the trace back, so a renamed test ID is fixed permanently. The hard part was making it refuse to go further. A deleted button has no locator left to promote, so it stays orphaned and the run still fails. A tool that quietly hides a real break would be worse than no tool. Privacy in the interactive export. Masking at capture time was the easy half. The exported player had to carry enough data to place hotspots and nothing else. The manifest is five top level keys and four keys per step. I verified zero locators, zero raw trace, and zero input values in the shipped HTML. Accomplishments that I am proud of Every claim here is a measured number I can reproduce locally. 10 of 10 sampled zooms landed on the correct element, with a mean timing error of 0.258 frame. The interactive export on a real TodoMVC recording completed 5 of 5 hotspots in order, with a maximum hotspot edge error of 0.014435 rendered pixels and no console errors. The quality report found a real defect on a real site. All five checkboxes on the recorded TodoMVC path expose no accessible name. 315 tests pass, typecheck is clean across 5 packages, and 6 of 6 Chromium end to end tests pass. The README states the limitations above the feature list. Chrome only, DOM based apps only, no cross origin iframes, open shadow roots only. What I learned Capture is cheap and permanent. Features are expensive and reversible. Phase 1 records every field in the schema, including ones that only much later phases read, and then uses almost none of them. That felt wasteful at the time. It was not. A field you failed to capture forces every existing user to re-record. The quality report proved the point. It shipped this week and cost about two hours, because the two fields it needs were captured on day one and ignored for months. I also learned how much a gated PRD changes agent assisted development. Ask an agent to "build the recorder" and it builds all eight phases badly. Give it three numbers that Phase 0 must hit before Phase 1 may start, and it builds one phase well and stops.

### What's next

Regeneration in CI, so a pull request that changes a route arrives with a regenerated README GIF attached and fails when the demo no longer matches. After that, comments anchored to elements rather than timestamps, so review survives a redesign.

## README (from the GitHub repository)

# Cutscene

Cutscene records a Chrome tab and the DOM events behind it. The editor uses
recorded element bounds to frame clicks instead of guessing from cursor
coordinates.

**[Click through a live demo](https://cutscene-editor-sandy.vercel.app/demo)**
· [what it is](https://cutscene-editor-sandy.vercel.app)

The demo is a static file exported by the editor from the recording below. It
pauses the video at every recorded click and waits for you to hit the real
element. No install and no account.

![Cursor-position zoom on the left and element-locked zoom on the right](docs/assets/element-locked-comparison.gif)

Same recording. Cursor-position zoom on the left; recorded-element zoom on the
right.

## Limitations

- Chrome only.
- DOM-based web applications only. Canvas, WebGL, maps, and similar surfaces
  fall back to pixels because they do not expose useful semantic elements.
- Cross-origin iframes cannot be traced.
- Shadow DOM is traced only when its root is open.
- Recording and editing are local by default. The optional self-hosted share
  server has public UUID links only; it has no accounts or private links. What
  is recorded and what leaves the machine is stated in the
  [privacy policy](site/privacy/index.html).

## What works today

- Tab video capture with optional microphone audio. Stopping a recording opens
  the editor with it already loaded; the extension keeps the five most recent
  recordings and flushes the take to storage while it runs, so an interrupted
  recording is openable instead of lost.
- A versioned JSONL trace containing clicks, inputs, navigation, scrolling,
  viewport changes, ranked locators, element bounds, and clock-sync markers.
- Capture-time masking for input values and sensitive elements.
- A local editor with an event list and trace lane. Hover a tick to inspect its
  recorded element; click it to seek.
- Automatic element-locked zooms with manual add, delete, retime, and retarget
  controls.
- Element-anchored callouts rendered consistently in preview, GIF, and MP4.
- Preconfigured CSS-selector blur tracks with enable/delete controls and the
  same redaction in preview, GIF, and MP4.
- Cursor smoothing, click ripple, idle hiding, local brand presets, and 9:16
  crop export.
- README GIF export with one global palette, plus 1080p H.264 MP4 export.
- Linear interactive-demo export that pauses the rendered video at every
  recorded click and continues through element-aligned hotspots.
- Step documentation, cropped screenshots, per-step GIFs, a Playwright flow
  skeleton, and imported SRT/VTT captions.
- A recording quality report derived from the trace alone: interactions on
  elements with no accessible name or role, and steps whose strongest locator
  is one an ordinary edit breaks.
- A minimal filesystem-backed server for uploading a bundle and sharing its
  video through a public link.
- Local demo regeneration that replays ranked locators, records fresh pixels
  and trace data, compares the trace, and rebuilds GIF, MP4, and documentation
  outputs without opening a pull request.

## Run locally

You need Chrome, Node.js, and pnpm 11.6.0.

```sh
pnpm install
pnpm build
```

Open `chrome://extensions`, enable **Developer mode**, choose **Load unpacked**,
and select `packages/extension/dist`.

The editor ships inside the extension, so recording a tab opens it directly.
To run the editor as a standalone page instead — for recordings you already
have on disk:

```sh
pnpm --filter @cutscene/editor exec vite
```

Open the local URL printed by Vite.

Start the optional share server:

```sh
pnpm --filter @cutscene/server start
```

It stores bundles in `data/` and listens on port `4180`. After loading a
recording in the editor, choose **Create share link** and enter the server URL.
The editor uploads the original three files and shows the public link and the
date it expires.

Before pointing anything public at it, know what it does and does not do. A
share link is public and unguessable; there are no accounts and no private
links. Recordings are deleted after their retention window, whether or not the
sweep has run. The owner token can delete a recording immediately with
`DELETE /api/recordings/<id>`. Writes are rate limited per address and the
server refuses new recordings once its store is full.

| Variable | Default | Meaning |
| --- | --- | --- |
| `CUTSCENE_DATA` | `data` | Where bundles are stored. |
| `PORT` | `4180` | Listening port. |
| `CUTSCENE_RETENTION_DAYS` | `30` | Days before a recording expires and is swept. |
| `CUTSCENE_STORE_LIMIT_BYTES` | 20 GiB | Refuse new recordings past this total. |
| `CUTSCENE_WRITE_BURST` | `20` | Writes one address may make at once. |
| `CUTSCENE_WRITE_PER_MINUTE` | `20` | Sustained write rate per address. |
| `CUTSCENE_TRUST_PROXY` | unset | Set to `1` only behind a proxy you control, so `X-Forwarded-For` is believed. |

## Check a recorded flow against a current build

Install the runner where the check should happen. Drift checks need only the
runner and a browser; rebuilding GIF, MP4 or documentation also needs the
editor, which carries the render pipeline.

```sh
npm install --save-dev @cutscene/runner @playwright/test
npx playwright install chromium
npx cutscene-regenerate --config demo.yml --dry-run

npm install --save-dev @cutscene/editor   # only if you rebuild outputs
```

Node 22.18 or newer. In GitHub Actions, the packaged action does the same thing
and comments the report on the pull request:

```yaml
- uses: macayu17/Cutscene@main
  with:
    config: demo.yml
    dry-run: true
```

The step fails when a demo drifts, exactly as a failing test does. Add
`heal: true` to promote the locator that actually resolved instead.

The runner validates `demo.yml`, optionally runs a seed command, and
replays the stored trace in Chromium. A normal run records a fresh WebM and
trace, compares the semantic actions, and rebuilds every declared output. Add
`--dry-run` to check locator drift without recording or rendering.

```yaml
version: 1
demos:
  - id: todo-flow
    trace: .cutscene/todo-flow.trace.jsonl
    baseUrl: ${{ env.PREVIEW_URL }}
    seed: pnpm run seed:demo
    inputs:
      step_0001: ${{ env.DEMO_TODO }}
    watch:
      - packages/app/src/routes/reports/**
    staleAfterCommits: 10
    outputs:
      - type: gif
        path: docs/assets/todo-flow.gif
        width: 800
```

An environment reference must occupy the whole value. Input overrides are
keyed by the recorded `stepId` and stay in memory; they are not written to the
trace or reports.

```powershell
$env:PREVIEW_URL='http://127.0.0.1:4173'
$env:DEMO_TODO='Recorded demo value'
pnpm --filter @cutscene/runner regenerate -- --config demo.yml --dry-run
```

Remove `--dry-run` to rebuild the declared outputs. Add `--demo todo-flow` to
run one configured demo. Fresh bundles are written under
`.cutscene/runs/<demo-id>/`; drift, trace-diff, and staleness reports are under
`.cutscene/reports/<demo-id>/`. `watch` and `staleAfterCommits` are optional,
but must be provided together.

- Exit `0`: every planned step matched; a normal run also wrote the fresh
  bundle, reports, and all declared outputs.
- Exit `1`: at least one step drifted, became orphaned, or was not evaluated;
  a normal run leaves the declared outputs unchanged.
- Exit `2`: configuration, seed, capture, report, or rendering failed.

### Repair recoverable drift

A step drifts when its strongest locator stops resolving but a lower-ranked one
still finds the element. Add `--heal` to promote the locator that actually
resolved and write the trace back, so the next run matches instead of drifting
again.

```powershell
pnpm --filter @cutscene/runner regenerate -- --config demo.yml --dry-run --heal
```

Healing never invents a locator. An orphaned step has nothing left to promote,
so it stays orphaned and the run still exits `1`. `--heal` repairs what is
recoverable and refuses to hide what is not.

Version 1 records and replays only `Enter`. Printable 

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 222 recognized source files, 1176 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 247)

```
.dockerignore
.github/ISSUE_TEMPLATE/bug_report.yml
.github/ISSUE_TEMPLATE/config.yml
.github/pull_request_template.md
.github/workflows/ci.yml
.github/workflows/release.yml
.gitignore
action.yml
AGENTS.md
CHANGELOG.md
CONTRIBUTING.md
Dockerfile
docs/deploy.md
docs/phase-1-design.md
docs/phase-1-evidence.md
docs/store-listing.md
docs/superpowers/plans/2026-07-16-comment-reanchoring.md
docs/superpowers/plans/2026-07-16-editor-share-flow.md
docs/superpowers/plans/2026-07-16-phase-7-local-regeneration.md
docs/superpowers/plans/2026-07-16-shared-review.md
docs/superpowers/plans/2026-07-16-team-collaboration.md
docs/superpowers/plans/2026-07-16-yjs-timeline.md
docs/superpowers/plans/2026-07-17-enter-key-replay.md
docs/superpowers/plans/2026-07-17-phase7-local-regeneration-completion.md
docs/superpowers/plans/2026-07-18-extension-popup-polish.md
docs/superpowers/plans/2026-07-18-phase8-interactive-clickthrough.md
docs/superpowers/plans/2026-07-20-semantic-demo-kit.md
docs/superpowers/plans/2026-07-20-submission-demo.md
docs/superpowers/plans/2026-07-22-ai-voiceover.md
docs/superpowers/specs/2026-07-14-element-callouts-design.md
docs/superpowers/specs/2026-07-14-readme-design.md
docs/superpowers/specs/2026-07-15-captions-design.md
docs/superpowers/specs/2026-07-15-cursor-treatment-design.md
docs/superpowers/specs/2026-07-15-local-brand-presets-design.md
docs/superpowers/specs/2026-07-15-playwright-skeleton-design.md
docs/superpowers/specs/2026-07-15-readme-gif-variants-design.md
docs/superpowers/specs/2026-07-15-selector-redaction-design.md
docs/superpowers/specs/2026-07-15-step-docs-design.md
docs/superpowers/specs/2026-07-15-vertical-export-design.md
docs/superpowers/specs/2026-07-16-comment-reanchoring-design.md
docs/superpowers/specs/2026-07-16-phase-7-local-regeneration-design.md
docs/superpowers/specs/2026-07-16-share-link-wedge-design.md
docs/superpowers/specs/2026-07-16-shared-review-design.md
docs/superpowers/specs/2026-07-16-yjs-timeline-design.md
docs/superpowers/specs/2026-07-17-enter-key-replay-design.md
docs/superpowers/specs/2026-07-17-phase7-local-regeneration-completion-design.md
docs/superpowers/specs/2026-07-18-extension-popup-polish-design.md
docs/superpowers/specs/2026-07-18-phase8-interactive-clickthrough-design.md
docs/superpowers/specs/2026-07-20-semantic-demo-kit-design.md
docs/superpowers/specs/2026-07-20-submission-demo-design.md
docs/superpowers/specs/2026-07-22-ai-voiceover-design.md
docs/superpowers/specs/2026-07-22-screen-capture-fallback-design.md
fly.toml
LICENSE
package.json
packages/editor/automation.html
packages/editor/e2e/interactive.spec.ts
packages/editor/index.html
packages/editor/LICENSE.md
packages/editor/package.json
packages/editor/playwright.config.ts
packages/editor/public/licenses/GPL-2.0.txt
packages/editor/public/licenses/NOTICE.txt
packages/editor/src/App.tsx
packages/editor/src/automation.test.ts
packages/editor/src/automation.ts
packages/editor/src/brand-kit-sync.test.ts
packages/editor/src/brand-kit-sync.ts
packages/editor/src/brand-panel.tsx
packages/editor/src/brand-render.ts
packages/editor/src/brand.test.ts
packages/editor/src/brand.ts
packages/editor/src/bundle.test.ts
packages/editor/src/bundle.ts
packages/editor/src/callout-render.test.ts
packages/editor/src/callout-render.ts
packages/editor/src/callouts-panel.tsx
packages/editor/src/callouts.test.ts
packages/editor/src/callouts.ts
packages/editor/src/camera.test.ts
packages/editor/src/camera.ts
packages/editor/src/cursor-export.test.ts
packages/editor/src/cursor-export.ts
packages/editor/src/cursor-panel.tsx
packages/editor/src/cursor-render.ts
packages/editor/src/cursor.test.ts
packages/editor/src/cursor.ts
packages/editor/src/demo-kit.test.ts
packages/editor/src/demo-kit.ts
packages/editor/src/docs-export.test.ts
packages/editor/src/docs-export.ts
packages/editor/src/export.test.ts
packages/editor/src/export.ts
packages/editor/src/gif-export.ts
packages/editor/src/interactive-player.ts
packages/editor/src/interactive.test.ts
packages/editor/src/interactive.ts
packages/editor/src/main.tsx
packages/editor/src/recordings.test.ts
packages/editor/src/recordings.ts
packages/editor/src/redactions-panel.tsx
packages/editor/src/redactions.test.ts
packages/editor/src/redactions.ts
packages/editor/src/segments-panel.tsx
packages/editor/src/segments.test.ts
packages/editor/src/segments.ts
packages/editor/src/share.test.ts
packages/editor/src/share.ts
packages/editor/src/store-collaboration.test.ts
packages/editor/src/store.ts
packages/editor/src/style.css
packages/editor/src/timeline-document.test.ts
packages/editor/src/timeline-document.ts
packages/editor/src/timeline-sync.test.ts
packages/editor/src/timeline-sync.ts
packages/editor/src/timeline.test.ts
packages/editor/src/timeline.tsx
packages/editor/src/tokens.css
packages/editor/src/video.tsx
packages/editor/src/zip.test.ts
[127 more files omitted for size]
```

### Dependencies

- package.json: typescript@^5.9.3, vitest@^4.1.10
- packages/editor/package.json: @cutscene/trace@workspace:*, @ffmpeg/core@0.12.10, @ffmpeg/ffmpeg@^0.12.15, @ffmpeg/util@^0.12.2, @fontsource/ibm-plex-mono@^5.3.0, @fontsource/ibm-plex-sans@^5.3.0, @playwright/test@^1.61.1, @types/react@^19.2.2, @types/react-dom@^19.2.2, react@^19.2.0, react-dom@^19.2.0, vite@^7.3.6, yjs@^13.6.31, zustand@^5.0.8
- packages/extension/package.json: @crxjs/vite-plugin@^2.7.1, @cutscene/editor@workspace:*, @cutscene/trace@workspace:*, @playwright/test@^1.61.1, @types/chrome@^0.1.43, @types/node@^24.13.3, vite@^7.3.6, yjs@^13.6.31
- packages/runner/package.json: @cutscene/editor@0.2.0, @cutscene/editor@workspace:*, @cutscene/trace@workspace:*, @playwright/test@^1.61.1, @playwright/test@>=1.61.1, @types/node@^24.13.3, yaml@^2.8.1
- packages/server/package.json: @cutscene/trace@workspace:*, @types/node@^22.10.0, esbuild@^0.28.1, yjs@^13.6.31

### Recent commits (newest first)

- docs: correct the stated test count to the current 337
- feat(server): make the share server an operable service
- feat: record a screen or window, and degrade honestly without a trace
- docs: record CI as verified green on the first push
- ci: give the test lane the browser and editor build its suite needs
- fix(server): count timeline snapshots against the store cap; correct the site licence claim
- fix: close the pre-publish gaps, and a race the harness kept retrying away
- fix: repair what the pre-publish review found
- docs: define the screen capture fallback
- feat(server): make the share server safe to expose
- docs: prepare the Chrome Web Store submission
- docs: record the published packages in the changelog
- feat(runner): make the regeneration story installable
- feat(extension): keep an interrupted recording instead of losing it
- feat(extension): meet the listing requirements for a public install
- feat(extension): open recordings in a bundled editor page
- docs: record the quality report and drift healing evidence
- docs: record available voice fallback
- docs: plan local AI voiceover
- docs: define local AI voiceover

## Key source files (fetched from GitHub, selected and truncated for size)

### SECURITY.md

```markdown
# Security

## Reporting

Report a vulnerability through GitHub's private advisory form:
<https://github.com/macayu17/Cutscene/security/advisories/new>. Do not open a
public issue.

Expect an acknowledgement within a week. This is a small project with one
maintainer, and that is stated so you can plan around it rather than guess.

## What is in scope

- The extension: anything that captures more than the tab being recorded, that
  records while no recording is active, or that writes an unmasked input value
  into `trace.jsonl`.
- The editor: anything that sends a recording anywhere the user did not ask it
  to go.
- The share server: path traversal into the bundle store, bypassing the review
  token checks, or reaching another recording's data.
- The runner: anything that executes content from a recording as code.

## What is not

- The share server serving public, unguessable links to anyone holding the URL.
  That is the documented design; it has no accounts and no private links.
- Recordings being readable by anyone with access to the machine that made them.
  They are stored locally by design.
- Any behaviour that requires an attacker to already control the extension's own
  origin or the user's browser profile.

## Handling recordings

A `trace.jsonl` masks input values at capture, before it is written. It does not
mask URLs, routes, or accessible names, which can carry a customer name or an
account id. Treat a recording of a real system as sensitive when you attach one
to a report.

```

### CONTRIBUTING.md

```markdown
# Contributing

## Before you write code

Read [`AGENTS.md`](AGENTS.md). It is written for coding agents but the rules are
the project's rules: phases are gated, `STATUS.md` records what is authorised,
and the trace format in [`PRD.md`](PRD.md) is captured in full even where nothing
reads it yet.

The design brief in `AGENTS.md` section 6 is binding for anything visual. Amber
is spent only on things the machine semantically understands.

## Running it

```sh
pnpm install
pnpm build
```

Load `packages/extension/dist` as an unpacked extension in Chrome, record a tab,
and the editor opens with the recording loaded.

## Verification

Everything must pass before a change lands:

```sh
pnpm test
pnpm typecheck
pnpm build
pnpm e2e
```

`pnpm e2e` includes the extension capture suite, which drives a headed Chrome
against a third-party site. CI runs everything except that suite, so run it
locally before proposing a change to capture.

## What a good change looks like

- A number, not an adjective. If you claim a behaviour improved, say by how much
  and how it was measured. `STATUS.md` is the record of those numbers.
- One runnable check per non-trivial behaviour. Vitest for units, Playwright for
  anything that needs a browser.
- No new runtime dependency without a justification in the pull request. Every
  dependency in the extension costs bundle size and a store review.
- TypeScript strict, no `any`, no default exports outside React components.
- Comments explain why, never what.

## Reporting a problem

Open an issue with the page you recorded, what you expected, and what happened.
A `trace.jsonl` helps enormously — check it for anything private first; input
values are masked at capture, but URLs and accessible names are not.

Security problems go to [`SECURITY.md`](SECURITY.md) instead, never to an issue.

```

### package.json

```
{
  "name": "cutscene",
  "private": true,
  "packageManager": "pnpm@11.6.0",
  "scripts": {
    "build": "pnpm -r build",
    "e2e": "pnpm --filter @cutscene/editor e2e && pnpm --filter @cutscene/runner e2e && pnpm --filter @cutscene/extension e2e",
    "test": "pnpm -r test",
    "typecheck": "pnpm -r typecheck"
  },
  "devDependencies": {
    "typescript": "^5.9.3",
    "vitest": "^4.1.10"
  }
}

```

### Dockerfile

```
# The Cutscene share server. The build bundles @cutscene/trace and yjs into one file,
# so the runtime image carries only that file and node built-ins — no node_modules, no
# workspace, and none of the TypeScript type-stripping the source-run needs.
FROM node:22-slim AS build
RUN corepack enable && corepack prepare pnpm@11.6.0 --activate
WORKDIR /repo
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm --filter @cutscene/server build

FROM node:22-slim AS runtime
WORKDIR /app
COPY --from=build /repo/packages/server/dist/index.js ./index.js
# Recordings live on a mounted volume so retention and the store survive restarts.
ENV CUTSCENE_DATA=/data
ENV PORT=8080
EXPOSE 8080
VOLUME ["/data"]
CMD ["node", "index.js"]

```

### packages/server/package.json

```
{
  "name": "@cutscene/server",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node src/index.ts",
    "build": "esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js",
    "test": "vitest run",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@cutscene/trace": "workspace:*",
    "yjs": "^13.6.31"
  },
  "devDependencies": {
    "@types/node": "^22.10.0",
    "esbuild": "^0.28.1"
  }
}

```

### packages/extension/package.json

```
{
  "name": "@cutscene/extension",
  "version": "0.2.0",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "vite build",
    "e2e": "playwright test",
    "test": "vitest run src --passWithNoTests",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@cutscene/editor": "workspace:*",
    "@cutscene/trace": "workspace:*"
  },
  "devDependencies": {
    "@crxjs/vite-plugin": "^2.7.1",
    "@playwright/test": "^1.61.1",
    "@types/chrome": "^0.1.43",
    "@types/node": "^24.13.3",
    "vite": "^7.3.6",
    "yjs": "^13.6.31"
  }
}

```

### packages/trace/package.json

```
{
  "name": "@cutscene/trace",
  "version": "0.2.0",
  "description": "Trace schema, ranked locators, clock mapping, drift detection and healing for Cutscene recordings.",
  "keywords": ["cutscene", "screen-recording", "dom", "locators", "playwright"],
  "license": "MIT",
  "author": "Ayush Kumar",
  "repository": { "type": "git", "url": "git+https://github.com/macayu17/Cutscene.git", "directory": "packages/trace" },
  "homepage": "https://github.com/macayu17/Cutscene#readme",
  "bugs": "https://github.com/macayu17/Cutscene/issues",
  "type": "module",
  "exports": "./src/index.ts",
  "files": ["dist"],
  "engines": { "node": ">=22.18" },
  "publishConfig": {
    "exports": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }
  },
  "scripts": {
    "build": "tsc -p tsconfig.build.json",
    "test": "vitest run",
    "typecheck": "tsc --noEmit"
  }
}

```

### packages/runner/package.json

```
{
  "name": "@cutscene/runner",
  "version": "0.2.0",
  "description": "Replay a Cutscene recording against a current build: locator drift, trace diff, and regenerated GIF, MP4 and documentation.",
  "keywords": ["cutscene", "screen-recording", "documentation", "playwright", "ci"],
  "license": "MIT",
  "author": "Ayush Kumar",
  "repository": { "type": "git", "url": "git+https://github.com/macayu17/Cutscene.git", "directory": "packages/runner" },
  "homepage": "https://github.com/macayu17/Cutscene#readme",
  "bugs": "https://github.com/macayu17/Cutscene/issues",
  "type": "module",
  "bin": {
    "cutscene-regenerate": "src/cli.ts"
  },
  "files": ["dist"],
  "engines": { "node": ">=22.18" },
  "publishConfig": {
    "bin": { "cutscene-regenerate": "dist/cli.js" }
  },
  "scripts": {
    "build": "tsc -p tsconfig.build.json",
    "regenerate": "node src/cli.ts",
    "test": "vitest run src",
    "typecheck": "tsc --noEmit",
    "e2e": "playwright test"
  },
  "dependencies": {
    "@cutscene/trace": "workspace:*",
    "yaml": "^2.8.1"
  },
  "peerDependencies": {
    "@cutscene/editor": "0.2.0",
    "@playwright/test": ">=1.61.1"
  },
  "peerDependenciesMeta": {
    "@cutscene/editor": { "optional": true }
  },
  "devDependencies": {
    "@cutscene/editor": "workspace:*",
    "@playwright/test": "^1.61.1",
    "@types/node": "^24.13.3"
  }
}

```

### packages/editor/package.json

```
{
  "name": "@cutscene/editor",
  "version": "0.2.0",
  "description": "The Cutscene editor and its render pipeline. Published so the runner can regenerate GIF, MP4 and documentation outputs.",
  "license": "SEE LICENSE IN LICENSE.md",
  "author": "Ayush Kumar",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/macayu17/Cutscene.git",
    "directory": "packages/editor"
  },
  "homepage": "https://github.com/macayu17/Cutscene#readme",
  "bugs": "https://github.com/macayu17/Cutscene/issues",
  "type": "module",
  "exports": {
    "./page": "./src/main.tsx",
    "./recordings": "./src/recordings.ts",
    "./package.json": "./package.json"
  },
  "files": [
    "dist"
  ],
  "publishConfig": {
    "exports": {
      "./package.json": "./package.json"
    }
  },
  "scripts": {
    "build": "vite build",
    "test": "vitest run src",
    "e2e": "playwright test",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@cutscene/trace": "workspace:*",
    "@ffmpeg/core": "0.12.10",
    "@ffmpeg/ffmpeg": "^0.12.15",
    "@ffmpeg/util": "^0.12.2",
    "@fontsource/ibm-plex-mono": "^5.3.0",
    "@fontsource/ibm-plex-sans": "^5.3.0",
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "yjs": "^13.6.31",
    "zustand": "^5.0.8"
  },
  "devDependencies": {
    "@playwright/test": "^1.61.1",
    "@types/react": "^19.2.2",
    "@types/react-dom": "^19.2.2",
    "vite": "^7.3.6"
  }
}

```

### site/app.js

```javascript
// Two jobs. Neither one gates content: if this file never runs, the page is
// complete, only the gutter readout stops tracking.

const gutterId = document.querySelector('.gutter-id');
const gutterT = document.querySelector('.gutter-t');
const scenes = [...document.querySelectorAll('[data-step]')];

if (gutterId && gutterT && scenes.length && 'IntersectionObserver' in window) {
  // Report the recorded step the current scene corresponds to.
  const observer = new IntersectionObserver((entries) => {
    const visible = entries
      .filter((entry) => entry.isIntersecting)
      .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
    if (!visible) return;
    gutterId.textContent = visible.target.dataset.step ?? '';
    gutterT.textContent = visible.target.dataset.t ?? '';
    // only count a scene once it crosses the middle band, so the readout
    // matches the scene actually filling the viewport
  }, { rootMargin: '-40% 0px -40% 0px', threshold: 0 });
  for (const scene of scenes) observer.observe(scene);
}

// Stop decoding the hero loop once it is off screen.
const hero = document.querySelector('.hero-vid');
if (hero && 'IntersectionObserver' in window) {
  new IntersectionObserver((entries) => {
    for (const entry of entries) {
      if (entry.isIntersecting) hero.play().catch(() => {});
      else hero.pause();
    }
  }, { threshold: 0.05 }).observe(hero);
}

```

[216 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]