13 Commits

Author SHA1 Message Date
882fd69a18 Add official NWS advisories as notifications, deduping forecast alerts
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 25s
Test, Build & Deploy / deploy (push) Successful in 20s
- nws.js: new fetchActiveAdvisories() hits the official NWS alerts feed
  (api.weather.gov /alerts/active?point=lat,lon) and normalizes each advisory
  into the app's alert shape, tagged source:'nws'. Adds classifyAdvisoryEvent
  (maps NWS events -> app buckets: heat/wind/cold/storm/precip, else advisory),
  nwsSeverity (Extreme/Severe->danger etc.), advisoryId (stable short id from
  the URN), advisoryIcon, and local effectiveDate/expiresDate for dedup.

- notifications store: cached _nwsAdvisories + refreshAdvisories() (best
  effort, cached 5 min, non-US/no-alerts safe). analyze() now merges
  advisories via new pure mergeAdvisories(): an advisory that is officially
  in force on a date supersedes the derived forecast alert of the same type
  on that date, so the official NWS warning replaces the forecast guess. Both
  are dismissible independently (nws-* vs forecast-* ids).

- New per-type toggle "advisory" (defaults on) across app/default settings and
  SettingsDialog, so official advisories are configurable like every other
  alert and the bell shows them with their own severity/icon.

- Wired: App.svelte calls refreshAdvisories() alongside analyze() on forecast
  load and changes.

- Tests: 12 advisory tests (classifier, severity, id, normalize, dedup incl.
  disable toggle) + 3 store integration tests (overlap dedup, non-overlap kept,
  toggle-off). 107 total pass.

- Verified end-to-end in headless Chromium against the live NWS feed for
  Rogers County, OK: the current Heat Advisory appears in the bell, and the
  derived heat alerts for the days it covers are suppressed while a
  non-overlapping day's forecast heat alert remains.
2026-09-08 02:33:37 +00:00
97b676093a Add moon phase card to Weather Details
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 23s
Test, Build & Deploy / deploy (push) Successful in 21s
- New src/lib/api/moon.js: fetches daily moon_phase from Open-Meteo
  (independent of the NWS/Open-Meteo weather source, since NWS has no moon
  data), plus pure helpers: moonName (8 phases), illumination fraction, and
  nextMoonPhase, which unwraps the ~29.5-day cycle and interpolates the next
  quarter boundary (new/first quarter/full/last quarter), returning its name,
  date, and days until it.
- New MoonPhaseCard.svelte: renders an SVG moon (two-circle terminator that
  correctly shows crescent/quarter/gibbous shapes), the current phase name,
  percent illuminated, and the next phase with date. Caches per-location via
  an effect; degrades gracefully when unavailable.
- Wired into WeatherDetail between Sun and UV and Temperature Range.
- 11 unit tests for moonName/illumination/interpolation/nextMoonPhase.
- Verified in headless Chromium: card renders, moon shape correct, no errors.
2026-09-08 02:25:01 +00:00
2eb34a709b Fix tab/view navigation: reactivity loop + Daily forecast TDZ
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 29s
Test, Build & Deploy / deploy (push) Successful in 22s
- notifications.svelte.js: analyze() iterated the reactive $state field
  `alerts` right after writing it, inside an $effect. That read-after-write
  made Svelte treat the effect as depending on `alerts`, so writing it
  re-ran the effect forever -> effect_update_depth_exceeded, which killed the
  click handler after setting the hash but before swapping the view. Iterate
  the local newAlerts array instead.

- DailyForecast.svelte: use `precip` {@const} before its declaration caused
  a 'Cannot access precip before initialization' TDZ error when the 7-day tab
  rendered. Moved the declaration above its first use.

Both verified in a real headless Chromium: tabs now switch correctly (hash +
active state + content), no console/page errors. 81/81 tests pass.
2026-09-08 02:08:21 +00:00
897f49ae22 Add installable PWA (manifest, service worker, icons) + per-type notification toggles
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 1m1s
Test, Build & Deploy / deploy (push) Successful in 22s
- manifest.webmanifest + apple/maskable PNG icons; relative paths so the
  app works both at dev root and under /weather
- plain-JS service worker (sw.js) with notificationclick/push handlers and
  an SW-mediated showNotification() path for native Android notifications
