# Changelog
All notable changes to Tessera are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 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](https://hex.pm/packages/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
```diff
- <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`.