From 335e6e31a871fcb9101abc9cf1debfb1b9f5df5b Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Mon, 31 Aug 2026 11:56:43 +0000 Subject: [PATCH] Now panel: show day's high + its feels-like, and peak rain chance with time Was showing right-now's temp/feels-like/rain% under a headline that's already a whole-day summary -- mismatched framing (a day-level headline next to point-in-time numbers). - Big temp is now today's forecast high, with a small 'High' label. - Feels-like is paired with that same high-temp hour, not right now. - Rain% is the day's peak chance, shown as 'X% chance of rain around HH:00' instead of the current hour's (often much lower) number. summarizeToday() now returns highTemp/highFeelsLike/rainChance alongside the existing headline/note/umbrella, computed from the same already-fetched hourly points. Frontend falls back to currentPoint's own values only if summary is unexpectedly null. Co-Authored-By: Claude Sonnet 5 --- backend/src/lib/summary.js | 22 +++++++++++++++++++--- frontend/src/index.css | 9 +++++++++ frontend/src/pages/Display.tsx | 20 ++++++++++++++++---- frontend/src/types.ts | 3 +++ 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/backend/src/lib/summary.js b/backend/src/lib/summary.js index f541a44..0fb2a7c 100644 --- a/backend/src/lib/summary.js +++ b/backend/src/lib/summary.js @@ -31,8 +31,17 @@ export function summarizeToday(points, nowHour) { // replaces) — an hour's icon can be non-rain while it still carries a real, if // slim, numeric chance. Worth surfacing in the headline rather than a flat "Dry" // that silently disagrees with a "5%" sitting right there on an hour card. - const precipValues = points.map(p => p.precip_prob).filter(v => v != null) - const maxPrecip = precipValues.length ? Math.max(...precipValues) : null + // Keep the whole peak point, not just the number, so the "now" panel can show + // *when* that peak chance falls ("30% chance of rain around 15:00") rather + // than whatever the current hour's own (possibly much lower) number is. + const rainPeakPoint = points.reduce((best, p) => + p.precip_prob != null && (!best || p.precip_prob > best.precip_prob) ? p : best, null) + const maxPrecip = rainPeakPoint?.precip_prob ?? null + + // Day's high, and the feels-like *for that same hour* — pairing "feels like" + // with the temp it actually describes, rather than the current hour's feels + // like sitting next to a headline high from a different time of day. + const highPoint = points.reduce((max, p) => (p.temp_c > max.temp_c ? p : max), points[0]) const laterPoints = points.filter(p => hourOf(p) >= nowHour) const earlierPoints = points.filter(p => hourOf(p) < nowHour) @@ -67,7 +76,14 @@ export function summarizeToday(points, nowHour) { // Umbrella nudge: real rain chance either right now or coming up later today. const umbrella = laterWetFraction > 0.25 || rainFraction > 0.25 - return { headline, note, umbrella } + return { + headline, + note, + umbrella, + highTemp: highPoint.temp_c, + highFeelsLike: highPoint.feels_like_c, + rainChance: rainPeakPoint ? { prob: rainPeakPoint.precip_prob, ts: rainPeakPoint.ts } : null, + } } function hourOf(point) { diff --git a/frontend/src/index.css b/frontend/src/index.css index 83784b8..aa4d373 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -81,7 +81,16 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; } flex-wrap: wrap; gap: clamp(10px, 1.8vw, 24px); } +.wx-now-temp-block { display: flex; flex-direction: column; align-items: center; line-height: 1; } .wx-now-temp { font-size: clamp(2.4rem, 5.5vw, 4.6rem); font-weight: 700; line-height: 1; } +.wx-now-temp-label { + margin-top: 2px; + font-size: clamp(0.65rem, 0.85vw, 0.85rem); + font-weight: 600; + text-transform: uppercase; + letter-spacing: .06em; + color: var(--text-muted); +} .wx-now-meta { display: flex; flex-direction: column; gap: 4px; color: var(--text-muted); font-size: clamp(0.8rem, 1.05vw, 1.05rem); } .wx-summary-headline { color: var(--text); font-weight: 600; font-size: clamp(0.95rem, 1.3vw, 1.3rem); } .wx-summary-note { color: #fbbf24; } diff --git a/frontend/src/pages/Display.tsx b/frontend/src/pages/Display.tsx index dbd9ad1..d672afc 100644 --- a/frontend/src/pages/Display.tsx +++ b/frontend/src/pages/Display.tsx @@ -95,6 +95,15 @@ export default function Display() { const currentPoint = data.hourly.find(p => hourOf(p.ts) === nowHour) ?? data.hourly[0] const todayKey = data.hourly[0] ? dateKeyOf(data.hourly[0].ts) : null + // The "now" panel shows the day's high (not the current temp) and pairs + // "feels like" with that same hour rather than right now — falls back to + // currentPoint's own values only if summary is unexpectedly null (no hourly + // data yet, which currentPoint being defined already makes unlikely). + const displayHighTemp = data.summary?.highTemp ?? currentPoint?.temp_c ?? null + const displayFeelsLike = data.summary?.highFeelsLike ?? currentPoint?.feels_like_c ?? null + const displayRainChance = data.summary?.rainChance + ?? (currentPoint?.precip_prob != null ? { prob: currentPoint.precip_prob, ts: currentPoint.ts } : null) + const dayKeys = [...new Set(data.threeHourly.map(p => dateKeyOf(p.ts)))].slice(0, 2) const nextDayGroups = dayKeys.map(key => ({ key, @@ -120,13 +129,16 @@ export default function Display() { {currentPoint && (
-
{roundTemp(currentPoint.temp_c)}
+
+ {displayHighTemp != null && roundTemp(displayHighTemp)} + High +
{data.summary && {data.summary.headline}} - {currentPoint.feels_like_c != null && <>Feels like {roundTemp(currentPoint.feels_like_c)}} - {currentPoint.feels_like_c != null && currentPoint.precip_prob != null && ' · '} - {currentPoint.precip_prob != null && <>{Math.round(currentPoint.precip_prob)}% chance of rain} + {displayFeelsLike != null && <>Feels like {roundTemp(displayFeelsLike)}} + {displayFeelsLike != null && displayRainChance && ' · '} + {displayRainChance && <>{Math.round(displayRainChance.prob)}% chance of rain around {formatHour(displayRainChance.ts)}} {data.summary?.note && {data.summary.note}}
diff --git a/frontend/src/types.ts b/frontend/src/types.ts index e3276aa..01ea991 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -27,6 +27,9 @@ export interface Summary { headline: string // "Scattered showers today", "Rain likely later", ... note: string | null // e.g. "Thunderstorms possible", alongside the headline umbrella: boolean // real rain chance now or later today + highTemp: number // today's forecast high (°C) + highFeelsLike: number | null // feels-like at the same hour as highTemp, not "now" + rainChance: { prob: number; ts: string } | null // today's peak precip% + when it falls } export type Provider = 'met_office' | 'open_meteo'