All notable changes to Etcher are documented here. The format follows Keep a Changelog and this project adheres to Semantic Versioning.
0.2.0 — 2026-05-14
A backwards-compatible second release: two new shape kinds, an eraser tool, undo/redo with full history, satellite titles, edge-resize grabbers, polygon midpoint insertion, a visibility toggle, and a complete programmatic API so consumers can drive the layer without rendering its built-in toolbar.
Added
- Callout tool (
kind: "callout") — blueprint-style leader-line annotation: an anchor dot pointing at the image, a thin line to a resizable text bbox (with a horizontal underline spanning the bbox bottom). Text inside scales to fit the bbox. - Text tool (
kind: "text") — freestanding text label drawn as a click-drag bbox. Inline editor (<foreignObject>+<input>) opens on commit and on double-click for re-edit. Font scales with bbox height; bbox shrink-wraps to the text on release. - Eraser tool — press-and-drag wipes shapes by sweep. Each shape
the cursor crosses dims (
.is-erasing) for preview; release flushes them all as a single compound delete. Idle hover (eraser selected, no button held) previews the single shape under the cursor. - Optional title field per annotation — every kind can carry a
short label (
title varchar(200)). On rect/circle/polygon/freehand the title renders as a movable, resizable satellite group with a dashed leader line back to the parent's nearest perimeter point (leader auto-hides when the title is inside the parent). On callouts the title is the in-bbox content. Drag to move (persisted asmetadata.title_box), 4 corner handles to resize, double-click to inline-edit. - Edge-midpoint resize grabbers on rectangles — small rounded
rect handles on each side; drag a side to slide one edge while
the opposite edge stays anchored. Distinct visual +
ns-resize/ew-resizecursors so they don't conflate with polygon midpoints. - Polygon midpoint vertex insertion — every polygon edge now carries a "ghost" midpoint handle that lights up when the cursor is near. Grab it to insert a new vertex at that midpoint and place it via a vertex-style drag.
- Undo / Redo — toolbar buttons + ⌘Z / ⌘⇧Z / Ctrl+Y keyboard
shortcuts. 50-op session history covers geometry, style, metadata,
title text, and deletes (including bulk-delete from the eraser).
Delete recreation honors a new
restore: trueflag so the consumer can suppress its create-time UI (e.g. comment composer) on undo. - Visibility toggle — eye / eye-slash button above the pencil in Fresco's nav column. Hides/shows the entire SVG overlay with one click.
- Color picker — bottom-toolbar swatches; persisted as
style.coloron each annotation; vertex + title handles inherit the shape's color viacurrentColor(no more always-orange dots). Override the palette viawindow.Etcher.colorSwatches; initial color viawindow.Etcher.defaultColor. - Tooltip slot extension API —
window.Etcher.tooltipSlots = { header, body, footer }lets consumers replace the tooltip content per-slot while keeping the wrapper (trash button, pin/unpin, hover bridge) under Etcher's control. Default slots read genericmetadata.{title,body,subtitle}keys. - Complete programmatic control surface on
window.Etcher.layerFor(frescoId)so every built-in button is callable from outside. Methods grouped by mode, visibility, tool, color, history, and shape selection/edit. Consumers can render their own toolbar and drive the layer headlessly. - CustomEvents for state changes:
etcher:mode-changed,etcher:tool-changed,etcher:color-changed,etcher:visibility-changed,etcher:history-changed,etcher:tooltip-show / -hide / -pin / -unpin. - Restored comment threads on undo-of-delete — the etcher:created
payload for a restore carries
restore_from_uuidso consumers can re-link soft-deleted child rows (e.g. comments) to the new uuid the server assigns to the recreated annotation. appendNavButtonmutable handle (Fresco 0.1.2+) — Etcher's nav buttons can now update their icon / title in place (used by the visibility toggle to flip eye ↔ eye-slash).
Changed
- Default
:toolslist onEtcher.Layer.layer/1is now[:rectangle, :circle, :polygon, :freehand, :callout, :text, :eraser](all seven). Pass an explicit list to subset. - Text + title + callout bboxes shrink-wrap to the rendered text on every render; the stored geometry is rewritten to the shrunk dimensions on release so storage always matches what's visible.
- Vertex handles now inherit the shape's color (
currentColor) instead of hard-coded orange. CSS hover / drag fills usefill-opacityso they tint correctly with whichever color the shape carries. - Single-shape deletes now flow through the same compound
bulk_deleteundo op the eraser uses, so the tooltip trash button also gets redo support.
Fixed
- Tooltip stops hijacking hover state on a different shape while another shape's tooltip is pinned.
- Pinned shape keeps its
.is-selectedoutline when the cursor leaves it (was getting stuck visually deselected). - Tooltip
.is-hoveredno longer sticks after the cursor leaves a pinned shape.
0.1.0 — 2026-05-06
Initial release.
Added
Etcher.LayerPhoenix LiveView function component — attaches an annotation overlay to a named Fresco viewer and adds a pencil button to its nav column.Etcher.Storagebehaviour — pluggable storage adapter contract with four callbacks (create/1,list_for/2,update/2,delete/1).Etcher.Storage.Default— bundled implementation backed by theetcher_annotationstable. Reads the consumer's Repo fromconfig :etcher, repo: ….Etcher.AnnotationEcto schema for the bundled table (UUIDv7 primary key,target_type/target_uuid, four geometry kinds: rectangle, circle, polygon, freehand).mix etcher.gen.migration— generates theetcher_annotationstable migration into the consumer'spriv/repo/migrations/.- JS engine at
priv/static/etcher.js— registers theEtcherLayerLiveView hook, draws shapes as SVG overlays anchored to image coordinates, emitsetcher:created/:updated/:deleted/:selectedevents. - Bottom drawing toolbar with rectangle / circle / polygon / freehand
tools; pencil-button toggle integrated with Fresco's nav column via
handle.appendNavButton/3(Fresco 0.2+).