- src/lib/pwa.js bootstraps registration and routes alerts to the SW
- Every alert kind (storm/precip/heat/cold/uv/wind) is now a per-type
  preference toggle in Settings; removed the old alertThresholds.thunderstorm
  boolean in favor of notificationTypes.storm
- Store dedups native notifications per forecast day and fires best-effort
  native notifications for newly-appeared alerts
- inline-assets keeps manifest/sw/icons in dist; deploy.yml uploads + verifies
  all PWA files via WebDAV
2026-09-08 00:59:19 +00:00
5bb35b278f Remove swipe destination peek labels
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 39s
Test, Build & Deploy / deploy (push) Successful in 22s
Drop the 'Hourly'/'Daily'/etc. peek chip shown at the edge while swiping.
The live finger-tracking slide remains; only the label affordance is
removed.
2026-08-30 19:26:19 +00:00
f5488eaa98 Live swipe feedback: content tracks finger with destination peek
All checks were successful
Test, Build & Deploy / test-and-build (push) Successful in 41s
Test, Build & Deploy / deploy (push) Successful in 25s
Replace the instant-jump swipe with a live drag on touch: the current tab
content translates with the finger in real time (translateX driven by
touchmove), a peek chip at the edge shows the destination tab, and on
release the sheet animates to the next tab or snaps back. Horizontal vs
vertical travel is decided after 12px so normal page scrolling is
untouched; touch-action:pan-y keeps horizontal gestures JS-owned. Radar
still pans its map instead of switching tabs.
2026-08-30 19:22:35 +00:00
38f0c04b3e Bake commit hash into build; log it via console.info on startup
Some checks failed
Test, Build & Deploy / test-and-build (push) Successful in 38s
Test, Build & Deploy / deploy (push) Failing after 20s
Build injects __WEATHER_COMMIT__ (VITE_COMMIT_HASH from CI's github.sha on
branch builds, git rev-parse HEAD locally) and main.js logs
console.info({ commit_hash }) so a deploy can be verified to match the
commit that produced it.
2026-08-30 19:01:13 +00:00
cc6b573748 Add swipe-to-switch between tabs on mobile
Touch swipe on the content area moves between tabs (left = next, right =
previous), gated to non-radar views so map pan gestures keep working.
Vertical scroll with |dx|<|dy| is untouched; 60px horizontal threshold.
2026-08-30 14:05:08 +00:00
36d3625d61 Make tabs hash-routable
- New src/lib/router.js: parseHash/ viewToHash for the 5 tab views, with a
  default fallback for empty/unknown hashes.
- App.svelte: initialize viewMode from location.hash, setView() updates both
  state and the URL hash, and a guarded hashchange listener syncs back/forward
  and manual URL edits. All tab onclicks route through setView().
- Tests for parse/viewToHash round-trips and fallback. 76 total pass.
2026-08-27 00:41:05 +00:00
7e06c17bfa Use light-drizzle icon for WMO 61 (light rain) and 80 (light showers)
Both codes now render 🌦️ instead of 🌧️ in the CODE_MAP. Updated the
weather-codes tests accordingly.
2026-08-27 00:35:29 +00:00
ff73e58d31 Add toggleable lightning strike layer to the radar map
- Lightning layer on PrecipitationRadar: toggleable  button (persisted via
  showLightning setting). When on, fetches recent strikes from the planned CORS
  relay and renders them as markers that pan/zoom with the slippy map.
- New src/lib/api/lightning.js: fetchLightningStrikes() reads
  https://cors.thecookiejar.me/lightning?lat&lon&radiusKm&limit (relay NOT
  deployed yet) with an 8s abort timeout and graceful failure - never blocks or
  crashes the map, shows an unavailable banner instead. Exports pure
  projectStrikeToLayer() for Mercator tile-layer marker projection.
- showLightning setting added to DEFAULT_SETTINGS + app store; toggle persists.
- Tests: fetch URL/parsing/error handling + layer projection. 71 total pass.
2026-08-27 00:30:25 +00:00
3635937085 Remove location label badge from radar pin 2026-08-24 15:46:36 +00:00
0e2a15dd28 Show today's daily high/low on the Now tab 2026-08-23 13:19:03 +00:00