Created: August 9, 2026
Last commit: August 11, 2026
JavaScript97.1%
HTML1.8%
CSS1.1%
README.md

scrawl

A tldraw canvas floating over your entire screen — the drawing surface of Annotate with tldraw's tooling, drivable by coding agents the way tldraw offline is.

  • Hotkey (Alt+Shift+A, or SCRAWL_HOTKEY) toggles between click-through and drawing. Alt+Shift+C clears every display. Esc returns the mouse to whatever is underneath.
  • Menu bar ✏︎ icon ( while drawing): every tool with its keyboard shortcut, colour, thickness, fade duration, undo/redo, clear, quit. There is no Dock icon.
  • Marks you draw fade after 5 seconds by default — change or disable it under Fade in the menu.
  • The on-screen chrome is optional. Interface switches the toolbar, style panel, menu, zoom controls, and helper buttons off individually or all at once, down to bare canvas. Your choices and the fade duration persist in ~/.scrawl/prefs.json.
  • Agents read and edit the live canvas over a local HTTP bridge — see .agents/skills/scrawl/.
pnpm install && pnpm dev     # or: pnpm build && pnpm start

Agent skill

.agents/skills/scrawl/SKILL.md teaches a coding agent to drive the overlay — how to authenticate, what each endpoint does, and where it departs from tldraw offline. Link it into wherever your agent looks for skills:

ln -s "$PWD/.agents/skills/scrawl" ~/.claude/skills/scrawl

Install it globally rather than per-project: it operates the running app from whatever repo you are in, which is rarely this one.

How it works

One transparent, borderless window per display, each running its own tldraw editor. The window sits at screen-saver level with type: 'panel' so it floats over other apps' fullscreen spaces, and ignores mouse events until you activate it — so it is invisible to your clicks in its resting state.

Decisions

  • Electron over Tauri. The three macOS behaviours this app is made of — floating above fullscreen spaces, click-through toggling, and per-display placement — are one-line Electron APIs and objc glue in Tauri.
  • The camera is locked. Page coordinates are screen pixels, so an annotation stays on the thing it points at. An unlocked camera would let a stray scroll slide every mark off its target.
  • The agent bridge mirrors tldraw offline's dialect (bearer token in a server.json, /api/search, /api/doc/:id/exec) instead of inventing one, so the existing tldraw-offline skill's snippets and habits carry over. It listens on 7237 to stay out of that app's way.
  • executeJavaScript rather than a per-tool API. One endpoint hands agents the whole tldraw Editor; a hand-maintained tool surface would be strictly smaller and constantly out of date.
  • Overlays are ephemeral. Annotation is throwaway by nature — there are no files, no persistence, and helpers.saveDoc() exists only so tldraw offline snippets don't crash.
  • Fade only applies to marks made while the overlay has the mouse. Anything drawn through the bridge lands while the overlay is click-through and therefore persists: a gesture at something on screen should evaporate, an agent's diagram should not dissolve while you read it.
  • The menu bar is the manual. Every tool sits in the tray menu with the shortcut tldraw already binds to it, so the shortcuts are discoverable without a tutorial and without a second keymap to maintain — the menu documents tldraw's own handling rather than duplicating it. It is also what makes hiding the on-screen chrome safe: with every panel switched off, the menu bar still drives every tool, colour, and action.

Security

The bridge binds to 127.0.0.1 and requires a per-launch token from ~/.scrawl/server.json (mode 0600). Anything holding that token can run arbitrary JavaScript in the overlay renderer. That is the feature, and the reason it is not exposed off-machine.

License

scrawl's own code is MIT. The tldraw SDK it depends on is not: the tldraw license permits development use and renders a "get a license for production" watermark, which the license forbids removing or interfering with. Production use or distribution needs a license key from tldraw.dev — drop it into the licenseKey prop in src/renderer/App.jsx.