feat(todo): Todo's server answers a place's forecast (ADR 0033, D40)

The weather of D40 comes through the instance, as Train's workouts'
(ADR 0036, D1): Todo's server gets a `weather` endpoint, `offered` and
`forecast`, answering a place's hours for the next 16 days from
Open-Meteo for people signed in alone, the place rounded to about a
kilometre, nothing kept or logged, 30 requests a person a minute.
Refusals are `WeatherRefused` (off, invalid, tooOften, unreachable).
`WEATHER=off` and `OPEN_METEO_API_KEY` switch it as they switch Train's,
and production's compose file passes them to Todo's server too.

D40 records the developer's decisions of 10 Nov 2026 on the weather and
the agent's details (what dry, sunny, a temperature range and calm mean
over a task's hours).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-10 14:43:15 +01:00
commit a91aa98817
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -768,6 +768,14 @@ Reactions are a list of emoji in the comment's payload, each once; a chip's tap
- *Read by a parser of our own, recurrence by `rrule`* (the developer's choice of three): the file's events (VEVENT: title, start, end or duration, all-day, place, cancelled, the dates a recurrence leaves out and the occurrences changed) are read by `ical.dart`, short and tested here; RFC 5545's recurrence rules, the large part easy to get wrong, are expanded by pub.dev's `rrule` (Apache-2.0); an event's time zone becomes the device's through `timezone`, already in the lock. **Rejected:** `icalendar_parser` beside `rrule` (one more dependency, less maintained, for the easy part), and recurrence of our own.
- *Shown as a task's date is*: an event becomes a day, a time and a duration on the device's clock, as `TaskDate`, so the lists, the agenda and the grids place it as they place tasks. It has no check, can't be dragged and opens nothing yet but its details. A saved filter takes an event of the next four weeks where its days do (the agent's reading of *where a filter's days include them*): read as a task of its project on that day, with no priority or labels, the filter answers, and wouldn't for the same task without a date or a century later; so `today` or `7 days & #School` take events, and `#School`, `no date` or `p1 & today` none, a calendar's being endless. A label's screen takes none: events have no labels.
**D40. The weather: a forecast on days, tasks tied to it, fitting days marked** (the developer's, 10 Nov 2026, the details flagged as the agent's). Todo shows Open-Meteo's forecast where days are shown, a task can be tied to weather it needs, and a task so tied is flagged when the forecast stops fitting it; the date picker marks the days that fit.
- *Through the instance, as Train's* (ADR 0036, D1): Todo's server gets a `weather` endpoint (`lib/src/weather/`, Train's copied): `offered`, and `forecast` answering a place's hours for the next 16 days, Open-Meteo's furthest, for people signed in alone, the place rounded to two decimals on the device and again on the server, nothing kept or logged, at most 30 requests a person a minute, refusals `WeatherRefused` (`off`, `invalid`, `tooOften`, `unreachable`), the operator's `WEATHER=off` and `OPEN_METEO_API_KEY` as Train's. **Why not the device directly:** Open-Meteo would see each person's address beside where they live.
- *For which place* (the developer's): one set in Settings (Weather's card, a place picked as a task's is, kept in the sealed settings, synced); else, for a task with a place (D32), that task's first place. The device's location is never read for it. Without either, no forecast and nothing flagged. Days show the forecast only for the place in Settings.
- *What a task can need* (the developer's): dry, sunny, a temperature range (from and to, either open: *Not freezing* is from 1 °C, *Warm* from 18 °C, the agent's), calm; kept in its sealed payload as `weather` (schema stays 1). What each means is the agent's: over the task's hours (its time to its end, an hour at least; a task without a time over 8:00 to 20:00 of its day), **dry** is every hour under 0.1 mm with a chance of rain under 40 %; **sunny** every hour clear, mainly clear or partly cloudy (WMO codes 0 to 2); the **range** every hour's temperature within it; **calm** every hour's wind under 20 km/h and its gusts under 40 km/h. A day beyond the forecast fits as far as anyone knows: nothing is flagged or marked there.
- *Flagged, nothing moved* (the developer's): a task whose forecast doesn't fit carries a mark on its row and says why in its details; its date and its reminder stay. The date picker marks each of the coming 16 days that fits the task's needs.
- *Asked as the app opens and hourly while it is open, and on a pull* (the agent's, as the calendars of D39), one request a place (Settings' place, else each task's place among the open tasks tied to weather, at most 10); held in memory alone, a forecast hours old being little use offline. The licence's "Weather data by Open-Meteo.com" stands under Weather's card in Settings.
## Steps
1. **The frame**: go_router and `NavigationFrame` with the four tabs (empty screens that