feat(frontend): Weather app — radar map, live conditions, archived forecasts & almanac (#184)#198
Merged
Conversation
Wires the weather channel (observations + on-demand forecast lookup) into MediaStreamProvider end-to-end, mirroring the flights channel's plumbing: ref-counted subscribe/unsubscribe, a reveal buffer for forward-windowed observations merged latest-per-station, and a generation-id-guarded forecast request/reply round-trip with an explicit null for "confirmed no product" vs undefined for "not yet requested/pending". mergeLatestPerStation compares start_date (not id) per station so a late/reordered window frame can't regress a station to older data. The subscribe/init/seek snapshot is treated as an outright per-station replacement rather than going through that same-or-newer merge rule -- found via TDD that a backward seek's snapshot is often *older* than what's already held, which the keep-newest rule would otherwise reject, leaving stale data on screen after a rewind. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01W179bqEPFP92a3JrsPkD5c
…ure pins stations.json (188 entries, generated from weather-recon's stations.csv), weatherUnits (cToF/ktToMph/kmToMiles/degToCompass/hpaToInHg), and weatherRadar (stampForUtcMs/frameUrlFor against the Wasabi radar index manifest) back a new WeatherMap component copied from FlightTracker's FlightMap blueprint (protocol registration, init/teardown, resize, live re-theme) with flight-specific sources/animation stripped and a radar image overlay + temp-colored station pins added instead. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01W179bqEPFP92a3JrsPkD5c
…comment Co-Authored-By: Claude Fable 5 <[email protected]>
Weather.tsx wires WeatherMap (Task 1) into a ClassicyApp shell: default station KJFK, subscribes to the weather channel for the app's mounted lifetime, requests a forecast once per selected station's nws_zone (skipped for the 36 null-zone CA/MX/KHSV stations), and shows normals/records for the current virtual MM-DD only inside the 09-09..09-12 almanac window. useAlmanac follows the useFlightTrack fetch-hook shape (AbortController + per-station cache, never throws) against the static Wasabi almanac manifest. tsconfig.json gains resolveJsonModule so Weather.tsx can statically import stations.json (Task 1 already documented this as the expected consumer). app.png is a minimal placeholder pixel-art icon so the build/registration gate passes now — Task 3 (icon, registration, polish) owns generating the final artwork and wiring <Weather> into app.tsx. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01W179bqEPFP92a3JrsPkD5c
…ment The subscribe-effect comment claimed FlightTracker has a "closed but mounted" gate — it doesn't (isRunning is map-membership set at boot by ClassicyAppLoad and never removed on close), so the comment now states the real architecture: unconditional subscribe-on-mount, like RadioScanner. New test pins the 0-is-falsy trap: temp_c 0 / wind 0kt @ 0° must render 32°F and "N 0 mph", never the absent-value dash. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01W179bqEPFP92a3JrsPkD5c
Wires <Weather /> into ClassicyDesktop (src/app.tsx) and replaces the Task 2 PIL placeholder with a proper 32x32 Mac OS 8-flavored icon: a sun peeking from behind a shaded, outlined puffy cloud. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01W179bqEPFP92a3JrsPkD5c
A TimeMachine seek past SEEK_THRESHOLD_MS left weatherForecastByZone holding the pre-seek product — a 9/11 forecast stayed on screen at a 9/9 clock. Unlike per-station observations (replaced by the post-seek weather snapshot), forecasts are request/reply only, so nothing ever invalidated them. The seek effect now clears weatherForecastByZone and re-fires the pending forecast request for the last-requested zone (no-op when none), mirroring the reconnect path in ws.onopen. The generation bump orphans any in-flight pre-seek reply via the existing id-echo guard. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01W179bqEPFP92a3JrsPkD5c
|
✅ Playwright E2E — 1 passed · 0 failed · 0 flaky · 0 skipped |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 4 of the Weather app (#184) — the app itself, and the final phase. A Mac OS 8-style Weather desktop app: North America map with the real NEXRAD radar mosaic advancing with the virtual clock, 188 temperature-colored station pins, and a side panel with the selected city's current conditions, the actual archived NWS forecast text, and the 2001 almanac.
What's in here
weatherchannel in MediaStreamProvider — flights-shaped: ref-counted subscribe, reveal-buffered delivery, latest-per-station merge (keep max-start_date; late stale windows can't regress a station), id-echoed on-demand forecast requests, reconnect + seek re-request (the seek path was added after browser verification caught a real anachronism: a 9/11 forecast staying on screen at a 9/9 clock — now cleared and re-requested on every threshold seek, with stale-id replies dropped).WeatherMap— FlightTracker's PMTiles basemap stack reused; radar as a MapLibreimagesource (bounds from the liveindex.json), frame-swapped only on 5-minute stamp changes, inserted below borders; pins colored by °F with selection ring.Verification
Deferred / follow-ups
Radar trailing-hour loop control (design doc's phase-5 polish item); almanac day keyed to UTC date (evening hours show next day's block — derive in display tz); hide radar layer when seeking outside frame coverage; forecasts issued mid-playback appear on reselect/seek only (deliberate v1 cut). Also noted for a future pass: the
isRunninggate used by older apps is map-membership set at boot (never reverts) — all channels effectively subscribe at boot; gating on real window-open state would be a cross-app optimization.Closes #184's app milestone: with PRs #191/#193/#194/#195/#196/#197, the Weather feature is complete end-to-end — curated stations → real archived data → streamer channel → desktop app.
🤖 Generated with Claude Code
https://claude.ai/code/session_01W179bqEPFP92a3JrsPkD5c