Skip to content

feat: add Cesium 3D globe visualization package - #530

Open
collord wants to merge 1 commit into
sqlrooms:mainfrom
collord:cesium-feature
Open

collord wants to merge 1 commit into
sqlrooms:mainfrom
collord:cesium-feature

Conversation

@collord

@collord collord commented Apr 11, 2026 •

Copy link
Copy Markdown

Introduces @sqlrooms/cesium, a new package for 3D geospatial visualization built on CesiumJS and Resium, along with an example app demonstrating earthquake data visualization on a 3D globe.

Package features:

  • CesiumSlice for state management (camera, clock, layers, scene)
  • SQL-driven entity layers via useSqlToCesiumEntities hook
  • Support for geojson, czml, sql-entities, and tileset layer types
  • Time-animated entities with clock sync
  • Depth testing against terrain, subsurface rendering, and camera height controls
  • CesiumPanel, CesiumClock, CesiumToolbar UI components

Example app (examples/cesium-app):

  • Earthquake explorer loading parquet data from HuggingFace
  • Demonstrates SQL query to 3D entity rendering pipeline
  • Uses vite-plugin-cesium for asset handling

Summary by CodeRabbit

  • New Features
    • New Cesium 3D globe package with interactive viewer, persistent camera, layer management, timeline playback with speed control, camera "zoom to fit", SQL-driven entity layers, viewer hooks, and utilities
    • UI components: embedded clock controls, toolbar, panels, and Cesium viewer wrapper
    • Example app: "Cesium Earthquake Explorer" with preset camera views, slab slicing, live histogram, SQL editor and rendering status overlays
  • Documentation
    • Package and example READMEs with setup, usage, and troubleshooting (including env token example)
  • Chores
    • Added project configs, TypeScript/Vite setup, package manifests, scripts, and .gitignore entries

@coderabbitai

coderabbitai Bot commented Apr 11, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new @sqlrooms/cesium package (components, hooks, types, Zustand slice, utilities, docs) and a complete Cesium example app demonstrating an earthquake visualizer with SQL-backed Cesium entity layers, clock sync, camera persistence, and example build/config files.

Changes

