Conversation
Drafts older than maxAgeMs (default 24h) are silently discarded on
restore and removed from localStorage, preventing stale drafts from
being recovered the next day.
Storage format updated to { data, savedAt } to track draft age.
https://claude.ai/code/session_01DroMmmZbjEwG2WV4N2QM7S
Greptile SummaryThis PR restricts draft recovery to an 8-hour window by wrapping persisted values in a Confidence Score: 5/5Safe to merge — logic is correct, backward compatibility is handled, and all new paths are covered by tests Both issues from prior review rounds are fully resolved: the type guard prevents old-format drafts from setting wasRestored, and the age check correctly expires stale drafts. No P0/P1 findings remain. No files require special attention Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A([Mount]) --> B[localStorage.getItem key]
B --> C{saved exists?}
C -- No --> Z([done, wasRestored=false])
C -- Yes --> D[JSON.parse saved]
D --> E{Parse error?}
E -- Yes --> F[console.error]
F --> Z
E -- No --> G{Type guard:\nobject, savedAt:number,\ndata truthy?}
G -- Fails --> H[removeItem key]
H --> Z
G -- Passes --> I{age = Date.now - savedAt\nage > maxAgeMs?}
I -- Yes --> J[removeItem key]
J --> Z
I -- No --> K[form.reset draft.data]
K --> L([wasRestored=true])
Reviews (2): Last reviewed commit: "Guard against old-format drafts missing ..." | Re-trigger Greptile |
| import { useEffect, useRef, useState } from "react"; | ||
| import { FieldValues, UseFormReturn } from "react-hook-form"; | ||
|
|
||
| const DEFAULT_MAX_AGE_MS = 8 * 60 * 60 * 1000; // 8 hours |
There was a problem hiding this comment.
Default expiry is 8 h, but PR says 24 h
DEFAULT_MAX_AGE_MS resolves to 28,800,000 ms (8 hours), yet the PR title is "Restrict draft recovery to 24-hour window" and the description states "default 24h". If the intended ceiling really is 24 hours, the constant should be 24 * 60 * 60 * 1000; if 8 hours was intentional, the PR title and description are misleading.
| const DEFAULT_MAX_AGE_MS = 8 * 60 * 60 * 1000; // 8 hours | |
| const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours |
There was a problem hiding this comment.
8 hours is intentional — the user explicitly requested it after the initial 24h implementation. PR title and description have been updated to reflect the correct 8-hour default.
Generated by Claude Code
| const parsed: PersistedDraft<T> = JSON.parse(saved); | ||
| const age = Date.now() - parsed.savedAt; | ||
| if (age > maxAgeMs) { | ||
| localStorage.removeItem(key); | ||
| return; | ||
| } | ||
| form.reset(parsed.data); | ||
| setWasRestored(true); |
There was a problem hiding this comment.
Old-format drafts silently corrupt
wasRestored state
Any draft saved by the previous version of the hook (plain JSON.stringify(values), no savedAt key) will reach this path with parsed.savedAt === undefined. Date.now() - undefined evaluates to NaN, and NaN > maxAgeMs is always false, so the age guard never fires. Execution continues to form.reset(parsed.data) where parsed.data is also undefined — React Hook Form resets the form to its defaultValues. Then setWasRestored(true) is called, triggering the "Draft restored" toast in home.tsx even though the form shows default values. The stale old-format key is never removed from localStorage, so this misfires on every subsequent page load.
Add a type guard before the age check to discard and remove the old format:
if (
typeof parsed !== "object" ||
parsed === null ||
typeof parsed.savedAt !== "number" ||
!parsed.data
) {
localStorage.removeItem(key);
return;
}There was a problem hiding this comment.
Good catch. Fixed in e250bbe — a type guard now validates the stored value has a numeric savedAt and a data field before proceeding. Anything missing either is removed from localStorage and skipped, so old-format drafts no longer trigger a false wasRestored. A dedicated test case covers this path.
Generated by Claude Code
Drafts written by the previous hook version (plain JSON values, no savedAt) would pass the age check (NaN > maxAgeMs is false), reset the form to undefined data, and incorrectly set wasRestored=true. Now any stored value that lacks a numeric savedAt or a data field is treated as invalid: it is removed from localStorage and recovery is skipped. https://claude.ai/code/session_01DroMmmZbjEwG2WV4N2QM7S
|
All done. Here's a summary of what was addressed: |
Drafts older than
maxAgeMs(default 8 hours) are silently discarded on restore and removed from localStorage, preventing stale drafts from being recovered the next day.Storage format updated to
{ data, savedAt }to track draft age. Old-format drafts (plain JSON values withoutsavedAt) are also discarded and cleaned up on first load.https://claude.ai/code/session_01DroMmmZbjEwG2WV4N2QM7S