Gadfly (2 models, correctness): the redirect effect checked live.isError
but the rendered "taking you to your gardens…" message read full.error —
which is the SEASON query when viewing a past year. A season-view 404 with
a healthy live garden would then show a redirect promise the effect never
fires, stranding the user.
Derive gardenGone once from the LIVE query (garden existence doesn't depend
on the season viewed) and use it for BOTH the bounce effect and the render
message, so the promise and the redirect can't disagree. Also removes the
duplicated not-found reasoning the maintainability finding flagged.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
`/` always dumped you on the gardens list; a phone user who lives in one
garden had to open the list and tap in every time. Now the device
remembers the garden it was last in and `/` resumes there.
- lib/lastGarden.ts: per-device localStorage (pansy:last-garden), same
swallow-failures rationale as the seed tray / recents. getLastGardenId
guards against a non-positive/garbage stored value.
- The `/` route redirects to the stored garden when present, else /gardens.
- The editor records the garden on successful load, and — if it 404s
(deleted or access revoked) — forgets it (only if it's the stored one,
so a bad direct link can't wipe a good resume target) and bounces to the
list, so a stale id can't trap the user on an error screen. Transient
errors still show the retryable message.
- ApiError.isNotFound getter (mirrors isConflict/isUnauthorized).
Verified live at 390px: resume into the last garden; a stale id bounces to
/gardens and clears itself. tsc + vitest (incl. new lastGarden test) green.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