Repository navigation
Legacy saved events are never upgraded to event_id identity, so normal title edits can strand valid bookmarks #173
Description
Problem
The #164 fix added eventId?: number to saved-event records and correctly uses it as the stable identity for new saves:
export interface SavedEvent {
uid: string
title: string
savedAt: number
eventId?: number
}
export function savedEventMatches(saved: SavedEvent, event: EventItem): boolean {
if (saved.uid !== event.uid) return false
return saved.eventId !== undefined ? saved.eventId === event.event_id : saved.title === event.title
}But existing production bookmarks created before PR #168 contain only uid, title, and savedAt. There is no migration that upgrades those legacy records with the current event's stable event_id when identity can be established.
For legacy records, savedEventMatches() falls back to exact title equality. This means a completely normal upstream title correction/rename to the same event causes the bookmark to be treated as missing/reused even when the current event still has the same UID and stable numeric event_id.
The user also cannot naturally repair the bookmark through the normal save flow. useSavedEvents.save() is a no-op whenever the UID already exists:
const next = savedEventsRef.current.some((item) => item.uid === event.uid)
? savedEventsRef.current
: [{ uid: event.uid, title: event.title, eventId: event.event_id, savedAt: Date.now() }, ...savedEventsRef.current]So selecting the still-valid current event and attempting to save it again does not upgrade the legacy record with eventId. The old record stays permanently on the conservative title-based fallback until the user explicitly removes it first.
This is distinct from #164. #164 prevented a reused UID from silently transferring a bookmark to an unrelated event. This issue is the migration consequence of that fix for legitimate pre-#168 bookmarks.
Reproduction
Current deployed/master SHA: b7ff67f22a8c9d58ee4d05010396f64651da1062.
- Seed storage with a bookmark produced by the pre-Fix all remaining open issues (#100, #160–#167, #169) #168 app:
{
"uid": "event-123",
"title": "Morning Coffee",
"savedAt": 1234567890
}- Load current data containing the same actual event with:
uid = "event-123"- stable
event_id = 42 - title corrected to
"Morning Coffee Meetup"
- Open Events -> Saved.
savedEventMatches()has nosaved.eventId, so it compares exact titles and returns false.- The event is rendered as stale/unavailable instead of the same saved event with its updated title.
- Navigate to the live event and press its save/bookmark control again.
useSavedEvents.save()sees the UID already exists and commits the unchanged legacy record, soeventId: 42is never added.- Reload. The bookmark remains stranded as stale.
Impact
- Existing users can lose access to valid saved events after harmless upstream title corrections.
- The new stable-identity model only protects bookmarks created after PR Fix all remaining open issues (#100, #160–#167, #169) #168; old bookmarks remain tied to mutable display text indefinitely.
- A user cannot repair an affected bookmark by re-saving the live event because the storage layer refuses to refresh an existing UID record.
- The conservative anti-UID-reuse behavior is correct, but without migration it creates false-positive stale bookmarks for the installed user base.
Suggested fix
Add a safe legacy-upgrade path rather than leaving old bookmarks permanently title-bound.
Possible approaches:
- During reconciliation, if a legacy record has no
eventIdand its UID + stored title matches the current event, atomically persist the currentevent.event_idinto that saved record. Future title changes then use stable identity. - Make
save(event)upgrade an existing legacy record when its current identity is safe to establish, instead of unconditionally treating an existing UID as a no-op. - If a legacy record's title no longer matches, keep the conservative stale state; do not guess identity solely from UID after the mismatch has already occurred.
The migration must preserve #164's protection against actual UID reuse.
Acceptance criteria
- A legacy
{uid,title,savedAt}bookmark that still matches the current event is upgraded once to include its stableevent_id. - After that upgrade, a later title change to the same
event_iddoes not make the bookmark stale. - A legacy bookmark whose title already differs from the current UID is not automatically rebound to that event.
- Re-saving a safely matched legacy bookmark can upgrade it instead of remaining a no-op.
- Migration is idempotent and preserves
savedAtunless an intentional policy says otherwise. - New-format bookmarks continue using
event_idand remain protected from UID reuse. - Add regression coverage for: legacy exact-title match -> upgrade -> later title edit; legacy mismatched title -> no upgrade; new-format same event/title edit; and true UID reuse with different
event_id.
Activity