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

0.2.1 — 2026-05-28

Fixed

  • Pages appended to the strip container after mount were invisible to the coordinate helpers. Internal sources was captured once from data-sources at mount and never extended, and screenToImage iterated for (i = 0; i < sources.length; i++) — so taps on pages added by multi-chapter infinite-scroll readers all collapsed onto the fallback {imageIdx: sources.length - 1, x: 0, y: 0} (the last original page at its origin). screenToImage now iterates the live [data-fresco-strip-img] set in the DOM and snaps the out-of-range fallback to the highest idx actually present, so appended pages route correctly even before appendSources is called. Pairs with etcher 0.5.3's layer.refreshPages() to fix drawing + addShape on appended chapters end-to-end.
  • imageToScreen and screenToImage scale fallback. When sources[idx].width was missing (an appended page, or a spec shipped without dimensions), the scale silently fell through to 1, which is correct only for source-pixel == screen-pixel images. Both helpers now consult img.naturalWidth as a secondary fallback before degrading to scale = 1, so once the bitmap loads the coords come out right even without appendSources.

Added

  • handle.appendSources([{url, width, height}, ...]) — extend the internal sources at runtime. Sequential append: the Nth spec lands at index sources.length (before push), matching the natural pattern of consumers appending <img data-image-idx="…"> elements to the container. width/height are in source-pixel space and feed screenToImage / imageToScreen for accurate routing before the appended bitmaps finish loading. Emits a sources-changed event so the host hook can pick up the new imgs for memory windowing, dominant-image tracking, and the image-loaded re-emit (with a synthetic image-loaded for imgs that were already complete when the listener attached, e.g. cached).

    Consumer flow:

    fetch(chapterUrl).then(function(resp) { return resp.json(); })
      .then(function(specs) {
        specs.forEach(appendImgToContainer);   // your DOM glue
        handle.appendSources(specs);            // teach the helpers
        layer.refreshPages();                   // etcher 0.5.3
      });

0.2.0 — 2026-05-24

Deep-linking companion release to etcher 0.5.0. Adds a high-level scroll helper that takes source-pixel coordinates directly, so peer libraries holding shape geometry (Etcher annotations, ML overlay boxes, server-side comment payloads) no longer need to own the per-image rendered-vs-natural ratio themselves.

Added

  • handle.scrollToImagePoint({imageIdx, srcX, srcY, align, behavior}) on the strip handle. srcY is in the image's source-pixel space (the same units <Fresco.scroll_strip> / <FrescoStrip.viewer> already require for :sources :width/:height, and the same units Etcher persists shape geometry in). The handle owns the imageEl + sources map, so the source-px → display-px conversion stays in one place. align is "center" | "top" | "bottom" (default "center"); behavior is "smooth" | "instant". Existing low-level scrollTo({imageIdx, y}) stays as the escape hatch for display-pixel callers.
  • Coordinate-space note in FrescoStrip.Viewer moduledoc explicitly calls out that everything the strip handle accepts / reports is in source-pixel space — calling out the convention so consumers don't have to discover it via console spelunking.

Why

The consumer's deep-link handler ("jump to this annotation's shape") used to need ~60 lines of glue: poll for the shape, read the image element, compute srcH = sources[idx].height, then displayY = srcY * img.offsetHeight / srcH, then container.scrollTo({ top: img.offsetTop + displayY - …, … }). The new helper makes that one call. Pairs with etcher 0.5.0's layer.revealShape (which now delegates to this helper internally for strip-mode shapes).

Compatibility

  • Pure additive — no existing API changed. scrollTo({imageIdx, y}) callers continue to work; new callers should reach for scrollToImagePoint first when they have source-pixel coords.
  • Co-installs with fresco 0.6.x as before; shared window.Fresco.viewerRegistry contract is unchanged.

0.1.1 — 2026-05-24

Docs-only release. Spells out Etcher compatibility in the hex description, README, and top-level FrescoStrip moduledoc so consumers searching for "manhwa annotations" or "scrolling reader with comments" find the package.

Changed

  • mix.exs description now mentions Etcher: "Annotation-ready: Etcher (>= 0.4.12) draws shapes on each page out of the box, sharing the same window.Fresco registry as fresco."
  • README — new "Annotations (Etcher)" section with a complete consumer wiring example (mount/3 + handle_event + <Etcher.layer /> template). Lead paragraph also notes Etcher is supported.
  • FrescoStrip moduledoc — new "Annotations out of the box" paragraph explaining strip-mode Etcher integration + the image_idx payload field.

No code changes. FrescoStrip.viewer/1's detailed Etcher integration moduledoc was already in place from 0.1.0.

0.1.0 — 2026-05-24

Initial release. Extracted from fresco <= 0.5.9 where the same component lived as Fresco.scroll_strip. Behavior is byte-for-byte identical to the pre-extraction implementation — only the module name and JS import path change.

Why a separate package?

  • Keep fresco lightweight for the common viewer / canvas consumer (lightboxes, paged readers, document viewers, lookbooks) who never needed the strip's memory-windowing / per-image-overlay machinery.
  • Let strip mode iterate on its own release cadence — most strip- side changes don't touch viewer/canvas, and vice versa.
  • Distinct mental models: viewer/canvas pan + zoom + rotate via CSS transforms; strip is a flat <img> list with native scroll. Two API surfaces, two changelogs.

Migration from fresco.scroll_strip

 # mix.exs
 defp deps do
   [
-    {:fresco, "~> 0.5.9"}
+    {:fresco, "~> 0.6.0"},
+    {:fresco_strip, "~> 0.1.0"}
   ]
 end

 # assets/js/app.js
 import "../../deps/fresco/priv/static/fresco.js"
+import "../../deps/fresco_strip/priv/static/fresco_strip.js"

 # template
-<Fresco.scroll_strip id="reader" sources={@pages} extensions={@ext} />
+<FrescoStrip.viewer id="reader" sources={@pages} extensions={@ext} />

Three lines of consumer diff. Etcher (0.4.12+) detects the strip handle at runtime — no code change needed there.

Shared window.Fresco global

Both fresco and fresco_strip contribute handles to the same window.Fresco.viewerRegistry. Peer libraries (Etcher annotations, ML overlays, comment threads) call window.Fresco.viewerFor(id) / onReady(id, cb) and get the right handle regardless of which package mounted it. Order-independent — whichever package's JS runs first creates the registry; the second piggy-backs.