Cohort / File(s) Summary
Package config & tooling
packages/cesium/package.json, packages/cesium/tsconfig.json, packages/cesium/eslint.config.js, packages/cesium/.gitignore
New package manifest, TypeScript/ESLint config, and .gitignore for @sqlrooms/cesium.
Core config & types
packages/cesium/src/cesium-config.ts, packages/cesium/src/cesium-types.ts
Zod schemas, defaults, and TS types for camera, clock, layers, and top-level Cesium slice config.
State slice
packages/cesium/src/cesium-slice.ts
Zustand slice implementing persisted Cesium config plus runtime viewer state and actions (viewer lifecycle, camera save/fly/zoom, clock controls, layer CRUD, clipping-plane handling).
Hooks & utils
packages/cesium/src/hooks/..., packages/cesium/src/utils.ts
Viewer accessor, bidirectional clock sync, SQL→Cesium entity conversion (time/availability handling), and ISO-8601 normalization util.
UI components
packages/cesium/src/components/...
React components: CesiumPanel, ViewerWrapper (lifecycle/terrain), Toolbar (zoom + layer toggles), Clock, and SQL-driven EntityLayer.
Package entry & docs
packages/cesium/src/index.ts, packages/cesium/README.md
Entrypoint re-exports public API and adds package README with usage/config examples.
Example app config
examples/cesium-app/package.json, examples/cesium-app/tsconfig.json, examples/cesium-app/vite.config.ts, examples/cesium-app/index.html, examples/cesium-app/.env.example, examples/cesium-app/.gitignore
New example app manifest, Vite config (React, Tailwind, Cesium plugin), HTML entry, .env example for Cesium Ion token, and .gitignore.
Example app source
examples/cesium-app/src/*, examples/cesium-app/src/index.css, examples/cesium-app/src/vite-env.d.ts
App bootstrap, ThemeProvider/App/Room components, combined room store wiring Cesium slice, Tailwind theming, type refs, panels/controllers, histogram/presets, slab mesh generation, and fetch script.
Example assets & scripts
examples/cesium-app/scripts/fetch-earthquakes.py, examples/cesium-app/public/...
Script to fetch USGS CSV and write Parquet; example uses parquet dataset and wiring to DuckDB connector.

Sequence Diagrams

sequenceDiagram
    participant App as App / Room
    participant Store as Room Store (Cesium slice)
    participant Viewer as Cesium Viewer
    participant DuckDB as DuckDB Connector
    participant EntityLayer as Cesium Entity Layer
    participant Clock as Clock Sync

    App->>Store: initialize roomStore with Cesium slice
    Store->>Viewer: create/register Viewer instance
    Viewer->>Store: set viewer, apply camera config
    Store->>Clock: apply persisted clock config
    Clock->>Viewer: set start/stop/current time & multiplier
    Store->>EntityLayer: register sql-entities layer (earthquakes)
    EntityLayer->>DuckDB: execute SQL query
    DuckDB-->>EntityLayer: return rows
    EntityLayer->>Viewer: create Cesium entities (positions, availability)
    Viewer->>Clock: onTick events
    Clock->>Store: update currentTime in store
Loading
sequenceDiagram
    participant User as User
    participant Toolbar as Cesium Toolbar
    participant Store as Cesium Store
    participant Viewer as Cesium Viewer
    participant Camera as Camera Listener

    User->>Toolbar: click zoom-to-fit
    Toolbar->>Store: call zoomToFit()
    Store->>Viewer: compute bounds and flyTo()
    Viewer->>Camera: camera moveEnd
    Camera->>Store: saveCameraPosition()
    Note over Store: persist camera to config

    User->>Toolbar: toggle layer visibility
    Toolbar->>Store: toggleLayerVisibility(id)
    Store->>Viewer: re-render layers
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • ilyabo
  • dmitriy-kostianetskiy
  • mihajlo-stojkovic

Poem

🐇 I hopped through code and globe of night,
SQL seeds sprouted points of light,
Clocks that tick and cameras glide,
Slabs and slices open wide,
Happy hops for features bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Cesium 3D globe visualization package' accurately reflects the main change: a new Cesium-based 3D visualization package with supporting example app.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (9)
examples/cesium-app/.gitignore (1)

27-27: Broaden env ignore pattern to reduce secret-leak risk.
Only .env is ignored currently; .env.development, .env.production, etc. can still be committed accidentally.

Suggested update
 # Environment
 .env
+.env.*
+!.env.example
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cesium-app/.gitignore` at line 27, The .gitignore currently only
ignores a single filename ".env", which still allows variants like
".env.development" or ".env.production" to be committed; update the .gitignore
entry for environment files to a broader pattern (e.g., ".env*" or ".env.*") so
all environment file variants are ignored, and ensure any specific safe
exceptions are handled with explicit negation if needed (refer to the existing
".env" entry to replace/expand it).
examples/cesium-app/.env.example (1)

1-3: Add a token-scope safety note.
This is good onboarding, but add a brief note to create a least-privilege/domain-restricted Ion token to reduce accidental quota abuse.

Suggested update
 # Cesium Ion Access Token
 # Get your free token at https://ion.cesium.com/tokens
+# Use a token scoped to this app/domain with minimal permissions.
 VITE_CESIUM_ION_TOKEN=your_token_here
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cesium-app/.env.example` around lines 1 - 3, Update the .env.example
to include a short safety note about token scope next to VITE_CESIUM_ION_TOKEN:
instruct users to create a least-privilege Cesium Ion token and, if possible,
restrict it to the specific domain or usage to avoid accidental quota abuse;
reference the environment variable name VITE_CESIUM_ION_TOKEN in the note so
it's clear which token to scope and restrict.
examples/cesium-app/src/Room.tsx (1)

23-27: Optional: tie selected state to modal visibility.

Using a constant false makes the sidebar button state static; consider binding isSelected to sqlEditorDisclosure.isOpen for consistent UI feedback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cesium-app/src/Room.tsx` around lines 23 - 27, The SidebarButton's
selected state is hard-coded to false; update the isSelected prop on
RoomShell.SidebarButton to reflect the modal visibility by using
sqlEditorDisclosure.isOpen instead of false so the button visually reflects the
SQL editor's open/closed state (keep onClick as sqlEditorDisclosure.onToggle).
examples/cesium-app/package.json (1)

7-9: Consider enforcing workspace build before dev.

Given the workspace-linked dependencies, add a predev step so the example starts reliably from a clean checkout.

♻️ Suggested script tweak
   "scripts": {
-    "dev": "vite",
+    "predev": "pnpm -w build",
+    "dev": "vite",
     "build": "tsc && vite build",
     "preview": "vite preview"
   },

Based on learnings: "Always run pnpm build first before running example apps, as example apps depend on built sqlrooms/* packages via workspace:* links".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cesium-app/package.json` around lines 7 - 9, Add a predev script
that runs the workspace build before the existing dev script so examples with
workspace:* links are built from a clean checkout; specifically, update
package.json scripts to add a "predev" entry that invokes the workspace build
command (e.g., pnpm -w -r build) so npm/pnpm will run it automatically before
the "dev" script defined alongside "build" and "preview".
packages/cesium/src/components/CesiumViewerWrapper.tsx (1)

185-196: Unsupported layer types silently produce no output.

Layers with type: 'geojson' | 'czml' | 'tileset' are defined in CesiumLayerConfig but return null here. Users configuring these layer types will see no error and no rendered content.

Consider adding a console warning or a placeholder component for unsupported types to aid debugging.

💡 Add warning for unsupported layer types
         switch (layer.type) {
           case 'sql-entities':
             return <CesiumEntityLayer key={layer.id} layerConfig={layer} />;

           // Future layer types: geojson, czml, tileset
           default:
+            if (process.env.NODE_ENV === 'development') {
+              console.warn(
+                `[CesiumViewerWrapper] Layer type "${layer.type}" not yet implemented: ${layer.id}`
+              );
+            }
             return null;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cesium/src/components/CesiumViewerWrapper.tsx` around lines 185 -
196, The render loop in CesiumViewerWrapper (layers.map -> switch on layer.type)
silently returns null for supported-but-unhandled types ('geojson', 'czml',
'tileset'); update the switch default to surface the issue by either logging a
warning (console.warn or app logger) that includes layer.id and layer.type
and/or rendering a small placeholder component (e.g.,
UnsupportedLayerPlaceholder) so developers see feedback instead of silent
failure; be sure to reference the existing map callback and return a valid React
node for unsupported types rather than null.
packages/cesium/README.md (1)

65-78: Consider immutable pattern for layer configuration example.

The example mutates cesiumConfig.cesium.layers directly with assignment. While this works during initial setup, using spread syntax would better demonstrate immutable patterns consistent with Zustand/Immer usage elsewhere in the codebase.

📝 Immutable config example
 const cesiumConfig = createDefaultCesiumConfig();
-cesiumConfig.cesium.layers = [
-  {
-    id: 'my-data',
-    type: 'sql-entities',
-    visible: true,
-    sqlQuery: 'SELECT longitude, latitude, name FROM my_table',
-    columnMapping: {
-      longitude: 'longitude',
-      latitude: 'latitude',
-      label: 'name',
-    },
-  },
-];
+cesiumConfig.cesium = {
+  ...cesiumConfig.cesium,
+  layers: [
+    {
+      id: 'my-data',
+      type: 'sql-entities',
+      visible: true,
+      sqlQuery: 'SELECT longitude, latitude, name FROM my_table',
+      columnMapping: {
+        longitude: 'longitude',
+        latitude: 'latitude',
+        label: 'name',
+      },
+    },
+  ],
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cesium/README.md` around lines 65 - 78, The example mutates the
default config by assigning to cesiumConfig.cesium.layers; instead, construct a
new config object using an immutable update (e.g., spread) so you don't mutate
the object returned by createDefaultCesiumConfig; update the snippet to create a
new cesiumConfig value that preserves existing properties and sets cesium.layers
to a new array (referencing createDefaultCesiumConfig and the
cesiumConfig.cesium.layers property names so reviewers can locate and replace
the direct assignment).
packages/cesium/src/hooks/useClockSync.ts (1)

81-83: Potential feedback loop between clock sync effects.

The selector at line 82 returns the entire clockConfig object which includes currentTime. When the tick handler (line 98) calls setCurrentTime, it updates config.clock.currentTime, causing clockConfig to change and triggering the effect at lines 112-115. This reapplies the full config back to Cesium, including the currentTime that was just read from Cesium.

While the 500ms throttle mitigates performance impact, consider selecting only the config properties that should flow store→Cesium (excluding currentTime) to avoid redundant writes.

♻️ Separate selectors to break feedback loop
 export function useClockSync(): void {
   const viewer = useStoreWithCesium((s) => s.cesium.viewer);
-  const clockConfig = useStoreWithCesium((s) => s.cesium.config.clock);
+  // Select individual config properties to avoid feedback loop with currentTime
+  const multiplier = useStoreWithCesium((s) => s.cesium.config.clock.multiplier);
+  const shouldAnimate = useStoreWithCesium((s) => s.cesium.config.clock.shouldAnimate);
+  const clockRange = useStoreWithCesium((s) => s.cesium.config.clock.clockRange);
   const setCurrentTime = useStoreWithCesium((s) => s.cesium.setCurrentTime);
   // ... existing code ...

   // Store → Cesium: Apply config changes (excluding currentTime which flows Cesium→Store)
   useEffect(() => {
     if (!viewer) return;
-    applyClockConfigToViewer(viewer, clockConfig);
-  }, [viewer, clockConfig]);
+    const clock = viewer.clock;
+    clock.multiplier = multiplier;
+    clock.shouldAnimate = shouldAnimate;
+    clock.clockRange = CLOCK_RANGE_MAP[clockRange];
+  }, [viewer, multiplier, shouldAnimate, clockRange]);

Also applies to: 112-115

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cesium/src/hooks/useClockSync.ts` around lines 81 - 83, The effect
is re-triggered because the selector (clockConfig from useStoreWithCesium)
includes currentTime which the tick handler (setCurrentTime) writes back; change
the selector used in useClockSync to return only the clock config fields that
should flow store→Cesium (exclude currentTime) — e.g., replace the clockConfig
selector with one that picks specific properties (intervals, multiplier,
start/stop, etc.) or create clockConfigWithoutCurrentTime, then update the
effect that reapplies config (the block referencing clockConfig at lines around
the apply/reapply to Cesium) to use that trimmed selector so setCurrentTime
updates no longer cause the effect to run and redundantly write currentTime back
to Cesium.
packages/cesium/src/components/CesiumClock.tsx (1)

28-40: Consider defensive guard for non-positive multiplier.

Math.log(multiplier) on line 33 returns -Infinity for 0 and NaN for negative values. While the current UI only sets positive values from SPEED_STEPS, a defensive guard would prevent unexpected behavior if the store ever receives invalid data from persistence or external code.

🛡️ Optional defensive fix
 function multiplierToSlider(multiplier: number): number {
+  // Guard against invalid multiplier values
+  const safeMultiplier = multiplier > 0 ? multiplier : 1;
   let closest = 0;
   let minDiff = Infinity;
   for (let i = 0; i < SPEED_STEPS.length; i++) {
     const step = SPEED_STEPS[i]!;
-    const diff = Math.abs(Math.log(multiplier) - Math.log(step.value));
+    const diff = Math.abs(Math.log(safeMultiplier) - Math.log(step.value));
     if (diff < minDiff) {
       minDiff = diff;
       closest = i;
     }
   }
   return closest;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cesium/src/components/CesiumClock.tsx` around lines 28 - 40, The
multiplierToSlider function calls Math.log(multiplier) which yields
-Infinity/NaN for non-positive inputs; add a defensive guard at the start of
multiplierToSlider to handle multiplier <= 0 (e.g., return index 0 or clamp to
the smallest positive SPEED_STEPS value) and also ensure you don't call Math.log
on any SPEED_STEPS entries that could ever be non-positive; update
multiplierToSlider to early-return a safe default (or clamp) when multiplier is
invalid and proceed with the existing nearest-step logic using Math.log only on
known positive values.
packages/cesium/src/cesium-types.ts (1)

37-47: Prefer composing CesiumRuntimeState to avoid type drift.

CesiumSliceState.cesium duplicates runtime fields already defined in CesiumRuntimeState; composing them would reduce maintenance risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cesium/src/cesium-types.ts` around lines 37 - 47, CesiumSliceState
currently re-declares runtime fields that already exist on CesiumRuntimeState;
update CesiumSliceState.cesium to compose the runtime type instead of
duplicating fields by replacing the inline runtime properties with a reference
to CesiumRuntimeState (e.g. have cesium: { config: CesiumSliceConfig['cesium'];
} & CesiumRuntimeState or cesium: { config: CesiumSliceConfig['cesium'];
runtime: CesiumRuntimeState } depending on the intended shape), ensuring you
keep the persisted config (CesiumSliceConfig['cesium']) and remove duplicated
fields like viewer, isAnimating, selectedEntity, isLoadingData to avoid type
drift and maintenance duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/cesium-app/README.md`:
- Line 8: The README's "Earthquake Data" bullet incorrectly says "magnitude
4.0+" but the code in src/store.ts filters with "WHERE Magnitude >= 5.0"; update
the README bullet for "Earthquake Data" to state "magnitude 5.0+" so the
documentation matches the actual filter in src/store.ts (look for the WHERE
Magnitude >= 5.0 clause or any constant used for the magnitude threshold).
- Line 45: The README's "Speed slider" range is incorrect; update the text to
match the actual options implemented in CesiumClock.tsx by referencing the
SPEED_STEPS constant (which yields speeds from 1x up to 31,536,000x, i.e., 1
year per second). Replace "0.1x to 100x" with a concise description that
reflects SPEED_STEPS (for example "1x to 31,536,000x (up to 1 year per second)")
or otherwise describe the slider as using the discrete SPEED_STEPS values
defined in CesiumClock.tsx.

In `@examples/cesium-app/src/index.css`:
- Around line 5-8: Update the Stylelint configuration so Tailwind v4 `@source`
directives are treated as known at-rules: in .stylelintrc.json (which currently
extends "stylelint-config-standard-scss") add/modify the
"scss/at-rule-no-unknown" rule to include an ignoreAtRules array containing
"source" and "apply" so the linter no longer flags Tailwind's `@source` directives
in examples/cesium-app/src/index.css as unknown.

In `@examples/cesium-app/src/Room.tsx`:
- Around line 16-18: The useEffect invoking roomStore.getState().initialize can
run twice under React 18 StrictMode; make initialization idempotent by guarding
it before calling initialize — e.g., check a persisted/in-memory flag on the
store state (like roomStore.getState().initialized) or add an initializeOnce
guard inside the initialize function so repeated calls do nothing; update the
call site where roomStore.getState().initialize is invoked (and/or the
initialize method itself) to return early if already initialized so database
setup/data download runs only once.

In `@packages/cesium/src/cesium-config.ts`:
- Around line 12-19: The file-level comment above the CameraPosition schema
incorrectly states "All angles in radians" while the CameraPosition z.object and
its fields longitude and latitude are documented and defaulted in degrees;
update the documentation to accurately reflect degrees (e.g., change the header
to "All angles in degrees" or otherwise clarify units) so CameraPosition,
longitude, and latitude consistently advertise degrees to API consumers.
- Around line 174-175: createDefaultCesiumConfig currently returns the shared
DEFAULT_CESIUM_CONFIG object which allows callers to accidentally mutate a
global singleton; change createDefaultCesiumConfig to return a fresh copy of
DEFAULT_CESIUM_CONFIG (e.g., deep-clone via structuredClone or other
project-approved deep copy utility) so each call produces an independent
CesiumSliceConfig instance and no mutations leak across callers.

In `@packages/cesium/src/components/CesiumEntityLayer.tsx`:
- Around line 89-94: The debug console.log in CesiumEntityLayer that prints
clock range and entity metadata should be removed from production: locate the
console.log call in the CesiumEntityLayer component (the block that logs
startTime, stopTime, entityCount, hasAvailability when setting the clock range)
and either delete it or wrap it behind a conditional debug check (e.g., only run
when a dev/debug flag or NODE_ENV !== 'production') or replace it with the
project's logger at debug level; ensure no plain console.log remains in the
production build.

In `@packages/cesium/src/hooks/useSqlToCesiumEntities.ts`:
- Around line 75-105: The map over rows builds Cesium entities without
validating coordinates or time; guard against NaN/invalid values by validating
lon/lat/alt are finite numbers and timeStr is a valid ISO before calling
Cartesian3.fromDegrees or JulianDate.fromIso8601. In the rows.map block
(rows.map, mapping.longitude, mapping.latitude, mapping.altitude,
Cartesian3.fromDegrees, mapping.time, toIso8601, JulianDate.fromIso8601) skip or
filter out rows where Number(...) yields NaN or Infinity, and only build
availability when the ISO/time parse succeeded (catch remains fine), otherwise
return null/undefined and filter those out before returning the final entity
array. Ensure label/color/size conversions only run for rows that passed the
coordinate/time checks.

---

Nitpick comments:
In `@examples/cesium-app/.env.example`:
- Around line 1-3: Update the .env.example to include a short safety note about
token scope next to VITE_CESIUM_ION_TOKEN: instruct users to create a
least-privilege Cesium Ion token and, if possible, restrict it to the specific
domain or usage to avoid accidental quota abuse; reference the environment
variable name VITE_CESIUM_ION_TOKEN in the note so it's clear which token to
scope and restrict.

In `@examples/cesium-app/.gitignore`:
- Line 27: The .gitignore currently only ignores a single filename ".env", which
still allows variants like ".env.development" or ".env.production" to be
committed; update the .gitignore entry for environment files to a broader
pattern (e.g., ".env*" or ".env.*") so all environment file variants are
ignored, and ensure any specific safe exceptions are handled with explicit
negation if needed (refer to the existing ".env" entry to replace/expand it).

In `@examples/cesium-app/package.json`:
- Around line 7-9: Add a predev script that runs the workspace build before the
existing dev script so examples with workspace:* links are built from a clean
checkout; specifically, update package.json scripts to add a "predev" entry that
invokes the workspace build command (e.g., pnpm -w -r build) so npm/pnpm will
run it automatically before the "dev" script defined alongside "build" and
"preview".

In `@examples/cesium-app/src/Room.tsx`:
- Around line 23-27: The SidebarButton's selected state is hard-coded to false;
update the isSelected prop on RoomShell.SidebarButton to reflect the modal
visibility by using sqlEditorDisclosure.isOpen instead of false so the button
visually reflects the SQL editor's open/closed state (keep onClick as
sqlEditorDisclosure.onToggle).

In `@packages/cesium/README.md`:
- Around line 65-78: The example mutates the default config by assigning to
cesiumConfig.cesium.layers; instead, construct a new config object using an
immutable update (e.g., spread) so you don't mutate the object returned by
createDefaultCesiumConfig; update the snippet to create a new cesiumConfig value
that preserves existing properties and sets cesium.layers to a new array
(referencing createDefaultCesiumConfig and the cesiumConfig.cesium.layers
property names so reviewers can locate and replace the direct assignment).

In `@packages/cesium/src/cesium-types.ts`:
- Around line 37-47: CesiumSliceState currently re-declares runtime fields that
already exist on CesiumRuntimeState; update CesiumSliceState.cesium to compose
the runtime type instead of duplicating fields by replacing the inline runtime
properties with a reference to CesiumRuntimeState (e.g. have cesium: { config:
CesiumSliceConfig['cesium']; } & CesiumRuntimeState or cesium: { config:
CesiumSliceConfig['cesium']; runtime: CesiumRuntimeState } depending on the
intended shape), ensuring you keep the persisted config
(CesiumSliceConfig['cesium']) and remove duplicated fields like viewer,
isAnimating, selectedEntity, isLoadingData to avoid type drift and maintenance
duplication.

In `@packages/cesium/src/components/CesiumClock.tsx`:
- Around line 28-40: The multiplierToSlider function calls Math.log(multiplier)
which yields -Infinity/NaN for non-positive inputs; add a defensive guard at the
start of multiplierToSlider to handle multiplier <= 0 (e.g., return index 0 or
clamp to the smallest positive SPEED_STEPS value) and also ensure you don't call
Math.log on any SPEED_STEPS entries that could ever be non-positive; update
multiplierToSlider to early-return a safe default (or clamp) when multiplier is
invalid and proceed with the existing nearest-step logic using Math.log only on
known positive values.

In `@packages/cesium/src/components/CesiumViewerWrapper.tsx`:
- Around line 185-196: The render loop in CesiumViewerWrapper (layers.map ->
switch on layer.type) silently returns null for supported-but-unhandled types
('geojson', 'czml', 'tileset'); update the switch default to surface the issue
by either logging a warning (console.warn or app logger) that includes layer.id
and layer.type and/or rendering a small placeholder component (e.g.,
UnsupportedLayerPlaceholder) so developers see feedback instead of silent
failure; be sure to reference the existing map callback and return a valid React
node for unsupported types rather than null.

In `@packages/cesium/src/hooks/useClockSync.ts`:
- Around line 81-83: The effect is re-triggered because the selector
(clockConfig from useStoreWithCesium) includes currentTime which the tick
handler (setCurrentTime) writes back; change the selector used in useClockSync
to return only the clock config fields that should flow store→Cesium (exclude
currentTime) — e.g., replace the clockConfig selector with one that picks
specific properties (intervals, multiplier, start/stop, etc.) or create
clockConfigWithoutCurrentTime, then update the effect that reapplies config (the
block referencing clockConfig at lines around the apply/reapply to Cesium) to
use that trimmed selector so setCurrentTime updates no longer cause the effect
to run and redundantly write currentTime back to Cesium.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b290a05d-c552-4370-b656-1e8142a97cb9

📥 Commits

Reviewing files that changed from the base of the PR and between 7f3afac and d3de271.

⛔ Files ignored due to path filters (2)
  • examples/cesium-app/public/sample-earthquakes.csv is excluded by !**/*.csv
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (31)
  • examples/cesium-app/.env.example
  • examples/cesium-app/.gitignore
  • examples/cesium-app/README.md
  • examples/cesium-app/index.html
  • examples/cesium-app/package.json
  • examples/cesium-app/src/App.tsx
  • examples/cesium-app/src/Room.tsx
  • examples/cesium-app/src/index.css
  • examples/cesium-app/src/main.tsx
  • examples/cesium-app/src/store.ts
  • examples/cesium-app/src/vite-env.d.ts
  • examples/cesium-app/tsconfig.json
  • examples/cesium-app/vite.config.ts
  • packages/cesium/.gitignore
  • packages/cesium/README.md
  • packages/cesium/eslint.config.js
  • packages/cesium/package.json
  • packages/cesium/src/cesium-config.ts
  • packages/cesium/src/cesium-slice.ts
  • packages/cesium/src/cesium-types.ts
  • packages/cesium/src/components/CesiumClock.tsx
  • packages/cesium/src/components/CesiumEntityLayer.tsx
  • packages/cesium/src/components/CesiumPanel.tsx
  • packages/cesium/src/components/CesiumToolbar.tsx
  • packages/cesium/src/components/CesiumViewerWrapper.tsx
  • packages/cesium/src/hooks/useCesiumViewer.ts
  • packages/cesium/src/hooks/useClockSync.ts
  • packages/cesium/src/hooks/useSqlToCesiumEntities.ts
  • packages/cesium/src/index.ts
  • packages/cesium/src/utils.ts
  • packages/cesium/tsconfig.json

