Skip to content

feat(ui): add Image Map widget with plotly scatter and click-to-select - #38

Open
lstein wants to merge 1 commit into
feat/image-map-04-projectionfrom
feat/image-map-05-widget
Open

feat(ui): add Image Map widget with plotly scatter and click-to-select#38
lstein wants to merge 1 commit into
feat/image-map-04-projectionfrom
feat/image-map-05-widget

Conversation

@lstein

@lstein lstein commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

PR 5 of the Image Map stack — the first frontend layer. Adds the "Image Map" workbench widget: a semantic 2D map of the gallery (backed by /v1/image_map) rendered as a plotly scattergl scatter, cluster-colored with DBSCAN noise dimmed.

  • Click-to-select: clicking a point hydrates the image via the bulk by-names endpoint and dispatches selectGalleryImage, so Preview follows automatically. A monotonic sequence guard means rapid clicks always resolve to the latest click, hydrate failures are caught (no unhandled rejections), and DTOs are fetched fresh (no stale star/board state).
  • Plotly integration: imperative plotly on a ref div (no react-plotly.js — unmaintained, React 19 risk), React.lazy-loaded so plotly.js-gl2d-dist-min ships as its own vite chunk (~504KB gz, verified absent from the eager vendor chunk). uirevision preserves pan/zoom across data refreshes.
  • Placement: right rail between Gallery and Preview in all built-in presets; users with persisted layouts reach it via the rail menu. Graceful states for indexing-disabled / computing / empty / error — and a working map is never replaced by an error screen when a refresh fails.
  • Pure trace/layout builders + a small external store keep the plotly host thin and unit-testable.

An adversarial review of this diff was run; its findings (click-ordering race, unhandled rejections, missing uirevision, URLSearchParams.size being newer than the browser baseline, stale-DTO cache, error-state UX) are all fixed above. Remaining noted trade-offs: plot-layer behavior (marker, custom zoom) arrives in the next PRs of the stack; the plotly canvas itself isn't jsdom-testable.

Testing

17 vitest tests (api mapping incl. query params, store transitions incl. error-keeps-data, cluster palette, trace/layout builders, registry). pnpm lint (oxfmt + oxlint + tsc) and pnpm build pass; manual smoke against the dev server pending backend deploy of the stack below it.

🤖 Generated with Claude Code

@lstein
lstein requested a review from blessedcoolant as a code owner August 2, 2026 21:06
@lstein
lstein force-pushed the feat/image-map-05-widget branch from cbcde6b to 0da5cc5 Compare August 2, 2026 21:18
@lstein
lstein marked this pull request as draft August 2, 2026 22:23
@lstein
lstein force-pushed the feat/image-map-05-widget branch from 0da5cc5 to 5fb4304 Compare August 2, 2026 22:38
@lstein
lstein force-pushed the feat/image-map-04-projection branch 2 times, most recently from 5091e5d to ec55e63 Compare August 3, 2026 01:38
@lstein
lstein force-pushed the feat/image-map-05-widget branch 2 times, most recently from 599acac to 72b3041 Compare August 3, 2026 02:26
@lstein
lstein force-pushed the feat/image-map-04-projection branch from ec55e63 to 9a46659 Compare August 3, 2026 02:26
@lstein
lstein marked this pull request as ready for review August 3, 2026 18:28
@lstein
lstein force-pushed the feat/image-map-04-projection branch from 9a46659 to 589a09b Compare August 4, 2026 01:43
@lstein
lstein force-pushed the feat/image-map-05-widget branch from 72b3041 to 8030e09 Compare August 4, 2026 01:43
@lstein
lstein force-pushed the feat/image-map-04-projection branch from 589a09b to bbab55d Compare August 4, 2026 05:37
@lstein
lstein force-pushed the feat/image-map-05-widget branch from 8030e09 to 0be308f Compare August 4, 2026 05:37
@lstein
lstein force-pushed the feat/image-map-04-projection branch from bbab55d to fe1b01c Compare August 4, 2026 14:58
@lstein
lstein force-pushed the feat/image-map-05-widget branch from 0be308f to 8f0e63b Compare August 4, 2026 14:58
@lstein
lstein force-pushed the feat/image-map-04-projection branch from fe1b01c to d1976a2 Compare August 4, 2026 22:32
@lstein
lstein force-pushed the feat/image-map-05-widget branch from 8f0e63b to e4f4132 Compare August 4, 2026 22:32
@lstein
lstein force-pushed the feat/image-map-04-projection branch from d1976a2 to 8754846 Compare August 5, 2026 00:31
@lstein
lstein force-pushed the feat/image-map-05-widget branch 3 times, most recently from 7259190 to 3726790 Compare August 5, 2026 01:18
@lstein
lstein force-pushed the feat/image-map-04-projection branch from 8754846 to 285ea0b Compare August 5, 2026 01:18
Adds the "Image Map" workbench widget: a semantic 2D map of the gallery
(backed by the /v1/image_map API) rendered as a plotly scattergl scatter,
cluster-colored, with DBSCAN noise dimmed. Clicking a point hydrates the
image via the bulk by-names endpoint and selects it in the gallery, so
Preview follows automatically.

- Imperative plotly on a ref div (no react-plotly), lazy-loaded so the
  plotly bundle ships as its own vite chunk (~500KB gz) outside the eager
  vendor chunk, only fetched when the widget renders.
- Placed in the right rail between Gallery and Preview in all built-in
  layout presets; graceful states for indexing-disabled, computing, empty,
  and error (so the widget degrades cleanly if the backend lacks the API).
- Pure trace/layout builders and a small external store keep the plotly
  host thin and the logic unit-testable; pan via plotly dragmode, custom
  zoom and the live current-image marker come in follow-up PRs.

Amended during the rebase onto the re-architected webv2:

- Widget module restructured to the manifest/implementation split: an
  eager manifest.ts with `load: () => import('./implementation')` and an
  i18n label (widgets.labels.imageMap in en.json) replaces the old
  index.ts single-manifest module; implementation.ts registered in
  scripts/widget-sources.mjs so it splits into its own widget chunk.
- Imports moved to the new homes: @workbench/widgetContracts (was
  @workbench/types), @platform/transport/http (was @workbench/backend/
  http), @platform/state/externalStore (was @workbench/externalStore).
- Selection goes through useWorkbenchCommands().gallery.selectItem with
  legacyGeneratedImageToGalleryItem, hydrating via galleryImages
  .resolveMany from @features/gallery (the old workbench/gallery/api.ts
  export is gone).
- 'image-map' added to FirstPartyWidgetTypeId, the workbench ownership
  manifest directoryRules, and workbenchState's default widget states /
  instance types alongside the layout presets.
- Plotly chunking uses the codeSplitting group style (manualChunks is
  gone) and 'plotly' is added to editorForbiddenInitialChunkNames; the
  architecture and browser performance baselines were recaptured to
  acknowledge the eager manifest source and the small owned-bytes growth
  (launchpad +5 B, editor +426 B raw).

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

Review fix: the image-map store is now registered as an account-owned
resource (reset to idle on account invalidation; scope-guarded fetch).
@lstein
lstein force-pushed the feat/image-map-04-projection branch from 285ea0b to 8984cd4 Compare August 5, 2026 01:21
@lstein
lstein force-pushed the feat/image-map-05-widget branch from 3726790 to 43542e6 Compare August 5, 2026 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant