Knowledge base

Annotating

Eighteen tools, one shared engine, and the one distinction that matters more than anything else on this page: on a still, redactions destroy pixels; on a recording, they do not.

Em dashes in quoted shipped strings are shown as plain hyphens; the originals use em dashes.

The tools

Eighteen tools. Move stands outside every group; the other seventeen sit in four labelled ones (apps/desktop/src/board.html:553-596):

  • Move.
  • Mark: Box, Filled box, Ellipse, Line, Arrow, Arrow+Text, Highlight, Spotlight, Text.
  • Hide privacy band: Redact, Blur, Pixelate.
  • Sequence: Step number.
  • Frame: Crop, Rotate, Background, Resize.

The same eighteen 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 (apps/desktop/src/board.html:553, apps/extension/popup.html:349, apps/extension/app.html:362, apps/web/public/editor/index.html:259).

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 (apps/desktop/src/annotate.js:286,271). 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 (apps/desktop/src/annotate.js:253). 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 (apps/desktop/src/annotate.js:279-281,542). A crop with nothing drawn on it is unsaved work, so closing after one asks the same Cancel/Discard/Save question (apps/desktop/src/annotate.js:599).

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 (apps/desktop/src/annotate.js:723,763). 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 (apps/desktop/src/annotate.js:328,332). Background pads the frame and paints a gradient in the current swatch colour behind a rounded, shadowed copy of the capture (apps/desktop/src/annotate.js:357). Resize takes 10 to 300 percent (apps/desktop/src/annotate.js:341).

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 (apps/desktop/src/annotate.js:174-180). That is why it sits in Mark beside Highlight and not in the privacy band: redact paints black over pixels, blur and pixelate resample them away, and spotlight changes an alpha. It is not a lighter-weight redaction and must not be offered as one.

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, blur and pixelate on a still genuinely destroy the pixels they cover.

On a recording, every mark is an overlay drawn at playback time. Redact, 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 (apps/desktop/src/board.js:829).

Gotcha: the desktop player currently shows no on-screen warning that recording redactions are overlays. The .md warning and Burn in are the only guardrails today. (The extension does show a permanent 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", with "Replace original" the one button that cannot be undone (apps/desktop/src/board.html:661-667). 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 (apps/desktop/src/board.js:1336,1455,1464). 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 (apps/desktop/src/board.js:1499-1507). If the file is not on this machine, the crop refuses in the same sentence the player uses (apps/desktop/src/board.js:1427).

The extension has no video crop, for the reason it has no Burn in: no ffmpeg, and none is coming (see extension.md). 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 12 MB." (apps/web/src/app/dashboard/a/[id]/annotate/editor.tsx:57, em dash shown as hyphen; the 12 MB figure is 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."