Skip to content
This repository was archived by the owner on Oct 4, 2026. It is now read-only.

feat(ui): image map current-image marker, focal-point zoom, auto-recenter - #40

Merged
lstein merged 4 commits into
mainfrom
feat/image-map-07-marker-zoom
Aug 15, 2026
Merged

lstein merged 4 commits into
mainfrom
feat/image-map-07-marker-zoom

Conversation

@lstein

@lstein lstein commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

PR 7 of the Image Map stack — PhotoMapAI's interaction core:

  • The gold current-image target tracks the gallery selection live: any selection restyles just the one-point marker trace, and when the marker drifts within 10% of a view edge the map recenters on it, preserving zoom width. Map-originated selections never recenter under the cursor (time-limited suppression stamps, so stale entries cannot suppress future recenters). The marker uses hoverinfo: 'skip' so it never shadows the point beneath it for clicks/hovers.
  • Custom focal-point wheel zoom + two-finger pinch (exp gain, ctrl+wheel = trackpad pinch), replacing plotly's scrollZoom (Safari issues). Coincident-touch pinch starts and zero/negative zoom factors are guarded; pinch-end synthetic clicks are suppressed including held-still pinches.
  • View preservation done right: each data refresh feeds the current axis ranges back into Plotly.react — verified against the bundled plotly source that uirevision alone does not preserve ranges set via the public relayout API. Percentile ranges (1st–99th) seed only the very first render.
  • All plotly restyle/relayout calls swallow rejections (WebGL-failure environments already surface an error through the store).

An adversarial review of this diff was run (it verified plotly semantics against the bundled source); all five findings — the false uirevision contract, the suppression-ref protocol breaks, pinch collapse, held-pinch click leak, and unhandled rejections — are fixed above, with tightened exact-value tests for the zoom math.

Testing

Pure viewport math fully unit-tested (percentiles incl. outliers/degenerate inputs, focal-invariant zoom + inverse property, exact wheel gains, non-positive factor clamp, width-preserving recenter, pad band). 907-test suite + lint + build pass at the stack top.

🤖 Generated with Claude Code

@lstein
lstein requested a review from blessedcoolant as a code owner August 2, 2026 21:49
@github-actions github-actions Bot added the python label Aug 2, 2026
@lstein
lstein marked this pull request as draft August 2, 2026 22:23
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from e01c3ee to 3a5662d Compare August 2, 2026 22:38
@lstein
lstein force-pushed the feat/image-map-06-floating branch from 035de15 to d36b46d Compare August 3, 2026 01:38
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from 3a5662d to 84be196 Compare August 3, 2026 01:38
@lstein
lstein force-pushed the feat/image-map-06-floating branch from d36b46d to 0a64ade Compare August 3, 2026 02:26
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch 2 times, most recently from 7960b81 to f15c9c5 Compare August 3, 2026 18:19
@lstein
lstein marked this pull request as ready for review August 3, 2026 18:28
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from f15c9c5 to e55f4db Compare August 4, 2026 01:43
@lstein
lstein force-pushed the feat/image-map-06-floating branch 2 times, most recently from a58e9c7 to d7e56a7 Compare August 4, 2026 05:37
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from e55f4db to 3545b1f Compare August 4, 2026 05:37
@lstein
lstein force-pushed the feat/image-map-06-floating branch from d7e56a7 to 925649a Compare August 4, 2026 14:58
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch 2 times, most recently from 343998e to cc30d46 Compare August 4, 2026 22:32
@lstein
lstein force-pushed the feat/image-map-06-floating branch 2 times, most recently from 08aa934 to ef19bf0 Compare August 5, 2026 00:31
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from cc30d46 to 16273bb Compare August 5, 2026 00:31
@lstein
lstein force-pushed the feat/image-map-06-floating branch from ef19bf0 to 8666f99 Compare August 5, 2026 00:38
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from 16273bb to aae9670 Compare August 5, 2026 00:38
@lstein
lstein force-pushed the feat/image-map-06-floating branch from 8666f99 to dc89f69 Compare August 5, 2026 01:18
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from aae9670 to 640ce5a Compare August 5, 2026 01:18
@lstein
lstein force-pushed the feat/image-map-06-floating branch from dc89f69 to b3136de Compare August 5, 2026 01:21
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from 640ce5a to d36dbbb Compare August 5, 2026 01:21
@lstein
lstein force-pushed the feat/image-map-06-floating branch from b3136de to 5a1e4a4 Compare August 7, 2026 20:41
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from d36dbbb to a400674 Compare August 7, 2026 20:41
@lstein
lstein force-pushed the feat/image-map-06-floating branch from 5a1e4a4 to cb0be51 Compare August 9, 2026 18:27
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from a400674 to 229ff97 Compare August 9, 2026 18:27
@lstein
lstein force-pushed the feat/image-map-06-floating branch from cb0be51 to 78a6155 Compare August 13, 2026 03:21
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from 229ff97 to df50a10 Compare August 13, 2026 03:21
@lstein
lstein force-pushed the feat/image-map-06-floating branch from 78a6155 to 17df57d Compare August 13, 2026 03:26
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from df50a10 to 7ff185d Compare August 13, 2026 03:26
@lstein
lstein force-pushed the feat/image-map-06-floating branch from 17df57d to 73f1fd4 Compare August 13, 2026 04:36
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from 7ff185d to 7115d12 Compare August 13, 2026 04:37
@lstein
lstein force-pushed the feat/image-map-06-floating branch from 73f1fd4 to 0dc1f2d Compare August 13, 2026 23:17
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from 7115d12 to d857e04 Compare August 13, 2026 23:17
lstein and others added 3 commits August 15, 2026 15:23
…nter

