This checklist covers the real desktop capture-to-export path: the parts that unit, browser, and Playwright tests cannot exercise, including real screen capture, a physical webcam, the system tray, the native compositor, and export. Run it before promoting a release candidate and after any change to native capture, preview, or export.
"Manual" is about the input, not the operator. These checks need real OS mouse and keyboard events, not a human hand — so an agent with the computer-use MCP runs them, on demand, and a request for one section after a targeted change is as much a run as the whole file before a promote. Availability check and the rule on partial runs: AGENTS.md.
Sections marked v1.8.0 cover what that release changed: chat-driven editing through the agent tool set, clip-anchored modifiers, local transcription, the macOS Metal compositor, and the new effect controls.
Sections marked post-1.10.0 cover what has landed on main since the v1.10.0 tag: the AI camera background, the caption anchor model, pixel-resolution crop, editor window bounds, update settings, the Windows recording encoder and AAC changes, imported audio and voice-over recording, and transcript word editing with word insertion. Run the whole file for a release candidate; the marked sections are the ones with no prior release to fall back on.
Sections and checks marked v2.0.0 cover what v2 changed: the editor's Record mode and automatic zooms after a take, Apple's source picker and the permissions window on macOS, the export dialog's settings panel, and the webcam's capture resolution and frame rate. The v2 editor also reshaped controls older sections name (the top bar, the transport, the inspector); those checks were rewritten in place rather than marked.
-
Drive the real Electron app with computer-use — real OS mouse and keyboard events. For a release candidate, launch the CI-built artifact (see Running the pass on a CI-built artifact below); for a change under development, a dev build started with
npm run dev."Manual" here usually means an agent holding the mouse, so the tempting shortcut is not a browser shim: it is driving the real app through CDP instead. Do not. Playwright's
.click(),javascript_tool-dispatched pointer events and anything else synthesised into the renderer arrive below the OS hit-test. On Windows and macOS the HUD is input-transparent until a real cursor move lifts it, so an injected click fires the DOM handler and comes back green while the path a user actually takes was never exercised at all.That trap is specific to the HUD and the countdown overlay — they are the only click-through windows; the editor is an ordinary one, and an injected click there does reach the handler a user would reach. The reason not to inject in the editor either is the first line of this file: this checklist covers what unit, browser and Playwright tests cannot. Drive it the way those tests already drive it and you have re-run the coverage you had, then written "passed" beside the parts nothing checked.
-
The app is single-instance per
userDatapath. If a leftover Electron/OpenScreen process still holds the lock, stop that process before relaunching; a second launch can exit successfully without opening a window. The lock is held by the OS and is released when the process dies, so there is nothing to delete on disk. -
For a dev build from a worktree, link or junction
node_modulesto the main checkout and provide the prebuilt native capture binaries for the platform before starting the dev build. Date those binaries against the change you came to test. Nothing rebuilds them, so an older helper runs the old code path in silence: the recording succeeds and the thing you were checking for is simply absent. See AGENTS.md for how to check one and how to rebuild it; when you cannot, test the CI-built artifact, because a dev build cannot answer a native question.A junctioned
node_modulesis only as current as the checkout it points at. Diff the twopackage.jsondependency sets before trusting it: a dependency added after that checkout's HEAD is simply absent, and the failure does not name it —vitelogs oneRollup failed to resolve importline among the build noise, the main process starts anyway, and the IPC handlers in the module that failed to bundle are never registered. Symptom seen:No handler registered for 'get-app-info'and no visible window, from a missingelectron-updater. Installing the one missing package into the shared tree reconciles that tree against the older lockfile, so re-check the packages you need afterwards rather than assuming the install was additive. -
Grant computer-use access to the process name that actually owns the window:
electron.exeorElectron.appfor a dev build, and the installed nameOpenscreenfor a packaged build or CI artifact. Do not grant access only to the installed app name when testing a dev build — it resolves to the installed executable and reports success while the dev window stays masked. This is step 4 and not step 1 for a reason: a dev build is not an installed app, so the resolver cannot find it until it is running and owns a window, and one unresolvable name voids the whole request.Ask for everything in ONE call — after the launches above, before the first check.
request_accesstakes a list, and once a grant is in place the rest of the pass runs without a single further prompt: a full capture-to-export run is dozens of clicks and none of them ask again. So the only thing keeping a human at the keyboard is how many dialogs you raise and when. Raise one, before the first check, and the operator can walk away for the rest of the run; discover a fourth app you need an hour in and they cannot. That is also why this cannot move earlier — the resolver needs the app running, and one unresolvable name voids the batch. Beyond the app under test, ask for:- the desktop shell — the tray icon and the native save dialogs live there, and the tray is the only reliable way back to the HUD;
- the OS settings app — needed to change display scaling, which is how DPI checks are run (see AGENTS.md; "the machine is at 100%" is not a reason to skip them), and on macOS to grant permissions;
- on macOS 15.2+, whatever draws Apple's source picker, and on macOS the permission prompts; on Linux, the portal's source picker. None of them is an OpenScreen window, and which process owns each is to confirm on the first run: the screenshot note names a masked window's process, so ask for that name, then write it here.
Name them the way the Start menu does, in the system's own language. The resolver matches installed-app display names, not executables: on a French Windows the shell is
Explorateur de fichiersandexplorer.exefails outright —notInstalled, with a nonsense suggestion attached — which then voids every other name in the same call. On an English install it isFile Explorer. When unsure, ask rather than guess; the tool lists the installed names it knows.There is no way to pre-approve any of this in config: the request has to be answered live. That is upstream (claude-code#46907, closed stale), and
bypassPermissionsdoes not cover it either (#43172). Batching is the whole mitigation. -
Read AGENTS.md for the computer-use mechanics, screenshot permissions, tray interaction, and cleanup procedure. Read one check, perform it, observe the result, then continue; close each modal or popover with
Escbefore the next check. -
The recording HUD is protected from capture by default and is invisible in screenshots. For this session only, launch with
OPENSCREEN_DISABLE_CONTENT_PROTECTION=1; this is the environment variable checked beforesetContentProtection(true). A packaged build gets it only when its executable is started from the shell where it is set. macOS 26 and later need no flag, and Linux has no protection to lift. Unset it before making any recording whose HUD must not appear in the video. -
A preview screenshot is downscaled. Settle every pixel-level question by exporting a frame and measuring the exported frame, not by judging fine edges, corners, shadows, or alignment from the preview screenshot.
-
Keep the first real recording or imported project available for the editor sections. Log crashes, hangs, data loss, security issues, and reproducible visual failures as soon as they occur.
-
Several v1.8.0 sections need a configured AI provider (chat editing, caption translation) or a built native compositor addon (preview, export). A dev build from a worktree needs the compositor addon installed for its platform, not only the capture binaries. When a prerequisite is missing, record the section as skipped with the reason; do not mark it passed.
The AI camera background needs two more things that nothing else does: the ONNX Runtime shared library staged beside the addon (
npm run fetch:onnxruntime, which a plainnpm run devdoes not run) and a recording that actually has a webcam track. Without the library the control is correctly absent — so an absent control is only a defect once you have confirmed the library is there. Check both directions before writing a verdict. -
Prefer a project with at least two clips from the same asset for the modifier sections. A single-clip project cannot exercise anchoring, reorder, or cross-boundary splitting at all, which is where the v1.8.0 timeline model changed.
A release candidate is tested on what CI built, not on a dev build. The artifact carries one matched native payload built from the commit under test, so the stale-binary traps of a dev build do not apply to it. It comes from .github/workflows/build.yml dispatched by hand on the branch under test with release_tag left empty, which builds every platform and publishes nothing: gh workflow run build.yml -R getopenscreen/openscreen --ref <branch> -f arch=both (arch picks the macOS architectures).
Get the artifact. The run's page lists the artifacts at the bottom, under Artifacts, for anyone logged in to GitHub; each one downloads as a zip. From a terminal, find the run, then download an artifact by name. Artifacts expire after 30 days.
gh run list -R getopenscreen/openscreen --workflow build.yml --branch <branch>
gh run download <run-id> -R getopenscreen/openscreen -n openscreen-windows| Artifact | What it holds |
|---|---|
openscreen-windows |
Openscreen.Setup.<version>.exe, the NSIS installer, with its .blockmap and latest.yml |
openscreen-windows-store |
Openscreen.Setup.<version>.appx, the Store package |
openscreen-mac-arm64, openscreen-mac-x64 |
Openscreen-macOS-Apple-Silicon-<version>.dmg or Openscreen-macOS-Intel-<version>.dmg, with the update ZIP and its JSON |
openscreen-linux |
Openscreen-Linux-<version> as .AppImage, .deb, .rpm and .pacman, with latest-linux.yml |
<version> comes from package.json, not from the branch, so it does not tell two candidates apart. The results row carries the run id, and the version About OpenScreen reports.
Windows.
-
Run the Setup
.exe. It is not code-signed, so SmartScreen's "Windows protected your PC" is expected; going past it is the machine owner's decision, not the agent's. The installer asks whether to install for the current user (%LOCALAPPDATA%\Programs\Openscreen\) or for all users (C:\Program Files\Openscreen\). -
If the installer started the app when it finished, quit that copy (Quit OpenScreen on the HUD, or tray → Quit). It runs without the flag below, and its single-instance lock makes the next launch exit silently.
-
Launch from PowerShell, so the flag reaches the process (adjust the path for an all-users install):
$env:OPENSCREEN_DISABLE_CONTENT_PROTECTION = "1" & "$env:LOCALAPPDATA\Programs\Openscreen\Openscreen.exe"
-
Grant
Openscreen, notelectron.exe. -
openscreen-windows-storeis unsigned as well, and Windows does not install an unsigned package. From a source checkout,powershell -File scripts/verify-appx-native.ps1 -Appx <file> -KeepRegisteredregisters it for a pass by hand, then grant the name its Start-menu entry shows. Loose registration needs Developer Mode, a machine-wide setting its owner turns on or not.
macOS.
- Take the artifact that matches the Mac:
arm64for Apple Silicon,x64for Intel. - The DMG is signed with the Developer ID, notarized and stapled only when the run has all six Apple secrets (
MAC_CERTIFICATE_P12,MAC_CERTIFICATE_PASSWORD,MAC_CSC_NAME,APPLE_ID,APPLE_TEAM_ID,APPLE_APP_SPECIFIC_PASSWORD). Without them the.appis ad-hoc signed and the DMG is neither signed nor notarized; the job's Notarize DMG step then shows as skipped. On the Mac,spctl -a -vv -t install <dmg>answersacceptedandsource=Notarized Developer IDfor a notarized DMG. - Gatekeeper refuses an unnotarized DMG that a browser downloaded. Clearing its quarantine attribute is a security override the Mac's owner decides on, not the agent. macOS ties permission grants to the code signature, and an ad-hoc signature changes with every build, so grants made to one candidate may not carry to the next.
- Open the DMG and drag Openscreen into Applications.
- On macOS 26 and later no flag is needed: launch it as a user would. On macOS 13 to 15 the flag takes a launch from Terminal,
OPENSCREEN_DISABLE_CONTENT_PROTECTION=1 /Applications/Openscreen.app/Contents/MacOS/Openscreen. Whether macOS then files the permission prompts under Terminal rather than OpenScreen is to confirm on the first run. - Grant
Openscreen, plus the picker and prompt processes from step 4.
Linux.
- AppImage:
chmod +x Openscreen-Linux-<version>.AppImage, then run it; add--no-sandboxif Chromium's sandbox cannot start. A package: install it with the distribution's tool (sudo apt install ./Openscreen-Linux-<version>.deb,sudo dnf install ./Openscreen-Linux-<version>.rpm,sudo pacman -U Openscreen-Linux-<version>.pacman), then runopenscreen. - No flag is needed. Which name to grant is to confirm on the first run: a package installs a desktop entry, an AppImage has none until it is integrated.
Logs of a packaged build. OpenScreen writes no log file. The main process logs to its standard output, so a launch from a terminal shows it there, [content-protection] OFF for the HUD window included; whether the Windows executable prints into the PowerShell that started it is to confirm on the first run. Launched with OPENSCREEN_DIAGNOSTIC=1 as well, the app keeps the last 500 main-process lines, and tray → Save Diagnostics writes them to a JSON file of your choice, with the capture helper's recent output. The renderer's console calls are stripped from packaged builds. Recordings and their .cursor.json and .session.json sidecars are in the recordings folder of %APPDATA%\Openscreen, ~/Library/Application Support/Openscreen or ~/.config/Openscreen.
- Start the app and confirm one launch window, the HUD, appears without a startup crash (on macOS the permissions window may open beside it: see the macOS section).
- Confirm the launch window remains usable after the first device enumeration completes.
- Confirm the HUD is visible when content protection is disabled for the test session.
- Activate
[data-testid="launch-tray-layout-button"]and confirm the bar switches between horizontal and vertical. - Confirm the chosen bar layout remains coherent when the HUD grows to show recording controls.
- Activate
[data-testid="hud-drag-handle"], drag the HUD across most of the primary display, and confirm it follows the pointer without drift. - Release the drag and confirm the HUD stays at the dropped position instead of jumping.
- Activate the language button by its visible language code and confirm a menu of locale choices opens.
- Press
Escwith the language menu open and confirm it closes without changing the locale. - Activate the camera toggle
[data-testid="launch-webcam-button"]and the microphone toggle[data-testid="launch-microphone-button"](absent on macOS 13 and 14) and confirm each turns on with one click. On a Mac with no camera attached, confirm the webcam toggle stays off and reports that camera access is blocked or the camera is unavailable; recording remains available with the webcam off. - Open the gear
[data-testid="launch-device-settings-button"](Device settings) and confirm it lists the input devices with a level meter that moves when you speak, and the cameras with a live preview; pick another device, close with Close (the X), and confirm the toggle now records from it. - v2.0.0 — With a camera attached and no saved choice, open the gear and confirm a Camera quality row sits under the camera list and above the preview, with three radio choices,
camera-quality-1080p,camera-quality-1440pandcamera-quality-2160p, labelled 1080p, 1440p and 4K, and that 4K is the one checked. - v2.0.0 — Pick 1080p, quit and relaunch the app, reopen the gear, and confirm 1080p is still checked.
recording-settings.jsoninuserDatacarries"camQuality": "1080p". - v2.0.0 — Quit, hand-edit
camQualityin that file to"720p", relaunch, and confirm the gear shows 4K checked rather than a row with nothing checked. - v2.0.0 — With no camera attached, or the camera disabled in the OS, confirm the gear reports "No camera found" and shows no Camera quality row.
- Hover each HUD control with real mouse moves, on the horizontal bar and again on the vertical one, while idle and while recording. Confirm each shows exactly one tooltip that is readable and not clipped by the window (above a horizontal bar, beside a vertical one, the Record button included), that the system audio, microphone, camera, cursor and gear buttons still show theirs while recording, and that the cursor button is absent when capture falls back to the browser (macOS or Linux without their native helper). The HUD is click-through, so Playwright cannot do this: see tooltips.md.
- Activate Hide recording bar and confirm the HUD hides without quitting the app.
- Refocus the app from its system-tray icon and confirm the HUD returns to the foreground.
- Activate Quit OpenScreen while idle and confirm the app exits cleanly, tray icon included.
- Relaunch the app after closing it and confirm the single-instance behavior does not leave a duplicate HUD.
The in-app source selector below is the Windows one, and the macOS one before 15.2. macOS 15.2+ picks in Apple's picker (see the macOS section), and Linux in the portal's, after record is pressed (see the Linux section).
- Activate
[data-testid="launch-source-selector-button"]and confirm the source selector opens on its Screens and Windows tabs. - Select a screen or application card with
data-testid="source-selector-card", activate[data-testid="source-selector-share-button"](Select), and confirm the selector closes with the source name on the HUD. - With no source picked, confirm
[data-testid="launch-record-button"]is enabled with the tooltip "Choose a screen or window to record", that activating it opens the source selector, and that picking a source starts the recording without a second click. - Confirm recording starts with a red stop state and an increasing elapsed timer, beside the pause, restart and cancel controls.
- Confirm the configured system-audio, microphone, webcam, and cursor states remain visible, and locked, while recording.
- Activate the recording control's pause action and confirm the timer stops advancing, then resume and confirm it advances again.
- Activate the restart action while recording and confirm the current recording is discarded and a fresh recording begins.
- Activate the cancel action while recording and confirm recording ends without opening an editor for the canceled take.
- Confirm stopping opens the editor with the recorded screen asset loaded.
- On Windows, stop once with system audio, microphone, webcam, and cursor all disabled and confirm the editor opens within a few seconds.
- Record once with microphone only and confirm the resulting playback contains audible microphone audio.
- Record once with system audio only and confirm the resulting playback contains audible system audio.
- Record with microphone and system audio enabled and confirm both sources are audible and reasonably balanced.
Record mode is the editor's pre-flight panel for the HUD: it edits the same settings, and Start recording hands them over. The HUD is closed while the editor is open, so every check here that ends in a take goes through that hand-over.
- On a first run, with no saved project (otherwise the editor reopens the most recent one), open the editor and confirm the empty state leads with Record your screen, followed by Import a video and an Open project link; activate Record your screen and confirm it opens the Record tab.
- Confirm Record mode lists Source, System audio, Microphone (absent on macOS 13 and 14), Camera, Editable cursor, Auto-zoom after recording (only while Editable cursor is On), and Hide desktop icons (macOS and Windows only). Editable cursor and Auto-zoom are both absent when capture falls back to the browser (macOS or Linux without their native helper), as the HUD's cursor button is.
- Turn Camera on and confirm a live preview; turn Microphone on and confirm its level meter moves when you speak; switch each device from its menu and confirm the preview or the meter follows.
- Pick a source from the Source row and confirm its name shows on the row and on the preview's badge. On Linux the row is plain text, "Your system will ask what to share", with nothing to pick.
- Activate Cancel and confirm the editor returns to Edit mode.
- Activate Start recording and confirm the editor window closes, the HUD comes back with the same source, audio, camera and cursor settings, and the take starts without another click (through the source picker first when no source was picked).
- With Editable cursor and Auto-zoom after recording On, click a few places during a take, stop, and confirm the editor opens with zoom regions already on the timeline at those clicks.
- Turn Auto-zoom after recording Off, record again, and confirm the new take opens with no zoom region; restart the app and confirm the row is still Off.
- Turn Editable cursor Off and confirm the Auto-zoom after recording row disappears; confirm a take recorded that way opens without automatic zooms.
- On Windows, turn Hide desktop icons On, record a screen, and confirm the recording shows the wallpaper where the icons were, and that the icons are back when the take stops. On macOS, confirm the icons are absent from the recording while they stay on the desktop.
The camera is captured at its own resolution and frame rate, and the resolution is the HUD's Camera quality setting (1080p, 1440p, 4K). Before that, nothing asked the camera for a size, so a UVC camera answered with the first format it lists, often 640x480, and the editor upscaled it. No unit test reaches a physical camera, so every check here is read off a real take. They need a physical camera, and ffprobe and ffmpeg from any install.
Where the take is. The webcam is its own file beside the screen recording: recording-<id>-webcam.mp4 from the Windows helper, recording-<id>-webcam.webm from the browser recorder (macOS, Linux, and Windows when the helper is missing). Both are in the recordings folder of userData, which the RECORDINGS_DIR: line the main process prints at startup names. Take the newest file, then:
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,avg_frame_rate,bit_rate:format=duration -of default=nw=1 <file>
A .webm is variable frame rate, so its avg_frame_rate says nothing about the camera. Count the frames instead, and divide nb_read_frames by duration:
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=width,height,nb_read_frames:format=duration -of default=nw=1 <file>
A Windows .mp4 is the opposite: written at a constant rate with the gaps padded by duplicate frames, so its frame count proves nothing either. On Media Foundation the camera's real rate is the delivered= count below.
What the Windows helper negotiated. Two places. The main process prints [native-wgc] capture started with a webcamFormat of width, height, fps and deviceName. The helper's own output is in tray → Save Diagnostics under helperOutput.windows, kept until the next take starts, so save it right after the take:
(Get-Content diag.json -Raw | ConvertFrom-Json).helperOutput.windows -split "`n" | Select-String "webcam"The lines that matter are INFO: Native webcam format <W>x<H>@<F> (uncompressed|compressed) on Media Foundation, INFO: DirectShow webcam connected subtype <S> <W>x<H>@<F> on DirectShow, INFO: Webcam capture loop ended: delivered=<N>, and WARNING: Native webcam started but no visible frame was available before screen capture. To exercise the negotiation without the HUD, npm run test:wgc-webcam:win drives the helper with the camera named in OPENSCREEN_WGC_TEST_WEBCAM_DEVICE_NAME and a target from OPENSCREEN_WGC_TEST_WEBCAM_WIDTH, _HEIGHT and _FPS, and prints the same lines. That is a helper-level run, not a pass of the checks below.
Camera classes. A check tagged with a class needs a camera of that class. With none at hand, log it as skipped: no such camera, never as passed. An untagged check needs any one physical camera. The results row names the camera and the classes it covers.
- [4K camera]: advertises a mode of 3840x2160 or more. List a camera's modes on Windows with
ffmpeg -f dshow -list_options true -i video="<camera name>", on Linux withv4l2-ctl --list-formats-ext -d /dev/video<N>, and on macOS by asking for an impossible size,ffmpeg -f avfoundation -video_size 1x1 -i "<index>"(-list_devices true -i ""gives the index), whose error lists the modes the device supports. - [sub-4K camera]: every mode is below 3840x2160. Most laptop cameras and plain 720p or 1080p webcams.
- [no-30-fps camera]: no mode at 30 fps or more at the size recorded, so 24 fps or less.
- [30 and 60 fps camera]: offers 30 and 60 fps at the same size.
- [DirectShow-only camera]: one Media Foundation does not enumerate, NVIDIA Broadcast for one. The helper output then carries
WARNING: Requested webcam device was not found by Media Foundation; trying DirectShow.
Resolution and bit rate
- [4K camera] Record about 20 s at each of 1080p, 1440p and 4K in turn, and probe each webcam file. The sizes are 1920x1080, 2560x1440 and 3840x2160, and
bit_rateclimbs with them. The encoder aims at 16, 24 and 40 Mbit/s; the BRIO measured in #875 gave about 14, 22 and 38. Abit_ratenear 4 Mbit/s at every choice is the old fixed rate. - On Windows, confirm the size and rate in
webcamFormatand in theINFO: Native webcam formatline equal the file'swidth,heightandavg_frame_rate. If the file is smaller than the choice on a camera that advertises the size, read the(uncompressed)or(compressed)tag on that line and the camera's mode list first: an uncompressed mode is preferred over a larger compressed one, whatever its size, so a smaller size can be the intended result. Write the modes and the outcome in the row instead of passing or failing it. - [sub-4K camera] Record at the default 4K. The take still has a camera and no "The camera could not be opened. Recording without it." toast appears. The file is the camera's best mode, for example 1920x1080 or 1280x720, and never 3840x2160 upscaled.
bit_rateis that size's tier (8 Mbit/s at 720p, 16 at 1080p), not 40. - [sub-4K camera] Record at 1440p on a camera whose best mode is 1080p and confirm the file is 1920x1080.
- Extract the last frame,
ffmpeg -sseof -1 -i <file> -frames:v 1 last.png, and view it at 100%. It is the live picture: colours match the room with no green or magenta cast, no rows shifted or sheared, and not black. That covers the NV12 conversion, and a camera that stopped mid-take. - With a 4K take, the default, play the project in the preview and scrub across the camera, then export MP4 at 1080p. The preview keeps up without stalls, the export completes, and the last exported frame shows the camera.
Frame rate
- On a camera with a 30 fps mode, record about 20 s. On Windows,
avg_frame_rateis30/1and, on Media Foundation,delivered=in the helper output is within 10% of 30 times the file'sduration. Run it on an idle machine: a 4K take under heavy competing CPU load can drop frames (174 of 240 seen with a lint pass running), so repeat once idle before logging a failure. On a.webm,nb_read_framesdivided bydurationis within 10% of 30. - [no-30-fps camera] Record about 20 s. On Media Foundation,
delivered=divided bydurationis about 24 for a 24 fps camera, andfpsinwebcamFormatand the file'savg_frame_ratematch it. If they say 30 whiledelivered=says 24, the file is padded with duplicates and nothing else shows it: log a failure, not a pass. On DirectShow,fpsinwebcamFormat, the rate in theINFO: DirectShow webcam connectedline andavg_frame_rateall equal the rate the graph settled on. On a.webm,nb_read_framesdivided bydurationis about 24. - [30 and 60 fps camera] Confirm the helper output reads
@30for the size recorded and the file is30/1, not 60.
During a take
- While a take runs, activate the gear and confirm the panel does not open. After the take, open it and confirm the choice is the one from before the take.
- Open the gear, then start the take by a route that does not dismiss the panel first. The panel closes on any pointer press outside it, on
Escand when the HUD loses focus, so the record button closes it. If every route you try does, logskipped: panel cannot be held open by real input;LaunchWindow.test.tsxcovers the guard. With the panel open in the take, activate another Camera quality choice. The checkmark does not move, the take goes on, and the camera does not stop: the last frame of the webcam file is live. The webcam file keeps the resolution chosen before the take, andcamQualityinrecording-settings.jsonis unchanged. - Choose 1080p in the gear, open the editor's Record mode, which has no quality choice of its own, turn Camera on, activate Start recording, and confirm the take's webcam file is 1080p.
No visible frame, on Windows with Media Foundation
The helper waits up to 3 s for the camera's first visible frame before starting the screen capture. The warning is written to the helper output only, with no toast.
- Cover the lens completely with a privacy shutter or opaque tape, since a fingertip lets light through. Record at least 5 s, stop, save diagnostics, and confirm
helperOutput.windowscarriesWARNING: Native webcam started but no visible frame was available before screen capture. The take is still saved, with a black camera. If a camera's own noise with the lens covered reads as a picture, note that in the row. - Repeat with the lens open in a dim room where the scene is still readable, and confirm the warning is absent. A camera's NV12 is studio range, where black is 16 and not 0, and an all-black frame used to read as a picture.
Backends
- [DirectShow-only camera] On Windows, record 4K and confirm the helper output shows the Media Foundation warning above, then
INFO: DirectShow webcam format <W>x<H>@<F>andINFO: DirectShow webcam connected subtype <S> <W>x<H>@<F>. The take has a camera. The file's size is the connected size.fpsinwebcamFormatand the file'savg_frame_rateequal the rate the graph settled on, and when it differs from 30 the helper output saysINFO: DirectShow webcam negotiated <N> fps (asked for 30). - On the browser recorder, on macOS or Linux, or on Windows in a dev build whose
electron/native/bin/win32-x64/wgc-capture.exeis renamed away for the run, record at 1080p and confirm the.webmis 1920x1080 on a camera that offers it, the size closest to the choice otherwise. Put the helper back afterwards. - [4K camera] On the browser recorder, record at 4K with motion in front of the lens and confirm the
.webmis 3840x2160 and itsbit_rateclimbs well past 18 Mbit/s, the ceiling the sidecar used to inherit from the screen recording's rate. A static scene can come out lower, so note whether it was one.
Sharpness
- Take two recordings of one scene, holding a page of small printed text at arm's length: one at 640x480, from a camera that only offers 640x480 or from a release older than #875, and one at 1080p or 4K. Add a Full Camera segment over the same moment in each project, export MP4 at 1080p, extract a frame with
ffmpeg -ss <t> -i <export> -frames:v 1 frame.png, and view both at 100%. The 1080p take resolves the text and the 640x480 take, upscaled about threefold, does not. With neither reference at hand, logskipped: no 640x480 reference.
- Confirm the editor opens after a successful stop, in Edit mode, with the expected project title and asset and a "Recording added to a new project" toast.
- Confirm
[data-testid="preview"]is present and its current-time value starts at the beginning of the project. - Confirm the loaded video is visible in the preview rather than an empty state or broken-video state.
- Confirm the timeline contains a clip for the recorded or imported asset.
- Activate the project name ("Rename project"), enter a new non-empty title, and confirm the title changes.
- Confirm the dot after the project name reports "Saved" on hover after the rename: edits are saved as they land, with no save step.
- Switch among the Media, Edit, and Record tabs and confirm each selected tab visibly changes state.
- Return to Edit after visiting Media and Record and confirm the preview, timeline, and inspector are back and usable. Media shows the media library over a short arrange-only timeline; Record shows its settings panel and no timeline.
- In Edit mode, activate the Chat panel button in the top bar and confirm the chat panel opens or closes without changing the project.
- Hover the timeline toolbar, the top bar (chat panel, undo, redo), the inspector rail and the Record mode rows with real mouse moves. Confirm each icon-only control names itself in one tooltip and shows no native one beside it, that the Add buttons, Play or Pause, Undo, Redo and Send show your own key as a chip, and that no tooltip covers the next control you would reach for (the rail opens its tooltips to the left).
- Resize the chat panel by its visible divider and confirm the preview area resizes without moving the timeline content.
- Resize the timeline by its visible top divider and confirm the timeline height changes without a layout crash.
The transport sits in the timeline header: Play (which becomes Pause) and the time readout, nothing else. The ruler seeks, and Left / Right step 1/60 s.
- Activate Play (also
Space, or the key you set in the shortcuts dialog) and confirm[data-testid="preview"]changesdata-is-playingfromfalsetotrue. - Activate Pause and confirm playback stops and the preview reports
data-is-playing="false". - Confirm the transport time readout advances while playback is running.
- Confirm the playhead advances with the video instead of remaining at its starting position.
- Seek while paused and confirm the preview frame changes to the selected time.
- Seek while playing and confirm playback continues from the new time without a visible stuck frame.
- While paused, press Left and Right and confirm the playhead and the preview step back and forward by 1/60 s.
- Play through the end of the last clip and confirm playback stops at the total time and
data-is-playingreturns tofalse. - With a webcam recording, confirm the webcam picture-in-picture appears aligned with the screen content.
- Add a full-camera segment, scrub into it, and confirm the webcam grows to fullscreen then returns at the segment end.
- Confirm the preview's webcam, cursor, background, and region effects remain synchronized while scrubbing.
- Confirm the timeline ruler displays time labels from the project start through its duration.
- Click a position on the ruler and confirm the playhead and preview seek to that time.
- Drag across the ruler or timeline track and confirm the playhead follows the pointer.
- Hold
Ctrlwhile scrolling over the timeline and confirm the timeline zooms around the pointer position. - Hold
Shiftwhile scrolling over the timeline and confirm the visible time range pans without changing the project. - Confirm the playhead remains aligned with the ruler and clip positions after zooming and panning.
- Drag the navigator window and confirm the main timeline follows its visible range.
- Drag a navigator handle and confirm the visible range narrows or widens without changing clip data.
- Confirm an empty-area click clears any selected region and closes its selection inspector.
- Confirm the reworked ruler keeps readable labels at the narrowest and widest zoom levels rather than colliding or disappearing.
- Confirm the playhead stays exactly on the time it reports after zooming, panning, and resizing the timeline.
- Rename the project, save it with
Ctrl/Cmd+S, and toggle the export button's availability, and confirm the top bar keeps its layout instead of reflowing on each state change.
- Switch to the Media tab and confirm the project asset appears as a card with its file name, duration, and size.
- Drag a listed media asset onto the timeline strip below and confirm a new clip appears.
- Click an asset card, activate Add to timeline in its detail pane, and confirm a clip is appended with an "Added to the timeline" toast.
- Click a clip and confirm it receives a selected visual state.
- Drag a selected clip before another clip and confirm the clip order changes.
- Double-click a clip and confirm the Edit clip dialog opens; confirm its "Edit start and end" pencil on the clip card opens the same dialog.
- Drag the "Adjust clip start" grip in Edit clip and confirm "Trim range" and "Final duration" change.
- Drag the "Adjust clip end" grip and confirm "Trim range" and "Final duration" change.
- Pick a ratio preset in Edit clip and confirm the crop frame snaps to it and keeps that shape while a corner is dragged; Free unlocks it.
- Change the crop or the grips, press Apply, and confirm the preview and the clip reflect it; confirm Cancel leaves the clip unchanged.
- Select a clip and activate "Delete clip"; confirm only that clip is removed.
- Select a clip, use the configured copy and paste shortcuts, and confirm a duplicate clip appears.
- With two or more clips on the timeline, activate the inspector rail's Edit clip button and confirm a "Choose a clip to edit" menu lists the clips with their time ranges and opens Edit clip for the one picked. Only one clip can be selected at a time.
The timeline toolbar adds each kind at the current time: Add a zoom (Z), Add a trim (T), Add a speed change (S), Add text, an arrow, an image or a blur (an annotation, A), Add Full Camera (C, offered only in a project with a camera). The key shown in each tooltip is your own binding. Selecting a pill opens its settings in the inspector.
- Drag a trim region's left edge and confirm its start time changes.
- Drag a trim region's right edge and confirm its end time changes.
- Scrub across a trim region and confirm the preview skips the marked interval during playback.
- Select a trim, activate Bring this part back in its inspector, and confirm the interval is restored.
- Activate Add a zoom at the current time and confirm a zoom region appears.
- Select the zoom region and pick each level in the Zoom level row, then type one in the field beside it; confirm the preview scale follows. Levels deeper than the recording can take without blurring are not offered, and a typed value out of range answers "Zoom goes from 1× to …×".
- Drag the zoom focus point in the preview and confirm the zoom follows the new focus.
- Change the zoom's 3D camera among Off, 3D Orbit, Screen turned left and Screen turned right, and confirm the preview orientation changes; with 3D Orbit under Auto focus, move the cursor or click from one side of the recording to the other and confirm the screen turns to that side; under Manual focus, drag the focus point to one side and confirm the camera settles on that side and stays there while the cursor moves.
- With the cursor hidden (Cursor facet, Show cursor off) and the zoom under Auto focus, confirm 3D Orbit is not offered; switch the zoom to Manual focus and confirm it is.
- Set a zoom region's Focus mode to Auto and confirm its focus follows cursor telemetry across the whole region.
- Use Auto-enhance → Automatic zooms and confirm it adds suggested zoom regions when cursor telemetry supports suggestions, or says why not ("No room for automatic zooms" on a take whose zooms were already placed after recording).
- Select a zoom region and activate Delete zoom in the inspector; confirm it disappears from the lane.
- With a zoom, a speed, a trim, an annotation and a Full Camera region on the timeline, plus an imported audio track and captions, activate Clear timeline (the eraser button at the end of the toolbar, after Add Full Camera and a divider, or after the last add button when there is no camera; both are absent while no region exists) and confirm every region disappears in one step while the clips, the audio track and the captions stay, and one
Ctrl+Zbrings all the regions back. - Activate Add a speed change at the current time and confirm a speed region appears.
- Pick each speed in the Playback speed row (0.5×, 1×, 1.5×, 2×, 4×) and confirm the lane label and preview timing change.
- Enter a custom speed in the Custom speed field, commit it, and confirm the value is kept, with no preset pressed.
- Play across a speed region and confirm the preview reflects the region's speed.
- Select a speed region and activate Delete speed region; confirm normal speed returns.
- Activate Add text, an arrow, an image or a blur at the current time and confirm an annotation region appears.
- Select a text annotation, replace its text, and confirm the new text appears in the preview, its box fitted to the text.
- Change the text's size (24, 32, 48, 72 or typed), its colour, and its Background plate (None, Dark, Light); confirm each change is visible in the preview and that the text stays readable on every plate.
- Pick each Text animation (the row labelled "Select animation") and confirm the animation runs when the playhead enters the region.
- Switch the annotation's type to Image, upload a supported image (JPG, PNG, GIF or WebP), and confirm the image appears in the preview.
- Switch the type to Arrow, change its direction, colour and thickness, and confirm the arrow changes.
- Switch the type to Blur, change its blur type (Smooth, Mosaic) and shape (Rectangle, Oval), and confirm the selected area is obscured.
- Drag a text, image or arrow annotation in the preview, over the padding too, and confirm its position persists when the playhead leaves and returns; confirm a blur annotation cannot leave the footage it hides.
- Select an annotation and activate Delete annotation; confirm it disappears from the preview and lane.
- Use undo and redo after adding, editing, and deleting at least one region and confirm each operation restores the prior state.
A trim is skipped during playback but the playhead can still be parked on it. Both halves of this are preview-only — the render still cuts.
- Park the playhead inside a trim and confirm the preview shows the frame that is actually at that time, not the first frame of the next kept segment.
- Draw a zoom that lies entirely inside a trim, park the playhead on it, and confirm the zoom fires in the preview.
- Repeat with an annotation and with a Full Camera segment entirely under a trim.
- Confirm a region that merely overlaps a trim without being contained by it behaves as an ordinary region — this is not the same case, and testing it instead is the easy way to record a false failure. Check the saved spans rather than the pills, which look identical at normal zoom.
- Confirm a zoom under a trim plays dry: full strength on its own span, with no ease-in reaching the kept frames beside the cut.
- Confirm speed regions under a trim are not emitted — a still frame has no rate to show.
- Export the range and confirm the trimmed span is absent from the output: the modifiers showing in the preview must not resurrect the cut frames.
Zoom, speed, annotation, and full-camera regions are stored against a clip in that clip's own source time, not at an absolute ruler position. See timeline-model.md. These checks exist because the failure mode is silent: the pill stays where it was drawn while the effect fires somewhere else.
- Draw a zoom wholly inside one clip, reorder that clip to another position, and confirm the zoom travels with the clip and keeps its length.
- Confirm the moved zoom still fires over the same picture content, not at the ruler position it originally occupied.
- Draw a region across a boundary between two clips, move one of those clips away, and confirm the region splits into one pill per clip instead of remaining one pill at the old position.
- Put the two clips back side by side and confirm the fragments render as a single pill again.
- Confirm two regions of the same kind with identical properties that touch display as one pill.
- Change one of the two merged regions and confirm the pill separates into two.
- Drag a zoom pill into a neighbouring zoom with a different level and confirm it clamps at the neighbour's edge and the neighbour does not move.
- Confirm the same repel behaviour for two speed regions with different speeds.
- Add a trim inside a clip that a zoom already covers and confirm the covered part is hidden without shifting any later region on the ruler.
- Confirm the ruler still shows the trimmed span occupying its place while playback skips it.
- Delete a clip and confirm modifiers anchored only to that clip disappear while modifiers on other clips are untouched.
- Duplicate a clip and confirm its modifiers are duplicated with the copy.
- Change a clip's in and out points in the Edit Clip dialog and confirm anchored modifiers clamp to the new range rather than drifting past it.
- Select a zoom, copy it with the configured copy shortcut, move the playhead, and paste; confirm the "Region copied" and "Region pasted" toasts and a new zoom at the playhead with the copied length, level, 3D camera and focus, anchored to the clip it lands on.
- Repeat the copy and paste for a speed region, a text annotation, and a trim (a trim pastes as a new trim of the copied length).
- Copy with nothing selected, and paste before anything was copied, and confirm neither changes the project.
- Save, reopen the project, and confirm every modifier is still on the same clip content after the reorder performed above.
- Zoom and pan the timeline and confirm each pill's span still matches the time at which its effect fires in the preview.
- Export a short range that covers a reordered clip and a trim, and confirm the exported frames agree with the preview about where each modifier fires.
Audio is a clip-anchored region on its own lanes, not a free-floating pill: a track travels
with the clip it was placed over through reorder, trim and delete. One user-visible track can be
stored as several fragments (one per clip it covers) that share a trackId; the lane draws them
as one pill, and each fragment's offsetMs is advanced so a bed spanning a cut does not restart
at the boundary. Most defects in this area are that seam, so prefer a project with at least two
clips and place a track across the junction.
Two things here cannot be judged from the preview alone and need an exported file: the mix and the fades. Export and probe.
- Import an audio file and confirm it appears as a pill on an audio lane with a waveform, and that a clip lane above is unchanged.
- Confirm the pill's label names the imported file rather than the project or the primary asset.
- Play the project and confirm the imported audio is heard over the recording rather than replacing it.
- Open the track's inspector and confirm output level (gain), fade-in, fade-out, loop and mute controls are present.
- v2.0.0 — Confirm a newly imported music track starts at -18 dB with 1 s fades, and that its inspector's Reset audio returns to those values. Play it under speech and confirm the music dips while the voice speaks, in the preview and in the export (about 10 dB in the file).
- Change the gain and confirm the change is audible in the preview; export and confirm the same level in the file.
- Mute the track, confirm silence in the preview, and confirm the exported file has that track absent rather than merely quiet.
- Set a fade-in and a fade-out, then export and inspect the waveform of the result — the ramps must be at the track's own edges. A fade timed to the end of the programme instead of the end of the track is the specific defect here.
- Place a track that runs past the end of the programme and export: confirm it is cut off at the last frame and that its fade-out is not pulled forward to the truncation point.
- Enable loop on a track shorter than its span and confirm it repeats to fill the span, in the preview and in the export.
- Drag a track across a clip boundary and confirm it plays continuously across the junction, with no restart or click at the seam.
- Reorder the clips underneath a track and confirm the track follows the clip it was placed over rather than staying at its ruler position.
- Trim away a stretch the track sits over and confirm the track is silent through the cut while its own clock keeps running — the words after the cut must land on the picture they belong to.
- Put a speed region under a track and confirm the track is not time-stretched with the picture, and that it still starts where it did.
- Delete the clip a track is anchored to and confirm the track goes with it rather than being left pointing at nothing.
- Drag a pill's edge and confirm the ghost of the rest of the file is drawn around it, and that the edge stops at the file's bounds instead of running past them.
- While dragging an edge, confirm the readout shows
in → out / lengthand that the numbers move with the drag. - Hold
Altand drag a pill's body: confirm the media slides inside a span that does not move, and that the rate follows the file rather than the timeline — a four-minute bed must be traversable without dragging four minutes of ruler. - Focus an audio pill and press
Space: confirm it selects the pill without also toggling playback. - Record a voice-over against the timeline and confirm the timeline's own audio is silenced for the take, then audible again afterwards.
- Confirm the recorded take lands on the voiceover lane at the playhead and plays back.
- Restart the app, reopen the project, and confirm the imported audio still loads. Approval to read a file outside the recordings directory is granted by the picker for the session and by the project on load; if that second grant regressed, the waveform is empty and the track is silent only after a restart, which is why this check has to follow one.
- Ask the assistant to add a music bed and confirm it places one on the correct lane; ask it to remove it and confirm the pill and its asset both go.
The transcript is the inspector's Transcript facet. Captions have no facet of their own: the facet's Captions button opens their settings in place of the transcript.
- With no transcript, confirm the Transcript facet offers Transcribe now instead of showing an empty editor.
- Start transcription for the loaded asset and confirm a visible in-progress state appears.
- Confirm a completed transcription displays words in timeline clip order.
- Click a transcript word and confirm the playhead seeks to that word's start.
- Play the project and confirm the current word receives the cue highlight as playback advances.
- Place the caret in the transcript and press
BackspaceorDelete; confirm the affected word becomes marked as skipped rather than disappearing from the transcript. - Hover a skipped word and activate its restore control (
Restore "<word>"); confirm the word is kept again. - Activate Captions in the Transcript facet and confirm the caption settings open in place of the transcript.
- Toggle Show captions and confirm captions appear or disappear in the preview; with no transcript, confirm the toggle is unavailable.
- Pick each Style (Classic, Bold, Minimal, Light), then under Customize change the font, Bold, size, text colour, background plate and its opacity; confirm each committed change is visible.
- Pick a language beside Translate, run it with a configured provider, and confirm translated captions appear, with a Display row to switch languages.
- Switch Display back to "Original (transcript)" and confirm the source transcript returns.
- Confirm the transcript pane states that transcription runs locally and that no upload occurs when it is started.
- With the Whisper helper binary absent, activate the transcribe action and confirm the UI reports why nothing happened, and that the main-process log carries exactly one matching
[stt]line. The failure now reaches a toast (transcriptionStore.ts) and the log (whisperServer.ts), but the sentence shown is one the app writes for itself —whisper-stt-server binary not found; build it via scripts/build-whisper-stt.sh, produced before any helper process starts — so it points a packaged-build user at a script they do not have. Helper stderr is a separate source, and only ever for a helper that did start. Verify against a build whose helper was deliberately not packaged, not only against a working one. - Run transcription in the packaged build and confirm the model is fetched or reused without an error about a missing cache directory.
- Confirm a second transcription reuses the cached model instead of downloading it again.
- Confirm the completed transcript reports the detected language on the asset's card in the Media tab.
- Choose an explicit language under Regenerate as on that card, regenerate, and confirm the new transcript replaces the old one with its own word timings.
- Confirm word timings are monotonic: click several words in order and confirm each seek lands later than the previous one.
- Confirm silent stretches appear as a silence span with its duration rather than as missing text.
- Activate a silence span's trim control and confirm a trim appears on the timeline covering that interval.
- Restore that silence from the transcript and confirm the trim is removed.
- Confirm transcription is unavailable with a clear message rather than a crash when the app runs outside Electron.
- Translate captions, then delete the translation, and confirm the original transcript text and timings are unchanged.
- Confirm a project carrying caption annotations from the old feature reports them and offers to remove them.
- Play across a zoom region with captions on and confirm the captions stay in the frame instead of scaling and drifting with the zoom.
- Export that range and confirm the exported frames show the same caption placement as the preview.
Captions are placed by an anchor and a margin, not by an invisible band: anchorV (top/bottom) with insetY, and anchorH (left/center/right) with insetX. The margin is reserved on the anchored side and applies to the plate, not the text, so it is measurable in an exported frame. Projects from before this change migrate their insetX.
- Open the caption settings (Transcript facet → Captions), enable captions, and seek to a moment with speech; confirm a caption renders with its plate.
- Confirm Position is a grid of six anchors, the camera position's minus its middle row: Top left, Top, Top right, Bottom left, Bottom, Bottom right.
- Choose a top anchor and confirm the caption moves to the top of the frame and the slider relabels to "Distance from top".
- Choose a left anchor and confirm the caption band moves to the left edge with its margin, and that a second slider reads "Distance from left"; choose a centre anchor and confirm that slider is absent.
- Drag the distance slider to each extreme and confirm the caption reaches the true frame edge rather than stopping short at an invisible band boundary.
- Pick a Style, change the size under Customize, and confirm no style shows as selected any more; pick the style again and confirm its look returns.
- Export a frame and measure the plate's edge against the inset: with
insetX: 10on a 1920-wide output, the plate's left edge is at x=192. Measure the exported frame, not the preview screenshot. - Confirm the plate's margin is reserved on the anchored side — a right-anchored caption keeps its margin on the right as the text grows.
- Open a project saved before this change and confirm its captions land where they did, with
insetXmigrated rather than reset. - Play across a zoom with captions on and confirm the captions do not scale or drift with the zoom, in the preview and in the export.
Three gestures on the word stream: double-click corrects a word, Backspace cuts it from the film, and typing between two words adds one. The third is DEV-ONLY and must be absent from a packaged build — see the gate check at the end of this section, and run it against a packaged build, not a dev one.
An added word is a clip, not a pause: the clip it lands in is cut in two and a generated clip goes between the halves, on its own asset with its own file. Until there is TTS that file is a test pattern over noise — deliberately unmistakable, because a held frame is indistinguishable from a decoder stuck at the end of a clip. So "the picture froze" is not a pass here: the mire must be visible, and at the insertion point rather than at the end of the clip.
- Double-click a transcribed word, type a correction, commit it, and confirm the word changes in the pane and in the captions.
- Confirm the correction survives re-transcribing the same asset, with the same timings.
- Place the caret between two words, type, and confirm an amber clip appears on the timeline at that moment — not at the end of the clip.
- Play across it and confirm the mire is on screen for the inserted stretch and the noise is audible, then the recording resumes exactly where it stopped.
- Confirm the words after the insertion still line up with the picture: scrub to a later word and confirm the playhead lands on it, and that its caption appears at that moment and not earlier.
- Confirm the inserted text gets a caption line of its own, over its own media, rather than being glued to the line before it.
- Double-click the inserted word and make the text longer: confirm the amber clip grows and the film grows with it.
- Make the text shorter and confirm the clip shrinks back — both directions, not only the one that is easy to see.
- Delete the inserted word from the transcript and confirm the clip is one clip again, at its original length, with the recording continuous across the join.
- Insert again, then drag the amber clip elsewhere on the timeline and confirm the two halves rejoin behind it, exactly as deleting it does.
- Zoom out until the amber clip is a few pixels wide, select it, and confirm its edit and delete controls appear beside the clip rather than being clipped away inside it.
- Confirm the amber clip behaves as an ordinary clip: it can be selected, moved, cropped and deleted from the timeline, and deleting it there removes the word from the transcript too.
- Export a project containing an insertion and confirm the mire and its noise are in the exported file at the right moment and for the right duration.
- On the voice-over lane, insert a word into a take: confirm the take splits, the generated audio plays between the halves, and the take's later content moves later by that much.
- Confirm that insertion did not lengthen the film — the clips decide the length, and a take pushed past the last frame is clamped at export.
- Insert a word into the film while a take is laid over it, and confirm the take's own span is unchanged: it keeps talking against a picture that has slid.
- Ask the assistant to correct a misheard name and confirm it does; ask it to rewrite an inserted word and confirm it refuses with a reason rather than resizing generated media.
- Against a packaged build: confirm typing between two words does nothing, that double-clicking an amber word does not open an editor, and that the transcript hint does not advertise adding words. Correcting an ordinary word must still work — that one ships.
- In Edit mode, open the chat panel with the top bar's Chat panel button and confirm the chat surface appears.
- Confirm the chat header shows controls for AI settings, history, and a new conversation.
- Send a short request and confirm the user message appears in the conversation.
- Confirm the provider returns an assistant response without an unhandled error.
- Open the model picker and confirm the active model is visibly selected.
- Change the reasoning effort when the configured provider supports it and confirm the chosen value remains selected.
- Run an edit request that creates a supported timeline change and confirm the applied operation is visible in the conversation.
- Use the conversation rewind control by its
aria-labeland confirm the rewind confirmation surface appears. - Confirm a rejected or canceled rewind leaves the timeline unchanged.
- Open AI settings and confirm the provider list, connection status, and configuration form load.
- For an API-key provider, enter a key and confirm the provider becomes connected without displaying the raw key afterward.
- For a device-flow provider, confirm the challenge panel shows a user code and an Open login page action.
- Open conversation history and confirm the current conversation is listed.
- Start a new conversation, switch back to the prior one, and confirm each conversation retains its own messages.
- Rename a conversation with its visible rename control and confirm the new title appears.
- Delete a conversation with its visible delete control and confirmation prompt, then confirm it no longer appears.
The agent may only call the fixed tool set in ai-agent.md; it never writes the document freehand. These checks are about the edit actually landing on the timeline, the turn being one undo unit, and a failed turn leaving the project intact.
- With no provider connected, open the chat and confirm the "bring your own AI" welcome view appears with the composer disabled instead of an error.
- Connect a provider and confirm the same panel becomes a usable conversation without restarting the app.
- Ask the agent to cut the silences and confirm the result appears as trim regions on the timeline rather than as rewritten clips.
- Confirm the seekable duration after that edit still reaches the full recording, so the trims are reversible.
- Ask for a zoom on a described moment and confirm a zoom pill appears at approximately the requested time and the preview scales there.
- Ask for a speed change over a described range and confirm a speed region appears with the requested factor.
- Ask for a text annotation and confirm it appears in the preview with the requested text.
- Ask for a full-camera segment and confirm the region appears and the camera fills the frame while it plays.
- Confirm each applied operation is summarized in the conversation and that the number of summarized operations matches what the timeline gained.
- Ask the agent to remove one of the modifiers it created and confirm that modifier alone disappears.
- Ask the agent to restore the full timeline and confirm the trims it added are gone.
- Confirm modifiers created by the agent are anchored like hand-drawn ones: reorder a clip and confirm they travel with it.
- After a turn that applied several operations, undo once and confirm the whole turn reverts as a single unit rather than one tool call at a time.
- Redo and confirm the whole turn returns.
- Ask for something outside the tool set and confirm the agent explains rather than silently doing nothing or leaving an invalid document.
- Send a request while the project has no asset and confirm a clear response instead of an unhandled error.
- Use the rewind control on an earlier user message, confirm in the dialog, and confirm the timeline, the conversation tail, and the later checkpoints all roll back together.
- Cancel a rewind at the confirmation dialog and confirm both the timeline and the conversation are untouched.
- Confirm the context badge shows a percentage and that its tooltip reports used and budget tokens.
- Activate the compact button (Summarize earlier messages to use less context) on a conversation with enough history and confirm an earlier-context summary message appears and the percentage drops.
- Activate the compact button on a short conversation and confirm the "not enough history" message rather than a failure.
- Confirm a compaction failure leaves the conversation history unchanged.
- Use the copy control on an assistant message and confirm the message text reaches the clipboard.
- Open the timeline toolbar's Auto-enhance menu, choose Smart cuts (it needs a transcript), and confirm the chat panel opens with the prompt prefilled and sent through the normal send path.
- Confirm the edit produced by that auto-enhance request can be rewound like any other turn.
- Choose Smart cuts with no provider connected and confirm the setup view appears instead of a failed send.
- Choose Automatic zooms and confirm it adds zooms without involving the provider.
- Restart the app and confirm conversations are gone while the provider configuration persists; this is a known gap, not a defect to file.
- Confirm the provider API key is never displayed in the settings form after it is saved.
- Confirm
[data-testid="native-compositor-mount"]shows a live composited preview rather than an empty surface. - Confirm
[data-testid="native-compositor-error"]is absent during a normal run. - Scrub back and forth across a clip boundary several times and confirm the preview keeps up without a stall on each crossing.
- Seek to the very end of the project and confirm the last frame is shown instead of a blank or stuck frame.
- Confirm no loading overlay remains on top of an already valid preview frame.
- On a machine with no compatible GPU, confirm
[data-testid="native-compositor-cpu-notice"]appears, the export dialog shows its CPU warning, and the export still completes. - Export the same project on macOS and on Windows and compare frames at identical timestamps for background, blur, shadow, roundness, padding, cursor, and text.
- On macOS, export an MP4 from a project with audio and confirm the output has audio.
- On macOS, export a frame containing a text annotation and confirm the text is upright, centred in its box, and that its background plate fits the text.
- On macOS, export a frame containing a blur annotation and confirm the area is actually obscured.
- On macOS, export a range with the cursor visible and confirm the cursor and its trail are rendered.
- On macOS, export frames with each 3D camera preset and confirm the tilt matches the Windows render.
- On macOS, export a frame with Blur background above 0% and confirm it matches the Windows render.
- Export a range containing a zoom with an annotation and captions on screen, and confirm neither follows the zoom in the exported frames.
- Confirm the packaged macOS app refuses to start or reports clearly when the compositor addon is missing, rather than failing at first render.
The dialog is one settings panel: Format (MP4 / GIF), Quality (720p, 1080p or Source), Frame rate (24, 30 or 60), and for a GIF its size and loop controls. Every MP4 is H.264.
- Confirm the top bar's Export button is disabled when the project has no asset.
- v2.0.0 — With a loaded project, activate Export and confirm the dialog opens on one settings panel: Format (MP4 / GIF), Quality, Frame rate, and no idle hint plate where the progress block will appear.
- Confirm the MP4 quality choices are 720p, 1080p, and Source, and that each one updates the displayed output dimensions.
- Select 24, 30, and 60 FPS and confirm the selected frame rate remains visible.
- Confirm there is no codec choice anywhere in the dialog: every MP4 export is H.264.
- Select GIF and confirm GIF frame-rate (15, 20, 25, 30 FPS), size (Small, Medium, Large, Original), and Loop GIF controls appear.
- Change GIF frame rate and size, toggle looping, and confirm the summary reflects the choices.
- Start an MP4 export with Export MP4 and confirm the native rendering progress reports advancing frames or percentage.
- Confirm the export dialog reports Saved to with the output path after MP4 completes, and that Show in folder opens it.
- v2.0.0 — Confirm the one-time star prompt under Saved to, when it appears, goes away on either answer and does not come back on the next export.
- v2.0.0 — Export a take with speech at the Audio facet's default output level and measure it (
ffmpeg -i <file> -af ebur128=peak=sample -f null -): integrated loudness about -16 LUFS (a very quiet voice gets at most +12 dB), sample peak under -1.5 dBFS. - Open the exported MP4 outside the app and confirm it plays through the expected duration with audio when the source has audio.
- Start a GIF export and confirm frame rendering and file writing complete without an unhandled error.
- During GIF rendering, press Cancel and confirm it waits for native cleanup, returns to the same export options, leaves no partial GIF, and preserves an existing destination; retry and confirm a complete GIF is saved.
- Open the exported GIF outside the app and confirm it contains the expected motion and loop behavior.
- Export a GIF from colour-rich footage long enough to exhaust the encoder's first code widths, and confirm no frame degrades into corrupted stripes or shifted colours partway through.
- Open that GIF in a second viewer and confirm both decoders agree, since a code-width defect can decode differently per viewer.
- Export a project containing audio, a trim, a speed region, a zoom, an annotation, captions, and webcam layout changes when available.
- Compare that exported result with the preview for timing, skipped intervals, audio, webcam, captions, and effects.
- For every pixel-level comparison, export a frame and measure it with an image tool rather than relying on a preview screenshot.
The percentage is computed in the renderer against a predicted frame total, and speed regions change that total. The audio stretch now runs through libavfilter atempo rather than WSOLA; the chain is capped at eight stages and its priming loss is compensated, so the stretched span must land on its target without a silence hole.
- Export a project containing a speed region and confirm the progress bar reaches 100% rather than stalling at a fraction (a 1.25× region used to peg it at exactly 80%).
- Confirm the bar keeps moving during the audio phase instead of freezing once frame rendering ends.
- Confirm the reported frame total accounts for the speed region — a region at N× emits its span's frames divided by N.
- Probe the finished file and confirm the audio and video durations agree to within one frame; a stretch that fell short would show as an audio track measurably shorter than the video. Record the export's frame rate alongside the two durations — "within one frame" is a claim about
1/fps, and without the rate written down it cannot be checked (at 30 fps the tolerance is 33 ms; at 60 fps it is half that, and the same numbers would fail). - Record something that is deliberately noisy across each speed boundary — count out loud, or play a continuous tone — before running
silencedetectover the exported audio. A screen recording with only ambient mic is mostly silence, so the detector reports silence at the boundary whether or notatempoleft a hole, and the check passes or fails for reasons that have nothing to do with the stretch. - With that audio, confirm no silence block coincides with a speed region's boundary. If one does, run the same
silencedetectover the source and map the boundary back through the trims before recording a failure: silence that was already there is not a defect the stretch introduced. - Confirm the exported duration matches the source minus the trimmed spans, adjusted for each speed region. Compute it in frames, not seconds, and state the arithmetic: source frames − trimmed frames − (speed span × (1 − 1/N)) at the export's rate. A discrepancy of a handful of frames is not "close enough" — it is either explained or it is an open question, and the results log has to say which.
- Cancel an export mid-render and confirm no audio decode threads outlive the attempt.
The inspector's rail holds five facets: Composition (background, format, frame, motion), Camera layout, Audio, Cursor (only while a recording on the timeline has cursor data), and Transcript, whose Captions button opens the caption settings.
- Open OpenScreen menu → Keyboard Shortcuts, change one shortcut, save it, use the new key in the editor, and confirm it triggers the configured action.
- Confirm
Ctrl/Cmd+Ssaves the current project with a "Project saved" toast. - Confirm
Ctrl/Cmd+Oopens the project dialog. - Open the Composition facet and switch its Background among the Image, Gradient, and Color tabs.
- Select a built-in wallpaper and confirm the preview background changes.
- Choose a color swatch, or a hex value in the Color tab's Custom row, and confirm the background changes.
- Choose a gradient preset, then a one-colour gradient from the Gradient tab's Custom row, and confirm the preview background changes.
- With an image or gradient wallpaper, pick each Animation (None, Drift, Aurora, Waves) and confirm the background moves in the preview; confirm the row is absent under a solid colour.
- Drag Blur background, Padding, Roundness and Motion blur, and pick each Shadow (None, Light, Medium, Strong); confirm each changes the preview. Roundness is hidden at 0% padding unless a frame is on.
- Pick each frame Style (Window, Laptop, Phone, Screen) and each Theme (Light, Dark), and confirm the preview draws it around the recording; export and confirm the frame is in the file.
- With a zoom on a 3D camera in the project, confirm Depth of field appears under Motion (and is absent without one); turn it on and confirm the tilted zoom changes in the preview and in an export.
- Open the Camera layout facet and choose each Preset (Picture in picture, Dual frame, Vertical stack, No webcam); confirm the preview arrangement changes.
- Change Mirror webcam, Shrink on zoom, Camera shape (Rectangle, Square) and its roundness, Webcam size, and each Position; confirm each change is visible.
- In Webcam crop, drag the frame to move the camera's framing and a corner to zoom it; confirm the preview follows.
- Open the Audio facet, change Output level, and confirm the preview and an export follow it; Reset audio brings it back.
- Open the Cursor facet and toggle Show cursor and Auto-hide when inactive; confirm the preview changes.
- Change Size, Smoothing and Motion blur, pick each Click bounce (None, Light, Strong), and toggle Click impact; confirm each committed value remains visible.
- Pick each Cursor style and toggle 3D cursor; confirm the preview cursor changes. Under Cursor types, switch a type off and confirm that type is drawn as the arrow.
- Record a take with Editable cursor Off (the system cursor), open it in the editor, and confirm the rail has no Cursor facet; confirm a take recorded with it On shows the facet, and an imported video does not.
- Open OpenScreen menu → Switch to light theme (or dark) and confirm the editor switches between dark and light themes.
- Open OpenScreen menu → Change language, choose a non-English locale, and confirm visible UI strings change.
- Switch back to English and confirm the top bar, transport, inspector, and export labels return to English.
- In the Composition facet, pick another Format (16:9, 9:16, 1:1, 4:3, 4:5, 16:10, 10:16, or a timeline shape under Original) and confirm the preview frame changes shape.
- With a Format whose shape differs from the recording's, confirm the Recording row offers Whole and Follow cursor; confirm Follow cursor fills the frame with a window on the recording that follows the cursor, and Whole shows all of it.
- Pick Auto in the Format row, drag padding from 0 to 100, and confirm the frame reshapes live with one border thickness on all four sides; switch the camera layout to Dual frame, then Vertical stack, and confirm the frame widens, then turns portrait, with a square camera. Export and confirm the file has the preview frame's shape and the same even border. Then add a clip of another shape, a differently cropped clip, or a clip without a camera, and confirm the frame does not move and Auto stays listed, disabled with the reason shown ("Clips differ"); pick 16:9 and confirm Auto is no longer offered.
- Press
Escor click outside an open menu, popover, or dialog and confirm it closes.
- On macOS, open the application menu and confirm it lists About OpenScreen, Permissions…, Save Diagnostics, Star on GitHub and Check for Updates.
- On Windows and Linux, right-click the tray icon and confirm it lists Check for Updates and About OpenScreen. Outside the editor the tray is the only surface reachable by default there: the HUD is frameless and the editor and notes windows auto-hide their menu bar, so the Help menu appears only while Alt is held over one of those two windows.
- On Windows and Linux, open the editor, hold Alt, and confirm the Help menu lists Check for Updates and About OpenScreen.
- In the editor, click the OpenScreen wordmark in the top bar and confirm it opens a menu listing New project, Open project and Save project; Keyboard Shortcuts, AI settings, the theme switch and Change language; then Check for Updates, About OpenScreen and Star on GitHub. This is the discoverable path on Windows and Linux, where the two above are not.
- Confirm the About row in that menu shows the running version, and that it matches what the About box then reports.
- Open the wordmark menu and pick Keyboard Shortcuts; confirm the shortcuts configuration dialog opens and that only one dialog appears.
- Open the wordmark menu and pick AI settings; confirm it opens the same provider dialog the AI panel's gear does, and that only one dialog appears.
- Repeat that in Media mode, in Record mode, and in Edit mode with the chat panel collapsed — the three states in which the dialog had no owner before, and the reason the row must not be Edit-only.
- Connect or disconnect a provider from the menu's dialog while the chat panel is open behind it, close the dialog, and confirm the composer and the model pill follow without reopening the panel.
- Open the wordmark menu, then press Escape, click elsewhere in the top bar, and click the wordmark again — confirm each closes it and that the window does not start dragging instead of registering the click.
- post-1.10.0 — In a packaged build on a channel that owns its updates, right-click the tray icon and confirm an Update Settings submenu offers "Notify when an update is available", "Download updates automatically", and "Download and install updates automatically".
- post-1.10.0 — Pick a mode, restart, and confirm it is still selected;
update-settings.jsoninuserDatacarries it. - post-1.10.0 — Confirm the submenu is absent in a dev build and in a build on a channel that does not own its updates (
app.isPackaged && ownsItsUpdates), rather than present and inert. - post-1.10.0 — Confirm a background check that finds nothing shows no dialog at all — the background path never reports "you are current".
- post-1.10.0 — Confirm a failed or unavailable download stops short of the restart prompt rather than offering to restart into an installer that was never fetched.
- post-1.10.0 — Confirm no mode installs on quit: closing the HUD must not fire the installer.
- post-1.10.0 — Reach Save Diagnostics from the tray context menu while idle, and from the Help menu on Windows and Linux (Alt) or the app menu on macOS; confirm each writes a bundle. It is deliberately not in the wordmark menu.
- With the wordmark menu open, walk it with the Down and Up arrows and confirm focus wraps at both ends.
- Switch the app language and confirm the wordmark menu's labels follow — Keyboard Shortcuts and AI settings matching the dialogs they open, Check for Updates and About OpenScreen the wording the macOS app menu and the tray use.
- Open About and confirm it names the running version, the Electron/Chromium/Node versions, and the install channel.
- Confirm the About box opens in front of the HUD rather than behind it.
- On Windows and Linux, press Copy in the About box and confirm the clipboard holds that same block.
- Open the HUD's device-settings panel and confirm its About row reports the same version.
- Run Check for Updates from the menu, from the tray, and from the HUD panel, and confirm each reaches the same result dialog.
- Start a second check while one is still running and confirm the HUD button stays disabled and reads "Checking…" until the first check's dialogs are done, rather than re-enabling into a click that does nothing.
- Start a recording, then confirm Check for Updates is gone from the app menu, the Help menu and the tray for as long as the take runs, and returns when it stops.
- Open the HUD's device-settings panel, start a recording with it still open, and confirm the update button disappears while the version stays. Then stop the take, reopen the panel, and confirm the button is back — a HUD that mounts mid-take must not lose it permanently.
- On a Microsoft Store, Flathub, Snap, or Nix install, confirm no update affordance appears in the menu, the tray, or the HUD panel, while the version still shows.
- Type a zoom level outside the preset row (1.25, say) in the Custom zoom field, commit it, and confirm the preview scale and the retained value both follow, with no preset pressed.
- Activate the timeline toolbar's Auto-Focus all zooms and confirm every zoom switches to automatic focus.
- With the global toggle on, open a zoom's Focus mode row and confirm it shows Auto, disabled, with the note that the timeline's Auto-Focus button sets it, instead of silently ignoring a per-zoom change.
- Turn the global toggle off and confirm per-zoom focus mode becomes settable again.
- Set a speed of 16×, the maximum, and confirm the preview plays it; export that range and confirm the exported timing is correct.
- Enter a speed above 16× and confirm the "Speed can't go higher than 16×" message rather than a silently clamped value.
- Enable the webcam's Shrink on zoom and confirm the camera shrinks while a zoom plays and returns afterwards.
- Choose each webcam layout preset, Vertical stack and Dual frame included, and confirm the preview arrangement changes.
- Choose each webcam shape and confirm the mask changes in the preview.
- Apply each text animation in turn and confirm the animation runs when the playhead enters the region.
- Switch a text's Background plate from Dark to None and back, and confirm the dark plate returns rather than another colour.
- Switch a blur annotation between Smooth and Mosaic and confirm the obscured area changes accordingly in the preview.
- Set a blur shape to Oval and confirm the obscured area is elliptical in the preview.
- Confirm a new blur offers only Rectangle and Oval: Freehand is listed only for a blur that already uses it, in a project from an earlier release, with the note that the export fills the rectangle around it.
- Export a frame containing such a freehand blur and confirm the export covers its bounding box, which over-covers rather than under-covers, as that note states.
- In Edit clip, change the crop Ratio between a preset and Free, apply, and confirm the preview reframes.
- Confirm a cropped project exports with the cropped framing rather than the original.
The mask comes from the native compositor (ONNX Runtime + the vendored selfie-segmentation model), not from the renderer. Three things have to line up — the addon, the ONNX Runtime shared library beside it in electron/native/bin/<tag>/, and the model under public/mediapipe/ — and probeSegmentation answers with ready only when all three do. Check both directions: a control that silently does nothing is the defect this replaced.
- With a webcam recording loaded, open the Camera layout facet and confirm a Camera background row offers Original, Cutout, Blur and Custom.
- Choose Cutout and confirm the camera's background disappears in the preview, leaving the subject over the project background.
- Choose Blur and confirm the background blurs while the subject stays sharp, and that a blur-intensity slider appears.
- Choose Custom and confirm an image/color/gradient chooser appears and the selected wallpaper replaces the camera's background.
- Confirm the first frames after switching modes may render unsegmented — the worker starts lazily. Scrub or let the preview advance before judging; a still, paused preview is not evidence the effect is inert.
- Export a range with a camera background set and confirm the exported frames carry the same mask as the preview, not the untouched camera.
- Remove the ONNX Runtime library from
electron/native/bin/<tag>/(in a packaged build, under itsresourcesfolder), restart, and confirm the whole Camera background row is absent rather than present and inert. The persisted mode stays in the document and the camera renders unsegmented; that is correct. - Put the library back, restart, and confirm the row returns without any other change.
- On an Intel Mac, confirm the row is absent: upstream publishes no ONNX Runtime for osx-x64, so the probe can never answer
readythere.
- Double-click a clip and confirm the Edit Clip preview box carries the source's own aspect ratio, not a fixed 16:9 box — a portrait source must fill it rather than letterbox.
- Focus the crop frame in Edit clip and press an arrow key; confirm the frame moves by 1% of the source, and that with Shift held it resizes from its right or bottom edge, keeping a picked ratio. The arrows must not seek the timeline behind the dialog.
- Change the Ratio between a preset and Free, apply, and confirm the preview reframes and the clip's stale crop metadata does not survive.
- Hold Ctrl and scroll over the ruler, over the hint labels, and over the navigator bar — not only over the lanes — and confirm the timeline zooms in each case.
- Hold Shift and scroll over those same three places and confirm the visible range pans.
- Open the clip picker, click outside it, and confirm it closes.
- Select a clip and confirm its delete button is the same round raised button as the edit pencil, rather than sitting bare on the waveform; on a clip too narrow to hold them, confirm both sit beside it.
- Switch to the light theme and confirm the gradient picker and the dialogs follow it instead of staying dark.
- Confirm the top bar carries no second settings button beside the wordmark menu.
- Start a transcription and confirm the status reads Downloading speech model (first run) or Starting speech model before Transcribing, then a percentage, on every surface that shows it.
- Regenerate a transcript and confirm the busy label stays visible for the duration and is scoped to the timeline rather than leaking to unrelated surfaces.
- Open the media asset card's Regenerate as picker and confirm it lists every whisper language (101 entries including Auto), sorted by localized name — not a hand-picked handful.
- Choose a language, regenerate, and confirm the new transcript replaces the old one.
- Hover the inspector's switches and choice rows, in Composition, Camera layout and Cursor, with real mouse moves and again with the keyboard. Confirm no tooltip only repeats a visible label, that the icon-only options (camera preset, camera position, frame) each show their name, and that Depth of field, Auto-hide when inactive, 3D cursor, Click impact and Shrink on zoom each show one line saying what they do, on the switch itself. See tooltips.md.
- Move and resize the editor window, close it, reopen it, and confirm it returns at the same position and size.
- Maximize the editor, close and reopen, and confirm it returns maximized.
- Un-maximize, close, and confirm
editor-window.jsoninuserDatarecordsmaximized: falsewith the restored bounds. - Hand-edit that file to a zero width and height and a non-boolean
maximized, then relaunch; confirm the editor opens at its default size rather than restoring an unusable window.
Edits are saved as they land. The dot after the project name reads "Unsaved" (hover title) only in the middle of a drag, or when a save failed.
- Make a project change and confirm the dot after the project name reads "Saved" once it lands, with no save step.
- Save with OpenScreen menu → Save project (or
Ctrl/Cmd+S) and confirm the "Project saved" toast. - Close and reopen the project from the Open project dialog and confirm the asset and project title match before closing.
- Confirm clip order and each clip's in/out and crop settings survive reopen.
- Confirm trim, zoom, speed, annotation, and full-camera regions survive reopen with their positions and values.
- Confirm background, effects, layout, webcam, cursor, aspect-ratio, and caption settings survive reopen.
- Confirm the transcript and skipped-word ranges survive reopen.
- Confirm the seekable duration after reopen reaches the recording duration, not merely the end of the last region.
- Make a change, open another project, and confirm it opens with no unsaved-changes prompt, since the change was already saved; reopen the first project and confirm the change is there.
- When the unsaved-changes prompt does appear (a change that has not reached the disk, such as after a failed save), confirm Cancel keeps the current project loaded, Save & continue opens the next project after saving, and Discard opens it without the change.
- Open a project saved by a previous release and confirm it loads without a schema error.
- Confirm every modifier in that migrated project sits on the clip content it covered before, not at a shifted ruler position.
- Confirm a migrated project that had a region straddling two clips still renders it as one pill while the clips remain adjacent.
- Save the migrated project, reopen it, and confirm nothing shifted on the second round-trip.
- Run the complete capture-to-export flow on real Windows with the packaged build.
- Confirm a screen source and a single-window source both produce non-black video.
- Confirm the system tray icon appears and changes to a recording state while recording.
- v2.0.0 — With a physical camera, run Webcam capture quality on Windows: the Media Foundation checks, the no-visible-frame warning, and the DirectShow ones when a DirectShow-only camera is at hand.
- Right-click the tray icon while recording, choose Stop Recording, and confirm the editor opens.
- Confirm the HUD and notes window are excluded from captured video when content protection is enabled.
- Disable hardware H.264 if the test machine supports that diagnostic path and confirm the software-encoder notice is clear and non-blocking.
- Switch the recording HUD between displays and confirm it remains positioned on the intended display.
- Switch the desktop to an odd-pixel window size and confirm the recorded frame dimensions remain valid.
- On Windows 11 24H2 or later, record a single-window source, right-click inside that window and confirm the context menu is in the video; then open one that overhangs the window's edge and confirm it is cut off at the edge. Run it with nothing ordinary overlapping the recorded window: with another window (neither a popup nor a tool window) above it the menu was missing in the synthetic
--window-popupfixture (46 runs on a second Windows 11 build 26200 machine: present in 25 of 25 runs with none above, absent in 21 of 21 with one above, see #910), so treat that as expected rather than a failure, without assuming the same for every real application's menu. Before 24H2 the menu is absent too, which is expected (secondary-windowsapplied:falsein the log). - Run tray → Save Diagnostics and confirm a diagnostic bundle can be written.
- post-1.10.0 — Record with no encoder override and confirm the helper's
encoder-selectionlog line reportsvideoEncoderRuntime: "hardware". The plain sink-writer path never asked for hardware transforms before, so every ordinary recording ran the software encoder; on a slow machine that is what blew the stop-shutdown budget. - post-1.10.0 — Confirm forcing the software encoder still reports
"software", so the default is a default and not a hard-wire. - post-1.10.0 — Record with microphone and system audio and confirm the resulting MP4 carries a valid AAC track at a legal rate (48 kHz).
- Speak continuously for 30 s with the microphone on, then play the file in a neutral player (VLC, ffplay) and confirm there is no crackle. Include the moment you reach for the HUD to stop: that is where the holes of #911 clustered. In a waveform view, the defect looks like drops to digital silence of up to 10 ms in the middle of words.
- Record with the webcam and confirm
ffprobereadsprofile=High,has_b_frames=0andtv / bt709 / bt709 / bt709colour tags on the screen and webcam tracks (#922, #923). Then export: solid colours must match the source within ±2 in RGB. - post-1.10.0 — On a device whose native rate AAC cannot take (96 kHz), confirm the recording still succeeds with the rate snapped to 48 kHz rather than failing at
SetInputMediaType. The helper's ownaudio_sample_utils_testcovers the accept/reject probes at build time; this check is the end-to-end half. - post-1.10.0 — Confirm a long recording's audio stays in sync, so the downsample remainder is carried across packets rather than drifting.
- post-1.10.0 — On a device that can be set to 96 kHz, record system audio while a 36 kHz tone plays and confirm the recording carries no 12 kHz component. That fold is what an inadequate anti-alias filter produces, and neither of the two checks above would catch it: the rate-snap check only asks that the recording succeeds, and the sync check only asks that frame counts stay aligned. Probe tones must sit well inside what AAC keeps — 12 kHz is fine; a 20 kHz probe was absent from the app's recording, and a 192 kbps AAC encode alone (tested with ffmpeg) removes it too, so it cannot be measured. Needs an endpoint whose shared-mode format is above 48 kHz and an integer multiple of it — check the Advanced tab's format list, and check every endpoint, not just the current format of the default one; a USB DAC is one way to get such an endpoint. Exclusive-mode support is not enough, because loopback reports the shared-mode format. If every endpoint really is 48 kHz, the check cannot run at all: forcing a lower encoder target instead does not work, since every AAC rate that would divide 48 kHz is rejected by the Media Foundation encoder on the host tested.
- Run the complete capture-to-export flow on real macOS with the packaged build.
- Grant the permissions through the permissions window (below) and confirm the HUD reflects the granted devices.
- Record while switching Spaces with the HUD visible and confirm recording continues.
- Stop a recording and confirm the editor opens without a crash during native recorder shutdown.
- Confirm the tray or menu-bar item can refocus the HUD after it is hidden.
- Confirm the HUD and notes window are absent from a full-screen recording. The capture leaves them out by window id on every macOS version, so this holds with the content-protection flag set too.
- Confirm a physical webcam picture-in-picture records and plays back with the selected layout.
- v2.0.0 — With a physical camera, run Webcam capture quality on macOS. The webcam is recorded by the browser recorder here, so the
.webmchecks apply and the Windows helper checks do not. - Export MP4 and GIF and confirm both files open in a native macOS media viewer.
- Confirm closing and relaunching the packaged app does not leave an orphaned capture or editor window.
- On the newest supported macOS, confirm the HUD and notes windows are visible on screen rather than blanked by content protection.
- Confirm the HUD opens without waiting for the microphone permission prompt to be answered.
- Confirm local transcription reports the device it actually ran on and completes on a Metal-capable machine.
- Confirm the packaged
.appcontains the compositor addon and that the addon carries no build-machine path. - Confirm the packaged
.appbundles its ffmpeg libraries and runs on a machine with no developer toolchain installed. - On a Retina/HiDPI display, record the screen and confirm the recorded frame is filled edge to edge — not the desktop drawn small in one corner of a black rectangle. Then do the same for a single window. Issue #418 shipped exactly this, invisible on every 1× display because a point size and a pixel size are the same number there;
SCStreamConfigurationdoes not scale a frame up to fill an oversized buffer, so the surplus stays background black. Check the frame, not just the file's dimensions — the reporter's.mp4was 3024×1898 as expected and still wrong inside. - With a second display attached at a different scale factor, record each display in turn and confirm both fill their frame. A machine whose displays all share one scale factor cannot catch a units mix-up.
On macOS 15.2+ it opens at launch, until it has been closed once, while one of its rows was never asked. Before 15.2 it opens at launch while Screen Recording is missing or waits on a relaunch. Tray → Permissions… and the app menu open it any time. A Record-mode start with Editable cursor enabled may request Accessibility from macOS; a pending grant does not reopen this window or block recording. To see a first run on a Mac that has run OpenScreen before, its grants have to be reset first, which is the Mac owner's decision.
- On a first launch, confirm the window "OpenScreen needs a few permissions" lists, on macOS 15.2+, System audio (Optional), Accessibility (Recommended), Microphone (Optional) and Camera (Optional); before 15.2, or with
OPENSCREEN_MAC_SOURCE_PICKER=legacy, Screen & system audio (Required) comes first instead of System audio. - For each row whose permission has not been requested, activate its button (Allow, or Continue for the screen row) and confirm macOS raises its prompt; allow it and confirm the row turns to Allowed while the window stays open.
- For a permission already requested but still denied, confirm the row offers Open Settings on the window's first appearance, and that it opens that permission's System Settings pane instead of raising a new prompt.
- With Screen Recording ready, Accessibility ungranted, and Editable cursor enabled in Record mode, press Start recording. Confirm the macOS prompt may appear, the countdown still reaches an active take, and a warning says cursor effects may be limited while Accessibility is pending. Stop the take while leaving Accessibility pending, then start another take; confirm the OpenScreen permissions window does not reopen and recording still starts. Grant Accessibility in System Settings and confirm a later take has full cursor effects.
- Refuse a permission, reopen the window from the tray, and confirm its row still offers Open Settings and opens the matching System Settings pane.
- Before 15.2: confirm Get started stays disabled until Screen Recording is allowed; that the window offers Restart OpenScreen when macOS needs a relaunch to apply it; and that after System Settings' Quit & Reopen the window comes back.
- Close the window with Get started and confirm it does not open again at the next launch.
- Activate the HUD's source button and confirm Apple's picker opens and the HUD hides until it closes.
- Pick a display, then on another take a window, and confirm the HUD comes back with the pick's name on the source button and records what was picked, with no Screen Recording grant and no "bypass the system private window picker" alert.
- Dismiss the picker without a choice and confirm the HUD comes back idle; press record with no source and confirm it opens the picker, then starts once a source is picked.
- From Record mode, activate the Source row and confirm it opens the same picker rather than the app's own list.
- After picking a display, change Hide desktop icons in Record mode and confirm the pick is dropped, so the next take asks for a source again.
- With system audio never asked for (not from the permissions window either), turn system audio on and confirm macOS asks for it at once, not at the countdown; allow it and confirm the next take carries system audio. Refused, confirm the permissions window's System audio row offers Open Settings, and that turning OpenScreen on under "System Audio Recording Only" there brings system audio back.
- Launch with
OPENSCREEN_MAC_SOURCE_PICKER=legacyand confirm the app's own selector opens instead, with Screen Recording required.
- On macOS 13 or 14, confirm the HUD, its Device settings and Record mode offer no microphone, and that a take records without trying to.
- Run the complete editor-to-export flow on real Linux with the supported packaged or development build.
- Confirm the HUD remains interactive on the supported Linux window manager.
- Select a screen source in the compositor's portal picker and confirm the resulting recording is not black.
- Select a single WINDOW in the portal picker and confirm the recording contains only that window, at the window's dimensions — not the whole screen. Check the pixel size, not just the look of it:
ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 <file>should report the window's size, never the monitor's. This is the case that shipped broken in 1.8.0. - Record twice in a row and confirm the portal picker appears BOTH times, and that choosing a different source the second time actually changes what is recorded.
- Confirm the HUD shows no in-app source button on Linux, and that the record button starts a recording directly instead of opening a picker.
- Confirm the portal picker appears BEFORE the 3-2-1 countdown, not during or after it.
- Start the same flow from the editor's Record mode (Start recording) and confirm it behaves identically to the HUD — no source to pick, picker first, then countdown.
- Cancel the countdown after answering the picker and confirm the compositor's "screen is being shared" indicator goes away rather than lingering.
- Confirm the system tray or supported desktop indicator can refocus the HUD when it is hidden.
- Confirm microphone capture works with a physical device and the chosen device is audible in playback.
- Confirm the HUD's Device settings lists the physical camera, or reports "No camera found" when there is none.
- v2.0.0 — With a physical camera, run Webcam capture quality on Linux. The webcam is recorded by the browser recorder here, so the
.webmchecks apply and the Windows helper checks do not. - Confirm the native compositor preview loads without a blank surface or renderer crash.
- Export MP4 and GIF and confirm the files open in a system media player.
- Close and relaunch the app and confirm a saved project can be reopened without data loss.
| Date | Build / tag | Platform | Pass/fail | Notes |
|---|---|---|---|---|
| 2026-09-23 | dev fix/725-hidden-cursor-auto-zoom; rebuilt compositor addon SHA256 a0bcd61e17084c010141049a9978121e45be2985449ff1618ab6a3145f8381f6 |
Windows 11 Home 26200 | Partial — issue #725 slider slice passed | Human-driven real recording in the Electron dev build: moving a slider whose application cursor becomes hidden kept the reconstructed cursor hidden in the Studio recording and did not create a click-driven automatic zoom for that press. A visible control click still produced the normal automatic zoom. Sidecar inspection independently confirmed the hidden interval (visible: false) and that no zoom was centred on the press immediately preceding it. Recording, stop and editor open passed. Not covered: export, packaged build, audio/webcam, tray/HUD, other editor features, macOS or Linux. |
| 2026-09-12 | dev fix/643-gif-export-cancel, based on fbe461e9; rebuilt compositor addon SHA256 128c89fe65a459b3f17721d610ea9a78e0d3e980e9eb0b5fb2b0e5d018619e15 |
Windows | Partial — export cancellation passed | Computer-use OS input, real Electron and native save dialog, imported synthetic 8 s source. GIF 15 FPS / Medium (720p) / loop on: observed 55/120 frames, clicked Cancel while rendering (pending snapshot 104/120), returned to the same options; no final or partial output remained. Retried in that dialog to the same path and received Saved to; resulting GIF is 1280×720, 120 frames, 8,200,202 bytes, fully decodes with ffmpeg (exit 0). Automated real-addon tests separately pass existing-destination preservation, GIF retry, ordinary GIF and MP4 export. Desktop adapter required screenshot-based coordinates and Ctrl+O to reach the editor; HUD reachability is not claimed. An unrelated process error window partly obscured the retry success screenshot; file verification independently passed. Skipped: recording, audio/mic, webcam, tray/HUD, effect/caption comparisons, second viewer, macOS/Linux and release promotion. Task-local profile and evidence retained; test app stopped. |
| 2026-07-31 | dev build, claude/e2e-tests-v1-8-0-474894 (e9578f09) |
macOS 26.5, M1 | Partial — 1 defect | Ran launch/HUD, media, modifier anchoring, and export. Defect: a dangling asset blanks the preview. Modifier anchoring across a reorder verified in preview and in the exported frames. macOS export produced 1280×720 h264 + AAC at ~2× realtime. Chat sections skipped: no AI provider configured. HUD drag not runnable under computer-use (drop point is the desktop). |
| 2026-08-13 | installed v1.9.5-rc.1 |
Windows 11 26200, 1920×1080 @ 100% | Partial — 1 defect | Ran launch/HUD, source selection, recording, stop, editor open. Fragmented MP4 confirmed on the shipped artifact: 48 moof+mdat pairs over 47.6s, mvex present, mfra on clean stop. Defect: a recording that survives a helper kill is thrown away by the app — killing wgc-capture.exe mid-recording leaves a fully decodable 41s file (2460 packets, ffmpeg -f null - exit 0) with no .session.json and no .cursor.json, and stop answers "The recording could not be saved". Fixed in #363, re-verified end to end. Truncation ablation at 60%: plain MP4 unreadable, fragmented plays 29s. A dev build cannot test any of this — the prebuilt worktree helper predated the change and silently ran the old path. Editor/export/chat sections not run. |
| 2026-08-14 | release/v1.9.5 @ b1b81de5 (rc.2 candidate: dev TS + the CI-built rc.1 native payload, which is byte-identical since no native source changed) |
Windows 11 26200, 1920×1080 @ 100% | Pass — no defect | Regression net across the 65 commits since v1.9.2, not just the rc.2 delta. Four recordings. Every one a fragmented MP4 (mvex + ~1 moof/s, mfra only on a clean stop). GPU DXGI path still correctly opt-in (videoInput: cpu-rgb32) — the #336 regression has not crept back. No capture-pacing drift: HUD 00:59 → 60.067s at 60/1. Waveform correct in both directions: absent with no audio track, rendered with one. Audio muxes into the fragmented container (AAC 48k stereo) with 15 ms A/V drift, under one frame. Compositor renders and exports with no camera declared. #366: reopening returns to the saved project with its settings (Blur BG on, padding 9%) and mints no second project — 167→168 across a whole new recording. #363: helper killed mid-recording → editor opens on the recovered take (46 moof, no mfra), all three sidecars written, imported once. Export MP4 1080p60 from that recovered take: 46.0s / 2760 packets, decodes clean, duration matches the source exactly. Tray refocus works. NOT covered: DPI scaling — not re-run here, already validated when 60bb6d7c / 71cc88d6 landed; note that the display scale is a setting, so "this machine is at 100%" is never a reason a DPI bug cannot be tested (flip it to 150%, ~2 min). Also not covered: webcam PiP and the export webcam fixes, microphone, GIF, macOS/Linux, AI sections, packaging. |
| 2026-09-02 | worktree fix/wgc-aac-sample-rate (rebuilt wgc-capture.exe) |
Windows 11 | Partial — helper-level only | Not a capture-to-export HUD pass. Media Foundation SetInputMediaType for 96 kHz PCM into AAC returns 0xc00d36b4; 48 kHz succeeds. Live helper with snap on: forced 96 kHz encoder source reports 48 kHz and recording-started. Live helper with snap off: initialize fails at SetInputMediaType(audio) with 0xc00d36b4 (hardware, software, and plain MP4 retries). HUD, editor, export, webcam, and tray not run. |
| 2026-08-14 | installed v1.9.5-rc.1, macOS Apple Silicon DMG (CI-built, Developer ID signed). rc.2 is not published — only rc.1 exists on Releases; no native source changed between v1.9.5-rc.1 and origin/release/v1.9.5, so this artifact already carries the rc.2 native payload, but #366 (cross-platform TS) is absent from it |
macOS 26.5 (25F71), M1, 1920×1080 @ 2× | Fail — 1 blocker | The plan's assertion-1 criterion does not hold on macOS, in both directions. On a clean stop AVAssetWriter.finishWriting() collapses the fragments into a normal movie: ftyp mdat moov, mvex ABSENT, 0 moof, no mfra (45 s / 44.4 MB run). That is exactly the shape the plan calls the headline failure — and the pre-a6795d23 control recording (2026-08-10) has the same shape — so a clean-stop box walk cannot distinguish fragmented from plain on macOS; only the kill test can. Fragmenting is active: the takes whose writer died mid-fragment retain mvex + ~1 moof per second of media (shipped-build writer-failure samples: 35 moof/36.0 s, 14/15.0 s, 3/4.0 s; plus 18 on a surviving-helper kill). The one kill on the shipped build is the exception that proves the scope — capture had already stalled ~12 s before the kill, so it carries mvex but 0 moof and only 1.0 s. No macOS file, clean or killed, ever carried mfra. Blocker: every app-driven recording truncates, then the app discards it. (Root cause and fix reported in #375 — the fragments carry a negative composition offset in a version 0 trun, where ISO/IEC 14496-12 8.8.8.2 defines the field as unsigned, because frame reordering was left on; AVVideoAllowFrameReorderingKey: false clears it and restores the crash-resilience the fragmenting was for. Verified at helper level there; this rc.1 run only reproduced the failure and validated nothing about the fix. Re-run this section against a CI build carrying #375 before rc.2 ships.) 3/3 takes stopped writing early while the HUD kept counting — media 4.0 s / 36.0 s / 15.0 s against HUD 02:02 / 01:30 / 01:04. Helper emits {"event":"error","code":"writer-failed"}; main log AVFoundationErrorDomain Code=-11800 … (-16341). Stop then hangs ~30 s on "Saving…" and drops the take: no .session.json, no .cursor.json, no editor. The app does surface the raw error in a toast (confirmed by hand on the same machine at 13:28–13:35 — my automated runs screenshotted after it auto-dismissed, so an earlier draft of this row wrongly said there was none). 44,561,966 / 328,337,979 / 139,631,607 / 17,187,009 bytes decodable and thrown away (147 GB free — not disk). Reproduced standalone with the shipped helper at 1080p30/8 Mbps, 2/2 (~9 s, ~5 s), so it is not confined to the app's 4K60 path — but do not read that as load-independent: append rate demonstrably modulates how reliably it bites (#375 measures it reliable at ~57 fps and intermittent at 30 fps). Reproduced by hand, no automation involved, on six takes recording a YouTube page — and those six separate the trigger cleanly: system audio ON → 3/3 died at ~1.0 s and minted 0 projects; system audio OFF → 3/3 survived (3.3 s, 7.4 s, 25.0 s) and minted 1 project each. Audio is not the condition, only an accelerant — a controlled run with system audio off and not one screenshot taken during the capture (the screenshot layer hides non-allowlisted windows, so it was the last confound worth eliminating) died the same way: 8.008 s of video, 79,004,330 bytes then flat for 76 s with the helper still alive, 7 moof, 0 sidecars, 0 projects, same -11800/-16341. What audio changes is the window: with a track it is ~1 s, without one ~4–40 s. That reconciles the by-hand takes with mine — a take short enough to stop before the writer dies is clean, which is why 3.3 s and 7.4 s survived and 8.0 s did not, and why the 25.0 s one minted a project while still carrying mvex (never cleanly finalised). Turning audio off is therefore not a safe workaround. Untested here: microphone — this Mac has no input device, and whether a mic track triggers the same path is an inference, not a measurement. Helper A/B narrows the with-audio path to the fragmentation line: helper built twice from source identical to the rc.1 tag, differing only by writer.movieFragmentInterval (701 vs 700 lines) — with system audio at 1080p30, WITH the line writer-failed 2/2 (2.0 s, 1.0 s), WITHOUT it clean recording-stopped 3/3 (40.6 s, 37.9 s, 37.6 s). Read those counts as a sample, not a law: a later rebuild of the with-the-line arm survived 22.2 s at the same settings, so the failure is probabilistic and rate-dependent, and the byte-level evidence in #375 is what actually carries the case. The video-only local-vs-shipped gap (local survived 45 s, shipped failed 5/5) is explained by the same variable rather than by the released artifact — the shipped runs encoded at 56.6 fps against 29 fps locally. Kill test is confounded on the shipped build (capture already dead before the kill): 17.19 MB → only 1.0 s / 56 packets, 0 moof. On a helper that does not fail, a mid-write kill leaves 18 moof, decodes clean (ffmpeg -v error -f null - exit 0, 1373 packets) and no mfra — the shape the plan expects. #363 gap confirmed, and on macOS it fires with no kill at all: writer-failed alone loses the take; there is no app-side recovery. Audio: AAC 48 kHz stereo muxes into the fragmented container, video start 0.000000 vs audio 0.014479 → 14.5 ms drift, under one frame at 30 fps (measured on the 2.0 s written before the writer died). Compositor + export pass: preview renders with no camera declared; export MP4 1080p60 H.264+AAC via h264_videotoolbox (zero-copy VT), 5,726,865 bytes, 318 packets, decodes clean, duration matches to within 7 ms — source 26.713 s minus trims 19.910 + 1.513 = 5.290 s expected vs 5.283 s measured, under one frame at 60 fps. #366 not runnable as specified (absent from rc.1, rc.2 unpublished, and record→editor never completes); adjacent behaviour measured on an existing project — close+reopen kept 19→19 projects, exactly ONE project references the recording, and Blur BG / padding survived (showBlur=true, padding=16). NOT covered: Windows-only DPI and wgc-capture, GIF, AI sections, packaging (per plan); webcam PiP and microphone — this Mac has neither (Device settings reports "No microphone found" / "No camera found"). |
| 2026-08-22 | installed v1.10.0-rc.3 — CI-built NSIS artifact from build run 32582966489 (openscreen-windows), App menu → About reports 1.10.0-rc.3, native payload complete and uniformly stamped (19 files in resources/electron/native/bin/win32-x64, all 17:59:10, so helper + compositor addon + av* DLLs are one matched CI set) |
Windows 11 26200, 1920×1080 @ 100% | Pass — 2 minor defects | Pause works, and the measurement that says so is the wall clock. createdAt 20:25:52.208 against a file finalised at 20:30:56.754 is 304.55 s elapsed for a 286.333 s file — 18.21 s shorter, exactly the paused interval, so capture was genuinely suspended. The HUD timer froze at 03:58 across two reads 7 s apart with the indicator amber, and resume was clean (04:01 → 04:08 over 7 s, no time lost). An earlier draft of this row called this a blocking defect, on the strength of comparing the file duration against a timer read before the stop click; with tool round-trips of ~20 s that comparison is worthless, and the packet count offered as corroboration proves nothing either — a file is continuous 60 fps whether or not capture was ever suspended. Written down because the wrong version of this measurement is easy to repeat: compare against wall-clock elapsed, never against the last timer you happened to screenshot. Capture is otherwise sound, on two takes. 15.8 s: fragmented (ftyp uuid pdin moov then 16 moof/mdat, mvex present), mfra on the clean stop, 1920×1080 @ 60/1, 948 packets = 15.8 × 60, ffmpeg -v error -f null - exit 0, both sidecars written. 286.3 s: 287 moof, mfra present, 17,180 packets, decodes clean, .cursor.json 1.3 MB. No pacing drift and no dropped frames over 4 min 46. Export passes and honours its settings: 720p/30 requested from a 1080p60 source gave 1280×720, avg_frame_rate 85900/2863 = 30.004, 8590 packets matching the frame count the progress UI itself reported, duration 286.333 s identical to source, decodes clean, 124.5 MB, written to the path chosen in the native save dialog and reported back as "Saved to …". Composition verified by extracting a frame and reading it at full resolution (not from a preview screenshot): gradient background, content inset as a rounded card with a drop shadow, content aspect ≈1.76 against the 16:9 target, synthetic cursor drawn. Note the exporter adds a silent AAC 48 kHz stereo track even though no audio source was enabled. Retracted: "the HUD language menu ignores Escape". It does not — the maintainer confirms the key works by hand. Claude Desktop swallows Escape before it reaches the app under test, so a synthesised press proves nothing about the app, and GetForegroundWindow() returning the HUD does not rescue the inference: the key never left the driver. The companion observation (an outside click on the HUD's own drag handle did not dismiss the menu) is withdrawn with it, since the HUD's own chrome is not "outside" the popover in any meaningful sense. What is established is that the blur path shipped in this RC works: 54e12706 fix(hud): dismiss the HUD popovers when the window loses focus dismissed the menu on a click to the desktop. Rule for anyone driving keyboard checks from computer-use: Escape is unusable as evidence, and any negative keyboard result needs a by-hand confirmation before it goes in this table. Behaviour vs doc: the record button is not disabled without a source — it opens the source selector. No recording starts, so the check's intent holds, but AGENTS.md still describes a disabled button with a "Please select a source to record" tooltip, and that is why no tooltip appears. Passed: single launch window, no startup crash; HUD visible under OPENSCREEN_DISABLE_CONTENT_PROTECTION=1; tray layout toggles horizontal↔vertical both ways; HUD drag follows the pointer without drift and stays at the drop point; language menu opens with its locale list; minimize hides the HUD without quitting (6 processes still alive); relaunching routes through the single-instance lock, restores the window and mints no duplicate; source selector opens, selecting a card enables Share, and the HUD label becomes the picked source (Tout l'écran); record → stop opens the editor with the asset, a timeline clip and a rendered preview; About reports the RC version. Local transcription works, on GPU — an earlier draft of this row reported it broken, which was wrong. Relaunching with stdout/stderr captured and importing a 15 s asset that carries an audio track settles it: [whisper-stt] boot: model=…\whisper-ggml\ggml-small-q8_0.bin host=127.0.0.1 port=64720 threads=16, ggml_vulkan: 0 = NVIDIA GeForce RTX 4070 Ti, model loaded; backend=whispercpp-vulkan, then [stt] done on whispercpp-vulkan: 1 chunk(s), 15.0s audio in 0.1s (0.01 rtf, 106.8x real-time). The pane switched to "1 caption lines, derived live from the transcript". The real (minor) defect is the error message: on an asset with no audio track the captions pane says "Failed to fetch", which reads as a network failure and sent this run hunting a broken STT server that was never involved — the pipeline simply has no audio to extract. It should say so. Second minor find, from the same stderr: listProjects cannot read three saved projects — one ZodError (transcript.segments[0].endSec must be greater than or equal to startSec, repeated across segments, words and transcripts[0]) and two SyntaxError: Unexpected non-whitespace character after JSON, i.e. truncated or double-written project files. They are skipped silently in the UI. Caption anchoring — the rc.2→rc.3 delta — is present but its rendering was not measured. The Position section carries exactly the model those commits describe: Bottom/Top, the note "Long captions grow upward — the bottom edge stays put", Distance from bottom defaulting to 1.5 %, and Left/Center/Right. What could not be checked is where a caption actually lands, because the only transcript obtainable here came from a 300 Hz sine and yielded one line that never surfaced at any scrubbed position. Closed out of band: the maintainer ran the caption sections by hand on a real spoken-audio recording and reports them correct, which is the coverage this automated run could not supply and the last gap standing between this RC and a promote. Also confirmed from stderr: [content-protection] OFF for the HUD window (OPENSCREEN_DISABLE_CONTENT_PROTECTION=1), so the flag does log its effect, and with the flag unset the HUD is correctly invisible to screenshots. The consequence matters more than the cause: the eight caption anchoring/margin/inset cherry-picks that are the entire delta from rc.2 to rc.3 are NOT covered by this run. Not run: restart and cancel actions; audio capture of any kind; webcam PiP; GIF; DPI scaling; HUD/notes exclusion from captured video with content protection ON (the whole session ran with it off, and the exported frame confirms the HUD is captured when it is off); regions, modifiers, timeline navigation, clip operations, persistence; macOS and Linux. Environment limits that shaped this run, worth knowing before the next one. parsecd.exe runs elevated and holds an invisible always-foreground window (ParsecMinFrameRate16); the moment OpenScreen loses focus every computer-use click is refused, and because the process is elevated UIPI makes granting Parsec useless — tray-icon refocus could therefore not be tested at all. Relaunching the app (single-instance raises it) is the way back. Dragging the HUD only works while every intermediate pointer position stays inside the HUD's own 904×698 mostly-transparent window; as soon as one lands on the desktop, the tier-"click" shell gate refuses the drag mid-gesture and leaves the button down — release it explicitly. Finally, the Microsoft Store package (EtienneLescot.OpenScreen, 1.9.6) shadows the NSIS install in request_access: every grant resolved to the Store bundle and the RC window stayed masked in screenshots while reporting success, until the Store package was removed. Screenshots do not interrupt a recording — that hypothesis was raised and disproved by running a 90 s capture with none taken and then taking one mid-capture with the helper surviving. |
| 2026-08-23 | installed v1.10.0-rc.3 (Developer ID, unmodified) run with OPENSCREEN_SCK_CAPTURE_EXE pointed at a helper built from this branch |
macOS 26.6.2 (25G83), M1, 1728×1117 @ 2× | Pass — fixes a blocker | Window capture section only. Before: selecting any window in the source picker kills the helper the instant start() builds its filter — Assertion failed: (did_initialize), function CGS_REQUIRE_INIT, file CGInitialization.c, line 44, SIGABRT, -[SCContentFilter initWithDesktopIndependentWindow:] → SLSGetDisplaysWithRect. 6/6 attempts on the shipped rc.3, no file, no error surfaced in the UI (the HUD returns to idle as if nothing happened). Display capture is unaffected and always worked, which is why this went unnoticed: the two paths diverge at makeCaptureTarget, and only the window branch resolves a rect through SkyLight. After: record → 25.2s → stop → editor opened on the take, recording-1787475175449.mp4 12,559,123 bytes / 25.18s / 2674×1684, the MP4 and both sidecars written (.cursor.json, .session.json), one project minted, zero crash reports. Helper-level A/B on an identical request JSON isolates the change: shipped signed helper → assertion, no file; this branch's helper → recording-started/recording-stopped, 4.49s / 1336×840 decodable MP4. NOT covered: webcam PiP, microphone, system audio (all off for these runs), export, GIF, AI/transcript sections, Windows, Linux. Not covered by unit tests either — Package.swift scopes the Swift test target to what runs without a screen, a display server or a TCC grant, and this crash needs all three. |
| 2026-09-03 | dev build, worktree github-issue-385-38d731 @ 437e4bd2 (main). Natives rebuilt from this tree: wgc-capture.exe and compositor_view.node both dated 2026-09-03, verified by string probe ([segmentation] HIT / control OPENSCREEN_EXPORT_ENCODER HIT; helper control encoder-selection HIT). ONNX Runtime 1.27.1 staged. |
Windows 11 26200, 1920×1080 @ 100% | Partial — no defect, one validation unresolved | Post-1.10.0 slice. Passed: hardware H.264 is the Windows default (videoEncoderRuntime: "hardware" on the plain path); recording AAC valid at 48 kHz; camera background Original/Cutout/Blur/Custom all render in preview and in the exported frames; the control is correctly hidden when onnxruntime.dll is removed and returns when it is restored; caption anchor presets Top/Left move the band and relabel their sliders, and the exported plate's left edge measured x=192/1920 = 10.00% against insetX: 10; crop field down-arrow steps one source pixel (100 → 99.9479) and the preview box carries the source aspect; a zoom lying entirely inside a trim fires in the preview with the playhead parked on it (zoom 19.956–22.927 inside trim 19.931–23.596, checked in the saved project, not by eye); export progress reached 100% with a 1.5× speed region present and reported a real frame total; exported audio and video durations agree within one frame at the export's 30 fps — 41.200 s audio vs 41.167 s video, a 33 ms gap that is exactly 1/30; editor-window.json persists bounds and a hand-planted zero-size/non-boolean state is rejected on relaunch; Regenerate-as lists all 101 whisper language entries; regions and settings survive a restart. Skipped: Ctrl/Shift+scroll zoom and pan — the computer-use scroll action does not carry a modifier, so the fix that moved the wheel listener to the whole pane could not be exercised either over the ruler or over the lanes; tray context menu (Save Diagnostics, Update Settings) — the desktop shell is granted at tier click, which blocks right-click; Update Settings in general — dev build, app.isPackaged is false so the submenu is correctly absent; the illegal-AAC-rate snap end-to-end — no 96 kHz device on this machine, covered instead by the helper's own audio_sample_utils_test MF probes, which passed at build time. Note, not a defect: Escape does not reach the app through this driver (it failed to close the Edit Clip dialog too), so any Escape-based check here is untestable rather than failing — same conclusion as the rc.3 retraction. Unresolved — do not read this row as clearing it: the exported duration ran 0.48 s (≈13 frames at 30 fps) under the trim/speed arithmetic — 1236 frames against a predicted 1249, from source 46.300 s minus a 3.665 s trim minus 0.990 s saved by a 2.971 s span at 1.5×. Ruled out: the source being shorter than its container claims (-count_frames gives exactly 2778 = 46.300 s × 60 fps). Not reproduced under controlled conditions, and not attributable to any post-1.10.0 change — the trim/speed length arithmetic predates them — so it is logged as an open question rather than a defect against this slice. The silence check in the same run is also weaker than it looks: that recording was ambient-mic screen capture, i.e. mostly silence, so it could not have distinguished an atempo hole from the source's own quiet. Both are why the two checks above now demand controlled audio and frame-level arithmetic. |
| 2026-09-04 | signed follow-up candidate 256ec082 on feat/fix-av1-webm-clip-switch, packaged from a raw-Git-blob build tree (Openscreen.exe 32469490…3E6E, compositor_view.node 090B606C…AA3A) |
Windows 11 Pro Insider Preview 26340, 1707×1067 logical | Partial — no defect | AV1 clip-switch section only, driven by real computer-use against the packaged app. Three-clip synthetic project: H.264 blue 0–1.50 s, malformed AV1 1.50–1.54 s (AV1CodecConfigurationRecord version 127, avg_frame_rate=0/0, unknown-sized EBML, and a nonzero source start of 0.400 s), H.264 green from 1.54 s. Real pointer control throughout — hover then click on the click-through HUD's Open Studio, ruler clicks and the previous-clip control to seek, the play control to run. Seeking into the malformed AV1 clip renders that clip's own frame rather than a neighbour's: the preview turned red at 0:01.5, against blue at 0:01.2 and green at 0:02.3. Read those colours as the three fixtures' own identities — blue (0,0,255), AV1 (254,0,0), green (0,255,1) at source 0.400 s, measured from the files, not sampled off the screen: no screenshot from this session was retained, so the on-screen colours are a qualitative reading and the identification rests on the clip-switch log below. Playback from 1.2 s crossed both boundaries and reached the third clip (green at 0:03.4) before stopping at the end of the timeline. The app's own stderr binds that traversal to the fixtures in order (h264-before-blue.mp4 → legacy-av1-v127-zero-rate.webm → h264-after-green.mp4) and shows the malformed traits being exercised live — 38 × Unknown version 127 of AV1CodecConfigurationRecord found! plus Unknown-sized element at 0x3cd inside parent with finite size — with no panic, fatal or abort line, the process still logging and still accepting UI input at the end of the pass, and closing Studio from the UI ending the app. (Liveness was polled during the run, but those polls were not persisted; what is checkable afterwards is the log and the fact that every later click landed.) The AV1 clip is 40 ms, narrower than the 0.1 s playhead readout, so the AV1 frame is identified by clip-boundary navigation and by the logged clip switch, not by the readout alone. The fixtures are synthetic, not a reporter-supplied file. No Playwright ran in this pass — the earlier Playwright run against the same package is supplemental and is not manual evidence. Exit code not captured: the waiter script threw before recording it, so "normal exit" rests on the UI close, the process disappearing and the absence of any Windows error event for the app. Not run: capture/HUD recording flow, webcam, microphone, system audio, export, GIF, transcription, editor operations beyond seek and play, macOS and Linux. Environment note for the next run: request_access resolves only Start-Menu-registered applications and refreshes that index on a delay, so an unpacked build needs a temporary shortcut and a retry before it can be driven at all; and re-fronting through that shortcut launches a duplicate instance on the default profile instead of raising the isolated one. |
| 2026-09-04 | three packaged arms differing in exactly one file: hashing all 181 packaged files shows resources/electron/native/bin/win32-x64/compositor_view.node is the only path that differs. PR base 9f64ae6d built for this run as CDA2618F…164B; an older pre-fix addon from an unrelated branch build (18B44DFB…0CDA, 2026-09-02); this branch's 090B606C…AA3A. Shared Openscreen.exe 32469490…3E6E and project B915113B…3E93 |
Windows 11 Pro Insider Preview 26340.9233 | Pass — reproduces and clears the synthetic repro of issue 554 | Scripted, not a manual OS-input pass, and each arm ran once. All three arms were driven by the same Playwright script against the real packaged Electron process, so this row is a controlled A/B and does not replace the computer-use row above. The base build crashes where this branch's build does not. Playing H.264 → malformed AV1 → H.264, the PR-base arm terminated with exit code 3221225477 (0xC0000005, access violation) without reaching the third clip. The older pre-fix addon advanced its playhead to 1.907 s but still showed the first clip's blue [0,15,255,255] at 1.915 s, then terminated with the same 0xC0000005 before a screenshot could be taken. This branch's arm crossed at 1.908 s, rendered the third clip's green [0,216,0,255] (retained as a screenshot), and closed with exit code 0. All three arms logged Unknown version 127 of AV1CodecConfigurationRecord and reached the AV1 clip's cursor sidecar, each recording loaded=FAIL; those [live] lines are native-compositor output, so the addon was live in every arm. Limits: the media is the synthetic fixture matching the reported AV1 configuration, not the reporter's unavailable recording, so this clears the reproduction rather than the reporter's file; interaction was script-injected rather than real OS input; the host is build 26340, not the reporter's 26200; n=1 per arm against a memory-corruption crash; and the post-playback canvas probe reads a different, smaller canvas element than the pre-playback one, so only the green frame has a screenshot behind it. |
| 2026-09-05 | Windows single-frame decoder ownership follow-up; source SHA-256 FAF87561...87117, bundled addon 94bc16d7...43a2c, app.asar 8063a377...96f04 |
Windows 11 build 26340; packaged Electron 41.2.1 | Scoped pass: nonzero AV1 seek and cross-clip playback | Fresh package, unique profile, no addon override; all 15 packaged native files hash-match staging (the existing packaging filter excludes standalone ffmpeg.exe). Native keyboard opened Studio after HUD pointer targeting failed; Studio seek/play/close used native input. AV1 at timeline 1.500 s/source 0.400 s rendered red [255,24,0,255] with a retained screenshot. During native playback from the first H.264 clip, read-only CDP samples observed blue followed by third-clip green [0,216,0,255] at 1.571 s; native logs confirm the malformed AV1 and following H.264 sources. PID 35472 exited 0 through window Close. An earlier sandboxed launch failed during Chromium GPU startup and is excluded from this pass. Limits: synthetic media, two native playback attempts in one successful session, no repeated stability or reporter-file claim; capture/webcam/tray/export not run. Exact-endpoint 4.540 s and one subsequent seek showed first-clip blue after native free-run wrapped: unchanged clip-identity synchronization paths, outside this resource/preflight follow-up; endpoint correctness is not claimed. Historical A/B rows remain separate. |
| 2026-09-11 | dev build (npm run dev) of fix/582-antialias-decimation, base fbe461e9, final commit 8d97f799. The 96 and 192 kHz A/B ran the helper from a clean npm run build:native:win of the tree 8d97f799 carries; the 384 kHz A/B and the 48 kHz app run used helpers built with the same command from earlier revisions of this branch, whose helper sources differ from 8d97f799 only in comments and whitespace. The A/B's pre-fix helper was built from fbe461e9 in a separate worktree. No binary hash is given; build from the commit SHA. |
Windows 11 Pro 26340, 2560×1600 @ 150% (as read after the runs) | Scoped pass — no defect; the 96, 192 and 384 kHz (factors 2, 4 and 8) A/B ran and the alias is gone | Anti-alias decimation (#582) slice. 48 kHz path, in the app, with real OS input against the click-through HUD: source picker → Entire screen → Share; system audio on (helper launched with system: { enabled: true }); record with countdown; pause at 00:32 (timer yellow, control switches to resume) and resume (red at 00:36); stop → editor opens with the take; playback of the saved recording runs, playhead advancing through 0:04.6 and 0:11.6. Recorded stream aac, 48000 Hz, 2 ch, 192000 bit/s, 38.38 s. The endpoint ran at 48 kHz, so nothing decimated on this pass: the 1 kHz reference measured -26.97 dBFS, and the 4 kHz and 12 kHz alias bins were empty (-128 and -125 dBFS). The 20 kHz probe component is absent (-136 dBFS), and an AAC encode alone removes it too: the same tone encoded to AAC 192 kbps by ffmpeg, with no capture involved, reads -131 dBFS at 20 kHz against -13.99 dBFS at 1 kHz — so a 20 kHz probe cannot test the capture path. Decimation path, A/B'd against the pre-fix build at three rates: a USB DAC was made the default render endpoint and its shared-mode format set to 96000, 192000 and 384000 Hz in turn, so loopback reports that rate, the encoder target stays 48000, and the factor is 2, 4 and 8; the helper's own audio-format event confirmed the rate in every run. The helper was driven the way the app drives it (config JSON argument, stop on stdin), not through the app UI. Fold-bin readings, before → after, for 96 / 192 / 384 kHz: a 36 kHz tone (folds onto 12 kHz) -22.25 → at most -115.32, -23.90 → at most -138.02, -24.28 → at most -111.54 dBFS; a 42 kHz tone (folds onto 6 kHz) -28.20 → at most -137.14, -30.40 → at most -135.09, -30.96 → at most -117.25 dBFS. The after readings bound what the recordings hold and sit near each recording's floor; they are not the filter's leakage, which the design puts at -123 to -155 dBFS at these points. The 1 kHz reference reads -13.98 or -13.99 dBFS in every recording; each pre-fix recording shows its alias only in its own fold bin; every pre-fix reading matches the box average's frequency response to within 0.08 dB; the 384 kHz runs were repeated and agree to 0.01 dB. AAC track length is not used as evidence about the filter's delay or about frame accounting (tracks are whole 1024-sample AAC frames, and repeat runs of the same helper differ by up to six); that output frame counts do not drift is asserted by the helper's suite. Beyond these recordings the behaviour is covered by that suite (95 assertions, including a real-AudioMixer case) plus eleven mutation counter-proofs and a control that must stay green. Check the Advanced tab's format list, and every endpoint, before concluding a machine cannot run this — a device's current format is not its capability. Not run: microphone, webcam, tray menu, export, and every non-Windows platform. A/V sync was not eyeballed: against the output timeline this change puts a decimated stream 39 + 1/factor output frames late (0.81–0.82 ms at a 48 kHz output; the filter's own group delay is 40·factor source frames), below what watching can resolve, so it is asserted numerically in the suite instead. |
| 2026-09-13 | 6862a380 + microphone USB-label resolver patch; rebuilt release helpers | macOS 26.6.2, Apple Silicon, Shure MV7 | Partial | Real desktop HUD: selected explicit Shure MV7 (14ed:1012), microphone on, system audio/camera off; started/stopped capture and reached saved editor (16.1 s). Logs confirm the rebuilt helper path and decorated label. Raw AAC is non-silent (mean -37.4 dBFS, peak -13.3 dBFS). Separately, the resolver matched the decorated label to the real AVFoundation MV7 ID. Default input was also MV7, so audio alone does not prove non-default routing. Listening, mixed/system audio, export, non-default-input recording and other platforms were not run; this is not full acceptance. |
| 2026-09-15 | dev build (npm run dev), worktree e2e-macos-pr-652-b93321 on pr/509-hud-session-prefs: main pass on 8fa0740a (PR #652 head before the scope narrowing), rescope smoke on ac36aab4 | macOS 26.5, M1 | Pass — 1 defect found, fixed on the branch before merge | Settings-persistence slice for PR #652, driven with real OS mouse/keyboard. Capture prefs persist across relaunches from both the HUD and the Rec stage — display source, system audio, mic toggle, cursor mode, auto-zoom — and recording-settings.json matched the UI after every restart (final smoke state: mic on, system audio on, editable-overlay cursor, autoZoom on, screen:1:0). A window source is restored after restart; when that window no longer exists, refocusing the HUD falls back cleanly to "Screen". Re-enumeration on HUD focus: no flicker, no permission prompt, no errors. New-project appearance defaults applied without touching existing projects (on 8fa0740a, before the narrowing dropped them). Defect: the HUD stop button did nothing — select source → record → stop pill left the take recording with no log line. Root cause: in startWhenDevicesReady, when every device is ready on the first check the async body never reaches an await, so its finally clears startWhenDevicesReadyInFlight before startWhenDevicesReadyInFlight.current = pending runs; the ref keeps a settled promise forever and every later stop (or countdown cancel) returns it. The existing tests all make the gate wait once, so none caught it. Reported on PR #652; fixed on the branch in e159ced0 (merged to main as 1df32fed). The fix was not re-verified in a dedicated run — the post-narrowing smoke on ac36aab4 ("drop the Settings dialog") confirmed prefs still persist and an existing project still opens, nothing more. Not covered: explicit mic/camera device restore and preview stop on reset (host has no camera, mic is default-only), the full capture-to-export flow, export, transcript/AI sections, Windows/Linux. |
| 2026-09-15 | dev build (npm run dev), worktree editor-presets on feat/editor-presets (PR #675) | macOS 26.5, M1 | Pass — no defect | Style-presets header slice, driven with real OS mouse input. Confirmed the Presets trigger is in the editor header between the Media/Edit/Rec switcher and theme control, and is absent from the Composition pane header. Opened the menu from the new location and confirmed the built-in OpenScreen preset plus the empty user-preset state. Created E2E Header Smoke; the success toast appeared and the resulting .openscreenpreset file contained format/version metadata and appearance-only state. Applied the built-in preset and observed Applied “OpenScreen”; renamed the user preset to E2E Header Renamed and verified the file rename on disk; updated it from the current project and observed Preset updated; deleted it through the confirmation UI and verified the menu returned to No saved presets yet and the preset directory contained no files. Not covered: capture, media import, preview/export, transcript/AI sections, Windows/Linux, or preset portability between machines. |
| 2026-09-16 | worktree trim-modifiers-visibility-771405 on fix/privacy-blur-follows-zoom @ cd927180; compositor_view.node rebuilt from this tree by npm run build:native:compositor (2026-09-16 10:26, contains the new privacy_mask shader comment), ffmpeg DLLs hash-identical to the vendored SDK; renderer rebuilt with npm run build-vite | Windows 11 26200, NVENC export | Partial — no defect | Privacy blur under zoom and 3D tilt, through the app's real export (electron . export <project> -o <mp4>). Source: synthetic 1920×1080 clip with a red box at 55–70 % on both axes. Eight legacy projects (rest, 2.2× zoom centred, 2.2× zoom at 0.75/0.75, 2.2× zoom + iso), each exported without and with a white mosaic blur annotation over the box; frame at 3 s. Red pixels still visible: rest 0 (clear 42 777), centred 0 (207 933), off-centre 0 (207 932), iso 0 (140 398); the masked iso frame shows the mask following the tilted box. Before the fix the same geometry leaked 89–100 % of the box (D3D11 pixel test privacy_blur_under_zoom.rs). Not covered: the GUI editor preview (the HUD rendered as a black rectangle in computer-use screenshots, so the pass went through the CLI export, which shares compose_frame with the preview), oval and Gaussian styles, motion-blurred zoom ramps (unit-tested only), macOS and Linux rendering (Linux lib tests pass under WSL/lavapipe; Metal only compiles on macOS CI). |
| 2026-09-16 | worktree wf_62afabe4-4fb-7 on feat/3d-depth-of-field @ 7587de8e + the macOS golden and bench commits; compositor_view.node rebuilt from this tree by npm run build:native:compositor (string probe DOF_MAX_LOD HIT, control OPENSCREEN_EXPORT_ENCODER HIT), ffmpeg DLLs hash-identical to the vendored SDK; renderer rebuilt with npm run build-vite | Windows 11 26200, NVENC export | Partial — no defect | Depth of field on a tilted zoom, through the app's real export (electron . export <project> -o <mp4>). Source: synthetic 1920×1080 text-and-grid clip. Three legacy projects: iso zoom with focus at 0.92/0.08 and no depthOfField key (the default must apply), the same with depthOfField: false, and a flat zoom. Frame at 3 s: with the default the far (bottom-left) half of the tilted screen is visibly defocused and the near half stays sharp; with false the whole plane is sharp; the flat project renders a flat, sharp screen. Export wall time was the same with and without the effect (2.22–2.36 s over two warm runs each). Not covered: the GUI editor. Computer-use access to electron.exe was declined for this run, so the toggle on, the toggle off and the disabled state on a project without a tilt were checked only by the jsdom test depthOfFieldToggle.test.tsx. The preview cost was measured by the A/B harness instead (tilted_depth_of_field.rs, preview shape at 1280×720 + readback_direct: +0.03 ms/frame on hardware, +2.5 ms/frame on WARP). macOS and Linux rendering were not run here (the macOS golden runs on macOS CI; the Linux golden is opt-in under WSL). |
| 2026-09-24 | installed v1.13.0-rc.4 — CI-built DMG Openscreen-macOS-Apple-Silicon-1.13.0-rc.4.dmg from the GitHub Release (tag 187e0d8d), Developer ID signed + notarized (spctl: accepted, Notarized Developer ID), About reports 1.13.0-rc.4 (dmg); native payload one uniformly stamped set (all darwin-arm64 files 2026-09-22 10:05) | macOS 26.5 (25F71), M1 Mac mini, one 3840×2160 display; no camera and no microphone attached | Partial — 8 defects, none blocking in what ran | Full-file attempt with real OS mouse/keyboard; everything not run is listed. Passed: launch/HUD (layout toggle, drag follows pointer and stays dropped, language menu, minimize → tray refocus, close/relaunch, no orphan processes after Quit); source picker (Screens/Windows tabs); record with countdown, pause freezes timer, resume, restart resets, cancel opens no editor; stop from HUD and from tray Stop Recording (tray icon turns red while recording); recording 3840×2160 H.264 + AAC 48 kHz filled edge to edge (measured: 0 dark px on all four borders); system audio captured (−10.9 dB mean); relaunched without the protection flag → [content-protection] OFF … macOS 26.5.0 logged, HUD visible on screen and absent from the recorded frame. Editor: rename, Media/Edit/Rec modes, chat panel, play/pause, seek bar + ruler seek, trim edges + playback skips the trim, zoom levels/3D camera (Off, 3 fixed angles, orbit) in preview, auto zooms from clicks, speed presets + custom 2.5× + 150 → "can't go higher than 100×" toast, text annotation (text/colour/background/typewriter), arrow (direction/stroke), blur (mosaic/gaussian/oval), delete + ⌘Z/⌘⇧Z, second clip by drag from Media, reorder (modifiers stay anchored to their clip, checked in the document), Edit Clip crop (↓ on W = 99.97395 = one px of 3840, partial "8" stays editable, 80 % crop reframes), clip delete leaves other clip's modifiers. Transcript: local Whisper on whispercpp-metal (4.5× RT), "no data leaves the device" notice, word click seeks, cue highlight, Backspace cuts a word (trim created), double-click correction propagates to captions and to the translation, typing between words is inert in the packaged build; captions on/off, Top/Left anchor with relabelled sliders, translation FR via provider then delete → original back. Chat (DeepSeek): 2-op turn applied and undone/redone as one unit, "cut the silences" explained instead of guessing, rewind cancel leaves everything, rewind confirm rolls back. Imported audio: pill named after the file, gain/fades/mute/loop controls, exported gain exactly −4.0 dB, fades at the track's own edges (1.45 s ramps), loop fills to the end with no seam, mute → track absent (noise floor), recording audio unchanged under the bed, track not time-stretched by a 2× region and cut at the last frame without pulling the fade forward, still audible in an export after an app restart. Export: MP4/GIF options + dimensions, progress reaches 100 %, frame math exact (take 2: 1086 − 55.5 trim − 18.5 speed = 1012.1 expected, 1013 written; take 1: 1032.6 expected, 1032 written; audio = video duration in both at 30 fps), no silence hole at speed boundaries (silencedetect, the one gap maps to a pause already in the source), caption plate left edge x = 192 for insetX 10 and top edge y = 16 for 1.5 % (measured on PPM), annotation/captions do not follow a zoom, 3D tilt + text + oval blur + cursor all in exported frames; GIF cancel returns to options with no partial file, full GIF 1280×720, 508 frames, infinite loop, opens in Preview; MP4 opens in QuickTime. Settings: background image/colour/gradient, padding/roundness/shadow, cursor hide + style, light theme (gradient picker + dialogs follow), French UI incl. app menu and wordmark menu, custom shortcut (T→Y works, restored), ⌘S/⌘O dialog. App menu About / Save Diagnostics (valid JSON bundle) / Check for Updates → "up to date (1.13.0-rc.4)"; same from the HUD panel (which shows version, "No microphone found", "No camera found"); tray Update Settings has the 3 modes and the pick survives a restart; editor bounds (resize, maximize, un-maximize persisted; zero-size + maximized:"yes" file → sane default window). Defects: (1) Preview only — a text annotation with an animation renders inconsistently during playback in the native preview (blank, partial "R", or complete across repeated plays of the same span) while paused seeks and the export are correct; related: the first paused frame after creating an annotation or reopening a project is sometimes stale until the next seek. (2) Clicking the editor behind the Export modal while the native Save Exported Video panel is up sends that panel behind the editor: invisible, modal stuck on "Starting…", its Cancel inert — recovered only by pressing the panel's Cancel through Accessibility; reproduced twice. (3) HUD dialogs (Check for Updates from the HUD panel) grey a ~900×700 area of the desktop — already fixed on main by #733, not in rc.4: cherry-pick candidate. (4) GIF delay is truncated, not rounded (crates/compositor/src/gif_export.rs:231, 100 / fps): 15 fps → 6 cs → plays at 16.7 fps, 30.48 s for a 33.87 s programme (+11 %, not the ~4 % the comment allows); 30 fps has the same error. (5) Hovering a Backspace-cut transcript word shows no Restore button (undo and the trim inspector still restore). (6) Whisper appends a hallucinated repeat of the whole transcript over the trailing ~8 s of silence, stacked at 34.4 s. (7) The "frame-stepped and muted" hint (speed.previewFrameSteppingHint) is rendered nowhere at 20×. (8) Cosmetic: the shortcuts dialog lists Undo/Redo as Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y on macOS. Checklist drift, not defects: no loop or fullscreen transport control exists (transport.loop key unused); blur has no intensity/block-size control (fixed 12/12) and freehand only for legacy docs; projects autosave, so the unsaved indicator and Save/Discard/Cancel prompt never appear; the app menu puts Save Diagnostics between About and Check for Updates. Skipped — no hardware: microphone (mic-only, mic+system balance, HUD mic prompt), webcam PiP/layouts/shapes/full-camera/shrink-on-zoom, AI camera background (no webcam track), voice-over, second display at another scale factor; single Space: record while switching Spaces; driver limits: Ctrl/Shift/middle-mouse timeline zoom/pan (modifier+scroll never lands), Esc on menus; not run: chat history/rename/delete/new-conversation switch, compact context, copy message, API-key entry and device-flow panels, auto-enhance AI path, update download/install/restart flows (no newer release), old-project migration and legacy caption annotations, region merge/repel/cross-boundary split, clip copy/paste, transcript silence-span trim, re-transcription, word insertion (dev-only), notes window exclusion, Intel/Store/Windows/Linux blocks. Main-process logs clean (no error lines) across both launches; defect screenshots kept locally. |
| 2026-09-24 | installed v1.13.0-rc.6 — CI-built DMG Openscreen-macOS-Apple-Silicon-1.13.0-rc.6.dmg from the GitHub Release (tag 6eb86b17, build run 35990025043), Developer ID signed + notarized (spctl: Notarized Developer ID); native payload one uniformly stamped set (34 files, 2026-09-24 13:10). rc.6 = rc.5 (#733 backport) + cherry-picks of #748, #749, #750 (2 commits), #719 (2 commits), #666 | macOS 26.5 (25F71), M1 Mac mini, one 3840×2160 display; no camera, no microphone | Pass — risk-scoped slice, no defect | Scoped to what rc.5→rc.6 changed, not a full-file run (the full pass is the rc.4 row). Real OS mouse/keyboard; every level, duration and frame claim measured on exported files. #733 HUD dialogs: HUD device-settings → Check for Updates shows "up to date (1.13.0-rc.6)" with no grey rectangle over the desktop (rc.4: ~900×700 greyed). #743 file panels (widest blast radius — every save/open dialog): Export MP4 → Save Exported Video is now a sheet on the editor; three clicks on the editor behind the modal leave it attached and visible (rc.4: panel fell behind, modal stuck on "Starting…"); Cancel returns the modal to its options. Open Project → Browse files… and the audio-import picker (m) also open as sheets on the editor and complete. #744 GIF timing: GUI GIF export 15 fps / Medium: 517 frames, delays 7/6 cs alternating (345×7 + 172×6), playback 34.47 s = frames/15 exactly, against 34.40 s for the MP4 of the same project (rc.4: 30.48 s for a 33.87 s programme); CLI GIF 15 fps: 543 frames, 36.20 s = 543/15. #747: Keyboard Shortcuts → Fixed shows Undo "⌘ + Z" and Redo "⌘ + ⇧ + Z / ⌘ + Y". #719 CLI export of v2 projects: three hand-written v2 projects over a real take — with cropRegion {0.25,0.25,0.5,0.5} the CLI export on rc.4 ignored the crop (full frame), on rc.6 it renders the centre crop; cursorSize: 6 renders a cursor twice the default (3.0) size, compared frame-for-frame against the same project without the key; an out-of-range crop {-0.2,0.5,0.6,0.8} exports without error (clamped); the v2 crop project also opens in the GUI with the crop applied. #666 imported-audio gain: the Output level slider now goes well below −12 dB (set −37.5 and −39.0 dB); exported level of the 1 kHz bed by single-bin lock-in over 11–13 s: −31.1 dBFS at −4 dB (rc.4) → −65.9 dBFS at −39 dB (rc.6), Δ 34.8 dB for 35 dB requested, muted floor −96.2 dBFS. Smoke: record from the editor's Rec stage (tray icon red, stop from tray Stop Recording) → editor opens on the 13.23 s 3840×2160 H.264 + AAC take with an auto zoom → add a trim (0.68 s) → Export MP4 1080p30: 12.60 s (13.23 − 0.68 = 12.55 expected, within 2 frames). Main-process log: no error lines. Not covered: everything outside this slice (see the rc.4 row); #743 on Windows/Linux (the helper keeps owning the HUD there — unit-tested only); the native-bridge project save path was exercised only through Open; webcam/mic still untestable on this machine. Open, not in rc.6: #742 (animated annotation during playback, preview only), #745 (not reproduced), #729 (Whisper hallucination on trailing silence). |
| 2026-09-24 | claude/zoom-speed-adjustment-fb8abd, the zoom-pace PR for #683, on d951e532; compositor_view.node rebuilt from its first commit cc88c423 (the later motion-blur fix at speed boundaries does not touch these measurements, taken with motion blur off) by npm run build:native:compositor (2026-09-24 22:17), ffmpeg DLLs hash-identical to the vendored SDK; renderer rebuilt with npm run build-vite | Windows 11 26200 | Partial — no defect | Zoom pace and speed regions, through the app's real export (electron . export <project> -o <mp4>). Source: synthetic 1920×1080 clip, a 160 px white square centred. Two legacy projects: plain at 1× with a 1.25× zoom on [2 s, 3 s] and a 5× zoom on [6 s, 8 s]; sped with the whole clip at 4× and the same zooms at 4× the source times. Zoom scale measured on every frame of the 60 fps exports (area-weighted width of the square). New addon: plain and sped match frame for frame within a maximum scale difference of 0.0001; 1.25× moves 2→98 % in 0.53 s, peak 0.9 e-folds/s; 5× in 1.12 s, peak 2.8 e-folds/s; each zoom reaches 98 % before its region starts and leaves when it ends. Same app build pointed at the 2026-09-13 addon (OPENSCREEN_COMPOSITOR_VIEW_NODE): 5× peaks at 13 e-folds/s at 1× and 31 at 4×, where the whole zoom-in takes 0.18 s. Not covered: the GUI editor preview (same plan_frame as the export, not driven), Full Camera under a speed region and chained zooms (unit-tested only), macOS and Linux rendering. |
| 2026-09-25 | claude/ratio-auto-screen-studio-df6b2e (Auto format), tree of ace7af33 + 972f8cf3 before the default flip; the test projects state auto explicitly, so the flip does not change them. compositor_view.node not rebuilt (no Rust change in the PR): the 2026-09-24 22:17 build from zoom-speed-adjustment-fb8abd, its whole bin/win32-x64 directory copied aside and loaded through OPENSCREEN_COMPOSITOR_VIEW_NODE; renderer rebuilt with npm run build-vite | Windows 11 26200, NVENC export | Partial — no defect | Auto format, preview geometry and real export. Preview: the renderer in browser mode (vite-dev, ?windowType=editor), 4:3 fixture tests/fixtures/sample.webm with website/static/video/webcam.mp4 as camera, padding 50, geometry read from the DOM. Fixed 16:9 + top / bottom left 265 px empty per side against 42 px on top; Auto gave a portrait 259×418 frame, square 207×207 camera, 26 px on all four sides. Side by side, padding 0 → 100: frame 2.36 → 1.82, border 0 → 84 px, camera square throughout. Fixed 16:9 side by side gave a 4:5 camera and the block at 80 % of the width, which is the old formula's answer (computed, not re-measured on the old build). Export: electron . export <project> -o <mp4> --quality source, synthetic 1280×720 blue screen and 640×480 green camera, wallpaper #303030, padding 50, roundness and shadow 0, frame at 1.5 s parsed as PPM. Top / bottom 858×1258, camera 687×686, margins 86/86/85/86; side by side 1240×506, camera 405×406, margins 50 on all sides; PiP 1194×736, screen margins 74/74/74/76. Not run: the real Electron editor window (preview checked in browser mode only), the native live preview, the default flip on a fresh recording (unit tests only), a non-English Format menu, macOS and Linux. |
| 2026-09-25 | worktree zoom-overflow-layouts-698eb0, claude/zoom-overflow-layouts-698eb0 on ff3c3f51 + the slot-mask change; compositor_view.node rebuilt from this tree (cargo build -p compositor-view-napi --release), baseline addon built the same way from ff3c3f51; ffmpeg DLLs hash-identical to the vendored SDK; renderer rebuilt with npm run build-vite | Windows 11 26200, NVENC export | Partial — no defect | The block layouts' screen slot masks the zoom and the 3D (overflow hidden), through the app's real export (electron . export <project> -o <mp4>). Source: synthetic 1920×1080 testsrc2 screen and 1280×720 bars webcam, 4 s. Legacy 16:9 projects, padding 50 %, a depth-4 (2.2×) zoom at focus (0.3, 0.6) on [0.5 s, 3.5 s], frame at 2 s. dual-frame: the baseline addon spills the zoomed screen under the camera and off the frame; the new one crops it to its rounded slot, camera and gap untouched. dual-frame + iso: the footage tilts as in any layout and the slot, which stays upright, crops it; where the tilted footage leaves the slot, the wallpaper shows and the shadow follows the visible part. picture-in-picture: the whole export is bit-identical between the two addons (PSNR inf on all 240 frames). D3D11 frames through live::Player, the preview engine, also checked vertical-stack, the right preset and the follow-cursor camera. Not covered: the GUI editor preview (same plan_frame, not driven), a window or device frame in a block layout (left unmasked), privacy blur and click impacts in a slot (unit-tested only), macOS and Linux rendering (WGSL validated with naga, Metal not compiled). |
| 2026-09-26 | worktree motion-blur-padding-issue-11befb, claude/motion-blur-padding-issue-11befb on 1a669c22 (#794); compositor_view.node built from this tree (cargo build -p compositor-view-napi --release), baseline addon built the same way from 218bdbb0 (main); ffmpeg DLLs hash-identical to the vendored SDK; renderer rebuilt with npm run build-vite, plus a local, uncommitted pass-through of editor.frame in normalizeProjectEditor (the v2 CLI path drops it) | Windows 11 26200, NVENC export | Partial — one defect found and fixed | Motion blur of the framed screen, through the app's real export (electron . export <project> -o <mp4>). Legacy 16:9 project: phone frame, padding 50, motion blur 1, shadow 0.6, a depth-3 zoom on [3 s, 8 s]; sources: synthetic 1920×1080 testsrc2, then solid red and solid green to isolate the footage (red/green difference). Static frames: only encoder noise between the two addons. Ramps (60 fps frames 132 and 501): the baseline keeps the shell's rim and bezel razor-sharp around a smeared picture; the new addon smears them with it (87–109k px of shell and shadow change that are never footage). Footage outside the union of the sharp apertures at n−1 and n, +2 px: 1,185 px on one frame at first (a ~20 % tint over the bezel in the four output corners, where the phone overflows the output), fixed in 1a669c22, then 0 px over all 55 moving frames. Not covered: the GUI editor preview (same compositor path, not driven), window chrome and laptop/monitor frames on this machine (window chrome covered by the Linux lavapipe test), macOS (Metal not compiled here). |
| 2026-09-26 | worktree motion-blur-padding-issue-11befb, branch claude/cli-export-documents on 218bdbb0 + this change; renderer rebuilt with npm run build-vite; compositor_view.node the one in the worktree's electron/native/bin/win32-x64 (loading, not rendering, was under test) | Windows 11 26200, NVENC export | Partial — no defect | openscreen export on a project saved by the editor, through the app's real CLI (electron . export <project> -o <mp4> --json). Before: a copy of a real schema-7 editor project (%APPDATA%/openscreen/projects/proj_741d2d06…, monitor frame, 2 zooms with a 3D camera, 2 trims, speed regions ×5 and ×2 in legacyEditor) failed at once with Project file is not a valid .openscreen project. After: exported in 34 s, 1920×1080 60 fps, 21.62 s against 21.58 s expected (38.25 s − 4.60 s of trims − 9.44 s and 2.63 s taken by the two speed regions); the frame at 2 s shows the zoomed, tilted screen inside the monitor bezel. info on the same copy: version 8 after the load-time upgrade, the recording found, 2 zooms, 2 trims (its speed count, read from legacyEditor.speedRegions, was fixed after this run and is covered by projectCommands.test.ts). pack refuses it with pack does not support projects saved by the editor yet. A legacy v2 project (synthetic 4 s testsrc2, one zoom) still exports: 4.00 s. The original project file was only copied, hash-identical afterwards. Not covered: pack for documents (refused, not implemented), captions (its runner still reads v2 only), a document whose media moved (relink is the editor's own, unit-tested there), macOS and Linux. |
| 2026-09-26 | worktree fix-3d-cameras-left-right-02603d, claude/fix-3d-cameras-left-right-02603d on cbf85dc7 + this change; compositor_view.node rebuilt from this tree by npm run build:native:compositor, the rest of bin/win32-x64 copied from the installed v1.13.0-rc.6 (ffmpeg DLLs hash-identical to the vendored SDK); renderer rebuilt with npm run build-vite. Reference: installed v1.13.0-rc.6 (= v1.13.0) through its own CLI | Windows 11 26200, NVENC export | Partial — no defect | 3D cameras against v1.13.0, openscreen export on both builds, same synthetic 14 s 1080p grid and cursor sidecar (three held positions), zoom depth 2, hideCursor, motion blur 0. During the hold (2.5, 3.5, 7.5, 11.5 s), left here and iso in v1.13.0 differ only in a ~50×65 px box around the cursor sprite (0.05–0.09 % of pixels > 24; the cursor size changed since); same for follow-cursor. iso here renders byte-identical to left. right is the mirror of left: screen edge 1–2 px apart, row by row. With motion blur 0.35 the orbit differs while the camera moves (mean abs diff 9, 9 % of pixels): the mode-8 trail added on 25/09, kept on purpose. Not covered: editor preview, GUI, macOS and Linux, zoom ramps (their pacing changed with #683). |
| 2026-09-26 | worktree openscreen-screen-mask-zoom-e21ce9, claude/openscreen-screen-mask-zoom-e21ce9 on 1d67754c (integration/demo-never-ugly) + the frame-container change; compositor_view.node rebuilt from this tree by npm run build:native:compositor, baseline addon and renderer built from 1d67754c in a scratch worktree; ffmpeg DLLs hash-identical to the vendored SDK; renderer rebuilt with vite build | Windows 11 26200, h264 export | Partial — no defect | Block layouts under a window or device frame: the frame is the fixed container, the footage zooms and tilts inside it, and the block lays the frame out beside the camera. Through the app's real export (electron . export <project> -o <mp4>). Source: synthetic 1920×1080 testsrc2 screen and 1280×720 mandelbrot webcam, 8 s. Legacy projects, default padding, a depth-4 zoom at focus (0.3, 0.6) on [1.5 s, 3.5 s] and a depth-3 iso zoom on [5.5 s, 7.8 s], frames at 0.2, 2.8 and 7.0 s. dual-frame (16:9) and vertical-stack (9:16) under window, laptop, phone and monitor. Baseline: the whole frame zooms and tilts over the camera, and at rest the laptop's deck and the monitor's stand run under it. New: the frame stays at rest, lined up with the camera on its body, deck and stand clear of it; the flat zoom and the iso tilt stay inside the aperture, over the device's black glass or the window chrome's fill, with square top corners under the title bar. picture-in-picture with and without a laptop, no-webcam with a laptop, and both block layouts without a frame: every decoded frame identical to the baseline (framemd5, 480 video frames plus audio). Not covered: the GUI editor preview (same plan_frame and computeCompositeLayout, not driven), follow-cursor inside a frame (unit-tested only), the Auto format with a frame in a block layout (its resting shape ignores the frame, so the camera is not exactly square there), macOS and Linux rendering (WGSL validated with naga, Metal not compiled). |
| 2026-09-27 | worktree camera-size-zoom-balance-7e14d7, claude/camera-size-zoom-balance-7e14d7 on 7aa00935 (integration/demo-never-ugly) + the camera size change; compositor_view.node built from this tree (cargo build -p compositor-view-napi --release), baseline addon built the same way with the old 1/zoom curve; renderer built with vite build for each side, the baseline with the old 10–35 bounds; rest of bin/win32-x64 copied from a 26/09 build, ffmpeg DLLs hash-identical to the vendored SDK | Windows 11 26200, NVENC export | Partial — no defect | Camera size range and shrink on zoom, through the app's real export (electron . export <project> -o <mp4>). Source: a real 1920×1080 recording with its 640×480 webcam; legacy 16:9 projects, picture-in-picture bottom right, shrink on zoom on, a depth-3 (1.8×) zoom on [4 s, 10 s]; frames at 2.5 s and 7 s, camera boxes measured by difference with a camera-less export. During the zoom: default 25 %, 150×113 → 210×158 px; minimum 10 → 15 %, 60×45 → 126×95; maximum 35 → 50 %, 210×158 → 420×315, each equal to the computed rect. At rest: 378 and 540 px wide at 35 and 50 %. Not covered: the GUI editor preview and the slider (unit-tested: min 15, max 50), the square camera, macOS and Linux rendering (same plan_frame, not compiled here). |