aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/specs')
-rw-r--r--docs/superpowers/specs/2026-10-18-mirage-weather-cards-design.md176
1 files changed, 176 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-10-18-mirage-weather-cards-design.md b/docs/superpowers/specs/2026-10-18-mirage-weather-cards-design.md
new file mode 100644
index 0000000..b2ee825
--- /dev/null
+++ b/docs/superpowers/specs/2026-10-18-mirage-weather-cards-design.md
@@ -0,0 +1,176 @@
+# Mirage weather cards: design
+
+Date: 2026-10-18
+
+## Goal
+
+With most system cards moved to quickshell, the conky dashboard keeps the
+time-and-place cards. This adds a fuller weather set, adapted from the
+Conky-Mirage theme by alexsson-xexpanderx
+(https://github.com/alexsson-xexpanderx/Conky-themes/tree/master/Conky-Mirage),
+drawn in this repository's own design language.
+
+Mirage's tiles come over as ideas and maths, not as code. Mirage's look (glass
+cut from the wallpaper, wallpaper-derived palette, animated icons) does not.
+
+## Scope
+
+In:
+
+- Five weather cards: `now`, `hourly`, `week`, `details`, `sky`.
+- The current `weather` card is removed; `now` and `sky` cover it.
+- Data moves from OpenWeatherMap to Open-Meteo (no key; hourly, daily, UV and
+ air quality available).
+- Credit to Mirage in each new widget's header comment and in README.md.
+
+Out (deliberately):
+
+- Animated icons. The dashboard stays at its 2s update interval.
+- Wallpaper glass, wallpaper palette, Mirage's fold-to-strip, its 12h option.
+- Mirage's system tile.
+- IP-based geolocation. Location comes from `weather.env` only.
+- A second conky instance. Everything runs in the existing `dashboard.lua`
+ instance on the Hyprland special workspace `dash`.
+
+## Look
+
+The hybrid rule: DESIGN.md wins wherever it has an answer, and Mirage supplies
+only what DESIGN.md calls a card's variable band.
+
+- Palette roles, fonts (`FONT_MONO`, `FONT_HEAVY`, `FONT_UI`) and chrome come
+ from `lib/card.lua`.
+- Icons are Nerd Font weather glyphs, as today. Moon phases use the Nerd Font
+ `moon_*` glyph set (28 phases). Every new codepoint is rendered and looked
+ at before it is trusted (DESIGN.md, Glyphs).
+- Every size derives from the card's rect.
+
+## Cards
+
+| Card | Header left | Big value (top-right) | Rows | Variable band |
+|---|---|---|---|---|
+| `now` | condition glyph | current temperature, `body` | feels like, high / low, rain chance, description in `FONT_UI` | none |
+| `hourly` | glyph + "Next 24h" | next hour's temperature, `body` | none | temperature curve over 24 hours; every 3rd hour labelled with glyph and temperature; rain-chance bars under the curve in `rule` |
+| `week` | glyph + "Week" | none (band carries the value) | one per day: day name, glyph, min, range bar, max | the range bar in each row, positioned on the week's overall min..max scale |
+| `details` | glyph + "Details" | none | wind (speed + compass point), humidity, UV, AQI | a small wind compass with a needle at the wind direction |
+| `sky` | glyph + "Sky" | moon-phase glyph, `heading` | sunrise, sunset, day length, moon illumination % | the daylight arc, moved from today's `widgets/weather.lua` |
+
+### Colour thresholds
+
+Measured quantities use the `ok` / `warning` / `critical` roles:
+
+| Quantity | ok | warning | critical |
+|---|---|---|---|
+| UV index | < 3 | 3 to < 8 | >= 8 |
+| European AQI | <= 40 | <= 80 | > 80 |
+
+Humidity, wind and temperatures stay in `value` / `body`. This restores a
+`critical` user to the board, closing the open question in HANDOFF about
+DESIGN.md's three-states rule.
+
+### Failure states
+
+- No cache, or a cache that fails to parse: every card keeps its chrome and
+ draws `no weather data`.
+- A missing field: `--`, never `0`.
+- A stale cache (older than `STALE_AFTER`, 45 min): cards draw, and `now`
+ shows the age, as the current card does.
+- No air-quality line: AQI row shows `--`; the rest of the card is unaffected.
+
+## Data
+
+### Fetcher: `bin/weather-fetch.sh`
+
+Rewritten in bash with curl and jq, the tools it already uses.
+
+1. Reads `~/.config/udt/weather.env`: `CITY`, `COUNTRY`, `UNITS`
+ (`metric` or `imperial`). `KEY` is gone.
+2. Geocodes once via `https://geocoding-api.open-meteo.com/v1/search`
+ (name = `CITY`, filtered by `COUNTRY` code), caching `lat lon` in
+ `~/.cache/udt/weather-loc`. The cache is reused while `CITY`/`COUNTRY` are
+ unchanged (the cache file records them).
+3. Calls `https://api.open-meteo.com/v1/forecast` with `timeformat=unixtime`,
+ `timezone=auto`, `forecast_days=7`, `current` (temperature, apparent
+ temperature, weather code, is_day, humidity, wind speed and direction),
+ `hourly` (temperature, weather code, precipitation probability, is_day,
+ uv_index), `daily` (weather code, min, max, precipitation probability max,
+ sunrise, sunset). `imperial` adds `temperature_unit=fahrenheit` and
+ `wind_speed_unit=mph`; `metric` uses `kmh`.
+4. Calls `https://air-quality-api.open-meteo.com/v1/air-quality` with
+ `current=european_aqi`. Failure here is not fatal: the `air` line is
+ omitted.
+5. jq flattens both responses into the line format below, written to a temp
+ file and renamed onto `~/.cache/udt/weather.txt` (atomic, as today). A
+ forecast failure leaves the previous cache in place and exits non-zero.
+
+### Cache format: `~/.cache/udt/weather.txt`
+
+Tab-separated, one record per line, first field the record type. Numbers as
+Open-Meteo emits them; epochs in seconds.
+
+```
+units metric
+current <epoch> <temp> <feels> <code> <is_day> <humidity> <wind> <wind_deg>
+sun <sunrise_epoch> <sunset_epoch>
+air <european_aqi>
+hour <epoch> <temp> <code> <precip_prob> <is_day> <uv>
+day <epoch> <code> <min> <max> <precip_prob>
+```
+
+`hour` covers the 24 hours starting at the current hour; `day` covers 7 days
+starting today. `sun` is today's. Line-oriented so the Lua side parses with
+`gmatch` and needs no JSON parser.
+
+### Lua: `lib/weather.lua`
+
+Rewritten for Open-Meteo, same contract as today: pure functions on strings
+and numbers, nothing raises, nil on bad input.
+
+- `ICON` / `icon(code, is_day)`: WMO weather code to Nerd Font glyph.
+- `describe(code)`: WMO code to a short English description.
+- `parse(src)`: returns `{units, current, sun, air, hours, days}` or nil when
+ `current`, `sun` or at least one `hour` is missing.
+- `moon(epoch)`: returns `phase` (0..1, 0 = new) and `illumination` (0..1),
+ from the synodic month against a known new moon. `moon_icon(phase)` picks one
+ of the 28 glyphs.
+- `uv_role(uv)`, `aqi_role(aqi)`: threshold role names per the table above.
+- Kept from today: `compass`, `arrow`, `sun_t`, `is_day`, `STALE_AFTER`,
+ `is_stale`, `age_str`. Removed: OWM-specific `kmh` and `beaufort`, unless a
+ card ends up using them.
+
+The UV shown in `details` is the `hour` record for the current hour.
+
+## Widgets
+
+New files in `widgets/`: `now.lua`, `hourly.lua`, `week.lua`, `details.lua`,
+`sky.lua`. Each reads the cache through `lib/weather.lua` (same read-and-cache
+pattern as today's `weather.lua`, which is deleted). The daylight arc moves to
+`sky.lua`.
+
+`lib/card.lua` gets ICON entries for the new card titles if needed.
+
+## Layout
+
+`now` takes the old `weather` slot (col 1, row 7, w 3, h 5). `hourly`,
+`week`, `details` and `sky` go into the open right side of the 16x12 grid
+(cols 10 to 16). Exact cells are set in the plan and adjusted on the live
+board.
+
+## Config and docs
+
+- `weather.env.example`: drop `KEY` and its OpenWeatherMap notes.
+- README.md: weather section rewritten for Open-Meteo and the five cards,
+ Mirage credit, layout example updated from `weather` to the new names.
+- `conky.conf.in`: unchanged (`texeci 900` on the same script).
+
+## Testing
+
+- `test/test_weather.lua` rewritten against a fixture
+ `test/fixtures/weather.txt` (location-free; any names use `Example City`):
+ - WMO code to glyph and description, day and night, unknown code fallback.
+ - `parse`: full fixture, missing `air`, missing `current` (nil), garbage
+ input (nil), empty string (nil).
+ - `moon`: known full and new moon dates within one day of tolerance.
+ - `uv_role` / `aqi_role` at each threshold boundary.
+- Fetcher: run once by hand against the live API and inspect the cache.
+- Each card rendered offscreen with `test/render.lua` at its layout size, and
+ the live board checked after `./restart.sh` for size beside neighbours.