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 docsextras now includeLICENSEandCHANGELOG.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-navnow), the animation tuning constants (animationTime: 0.3/springStiffness: 10defaults), the viewport clamp (visibilityRatio: 1.0/constrainDuringPan: true), theswapSourcePreservingBoundshelper. 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
.dziis automatically treated as a tile pyramid. - Unchanged:
Tessera.generate/3,Tessera.generate_manifest/3,Tessera.generate_tile/4, theTessera.Storagebehaviour + the defaultTessera.Storage.Localadapter — 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 yourmix.exs. - In
app.js, importfresco.jsbeforetessera.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.Storagebehaviour with a defaultTessera.Storage.Localimplementation. Consumers can plug in any backend (S3, multi-bucket, CDN) by passingstorage: MyAdapter, storage_opts: [...]through to the generators.
Client-side
<Tessera.viewer sources={...}>— Phoenix LiveView function component. Accepts an ordered low → high qualitysourceslist. Each entry carries an intrinsic pixelwidth(omit for.dzisources); 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: trueso 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'sopenevent) — the image just gets sharper or softer; no jump back to home.
Requirements
- ImageMagick (
magickbinary) on the hostPATH(used bygenerate*). phoenix_live_view ~> 1.1,phoenix_html ~> 4.0,jason ~> 1.4.