1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
# 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 at big-value size, standing in for the title | current temperature, `body` | feels like, high / low, rain chance, last update | 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 `value`, half alpha, with a minimum height so a low chance still shows |
| `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.
- `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`, `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, 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.
|