Comment thread examples/cesium-app/README.md Outdated
Comment thread examples/cesium-app/README.md
Comment on lines +5 to +8
@source '../index.html';
@source 'src/**/*.{ts,tsx}';
@source '../../../packages/*/src/**/*.{ts,tsx}';
@source '../node_modules/@sqlrooms/*/dist/';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find Stylelint config files:"
fd -HI 'stylelint|.stylelintrc' .

echo
echo "Inspect unknown at-rule configuration:"
rg -n --no-heading -C3 'at-rule-no-unknown|scss/at-rule-no-unknown|ignoreAtRules' .

Repository: sqlrooms/sqlrooms

Length of output: 149


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Stylelint Configuration ==="
cat .stylelintrc.json

echo
echo "=== Target CSS File (lines 1-20) ==="
head -20 examples/cesium-app/src/index.css

echo
echo "=== Tailwind Version in Root ==="
jq '.devDependencies.tailwindcss // .dependencies.tailwindcss // "not found"' package.json

echo
echo "=== Tailwind Version in cesium-app ==="
jq '.devDependencies.tailwindcss // .dependencies.tailwindcss // "not found"' examples/cesium-app/package.json 2>/dev/null || echo "cesium-app package.json not found or no tailwindcss"

Repository: sqlrooms/sqlrooms

