Screenshots (canvas previews)
canvas-drop can capture a preview screenshot of each published canvas and reuse it
as the gallery cover and — for public canvases — the link-unfurl (Open Graph) image.
It's an optional, off-by-default extra: when it's off, the product behaves exactly as
it always has (gallery shows generative-art covers, links unfurl with the branded
/og.png).
Where previews appear today: the gallery cover and the public-link OG image. The capture itself is stored once per canvas as a private, access-gated asset, so the same image can back additional surfaces (e.g. dashboard thumbnails) without re-capturing — those are wired in as the product decides to surface them.
How it works
- Captured on publish. Publishing a canvas (editor publish or any deploy) schedules a capture. Capture is asynchronous — it never slows down or blocks a publish.
- One preview per canvas. A canvas has exactly one current preview, stored as WebP
renditions (
og,card,thumb). On the defaultautopreview mode it is overwritten on each republish, so the image always reflects the latest published version. The owner can override this per canvas (see Per-canvas control below). - One browser, a small queue. A single headless Chromium runs in the server process
and is reused across captures (a fresh, isolated browser context per job, recycled
periodically). Jobs are drained from a small database queue one at a time by default
(
CANVAS_DROP_SCREENSHOTS_CONCURRENCY), so a burst of publishes just queues up. - Access-gated by default. Previews are stored privately and served through an
access-gated route: a canvas's preview is only served to someone already allowed
to see that canvas. Restricted and otherwise gated canvas previews are never exposed publicly — a
per-canvas OG image is emitted only for
public_linkcanvases. - Capture is sandboxed. While a canvas is being captured, its backend primitives (AI, realtime, network) are neutered — a capture makes no AI spend and no outbound network calls. Dialogs are dismissed and a hard timeout bounds a slow/looping canvas.
Enablement — two org-wide layers
Auto-capture only runs when both org-wide layers say yes (each canvas owner can then further opt out or override per canvas — see Per-canvas control below):
- Environment availability —
CANVAS_DROP_SCREENSHOTSmust beon, and the runtime image must actually contain Chromium (see below). This is the operator's "is this capability available at all" switch. Default:off. - Admin runtime toggle — even when available, an admin must turn it on in the dashboard (Admin → Configuration → Screenshots enabled). Default: off.
When either layer is off, no browser is launched and auto previews are not served — the gallery and link unfurls behave exactly as they do today. (An owner-uploaded custom cover is independent of this gate: it's served even when auto-capture is off.)
Per-canvas control
Each canvas owner controls its cover from Settings → Preview (and over MCP via
update_canvas previewMode / set_canvas_preview):
- Automatic (
auto, the default) — screenshot on publish, as described above; subject to the two org-wide layers. - Off (
off) — never screenshot this canvas; it shows the generative cover. - Custom (
custom) — the owner uploads a cover image. It's encoded into the sameog/card/thumbrenditions, pinned so a publish never overwrites it, and served even when the org-wide auto-capture pipeline is off. Removing it reverts toauto.
Turning it on
1. Build an image that includes Chromium
The default image does not include Chromium (it would add ~300 MB, and the feature ships disabled). Build with the opt-in build arg:
docker build --build-arg SCREENSHOTS=1 -t canvas-drop:screenshots .
This installs Chromium and its system libraries into the image. The default build
(SCREENSHOTS=0) skips this entirely and stays lean.
2. Make it available in the environment
Set the env flag on the running container/instance:
CANVAS_DROP_SCREENSHOTS=on
Optional tuning (all have sensible defaults):
| Variable | Default | What it does |
|---|---|---|
CANVAS_DROP_SCREENSHOTS_CONCURRENCY |
1 |
Captures processed at once (one browser, N contexts) |
CANVAS_DROP_SCREENSHOTS_TIMEOUT_MS |
20000 |
Hard per-capture wall-clock timeout |
CANVAS_DROP_SCREENSHOTS_RECYCLE_EVERY |
50 |
Relaunch the browser after this many jobs |
CANVAS_DROP_SCREENSHOTS_LEASE_MS |
120000 |
Job lease (a crashed worker's job is reclaimed after this) |
CANVAS_DROP_SCREENSHOTS_MAX_ATTEMPTS |
3 |
Retries before a capture is marked failed |
CANVAS_DROP_SCREENSHOTS_FAILED_TTL_MS |
86400000 |
Failed-job rows reclaimed after this |
CANVAS_DROP_SCREENSHOTS_TOKEN_TTL_MS |
60000 |
Lifetime of the internal capture credential |
3. Flip the admin toggle
Sign in as an admin → Admin → Configuration → turn Screenshots enabled on. New publishes start getting previews immediately; existing canvases get one on their next publish.
Turning it off
Flip the admin toggle off (or unset CANVAS_DROP_SCREENSHOTS). Capture stops and
previews are no longer served — covers revert to generative art and unfurls to the
branded card. Stored previews are harmless and are reclaimed when a canvas is deleted.
Memory note
Chromium is memory-heavy. On a small single-VPS deployment, keep concurrency at 1
(the default) and rely on the per-job timeout and periodic browser recycle to bound
memory. Scale concurrency up only if the host has headroom.