Commit Graph
73 Commits
Author SHA1 Message Date
Nate EmmertandClaude Sonnet 4.6 e4a5a9c4bb Style nav links as buttons across all three pages
Replace plain text/opacity links with bordered pill buttons that fill
with accent color on hover — consistent across main app, conditions,
and dashboard headers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 15:47:01 -04:00
Nate EmmertandClaude Sonnet 4.6 f67e38857e Improve navigation between main app, conditions, and dashboard
- Main app: split tagline into attribution + Station/Dashboard nav pills (always visible, styled with border/hover)
- Conditions page: add Dashboard → link and ← App back-link in header
- Dashboard: add Conditions link, rename App → ← Weather in header nav

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 15:43:26 -04:00
Nate EmmertandClaude Sonnet 4.6 2986301599 Fix wind direction rose invisible on iOS (Retina DPR bug)
drawWindRose used canvas.width (physical pixels) for coordinates while
the context was already pre-scaled by devicePixelRatio. On iPhones
(dpr=2/3) this placed the entire rose off-screen. Fix by dividing by
dpr to get CSS dimensions before computing cx/cy/R.

Also make sWindDir stat always show a value ('—' fallback when winddir
is null) and add null guards on sEventRainTile/sTotalRainTile to
prevent a silent throw from aborting updateLatest() early.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 15:38:16 -04:00
Nate EmmertandClaude Sonnet 4.6 0d00c7f6ab Restore indoor sensor display and restructure dashboard layout
- Restore tempinf/humidityin tiles from PWS data on index.html (removed
  with thermostat cleanup but still available from Ambient station)
- Dashboard: replace two flat secondary-stat grids with labeled section
  cards — Wind, Precipitation, Climate, Indoor — for clearer visual
  hierarchy and category separation
- Indoor section card includes Ecobee portal link; appears only when
  station reports indoor sensor data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 10:07:17 -04:00
Nate EmmertandClaude Sonnet 4.6 e133781760 Remove thermostat/indoor sensor integration
Ecobee API is closed to new developer registrations and iOS sub-daily
automations are no longer available, making the HomeKit push approach
unworkable. Removes all related code:

- server.js: HomeKit push endpoints, token management, data files
- dashboard.html: thermostat section card, indoor stat tiles, indoor
  charts, HomeKit settings panel, HVAC badge CSS, all related JS
