Repro
FREENET_LIVE_E2E_SEND=1 cargo make test-e2e-real-node
Test 3 (live-node.spec.ts:343 — multi-round + read + archive across nodes) fails at line 478:
Error: round one stays visible after click-to-read
Locator: locator('iframe#app').contentFrame().getByText(/round one/i)
Expected: visible
Timeout: 10000ms
Error: element(s) not found
Flow that fails
- alice@gw → bob@peer: 'round one' (delivered, asserted visible)
- bob clicks the row → opens detail (asserted visible via
fm-detail-time)
- bob navigates back to inbox list
- Expected: row still visible,
read=true
- Actual: row gone
Suspected root cause
ui/src/app.rs:1191-1214 rebuilds the inbox list by merging contract messages with local_state::kept_for(&alias) (rows the contract evicted but the user has read). On click-to-read:
local_mark_read (local_state.rs:445) bumps the local-state delegate.
- The contract's copy of the message gets evicted from the live
emails list (read-window scrubbing in inbox contract).
kept_for should resurface it as a kept row (read=true).
Either:
local_mark_read isn't producing a KeptMessage entry in SNAPSHOT before the rebuild fires, OR
- The rebuild reads
SNAPSHOT before the delegate's echo lands, OR
- Cross-node specific: contract eviction races the local-state delegate write so the row falls out of both sets.
Single-node manual flow (offline example-data,no-sync) doesn't exercise this — Playwright email-app.spec.ts doesn't click-to-read.
Why it matters for #81
Issue #81 wants FREENET_LIVE_E2E_SEND gate flipped off so cross-node delivery is a release blocker. The alice → bob test passes (modulo a separate too-greedy log regex — see #81 followup). Multi-round test 3 fails on this real product bug. Gate cannot flip until this is fixed, otherwise tag releases break.
Proposal
- Trace the
kept_for rebuild race: log when local_mark_read writes the kept entry vs when the rebuild reads it.
- Likely fix: make
mark_read synchronously add to SNAPSHOT (optimistic write) before awaiting the delegate echo, the way drafts are handled.
- Add a unit test on the offline path (mock contract evicting a read message) that pins
kept_for resurrection.
Out of scope
alice → bob log-grep regex over-matches webapp re-publish noise. Fix in #81.
Repro
FREENET_LIVE_E2E_SEND=1 cargo make test-e2e-real-nodeTest 3 (
live-node.spec.ts:343— multi-round + read + archive across nodes) fails at line 478:Flow that fails
fm-detail-time)read=trueSuspected root cause
ui/src/app.rs:1191-1214rebuilds the inbox list by merging contract messages withlocal_state::kept_for(&alias)(rows the contract evicted but the user has read). On click-to-read:local_mark_read(local_state.rs:445) bumps the local-state delegate.emailslist (read-window scrubbing in inbox contract).kept_forshould resurface it as a kept row (read=true).Either:
local_mark_readisn't producing aKeptMessageentry inSNAPSHOTbefore the rebuild fires, ORSNAPSHOTbefore the delegate's echo lands, ORSingle-node manual flow (offline
example-data,no-sync) doesn't exercise this — Playwrightemail-app.spec.tsdoesn't click-to-read.Why it matters for #81
Issue #81 wants
FREENET_LIVE_E2E_SENDgate flipped off so cross-node delivery is a release blocker. Thealice → bobtest passes (modulo a separate too-greedy log regex — see #81 followup). Multi-round test 3 fails on this real product bug. Gate cannot flip until this is fixed, otherwise tag releases break.Proposal
kept_forrebuild race: log whenlocal_mark_readwrites the kept entry vs when the rebuild reads it.mark_readsynchronously add toSNAPSHOT(optimistic write) before awaiting the delegate echo, the way drafts are handled.kept_forresurrection.Out of scope
alice → boblog-grep regex over-matches webapp re-publish noise. Fix in #81.