git.lucas.co / cce-weather
weather: now, the next 24 hours and the week
git clone https://git.lucas.co/cce-weather.git

CLAUDE.md (3.4K)

 1 # cce-weather
 2 
 3 Current conditions, the next 24 hours and the week ahead. Read the workspace
 4 guide (`../cce-compositor/WORKSPACE.md`) first; this file covers only what is
 5 particular to this crate.
 6 
 7 ## Shape
 8 
 9 - `src/main.rs` — the `Application`: root plate, a control row (place
10   search `TextBox`, unit toggle, refresh), then three pane plates — now, the
11   hourly chart (a canvas well: glyphs, curve with labels, precipitation-chance
12   bars, hour labels) and the daily rows (range bar on the week's scale,
13   coloured cold → warm). A search with several matches swaps the panes for
14   up to five list-row buttons; one match is taken without asking.
15 - `src/api.rs` — Open-Meteo forecast + geocoder over blocking reqwest, on
16   worker threads; results come back through the loop's `Sender`. The
17   requested field lists and the serde structs move together.
18 - `src/config.rs` — config, state, units.
19 - `src/glyph.rs` — WMO code → words and → glyph. The glyphs are the
20   cce-icons set's multicolour `weather-*` family (sun, moon, clouds, rain,
21   snow, bolt in their own colours), drawn with `PaintCtx::icon_untinted` —
22   never `icon`, which would tint them one colour. Clear and partly cloudy
23   have a night variant (`is_day`); the rest are one glyph for both. Every
24   icon this app draws comes from that set; the refresh button's text
25   fallback is the word "Refresh". Without `CCE_ICONS_DIR` reaching the set
26   (a shadow's isolated HOME), every glyph draws blank.
27 
28 ## Data
29 
30 Open-Meteo needs no key or account. `timezone=auto` makes every time string
31 local wall-clock time *at the place*, without an offset — compare them as
32 strings (`hour_index`), never against the machine's clock.
33 
34 ## Config vs state
35 
36 - `~/.config/cce/cce-weather/config.kdl`:
37   `units "imperial"|"metric"`, `location "Name" lat=… lon=… region="…"`,
38   `refresh_minutes 15` (clamped 5..1440). No units anywhere → the
39   measurement locale (US → imperial).
40 - `~/.local/state/cce/weather/state.json`: ONLY choices made in the app (a
41   searched place, the toggle) plus the last forecast and the place/units it
42   was for. A choice made in the app wins over the config; a setting never
43   touched in the app keeps following the config. Delete the state file to
44   go back to the config entirely.
45 
46 ## Refresh
47 
48 A timer thread wakes every minute and compares the *wall clock* with the
49 last fetch, so a laptop waking from suspend refreshes within a minute — a
50 monotonic sleep for the whole interval does not count suspended time. A
51 failed fetch also resets the clock (one retry per interval, not per minute);
52 Refresh / Ctrl+R / F5 retry at once. The idle frame loop is untouched: the
53 thread only sends a message when a refresh is due.
54 
55 ## Text
56 
57 Pass the family from `list_font_parsed().0`, never `list_font()`: the
58 latter may carry a size, which overrides every size asked for (the big
59 temperature rendered at body size until this).
60 
61 ## Verifying
62 
63 `cargo test -p cce-weather` (offline); `cargo test -p cce-weather -- --ignored`
64 hits the real service. Visually, shadow only, with the fonts exported:
65 
66 ```sh
67 cce-shadow start --new            # add --scale 2 for the live display's scale
68 cce-shadow spawn env CCE_FONTS_DIR=/home/lsgalante/Dropbox/Fonts \
69   CCE_ICONS_DIR=/home/lsgalante/projects/cce/cce-icons/svg \
70   /home/lsgalante/projects/cce/target/release/cce-weather
71 cce-shadow shot-window 0
72 ```
73 
74 The shadow has its own HOME, so its state file is not the real one.