- index.html: Indoor Temp stat card
- app.js: indoor sensor display logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 09:19:21 -04:00
nmemmertandClaude 5eae872773 Accept GET query params on HomeKit push endpoint (#6)
* Add Ecobee thermostat integration to dashboard

- Server: PIN-based OAuth flow to authorize Ecobee access without
  exposing redirect URIs; tokens stored server-side and auto-refreshed
- Server: /api/ecobee/config, /api/ecobee/auth/pin,
  /api/ecobee/auth/complete, /api/ecobee/data, /api/ecobee/runtime
  endpoints behind dashboard auth
- Dashboard: Ecobee section card showing indoor temp, humidity,
  heat/cool setpoints, HVAC mode, equipment status badge, and
  per-sensor temperature readings from remote sensors
- Dashboard: stacked bar chart of daily heating/cooling runtime hours
  for the past 7 days
- Dashboard: Ecobee setup panel with API key entry and step-by-step
  PIN authorization flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMhuFaecxraQzUPN4h9nsB

* Replace ecobee API integration with HomeKit push endpoint

Ecobee removed public developer API access, so pivot to HomeKit:
- Add POST /api/homekit/push accepting thermostat data from iOS Shortcuts
- Add GET /api/homekit/data returning latest reading to dashboard
- Add GET /api/homekit/config and POST /api/homekit/config/regenerate for token management
- Auto-generate auth token on first run, persist to data/homekit-config.json
- Auto-convert Celsius→Fahrenheit if temp < 50 (HomeKit always returns Celsius)
- Update dashboard section card to show temp, setpoints, humidity, mode, HVAC state
- Replace ecobee settings panel with push URL, token copy, and Shortcut setup instructions
- Remove runtime chart (not available without API access)

* Support GET query params on homekit push endpoint for iOS Shortcuts compatibility

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-23 12:40:52 -04:00
nmemmertandClaude e8bb117568 Add thermostat integration via HomeKit push (iOS Shortcuts) (#5)
* Add Ecobee thermostat integration to dashboard

- Server: PIN-based OAuth flow to authorize Ecobee access without
  exposing redirect URIs; tokens stored server-side and auto-refreshed
- Server: /api/ecobee/config, /api/ecobee/auth/pin,
  /api/ecobee/auth/complete, /api/ecobee/data, /api/ecobee/runtime
  endpoints behind dashboard auth
- Dashboard: Ecobee section card showing indoor temp, humidity,
  heat/cool setpoints, HVAC mode, equipment status badge, and
  per-sensor temperature readings from remote sensors
- Dashboard: stacked bar chart of daily heating/cooling runtime hours
  for the past 7 days
- Dashboard: Ecobee setup panel with API key entry and step-by-step
  PIN authorization flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMhuFaecxraQzUPN4h9nsB

* Replace ecobee API integration with HomeKit push endpoint

Ecobee removed public developer API access, so pivot to HomeKit:
- Add POST /api/homekit/push accepting thermostat data from iOS Shortcuts
- Add GET /api/homekit/data returning latest reading to dashboard
- Add GET /api/homekit/config and POST /api/homekit/config/regenerate for token management
- Auto-generate auth token on first run, persist to data/homekit-config.json
- Auto-convert Celsius→Fahrenheit if temp < 50 (HomeKit always returns Celsius)
- Update dashboard section card to show temp, setpoints, humidity, mode, HVAC state
- Replace ecobee settings panel with push URL, token copy, and Shortcut setup instructions
- Remove runtime chart (not available without API access)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-23 08:24:14 -04:00
Nate EmmertandClaude Sonnet 4.6 151e3c9202 Fix wind direction display wrapping on iPad
Move wind direction to its own line below "mph" so it doesn't
wrap or get clipped on narrow stat tiles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-20 10:48:09 -04:00
Nate EmmertandClaude Sonnet 4.6 ec1e9d3359 Dashboard visual redesign: hero, section cards, sparklines, color tiles, today bar
- Hero section with 5.5rem temperature, feels-like, live wind description
- Humidity, pressure, solar radiation, wind sparklines drawn from history data
- Today at a glance bar: today's high/low temp, rain total, peak gust
- Tile color tinting by value: temp (hot/warm/cool/cold), UV severity, wind, rain rate
- Wind cardinal direction shown in hero subtitle and secondary wind tile
- Chart lines now have subtle gradient area fill (non-dashed series only)
- Chart height increased from 200px to 240px; cards get hover shadow
- Section cards with light headers group conditions, totals, and history

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-20 08:57:29 -04:00
Nate EmmertandClaude Sonnet 4.6 c5cdd39eb3 Add maxdailygust, indoor feelsLike/dewPoint, and Ambient rain accumulators
- Max Gust Today tile (maxdailygust from Ambient)
- Feels Like In tile (feelsLikein, Ambient-computed)
- Use Ambient's dewPointin directly instead of computing from tempinf/humidityin
- Event Rain tile (eventrainin, shows only when > 0)
- Total Rain lifetime tile (totalrainin)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-20 08:50:01 -04:00
Nate EmmertandClaude Sonnet 4.6 a535721bf8 Add NWS alerts, dry/wet streak, and indoor sensor support to dashboard
- NWS active alerts shown as color-coded cards at top of dashboard (5-min cache), auto-refresh every 5 min
- Station location (lat/lon) configurable in settings; falls back to push subscription coordinates
- Consecutive dry/wet streak tile in extended stats (60-day lookback, 0.01" rain threshold)
- Indoor sensor tiles (Temp In, Humidity In, Dew Point In) auto-show when station reports tempinf/humidityin
- Indoor temperature and humidity history charts when indoor data is present in selected range

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-20 08:42:33 -04:00
Nate EmmertandClaude Sonnet 4.6 e851fd1ca0 Add station alerts, dashboard analytics, and settings improvements
Alerts (server):
- Station temp high/low threshold alerts via push + ntfy
- Freeze warning at ≤34°F (station reading)
- Station wind gust threshold alert from real readings
- Station offline alert when no data for >20 min (auto-clears on reconnect)

Dashboard:
- Trend arrows (▲/▼/–) on temp, humidity, dew, wind, pressure, UV, solar tiles
  comparing current vs 1h ago via new /api/station/trend endpoint
- Offline banner when station last reported >15 min ago
- Monthly summary table (avg temp, high temp, max gust, total rain, readings)
- Temperature calendar heatmap — GitHub-style year grid, blue→orange scale
- Alert threshold settings UI: temp high/low, wind gust, rain rate
  saved to all push subscriptions via new /api/subscriptions/thresholds
- Station name setting — persisted in auth file, served on conditions page
- Change password form in settings (no recovery flow needed)

Server:
- GET/POST /api/dashboard/station-name
- POST /api/dashboard/change-password
- GET /api/station/monthly-summary
- GET /api/station/trend
- GET /api/subscriptions, POST /api/subscriptions/thresholds
- /api/conditions/public includes stationName field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 15:14:35 -04:00
Nate EmmertandClaude Sonnet 4.6 beb627830f Add dashboard password recovery via daily server-log token
- getDailyRecoveryToken(): HMAC of SESSION_SECRET + current day — changes
  every 24h, requires no extra storage
- Logs token to console on startup (visible in CasaOS container logs)
- GET/POST /dashboard/recover: validates token, sets new password
- dashboard-recover.html: enter token + new password, with instructions
- dashboard-login.html: 'Forgot password?' link + success message after recovery

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 15:05:44 -04:00
Nate EmmertandClaude Sonnet 4.6 e10bfaa57f Add explicit back-to-app link on conditions page header
Replaces implicit station-name link with a visible '← App' nav link
in the accent color, keeping station name as non-interactive label.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 14:57:36 -04:00
Nate EmmertandClaude Sonnet 4.6 1b206df92c Add Station and Dashboard links from main app
Tagline in header links to /conditions and /dashboard on desktop.
Settings tab adds a Station section with both links for mobile access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 14:55:46 -04:00
Nate EmmertandClaude Sonnet 4.6 9ec31bfeb6 Add link back to main app in dashboard header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 14:53:50 -04:00
Nate EmmertandClaude Sonnet 4.6 489f48e1ba Link station name back to main app on conditions page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 14:53:27 -04:00
Nate EmmertandClaude Sonnet 4.6 a4b5f08cd2 Set station name to Genesis 8:22 on public conditions page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 14:43:58 -04:00
Nate EmmertandClaude Sonnet 4.6 39d908b0fa Redesign public conditions page with radar-sweep hero and instrument layout
Full-bleed hero with animated radar sweep ring, dot-grid background, and a
giant temperature number that shifts color by value (ice blue → amber → red).
Canvas compass with N/S/E/W ticks and wind arrow in the Wind section.
Atmosphere, Wind, and Precipitation+Solar sections with Barlow Condensed data
tiles. Full light/dark theme support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 14:41:55 -04:00
Nate EmmertandClaude Sonnet 4.6 7665146b44 Add dashboard analytics, public conditions page, and station data features
- Dashboard: account-protected station data recorder with PBKDF2 auth and
  HMAC-signed sessions; Chart.js time-series charts with 1h–3y range selectors;
  wind rose canvas; auto-refresh every 2 min; 1-min polling cron
- Analytics endpoints: all-time records, rain totals (7d/30d/YTD), heating/cooling
  degree days, CSV export, year-over-year overlay, hourly anomaly detection
- Dashboard UI: pressure trend arrow, rain tile row, HDD/CDD tiles, anomaly tile,
  YoY toggle, collapsible records panel, CSV export, backfill progress bar
- Public conditions page at /conditions (no auth) with auto-refresh every 2 min
- Rain rate push alert via web-push and ntfy when threshold exceeded
- Station data stored as per-day JSONL files; backfill imports up to 1yr from Ambient

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 14:26:51 -04:00
Nate EmmertandClaude Sonnet 4.6 46e927370a Add Ambient historical data backfill to dashboard
- POST /api/station/backfill: pages backward through Ambient's historical API
  at 1 req/sec, deduplicates against existing JSONL records, writes to daily files
- GET /api/station/backfill/status: polls live progress (days done, readings inserted)
- Dashboard settings panel: day-range selector (7d/30d/90d/180d/1y), Import button,
  progress bar with live count; auto-reloads charts when complete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 14:02:27 -04:00
Nate EmmertandClaude Sonnet 4.6 2501842083 Auto-refresh charts every 2 min; show next refresh time in status line
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 13:52:21 -04:00
Nate EmmertandClaude Sonnet 4.6 7ba246a628 Fix stale poll interval message (10 min → 1 min)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 13:43:12 -04:00
Nate EmmertandClaude Sonnet 4.6 4aca8df808 Add API key settings panel to dashboard
- Station API Keys section at bottom of dashboard (auto-opens if unconfigured)
- Shows configured/missing status on load
- Fields for Ambient API Key and Application Key; saves via existing endpoint
- POST /api/ambient-config now requires dashboard auth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 13:39:27 -04:00
Nate EmmertandClaude Sonnet 4.6 fa4895a741 Add station dashboard with auth and historical data recording
- Protected /dashboard route with PBKDF2 password + HMAC-signed cookies
- First-run setup flow at /dashboard/setup
- Records Ambient station readings to daily JSONL files in app/data/station/
- Deduplicates on dateutc so 1-min polling never writes duplicate rows
- Dashboard charts: temp, humidity/dew, wind+gusts, wind rose, pressure,
  rain rate, rain accumulation, UV, solar radiation
- History range selector: 1h / 6h / 24h / 7d / 30d / 90d / 1y / 2y / 3y
- Auto-downsamples to 720 points for long ranges
- Cron interval changed from 10 min to 1 min (with dedup guard)
- app/data/ added to .gitignore (contains keys and session secret)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 13:32:18 -04:00
Nate EmmertandClaude Sonnet 4.6 fdc374b8bd Use Ambient station readings for wind/temp threshold alerts in cron
When station keys are configured and the last reading is < 15 min old,
the alert cron uses windgustmph and tempf from the station instead of
Open-Meteo model values. AQI still comes from Open-Meteo (no station
equivalent). Alert bodies now include "(station)" or "(forecast model)"
so the source is visible in the notification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 12:19:29 -04:00
Nate EmmertandClaude Sonnet 4.6 9620c1b0cd Add Ambient station primary source toggle; swap location name to station
When configured, a new "Use as primary source" toggle appears in Settings
under Ambient Weather Station (defaults on). When on, the location header
shows the station name and "Ambient Weather Station" subtitle instead of the
city; the hero badge is hidden as redundant. Toggling off restores the city
name immediately and re-shows the hero badge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-19 12:13:00 -04:00
nmemmert 2d2e36d9fa Merge pull request #4 from nmemmert/claude/ambient-station-overlay-m8dh80
Use station data for current conditions; persist keys server-side
2026-08-19 11:56:20 -04:00
Claude 0005698264 Use station data for current conditions; persist keys server-side
Station overlay:
- When Ambient Weather station data is present and fresh (<15 min),
  its readings replace the model's current conditions: temp, feels-like,
  humidity, dewpoint, wind speed/direction, gusts, pressure (inHg→hPa),
  UV index, rain today, wind peek summary, and data-panel readouts
- A small "📡 My Station" badge appears in the hero when active
- applyStationOverlay() is called at the end of renderWeather (instant
  if station data already loaded) and again whenever station data
  refreshes (so the display stays live every 60 s)
- Hourly/daily forecasts continue to use Open-Meteo

Server-side key persistence:
- POST /api/ambient-config saves keys to data/ambient-config.json
  (same persistent volume as VAPID and subscriptions)
- GET /api/ambient-config returns {configured: bool} without exposing keys
- /api/ambient checks query params → saved config → env vars in order
- Settings Save button posts keys to server after a successful test;
  confirms "saved for all devices" in the status line
- On page load, if no localStorage keys but server is configured,
  the station loads automatically on any device
2026-08-19 15:53:10 +00:00
nmemmertandClaude b9e2b463f5 Add Ambient Weather personal station integration
* Add Ambient Weather personal station integration

Fetches live data from the user's Ambient Weather station via their
REST API and displays it as a "My Station" card in the conditions tab.

- server.js: add /api/ambient endpoint with 60s cache using
  AMBIENT_API_KEY and AMBIENT_APP_KEY env vars
- docker-compose.yml: expose AMBIENT_API_KEY/AMBIENT_APP_KEY env vars
- index.html: add collapsible "My Station" drawer to conditions tab
  showing temp, humidity, wind, gusts, daily rain, pressure, UV,
  solar radiation, and indoor readings
- app.js: add fetchAmbientStation() and renderAmbientStation(),
  auto-refresh every 60s, peek text summary in drawer header
- style.css: station drawer styles
- .gitignore: add .env so API keys stay off git

Co-Authored-By: Claude <noreply@anthropic.com>

* Move Ambient Weather keys to Settings panel

Users can now enter their API Key and Application Key directly in the
Settings tab under "Ambient Weather Station". Keys are saved to
localStorage and sent per-request to the server proxy.

- index.html: add API Key + Application Key inputs with Save button
- app.js: persist keys in localStorage, populate inputs on load,
  wire Save button (validates by testing the connection), pass keys
  as query params in fetchAmbientStation and its auto-refresh loop
- server.js: accept apiKey/appKey from query params with env var
  fallback; cache keyed per-user so multiple callers don't collide

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-19 11:35:16 -04:00
Nate EmmertandClaude Sonnet 4.6 b9e6cad190 Add CSS-based fullscreen for Map tab; fix state desync bug
- Add fullscreen toggle button (⛶/✕) to Map tab controls row
- CSS class `map-fs` hides header/nav and expands map to fill screen
- Drop unreliable requestFullscreen() API (blocked on iOS/PWA) in favor of pure CSS approach
- Fix button state desync: syncBtn() runs on init so stale ✕ state can't persist across reloads
- Shrink embedded radar height on mobile (220px) and tune dvh calculations for safe-area insets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-17 09:44:33 -04:00
nmemmert 48ae797520 Merge pull request #2 from nmemmert/claude/pwa-alerts-radar-swipe-h9xik2 2026-08-16 21:25:03 -04:00
Claude 4d92739df4 Fix alert dedup across reloads, add radar fullscreen, fix swipe conflicts
- Persist alertIdsSeen to localStorage so already-seen alerts don't
  re-notify every time the PWA is opened; cap stored IDs at 200 entries
- Use serviceWorker.showNotification() for alert notifications so they
  work when the app is backgrounded, with fallback to new Notification()
- Add ⛶ fullscreen button to radar embed; uses Fullscreen API and
  resizes the MapLibre canvas after toggle
- Fix swipe-to-change-tab: block gesture when touch starts on a map,
  hourly scroll row, or radar timeline; also require the gesture to be
  more horizontal than vertical (dx > dy*1.5) to avoid conflict with
  vertical scrolling
2026-08-17 01:10:09 +00:00
Nate EmmertandClaude Sonnet 4.6 a25864d9ed Upgrade maps to MapLibre GL JS; compact header; move search to settings
- Replace Leaflet with MapLibre GL JS v4.7.1 across all map instances
- CARTO dark/light and Esri satellite raster basemaps (no API key)
- All overlays (hurricane, wildfire, SPC, wind arrows) ported to GeoJSON sources/layers
- AQI, alert, temp, sun/moon markers via maplibregl.Marker with custom HTML
- Radar animation pre-loads all frames on load for smoother playback
- Compact loc-header: tighter padding and font sizes, all info preserved
- Search bar moved from header to Settings tab under Location section
- Service worker bumped to weather-v7

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-14 11:04:32 -04:00
Nate Emmert 1863f6784b Fix XSS, validation, and CSS regressions; refactor proxy retry logic
- Sanitize geocode suggestion rendering to prevent XSS
- Add lat/lon validation and cache eviction to weather proxy endpoints
- Extract shared fetchWithRetry helper, dedupe retry/timeout logic
- Stop leaking upstream error details to clients
- Fix radar provider metadata and drop dead nowcast field
- Fix missing --surface-radius-sm var and duplicate/conflicting CSS rules
2026-06-15 08:51:18 -04:00
Nate Emmert 49b54e0fe4 Merge branch 'fix/network-main-parity' into main (resolve conflicts) 2026-03-17 14:21:49 -04:00
Nate Emmert 950e2708ab Enhance mobile UX: learn guide, alerts actions, and shareable weather cards 2026-03-17 14:21:32 -04:00
Nate Emmert 8f9a2d1c02 Unify map tab with feature toggles and shared radar overlay 2026-03-17 12:30:18 -04:00
Nate Emmert 98c45bb26f WIP: Begin unified map refactor - HTML structure, storage, CSS, state updates 2026-03-17 12:25:40 -04:00
Nate Emmert f4505b5650 Increase maps height to match radar and improve wind arrow visibility 2026-03-17 12:04:15 -04:00
Nate Emmert 6c7e2f7235 Increase RainViewer max zoom to 17 for better detail 2026-03-17 12:01:59 -04:00
Nate Emmert 14995987ae Stabilize SW and cache headers to match backup behavior 2026-03-17 11:36:12 -04:00
Nate 0d6d3985cc App title black text with white halo shadow 2026-03-16 15:29:09 -04:00
Nate 36605d8c90 Always-visible app title: white text + drop shadow on masthead h1/tagline 2026-03-16 15:24:46 -04:00
Nate ea20d31217 Fix persistent caching: no-store/no-cache headers, SW v4, versioned asset URLs 2026-03-16 15:19:54 -04:00
Nate 86914a7329 Rename app to NeCloud Weather and add iPhone/PWA PNG icons 2026-03-16 15:08:29 -04:00
Nate 37b751729e Add dynamic weather-based background themes with day/night variants 2026-03-16 15:05:38 -04:00
Nate 359e76bccb Add maps fullscreen and enforce single-line search controls 2026-03-16 15:03:27 -04:00
Nate 5003d3ec09 Fix stale UI cache with network-first app shell and forced SW updates 2026-03-16 14:57:59 -04:00
Nate defdfe2200 Refine mobile search bar to single-line compact controls 2026-03-16 14:49:18 -04:00