aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans/2026-09-17-weather-widget.md
AgeCommit message (Collapse)AuthorFilesLines
2026-09-17feat: put the weather card under the clock, scale its type to widthDanilo M.1-4/+4
Placement is column 1 row 2, directly beneath the clock, which drops to a single-row cell to make room. The card's type now scales off the cell WIDTH rather than its height. Scaling off height meant the text shrank every time the cell got shorter, and beside the clock's numerals the card read as a whisper. Width is the stable dimension for this layout and is what constrains the text anyway, since the stat rows run edge to edge. Verified against the live dashboard, not only the offscreen render: the two disagreed about type size precisely because the offscreen harness was cropped to the card and never showed it next to the clock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17fix: size the weather card to its contentDanilo M.1-2/+9
The first draft placed every band at a fixed pixel offset and let the arc absorb whatever height was left. In a 1x2 cell on this monitor that is 794px of arc: a cathedral arch under a fifth of a card of content, nothing like the mockup it was shaped after. Three changes, all visible in an offscreen render of the real widget: - Type sizes and band offsets scale with the rect, within clamps, so the proportions hold on the 1920 monitor as well as this one. - The arc gets a bounded band that follows the stats instead of being pinned to the bottom, so no dead strip opens between them. - The layout gives the widget a 1x1 cell. Its content is about 350px tall and a full-height cell cannot be filled without inventing data. Found before restarting conky by loading conky's own cairo bindings standalone and rendering the widget to a PNG, which is a far tighter loop than install, restart, switch workspace, screenshot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17fix: let the shellcheck directive bind to the source lineDanilo M.1-1/+3
`set -a; . "$ENV_FILE"; set +a` on one line meant the `# shellcheck source=/dev/null` above it attached to the `set`, not the `.`, so SC1090 fired anyway. Split across lines, the directive binds and the script lints clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17fix: correct the .cod rationale, and stop world-reading the cacheDanilo M.1-3/+13
The comment claimed a bad key returns HTTP 200 with an error body, so the status code alone proves nothing. Measured against the live API, that is wrong: a bad key returns 401 and an unknown city 404, and with curl -f both fail at curl without the .cod check ever running. The claim came from the polybar script, which ran curl WITHOUT -f and so genuinely did receive error bodies with a success exit. The check was ported but not the reason for it. It still earns its place behind -f, for a 200 whose body is not usable weather, and the comment now says that instead of something false. The spec carried the same wrong sentence and is corrected too. Separately, the cache landed at 644 in a directory where every other generated file is 600, and the response carries the configured city and its coordinates. A umask before the write puts it at 600 without a window where it is briefly readable. The .cod discrepancy was found by the Task 6 implementer, which noticed the bad-key test produced a different message than the plan predicted and said so rather than reporting the expected text as achieved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17fix: guard now, and clamp a negative age, in the staleness pairDanilo M.1-2/+23
is_stale and age_str were the only functions in the module doing arithmetic on an unchecked argument: a nil `now` raised, and a raise in this project is a blank dashboard with no message. Every caller passes os.time(), which cannot be nil, so this was unreachable, but it is the same bug already fixed once in icon() and the module header promises nothing raises. age_str also printed a negative age when the observation timestamp was in the future, which a clock skew can produce. "stale -17m" on the card reads as a broken widget, so the age now floors at zero and such a reading counts as fresh: weather from the future is not old. With this the whole module is raise-free, verified across every function against nil, string, table, boolean, NaN, negative and zero arguments in all three positions. Both cases were found by the Task 5 implementer, which was asked to probe beyond its given tests and reported them without changing the code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17fix: do not truncate a number in exponent formDanilo M.1-3/+18
The numeric patterns stopped at the 'e', so a temp of 1.8e1 parsed as 1.8 and the card would have drawn 2 degrees instead of 18. Wrong in the worst way available: silently, and still looking like weather. OWM has not been seen to emit exponent form, and sampling several cities near and below zero returned plain decimals throughout, so this was not reachable in practice. Fixed regardless, because two characters in a character class is cheaper than the reasoning required to be sure it stays unreachable, and unlike the NaN case noted in sun_t this one fails invisibly rather than leaving a mark. Tests now cover both a negative temperature, which is ordinary here for half the year, and the exponent form. Reverting the pattern fails them. Found by adversarial probing of the committed parser, not by the plan's own tests, which only exercise the fixture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17docs: note that sun_t passes a NaN timestamp throughDanilo M.1-0/+5
NaN is a number, so the type guard admits it, and every comparison against NaN is false, so neither clamp catches it and the function returns NaN. Left unguarded on purpose. No caller can produce it: parse() matches %d+ for the sun times and `now` is os.time(). If one ever did, Cairo ignores a NaN coordinate, so the arc would lose its dot rather than the dashboard going blank. A ponytail: comment names the ceiling and where the guard would go. Found while probing the committed code, not by the tests, which do not cover NaN. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17docs: explain the Beaufort index arithmetic and why compass existsDanilo M.1-0/+9
Two comments, no behaviour change. `force - 1` reads as an off-by-one until you know the scale starts at 0 while ipairs starts at 1, so the loop index is already force + 1. Said plainly now, along with why the fall-through returns 12. compass() has no caller: the card draws arrow(). It is kept because it shares arrow()'s binning exactly, so testing both pins that shared logic from two angles, and switching the card to "S 9 km/h" becomes a one-word change. Without this note the next reader finds an unused function and has to reconstruct the reasoning. Raised by code review as the only two points worth acting on; no Critical or Important issues were found. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17fix: guard a nil timestamp in the icon lookupDanilo M.1-1/+5
icon(800, nil, 1500, 1900) compared nil against a number and raised, which in this project is a blank dashboard with no message. The module's own header promises nothing raises, and is_day() already guards the same case, so the inconsistency was the bug. Not reachable from the widget as written: it passes os.time(), and parse() refuses a response without sunrise and sunset, so no caller can currently produce the failing combination. Fixed anyway, because the contract is what future callers will rely on and the fix is one word. The test file also now says why the codepoint VALUES carry no assertion: comparing M.ICON.snow against a literal would only prove the escape was typed twice. Whether E31A draws a snow cloud is what the glyph sheet is for, and mutation testing confirms the assertions cannot see it. Found by code review. The same fix is applied to the plan, so Tasks 2-5 inherit the guarded version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17fix: correct the day/night constants in Task 1's testDanilo M.1-1/+4
DAY was 1000 against a sunrise of 1500, so every timestamp in the test evaluated as night and the first day assertion could never pass. The implementation was right; the test constants were not. DAY is now 1700, inside the 1500..1900 window, with a comment saying why the value has to be checked against the window rather than just named. Caught by the Task 1 implementer, which stopped rather than quietly inverting the day/night comparison to make the assertions pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17docs: add the weather widget implementation planDanilo M.1-0/+1186
Nine tasks, TDD throughout: the domain tables and parser get tests, the card gets a screenshot. The icon codepoints in the plan were each rendered and inspected rather than derived. That caught U+E30A, which is present in the font and draws a sun-with-rain, standing in for snow; the correct glyph is U+E31A. A wrong-but-present codepoint fails this way, plausibly, which is why the plan tells the implementer to look at the rendered sheet instead of trusting a fontconfig query. Task 6 and Task 8 both state which outcomes are acceptable when the OWM key is still activating, so an inactive key blocks neither. Paths are relative to the repo root rather than absolute: the first version of this file hardcoded a home directory in eighteen places and the pre-commit hook rejected it, correctly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>