Skip to content

Legacy saved events are never upgraded to event_id identity, so normal title edits can strand valid bookmarks #173

Open

Description

@lnorton89

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.

  1. 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
}
  1. Load current data containing the same actual event with:
    • uid = "event-123"
    • stable event_id = 42
    • title corrected to "Morning Coffee Meetup"
  2. Open Events -> Saved.
  3. savedEventMatches() has no saved.eventId, so it compares exact titles and returns false.
  4. The event is rendered as stale/unavailable instead of the same saved event with its updated title.
  5. Navigate to the live event and press its save/bookmark control again.
  6. useSavedEvents.save() sees the UID already exists and commits the unchanged legacy record, so eventId: 42 is never added.
  7. 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:

  1. During reconciliation, if a legacy record has no eventId and its UID + stored title matches the current event, atomically persist the current event.event_id into that saved record. Future title changes then use stable identity.
  2. 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.
  3. 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 stable event_id.
  • After that upgrade, a later title change to the same event_id does 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 savedAt unless an intentional policy says otherwise.
  • New-format bookmarks continue using event_id and 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions