From 8bbfd62d16a3a58f5d066b3019161c3ce6d4b0bd Mon Sep 17 00:00:00 2001 From: Theo Weijmer Date: Sun, 13 Sep 2026 10:01:14 +0200 Subject: [PATCH] feat: toon vandaag ook in de 5-daagse verwachting (nu 6 dagen) ForecastMapper mapt voortaan index 0 t/m 5 van Open-Meteo (was 1 t/m 5), zodat vandaag als eerste kolom in de verwachtingsstrook verschijnt naast de bestaande 5 dagen erna. Verwachtingskolommen gaan van 5 naar 6 breed (103px i.p.v. 124px), weericoon iets verkleind (58 -> 50px) om te passen. Spec bijgewerkt om de wijziging vast te leggen. Service-worker-versie naar v4 zodat het scherm dit zelf binnen enkele minuten oppikt. Co-Authored-By: Claude Sonnet 5 --- docs/weerscherm-spec.md | 9 +++++---- public/styles.css | 4 ++-- public/sw.js | 2 +- src/ForecastMapper.php | 6 +++--- tests/DashboardServiceTest.php | 4 ++-- tests/ForecastMapperTest.php | 18 +++++++++--------- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/docs/weerscherm-spec.md b/docs/weerscherm-spec.md index 1df1f86..5509c7a 100644 --- a/docs/weerscherm-spec.md +++ b/docs/weerscherm-spec.md @@ -1,6 +1,6 @@ # Weerscherm: bouwspecificatie v1 -Opdracht voor Claude Code: bouw een PWA die fullscreen draait op een iPad mini (liggend) in de woonkamer. Het scherm toont actuele gegevens van een Netatmo-weerstation en een weersverwachting voor 5 dagen. +Opdracht voor Claude Code: bouw een PWA die fullscreen draait op een iPad mini (liggend) in de woonkamer. Het scherm toont actuele gegevens van een Netatmo-weerstation en een weersverwachting voor 6 dagen. **Ontwerp A** in `docs/design/weerscherm-layouts.html` is leidend voor de layout. Dat bestand is een statische mockup met voorbeelddata. De CSS van ontwerp A (klasse `.da` en alle `.a-*`-klassen) is getest op 1133 × 744 punten en dient als uitgangspunt. De tabbladen B en C zijn niet in scope. @@ -26,7 +26,8 @@ Werkwijze: lees eerst dit document en de mockup. Stel daarna een plan voor per f - Buitentemperatuur, met de laagste en hoogste temperatuur van vandaag (inclusief tijdstippen) en de trend. - De 3 binnensensoren, elk met huidige, laagste en hoogste temperatuur van vandaag. - Regen: vandaag, de afgelopen 3 dagen per dag, en het totaal van de huidige maand. -- Verwachting voor de komende 5 dagen (vanaf morgen): max/min-temperatuur, neerslag in mm, neerslagkans en een weericoon. +- Verwachting voor 6 dagen (vandaag t/m 5 dagen verder): max/min-temperatuur, neerslag in mm, neerslagkans en een weericoon. + _Wijziging 2026-09-13: was oorspronkelijk 5 dagen vanaf morgen; op verzoek van Theo toegevoegd: vandaag als eerste kolom._ - Installeerbaar als PWA op het beginscherm, blijft werken met de laatst bekende data als de verbinding wegvalt. **Niet in v1:** nachtmodus (ontwerp C), andere meetwaarden (CO₂, luchtvochtigheid, wind), meerdere stations, een instellingenscherm, pushmeldingen. @@ -255,7 +256,7 @@ De iconen `sun`, `partly`, `rain` en `showers` staan in de mockup. Maak `cloud`, - "Vandaag" groot, in mm met 1 decimaal. - Daarnaast 4 staafjes: de 3 vorige dagen (labels "Di", "Wo", "Do") en vandaag, uitgelicht. De hoogte is `mm / max(4 waarden) × 70px`, met een minimum van 2px. Zijn alle waarden 0, dan zijn alle staafjes 2px. - Daaronder "September 38,5 mm". -- **Verwachting (rechtsonder):** 5 kolommen met dagafkorting ("Za"), icoon (58px), "max° min°" als hele getallen (min in `--muted`) en "x,x mm" plus neerslagkans in %. Geen neerslag toont "0 mm". +- **Verwachting (rechtsonder):** 6 kolommen met dagafkorting ("Za"), icoon (50px), "max° min°" als hele getallen (min in `--muted`) en "x,x mm" plus neerslagkans in %. Geen neerslag toont "0 mm". Eerste kolom is vandaag. ### 6.4 Verversen @@ -342,7 +343,7 @@ Hangt af van de iPadOS-versie in §0: - [ ] Op de iPad mini, gestart vanaf het beginscherm, draait de app fullscreen zonder Safari-balken en klopt de layout visueel met ontwerp A. - [ ] Alle waarden komen overeen met de Netatmo-app: buiten, 3× binnen, min/max met tijdstippen, regen vandaag en de afgelopen 3 dagen, en het maandtotaal. -- [ ] De verwachting toont 5 dagen vanaf morgen met het juiste icoon per WMO-code. +- [ ] De verwachting toont 6 dagen (vandaag t/m 5 dagen verder) met het juiste icoon per WMO-code. - [ ] De data ververst zichzelf. Na middernacht staat de nieuwe datum er binnen 5 minuten. - [ ] Wifi uit: de laatste data blijft staan, met een melding "geen verbinding sinds". Wifi aan: herstelt vanzelf. - [ ] De Netatmo-token ververst meerdere dagen achter elkaar zonder handmatig ingrijpen. diff --git a/public/styles.css b/public/styles.css index 647135f..9c7fbee 100644 --- a/public/styles.css +++ b/public/styles.css @@ -129,11 +129,11 @@ html, body { .a-bar.now span { color: var(--ink); font-weight: 600; } /* Verwachting */ -.a-fc { display: grid; grid-template-columns: repeat(5, 1fr); text-align: center; } +.a-fc { display: grid; grid-template-columns: repeat(6, 1fr); text-align: center; } .a-day { display: flex; flex-direction: column; align-items: center; gap: 6px; border-left: 1px solid var(--line); } .a-day:first-child { border-left: 0; } .a-dname { font-size: 19px; font-weight: 600; } -.a-icon { width: 58px; height: 58px; } +.a-icon { width: 50px; height: 50px; } .a-hl { font-size: 28px; font-weight: 500; } .a-hl span { font-size: 22px; color: var(--muted); font-weight: 400; margin-left: 6px; } .a-fr { font-size: 16px; color: var(--rain); } diff --git a/public/sw.js b/public/sw.js index 5627d46..c90ef8b 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,6 +1,6 @@ /* Service worker: app-shell cache-first, API network-first met timeout en fallback op de laatste response. */ -const VERSION = 'weerscherm-v3'; +const VERSION = 'weerscherm-v4'; const SHELL = [ './', './index.html', diff --git a/src/ForecastMapper.php b/src/ForecastMapper.php index a63273a..62c6725 100644 --- a/src/ForecastMapper.php +++ b/src/ForecastMapper.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace Weerscherm; -/** Zet het "daily"-blok van Open-Meteo om naar 5 dagen vanaf morgen (index 0 is vandaag). */ +/** Zet het "daily"-blok van Open-Meteo om naar 6 dagen, inclusief vandaag (index 0). */ final class ForecastMapper { - private const DAYS = 5; + private const DAYS = 6; /** * @param array $daily @@ -17,7 +17,7 @@ final class ForecastMapper { $dates = is_array($daily['time'] ?? null) ? $daily['time'] : []; $out = []; - for ($i = 1; $i <= self::DAYS && isset($dates[$i]); $i++) { + for ($i = 0; $i < self::DAYS && isset($dates[$i]); $i++) { if (!is_string($dates[$i])) { continue; } diff --git a/tests/DashboardServiceTest.php b/tests/DashboardServiceTest.php index 80d1a24..a3ae9b8 100644 --- a/tests/DashboardServiceTest.php +++ b/tests/DashboardServiceTest.php @@ -63,8 +63,8 @@ final class DashboardServiceTest extends TestCase self::assertSame(['2026-09-08', '2026-09-09', '2026-09-10'], array_column($p['rain']['previous_days'], 'date')); self::assertEqualsWithDelta(38.5, $p['rain']['month_total'], 0.0001); self::assertFalse($p['rain']['stale']); - self::assertCount(5, $p['forecast']); - self::assertSame('2026-09-12', $p['forecast'][0]['date']); + self::assertCount(6, $p['forecast']); + self::assertSame('2026-09-11', $p['forecast'][0]['date']); self::assertFalse($p['forecast_stale']); self::assertSame([], $p['errors']); } diff --git a/tests/ForecastMapperTest.php b/tests/ForecastMapperTest.php index 1a768e0..8010afb 100644 --- a/tests/ForecastMapperTest.php +++ b/tests/ForecastMapperTest.php @@ -15,16 +15,16 @@ final class ForecastMapperTest extends TestCase return json_decode((string) file_get_contents(__DIR__ . '/fixtures/forecast.json'), true)['daily']; } - public function testSkipsTodayAndReturnsFiveDays(): void + public function testIncludesTodayAndReturnsSixDays(): void { $f = ForecastMapper::map($this->daily()); - self::assertCount(5, $f); + self::assertCount(6, $f); self::assertSame([ - 'date' => '2026-09-12', 'weather_code' => 2, 'temp_max' => 18.3, 'temp_min' => 10.1, - 'precip_mm' => 0.4, 'precip_prob' => 20, + 'date' => '2026-09-11', 'weather_code' => 3, 'temp_max' => 17.9, 'temp_min' => 11.0, + 'precip_mm' => 0.0, 'precip_prob' => 10, ], $f[0]); - self::assertSame('2026-09-16', $f[4]['date']); + self::assertSame('2026-09-16', $f[5]['date']); } public function testNullValuesStayNull(): void @@ -35,9 +35,9 @@ final class ForecastMapperTest extends TestCase $f = ForecastMapper::map($daily); - self::assertNull($f[2]['weather_code']); - self::assertNull($f[2]['precip_prob']); - self::assertSame(17.4, $f[2]['temp_max']); + self::assertNull($f[3]['weather_code']); + self::assertNull($f[3]['precip_prob']); + self::assertSame(17.4, $f[3]['temp_max']); } public function testShorterResponseGivesFewerDays(): void @@ -47,7 +47,7 @@ final class ForecastMapperTest extends TestCase $daily[$key] = array_slice($values, 0, 3); } - self::assertCount(2, ForecastMapper::map($daily)); + self::assertCount(3, ForecastMapper::map($daily)); } public function testMissingTimeArrayGivesEmptyList(): void