Length of output: 1962


Configure Stylelint to accept Tailwind v4 @source directives.

The CSS file uses Tailwind v4's @source at-rules (lines 5-8), which are valid but not standard SCSS. The current Stylelint config (extends: "stylelint-config-standard-scss") will flag these as unknown at-rules via scss/at-rule-no-unknown.

Update .stylelintrc.json to ignore Tailwind at-rules:

{
  "extends": ["stylelint-config-standard-scss"],
  "rules": {
    "scss/at-rule-no-unknown": ["error", { "ignoreAtRules": ["source", "apply"] }],
    // ... rest of existing rules
  }
}
🧰 Tools
🪛 Stylelint (17.6.0)

[error] 5-5: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 6-6: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 7-7: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 8-8: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cesium-app/src/index.css` around lines 5 - 8, Update the Stylelint
configuration so Tailwind v4 `@source` directives are treated as known at-rules:
in .stylelintrc.json (which currently extends "stylelint-config-standard-scss")
add/modify the "scss/at-rule-no-unknown" rule to include an ignoreAtRules array
containing "source" and "apply" so the linter no longer flags Tailwind's `@source`
directives in examples/cesium-app/src/index.css as unknown.

Comment thread examples/cesium-app/src/Room.tsx Outdated
Comment thread examples/cesium-quakes-standalone/src/cesium/cesium-config.ts
Comment thread packages/cesium/src/cesium-config.ts Outdated
Comment thread packages/cesium/src/components/CesiumEntityLayer.tsx Outdated
Comment thread packages/cesium/src/hooks/useSqlToCesiumEntities.ts Outdated
@collord

collord commented Apr 11, 2026

Copy link
Copy Markdown
Author

Addressed CodeRabbit feedback across two commits:

a647a2d6 — correctness fixes:

  • Removed manual initialize() call in Room.tsx (StrictMode double-init; RoomShell handles initialization automatically)
  • createDefaultCesiumConfig now returns structuredClone(DEFAULT_CESIUM_CONFIG) so callers can't mutate the shared default
  • useSqlToCesiumEntities skips rows with non-finite lon/lat/alt or unparseable time values before passing them to Cartesian3.fromDegrees / JulianDate.fromIso8601
  • useClockSync uses useShallow to select only store→Cesium inputs, excluding currentTime. Previously the tick handler wrote currentTime to the store, retriggering the effect that reapplied the full clock config to Cesium, creating a feedback loop.

81c9ae77 — cleanups:

  • Removed debug console.log in CesiumEntityLayer
  • Fixed examples/cesium-app/README.md: earthquake filter is magnitude 5.0+ (not 4.0+), and speed slider is logarithmic from 1x up to 1 year/sec (not "0.1x to 100x")
  • Fixed the stale "All angles in radians" doc comment on CameraPosition — longitude/latitude are degrees; only heading/pitch/roll are in radians

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cesium/src/hooks/useClockSync.ts`:
- Around line 86-95: The selector is creating a derived object inside
useStoreWithCesium (using useShallow) and omitted currentTime, breaking
store→Cesium updates (calls to setCurrentTime never update
viewer.clock.currentTime); refactor by selecting raw fields individually from
useStoreWithCesium (e.g., startTime, stopTime, multiplier, shouldAnimate,
clockRange, currentTime, and setCurrentTime) and use useMemo to group
non-primitive inputs if needed, then add a guarded effect that, when
store.currentTime changes and differs from viewer.clock.currentTime, writes
store.currentTime into viewer.clock.currentTime (and guard against feedback
loops by skipping when values are already equal or when the change originated
from the viewer), ensuring setCurrentTime calls propagate to
viewer.clock.currentTime.

In `@packages/cesium/src/hooks/useSqlToCesiumEntities.ts`:
- Around line 60-73: The code computes stopJd from the last row which assumes
rows are time-sorted; change it to derive stopJd from the maximum valid
timestamp across all rows when mapping.time exists. Iterate over rows, parse
each timestamp via toIso8601 and JulianDate.fromIso8601 (skip invalid parses),
track the latest JulianDate (compare with JulianDate.lessThan/greaterThan or
convert to seconds), and assign that to stopJd so availability uses the true max
time rather than rows[rows.length - 1].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6082c45d-6cca-42cb-b87f-20f0224b622e

📥 Commits

Reviewing files that changed from the base of the PR and between d3de271 and 81c9ae7.

📒 Files selected for processing (6)
  • examples/cesium-app/README.md
  • examples/cesium-app/src/Room.tsx
  • packages/cesium/src/cesium-config.ts
  • packages/cesium/src/components/CesiumEntityLayer.tsx
  • packages/cesium/src/hooks/useClockSync.ts
  • packages/cesium/src/hooks/useSqlToCesiumEntities.ts
✅ Files skipped from review due to trivial changes (1)
  • examples/cesium-app/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/cesium-app/src/Room.tsx
  • packages/cesium/src/components/CesiumEntityLayer.tsx
  • packages/cesium/src/cesium-config.ts

Comment thread examples/cesium-quakes-standalone/src/cesium/hooks/useClockSync.ts
Comment thread packages/cesium/src/hooks/useSqlToCesiumEntities.ts Outdated
@collord

collord commented Apr 12, 2026

Copy link
Copy Markdown
Author

Addressed the second round of CodeRabbit feedback in 6fc6aeb6:

  • useClockSync: Restored store→Cesium currentTime propagation. A lastTickTimeRef tracks values written by the tick handler; a separate effect watches store currentTime and applies it to the viewer only when the value differs from the last tick write. This prevents the feedback loop while allowing external setCurrentTime calls (e.g. timeline scrub) to reach the viewer. The mutation is extracted into a module-level applyCurrentTimeToViewer function to satisfy React Compiler's immutability rule (same pattern as the existing applyClockConfigToViewer).

  • useSqlToCesiumEntities: stopJd is now computed by scanning all rows for the maximum valid timestamp via JulianDate.greaterThan, instead of assuming the last row is latest. Also moved the mapping fallback inside the useMemo callback to fix the react-hooks/exhaustive-deps warning (previously it created a new object on every render, invalidating the memo).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cesium/src/hooks/useSqlToCesiumEntities.ts`:
- Around line 121-128: The descriptor creation is coercing missing/invalid
mapped values into "undefined"/"null" strings and allowing NaN/Infinity for
size; update the descriptors.push logic in useSqlToCesiumEntities so you first
read const val = row[mapping.label] / row[mapping.color] / row[mapping.size],
then only set label/color if val !== null && val !== undefined, converting to
String only after that check, and for size convert to Number and assign only
when Number.isFinite(sizeVal); ensure mapping.label, mapping.color and
mapping.size are guarded this way before adding them to the descriptor to avoid
invalid values leaking into rendering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0edc728-433a-44d6-b19a-587c3f4e824b

📥 Commits

Reviewing files that changed from the base of the PR and between 81c9ae7 and 6fc6aeb.

📒 Files selected for processing (2)
  • packages/cesium/src/hooks/useClockSync.ts
  • packages/cesium/src/hooks/useSqlToCesiumEntities.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cesium/src/hooks/useClockSync.ts

Comment thread packages/cesium/src/hooks/useSqlToCesiumEntities.ts Outdated
@collord

collord commented Apr 12, 2026

Copy link
Copy Markdown
Author

Addressed the remaining CodeRabbit comment in 0e42c01e:

  • useSqlToCesiumEntities: label, color, and size fields in entity descriptors are now guarded against null/undefined/NaN before coercion. Previously String(null) would produce "null" and Number(undefined) would produce NaN, leaking invalid values into Cesium entities.

@ilyabo

ilyabo commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the contribution!

I am not yet quite sure it's worth adding a dedicated package at this point (could you convince me otherwise? :) ), but an example would definitely be a valuable addition. In this PR I am trying to make the example a bit more interesting and a better showcase of what Cesium can do by using opensky flight data which can be nicely animated:
image

image

what do you think?

@collord

collord commented Apr 17, 2026

Copy link
Copy Markdown
Author

Significantly expanded the example based on feedback that the demo needed more visual impact. The Cesium example is now a Wadati–Benioff Explorer — a 3D globe cross-section tool for subduction zones. Four new commits:

2d467ee8 — feat(cesium-app): 3D earthquake explorer with subduction zone slicer

  • Six presets (Tonga, Japan, Chile, Indonesia, Cascadia, Hellenic) that fly the camera to an along-strike view and SQL-filter the catalog to a thin section perpendicular to the trench.
  • Live mag-vs-depth histogram panel (recharts) tied to the active slice.

bdd0f550 — feat(cesium-app): translucent Wadati–Benioff slab surface under seismicity

  • Parametric slab-top mesh rendered beneath the seismicity, tuned per-preset to Slab2.0 dip profiles so each zone has its own character (Cascadia flat, Tonga plunging to ~700 km).

0e726317 — fix(cesium): clipping plane lifecycle + layer clock re-sync on data change

  • Package-level fixes: removed a double-destroy crash on preset switch (Cesium's ClippingPlaneCollection.setOwner already destroys the prior collection; our manual .destroy() was clobbering it), and made CesiumEntityLayer re-sync the clock whenever entities change (previously a stale currentTime from the initial dataset would hide every entity from a filtered reload).

bf963c5a — feat(cesium-app): section cut, global seismicity, preset polish

  • Single-plane globe clip (section cut) + two-plane slab-mesh clip (thin ribbon within ±halfWidth of the section line).
  • SQL data-point filter uses 3× the slab width so the point cloud has lateral context.
  • flyToBoundingSphere with a subsurface target so each preset auto-frames the full dipping zone.
  • Dark scene controller (no stars/sun/atmosphere) for focus.
  • depthTestAgainstTerrain: true — subsurface events hidden until a preset cuts the globe open.
  • Swapped the bundled NA-only sample CSV for a global parquet (M5+ worldwide since 2013, ~17k events, ~850 KB) hosted on HuggingFace, matching the pattern used by canvas/kepler/minimal/multi-room. Temporarily at huggingface.co/datasets/collord/globalearthquakes — happy to move to sqlrooms/* whenever you tell me where.
  • scripts/fetch-earthquakes.py regenerates the parquet from the USGS FDSN feed.
  • RenderingStatus overlay covers the gap between parquet-download-done and entities-on-globe.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
examples/cesium-app/src/HistogramPanel.tsx (1)

53-53: Prefer selecting a stable primitive over the table object.

findTableByName('earthquakes') returns an object (or undefined). If its identity isn't referentially stable across unrelated store updates, this selector can trigger extra re-renders of the panel. Matching the pattern already used in RenderingStatus.tsx, coerce to a boolean inside the selector so only readiness transitions re-render:

♻️ Suggested change
-  const table = useRoomStore((s) => s.db.findTableByName('earthquakes'));
+  const tableReady = useRoomStore((s) =>
+    Boolean(s.db.findTableByName('earthquakes')),
+  );
   const {data, isLoading, error} = useSql<HistogramRow>({
     query: sqlQuery,
-    enabled: Boolean(table),
+    enabled: tableReady,
   });

As per coding guidelines: "When using Zustand/room-store selectors in React, do not create derived arrays/objects/functions inside the selector ... Select stable raw state first, then derive with React.useMemo in the component."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cesium-app/src/HistogramPanel.tsx` at line 53, The selector
currently returns the table object directly which may be referentially unstable;
change the selector to pick a stable primitive (e.g., select the db:
useRoomStore(s => s.db) or a boolean readiness flag like useRoomStore(s =>
Boolean(s.db.findTableByName('earthquakes')))), then derive the actual table
object with React.useMemo inside the component (e.g., const table =
React.useMemo(() => db.findTableByName('earthquakes'), [db]) or memoize based on
the boolean flag) so useRoomStore, findTableByName and the table local variable
are referenced but re-renders only occur on stable state changes.
examples/cesium-app/src/store.ts (1)

