Add target comparison modal, integration goal progress, and session planning + full catalog expansion

Features added this session:
- Target comparison: side-by-side overlay (CompareModal) from Targets page via ⊕ button on each row; shows altitude curves, key times, filter recommendations and per-filter integration progress for two targets simultaneously
- Integration goal progress dashboard card: per-target keeper minutes vs goal hours (from CLAUDE.md §16.3) broken down by filter, with color-coded progress bars; powered by new stats.integration_goals backend query
- Session planning timeline: Gantt-style "Plan Tonight" section on Dashboard (PlanningTimeline component) — search targets, set durations, sequential scheduling from dusk, overrun warnings, clipboard export
- Slew-optimized run order toggle (nearest-neighbor sort by RA/Dec angular distance)
- Best Nights 14-day card + Monthly Highlights card on Dashboard

Catalog expansions:
- Sharpless (Sh2), VdB, LDN, Barnard dark nebulae, LBN, Melotte, Collinder, Gum, RCW, Abell PN, Abell GC, PGC bright subset
- Caldwell/Arp/Melotte/Collinder number columns + cross-reference maps
- Weather score multiplier applied to composite sort
- galaxy_cluster type (ACO badge) throughout TypeBadge, CSS, filter chips

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 07:20:10 +02:00
parent 8f72745bc0
commit 2bb80a8475
45 changed files with 5613 additions and 628 deletions
@@ -1,3 +1,5 @@
import { useEffect, useRef } from 'react';
interface Props {
level?: 'warning' | 'critical';
temp?: number;
@@ -5,6 +7,31 @@ interface Props {
}
export default function DewAlert({ level, temp, dewPoint }: Props) {
const notifiedRef = useRef<string | null>(null);
// Trigger browser notification when dew alert level appears or escalates
useEffect(() => {
if (!level) return;
const key = level;
if (notifiedRef.current === key) return;
notifiedRef.current = key;
if (!('Notification' in window)) return;
const send = () => {
const margin = temp != null && dewPoint != null ? `Margin: ${(temp - dewPoint).toFixed(1)}°C. ` : '';
const body = level === 'critical'
? `${margin}Condensation imminent — protect optics immediately.`
: `${margin}Enable dew heaters now.`;
new Notification('Astronome — Dew Alert', { body, tag: 'dew-alert' });
};
if (Notification.permission === 'granted') {
send();
} else if (Notification.permission !== 'denied') {
void Notification.requestPermission().then(p => { if (p === 'granted') send(); });
}
}, [level, temp, dewPoint]);
if (!level) return null;
const margin = temp != null && dewPoint != null ? (temp - dewPoint).toFixed(1) : null;