You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Deep-link a viewer URL whose altitude lands inside point mode (alt < 120 km) and the viewport stays empty for ~60–90 seconds on first visit before any dots appear. At wider altitudes the page looks fine because the global res4 cluster set (38K dots) is already painted from Phase 1.
12:52:12 Phase 1: 38406 clusters in 192ms (res4 global, 580 KB)
12:52:13 WARN falling back to full HTTP read for: …samples_map_lite.parquet (60 MB)
12:52:13 WARN falling back to full HTTP read for: …isamples_202601_h3_summary_res8.parquet
12:53:37 Res8: 175653 clusters in 458ms (≈85 s after boot)
12:53:37 Entered point mode
12:53:37 Point mode: 47 samples in 22ms (dots finally appear)
The state machine is doing the right thing — zoomWatcher honors deep-link altitude, fires enterPointMode(), calls loadViewportSamples(). The wait is all in the network.
Root cause — DuckDB-WASM 1.24.0 client behavior, not the CDN
Initial hypothesis was that data.isamples.org Range support was broken. Live captures (curl + Playwright) show the server is fine:
GET with Range: bytes=0-1023 → HTTP/2 206, Content-Range: bytes 0-1023/62631623 ✔
OPTIONS preflight returns 204 with Access-Control-Allow-Headers: Range and the right Access-Control-Expose-Headers ✔
HEAD returns 200 with full Content-Length + Accept-Ranges: bytes. (RFC 7233 §3.1 lets a server ignore Range on HEAD; this is correct.)
The actual sequence emitted by the DuckDB-WASM worker (per Codex's Playwright capture):
HEAD samples_map_lite.parquet Range: bytes=0-
RESP 200, content-length=62631623, accept-ranges=bytes
console: falling back to full HTTP read
GET samples_map_lite.parquet (no Range header)
RESP 200, content-length=62631623 ← entire 60 MB pulled
So DuckDB-WASM 1.24.0 treats the HEAD-with-Range 200 (no Content-Range) as failure, logs falling back to full HTTP read, and re-fetches the file with no Range header — pulling all 60 MB on cold cache. The file is cache-control: public, max-age=31536000, immutable, so reload visits hit disk cache and look instant; cold visits eat the full 60 MB.
The pinned version is in Quarto's bundled OJS runtime, not the project:
At alt=136,823, zoomWatcher stays in cluster mode and only needs the 2.5 MB res8 file. The same wasteful "fallback then full read" still happens, but 2.5 MB completes in 1–2 s and the 175K res8 clusters paint immediately. The zoomed-in URL trips the much larger samples_map_lite.parquet fetch, which is why the symptom is altitude-dependent.
Recommended fixes (ordered)
Move off Quarto's bundled DuckDB-WASM 1.24.0. Either:
bump the Quarto bundle if a newer Quarto ships a fixed runtime, or
bypass DuckDBClient.of() and instantiate a newer DuckDB-WASM directly from CDN inside explorer.qmd's OJS cells, so we control the version regardless of Quarto. Either path requires verifying the wasm worker no longer logs falling back to full HTTP read on the same files. Worth a quick spike against @duckdb/duckdb-wasm@1.28.x (or current latest) before committing.
Surface the loading state during the boot→point-mode gap. Today updatePhaseMsg('Loading individual samples...', 'loading') only fires from inside loadViewportSamples (explorer.qmd:1451), but the camera handler already awaits loadRes(8, h3_res8_url) first (explorer.qmd:1838). The user gets no signal during the slow initial load. A "Fetching sample index…" message earlier in the chain converts "page is broken" into "page is busy" — and is worth shipping independently of fix (1), since 60 MB on a slow connection will always be a multi-second wait the user needs to see.
# Use GET, not HEAD — many servers (correctly per RFC 7233) ignore Range on HEAD,# so HEAD is not a reliable pass/fail check.
curl -s -o /dev/null -D - -H 'Range: bytes=0-1023' \
https://data.isamples.org/isamples_202601_samples_map_lite.parquet | head -10
# Expect: HTTP/2 206 + Accept-Ranges: bytes + Content-Range: bytes 0-1023/<size># Confirmed 206 as of 2026-05-10 — server is not the bug.
Symptom
Deep-link a viewer URL whose altitude lands inside point mode (alt < 120 km) and the viewport stays empty for ~60–90 seconds on first visit before any dots appear. At wider altitudes the page looks fine because the global res4 cluster set (38K dots) is already painted from Phase 1.
Repros (live deploy, current HEAD):
Reported by Raymond Yee, debugged via Playwright against prod 2026-05-09 / 2026-05-10.
Timing (live console log, alt=62054 URL, cold cache)
The state machine is doing the right thing —
zoomWatcherhonors deep-link altitude, firesenterPointMode(), callsloadViewportSamples(). The wait is all in the network.Root cause — DuckDB-WASM 1.24.0 client behavior, not the CDN
Initial hypothesis was that
data.isamples.orgRange support was broken. Live captures (curl + Playwright) show the server is fine:GETwithRange: bytes=0-1023→HTTP/2 206,Content-Range: bytes 0-1023/62631623✔OPTIONSpreflight returns204withAccess-Control-Allow-Headers: Rangeand the rightAccess-Control-Expose-Headers✔HEADreturns200with fullContent-Length+Accept-Ranges: bytes. (RFC 7233 §3.1 lets a server ignoreRangeonHEAD; this is correct.)The actual sequence emitted by the DuckDB-WASM worker (per Codex's Playwright capture):
So DuckDB-WASM 1.24.0 treats the HEAD-with-Range
200(noContent-Range) as failure, logsfalling back to full HTTP read, and re-fetches the file with noRangeheader — pulling all 60 MB on cold cache. The file iscache-control: public, max-age=31536000, immutable, so reload visits hit disk cache and look instant; cold visits eat the full 60 MB.The pinned version is in Quarto's bundled OJS runtime, not the project:
docs/site_libs/quarto-ojs/quarto-ojs-runtime.js:228—const duckdb = dependency("@duckdb/duckdb-wasm", "1.24.0", "+esm");Why the wider URL looks fine
At alt=136,823,
zoomWatcherstays in cluster mode and only needs the 2.5 MB res8 file. The same wasteful "fallback then full read" still happens, but 2.5 MB completes in 1–2 s and the 175K res8 clusters paint immediately. The zoomed-in URL trips the much largersamples_map_lite.parquetfetch, which is why the symptom is altitude-dependent.Recommended fixes (ordered)
Move off Quarto's bundled DuckDB-WASM 1.24.0. Either:
DuckDBClient.of()and instantiate a newer DuckDB-WASM directly from CDN insideexplorer.qmd's OJS cells, so we control the version regardless of Quarto. Either path requires verifying the wasm worker no longer logsfalling back to full HTTP readon the same files. Worth a quick spike against@duckdb/duckdb-wasm@1.28.x(or current latest) before committing.Surface the loading state during the boot→point-mode gap. Today
updatePhaseMsg('Loading individual samples...', 'loading')only fires from insideloadViewportSamples(explorer.qmd:1451), but the camera handler alreadyawaitsloadRes(8, h3_res8_url)first (explorer.qmd:1838). The user gets no signal during the slow initial load. A "Fetching sample index…" message earlier in the chain converts "page is broken" into "page is busy" — and is worth shipping independently of fix (1), since 60 MB on a slow connection will always be a multi-second wait the user needs to see.Independent of (1)/(2): re-verify halo/terrain rendering once samples load. After the slow load completed in my repro, "5,000 samples" rendered but were hard to spot at the test viewport size — possibly intersects explorer: halo crescents / disappearing dots over hilly terrain (root cause not yet identified) #185.
Diagnostic command (corrected)
Refs
freshSelectionToken) are correctness fixes around this same boot path; this issue is about the latency of the same path.