Skip to content

viewer: ImageLayers tolerates mixed-size layers (letterbox, no overflow) - #292

Draft
ivanmkc wants to merge 8 commits into
masterfrom
feat/image-layers
Draft

viewer: ImageLayers tolerates mixed-size layers (letterbox, no overflow)#292
ivanmkc wants to merge 8 commits into
masterfrom
feat/image-layers

Conversation

@ivanmkc

@ivanmkc ivanmkc commented Jul 28, 2026

Copy link
Copy Markdown
Owner

ImageLayers documents a same-size contract (all layers share the first layer's natural aspect) but never enforced it. A layer with a different aspect was absolutely positioned at width:100% with natural height, overflowing the stack — surfaced by an emberwood debug board mixing 1400x1045 zmaps with 2400x300 probe strips in one component.

Fix: non-first layers get height:100% + object-fit:contain, so mismatched layers letterbox inside the first layer's box. Same-size boards render identically (contain is a no-op at equal aspect).

No dimension check/warning added — open question whether authoring-time validation (push linter) is the better place for it.

Compare a render against its blueprint or flip a whole generation
chain in one display. Dependency-free (pattern: ImageCarousel);
layers prop is plain data with URL-scheme re-check; registered in
resolver + core whitelist. Deployed + Playwright-verified (toggle
delta test).
- sparse src-keyed user overrides replace index-array state: live
  patches that grow/reorder/re-author layers keep working (was:
  new layers permanently invisible with dead controls; reorders
  glued user toggles to slots)
- hidden layers no longer mount their <img>; loading=lazy per the
  ImageCarousel convention (was: 109 PNGs / 68 MB eager fetch on
  the production board)
- a11y: sliders get accessible names, slider moved out of the
  checkbox label (was: anonymous range inputs, value polluting
  the checkbox AX name); role=group on the control row; theme
  text token for labels (contrast)
- NaN-guard via Number.isFinite; stable key={src}
- registration comment hygiene + dedupe stray TileGrid whitelist
  entry
- Mantine Checkbox/Slider replace raw native inputs (house style
  per Checklist/SliderGallery; fixes touch targets + control
  contrast as a side effect)
- all/none bulk toggles per instance (218 flat controls at
  production density had no bulk affordance)
- authoring-time validation for layers[] in core validate: src
  scheme (http(s)/data:image), opacity range, visible type -
  closes the stored-then-blank silent-breakage gap; covered by
  unit tests (17 pass)
Long boards append new content at the end; the pill appears only
when the reader is more than ~3/4 of a viewport above the bottom
(scroll + resize tracked) and smooth-scrolls the stage. Hidden in
print.
first click landed 4.4k px short on the image-heavy board — the
page grows under the smooth scroll. Re-jump on content resize
until ~700ms of height quiet; any user input cancels.
resize-quiet ended the chase mid-download (network fetches emit no
resize events; still 4.4k px short). Pin-loop instead: 300ms tick,
wait while the smooth scroll moves, pin scrollTop when stalled
short, stop once height holds at the bottom (20s cap, user input
cancels).
Stale packages/viewer/build (tsc output bundled by build:server) rode
COPY . . into the image and shipped an old core whitelist: the live
viewer rejected and mis-rendered ImageLayers boards. Hermetic context
= always build from clean source.
pages[] is a plain-data prop (MapDetail.items convention): only
the active page's tree is resolved, so off-page images are never
fetched — a 400-node board pays for one page. Validator walks
every page tree at push time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants