Skip to content

Conversation

@oliveirara
Copy link
Contributor

@oliveirara oliveirara commented Sep 8, 2025

Summary by CodeRabbit

  • New Features

    • Introduced the LaStBeRu Explorer dashboard with an interactive sky map, filterable object list, detailed data tables, and a cutout image gallery with resilient loading.
    • Automatic data preparation from object storage into standardized JSON for fast loading.
  • Deployment

    • Added automated build and publish of the dashboard to GitHub Pages, enabling easy web access.
  • Documentation

    • Added dashboard usage guide and quick start.
    • Updated MinIO service link in the main README.
  • Chores

    • Added project configs (linting, TypeScript, Vite) and ignore rules.
    • Added a development container setup.

- update minio endpoint url in:
  - readme
  - dashboard/data/prepare_data.ipynb
  - dashboard/streamlit-css.py
  - notebooks/footprints/footprints.ipynb
  - notebooks/how_to.ipynb
  - notebooks/modified-gravity-tests/01_lastberu_cosmo_ground.ipynb
  - notebooks/proposals/proposals.ipynb
- remove dashboard directory and all its contents
- this includes streamlit app, data files, and requirements
- scaffold basic React/TypeScript application with Vite
- configure ESLint for code quality and formatting
- set up basic UI layout with MUI components
- add React Query for data fetching
- implement Recoil for state management
- create initial components: App, DataTables, SkyMap, Gallery, FiltersDrawer, ObjectsTable
- implement data loading from static JSON files
- implement basic filtering and selection of objects
- add basic sky map visualization with object positions
- add cutout image gallery with placeholder images
- implement basic UI theme and styling
- add .gitignore file to exclude node_modules and other unnecessary files
- add public directory with placeholder data and images
- add types file for data structures
- read data from minio
- process data
- save data to public/data directory
- configures ci to automatically build and deploy the react dashboard to github pages
- sets up python and node.js environments, installs dependencies, prepares data from minio, and builds the project
- deploys the built dashboard to github pages on pushes to the main or streamlit branches
- install pyarrow and fastparquet for enhanced data processing capabilities
@oliveirara oliveirara self-assigned this Sep 8, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 8, 2025

Walkthrough

Introduces a React + TypeScript dashboard under dashboard/ with data loading, filtering, sky map, and cutout gallery. Adds data preparation script pulling artifacts from MinIO. Sets up Vite, ESLint, theme, and CI to build/deploy to GitHub Pages. Adds a devcontainer. Updates notebook MinIO endpoints and a README link.

Changes

Cohort / File(s) Summary
Devcontainer
/.devcontainer/devcontainer.json
Adds Python 3.11 dev container; installs requirements and Streamlit; auto-opens files; runs Streamlit on port 8501; forwards/exposes port.
CI/CD: GitHub Pages deploy
/.github/workflows/deploy.yml
New workflow: build dashboard, prepare data from MinIO via secrets, upload artifact, and deploy to Pages on selected branches.
Root docs
/README.md
Updates MinIO service link URL in Download Data section.
Dashboard: project scaffolding & config
/dashboard/package.json, /dashboard/tsconfig.json, /dashboard/vite.config.ts, /dashboard/index.html, /dashboard/eslint.config.js, /dashboard/.gitignore, /dashboard/README.md
Adds Vite React+TS project, ESLint config, HTML entry, TypeScript config, build chunks, ignore rules, and dashboard README.
Dashboard: application source
/dashboard/src/App.tsx, /dashboard/src/main.tsx, /dashboard/src/theme.ts, /dashboard/src/types.ts, /dashboard/src/env.d.ts, /dashboard/src/hooks/useDebounce.ts
Adds main app, bootstrapping, theme, shared types, env typings, and a debounce hook.
Dashboard: data/API & UI components
/dashboard/src/api.ts, /dashboard/src/components/*
Adds JSON data loaders, MinIO cutout URL utilities, and UI components: DataTables, ObjectsTable, SkyMap, CutoutGrid, Gallery, VirtualizedList, FiltersDrawer.
Dashboard: data prep pipeline
/dashboard/prepare_data.py, /dashboard/public/data/.gitkeep
Adds script to fetch/process data from MinIO and emit JSON (database, consolidated, dictionary, cutouts); placeholder to keep data dir.
Notebooks: MinIO endpoint updates
/notebooks/footprints/footprints.ipynb, /notebooks/how_to.ipynb, /notebooks/proposals/proposals.ipynb
Updates MINIO_ENDPOINT_URL host values to a new ngrok domain; no logic changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant GH as GitHub
  participant WF as Actions: deploy.yml
  participant MinIO as MinIO (secrets)
  participant Pages as GitHub Pages

  Dev->>GH: push to dashboard/**
  GH-->>WF: trigger build job
  WF->>WF: setup Python/Node
  WF->>MinIO: run prepare_data.py (env secrets)
  MinIO-->>WF: data files (JSON)
  WF->>WF: npm ci && vite build (BASE_PATH)
  WF->>Pages: upload-pages-artifact
  Note over WF,Pages: On main/streamlit only
  GH-->>WF: trigger deploy job
  WF->>Pages: deploy artifact
  Pages-->>Dev: page URL output
Loading
sequenceDiagram
  autonumber
  actor User
  participant App as Dashboard App
  participant RQ as React Query
  participant FS as /public/data/*.json
  participant MinIO as MinIO Gateway

  User->>App: open dashboard
  App->>RQ: fetch db, consolidated, dictionary, cutouts
  RQ->>FS: GET /data/database.json
  RQ->>FS: GET /data/consolidated_database.json
  RQ->>FS: GET /data/dictionary.json
  RQ->>FS: GET /data/cutouts.json
  FS-->>RQ: JSON datasets
  RQ-->>App: resolved data
  App->>User: render tables, sky map, filters

  User->>App: open Cutouts/Gallery
  App->>MinIO: resolve cutout URL (ngrok-aware)
  MinIO-->>App: image or blob URL
  App->>User: show thumbnails/modal
Loading
sequenceDiagram
  autonumber
  participant Script as prepare_data.py
  participant MinIO as MinIO
  participant FS as dashboard/public/data/

  Script->>MinIO: download Processed_Cutouts.parquet
  Script->>MinIO: download FITS.parquet
  Script->>Script: merge/normalize cutouts (bands, paths)
  Script->>FS: write cutouts.json

  Script->>MinIO: download Consolidated_Data.csv
  Script->>Script: type normalize
  Script->>FS: write consolidated_database.json

  Script->>MinIO: download Database.csv
  Script->>Script: build dictionary, expand multi-values
  Script->>FS: write dictionary.json
  Script->>FS: write database.json
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

In a meadow of code I hop with glee,
A dashboard blooms for all to see.
Stars on a map, cutouts in rows—
MinIO whispers where data flows.
Pipelines churn, Pages take flight,
Thump-thump! I ship this starlit night. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch streamlit

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 and usage tips.

- trigger deployment workflow only when changes occur in the dashboard directory
- introduce features, quick start guide, data files, theming, and scripts
@oliveirara oliveirara merged commit 699d1a3 into main Sep 8, 2025
2 of 4 checks passed
@oliveirara oliveirara deleted the streamlit branch September 8, 2025 02:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 29

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
notebooks/how_to.ipynb (1)

64-72: Remove hardcoded MinIO credentials from all notebooks
Hardcoded MINIO_ENDPOINT_URL, ACCESS_KEY, and SECRET_KEY are present in:

  • notebooks/how_to.ipynb (lines 64–72)
  • notebooks/proposals/proposals.ipynb (lines 38–44)
  • notebooks/modified-gravity-tests/01_LaStBeRu_cosmo_ground.ipynb (lines 85–91)
  • notebooks/footprints/footprints.ipynb (lines 54–60)

Load these via

os.getenv("MINIO_ENDPOINT_URL")
os.getenv("MINIO_ACCESS_KEY")
os.getenv("MINIO_SECRET_KEY")

and raise an error if any are unset. Rotate exposed credentials and audit access.

rg -n -C1 -S '(MINIO_.*(KEY|SECRET)|slcomp)' notebooks/**/*.ipynb
notebooks/proposals/proposals.ipynb (3)

38-46: Remove hard-coded MinIO endpoint and credentials (secrets in repo).

Endpoint, access key, and secret are committed in plain text. Move to environment variables and fail fast if missing.

Apply this diff:

- MINIO_ENDPOINT_URL = "nonarithmetically-undeliberating-janelle.ngrok-free.app"
- ACCESS_KEY = "slcomp"
- SECRET_KEY = "slcomp@data"
- client = Minio(
-     MINIO_ENDPOINT_URL,
-     access_key=ACCESS_KEY,
-     secret_key=SECRET_KEY,
-     secure=True,
- )
+ MINIO_ENDPOINT_URL = os.environ["MINIO_ENDPOINT_URL"]
+ ACCESS_KEY = os.environ["MINIO_ACCESS_KEY"]
+ SECRET_KEY = os.environ["MINIO_SECRET_KEY"]
+ MINIO_SECURE = os.getenv("MINIO_SECURE", "1") not in {"0", "false", "False"}
+ client = Minio(
+     MINIO_ENDPOINT_URL,
+     access_key=ACCESS_KEY,
+     secret_key=SECRET_KEY,
+     secure=MINIO_SECURE,
+ )

534-536: Fix RA range logic (6h–20h → degrees).

Current expression uses 8*20 (160°), not 20*15 (300°). This incorrectly filters RA.

Apply this diff:

- Database = Database.query(
-     "(RA>=6*15 and RA <=8*20) and (DEC >=-75 and DEC <= 15)"
- ).reset_index(drop=True)
+ Database = Database.query(
+     "(RA >= 6*15 and RA <= 20*15) and (DEC >= -75 and DEC <= 15)"
+ ).reset_index(drop=True)

727-731: NaN handling in filter doesn’t work with isin([np.nan, ...]).

NaN never matches via isin. Use isna() for missing System_Type.

Apply this diff:

- data_with_match = data_with_match[
-     data_with_match.System_Type.isin([np.nan, "Single Lens Galaxy"])
- ].reset_index(drop=True)
+ data_with_match = data_with_match[
+     data_with_match["System_Type"].isna() | (data_with_match["System_Type"] == "Single Lens Galaxy")
+ ].reset_index(drop=True)
🧹 Nitpick comments (62)
dashboard/src/components/VirtualizedList.tsx (7)

3-9: Ensure stable React keys; optionally add keyExtractor.

As written, keys depend on renderItem providing them. To avoid “Each child should have a unique key” warnings, accept an optional keyExtractor and apply it where items are rendered.

 interface VirtualizedListProps<T = unknown> {
   items: T[];
   renderItem: (item: T, index: number) => React.ReactNode;
   itemHeight: number;
   containerHeight: number;
   overscan?: number;
+  keyExtractor?: (item: T, index: number) => React.Key;
 }
@@
-  overscan = 5
+  overscan = 5,
+  keyExtractor
 }: VirtualizedListProps<T>) => {
@@
-        {items.map((item, index) => renderItem(item, index))}
+        {items.map((item, index) => {
+          const node = renderItem(item, index);
+          const key = keyExtractor ? keyExtractor(item, index) : index;
+          return React.isValidElement(node)
+            ? React.cloneElement(node, { key })
+            : <div key={key}>{node}</div>;
+        })}
@@
-          {visibleItems.map(({ item, index }) =>
-            renderItem(item, index)
-          )}
+          {visibleItems.map(({ item, index }) => {
+            const node = renderItem(item, index);
+            const key = keyExtractor ? keyExtractor(item, index) : index;
+            return React.isValidElement(node)
+              ? React.cloneElement(node, { key })
+              : <div key={key}>{node}</div>;
+          })}

Also applies to: 12-17, 50-52, 75-77


27-29: Clamp overscan to a non-negative integer.

Guards against negative or fractional overscan values.

-    const start = Math.max(0, startIndex - overscan);
-    const end = Math.min(items.length - 1, endIndex + overscan);
+    const safeOverscan = Math.max(0, Math.floor(overscan));
+    const start = Math.max(0, startIndex - safeOverscan);
+    const end = Math.min(items.length - 1, endIndex + safeOverscan);

41-43: Reduce scroll jank with rAF batching.

Scrolling can fire dozens of events per frame; batch setState with requestAnimationFrame.

+  const rafRef = React.useRef<number | null>(null);
-  const handleScroll = React.useCallback((e: React.UIEvent<HTMLDivElement>) => {
-    setScrollTop(e.currentTarget.scrollTop);
-  }, []);
+  const handleScroll = React.useCallback((e: React.UIEvent<HTMLDivElement>) => {
+    const st = e.currentTarget.scrollTop;
+    if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
+    rafRef.current = requestAnimationFrame(() => setScrollTop(st));
+  }, []);
+
+  React.useEffect(() => {
+    return () => {
+      if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
+    };
+  }, []);

45-47: Clamp scrollTop when data or sizes shrink to avoid stale window.

If totalHeight decreases below current scrollTop, visible window can be off until the next user scroll.

   const totalHeight = items.length * itemHeight;
 
+  React.useEffect(() => {
+    const maxScroll = Math.max(0, totalHeight - containerHeight);
+    setScrollTop((s) => Math.min(s, maxScroll));
+  }, [totalHeight, containerHeight]);

61-62: Remove unused positioning on outer container.

position: 'relative' isn’t needed here; the absolutely positioned layer is relative to its own parent container below.

-        overflowY: 'auto',
-        position: 'relative'
+        overflowY: 'auto'

67-73: Hint GPU acceleration for smoother scrolling.

Small win: willChange helps browsers plan for frequent transforms.

           style={{
             transform: `translateY(${visibleIndices.start * itemHeight}px)`,
             position: 'absolute',
             top: 0,
             left: 0,
-            right: 0
+            right: 0,
+            willChange: 'transform'
           }}

47-54: Make the “20 items” heuristic configurable or data-driven.

Consider a prop like minVirtualizeCount = 20 or auto-enable when items.length * itemHeight > containerHeight.

dashboard/public/data/.gitkeep (1)

1-2: Make the generator path explicit (minor clarity).

Reference the exact script path to avoid ambiguity when browsing the repo.

-# Files are generated by prepare_data.py which fetches data from MinIO
+# Files are generated by dashboard/prepare_data.py which fetches data from MinIO
notebooks/how_to.ipynb (7)

23-27: Import display explicitly for portability.

Fixes the Ruff F821 and makes the notebook runnable after export.

 import io
+from IPython.display import display

333-338: MinIO client: prefer streaming/read() over .data (API compatibility + memory).

.data isn’t guaranteed across minio client versions and loads entire objects into memory. Use read() or stream.

Example refactor (apply similarly to other occurrences):

-Database_object = client.get_object("slcomp", "Data/Database.csv").data
-Database = pd.read_csv(io.StringIO(Database_object.decode("utf-8")), low_memory=False, dtype=object)
+resp = client.get_object("slcomp", "Data/Database.csv")
+try:
+    Database = pd.read_csv(io.StringIO(resp.read().decode("utf-8")), low_memory=False, dtype=object)
+finally:
+    resp.close()
+    resp.release_conn()

Also applies to: 752-761, 1098-1107, 1436-1445, 1691-1694, 2008-2015


1882-1886: Ensure directories exist before fget_object writes (robustness).

Current code may fail if nested directories aren’t present.

-[
-    client.fget_object("slcomp", "Cutouts/" + file_path, file_path)
-    for file_path in Cutouts_Catalog.query('JNAME=="J114833.1+193003.2"').file_path
-]
+for file_path in Cutouts_Catalog.query('JNAME=="J114833.1+193003.2"').file_path:
+    os.makedirs(os.path.dirname(file_path), exist_ok=True)
+    client.fget_object("slcomp", f"Cutouts/{file_path}", file_path)

2260-2266: Same: create parent dirs before saving processed cutouts.

-[ 
-    client.fget_object("slcomp", "Cutouts/" + file_path, file_path)
-    for file_path in Processed_Cutouts_Catalog.query('JNAME=="J114833.1+193003.2"').file_path
-]
+for file_path in Processed_Cutouts_Catalog.query('JNAME=="J114833.1+193003.2"').file_path:
+    os.makedirs(os.path.dirname(file_path), exist_ok=True)
+    client.fget_object("slcomp", f"Cutouts/{file_path}", file_path)

2978-2980: Boolean filter should not compare to string literal.

If is_rgb is boolean, compare as a boolean or filter directly.

-len(Processed_Cutouts_Catalog.query('is_rgb=="True"'))
+Processed_Cutouts_Catalog["is_rgb"].sum()  # or: len(Processed_Cutouts_Catalog[Processed_Cutouts_Catalog.is_rgb])

2403-2404: Avoid np.hstack on mixed list/scalar cells; use explode.

hstack risks splitting strings into characters if a cell isn’t a list.

-pd.DataFrame(np.hstack(Database.Lens_Type), columns=["Lens_Type"]).value_counts()
+tmp = Database[["Lens_Type"]].copy()
+tmp["Lens_Type"] = tmp["Lens_Type"].apply(lambda v: v if isinstance(v, list) else [v])
+tmp.explode("Lens_Type")["Lens_Type"].value_counts()

-pd.DataFrame(np.hstack(Database.Source_Type), columns=["Source_Type"]).value_counts()
+tmp = Database[["Source_Type"]].copy()
+tmp["Source_Type"] = tmp["Source_Type"].apply(lambda v: v if isinstance(v, list) else [v])
+tmp.explode("Source_Type")["Source_Type"].value_counts()

Also applies to: 2437-2438


1-3045: Strip heavy notebook outputs before committing (repo hygiene).

Outputs bloat diffs and repo size; keep notebooks lightweight or use nbdime/LFS.

README.md (1)

52-55: Endpoint drift: README MinIO link differs from notebook endpoint.

README uses ruggedly-quaky-maricruz…, notebooks use nonarithmetically-undeliberating-janelle… Consolidate to a single canonical URL or document that endpoints are ephemeral.

Suggestion:

  • Define MINIO_ENDPOINT_URL once (e.g., in an .env example) and reference it in README and notebooks.
  • Consider a stable subdomain (e.g., minio.slcomp.org) that CNAMEs to the current tunnel.
dashboard/.gitignore (2)

15-15: Typo: TypeScript build info pattern.

The file is typically tsconfig.tsbuildinfo. Use a broader pattern.

-tsbuildinfo.tsbuildinfo
+*.tsbuildinfo

12-12: Consider ignoring parquet artifacts if added later.

Future-proof the generated data rule.

 public/data/*.json
+public/data/*.parquet
notebooks/footprints/footprints.ipynb (2)

54-54: Make endpoint configurable; avoid hardcoding ephemeral ngrok URL

Bind MINIO_ENDPOINT_URL from env (optionally support a local default) to avoid future breakage and simplify CI/CD and local dev.

-import os
+import os
-MINIO_ENDPOINT_URL = "nonarithmetically-undeliberating-janelle.ngrok-free.app"
+MINIO_ENDPOINT_URL = os.getenv(
+    "MINIO_ENDPOINT_URL",
+    # Optional local/dev fallback (adjust as appropriate)
+    "nonarithmetically-undeliberating-janelle.ngrok-free.app"
+)

Consider centralizing this in a small config module used by notebooks and dashboard/prepare_data.py, and document required env vars in README and GH Actions (use repository Secrets).


57-57: Be explicit about TLS when initializing Minio client

Given the ngrok endpoint is HTTPS, pass secure=True explicitly to avoid ambiguity across client versions.

 client = Minio(
     MINIO_ENDPOINT_URL,
     access_key=ACCESS_KEY,
     secret_key=SECRET_KEY,
+    secure=True,
 )
notebooks/proposals/proposals.ipynb (1)

996-1058: Close image resources and simplify subplot loop.

Use context managers to avoid file/resource leaks and potential warnings.

Apply this diff:

-            data_io = io.BytesIO(
-                client.get_object("slcomp", "Cutouts/" + data.iloc[k].file_path).data
-            )
-            # im = plt.imread(data_io)
-            im = Image.open(data_io)
-            im.load()
-            ax = plt.subplot(gs[i, j])
+            data_io = io.BytesIO(
+                client.get_object("slcomp", "Cutouts/" + data.iloc[k].file_path).data
+            )
+            ax = plt.subplot(gs[i, j])
+            with Image.open(data_io) as im:
+                im.load()
                 if data.iloc[k].survey == "CS82":
-                ax.imshow(im, interpolation="lanczos", aspect="auto", cmap="gray")
+                    ax.imshow(im, interpolation="lanczos", aspect="auto", cmap="gray")
                 else:
-                ax.imshow(im, interpolation="lanczos", aspect="auto")
+                    ax.imshow(im, interpolation="lanczos", aspect="auto")
                 ax.set_xticklabels([])
                 ax.set_yticklabels([])
                 ax.set_xticks([])
                 ax.set_yticks([])
@@
-    im = Image.open(f"mosaics/{jname}.png")
-    im2 = im.crop(im.getbbox())
-    im2.save(f"mosaics/{jname}.png")
+    with Image.open(f"mosaics/{jname}.png") as _im:
+        im2 = _im.crop(_im.getbbox())
+        im2.save(f"mosaics/{jname}.png")
dashboard/tsconfig.json (1)

2-21: Tighten TS config for Vite/React.

Add Vite client types and convenient import aliasing.

Apply this diff:

   "compilerOptions": {
@@
-    "jsx": "react-jsx"
+    "jsx": "react-jsx",
+    "types": ["vite/client"],
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["src/*"]
+    }
   },
dashboard/src/hooks/useDebounce.ts (1)

6-14: Guard zero/negative delays.

Immediate update when delay <= 0 avoids unnecessary timers.

Apply this diff:

   useEffect(() => {
-    const handler = setTimeout(() => {
-      setDebouncedValue(value);
-    }, delay);
+    if (delay <= 0) {
+      setDebouncedValue(value);
+      return;
+    }
+    const handler = setTimeout(() => {
+      setDebouncedValue(value);
+    }, delay);
dashboard/package.json (2)

6-11: Add a dedicated typecheck and fix script.

Keeps build fast and enables quick autofixes.

Apply this diff:

   "scripts": {
     "dev": "vite",
-    "build": "tsc && vite build",
+    "build": "tsc --noEmit && vite build",
     "preview": "vite preview",
-    "lint": "eslint src --ext .ts,.tsx"
+    "lint": "eslint src --ext .ts,.tsx",
+    "lint:fix": "eslint src --ext .ts,.tsx --fix",
+    "typecheck": "tsc --noEmit"
   },

1-5: Add engines for predictable CI/dev behavior.

Pin Node to the version Vite/TS expect.

Apply this diff:

 {
   "name": "slcomp-dashboard",
   "version": "0.1.0",
   "private": true,
   "type": "module",
+  "engines": {
+    "node": ">=18.18.0"
+  },
.devcontainer/devcontainer.json (1)

20-20: Security note: disabling CORS/XSRF.

Okay for local dev, but avoid committing insecure defaults; add a comment or guard via env flag.

Apply this diff:

-  "updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo '✅ Packages installed and Requirements met'",
+  "updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo '✅ Packages installed and Requirements met' # dev-only",
dashboard/vite.config.ts (2)

4-7: Normalize BASE_PATH for safety.

Ensures leading/trailing slashes so GH Pages routing is correct regardless of input.

Apply this diff:

-// For GitHub Pages deployment: set BASE_PATH environment variable to your repo name
-// Example: export BASE_PATH=/slcomp/ before building
-const basePath = process.env.BASE_PATH || '/';
+// For GitHub Pages deployment: set BASE_PATH to your repo name (with or without slashes)
+const rawBase = process.env.BASE_PATH || '/';
+const basePath = (() => {
+  let b = rawBase.trim();
+  if (!b.startsWith('/')) b = '/' + b;
+  if (!b.endsWith('/')) b = b + '/';
+  return b;
+})();

23-31: Consider strict port to avoid collision during dev.

Small DX improvement.

Apply this diff:

   server: { 
-    port: 5173,
+    port: 5173,
+    strictPort: true,
dashboard/src/types.ts (3)

1-4: Avoid any in data records.

Prefer unknown and narrow at use sites to keep type-safety.

Apply this diff:

 export interface DataRecord {
   JNAME: string;
-  [key: string]: any; // dynamic attributes
+  [key: string]: unknown; // dynamic attributes
 }

6-9: Same here for ConsolidatedRecord.

Apply this diff:

 export interface ConsolidatedRecord {
   JNAME: string;
-  [key: string]: any;
+  [key: string]: unknown;
 }

18-21: Avoid any in DictionaryEntry.

Keeps downstream code safer.

Apply this diff:

 export interface DictionaryEntry {
   JNAME?: string[] | string;
-  [key: string]: any;
+  [key: string]: unknown;
 }
dashboard/eslint.config.js (2)

3-3: Ignore pattern may miss other build artifacts.

If you want to fully avoid scanning generated/static files, consider also ignoring coverage/, .vite/, and public/data/*.json (though files: only targets src/, so this is optional).


21-26: Consider enabling unused-disable reporting.

Helps keep config tidy by flagging stale // eslint-disable lines.

   {
     files: ['src/**/*.{ts,tsx}'],
+    linterOptions: { reportUnusedDisableDirectives: true },
     languageOptions: {
dashboard/prepare_data.py (2)

24-27: Make output path robust to working directory.

Resolve DATA_PATH relative to this script so CI/local runs are consistent.

-# Data will be saved to public/data directory
-DATA_PATH = "public/data"
+# Data will be saved to dashboard/public/data (relative to this file)
+DATA_PATH = str((pathlib.Path(__file__).parent / "public" / "data").resolve())

74-76: Consider ordered categories for band.

If band sorting matters in the UI, set ordered=True to enforce intended order.

-cutouts.band = pd.Categorical(
-    cutouts.band, categories=["u", "g", "r", "i", "z", "y", "trilogy", "lsb"]
-)
+cutouts.band = pd.Categorical(
+    cutouts.band,
+    categories=["u", "g", "r", "i", "z", "y", "trilogy", "lsb"],
+    ordered=True,
+)
.github/workflows/deploy.yml (2)

23-23: Clean trailing spaces to satisfy linters.

Minor formatting to appease YAML linters and keep diffs clean.

-        uses: actions/checkout@v4
-          
+        uses: actions/checkout@v4
@@
-          
+ 
@@
-          
+ 
@@
-          
+ 
@@
-          
+ 
@@
-        
+ 
@@
-          
+ 

Also applies to: 28-28, 35-35, 40-40, 49-49, 54-54, 57-57, 63-63


36-54: Optional: set working-directory instead of cd for clarity.

Reduces repetition and chances of path mistakes.

-      - name: Install Python dependencies
-        run: |
-          cd dashboard
-          pip install pandas numpy minio pyarrow fastparquet
+      - name: Install Python dependencies
+        working-directory: dashboard
+        run: pip install pandas numpy minio pyarrow fastparquet
@@
-      - name: Prepare data from MinIO
+      - name: Prepare data from MinIO
         env:
           MINIO_ENDPOINT_URL: ${{ secrets.MINIO_ENDPOINT_URL }}
           MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
           MINIO_SECRET_KEY: ${{ secrets.MINIO_SECRET_KEY }}
-        run: |
-          cd dashboard
-          python prepare_data.py
+        working-directory: dashboard
+        run: python prepare_data.py
@@
-      - name: Install dependencies
-        run: |
-          cd dashboard
-          npm ci
+      - name: Install dependencies
+        working-directory: dashboard
+        run: npm ci
@@
-      - name: Build
-        run: |
-          cd dashboard
-          export BASE_PATH=/slcomp/
-          npm run build
+      - name: Build
+        working-directory: dashboard
+        env:
+          BASE_PATH: /slcomp/
+        run: npm run build
@@
-      - name: Upload artifact
+      - name: Upload artifact
         uses: actions/upload-pages-artifact@v3
         with:
           path: 'dashboard/dist'
dashboard/src/main.tsx (2)

8-24: Harden ErrorBoundary types and add a lightweight reset

Tighten types and provide a simple “try again” without a full reload.

-class ErrorBoundary extends React.Component<{children: React.ReactNode}, {error: any}> {
-  constructor(props:any){
+class ErrorBoundary extends React.Component<React.PropsWithChildren, { error: Error | null }> {
+  constructor(props: React.PropsWithChildren){
     super(props); this.state = { error: null };
   }
-  static getDerivedStateFromError(error:any){ return { error }; }
-  componentDidCatch(err:any, info:any){ console.error('App crashed:', err, info); }
+  static getDerivedStateFromError(error: Error){ return { error }; }
+  componentDidCatch(err: Error, info: React.ErrorInfo){ console.error('App crashed:', err, info); }
   render(){
     if(this.state.error){
       return <div style={{padding:24,fontFamily:'monospace',color:'#eee'}}>
         <h2>Application Error</h2>
-        <pre>{String(this.state.error)}</pre>
-        <p>Check console for stack trace.</p>
+        <pre>{String(this.state.error.message || this.state.error)}</pre>
+        <p>Check console for stack trace.</p>
+        <button onClick={() => this.setState({ error: null })} style={{marginTop:12}}>Try again</button>
       </div>;
     }
     return this.props.children;
   }
 }

37-46: Guard #root existence and wrap in StrictMode

Prevents a cryptic null deref and enables extra checks in dev.

-ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-  <QueryClientProvider client={qc}>
-    <ThemeProvider theme={darkAquaTheme}>
-      <CssBaseline />
-      <ErrorBoundary>
-        <App />
-      </ErrorBoundary>
-    </ThemeProvider>
-  </QueryClientProvider>
-);
+const rootEl = document.getElementById('root');
+if (!rootEl) throw new Error('Root element #root not found');
+ReactDOM.createRoot(rootEl).render(
+  <React.StrictMode>
+    <QueryClientProvider client={qc}>
+      <ThemeProvider theme={darkAquaTheme}>
+        <CssBaseline />
+        <ErrorBoundary>
+          <App />
+        </ErrorBoundary>
+      </ThemeProvider>
+    </QueryClientProvider>
+  </React.StrictMode>
+);
dashboard/README.md (2)

18-24: Fix markdownlint MD058: add blank lines around the table

Adds the required blank lines for table blocks.

-## Data Files (place in `public/data/`)
-| File | Purpose |
+## Data Files (place in `public/data/`)
+
+| File | Purpose |
 ...
-| `dictionary.json` | Reference dictionary |
+| `dictionary.json` | Reference dictionary |
+

11-15: Optional: call out data preparation step to avoid 404s

If data is generated by prepare_data.py in CI, mention how to populate public/data locally.

I can propose a short “Prepare data locally” subsection if you confirm the intended local workflow.

dashboard/src/env.d.ts (1)

3-12: Add missing type for VITE_MINIO_SCHEME (if referenced)

AI summary mentions usage; add it (optional) to avoid TS errors if used.

 interface ImportMetaEnv {
   readonly VITE_MINIO_ENDPOINT: string;
+  readonly VITE_MINIO_SCHEME?: 'http' | 'https';
   readonly VITE_MINIO_ACCESS_KEY: string;
   readonly VITE_MINIO_SECRET_KEY: string;
   readonly VITE_MINIO_BUCKET: string;
 }

If you remove secrets per above, keep only ENDPOINT/BUCKET (and optional SCHEME).

dashboard/src/theme.ts (2)

1-1: Avoid as any on theme.shadows; type it as Shadows

Keeps TS safety while satisfying MUI’s 25-shadow tuple.

-import { createTheme, alpha } from '@mui/material/styles';
+import { createTheme, alpha } from '@mui/material/styles';
+import type { Shadows } from '@mui/material/styles';
@@
-const customShadows = [
+const customShadows = [
   'none',
   '0 2px 4px -2px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.02)',
   '0 4px 12px -2px rgba(0,0,0,0.65), 0 0 0 1px rgba(255,255,255,0.02)',
   '0 6px 18px -4px rgba(0,0,0,0.65), 0 0 0 1px rgba(255,255,255,0.025)',
   '0 10px 28px -6px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.025)',
   ...Array(20).fill('0 0 0 1px rgba(0,0,0,0.4)') as string[]
-] as const;
+] as unknown as Shadows;
@@
-  shadows: customShadows as any,
+  shadows: customShadows,

Also applies to: 3-10, 38-38


42-52: Add Firefox scrollbar styling (WebKit-only at the moment)

Small cross-browser polish.

         body: {
           backgroundColor: '#03060a',
           backgroundImage: [
             'radial-gradient(circle at 20% 15%, rgba(0,200,255,0.09), rgba(0,0,0,0) 45%)',
             'radial-gradient(circle at 80% 75%, rgba(120,60,255,0.10), rgba(0,0,0,0) 50%)',
             'linear-gradient(135deg, #020409 0%, #040b14 45%, #020409 100%)'
           ].join(','),
           backgroundAttachment: 'fixed',
           overscrollBehavior: 'none',
-          WebkitFontSmoothing: 'antialiased'
+          WebkitFontSmoothing: 'antialiased',
+          scrollbarWidth: 'thin',
+          scrollbarColor: '#145566 #03060a'
         },
dashboard/src/components/DataTables.tsx (1)

1-1: Memoize column definitions to avoid DataGrid state resets and extra work.

Apply:

-import React from 'react';
+import React, { useMemo } from 'react';

And:

-  const dbCols = autoCols(database);
-  const consCols = autoCols(consolidated);
+  const dbCols = useMemo(() => autoCols(database), [database]);
+  const consCols = useMemo(() => autoCols(consolidated), [consolidated]);
dashboard/src/components/Gallery.tsx (3)

90-96: Don’t open modal for missing images; add alt text.

Improves UX and accessibility.

Apply:

-          <Grid item key={img.key} xs={6} sm={4} md={3} lg={2} onClick={()=> openModal(idx)} style={{ cursor: img.url ? 'pointer':'default' }}>
+          <Grid item key={img.key} xs={6} sm={4} md={3} lg={2} onClick={()=> img.url && openModal(idx)} style={{ cursor: img.url ? 'pointer':'default' }}>
@@
-                {img.url ? (<img src={img.url} style={{ maxWidth:'100%', maxHeight:120, objectFit:'contain' }} />) : (<Typography variant="caption" color="text.secondary">No image</Typography>)}
+                {img.url ? (<img src={img.url} alt={`${img.survey} ${img.band} cutout for ${img.key}`} style={{ maxWidth:'100%', maxHeight:120, objectFit:'contain' }} />) : (<Typography variant="caption" color="text.secondary">No image</Typography>)}

120-123: Alt text for modal image.

Apply:

-                  <img src={current.url} style={{ maxWidth:'100%', maxHeight:'70vh', objectFit:'contain', borderRadius:4 }} />
+                  <img src={current.url} alt={`${current.survey} ${current.band} cutout for ${current.key}`} style={{ maxWidth:'100%', maxHeight:'70vh', objectFit:'contain', borderRadius:4 }} />

37-51: Optional: Fetch in parallel with limited concurrency for large sets.

Sequential await per item will be slow; consider a small pool (e.g., 6–8) and update progress as promises settle.

If helpful, I can provide a p-limit based diff.

dashboard/src/components/CutoutGrid.tsx (2)

1-1: Import useEffect for cleanup.

Apply:

-import React, { memo } from 'react';
+import React, { memo, useEffect } from 'react';

42-49: Add alt text for accessibility.

Apply:

-        {!isLoading && data && <img 
-          src={data} 
+        {!isLoading && data && <img
+          src={data}
+          alt={`${record.survey} ${record.band} cutout for ${record.JNAME}`}
           loading="lazy" 
           decoding="async"
           crossOrigin="anonymous"
           style={{ width:'100%', height:'100%', objectFit:'contain', imageRendering:'auto' }}
dashboard/src/components/SkyMap.tsx (4)

231-233: Keep marker radius constant in screen px under anisotropic scaling.

Use the larger axis scale for px→world conversion; current code uses only baseScaleX, producing ellipses with zoom/aspect changes.

-      const pxToWorld = 1 / (baseScaleX*zoom);
-      const rWorld = targetPx * pxToWorld;
+      const invS = 1 / (Math.max(baseScaleX, baseScaleY) * zoom);
+      const rWorld = targetPx * invS;

and

-        const pxToWorld = 1 / (baseScaleX*zoom);
-        const rWorld = targetPx * pxToWorld;
+        const invS = 1 / (Math.max(baseScaleX, baseScaleY) * zoom);
+        const rWorld = targetPx * invS;

Also applies to: 246-247


362-377: Add pointercancel handler to avoid stuck “grabbing” state on gesture interruptions.

     canvas.addEventListener('pointerup', onUp);
+    canvas.addEventListener('pointercancel', onUp);
     canvas.addEventListener('pointerleave', onUp);
@@
-      canvas.removeEventListener('pointerup', onUp as any);
+      canvas.removeEventListener('pointerup', onUp as any);
+      canvas.removeEventListener('pointercancel', onUp as any);
       canvas.removeEventListener('pointerleave', onUp as any);

97-106: Remove unused refs (dead state).

lastPtsRef, lastSelectedRef, lastPanRef, lastZoomRef are assigned but never read. Trim to reduce churn.


307-312: Redundant redraw effects.

Both effects call draw(); a single effect keyed on draw is sufficient.

-  useEffect(()=>{ draw(); }, [draw]);
-
-  // Redraw selection change
-  useEffect(()=>{ draw(); }, [selected, draw]);
+  useEffect(()=>{ draw(); }, [draw]);
dashboard/src/components/ObjectsTable.tsx (1)

31-33: Tighten types for handlers and row-className.

Avoid any; use DataGrid types for better safety and editor help.

-import { DataGrid, GridColDef } from '@mui/x-data-grid';
+import { DataGrid, GridColDef, GridRowParams, GridPaginationModel, GridRowClassNameParams } from '@mui/x-data-grid';
@@
-  const handleRowClick = useCallback((params: any) => {
+  const handleRowClick = useCallback((params: GridRowParams) => {
     onSelect(params.row.JNAME);
   }, [onSelect]);
@@
-  const handlePaginationChange = useCallback((model: any) => {
+  const handlePaginationChange = useCallback((model: GridPaginationModel) => {
     setPage(model.page);
     setPageSize(model.pageSize);
   }, []);
@@
-    getRowClassName: (params: any) => params.row.JNAME === selected ? 'selected-row' : '',
+    getRowClassName: (params: GridRowClassNameParams) => (params.row as any).JNAME === selected ? 'selected-row' : '',

Also applies to: 35-38, 65-66, 22-24

dashboard/src/App.tsx (6)

170-171: Avoid mutating dependencies with in-place sort.

references.sort() mutates the array and can create subtle ordering bugs. Sort a copy instead.

-  const allReferences = useMemo(()=> references.sort(), [references]);
+  const allReferences = useMemo(()=> [...references].sort(), [references]);

53-66: Stream min/max instead of building large arrays.

Math.min(...vals) with spreads is memory-heavy for big datasets. Compute min/max in one pass.

-  const domain = useMemo(()=>{
-    const acc: Record<string,{min:number;max:number}> = {};
-    numericFields.forEach(f=>{
-      const vals: number[] = [];
-      for(const r of database){
-        if(!r) continue;
-        let v: unknown = r[f.key];
-        if(typeof v === 'string'){ const parsed = parseFloat(v); if(!isNaN(parsed)) v = parsed; }
-        if(typeof v === 'number' && !isNaN(v)) vals.push(v);
-      }
-      if(vals.length){ acc[f.key] = { min: Math.min(...vals), max: Math.max(...vals) }; }
-    });
-    return acc;
-  }, [database, numericFields]);
+  const domain = useMemo(()=>{
+    const acc: Record<string,{min:number;max:number}> = {};
+    for (const f of numericFields) {
+      let min = Infinity, max = -Infinity;
+      for (const r of database) {
+        if(!r) continue;
+        let v: unknown = r[f.key];
+        if (typeof v === 'string') { const p = parseFloat(v); if (!isNaN(p)) v = p; }
+        if (typeof v === 'number' && !isNaN(v)) { if (v < min) min = v; if (v > max) max = v; }
+      }
+      if (min !== Infinity) acc[f.key] = { min, max };
+    }
+    return acc;
+  }, [database, numericFields]);

150-156: Reset should also clear current selection (optional).

When resetting filters, keeping a stale JNAME selected can confuse users. Clear selection too.

-  const resetFilters = useCallback(() => {
-    setFilters(initialFilters);
-  }, [initialFilters]);
+  const resetFilters = useCallback(() => {
+    setFilters(initialFilters);
+    setJName('');
+  }, [initialFilters]);

28-33: Static JSON: make queries non-stale and avoid retries.

These endpoints are static assets. Consider disabling retries and marking data perpetually fresh.

-  const { data: database = [], isLoading: dbLoading, error: dbError } = useQuery({ queryKey: ['db'], queryFn: loadDatabase });
+  const { data: database = [], isLoading: dbLoading, error: dbError } =
+    useQuery({ queryKey: ['db'], queryFn: loadDatabase, staleTime: Infinity, retry: false });
-  const { data: consolidated = [], isLoading: consLoading, error: consError } = useQuery({ queryKey: ['cons'], queryFn: loadConsolidated });
+  const { data: consolidated = [], isLoading: consLoading, error: consError } =
+    useQuery({ queryKey: ['cons'], queryFn: loadConsolidated, staleTime: Infinity, retry: false });
-  const { data: dictionary = {} as Record<string, unknown>, isLoading: dictLoading, error: dictError } = useQuery({ queryKey: ['dict'], queryFn: loadDictionary });
+  const { data: dictionary = {} as Record<string, unknown>, isLoading: dictLoading, error: dictError } =
+    useQuery({ queryKey: ['dict'], queryFn: loadDictionary, staleTime: Infinity, retry: false });
-  const { data: cutouts = [], isLoading: cutoutsLoading, error: cutoutsError } = useQuery({ queryKey: ['cutouts'], queryFn: loadCutouts });
+  const { data: cutouts = [], isLoading: cutoutsLoading, error: cutoutsError } =
+    useQuery({ queryKey: ['cutouts'], queryFn: loadCutouts, staleTime: Infinity, retry: false });

184-188: Disable “Reset Filters” based on actual state, not reference equality.

filters === initialFilters only catches the “just-reset” reference. Consider a shallow check on fields to better reflect user changes.

Example:

- disabled={filters === initialFilters}
+ disabled={
+   filters.jnameSearch === '' &&
+   filters.references.length === 0 &&
+   Object.values(filters.numeric).every(v => v == null)
+ }

198-203: Error panel: avoid leaking raw errors to end users.

Render a friendly message and log details to console for diagnostics.

-  <Typography variant="body2" color="text.secondary">{String(anyError)}</Typography>
+  <Typography variant="body2" color="text.secondary">
+    One or more datasets failed to load. Please retry or check hosting configuration.
+  </Typography>
+  {console.error('[Explorer] Data load error:', anyError)}
dashboard/src/components/FiltersDrawer.tsx (1)

31-31: Format numeric labels.

Long floats can be noisy. Format to a few decimals.

-const pct = (min: number, max: number) => `${min} – ${max}`;
+const fmt = (n: number) => (Number.isInteger(n) ? n : +n.toFixed(4));
+const pct = (min: number, max: number) => `${fmt(min)} – ${fmt(max)}`;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fec4378 and 13a3b20.

⛔ Files ignored due to path filters (2)
  • dashboard/package-lock.json is excluded by !**/package-lock.json
  • dashboard/public/telescope.png is excluded by !**/*.png
📒 Files selected for processing (30)
  • .devcontainer/devcontainer.json (1 hunks)
  • .github/workflows/deploy.yml (1 hunks)
  • README.md (1 hunks)
  • dashboard/.gitignore (1 hunks)
  • dashboard/README.md (1 hunks)
  • dashboard/eslint.config.js (1 hunks)
  • dashboard/index.html (1 hunks)
  • dashboard/package.json (1 hunks)
  • dashboard/prepare_data.py (1 hunks)
  • dashboard/public/data/.gitkeep (1 hunks)
  • dashboard/src/App.tsx (1 hunks)
  • dashboard/src/api.ts (1 hunks)
  • dashboard/src/components/CutoutGrid.tsx (1 hunks)
  • dashboard/src/components/DataTables.tsx (1 hunks)
  • dashboard/src/components/FiltersDrawer.tsx (1 hunks)
  • dashboard/src/components/Gallery.tsx (1 hunks)
  • dashboard/src/components/ObjectsTable.tsx (1 hunks)
  • dashboard/src/components/SkyMap.tsx (1 hunks)
  • dashboard/src/components/VirtualizedList.tsx (1 hunks)
  • dashboard/src/env.d.ts (1 hunks)
  • dashboard/src/hooks/useDebounce.ts (1 hunks)
  • dashboard/src/main.tsx (1 hunks)
  • dashboard/src/theme.ts (1 hunks)
  • dashboard/src/types.ts (1 hunks)
  • dashboard/tsconfig.json (1 hunks)
  • dashboard/vite.config.ts (1 hunks)
  • notebooks/footprints/footprints.ipynb (1 hunks)
  • notebooks/how_to.ipynb (1 hunks)
  • notebooks/modified-gravity-tests/01_LaStBeRu_cosmo_ground.ipynb (1 hunks)
  • notebooks/proposals/proposals.ipynb (1 hunks)
🔥 Files not summarized due to errors (1)
  • notebooks/modified-gravity-tests/01_LaStBeRu_cosmo_ground.ipynb: Error: Server error: no LLM provider could handle the message
👮 Files not reviewed due to content moderation or server errors (1)
  • notebooks/modified-gravity-tests/01_LaStBeRu_cosmo_ground.ipynb
🧰 Additional context used
🧬 Code graph analysis (6)
dashboard/src/api.ts (1)
dashboard/src/types.ts (4)
  • DataRecord (1-4)
  • ConsolidatedRecord (6-9)
  • Dictionary (23-23)
  • CutoutRecord (11-16)
dashboard/src/main.tsx (1)
dashboard/src/theme.ts (1)
  • darkAquaTheme (13-137)
dashboard/src/components/Gallery.tsx (2)
dashboard/src/types.ts (1)
  • CutoutRecord (11-16)
dashboard/src/api.ts (1)
  • getCutoutObject (52-104)
dashboard/src/App.tsx (8)
dashboard/src/components/DataTables.tsx (1)
  • DataTables (32-87)
dashboard/src/components/CutoutGrid.tsx (1)
  • CutoutGrid (12-25)
dashboard/src/api.ts (4)
  • loadDatabase (5-8)
  • loadConsolidated (10-13)
  • loadDictionary (15-20)
  • loadCutouts (22-25)
dashboard/src/components/FiltersDrawer.tsx (2)
  • FiltersState (12-16)
  • FiltersDrawer (33-148)
dashboard/src/hooks/useDebounce.ts (1)
  • useDebounce (3-17)
dashboard/src/components/ObjectsTable.tsx (1)
  • ObjectsTable (18-109)
dashboard/src/components/SkyMap.tsx (1)
  • SkyMap (41-398)
dashboard/src/types.ts (1)
  • CutoutRecord (11-16)
dashboard/src/components/CutoutGrid.tsx (2)
dashboard/src/types.ts (1)
  • CutoutRecord (11-16)
dashboard/src/api.ts (1)
  • getCutoutObject (52-104)
dashboard/src/components/DataTables.tsx (1)
dashboard/src/types.ts (2)
  • DataRecord (1-4)
  • ConsolidatedRecord (6-9)
🪛 markdownlint-cli2 (0.17.2)
dashboard/README.md

18-18: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🪛 Ruff (0.12.2)
notebooks/how_to.ipynb

64-64: Undefined name display

(F821)

dashboard/prepare_data.py

245-245: Do not use bare except

(E722)

🪛 actionlint (1.7.7)
.github/workflows/deploy.yml

25-25: the runner of "actions/setup-python@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 YAMLlint (1.37.1)
.github/workflows/deploy.yml

[error] 23-23: trailing spaces

(trailing-spaces)


[error] 28-28: trailing spaces

(trailing-spaces)


[error] 35-35: trailing spaces

(trailing-spaces)


[error] 40-40: trailing spaces

(trailing-spaces)


[error] 49-49: trailing spaces

(trailing-spaces)


[error] 54-54: trailing spaces

(trailing-spaces)


[error] 57-57: trailing spaces

(trailing-spaces)


[error] 63-63: trailing spaces

(trailing-spaces)

🔇 Additional comments (11)
dashboard/src/components/VirtualizedList.tsx (2)

1-82: Solid, minimal virtualization component.

Clean math, correct index clamping, and nice fallback for small lists. Good reuse potential.


6-7: Validate heights > 0.

Guard against itemHeight <= 0 or containerHeight <= 0 to prevent division edge cases.

Do you want me to add a small runtime assert (and story/test) to enforce positive heights?

notebooks/proposals/proposals.ipynb (1)

875-881: Verify and correct boolean comparison for is_rgb

  • Comparing to string "True" may mis-filter; confirm Cutouts["is_rgb"].dtype is boolean.
  • If it’s not, convert first:
    Cutouts["is_rgb"] = Cutouts["is_rgb"].astype("boolean")
  • Then update the query:
    - .query('cutout_size == "20asec" and is_rgb == "True"')
    + .query('cutout_size == "20asec" and is_rgb == True')
dashboard/src/types.ts (1)

11-16: Align CutoutRecord with the JSON shape from prepare_data

Remove any fields dropped in data prep (cutout_size, file_name) and keep band required; add the two columns still present in /data/cutouts.json:

 dashboard/src/types.ts
 export interface CutoutRecord {
   JNAME: string;
   survey: string;
   band: string;
   file_path: string;
+  processing: string;
+  is_rgb: string;
 }

Likely an incorrect or invalid review comment.

dashboard/eslint.config.js (1)

10-19: ESLint top-level await is supported; no changes required. dashboard/package.json specifies eslint ^9.9.0 and CI uses Node 18, satisfying the flat-config ESM and top-level-await requirements. Optional: switch to static imports for broader environment compatibility.

dashboard/prepare_data.py (1)

12-21: Validate MinIO vars early, strip scheme, and fail fast
Add a guard at the top of dashboard/prepare_data.py to ensure all three env vars are set, remove any http(s):// scheme before passing to Minio, and raise a clear error. If you support Python < 3.9, replace str.removeprefix with urlparse or a regex. Also confirm that your endpoint is in host[:port] form:

 MINIO_ENDPOINT_URL = os.getenv("MINIO_ENDPOINT_URL")
 ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY")
 SECRET_KEY = os.getenv("MINIO_SECRET_KEY")

+if not (MINIO_ENDPOINT_URL and ACCESS_KEY and SECRET_KEY):
+    raise RuntimeError(
+        "MINIO_ENDPOINT_URL, MINIO_ACCESS_KEY, and MINIO_SECRET_KEY must be set"
+    )
+# strip any URL scheme (use urlparse or removeprefix on Python ≥3.9)
+endpoint = MINIO_ENDPOINT_URL.removeprefix("https://").removeprefix("http://")
 
 client = Minio(
-    MINIO_ENDPOINT_URL,
+    endpoint,
     access_key=ACCESS_KEY,
     secret_key=SECRET_KEY,
     secure=True,
 )
dashboard/src/main.tsx (2)

26-35: React Query defaults look good

Sane cache policy for an interactive dashboard; no changes needed.


26-35: No changes needed: @tanstack/react-query v5.56.2 supports gcTime.

dashboard/src/components/CutoutGrid.tsx (1)

30-35: Optional: avoid caching blob URLs in React Query.

If using @tanstack/react-query v5, set gcTime: 0; if v4, set cacheTime: 0 to reduce holding blob URLs in memory after unmount.

Want me to propose the exact option based on your installed version?

dashboard/src/components/SkyMap.tsx (1)

380-397: Nice UX polish.

Controls, zoom bounds, and DPR handling look solid. The selection glow is tasteful and performant with conditional RAF.

dashboard/src/components/ObjectsTable.tsx (1)

40-48: Auto-scroll to selection is a nice touch.

Keeping the selected row visible improves ergonomics with large datasets.

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