Any CSV with ≥ 2 az_deg,alt_deg rows now works. Points are sorted by
azimuth, then linearly interpolated to fill all 360 degrees. A 28-point
sparse horizon profile interpolates cleanly; an exact 360-point file is
used as-is. Also resets the file input after upload so re-uploading the
same file works without picking a different one first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SNR calculator: signalFromSB calibration was 4750× too small (0.001 vs ~4.75
at SB=21). Calibration is now derived consistently from the sky background
constants: C[filter] = sky_e_s[filter] / 10^((21 - 21.5) / 2.5). Also made
it filter-aware so narrowband filters use their own reference. Replaced the
broken 500+/billions display with a proper per-filter result or a
'too faint for this setup' message when signal ≈ 0.
Score weights: 'Best score tonight' sort now accepts score_alt/fov/time/moon
query params (0.0–1.0, server-side normalised to sum=1). Frontend adds a
⚙ weights button next to the sort dropdown that reveals 4 sliders showing
effective %, persisted to localStorage. Weights default to 40/30/20/10.
Selected filter: clicking a filter pill in the Filters tab now highlights the
row (bg + amber outline on the pill + ▶ marker) so it's clear which filter
the SNR calculator and workflow card are showing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add shared Arc<Semaphore> (1 permit) through main → AppState and jobs.
All heavy operations (catalog refresh, rebuild, nightly recompute,
factory reset) now serialise: a second trigger returns "already_running"
instead of spawning a parallel task that fights over the SQLite WAL lock.
- Scheduled nightly job acquires the semaphore too, so it waits rather
than stomping on a manual rebuild triggered at startup.
- Replace 90 × precompute_lightweight calls (90 separate transactions,
horizon fetched 90 times) with precompute_next_90_nights: one bulk
SELECT to find missing dates, horizon fetched once, all inserts in a
single transaction. Eliminates the 1–5s per-INSERT lock waits seen
when multiple jobs were competing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Factory reset endpoint clears computed tables (catalog, nightly_cache,
tonight, weather_cache), VACUUMs the DB, then rebuilds in background.
Preserves all user data (imaging_log, gallery, phd2_logs, horizon).
- Catalog rebuild now fetches data BEFORE touching the DB — network
failures no longer leave the catalog empty. DELETE + INSERT wrapped
in a single transaction via replace_catalog() so a mid-write failure
rolls back and old data is preserved.
- Added nightly_cache indexes and bumped pool to 10 connections with
30s acquire timeout to prevent exhaustion during rebuilds.
- Settings page: factory reset button with inline confirmation dialog.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All profiles (including the default AT71+ATR2600C) now show Edit and Use buttons
- Active profile tracked in localStorage via astronome_active_profile
- Default profile is seeded from localStorage on first load so it can be renamed
- Delete button only shown when more than one profile exists
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The seasonal peak subquery used a correlated SELECT inside a GROUP BY,
causing a full nightly_cache scan per object (210-270s for 14k objects).
Replaced with a simple MAX() GROUP BY — now instant.
Also added three indexes on nightly_cache(night_date) that were missing
and causing all dashboard queries to run 2+ second full table scans.
Replaced 🔴 emoji in night mode buttons with CSS circles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NightModeProvider context (localStorage persisted) in contexts/NightMode.tsx
- Full-screen fixed red overlay (rgba 160,0,0 @ 55%, mix-blend-mode: multiply) fades in over the entire UI; multiply blend keeps dark backgrounds black while turning all white/bright content deep red
- Desktop: toggle button at the bottom of the sidebar, glows red when active
- Mobile: floating red circle button fixed just above the bottom nav bar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Layout:
- Remove body min-width: 1280px
- Sidebar hidden on mobile (<768px); replaced by a fixed bottom navigation bar (BottomNav component) with icon + label for all 7 routes
- PageShell main gets className="app-main"; padding overridden to 0 on mobile (68px bottom clearance for bottom nav)
- All page root divs get className="page-body"; mobile override to 12px 14px padding
Dashboard:
- 4-col stat grid → 2×2 on mobile (dash-stat-grid)
- 3-col timing/targets/forecast → single column (dash-three-col)
- 2-col monthly/best-nights → single column (dash-two-col)
- Run order timeline: overflow-x: auto for narrow screens
Targets:
- Filter bar rows get className="filter-row": horizontal scroll on mobile (no-wrap + overflow-x: auto)
- Table gets className="targets-table": columns 3-7 (Size/Fill/Mosaic/Mag/Diff) and 10+ (Vis/Int/Goal/Compare) hidden on mobile, keeping only Type, Name, Filter, Alt
Calendar:
- Split view gets dash-two-col (stacks on mobile)
- Month grid: minWidth 308px + overflowX auto; cell minWidth 44px for touch targets
Compare modal: columns stack vertically on mobile (compare-body / compare-col classes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>