Record and stitch
Planning (no I/O):
ts
const DEMO_WALK_ROOT = 'test-results/e2e-demo'
type DemoWalkKind = 'stills' | 'video'
function isDemoVideo(env: Record<string, string | undefined>): boolean
function isDemoScreenshotCapture(
env?: Record<string, string | undefined>,
): boolean
function demoScreenshotAnimations(
env: Record<string, string | undefined>,
): 'disabled' | 'allow'
function isDemoClean(env?: Record<string, string | undefined>): boolean
function demoWalkKind(env: Record<string, string | undefined>): DemoWalkKind
function demoWalkKindRoot(env: Record<string, string | undefined>): string
function demoVideoStamp(nowMs: number): string
function demoVideoDir(env: Record<string, string | undefined>, stamp: string): string
function demoChatArtifactPath(
env: Record<string, string | undefined>,
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<void>
function ensureDemoVideoDir(dir: string): Promise<void>
function savePersonaVideo(video: Video | null, persona: string, dir: string): Promise<string | null>
function stitchDemoWalk(meta: DemoRecordMeta): Promise<string | null>
function writeDemoRecordManifest(meta: DemoRecordMeta, files: Record<string, string>): Promise<string>
function writeDemoLatestPointer(
dir: string,
env?: Record<string, string | undefined>,
): Promise<void>
function copyWalkForChat(walk: string, env?: NodeJS.ProcessEnv, stamp?: string): Promise<string | null>
function writeWalkTranscript(meta: DemoRecordMeta): Promise<string | null>
function copyTranscriptForChat(transcript: string, env?: NodeJS.ProcessEnv, stamp?: string): Promise<string | null>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/<stamp>/ or e2e-demo/video/<stamp>/ 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.