73-91: Minor: 'earthquakes' table name duplicated across files.

tableName: 'earthquakes' (line 78) and the data source tableName: 'earthquakes' (line 111) both duplicate the EARTHQUAKE_TABLE constant that already exists in earthquake-slice.ts (currently not exported). Consider exporting and reusing it to keep the table identifier in one place; similarly, EARTHQUAKE_LAYER_ID is already imported here, so the pattern is consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cesium-app/src/store.ts` around lines 73 - 91, Export the
EARTHQUAKE_TABLE constant from earthquake-slice.ts and replace the hardcoded
'earthquakes' strings in store.ts (both the layers entry's tableName and the
data source tableName) with that exported EARTHQUAKE_TABLE constant; update the
import in store.ts (where EARTHQUAKE_LAYER_ID is imported) to also import
EARTHQUAKE_TABLE so the table identifier is maintained in a single source (refer
to symbols EARTHQUAKE_TABLE in earthquake-slice.ts and the layers.tableName /
data source tableName in store.ts).
examples/cesium-app/src/PresetPanel.tsx (1)

40-74: preset.cameraHeight is defined but never read.

Every entry in SUBDUCTION_PRESETS sets cameraHeight (e.g. 900_000), and the SubductionPreset interface documents it as "Camera height above ellipsoid at fly-to, in meters", but flyToPreset derives range from slabDepthKm instead (line 62) and handleClear uses a hard-coded 18_000_000. Either wire cameraHeight into the range computation or drop the field to avoid drift between preset data and actual behaviour.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/cesium-app/src/PresetPanel.tsx` around lines 40 - 74, The
SubductionPreset.cameraHeight field is never used; update flyToPreset to prefer
preset.cameraHeight (meters) as the camera range when present and otherwise fall
back to the existing slab-derived range calculation, and likewise make
handleClear use the same default/fallback rather than a hard-coded 18_000_000 so
preset data and clear behavior stay consistent; touch the flyToPreset function
(and the handleClear logic) to compute a rangeMeters = preset.cameraHeight ??
computedFallback and pass that into HeadingPitchRange, keeping existing
conversions and fallbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/cesium-app/src/earthquake-presets.ts`:
- Around line 305-336: The docstring and implementation of buildSliceWhereClause
use a single-row cosine scale (cosLat computed from preset.latitude) while
SLICE_ALONG_SECTION_KM = 1500 allows ~13° latitude span, causing up to ~20–25%
edge scale error; fix by either (A) reducing SLICE_ALONG_SECTION_KM to a few
hundred km or (B) updating the docstring to explicitly warn about degradation
for large extents and add a TODO note referencing cosLat/preset.latitude to
revisit if large windows are needed; choose one of these and update references
to cosLat, SLICE_ALONG_SECTION_KM, and the function docstring accordingly so
code and comment are aligned.

In `@examples/cesium-app/src/store.ts`:
- Around line 10-14: Update the stale file-level JSDoc in
examples/cesium-app/src/store.ts (the top-of-file comment) to match the actual
viewer configuration (depthTestAgainstTerrain = true) and explain that because
depthTestAgainstTerrain is true subsurface events require a clipping plane to be
visible (match the inline comment near the viewer config around the existing
comment at lines ~66-69); make the same correction in
examples/cesium-app/src/earthquake-slice.ts (the file-level JSDoc around lines
~75-77) so both docstrings accurately state depthTestAgainstTerrain = true and
mention the clipping-plane requirement; keep the explanation brief and
consistent with the inline comments and references to the earthquake
slice/updateLayer behavior.

---

Nitpick comments:
In `@examples/cesium-app/src/HistogramPanel.tsx`:
- Line 53: The selector currently returns the table object directly which may be
referentially unstable; change the selector to pick a stable primitive (e.g.,
select the db: useRoomStore(s => s.db) or a boolean readiness flag like
useRoomStore(s => Boolean(s.db.findTableByName('earthquakes')))), then derive
the actual table object with React.useMemo inside the component (e.g., const
table = React.useMemo(() => db.findTableByName('earthquakes'), [db]) or memoize
based on the boolean flag) so useRoomStore, findTableByName and the table local
variable are referenced but re-renders only occur on stable state changes.

In `@examples/cesium-app/src/PresetPanel.tsx`:
- Around line 40-74: The SubductionPreset.cameraHeight field is never used;
update flyToPreset to prefer preset.cameraHeight (meters) as the camera range
when present and otherwise fall back to the existing slab-derived range
calculation, and likewise make handleClear use the same default/fallback rather
than a hard-coded 18_000_000 so preset data and clear behavior stay consistent;
touch the flyToPreset function (and the handleClear logic) to compute a
rangeMeters = preset.cameraHeight ?? computedFallback and pass that into
HeadingPitchRange, keeping existing conversions and fallbacks.

In `@examples/cesium-app/src/store.ts`:
- Around line 73-91: Export the EARTHQUAKE_TABLE constant from
earthquake-slice.ts and replace the hardcoded 'earthquakes' strings in store.ts
(both the layers entry's tableName and the data source tableName) with that
exported EARTHQUAKE_TABLE constant; update the import in store.ts (where
EARTHQUAKE_LAYER_ID is imported) to also import EARTHQUAKE_TABLE so the table
identifier is maintained in a single source (refer to symbols EARTHQUAKE_TABLE
in earthquake-slice.ts and the layers.tableName / data source tableName in
store.ts).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5200953-6544-464b-b992-5e28620e911a

📥 Commits

Reviewing files that changed from the base of the PR and between 0e42c01 and bf963c5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • examples/cesium-app/README.md
  • examples/cesium-app/package.json
  • examples/cesium-app/scripts/fetch-earthquakes.py
  • examples/cesium-app/src/HistogramPanel.tsx
  • examples/cesium-app/src/PresetPanel.tsx
  • examples/cesium-app/src/RenderingStatus.tsx
  • examples/cesium-app/src/Room.tsx
  • examples/cesium-app/src/SceneStyleController.tsx
  • examples/cesium-app/src/SlabSurfaceController.tsx
  • examples/cesium-app/src/SliceClippingController.tsx
  • examples/cesium-app/src/earthquake-presets.ts
  • examples/cesium-app/src/earthquake-slice.ts
  • examples/cesium-app/src/panel-keys.ts
  • examples/cesium-app/src/slab-mesh.ts
  • examples/cesium-app/src/store.ts
  • packages/cesium/src/cesium-slice.ts
  • packages/cesium/src/components/CesiumEntityLayer.tsx
✅ Files skipped from review due to trivial changes (3)
  • examples/cesium-app/package.json
  • examples/cesium-app/README.md
  • examples/cesium-app/src/panel-keys.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cesium/src/components/CesiumEntityLayer.tsx
  • packages/cesium/src/cesium-slice.ts

Comment thread examples/cesium-quakes-standalone/src/earthquake-presets.ts
Comment thread examples/cesium-quakes-standalone/src/store.ts
@collord

collord commented Apr 17, 2026

Copy link
Copy Markdown
Author

I updated my example to try to showcase why 3D matters for a general audience, still earthquakes though. I should be explicit that I'm personally interested in problems that involve the subsurface. Subsurface/3D spatial information systems is a nontrivial market that is addressed in very narrow domain-specific tools with typically very high per-seat costs. Examples are: support for tunnelling in the civil engineering domain (visualizing/modeling geotechnical data); visualization and modeling of subsurface environmental chemistry data in e.g. delineating a plume of a pollutant in groundwater; visualization and modeling of subsurface whole rock analytical or multispectral imaging data in a minerals exploration context; temperature in geothermal exploration; geophysics assets in all of the above, etc. etc.

I am excited to have the real foundation for a frontend-heavy data-intensive experiments that duckdb-wasm and sqlrooms brings together.

Thank you for your consideration.

Thanks for the contribution!

I am not yet quite sure it's worth adding a dedicated package at this point (could you convince me otherwise? :) ), but an example would definitely be a valuable addition.
what do you think?

@collord

collord commented Apr 17, 2026

Copy link
Copy Markdown
Author

Addressed the third CodeRabbit round in 2154b4b1:

  • buildSliceWhereClause cosLat accuracy: swapped the preset-center cos(lat) for per-row COS(RADIANS(latitude)). The 1500 km along-section window spans ~13° of latitude, so a preset-center cosine could mis-scale edge points by ~20%. Remaining error is bounded by the flat-earth approximation itself (<1% for this extent).
  • Stale JSDocs: the top-of-file comments in store.ts and earthquake-slice.ts still claimed depthTestAgainstTerrain = false. Updated to match the current behaviour (globe occludes subsurface events until a preset's section-cut clipping plane exposes them).
  • HistogramPanel selector stability: gate useSql on a stable Boolean(findTableByName(...)) rather than the table object, matching the pattern already used in RenderingStatus and the selector guidance in CLAUDE.md.
  • preset.cameraHeight cleanup: removed the unused field. The camera range is now derived from slab depth via flyToBoundingSphere, so the field was no longer driving any behaviour.
  • EARTHQUAKE_TABLE dedupe: exported the constant from earthquake-slice.ts and used it in store.ts (both the layer and data source tableName) and HistogramPanel.tsx instead of hardcoding 'earthquakes'.

@collord

collord commented Apr 18, 2026

Copy link
Copy Markdown
Author

Fair point — on reflection, a dedicated Cesium package isn't worth the monorepo's long-term maintenance cost for what this is right now.

Pushed 7c284978 to fold @sqlrooms/cesium into a self-contained example. examples/cesium-app is renamed to examples/cesium-quakes-standalone and the package source lives at examples/cesium-quakes-standalone/src/cesium/ (same structure — cesium-config.ts, cesium-slice.ts, cesium-types.ts, utils.ts, components/, hooks/), just no longer published as a workspace package.

No behaviour change — the example still renders the Wadati–Benioff globe with subsurface seismicity, section-cut clipping, and the slab surface mesh. Build + typecheck green across the monorepo (41 packages, 45 typechecks).

Re: the two examples — I don't have a strong opinion on which ships; happy for your opensky flight data branch (#534) to be the canonical Cesium example if you prefer. The quakes demo is there if you think the subsurface/clipping story adds something the flight demo doesn't, but no pressure.

Adds examples/cesium-quakes-standalone, a Wadati-Benioff zone explorer
built on CesiumJS + Resium. Six subduction-zone presets fly the camera to
an along-strike view and cut the globe open with a clipping plane,
revealing the dipping plane of seismicity that traces the descending slab
from the trench down to ~700 km.

The Cesium integration lives inline under src/cesium/ rather than as a
workspace package, per review feedback on sqlrooms#530. The example depends only
on packages already on main: duckdb, recharts, room-shell, sql-editor, ui.

- SQL-driven entity layer: DuckDB query -> Cesium entities, with depth
  projected to altitude_m = -depth * 1000 so events sit at their true 3D
  position inside the Earth
- Section-cut clipping plane + a translucent parametric slab surface tuned
  per-preset to Slab2.0 dip profiles
- Live magnitude-vs-depth histogram tied to the active slice
- Global M5+ catalog since 2013 (~17k events) loaded as parquet;
  scripts/fetch-earthquakes.py regenerates it from the USGS FDSN feed

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants