Season view: filter the editor to a year (#54) #66

Merged
steve merged 3 commits from feat/season-view into main 2026-07-21 05:47:35 +00:00
Owner

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.ai/code/session_01H3zbym8Doka2d7D48maSgZ

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
steve added 2 commits 2026-07-21 05:41:59 +00:00
"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
Thread the season year through a call site that arrived with seed lots
Build image / build-and-push (push) Successful in 5s
Gadfly review (reusable) / review (pull_request) Canceled after 5m28s
Adversarial Review (Gadfly) / review (pull_request) Canceled after 5m28s
1029aff2c7
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: 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 — 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

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>
steve added 1 commit 2026-07-21 05:47:16 +00:00
Scope the season's plant lookup to the season
Build image / build-and-push (push) Successful in 4s
34ff4b99f5
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
Author
Owner

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 main 2026-07-21 05:47:35 +00:00
steve deleted branch feat/season-view 2026-07-21 05:47:35 +00:00
Sign in to join this conversation.