Merge /conditions into the app as a Station tab

- New Station tab (top tabs, bottom nav, swipe order) carrying the old /conditions look:
  radar-ring hero, condition pill, wind compass, atmosphere/precip/solar cards, indoor
- Scoped sl-* styles and station-live.js; units follow the app's metric/imperial setting
- Empty/error states when no station is connected
- /conditions now 301-redirects to /?tab=station; removed conditions.html,
  /api/conditions/public and the old My Station drawer
- /api/ambient-config also returns the station name
- Dashboard and settings links updated; SW cache v8; manifest shortcut for Station
- Drops an indoor-temp block in applyStationOverlay that referenced an out-of-scope helper

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Nate EmmertandClaude Sonnet 5.5 committed 2026-10-15 12:45:47 -04:00
1 parent 7dea2734ea
commit 1830fb2e2b
11 files changed
+361 -917

No files matched your search

+10 -89
View File
@@ -2658,7 +2658,7 @@ syncOfflineState();
// ── Swipe gestures for tab switching ──────────────────────────────────────────
(function initSwipe() {
const TABS = ['conditions', 'unified-map', 'alerts', 'learn', 'settings'];
const TABS = ['conditions', 'station', 'unified-map', 'alerts', 'learn', 'settings'];
let touchX = 0;
let touchY = 0;
let blocked = false;
@@ -2813,7 +2813,9 @@ if (ambientSaveBtn) {
}
// On load, check if server already has keys configured (other device saved them)
fetch('/api/ambient-config').then(r => r.json()).then(({ configured }) => {
fetch('/api/ambient-config').then(r => r.json()).then(({ configured, stationName }) => {
if (stationName) state.stationName = stationName;
if (!configured && !state.ambientApiKey && typeof window.stationTabMessage === 'function') window.stationTabMessage('unconfigured');
if (configured && !state.ambientApiKey) {
if (ambientStatusEl) { ambientStatusEl.textContent = '✓ Keys configured on server'; show(ambientStatusEl); }
fetchAmbientStation();
@@ -2968,10 +2970,7 @@ if (pwsSaveBtn) {
let stationRefreshTimer = null;
async function fetchAmbientStation() {
const loadingEl = document.getElementById('station-loading');
const contentEl = document.getElementById('station-content');
const errorEl = document.getElementById('station-error');
const peekEl = document.getElementById('station-drawer-peek');
const tabMsg = (kind, detail) => { if (typeof window.stationTabMessage === 'function') window.stationTabMessage(kind, detail); };
const apiKey = state.ambientApiKey;
const appKey = state.ambientAppKey;
@@ -2984,18 +2983,14 @@ async function fetchAmbientStation() {
const r = await fetch(`/api/ambient${qs}`);
if (!r.ok) {
const { error } = await r.json().catch(() => ({}));
if (errorEl) { errorEl.textContent = error || 'Station unavailable'; show(errorEl); }
if (loadingEl) hide(loadingEl);
tabMsg(r.status === 503 ? 'unconfigured' : 'error', error || 'Station unavailable');
return;
}
const devices = await r.json();
if (!Array.isArray(devices) || !devices.length) {
if (errorEl) { errorEl.textContent = 'No devices found on this account.'; show(errorEl); }
if (loadingEl) hide(loadingEl);
tabMsg('error', 'No devices found on this account.');
return;
}
if (loadingEl) hide(loadingEl);
if (errorEl) hide(errorEl);
renderAmbientStation(devices[0]);
// Auto-refresh every 60 s
@@ -3014,8 +3009,7 @@ async function fetchAmbientStation() {
} catch {}
}, 60000);
} catch (e) {
if (errorEl) { errorEl.textContent = 'Could not reach station.'; show(errorEl); }
if (loadingEl) hide(loadingEl);
tabMsg('error', 'Could not reach your station.');
}
}
@@ -3075,14 +3069,6 @@ function applyStationOverlay(device) {
if (data.uv != null) setEl('cur-uv', data.uv);
const indoorCard = document.getElementById('st-indoor-card');
if (data.tempinf != null) {
setEl('st-tempin', fmtTemp(data.tempinf));
setEl('st-tempin-unit', isMetric ? 'C' : 'F');
setEl('st-humin', data.humidityin != null ? `${data.humidityin}% RH` : '');
if (indoorCard) indoorCard.classList.remove('hidden');
}
// Rain summary in hero sub-line
const rainEl = document.getElementById('cur-precip');
if (rainEl && data.dailyrainin != null) {
@@ -3113,73 +3099,8 @@ function applyStationOverlay(device) {
function renderAmbientStation(device) {
state.ambientDevice = device;
const data = device?.lastData || {};
const info = device?.info || {};
const contentEl = document.getElementById('station-content');
const peekEl = document.getElementById('station-drawer-peek');
const stName = info.name || device?.macAddress || 'My Station';
const nameEl = document.getElementById('station-name');
if (nameEl) nameEl.textContent = stName;
const updatedEl = document.getElementById('station-updated');
if (updatedEl && data.dateutc) {
const ago = Math.round((Date.now() - data.dateutc) / 60000);
updatedEl.textContent = ago <= 1 ? 'just now' : `${ago} min ago`;
}
const isMetric = state.units !== 'us';
function fmtTemp(f) {
if (f == null) return '--';
return isMetric ? Math.round((f - 32) * 5 / 9) : Math.round(f);
}
function fmtSpeed(mph) {
if (mph == null) return '--';
return isMetric ? Math.round(mph * 1.60934) : Math.round(mph);
}
function fmtRain(inches) {
if (inches == null) return '--';
return isMetric ? (inches * 25.4).toFixed(1) : inches.toFixed(2);
}
const tempUnit = isMetric ? 'C' : 'F';
const speedUnit = isMetric ? 'km/h' : 'mph';
const rainUnit = isMetric ? 'mm' : 'in';
const setEl = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
setEl('st-temp', fmtTemp(data.tempf));
setEl('st-temp-unit', tempUnit);
setEl('st-feels', data.feelsLike != null ? `Feels ${fmtTemp(data.feelsLike)}°` : '');
setEl('st-hum', data.humidity != null ? `${data.humidity}` : '--');
if (data.tempf != null && data.humidity != null) {
const tc = (data.tempf - 32) * 5 / 9;
const alpha = Math.log(data.humidity / 100) + (17.62 * tc) / (243.12 + tc);
const dewC = (243.12 * alpha) / (17.62 - alpha);
const dewDisplay = isMetric ? Math.round(dewC) : Math.round(dewC * 9 / 5 + 32);
setEl('st-dew', `Dew ${dewDisplay}°`);
}
setEl('st-wind', fmtSpeed(data.windspeedmph));
setEl('st-wind-unit', speedUnit);
setEl('st-wind-dir', data.winddir != null ? compassDirFull(data.winddir) : '');
setEl('st-gust', fmtSpeed(data.windgustmph));
setEl('st-gust-unit', speedUnit);
setEl('st-rain-day', fmtRain(data.dailyrainin));
setEl('st-rain-unit', rainUnit);
const rrDisplay = data.hourlyrainin != null ? `${fmtRain(data.hourlyrainin)} ${rainUnit}/hr` : '';
setEl('st-rain-rate', rrDisplay);
setEl('st-pressure', data.baromrelin != null ? data.baromrelin.toFixed(2) : '--');
setEl('st-uv', data.uv != null ? data.uv : '--');
setEl('st-solar', data.solarradiation != null ? Math.round(data.solarradiation) : '--');
if (peekEl && data.tempf != null) {
peekEl.textContent = `· ${fmtTemp(data.tempf)}°${tempUnit} ${data.humidity != null ? data.humidity + '% RH' : ''}`;
}
if (contentEl) show(contentEl);
// The Station tab (station-live.js) renders the readings
if (typeof window.renderStationTab === 'function') window.renderStationTab(device);
// Update the main conditions display with real station readings
if (state.currentWeather) applyStationOverlay(device);