Annotating
Twenty tools, one shared engine, and the one distinction that matters more than anything else on this page: REDACT and GREY OUT are the tools that hide. On a still they destroy the pixels they cover; on a recording they do not. Blur and pixelate are emphasis tools and hide nothing anywhere.
None of the shipped strings quoted here contains an em dash, so nothing is substituted; they are verbatim. In a quote, a name in braces, such as {name}, or a capital N stands for a part the app fills in as it runs. Three dots at the start or end of a quote stand for words left out, unless the article says the app prints them.
The tools
Twenty tools. Move and Eraser stand outside every group; the other eighteen sit in five
labelled ones (the .tool buttons in #annoBar, apps/desktop/src/board.html):
- Move, Eraser.
- Mark: Box, Filled box, Ellipse, Line, Arrow, Arrow+Text, Highlight, Spotlight, Text.
- Hide privacy band: Redact, Grey out.
- Obscure: Blur, Pixelate. A plain group right after the band: they obscure without removing (see below).
- Sequence: Step number.
- Frame: Crop, Rotate, Background, Resize.
Eraser stands outside the groups with Move because each named group is a kind of mark you add and the eraser takes one away: touch an annotation and the whole annotation goes, so it is a tool for undoing a mark rather than a pixel eraser. It is deliberately not in the privacy band, because erasing a redaction reveals what was hidden, which is the opposite of what that band promises. Undo, Redo and Clear are unchanged.
The same twenty buttons, the same data-tool values and the same word labels are on all four
pages that load the engine: the desktop board, the extension's popup and expanded board, and the
web editor frame (.tgroup.solo in #annoBar on apps/desktop/src/board.html, apps/extension/popup.html,
apps/extension/app.html and apps/web/public/editor/index.html).
One engine file, byte-mirrored across desktop, extension and web: the desktop copy
(apps/desktop/src/annotate.js) is the source, mirrored by scripts/check-annotate.mjs --fix,
and drift fails pnpm test (joined-to-code: scripts/check-annotate.mjs).
What changes the picture, and what only draws on it
Crop, Rotate, Background and Resize change the picture. They replace the frame: the canvas takes
new dimensions and every mark already on it is moved by the same six numbers that redrew the
bitmap, so what was circled is still circled (mapShape and setFrame in apps/desktop/src/annotate.js). A mark that
falls wholly outside the new frame is dropped, a rectangle straddling the edge is clipped, and a
line or a label is kept whole or not at all, because clipping a line moves what it points at
(clipMark in apps/desktop/src/annotate.js). Undo puts the old bitmap and the old coordinates back in one
act, since the undo stack holds whole editor states rather than a list of marks
(annoSnap and annoRestore in apps/desktop/src/annotate.js). A crop with nothing drawn on it is unsaved work, so
closing after one asks the same Cancel/Discard/Save question (annoDirty in apps/desktop/src/annotate.js).
The four have one injected panel between them, in four modes, with Apply and Cancel and a readout
that states the resulting pixel size before anything is committed
(openGeo and applyGeo in apps/desktop/src/annotate.js). There is no live preview. Rotate turns a quarter at a
time with its two buttons and straightens up to 15 degrees either way with its slider, scaling up
just enough that a straightened picture still covers every corner and leaves no empty wedge
(rotate90 and straighten in apps/desktop/src/annotate.js). Background pads the frame and paints a gradient in the
current swatch colour behind a rounded, shadowed copy of the capture
(padBackground in apps/desktop/src/annotate.js). Resize takes 10 to 300 percent
(GEOCFG.resize and resizeTo in apps/desktop/src/annotate.js).
Spotlight hides nothing. It dims the picture outside the region so the eye reads the bright part
as the subject; the pixels underneath are unchanged and are still in the saved file
(dimOutside in apps/desktop/src/annotate.js). That is why it sits in Mark beside Highlight and not in
the privacy band.
Since 2026-09-14 spotlight has company on that side of the line. Blur and pixelate READ the picture
and paint a coarser copy of it, which is what makes them look like a blur and a mosaic - and what
makes them recoverable: published tools undo a mosaic (Depix, Unredacter) and deconvolution undoes
a blur. They are emphasis tools. REDACT and GREY OUT are the only ones in this band that hide:
Redact paints solid black and Grey out a fixed grey, and neither carries anything of what was
underneath (the greyout branch of drawShape in apps/desktop/src/annotate.js). Grey out is the
softer of the two to look at, not to see through: its fill is just as opaque, and it ignores the
colour swatch exactly as Redact does. Do not offer blur or pixelate as a lighter-weight redaction,
and do not describe them as removing, destroying or replacing anything.
Stills versus recordings
On a still, marks are baked into the picture at Save annotation and also kept as data so the .md can describe them (joined-to-code: bp35 checks). Redact and Grey out on a still genuinely destroy the pixels they cover. Blur and pixelate are baked in too, but what they bake is a coarser copy of the picture, so the content is obscured rather than gone.
On a recording, every mark is an overlay drawn at playback time. Redact, grey out, blur and pixelate do NOT remove pixels from the file. The saved .md carries a bold warning and deliberately withholds the effects' positions, so the .md cannot be used as a map to what was hidden (joined-to-code: bp35 redaction-coordinates check).
Burn in writes a separate annotated.mp4 with shapes baked in and effects applied as ffmpeg
regions; the card keeps pointing at the original (burnEffects in apps/desktop/src/board.js).
The desktop player says so on screen too: a recording carrying any Hide mark shows a warning under
the video that the marks are drawn over it, not into it, and names Burn in as the way out
(#vidRedactWarn in apps/desktop/src/board.html, raised from renderAnnoList in
apps/desktop/src/board.js). The extension carries the same warning; see extension.md.
Cropping a recording
Desktop only, on the file on this machine. Crop on the video bar drags a region over the player,
and ffmpeg writes the result to a separate file; the original is untouched until the three-way
confirm is answered, and its primary answer is "Save as a new card" (#cropSaveNew in
apps/desktop/src/board.html), with "Replace original" the one button that cannot be undone
(#cropKeep in apps/desktop/src/board.html). The card's marks are
translated into the cropped frame by the same mapper the still-image tools use, and the message
above the buttons says how many fell outside it (cropAnnotations in apps/desktop/src/board.js). The
interaction track is deliberately not carried to the new card: its click positions are
percentages of the original frame, so against a cropped file they would point at the wrong region
(saveCropAsNewCard in apps/desktop/src/board.js). If the file is not on this machine, the crop refuses in the
same sentence the player uses (showCropMode in apps/desktop/src/board.js).
The extension has no video crop. A recording already uploaded to the cloud is not re-encoded; this crops the local file only.
Timed marks
On a recording, "Annotate here" stamps a mark with a three-second window at the playhead. Each mark's list row can be retimed with in, out and always, relative to the playhead. Trimming the recording shifts or drops windows correctly on both trim outcomes (apps/desktop/src/annotate.js; joined-to-code: b1 trim checks).
Names, formats, and the unsaved-work guard
The file name and format chosen in the editor top bar persist on the card and reach every save path (joined-to-code: bp38b checks). A typed extension is stripped, so name.png never becomes name.png.png. Closing with unsaved marks asks Cancel/Discard/Save, with Save focused (joined-to-code: b3 checks).
Annotating on the web
Web annotation of a library capture requires all four of: editor role, the item not in trash, a screenshot (recording marks are timed and drawn by the player overlay), and loadable bytes. Each refusal is a full sentence shown before the editor opens, so nobody draws for ten minutes and then fails (apps/web/src/app/dashboard/a/[id]/annotate/page.tsx). Save errors relay the server's own wording, including "That image is too large to save. The limit is {limit} MB, and the editor's Resize tool is how to get under it." (apps/web/src/app/dashboard/a/[id]/annotate/editor.tsx, which says it word for word before spending the upload; {limit} is 12, pinned by annotate-routes.test.ts, joined-to-code).
The standalone /annotate route stores nothing and says so. That sentence is true of that route only; the dashboard annotate flow saves to the library.
The Annotate button on the web asset page appears only once the capture is ready. While a capture shows "Not playable yet" there is no annotate entry (apps/web/src/app/dashboard/a/[id]/page.tsx).
The sentence a support agent can paste
"Redactions on a recording are drawn over the player, not into the file; use Burn in and share the annotated copy instead."