Light the house for someone who has died (#80) #138
No reviewers
Labels
No labels
area/ai
area/backend
area/frontend
area/infra
area/scheduler
area/wled
good-first-issue
priority/high
priority/low
priority/medium
type/bug
type/chore
type/ci-cd
type/docs
type/feature
type/qa
v1.0.0
v1.1.0
v1.2.0
v2.0.0
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Iris-WLED!138
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/80-memorial"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #80. All four gaps and all five acceptance criteria.
A Memorial button on the calendar toolbar takes a name, a date, optional notes and a "remember every year" toggle, and produces a candidate scheme to review.
The issue is right that most of this already worked — a memorial is structurally a user-defined event, and that path has been complete since v1. So this is deliberately thin, and there's a test asserting the generic
POST /eventspath still does the job, because that claim is load-bearing for the whole design.Gap 1: the AI prompt was holiday-shaped
The highest-value gap, and the one that would have hurt. It opened "generate a JSON lighting scheme for a holiday or observance event" with festive few-shot examples — asked for a memorial it would reliably return something bright and fast for someone's funeral.
The system prompt now branches, and the examples are replaced rather than appended: showing festive and sombre examples together invites the model to average two incompatible briefs.
Branching is also the right shape. Filtering a celebratory scheme into a sombre one afterwards gives a muddy version of the wrong idea, not the right one.
The brief names what to avoid — strobes, chases, sparkles, festive — rather than only asking for dignity, because those are exactly what a model reaches for when it thinks "event".
There's a limit to what a test can assert about a prompt: it can check the model was asked the right question, not that it answered well. So the tests assert the brief, and Gap 2 is what guarantees a dignified result when nothing is asked at all.
Gap 2: the no-AI floor
A
memorialtag template inschemes/builtin.yaml— soft amber candlelight on a slow Breathe, brightness 120, an eight-second transition.AI_PROVIDERis optional and capped, so without this a memorial with no provider fell through to solid white.Tested relative to the rest of the library rather than in the abstract: dimmer and slower than the patriotic template, which is the comparison that actually means "dignified".
Gap 3: annual recurrence
Opt-in, on a separate tag — most memorials mark someone who died this year, and quietly repeating one forever is a stranger default than not. The Jan 1 job materialises the next occurrence reusing the same
event_key, which is what makes the anniversary reuse the approved scheme with no AI call and nothing to review. That mechanism already existed; this only creates the row.It rolls from the most recent occurrence rather than the original, so an edit to the name carries forward instead of being reverted every January to whatever was first typed.
A 29 February anniversary is skipped in non-leap years rather than moved. Someone who died on a leap day has an anniversary that genuinely only occurs every four years, and picking the 28th or 1 March on their behalf is not this code's decision to make.
Gap 4: the UI
There was no event-create UI at all, so this is the first one. Priority is on the form, not hidden behind its default: user events sit at 100 and outrank everything, so a memorial on 23 December replaces Christmas Eve. The issue calls that the intended default, and it is — but a default that surprising belongs in front of someone at the moment they choose it rather than discovered in December. The field says so in as many words.
The event key is slugified from the name, and that's load-bearing rather than cosmetic: scheme reuse is keyed on it, so the same name must produce the same slug in two different years or the anniversary silently generates a fresh candidate. It has its own tests, including accents and punctuation.
Also corrected
builtin.yaml's header comment claimed65=Colorful, 66=Traffic. Those are Palette and Fire 2012 (#132) — the same class of error as the one just fixed, sitting in the file most likely to be read by someone choosing an effect.Verification
34 new backend tests, 8 new frontend. The prompt branch was mutation-tested — removing it fails three tests with the right diagnosis. Backend
ruff/mypyclean, 795 tests passing. Frontendtsc -bclean,eslintclean.Out of scope, as the issue says
No external death feed, no automatic detection. Memorials are created by hand, on purpose, by someone who has decided to mark a specific person. That's the character of the feature.
🤖 Generated with Claude Code
c257a58ef5084f018d0d084f018d0d77ad4d8d26