explorer: surface 'Fetching sample index…' during cold-cache boot→point-mode wait (#190 fix 2)#191
Conversation
isamplesorg#190 fix 2) On a cold-cache deep-link to a point-mode altitude, DuckDB-WASM 1.24.0 falls back to a full HTTP read of samples_map_lite.parquet (~60 MB) and the res8 cluster file, blocking sample dots for 60-90s. Today the only phase-message signal during that wait is the internal "Loading H3 res8..." string, followed by a misleading "Zoom closer for individual samples." flash before point mode finally fires "Loading individual samples...". Fix the user-facing signal independently of the version bump (issue isamplesorg#190 fix 1, blocked on UBIGINT representation changes in newer wasm builds): - loadRes() now accepts opts.loadingMsg / opts.suppressDoneMsg so callers can override the default phase text and skip the intermediate done message when the next step will overwrite it anyway. - Camera handler's cluster→point branch passes loadingMsg='Fetching sample index…' and suppressDoneMsg=true, so the cold-cache wait shows one coherent message instead of three transient ones. Other loadRes callers (sourceFilter handler, cluster-mode resolution changes) keep the original "Loading H3 res\${res}..." behavior unchanged. Refs isamplesorg#190. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex review (gpt-5.4)Asked Codex to review focusing on correctness, race interaction with 1. Medium —
|
…ew round 2) Address Codex review of isamplesorg#191: 1. loadRes now returns true only when it applied fresh data; false on stale-generation supersession or caught failure. Camera handler's cluster→point branch uses the return value to gate enterPointMode() instead of treating a normal `await` return as success — fixes the pre-existing race where a source-filter change during the 60-90s cold-cache wait can supersede the res8 load (currentRes is still 4 at that point) and the camera handler would otherwise enter point mode with res8 not actually loaded. 2. Add matching generation guard in loadRes's catch block — a stale failure must not overwrite UI state owned by a newer in-flight call. Same shape applied to the `finally` so a stale call's cleanup can't clear `loading` while a newer load is still in flight. 3. Add opts.errorMsg so the camera-handler boot path can surface "Failed to fetch the sample index — try zooming out and back in." instead of the internal "Failed to load H3 res8 — try zooming again." Coherent with the new "Fetching sample index…" loading copy. 4. Camera handler also re-checks altitude (user may have zoomed back out during the wait) and `mode` (another path may have already entered point mode) before enterPointMode(). Also widen the inline comment to acknowledge the override messages apply to any cluster→point transition where currentRes !== 8, not just cold-cache boot — intentional, the copy reads sensibly for both. Refs isamplesorg#190. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 2: addressed all three Codex findings in e0f70ef
What the user sees now (cold-cache deep-link, three race scenarios)
Diff for round 2 is +49/-13, all in |
Codex round-2 reviewConfirmed clean
Residual finding (Medium/Low) — supersession can leave point transition stalled
The bad-state-entry is gone (which was the actual correctness bug). What remains is a liveness gap: if a user toggles the source filter during the 60-90s cold-cache wait, they'll stay in cluster mode at point altitude until they pan/zoom the camera even a tiny bit. Codex's suggested fix shapes:
RecommendationThe pre-existing PR was scoped to the cold-cache UX fix; the supersession liveness gap is a marginal scenario (user changes source filter during the boot wait) on a code path that has had similar ergonomic gaps for a while. Suggest:
Open to either path. If we keep round-2 as the final state of this PR, the residual finding gets a tracking issue. If we want to land it here, I'd take shape (1) — call the camera-mode reconciliation from the source-filter handler's |
…eness gap (review round 3)
Address Codex round-2 residual finding: in round 2, the camera handler
correctly refused to call enterPointMode() when its awaited res8 load
came back stale (false). But in the source-filter supersession case,
no camera event necessarily fires afterwards, so the user could end up
stranded — camera below ENTER_POINT_ALT, mode === 'cluster',
currentRes === 4, loading === false — until they nudged the camera.
Factor the cluster→point transition into an idempotent
tryEnterPointModeIfNeeded() helper. Two callers:
1. Camera handler's `targetMode === 'point' && mode !== 'point'`
branch — the normal cold-cache deep-link path.
2. Source-filter handler's `mode === 'cluster'` branch, immediately
after its own loadRes(currentRes, ...) settles — drives the
cluster→point transition forward when the user toggled the
filter mid-way through the boot wait.
Helper short-circuits if already in point mode or above
ENTER_POINT_ALT, and re-checks both after its own (potentially long)
res8 await for the same reasons round 2 already handled (zoom-back-out
during wait, another path entered point mode first).
Net change: 1 new ~20-line helper, camera-handler block shrinks by
~30 lines (delegated to helper), source-filter handler gains a single
`await tryEnterPointModeIfNeeded()` call.
Refs isamplesorg#190.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 3: closes the supersession liveness gap in 1f109e0Addressed Codex's round-2 residual finding by factoring the cluster→point transition into an idempotent helper, then calling it from the source-filter handler too: async function tryEnterPointModeIfNeeded() {
if (mode === 'point') return;
if (viewer.camera.positionCartographic.height >= ENTER_POINT_ALT) return;
let res8Ready = currentRes === 8;
if (!res8Ready && !loading) {
res8Ready = await loadRes(8, h3_res8_url, {
loadingMsg: 'Fetching sample index…',
suppressDoneMsg: true,
errorMsg: 'Failed to fetch the sample index — try zooming out and back in.',
});
}
const hNow = viewer.camera.positionCartographic.height;
if (res8Ready && mode !== 'point' && hNow < ENTER_POINT_ALT) {
enterPointMode();
}
}Call sites
Walkthrough of the previously-broken supersession path
Other paths considered
StatsRound 3 diff: +54/-39 (net +15 lines; the camera-handler block shrinks by ~30 lines, replaced by the ~30-line helper + 2-line delegation). The full PR (rounds 1+2+3) is now +88/-12 in |
Codex follow-up review + fixFound one remaining Medium/Low liveness gap after Round 3: Repro shape:
Applied fix in 5bb5a78: after camera cluster-resolution Validation:
Working tree is clean and the fix is pushed to the PR branch. |
Code Review (final state, post-round 3 + 5bb5a78)Branch: OverviewImplements fix 2 of #190: surfaces a coherent loading message during the cold-cache 60-90s Three logical layers landed together:
Correctness — looks solid
Edge cases worth flagging
Style / project conventions
Suggestions (non-blocking)
Risks
VerdictReady to merge modulo a manual smoke-test of the source-filter-during-cold-cache-wait scenario. The three review rounds tightened a real correctness bug (round 2) and a real liveness gap (round 3) that had nothing to do with the original UX-message ambition — solid surfacing-by-fix-attempt outcome. Suggest a small follow-up for: (a) Playwright coverage of |
Codex independent reviewI re-reviewed the final PR state after FindingsNo blocking findings. What I verified:
Review notesThe The remaining risks are non-blocking:
VerdictReady to merge. Suggested follow-ups after merge: add focused Playwright coverage for the helper/chase invariant, and optionally smooth the rare failure-message flicker. |
…ed (closes #194) (#195) * explorer: document loadRes-chase invariant on tryEnterPointModeIfNeeded (closes #194) Adds a load-bearing invariant block to the helper's doc comment plus a short pointer above the `!loading` short-circuit. Codifies the contract that every `loadRes` call site in the zoomWatcher cell must be followed by `await tryEnterPointModeIfNeeded()` (or be inside the helper itself, or in a path where the user can't be at point altitude). Without the chase, the helper's `!loading` bail-out can strand the user in cluster mode at point altitude — the exact bug round-3 of #191 fixed by adding the source-filter handler's chase call. Future contributors adding new `loadRes` sites need this invariant called out in the code, not buried in the PR thread. Doc-only change: no behavior change. Closes #194. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * explorer: tighten loadRes-chase invariant doc per Codex review - Switch verification grep from `loadRes\(` to `await[[:space:]]+loadRes\(` so it matches actual call sites (4 lines) instead of also matching the explanatory comments above the helper that mention `loadRes(...)` in prose. - Add "outside this helper" qualifier so the helper's own internal loadRes call is not visually flagged as an exception to the invariant. Doc-only refinement of #194 fix; no behavior change. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Implements fix 2 from #190 — surfaces a coherent loading message during the cold-cache
boot→point-modegap so deep-links to a point-mode altitude don't look broken during the 60-90s wait. Three review rounds also tightened underlying race / liveness bugs inloadResthat the longer user-visible window made unsafe.Independent of fix 1 (move off Quarto's bundled DuckDB-WASM 1.24.0), which is blocked on UBIGINT representation changes in newer wasm builds (see spike result comment).
Phase-message sequence (cold-cache deep-link, alt < 120 km)
Before:
Loading H3 res8...— internal jargon, fires ~immediately, persists ~85 s175,653 clusters, X samples. Zoom closer for individual samples.— flashes for one frame; misleading because the user is already zoomed inLoading individual samples...— fires from insideloadViewportSamples47 individual samples. Click one for details.After:
Fetching sample index…— fires ~immediately, persists during the cold-cache waitLoading individual samples...47 individual samples. Click one for details.On failure:
Failed to fetch the sample index — try zooming out and back in.(instead ofFailed to load H3 res8 — try zooming again.).Implementation (final state)
1. New
loadResopts (round 1)opts.loadingMsg— override the default"Loading H3 res${res}..."text.opts.suppressDoneMsg— skip the interim"... Zoom closer for individual samples."done message that would otherwise flash before point mode overwrites it.opts.errorMsg— override the default"Failed to load H3 res${res} — try zooming again."failure copy.2. Hardened
loadResrace contract (round 2)trueonly when fresh data was applied andcurrentRes = res;falseon stale-generation supersession or caught failure. Callers that gate follow-up work on the cluster resolution being ready (e.g. beforeenterPointMode) must use the return value.catchblock now has the same generation guard as the success path, so a stale failure can't overwrite UI state owned by a newer in-flight call.finallyonly releases theloadingbusy-flag if the call is still the current generation, so a stale call's cleanup can't clear the flag while a newer load is still in flight.3. Idempotent
tryEnterPointModeIfNeeded()helper (round 3 + 5bb5a78)Centralizes the cluster→point transition. Called from four
loadRessites that could be in-flight when the user crosses belowENTER_POINT_ALT:targetMode === 'point' && mode !== 'point'branch (the original cold-cache deep-link path).mode === 'cluster'branch, after its ownloadRes(currentRes, ...)settles.The helper short-circuits if already in point mode or above
ENTER_POINT_ALT, and re-checks both after its own (potentially long)loadResawait. Closes the supersession liveness gap where a source-filter toggle during the boot wait would supersede the camera handler's pending res8 load and strand the user in cluster mode at point altitude until they nudged the camera.Stats
+101/-12 across four commits, all in
explorer.qmd. No tests added (deterministic parts oftryEnterPointModeIfNeededwould benefit from Playwright coverage — tracked as a follow-up).Test plan
Fetching sample index…appears and persists until sample dots render, with noLoading H3 res8...orZoom closer for individual samples.flashes in between.Loading H3 res${res}...→${count} clusters, ${samples} samples. Zoom in for finer detail.).Loading H3 res${res}...still appears (default loadRes message), no spuriousFetching sample index…flicker because the helper short-circuits at the altitude check.Fetching sample index…is showing. Confirm: phase message switches toLoading H3 res${currentRes}...while the source-filter's loadRes runs, then back toFetching sample index…for the recovery loadRes(8), then point mode is entered. Do not end up in cluster mode at point altitude.Loading individual samples...still appears.Follow-ups (post-merge)
Three follow-up issues filed:
tryEnterPointModeIfNeededinvariants — short-circuit atmode === 'point'/ aboveENTER_POINT_ALT, source-filter→cluster chase path, supersession recovery.tryEnterPointModeIfNeeded()call paintsFetching sample index…over a just-paintedFailed to load H3 res${target}from a failed cluster reload.loadRescaller chases withtryEnterPointModeIfNeeded" invariant near the helper definition, to make the supersession-recovery contract self-evident for future contributors adding newloadRessites.Refs
🤖 Generated with Claude Code