Resume the last garden on this device (#97)
`/` 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
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
// The last garden opened on THIS device, so a returning user resumes where they
|
||||
// were instead of always landing on the gardens list. Per-device, in
|
||||
// localStorage (same rationale as the seed tray and PlantPicker recents): a
|
||||
// convenience, not authoritative state — a quota/availability failure is
|
||||
// swallowed, and a stored id that no longer loads clears itself (see the editor).
|
||||
//
|
||||
// Only the id is stored. The garden is resolved by the editor on load; if it's
|
||||
// gone (deleted, or access revoked), forgetLastGarden() drops it so `/` stops
|
||||
// resuming a garden that can't open.
|
||||
|
||||
const KEY = 'pansy:last-garden'
|
||||
|
||||
/** The last-opened garden id on this device, or null if none/unparseable. */
|
||||
export function getLastGardenId(): number | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(KEY)
|
||||
if (raw == null) return null
|
||||
const n = Number(raw)
|
||||
return Number.isInteger(n) && n > 0 ? n : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** Record the garden the device is now in, so `/` resumes here next time. */
|
||||
export function rememberLastGarden(id: number): void {
|
||||
try {
|
||||
localStorage.setItem(KEY, String(id))
|
||||
} catch {
|
||||
// Resume is a convenience; ignore quota/availability failures.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the stored last garden. With `onlyIfEquals`, clears only when the stored
|
||||
* id matches — so a 404 on a directly-linked garden can't wipe a different, still
|
||||
* good resume target the user had.
|
||||
*/
|
||||
export function forgetLastGarden(onlyIfEquals?: number): void {
|
||||
try {
|
||||
if (onlyIfEquals != null && getLastGardenId() !== onlyIfEquals) return
|
||||
localStorage.removeItem(KEY)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user