- 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.
- 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.
- 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.
- 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
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.
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.
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.
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.
- 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.
- 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.