This repository was archived by the owner on Oct 4, 2026. It is now read-only.
Repository navigation
feat(ui): image map live refresh, status footer, hover thumbnails - #41
Merged
Merged
Conversation
lstein
marked this pull request as draft
August 2, 2026 22:24
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 2, 2026 22:38
aa21512 to
32045e9
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
2 times, most recently
from
August 3, 2026 01:38
3a5662d to
84be196
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 3, 2026 01:38
32045e9 to
bf26409
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 3, 2026 02:26
84be196 to
7960b81
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 3, 2026 02:26
bf26409 to
0015963
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 3, 2026 18:19
7960b81 to
f15c9c5
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 3, 2026 18:20
0015963 to
32b12f4
Compare
lstein
marked this pull request as ready for review
August 3, 2026 18:28
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 4, 2026 01:43
f15c9c5 to
e55f4db
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 4, 2026 01:43
32b12f4 to
2d68eb8
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 4, 2026 05:37
e55f4db to
3545b1f
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
2 times, most recently
from
August 4, 2026 14:58
312156e to
8a2e9a2
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
2 times, most recently
from
August 4, 2026 22:32
343998e to
cc30d46
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
2 times, most recently
from
August 5, 2026 00:31
c47f5ae to
e239eca
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 5, 2026 00:31
cc30d46 to
16273bb
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 5, 2026 00:38
e239eca to
78b62a5
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 5, 2026 00:38
16273bb to
aae9670
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 5, 2026 01:18
78b62a5 to
a43f464
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
2 times, most recently
from
August 5, 2026 01:21
640ce5a to
d36dbbb
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 5, 2026 01:21
a43f464 to
311ecaa
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 7, 2026 20:41
d36dbbb to
a400674
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 7, 2026 20:41
311ecaa to
4bf36a2
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 9, 2026 18:27
a400674 to
229ff97
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 9, 2026 18:27
4bf36a2 to
29de7d4
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 13, 2026 03:21
229ff97 to
df50a10
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 13, 2026 03:21
29de7d4 to
7646455
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 13, 2026 03:26
df50a10 to
7ff185d
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 13, 2026 03:26
7646455 to
ef0eb5c
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 13, 2026 04:37
7ff185d to
7115d12
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 13, 2026 04:37
ef0eb5c to
353ef22
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 13, 2026 23:17
7115d12 to
d857e04
Compare
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 13, 2026 23:17
353ef22 to
6ad08aa
Compare
lstein
force-pushed
the
feat/image-map-07-marker-zoom
branch
from
August 15, 2026 19:37
57c86da to
328d8db
Compare
- Socket-driven refresh replaces polling: an `image_map_projection_ready` event (routed by the backend to the owning user) or a socket reconnect refetches the point set — but only once the widget has loaded the map, so users who never open it pay nothing. Contracts for the two new backend events are added to the typed socket event map. - New widget footer: point count, an "updating…" hint while the served projection is stale, embedding-index progress (admins are the only recipients of those events), and a manual refresh button. - Hover previews: dwelling on a point for 150ms shows its thumbnail beside the cursor (PhotoMapAI behavior). Thumbnail URLs are cached per session — unlike full DTOs they derive from the immutable image name. The plot needs no explicit theming work: its background is transparent and axes are hidden, so it inherits every workbench theme for free. Amended during the rebase onto the re-architected webv2: - The socket event contracts moved out of the deleted shared event map (`workbench/backend/events.ts`): the two image-map events are domain contracts, so they now live in `workbench/image-map/events.ts` as plain interfaces (the queue feature's `data/events.ts` is not a public queue module, and `socketHub.on` is stringly-typed, so no shared map is needed). The hub itself is imported from `@platform/transport/socketHub` (same on/onConnectionChange surface as the old workbench hub). - The data runtime moved to main's host slot: `hasHost: true` on the eager manifest with `host: ImageMapDataRuntime` returned from the deferred implementation (queue/workflow pattern); the component wraps `attachImageMapDataRuntime` in `useMountEffect`. The footer likewise ships through the implementation's `footer` slot instead of the old single-manifest module. - The thumbnail cache resolves names through the gallery feature's public interface (`galleryImages.resolveMany`) instead of the deleted `workbench/gallery/api`; the footer's IconButton/Tooltip come from `@platform/ui`, and `WidgetViewProps` from `@workbench/widgetContracts`. - The runtime test mocks `@platform/transport/socketHub` at its new path; the store and its tests were otherwise untouched by the move. - Performance baselines recaptured (build + browser, browser verified passing twice): mounting the singleton host at editor boot fetches the image-map implementation chunk on every editor route (+2 script requests, editor owned bytes +34 for the eager `hasHost` flag). Plotly itself stays behind the lazy plot import and no map data is fetched at boot — the runtime only refreshes a map the user has already opened. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Review fix: the thumbnail cache and index-count state are account-owned resources — cleared on account invalidation, with scope-guarded caching.
…at/image-map-07-marker-zoom The base was rebased, so this branch was replayed onto its new tip and the two stale baseline-regeneration commits from the previous catch-up merges were dropped. These numbers come from a fresh build of the replayed tree.
lstein
force-pushed
the
feat/image-map-08-live-theme
branch
from
August 15, 2026 21:16
79f59b2 to
cc3029f
Compare
…d, and other users Six findings from an adversarial review of the live-refresh work: - A failed WebGL canvas is left alone by socket-driven refreshes. Every successful refresh clears `renderError`, which was designed as the user's deliberate retry; now that events refresh on their own, that cleared the error, remounted the plot into the same failure, and flickered once per event for the length of a backfill. - The "has the map loaded" guard tests `loaded`/`error` rather than `!== 'idle'`. `loading` passed, so an event during the first fetch queued a rerun and forced a second full point set the moment the first settled. - A hover preview survives a refresh. Its teardown lived in the cleanup of an effect keyed on `points`, so on a busy server the thumbnail vanished mid-hover and never returned — `Plotly.react` resets hover state and no new `plotly_hover` fires until the pointer moves. Staleness is now derived from whether the hovered image is still on the map. - The preview flips to the other side of the cursor when it would not fit. It is `position: fixed`, so near the right or bottom edge it was simply off-screen with no way to scroll to it. - `image_map_projection_ready` is ignored when it names another user. Admins receive every user's projection events, and an admin refetch usually finds its own all-images projection stale and enqueues another full recompute, so one user's activity drove UMAP fits on every admin's client. Unknown on either side still refreshes: single-user mode has no session user. - The status event carries `failed`, and the footer reports it. It is the only thing that explains an index that settles below `total`, so a backfill that gave up on some images used to just stop short with no explanation. Browser baselines regenerated: `hasHost: true` puts the widget's host runtime in the editor's initial bundle by design (+2 requests, +6 modules), which is the same budget change the original branch recorded before the rebase.
… editor entry Follow-ups from a second review of the same work: - The hover resolution no longer checks `disposed`. That flag belongs to the scene effect, which re-runs on every socket-driven refresh rather than only on unmount, so a refresh landing mid-dwell still dropped the thumbnail — the exact failure the previous round set out to remove, reached by another path. Unmount is already covered by the session bump in the cleanup. - The connection handler refreshes only on a real disconnected -> connected transition. `onConnectionChange` replays the current status synchronously on subscribe, and this runtime is remounted by every Launchpad -> Editor navigation, so an already-connected socket was refetching the whole point set on each entry into the editor. - The footer renders only for the `ready` state. `disabled`, `model_missing` and `empty` all carry data too, so "Image indexing is off" got a `0 points` line and a Refresh button under it, and `computing` showed a second refresh control beside its own "Check again". Not fixed here: a preview hidden because its image left the map can reappear if a later refresh restores that image while the pointer has not moved. Both mechanisms for retiring it — clearing state from an effect, or reading a ref during render — are rejected by the project's lint rules, and the case is too narrow to justify a suppression in production code. Filed instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR 8 of the Image Map stack:
image_map_projection_ready(routed by the backend to the owning user) or a socket reconnect refetches the points — but only once the widget has loaded the map, so users who never open it pay nothing. A refresh requested mid-flight is queued and runs when the current fetch settles (the projection-ready-during-initial-GET window is exactly when the event matters most). Typed contracts for both new events are added to the socket event map.An adversarial review covering this diff was run; its findings (swallowed mid-flight refresh, hover race) are fixed above. Accepted trade-offs it confirmed: after first load, projection-ready events refetch for the session even with the widget closed (host runtimes are always mounted; admin scope makes any user's activity relevant); the thumbnail-URL cache grows by name→string entries only.
Testing
Runtime attach/detach with mocked socketHub (gating on load state, reconnect refresh, index-count storage), store chaining semantics. Full suite green at the stack top.
🤖 Generated with Claude Code