Ports PhotoMapAI's interaction core to the Image Map widget:

- The gold "Current Image" target now tracks the gallery selection live:
  selecting anywhere (gallery grid, arrows, the map itself) restyles just
  the single-point marker trace, and when the marker drifts within 10% of
  a viewport edge the view recenters on it, preserving the zoom width.
  Selections that originate from a map click never recenter the map under
  the user's cursor.
- Custom focal-point wheel zoom and two-finger pinch (exp gain, ctrl+wheel
  treated as trackpad pinch), replacing plotly's scrollZoom for its
  long-standing Safari issues. Pinch-end synthetic clicks are suppressed.
- Initial view fits the 1st-99th percentile of coordinates so a few
  outliers cannot dwarf the map; `uirevision` preserves the user's pan and
  zoom across data refreshes.

All viewport math (percentile ranges, focal-invariant zoom, width-
preserving recenter) lives in a pure module with unit tests; the plotly
host only glues events to it.

Amended during the rebase onto the re-architected webv2:

- The current-selection accessor now comes from the gallery feature's
  public interface: `getSelectedGalleryImageFromValues` is imported from
  `@features/gallery/contracts` (the same helper PreviewWidgetView uses)
  instead of the old `@workbench/image-actions/selectedImage`. It resolves
  main's mixed image/video `GalleryItem` selection and already returns
  images only, so a video selection simply clears the gold marker.
- `useWidgetValuesSelector('gallery', ...)` from @workbench/
  WorkbenchContext is unchanged and keeps feeding the selector; the
  map-click suppression handshake with useSelectMapImage still matches by
  image name, which survives the GalleryItem round-trip.
- performance/architecture-baseline.json reformatted (one array collapsed
  to a single line) to satisfy oxfmt; the drift came in with the earlier
  ported baselines. No baseline values changed and the build-time
  performance check passes without a recapture — all new code lands in
  the lazy plotly widget chunk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rectness

Six findings from an adversarial review of the marker/zoom/recenter work:

- Wheel deltas are normalized by `deltaMode` before the exp() gain. Firefox
  reports a classic mouse wheel in LINES (deltaY +/-3), so read as pixels one
  notch zoomed 0.3% — ~230 notches to double the view — and because the
  handler preventDefaults, the user got neither zoom nor scroll.
- The trackpad-pinch gain now applies only to trackpad-sized deltas. Browsers
  report a pinch as ctrl+wheel, but so is ctrl held over a real mouse, where
  the 10x gain on a 100px delta zoomed 2.7x in a single notch. A per-event
  clamp backstops every device.
- The initial whole-map fit is chained off `Plotly.Plots.resize` rather than
  fired alongside it. The resize defers its own autosize relayout, so the fit
  was solved against the plot's previous dimensions — and with the axes
  scale-anchored, that re-solve crops one of them. The case that bites is a
  map mounted in a collapsed panel that is then opened.
- `initialFitDoneRef` resets when the plot is purged. It stood for a layout
  that purging destroys, so any remount of the same component instance —
  StrictMode's double-mount in development — permanently lost the fit and
  rendered the cropped sliver the fit exists to prevent.
- A third finger landing mid-pinch no longer strands the baseline. Going
  2 -> 3 -> 2 fingers left the original distance in place while the fingers
  moved, so the next touchmove applied an arbitrary factor in one frame.
- Map-click suppression is consumed before the no-marker return, and expired
  stamps are dropped unconditionally. A click whose selection never arrived
  (image deleted, or resolved to a non-image) left a stamp that swallowed the
  recenter for a later, unrelated gallery pick of the same name.
…at/image-map-06-floating

The stale regeneration commit from the previous catch-up merge was dropped in
the rebase; these numbers come from a fresh build of the rebased tree. Only
the compressed sizes moved — the owned-byte and request counts were already
correct on the base, which carries the image map's own sources.
@lstein
lstein force-pushed the feat/image-map-07-marker-zoom branch from 57c86da to 328d8db Compare August 15, 2026 19:37
Base automatically changed from feat/image-map-06-floating to main August 15, 2026 21:11
@lstein
lstein enabled auto-merge August 15, 2026 21:36
@lstein
lstein merged commit a6deb5d into main Aug 15, 2026
19 checks passed
@lstein
lstein deleted the feat/image-map-07-marker-zoom branch August 15, 2026 21:36
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant