Clicking a nearby target opens it inside the same drawer: the drawer
content switches to the new target (fetched via useTarget) and all
tabs/hooks update automatically. A '← [name]' back button appears in
the tab bar to return to the previous target. Navigation is stackable
(can chain through multiple nearby targets). Tab and filter selection
reset on each navigation step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>