From d8003b11fb36eb6a8b262da197fa2fc06a9c844e Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Wed, 22 Jul 2026 00:53:43 -0400 Subject: [PATCH] Address review: key the "gone" message and the bounce off the same query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ --- web/src/pages/GardenEditorPage.tsx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/web/src/pages/GardenEditorPage.tsx b/web/src/pages/GardenEditorPage.tsx index f0a6a85..53aacad 100644 --- a/web/src/pages/GardenEditorPage.tsx +++ b/web/src/pages/GardenEditorPage.tsx @@ -57,6 +57,14 @@ export function GardenEditorPage() { const me = useMe() usePageTitle(full.data?.garden.name ?? 'Garden') + // The garden itself is gone (deleted, or access revoked) — keyed on the LIVE + // query, since whether the garden EXISTS doesn't depend on the season being + // viewed. Both the bounce effect and the "gone" render message read this one + // value, so the promise ("taking you to your gardens…") and the redirect can't + // disagree — e.g. a season-view error while the live garden is fine must not + // claim a redirect that never fires. + const gardenGone = live.error instanceof ApiError && live.error.isNotFound + const selectedId = useEditorStore((s) => s.selectedId) const select = useEditorStore((s) => s.select) const selectedPlantingId = useEditorStore((s) => s.selectedPlantingId) @@ -147,11 +155,11 @@ export function GardenEditorPage() { // this one, so a bad direct link can't wipe a good resume target) and bounce to // the list. Transient errors (500/network) fall through to the retryable screen. useEffect(() => { - if (live.isError && live.error instanceof ApiError && live.error.isNotFound) { + if (gardenGone) { forgetLastGarden(gid) navigate({ to: '/gardens' }) } - }, [live.isError, live.error, gid, navigate]) + }, [gardenGone, gid, navigate]) // If the focused object vanished — deleted, or a stale ?focus id on load — leave // focus mode so the canvas isn't stuck dimmed with no way out. @@ -277,13 +285,13 @@ export function GardenEditorPage() { if (full.isPending) return

Loading garden…

if (full.isError) { - // A not-found is handled by the effect above (forget + bounce to the list); - // say so rather than flashing a dead-end error during the redirect. - const gone = full.error instanceof ApiError && full.error.isNotFound + // Only promise the bounce when the GARDEN is gone (the effect above keys on + // the same gardenGone). A season-view error while the live garden is fine is a + // different, non-redirecting failure and gets the generic message. return (
- {gone + {gardenGone ? 'That garden is no longer available — taking you to your gardens…' : 'Could not load this garden.'}