Calendar event updates do not appear after save (3 e2e tests), plus a kind-flip that breaks replacement #62
Labels
No labels
bug
discussion
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
edufeed/edufeed-app#62
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Three e2e tests fail on the calendar update path, and they fail on content rather than on locators. Split out of #39, which was scoped to stale selectors.
Each test creates an event (works), opens the edit modal (works — the prefill test passes), changes one field, saves, waits 5 s, hard-navigates back to the same naddr URL, and waits 10 s more. The new value never arrives. Three independent tests, three different fields, one shape.
Found by TestOER while verifying #39 at
2d94d16b; the other 16 tests in those two files pass once the create-hub selector is disambiguated.What is not the cause — read from source, not reproduced
calendar-actions.svelte.js:updateEvent()looks correct on the two things that would obviously break replacement::138) and threaded intobuildCalendarEventTags, and the function throws rather than inventing one if it is missing. Same(kind, pubkey, d-tag)coordinate, so the naddr in the URL stays valid.:193-198).So this is not "the update silently never published" and not "editing forks the event to a new address", which were the first two candidates.
Candidates that remain
Neither of these can be settled by reading, and neither TestOER nor I have distinguished them:
gotore-hydrates from whatever the loader finds first.The distinguishing test is cheap: after a save, query the e2e strfry directly for
kinds:[31923]at that author + d-tag and count. Two events means the relay kept both; one event with the new title means the relay is fine and the client is showing something stale.A separate, real bug found while reading this
updateEvent()builds the replacement with:and
convertFormDataToEvent()(calendar.js:346,357) setskindto 31922 for date-based and 31923 for time-based events from the form state. So toggling all-day on an existing event changes the kind while keeping the d-tag.Under NIP-01 a replaceable event is addressed by
(kind, pubkey, d-tag)— a different kind is a different coordinate. The original event is therefore never replaced: you end up with two live events, and the naddr already in the URL (which encodes the old kind) keeps resolving to the stale one.Not reproduced — found by reading, and the three failing tests above do not touch the all-day toggle, so this is not their cause. Worth confirming and fixing on its own. The plausible fix is to delete the old coordinate when the kind flips, or to refuse the toggle on an existing event; which one is a product call.
Scope
Not a regression from #39 — those tests were already failing before it, behind the stale selectors, so nobody had seen this far down the flow. #39 fixed the selectors and made these three visible.
Root cause:
updateEventpublishes without ever writing the new version into the local store, so the stale cached copy short-circuits the relayDiagnosed by reading, on
dev@3a4b9e8c. This resolves both halves of the question — the relay is fine (TestOER measured one event at the coordinate carrying the new title), and the client never asks it.The asymmetry between create and update
eventStore?createEvent—calendar-actions.svelte.js:105publishEventOptimisticpublish-service.js:244updateEvent—calendar-actions.svelte.js:198publishEventpublish-service.js:133-235contains zeroeventStorereferencescalendar-actions.svelte.jsdoes not importeventStoreat all.persistEventsToCache(stores/event-cache.svelte.js:70) is wired toeventStore.insert$. So:CACHEABLE_KINDS,event-cache.svelte.js:27-28).fetchEventById→addressLoader→ step 1 is the cache (loaders/base.js:39-45passescacheRequest).addressPointerLoadingSequence(applesauce-loaders/dist/loaders/address-loader.js:57-63) removes a pointer fromremainingas soon as any event at that address comes back, and returns whenremainingis empty. A cache hit therefore ends the sequence — relay hints, extra relays and lookup relays are never tried.nostrUtils.js:243-246takesfirstValueFrom, so the stale event is the answer.That accounts for every observation, including the ones that look like counter-evidence: creation passes (nothing cached yet), the prefill test passes (it asserts the original title, which is exactly what the stale read returns), deletion passes.
Correction: the cache cannot hold both versions
The open question in the previous comment was whether IDB holds both versions and emits the older first. It cannot:
nostr-idbkeys addressable events by address, not id —getEventUIDreturnskind:pubkey:d(database/common.js:14-21).addEventsre-reads the existing record and only writesif (!existingEvents[uid] || event.created_at > existingEvents[uid])(database/insert.js:23).sortByDate= newest-first (utils.js:2,query-filter.js:281).So at most one version per address is stored, newest-wins. It holds the old one because the new one was never offered. Not an ordering bug — a missing write.
The fix
Make
updateEventdo what every other edit path already does.EditProfileModal.svelte:283andEditCommunityModal.svelte:299both calleventStore.add(signed)immediately afterpublishEvent; calendar is the outlier.Blast radius — same shape, not yet verified
Any update to an already-cached replaceable/addressable kind published through
publishEventwithout an accompanyingeventStore.addwill read back stale after a reload. Candidates worth checking, in descending order of likelihood:calendar-actions.svelte.js:279— kind 31924 calendar, cacheable, same file, no add.services/relay-settings-service.js:66— kind 10002, cacheable, noeventStorereference in the file.services/pin-list-service.js:102,135.The two profile/community modals are not affected — they add.
Related, and a separate defect
nostr-idb'sNostrIDB.add()only populates its in-memoryeventMapwhen the uid is absent (nostrdb/nostrdb.js:105), andloadEventsByUIDconsultseventMapbefore IDB (query-filter.js:264-268). So within a single page session, a second version at the same address never replaces the first in that map. Masked here by the fullpage.goto()reload, but it means "clear the cache" and "reload the page" are not interchangeable when testing this.Also correcting the issue text
These events are kind 31922, not 31923 — the tests fill
#startDateonly, soconvertFormDataToEventtakes the date-based branch (calendar.js:346). Akinds:[31923]query returns 0 for them and reads exactly like "never published".The all-day kind-flip bug already in this issue is unrelated to the three failures and still stands on its own.
Closed by #63, merged to
devatb82bfe9c.One part of this issue was not fixed and is now #65 — the all-day toggle flipping 31922 <-> 31923 while keeping the d-tag. It needs a product decision, so it is split out rather than left inside a closed issue.
Verification: TestOER ran the full
calendar-creation+calendar-editingset at961c3a82twice, 20 passed / 0 failed, first-attempt passes with 0 retries and 0 flaky,--workers=1on separate ports, HEAD confirmed before and after each run.The progression, for anyone reading this later:
3a4b9e8cbaseline9d2c309eeventStore.add485cab0d+created_atbump:187and:305pass first attempt961c3a82test scopingThe step worth remembering is the middle one: the
eventStore.addfix alone looked like it had helped, and it had not. What separated them was that the last failure changed shape —element(s) not foundbecamestrict mode violation— which is what proved the read had gone fresh while the assertion was still wrong.If you are retesting this issue by hand, do not test it with the all-day toggle — you will see the old symptom and it is a different bug (#65).
TestOER reproduced #65 in a browser at
b82bfe9c(twice): toggling all-day on an existing event changes the NIP-52 kind (31922 <-> 31923) while keeping the d-tag, so the app publishes to a different coordinate. The original event survives byte-identical, the naddr in the URL still resolves to it, and the page renders the pre-edit title with no error shown.That is the same user-visible symptom this issue describes — "my edit did nothing" — but no cache fix can touch it, because the edit genuinely went somewhere else.
To verify #62 is fixed, edit a title, a description, or a date without touching the all-day toggle. Those are the three paths the e2e tests cover, and they are green 20/0.
#65 is fixed separately in PR #66.