All notable changes to Etcher are documented here. The format follows Keep a Changelog and this project adheres to Semantic Versioning.

0.2.1 — 2026-05-14

Patch release: pan / zoom now work over shapes.

Fixed

  • Scroll-wheel zoom and click-drag pan on the underlying viewer stopped working whenever the cursor was over an annotation (rectangle, circle, polygon, freehand, callout, text). Root cause: shapes had pointer-events: visiblePainted so they caught wheel + pointerdown before OSD's MouseTracker on the canvas sibling could see them — pointer events bubble UP the DOM, not sideways. Shapes are now pointer-events: none, and hover + click are re-detected at the document level via image-px hit-testing (reuses the eraser's per-kind point-in- shape check). Hover styling, tooltips, click-to-pin, click-to- edit, and dblclick-inline-edit all continue to work; pan and zoom now pass through every annotation cleanly.

Internal

  • Renamed _eraserHit/2 to a shared _shapeContainsPoint/2 helper. The eraser keeps a thin alias for readability at its call sites.
  • New helpers: _shapeAt/1 (topmost-shape lookup), _onShapeTap/1 (shared tap-handling entry), _wireGlobalShapeListeners/0 + _unwireGlobalShapeListeners/0, _setHoveredShape/1.
  • Tap-vs-drag disambiguation with a 5px dead-zone keeps a quick click-without-drag firing the shape's selection / pin / edit flow, while any drag-with-movement passes through to OSD's pan unchanged.

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 as metadata.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-resize cursors 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: true flag 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.color on each annotation; vertex + title handles inherit the shape's color via currentColor (no more always-orange dots). Override the palette via window.Etcher.colorSwatches; initial color via window.Etcher.defaultColor.
  • Tooltip slot extension APIwindow.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 generic metadata.{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_uuid so consumers can re-link soft-deleted child rows (e.g. comments) to the new uuid the server assigns to the recreated annotation.
  • appendNavButton mutable 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 :tools list on Etcher.Layer.layer/1 is 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 use fill-opacity so they tint correctly with whichever color the shape carries.
  • Single-shape deletes now flow through the same compound bulk_delete undo 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-selected outline when the cursor leaves it (was getting stuck visually deselected).
  • Tooltip .is-hovered no longer sticks after the cursor leaves a pinned shape.

0.1.0 — 2026-05-06

Initial release.

Added

  • Etcher.Layer Phoenix LiveView function component — attaches an annotation overlay to a named Fresco viewer and adds a pencil button to its nav column.
  • Etcher.Storage behaviour — pluggable storage adapter contract with four callbacks (create/1, list_for/2, update/2, delete/1).
  • Etcher.Storage.Default — bundled implementation backed by the etcher_annotations table. Reads the consumer's Repo from config :etcher, repo: ….
  • Etcher.Annotation Ecto schema for the bundled table (UUIDv7 primary key, target_type / target_uuid, four geometry kinds: rectangle, circle, polygon, freehand).
  • mix etcher.gen.migration — generates the etcher_annotations table migration into the consumer's priv/repo/migrations/.
  • JS engine at priv/static/etcher.js — registers the EtcherLayer LiveView hook, draws shapes as SVG overlays anchored to image coordinates, emits etcher:created / :updated / :deleted / :selected events.
  • 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+).