Skip to main content

CHANGELOG.md

# 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`.