Every screen recorder follows your cursor. Dolly follows your focus — so it works when you are using the keyboard, which is most of a developer demo.
Local-only screen recording with an automatic camera. Windows, macOS and Linux, including Wayland. No account, no cloud, no telemetry. AGPL-3.0.
Status: in development. The signal model, the camera solver, capture, render and the editor are built; the accessibility layer works on Windows and is written but unverified on macOS and Linux. What is measured is measured — see docs/evidence/ — and what is not says so. See Roadmap.
Eight form fields tabbed through with the pointer parked in a corner. Every segment on the camera track is a zoom the accessibility tree asked for, and the inspector shows the working: 28px text, a 1080p export, so 1.6x. The frame being recorded is a mock — see tools/screenshots for what in these images is real and what is not — but the camera, the reasons and the timeline are the program's own output.
Press ⌘K. A command palette opens. Type a few characters, hit Enter. Tab through a settings form. Run something in a terminal.
The cursor never moved. Every automatic-zoom screen recorder — Screen Studio, Cap, FocuSee, ScreenArc — generates its camera movement from cursor position, so the camera never moved either. You get a flat, unzoomed recording of precisely the workflow that most needed zooming, because the text is small, the fields are 200 pixels wide, and your viewer is watching in a browser tab on a laptop.
That is not an edge case. It is most technical demos.
Every operating system maintains a semantic tree of the interface — what has focus, what kind of element it is, exactly where it sits, how tall its text is. Screen readers have used it for thirty years. It is called AXUIElement on macOS, UI Automation on Windows, AT-SPI on Linux.
No screen recorder uses it. Dolly does.
When you tab to the next field, Dolly knows the focus moved, that the new element is a text field, that its rectangle is (840, 412, 260, 32), and that the text inside it is 14 pixels tall. It frames that field. When a dialog opens, it frames the dialog on the frame it appeared. When you press ⌘K, it frames the palette.
No cursor required.
Once you know the text height, you can stop guessing at zoom. Ask why you zoom at all and the answer is always the same — because the text is too small to read at the export resolution. So compute it:
required_scale = min_legible_px ÷ (text_height_px × output_height ÷ source_height)
A 14px label on a 1440p display exported to 1080p needs 2.1×; on a 4K display it needs 3.1×. A 32px heading needs none at all — and Dolly correctly does nothing, and tells you why. Every zoom segment in the timeline carries its arithmetic:
focus: text field · 14px text · 1080p export · needs 2.1x
The camera stops being a stylistic dial and becomes a derived quantity with a stated purpose.
The accessibility tree marks secure fields explicitly — AXSecureTextField on
macOS, IsPassword on Windows, ROLE_PASSWORD_TEXT on Linux. So Dolly
redacts them automatically, before any scaling, by downsampling rather than
blurring so the information is genuinely gone rather than smeared.
No manual masking, no forgetting, no re-recording because a token was on frame 4,200.
The region is held for as long as the field is on screen rather than for as long as it has focus, and it is the union of where the field went while the page scrolled — a redaction that tracked focus would uncover the password the moment you tabbed away. The preview blurs; the export downsamples, and says so.
What it does not cover: a password echoed in a terminal, a key in a config file, a token in a DevTools panel. Those are not secure fields and Dolly cannot see them. Manual redaction exists for those. See PRIVACY.md.
The camera is the thesis; the rest is the work that makes a recording into a video, and most of it is Recordly's design ported into Go (CREDITS.md):
- Timeline editing. Cut a span and everything after it moves up. Split at
the playhead. Speed a clip from 0.25x to 8x — the frames are re-timed and the
audio goes through
atempo, so a sped-up section keeps its pitch. Mute one clip without losing its length. - Cursor polish. A spring between the recorded path and the drawn one, motion blur along the direction of travel, a dip on click, a tip in the direction of movement, and a loop-closing blend that eases the pointer back to where it started for an export meant to repeat.
- A webcam bubble. Nine positions or free placement, size, margin, roundness from square to circle, shadow, mirroring, and an option to keep it the same size relative to the content while the camera pushes in.
- A frame worth publishing. Backgrounds — colour, ramp, wallpaper or your own image — padding, corner radius, drop shadow, and a blur that applies to the background and never to the recording.
- MP4, WebM and GIF. A GIF gets the three controls that decide whether it is 3 MB or 30: frame rate, loop, and a height cap.
- Keyboard shortcuts you can rebind, with a sheet that reads from the same list the handler does, so it cannot describe a key that does something else.
The preview shows the exported frame — the background, the padding, the corner radius — rather than only the recording, so what you are setting is what you are looking at. The drop shadow is the one thing it does not draw, and the slider says so.
| Tool | Signal | Keyboard-driven demos | Zoom extent | Secure fields |
|---|---|---|---|---|
| Screen Studio | Cursor | Blind | Preset | Manual |
| Cap | Cursor | Blind | Preset | Manual blur |
| Recordly | Cursor activity | Blind | Preset depth, manual regions | None |
| FocuSee | Cursor clicks | Blind | Preset | None |
| OBS | None | N/A | Manual | None |
| Dolly | Focus tree + cursor | Works | Derived from legibility | Automatic |
Cap and Recordly are good tools and are better than Dolly at several things — more mature editors, instant sharing, captions, annotations, an extension marketplace, and real users. If you want a polished recorder this week and your demos are mouse-driven, use one of them.
Dolly's presentation layer is largely Recordly's, ported and credited: the clip and speed model, the cursor spring, the webcam overlay layout. That work did not need doing twice, and their licence invites the reuse. See CREDITS.md for what was taken and what changed on the way in.
The difference is the signal, not the polish. Three claims, in order of how novel each actually is:
- The camera is driven by focus and semantic structure, not cursor position. Nobody in this category reads the accessibility tree. It is the difference between working and not working for keyboard-driven interaction.
- Zoom extent is derived from text legibility rather than chosen from a preset. Novel here, and a defensible thesis.
- Automatic redaction of secure fields. Falls out of the same signal at near-zero cost.
Everything else — cross-platform, local-only, no account — is positioning, not novelty.
Dolly reads the operating system's accessibility tree. That means it could see the structure of every application on screen, and you are right to care about that.
- Element text is not stored, and there is nowhere to put it. The record Dolly writes has fields for role, bounds, text height and a secure flag. There is no field for content. A test asserts it.
- Keystrokes record that a key was pressed, never which one. The input event struct has a timestamp and no key-code field.
- The tree is read only while recording. The watcher starts on record and stops on stop. No daemon, no background reads.
- Nothing leaves the machine. No account, no server, no telemetry. Verify it: disconnect from the network and everything except the one-time FFmpeg download works identically. Enforced by an isolation test and an import-boundary check, not by a policy document.
docs/PRIVACY.md shows a real focus.json so you can see
exactly what is stored.
Dolly consumes three signal layers and degrades in a stated order, per application rather than per session — because coverage is an application property. A native app exposes a rich tree; an Electron app may expose little; a game exposes nothing.
| Available | Behaviour | What you are told |
|---|---|---|
| Focus + pointer + timing | Full fusion | Nothing. This is normal. |
| Focus only (Wayland) | Focus-driven camera, no drawn cursor | "Cursor tracking isn't available in this session. The camera follows keyboard focus." |
| Pointer + timing only | Cursor-driven, as competitors work | "This app doesn't expose its interface structure, so the camera follows the cursor." |
| Neither | Manual keyframes | "Automatic camera isn't available here. You can add zooms manually." |
The editor shows a coverage strip naming which application each part of the recording came from and how well the tree covered it. If one window behaved differently, the answer is already on screen.
AT-SPI is D-Bus, not display-server dependent, so it works under Wayland — where global cursor position is deliberately denied and no portal exists for it. The platform where the cursor approach fails completely is a platform where the focus approach still works. That is written up in docs/DISCOVERIES.md.
The record panel, photographed on a machine that has not downloaded FFmpeg yet
— which is why there are no audio devices and why it says so rather than
offering an empty list. The display, the permission state and the FFmpeg state
are whatever internal/capture, internal/perm and internal/deps reported
there.
Requires Go 1.25+, Node 20+, and the Wails v2 CLI.
go install github.com/wailsapp/wails/v2/cmd/wails@latest
wails dev # run with hot reload
wails build # produce a binary in build/binmain.go embeds frontend/dist, which is a build artifact and is not
committed, so a bare go build ./... needs the interface built first:
cd frontend && npm ci && npm run build # or: make frontendwails build and make build do this for you. CI writes a placeholder for the
jobs that only test Go.
FFmpeg (~111 MB) is fetched on first launch, or reused from an existing Lathe installation if you have one. Dolly does not implement a codec; capture and encoding are FFmpeg's job.
Tests:
go test ./... # unit, property, golden, isolation
go test -tags corpus ./test/... # corpus and render
go test -tags hardware ./test/... # real capture and accessibility
cd frontend && npm run build # includes the design-system check| Document | What is in it |
|---|---|
| CAMERA.md | The attention model, the legibility formula and the solver, as prose |
| DECISIONS.md | Architecture decision records, each with its cost |
| DISCOVERIES.md | What three accessibility APIs actually do, measured |
| PRIVACY.md | What is stored, what is not, and a real focus.json |
| TESTING.md | The corpus, the invariants, the property tests |
| KNOWN_GAPS.md | What Dolly does not do, and why |
| CREDITS.md | What was ported from Recordly and Lathe, and what changed |
| tools/screenshots | How the images above were made, and which parts of them are real |
Every number published anywhere in this repository traces to docs/evidence/, with the machine and the method stated.
| Phase | What lands | State |
|---|---|---|
| 0 | Foundations, design system, CI | built |
| 1 | The accessibility spike — the project's largest risk, retired first | Windows verified; macOS and Linux written, unverified |
| 2 | Tracks, the eighteen-track corpus | built |
| 3 | The attention model | built |
| 4 | The camera solver, all invariants, golden paths | built, 10,000-check property tests passing |
| 5 | Capture, permissions, crash recovery | built; needs FFmpeg on a real machine to verify |
| 6 | Render and export | built; frame-exact tests need FFmpeg |
| 7 | The editor | built |
| 8 | Redaction and presentation | built — redaction, backgrounds, cursor polish, webcam overlay |
| 8b | Timeline editing, GIF export, shortcuts | built; the render paths need FFmpeg to verify |
| 9 | Wayland verification, usability testing, polish | not started |
The honest summary: everything that can be tested without hardware is tested and passing. Everything that needs a real display server, a real FFmpeg or a real macOS machine is written and waiting for one.
Dolly's presentation layer — the clip and speed model, the cursor spring, the webcam overlay layout, the GIF options — is ported from Recordly by @webadderall, AGPL-3.0, and adapted to Go and to a parallel renderer. Three of those ports deliberately differ from the original, and CREDITS.md says which and why.
Recordly itself began as a fork of OpenScreen, and says so.
The toolkit and the design system come from Lathe. Capture and encoding are FFmpeg's. The signal the whole project rests on is thirty years of accessibility work by people building screen readers.
AGPL-3.0. See LICENSE.



