Skip to content

fix: keep saved events when their feed stops carrying them - #15

Merged
hhkaos merged 2 commits into
mainfrom
feat/saved-event-snapshots
Nov 5, 2026
Merged

hhkaos merged 2 commits into
mainfrom
feat/saved-event-snapshots

Conversation

@hhkaos

@hhkaos hhkaos commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Closes the last reader-side item in #13 — and the premise there was wrong, which is the finding.

The reader already pruned, silently

boardEvents() resolved every {feedUrl, eventId} against the live feed:

return allEvents().filter(function (event) {
  return refs.some(function (item) { return sameRef(item, eventRef(event)); });
});

So a saved event whose publisher stopped carrying it left the collection
without a word, and the pointer stayed in localStorage aimed at nothing.
Reproduced before fixing: two events in Favorites, the publisher removes one,
Actualizar → Favorites · 1 evento, no message. Unsubscribing from a feed did
the same to every saved event from it.

And it did this for any absence — aged out, cancelled, recycled id,
truncated response, changed URL. None of those is "withdrawn".

Who it hits: eventos.wiki publishes 485 events and Sarao 180 — publishers who
prune are exactly these. The collection that suffers most is "Mis charlas",
which by definition accumulates past talks.

What changed

  • A collection holds the event, not a pointer. The live event still wins
    while the feed carries it (it may have been edited); the saved copy is the
    fallback instead of nothing. Pre-existing pointers are backfilled from the
    feed as it loads.
  • Mark, never remove. With the card no longer vanishing there is something
    to mark, and covers decides whether the absence means anything: inside the
    window a feed declares complete, "Retirado del feed"; outside it, or without
    covers, nothing. A feed still loading, failed, or truncated claims nothing
    either. Events from an unsubscribed feed get "Feed no suscrito".
  • Cleanup in Ajustes, with a count and a confirmation, scoped to what can
    no longer be followed: unsubscribed feeds, withdrawn events, and
    pre-snapshot pointers with nothing left to point at.

Two UX calls, both deliberate

No "remove past events". A saved past event is the only history this
reader has — the feed no longer carries it — and it is the one a reader asked
for by name. "Solo futuros" already keeps them out of lists, which is a view,
not a deletion.

No preference toggle. Whether an absence means removal depends on each
feed's covers, which the reader cannot see. A setting here would hand the
user a decision the data already answers.

Verification

End to end in a browser: legacy pointers backfill; a pruned event survives and
is badged only when covers covers its date; an event before covers.from
survives unbadged; unsubscribing badges all three "Feed no suscrito"; the
cleanup removes exactly those and the summary goes empty; saving a new event
through the picker writes a snapshot with savedAt.

Aside, filed while testing: a value outside the attendanceMode enum makes <ote-events> throw and render an empty widget rather than degrading. My test data was invalid, but one malformed event blanking the whole list is worth reporting upstream.

🤖 Generated with Claude Code

hhkaos and others added 2 commits November 5, 2026 11:05
A collection stored {feedUrl, eventId} and resolved it against the live
feed on every render, so the day a publisher pruned an event it left the
collection without a word — and the pointer stayed in localStorage,
aimed at nothing. Unsubscribing from a feed did the same thing to every
saved event from it. Reproduced before fixing: two events in Favorites,
publisher removes one, Actualizar, "Favorites · 1 evento", no message.

The feeds this hits are the ones that prune: eventos.wiki publishes 485
events and Sarao 180. The collection that suffers most is "Mis charlas",
which by definition accumulates past talks.

So a collection now holds the event, not a pointer to it. The live event
still wins while the feed carries it — it may have been edited — and what
was saved is the fallback rather than nothing. Collections saved before
this are backfilled from the feed as it loads.

With the card no longer vanishing there is something left to mark, which
is where covers earns its keep: inside the window a feed declares itself
complete for, an absent event has been withdrawn and is badged as such;
outside it, or in a feed that declares no covers, nothing is claimed and
nothing is said. A feed that is still loading, failed, or returned a
truncated response claims nothing either — absence on its own is not a
statement.

Ajustes gains a cleanup for what can no longer be followed at all:
unsubscribed feeds, withdrawn events, and pre-snapshot pointers with
nothing left to point at. Not past events — a saved past event is the
only history this reader has, since the feed no longer carries it, and
"Solo futuros" already hides them from lists without deleting anything.
No preference toggle either: whether an absence means removal depends on
each feed's covers, which the reader cannot see.

Verified in a browser end to end: legacy pointers backfill, a pruned
event survives and is badged only when covers says so, an event before
covers.from survives unbadged, unsubscribing badges all three as
"Feed no suscrito", the cleanup removes exactly those and the summary
goes empty, and saving a new event through the picker writes a snapshot.

Refs #13

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…shots

# Conflicts:
#	CHANGELOG.md
#	app.js
#	package.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant