All notable changes to Tessera are documented here. The format is based on Keep a Changelog and the project adheres to Semantic Versioning.

0.2.1 — 2026-05-12

Patch release — package metadata polish only, no code or behavior changes.

Changed

  • mix docs extras now include LICENSE and CHANGELOG.md, so the README's [LICENSE](LICENSE) reference resolves on HexDocs and the CHANGELOG gets a proper standalone page in the published docs.

0.2.0 — 2026-05-12

Breaking change: Tessera no longer hosts its own image viewer. It's now a layer that composes onto Fresco. The Fresco viewer owns the OpenSeadragon instance, the Heroicons nav overlay, animations, and viewport clamping. Tessera focuses on what makes Tessera distinctive: the DZI source provider and the multi-layer progressive-zoom logic.

What changed

  • Removed: <Tessera.viewer src=...> LiveView component.
  • Added: <Tessera.layer fresco_id sources> — attaches Tessera's behavior to a named Fresco viewer.
  • Removed (moved to Fresco): the OSD lazy-loader, the Heroicons nav overlay (fresco-nav now), the animation tuning constants (animationTime: 0.3 / springStiffness: 10 defaults), the viewport clamp (visibilityRatio: 1.0 / constrainDuringPan: true), the swapSourcePreservingBounds helper. All available via Fresco's default viewer settings + viewer-handle API.
  • Added (client side): Tessera now registers a DZI source provider with Fresco at load time, so any URL ending in .dzi is automatically treated as a tile pyramid.
  • Unchanged: Tessera.generate/3, Tessera.generate_manifest/3, Tessera.generate_tile/4, the Tessera.Storage behaviour + the default Tessera.Storage.Local adapter — the server-side generator API is identical to 0.1. Migration from 0.1 only affects the template.

Required dependency change

  • Add {:fresco, "~> 0.1"} alongside {:tessera, "~> 0.2"} in your mix.exs.
  • In app.js, import fresco.js before tessera.js. Spread both hook namespaces into your LiveSocket hooks: { ...window.FrescoHooks, ...window.TesseraHooks, ...colocatedHooks }.

Migration from 0.1

- <Tessera.viewer
-   id="photo"
-   src={~p"/uploads/photo-medium.jpg"}
-   class="w-full h-[80vh] rounded"
- />
+ <Fresco.viewer
+   id="photo"
+   src={~p"/uploads/photo-medium.jpg"}
+   class="w-full h-[80vh] rounded"
+ />
+
+ <Tessera.layer
+   fresco_id="photo"
+   sources={[
+     %{url: ~p"/uploads/photo-medium.jpg", width: 1024},
+     %{url: ~p"/dzi/photo.dzi"}
+   ]}
+ />

The sources list shape is unchanged from 0.1; it moves from a <Tessera.viewer sources=...> attr to a <Tessera.layer sources=...> attr.

0.1.0 — 2026-05-11

Initial release. OpenSeadragon-backed deep zoom for Phoenix apps — generate DZI tile pyramids from images and render them with a LiveView component.

Server-side

  • Tessera.generate/3 — eager full-pyramid DZI generator. Shells out to ImageMagick (magick convert -define dzi:tile-size=... input output.dzi) and writes the manifest plus the entire tile tree to a caller-supplied output directory.
  • Tessera.generate_manifest/3 + Tessera.generate_tile/4 — lazy on-demand primitives. The manifest is just XML derived from the image's intrinsic width/height; individual tiles are cropped + resized per-request, so the pyramid grows organically as users zoom into the regions that actually matter.
  • Tessera.Storage behaviour with a default Tessera.Storage.Local implementation. Consumers can plug in any backend (S3, multi-bucket, CDN) by passing storage: MyAdapter, storage_opts: [...] through to the generators.

Client-side

  • <Tessera.viewer sources={...}> — Phoenix LiveView function component. Accepts an ordered low → high quality sources list. Each entry carries an intrinsic pixel width (omit for .dzi sources); the JS hook computes thresholds dynamically and swaps between layers as the user zooms in or out. Downgrade has 15% hysteresis so the source can't flicker around a boundary.
  • priv/static/tessera.js — companion JS hook (TesseraViewer) that lazy-loads OpenSeadragon from jsDelivr on first mount.
  • Self-injected Heroicons navigation overlay (zoom-in / zoom-out / reset / fullscreen) — replaces OSD's default PNG-sprite controls so the viewer doesn't need a CDN prefixUrl.
  • Snappy animation tuning (animationTime: 0.3, springStiffness: 10) so pan/zoom track input directly instead of drifting into place.
  • visibilityRatio: 1.0 + constrainDuringPan: true so the image stays clamped to the viewer rectangle — no off-screen drift.
  • Source swaps preserve the user's viewport rectangle (fitBounds(_, true) after the new source's open event) — the image just gets sharper or softer; no jump back to home.

Requirements

  • ImageMagick (magick binary) on the host PATH (used by generate*).
  • phoenix_live_view ~> 1.1, phoenix_html ~> 4.0, jason ~> 1.4.