"Change the garlic bed to cucumbers this year" has a year in it, and pansy had no notion of one — the editor showed whatever is currently planted, full stop.
No schema change, and specifically no seasons table
The data has always supported seasons: plantings carry planted_at/removed_at, and "clear bed" soft-removes rather than deleting. A season is a date range over data that already exists. A seasons table would duplicate what the dates already say and create a second source of truth about when something was in the ground.
?year=YYYY on /full returns every plop whose [planted_at, removed_at] interval overlapped that calendar year — so garlic planted in October and pulled the following July appears in both years, which is what actually happened.
Undated plantings appear in every year. Everything predating this feature has a null planted_at, and a rule that excluded them would empty every existing garden the moment a year was selected — technically defensible, useless in practice.
Without the param, /full behaves exactly as before. The existing tests pass unchanged, which was the point of doing it this way.
Widening the plops means widening the plants
A plant pulled last July isn't active, but its plops still have to render with the right icon and colour. ListReferencedPlants takes an includeRemoved flag that tracks the same switch — otherwise a past season draws a bed full of grey unnamed circles.
The past is read-only
Gated at the single canEdit that the palette, inspector, keyboard nudging, placement and drag handles all key off. Editing the past by accident is the failure mode this feature introduces, so the state is stated in a banner rather than implied by a dropdown you set a while ago — with the way back to the live garden right next to it.
The season is a separate query, deliberately
The optimistic mutations all patch gardenFullKey(gardenId). Folding a year into that key would let them write into whichever season happened to be on screen. Read-only views never need that machinery, and keeping them under their own key means they can't accidentally join it.
The selector offers only years with data
Plus the current one. A free numeric field invites a typo, and a typo'd year produces a confidently empty garden that reads as data loss rather than a mistake. The server bounds the year for the same reason.
GET /gardens/:id/full?year=YYYY
GET /gardens/:id/years
Verification
3 new service tests covering every acceptance criterion: a plop planted and pulled inside 2025 appearing only there; one spanning the year boundary appearing in both; an undated one appearing in every year including 2024; the live view still returning only what's in the ground; a past season carrying its plants along; the year list containing exactly the years with data plus the current one, newest first; and out-of-range years refused.
go test ./..., go vet ./..., tsc --noEmit, npm run build green.
Closes #54. Phase 1 of #58.
*"Change the garlic bed to cucumbers **this year**"* has a year in it, and pansy had no notion of one — the editor showed whatever is currently planted, full stop.
### No schema change, and specifically no `seasons` table
The data has always supported seasons: plantings carry `planted_at`/`removed_at`, and "clear bed" soft-removes rather than deleting. **A season is a date range over data that already exists.** A `seasons` table would duplicate what the dates already say and create a second source of truth about when something was in the ground.
`?year=YYYY` on `/full` returns every plop whose `[planted_at, removed_at]` interval overlapped that calendar year — so garlic planted in October and pulled the following July appears in **both** years, which is what actually happened.
**Undated plantings appear in every year.** Everything predating this feature has a null `planted_at`, and a rule that excluded them would empty every existing garden the moment a year was selected — technically defensible, useless in practice.
Without the param, `/full` behaves exactly as before. **The existing tests pass unchanged**, which was the point of doing it this way.
### Widening the plops means widening the plants
A plant pulled last July isn't active, but its plops still have to render with the right icon and colour. `ListReferencedPlants` takes an `includeRemoved` flag that tracks the same switch — otherwise a past season draws a bed full of grey unnamed circles.
### The past is read-only
Gated at the single `canEdit` that the palette, inspector, keyboard nudging, placement and drag handles all key off. **Editing the past by accident is the failure mode this feature introduces**, so the state is stated in a banner rather than implied by a dropdown you set a while ago — with the way back to the live garden right next to it.
### The season is a separate query, deliberately
The optimistic mutations all patch `gardenFullKey(gardenId)`. Folding a year into that key would let them write into whichever season happened to be on screen. Read-only views never need that machinery, and keeping them under their own key means they can't accidentally join it.
### The selector offers only years with data
Plus the current one. A free numeric field invites a typo, and a typo'd year produces a confidently empty garden that reads as data loss rather than a mistake. The server bounds the year for the same reason.
```
GET /gardens/:id/full?year=YYYY
GET /gardens/:id/years
```
### Verification
3 new service tests covering every acceptance criterion: a plop planted and pulled inside 2025 appearing only there; one spanning the year boundary appearing in both; an undated one appearing in every year including 2024; the live view still returning only what's in the ground; a past season carrying its plants along; the year list containing exactly the years with data plus the current one, newest first; and out-of-range years refused.
`go test ./...`, `go vet ./...`, `tsc --noEmit`, `npm run build` green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
"Change the garlic bed to cucumbers this year" has a year in it, and pansy had
no notion of one — the editor showed whatever is currently planted, full stop.
The data has always supported seasons: plantings carry planted_at and
removed_at, and "clear bed" soft-removes rather than deleting. A season is a
date range over data that already exists. No schema change, and specifically no
seasons table — it would duplicate what the dates already say and create a
second source of truth about when something was in the ground.
?year=YYYY on /full returns every plop whose [planted_at, removed_at] interval
overlapped that calendar year, so garlic planted in October and pulled the
following July appears in BOTH years, which is what actually happened. Undated
plantings appear in every year: everything that predates this feature has a null
planted_at, and a rule that excluded them would empty every existing garden the
moment a year was selected. Without the param /full behaves exactly as before —
the existing tests pass unchanged, which was the point of doing it this way.
Widening to past plops means widening the referenced-plant lookup with it.
A plant pulled last July isn't active, but its plops still have to render with
the right icon and colour, so ListReferencedPlants takes an includeRemoved flag
that tracks the same switch.
A past season is READ-ONLY, gated at the single canEdit the palette, inspector,
nudging, placement and drag handles all key off. Editing the past by accident is
the failure mode this feature introduces, so the state is stated in a banner
rather than implied by a dropdown you set a while ago, with the way back to the
live garden next to it.
The season is a separate query under its own key. The optimistic mutations all
patch gardenFullKey(gardenId); folding a year into that key would let them write
into whichever season happened to be on screen. Read-only views never need that
machinery, and keeping them out of it means they can't accidentally join it.
The year selector offers only years the garden holds data for, plus the current
one. A free numeric field invites a typo, and a typo'd year produces a
confidently empty garden that reads as data loss rather than a mistake — the
server bounds the year for the same reason.
Closes#54
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
Live status board. Findings are posted in each model's own comment. Advisory only — does not block merge.
<!-- gadfly-status-board -->
## 🪰 Gadfly — live review status
1/5 reviewers finished · updated 2026-07-21 05:47:17Z
#### `claude-code/sonnet` · claude-code — ✅ done
- ✅ **security** — No material issues found
- ✅ **correctness** — No material issues found
- ✅ **maintainability** — No material issues found
- ✅ **performance** — Minor issues
- ✅ **error-handling** — No material issues found
#### `glm-5.2:cloud` · ollama-cloud — ⏳ 3/5 lenses
- ✅ **security** — No material issues found
- ✅ **correctness** — No material issues found
- ⚠️ **maintainability** — could not complete
- 🔄 **performance** — running
- 🔄 **error-handling** — running
#### `kimi-k2.6:cloud` · ollama-cloud — ⏳ 4/5 lenses
- ✅ **security** — No material issues found
- ✅ **correctness** — No material issues found
- ✅ **maintainability** — Minor issues
- ✅ **performance** — No material issues found
- 🔄 **error-handling** — running
#### `opencode/glm-5.2:cloud` · opencode — ⏳ 4/5 lenses
- ✅ **security** — No material issues found
- ⚠️ **correctness** — could not complete
- ✅ **maintainability** — Minor issues
- ✅ **performance** — No material issues found
- 🔄 **error-handling** — running
#### `opencode/kimi-k2.6:cloud` · opencode — ⏳ 1/5 lenses
- 🔄 **security** — running
- 🔄 **correctness** — running
- ✅ **maintainability** — No material issues found
- 🔄 **performance** — running
- 🔄 **error-handling** — running
<sub>Live status board. Findings are posted in each model's own comment. Advisory only — does not block merge.</sub>
ListReferencedPlants(includeRemoved=true) returned every plant the garden has
ever held, so drawing one season loaded a decade of catalog on a decade-old
garden. It also meant the plant list and the plop list were selected by different
rules, which is the kind of near-miss that stays correct only by accident.
It now takes the same year the plops do, so the two selections always agree.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
Fixed in c30a1f4. Fair catch, and the framing in the finding undersells it slightly.
includeRemoved=true returned every plant the garden has ever held, so drawing one season loaded a decade of catalog on a decade-old garden. But the sharper problem is that the plant list and the plop list were selected by different rules — the plops by year, the plants by "all history". That happens to be a superset, so it's correct today, but only by accident: any future narrowing of one selection silently breaks the other, and the failure mode is plops rendering as grey unnamed circles.
ListReferencedPlants now takes the same year the plops do, so the two selections agree by construction rather than by luck. Test asserts a 2020 season carries only the 2020 plant and a 2026 season only the 2026 one.
go test ./..., go vet ./..., gofmt green.
Fixed in `c30a1f4`. Fair catch, and the framing in the finding undersells it slightly.
`includeRemoved=true` returned every plant the garden has *ever* held, so drawing one season loaded a decade of catalog on a decade-old garden. But the sharper problem is that **the plant list and the plop list were selected by different rules** — the plops by year, the plants by "all history". That happens to be a superset, so it's correct today, but only by accident: any future narrowing of one selection silently breaks the other, and the failure mode is plops rendering as grey unnamed circles.
`ListReferencedPlants` now takes the same `year` the plops do, so the two selections agree by construction rather than by luck. Test asserts a 2020 season carries only the 2020 plant and a 2026 season only the 2026 one.
`go test ./...`, `go vet ./...`, `gofmt` green.
steve
merged commit 8c5ddb21ec into main2026-07-21 05:47:35 +00:00
steve
deleted branch feat/season-view2026-07-21 05:47:35 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #54. Phase 1 of #58.
"Change the garlic bed to cucumbers this year" has a year in it, and pansy had no notion of one — the editor showed whatever is currently planted, full stop.
No schema change, and specifically no
seasonstableThe data has always supported seasons: plantings carry
planted_at/removed_at, and "clear bed" soft-removes rather than deleting. A season is a date range over data that already exists. Aseasonstable would duplicate what the dates already say and create a second source of truth about when something was in the ground.?year=YYYYon/fullreturns every plop whose[planted_at, removed_at]interval overlapped that calendar year — so garlic planted in October and pulled the following July appears in both years, which is what actually happened.Undated plantings appear in every year. Everything predating this feature has a null
planted_at, and a rule that excluded them would empty every existing garden the moment a year was selected — technically defensible, useless in practice.Without the param,
/fullbehaves exactly as before. The existing tests pass unchanged, which was the point of doing it this way.Widening the plops means widening the plants
A plant pulled last July isn't active, but its plops still have to render with the right icon and colour.
ListReferencedPlantstakes anincludeRemovedflag that tracks the same switch — otherwise a past season draws a bed full of grey unnamed circles.The past is read-only
Gated at the single
canEditthat the palette, inspector, keyboard nudging, placement and drag handles all key off. Editing the past by accident is the failure mode this feature introduces, so the state is stated in a banner rather than implied by a dropdown you set a while ago — with the way back to the live garden right next to it.The season is a separate query, deliberately
The optimistic mutations all patch
gardenFullKey(gardenId). Folding a year into that key would let them write into whichever season happened to be on screen. Read-only views never need that machinery, and keeping them under their own key means they can't accidentally join it.The selector offers only years with data
Plus the current one. A free numeric field invites a typo, and a typo'd year produces a confidently empty garden that reads as data loss rather than a mistake. The server bounds the year for the same reason.
Verification
3 new service tests covering every acceptance criterion: a plop planted and pulled inside 2025 appearing only there; one spanning the year boundary appearing in both; an undated one appearing in every year including 2024; the live view still returning only what's in the ground; a past season carrying its plants along; the year list containing exactly the years with data plus the current one, newest first; and out-of-range years refused.
go test ./...,go vet ./...,tsc --noEmit,npm run buildgreen.🤖 Generated with Claude Code
https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
🪰 Gadfly — live review status
1/5 reviewers finished · updated 2026-07-21 05:47:17Z
claude-code/sonnet· claude-code — ✅ doneglm-5.2:cloud· ollama-cloud — ⏳ 3/5 lenseskimi-k2.6:cloud· ollama-cloud — ⏳ 4/5 lensesopencode/glm-5.2:cloud· opencode — ⏳ 4/5 lensesopencode/kimi-k2.6:cloud· opencode — ⏳ 1/5 lensesLive status board. Findings are posted in each model's own comment. Advisory only — does not block merge.
Fixed in
c30a1f4. Fair catch, and the framing in the finding undersells it slightly.includeRemoved=truereturned every plant the garden has ever held, so drawing one season loaded a decade of catalog on a decade-old garden. But the sharper problem is that the plant list and the plop list were selected by different rules — the plops by year, the plants by "all history". That happens to be a superset, so it's correct today, but only by accident: any future narrowing of one selection silently breaks the other, and the failure mode is plops rendering as grey unnamed circles.ListReferencedPlantsnow takes the sameyearthe plops do, so the two selections agree by construction rather than by luck. Test asserts a 2020 season carries only the 2020 plant and a 2026 season only the 2026 one.go test ./...,go vet ./...,gofmtgreen.