Why a batch of user-facing bugs (#286, #287) got past CI
Testing (Ivvor, 2026-06-01) surfaced sent→Drafts, deleted-returns, and one-sided-threads bugs against fresh nodes. None were caught by the suite. This issue documents why and what to change. Not a feature bug — a coverage/process bug.
Root causes
1. Offline suite has no persistence layer — can't catch round-trip bugs
example-data,no-sync keeps messages in an in-memory inbox.messages borrow (ui/src/app.rs). On page.reload() the WASM app restarts, memory wipes, and load_example_messages reseeds from scratch — there is no localStorage/IndexedDB/contract round-trip. Logout even explicitly .clear()s the cache to force a reseed.
Consequence: every offline page.reload() test asserts identity-list survival only (#36). A sent/deleted/draft message cannot be meaningfully re-asserted after reload offline. Sent→Drafts and deleted-returns are contract-state round-trip bugs and live exactly where offline is blind.
2. Iso (real-node) suite reloads but never re-asserts folder state
live-node.spec.ts is the only suite with real contracts. It reloads in two places — create identity → reload persists (asserts identity row) and multi-round + read + archive (asserts round one stays visible, the #113 read-state regression). Neither checks the Sent folder still holds a sent message, Drafts is empty after reload, or a deleted message stays deleted.
3. QA matrix marked uncovered behaviors as auto (false-greens)
4. Known data-model weakness documented, not tracked
live-node.spec.ts documents that a sent message is 'stashed only to the Sent folder — never folded back into the sender's own inbox Vec' and each side's inbox holds 'exactly ONE half of the conversation' — the root of one-sided-threads (#287), filed as an inline known-limitation annotation rather than a tracked issue.
What changed already (this sweep)
Remaining work
Tests (iso — the only place these reproduce)
Offline
Process
Related
Why a batch of user-facing bugs (#286, #287) got past CI
Testing (Ivvor, 2026-06-01) surfaced sent→Drafts, deleted-returns, and one-sided-threads bugs against fresh nodes. None were caught by the suite. This issue documents why and what to change. Not a feature bug — a coverage/process bug.
Root causes
1. Offline suite has no persistence layer — can't catch round-trip bugs
example-data,no-synckeeps messages in an in-memoryinbox.messagesborrow (ui/src/app.rs). Onpage.reload()the WASM app restarts, memory wipes, andload_example_messagesreseeds from scratch — there is no localStorage/IndexedDB/contract round-trip. Logout even explicitly.clear()s the cache to force a reseed.Consequence: every offline
page.reload()test asserts identity-list survival only (#36). A sent/deleted/draft message cannot be meaningfully re-asserted after reload offline. Sent→Drafts and deleted-returns are contract-state round-trip bugs and live exactly where offline is blind.2. Iso (real-node) suite reloads but never re-asserts folder state
live-node.spec.tsis the only suite with real contracts. It reloads in two places —create identity → reload persists(asserts identity row) andmulti-round + read + archive(assertsround one stays visible, the #113 read-state regression). Neither checks the Sent folder still holds a sent message, Drafts is empty after reload, or a deleted message stays deleted.3. QA matrix marked uncovered behaviors as
auto(false-greens)auto, evidence 'implicit via Send removes the draft'. Reality: that test only checks the draft row vanishes in-session, no reload;repro-106-107.spec.ts(Sent messages leaking into Drafts folder after cross-node send #107) asserts 'the draft persists' — the inverse — and contains no reload. The row described coverage that did not exist. This was precisely Ivvor's sent→Drafts bug, marked green.auto (implicit / covered transitively), unlinked.4. Known data-model weakness documented, not tracked
live-node.spec.tsdocuments that a sent message is 'stashed only to the Sent folder — never folded back into the sender's own inbox Vec' and each side's inbox holds 'exactly ONE half of the conversation' — the root of one-sided-threads (#287), filed as an inlineknown-limitationannotation rather than a tracked issue.What changed already (this sweep)
docs/qa/manual-test-inventory.md): added §"Persistence is iso-only" + the rule that across-reloadautorequires a real iso reload test; split sent/delete rows into honest in-session vs across-reload; removed implicit/transitive false-greens.sent message stays in Sent (not Drafts) across reload (#286)— sends, reloads, asserts Sent survives + Drafts empty.Remaining work
Tests (iso — the only place these reproduce)
live-node.spec.ts→deleted message stays deleted across reload (#286).test.fixmeinlive-node.spec.ts; blocked on wiring the cross-node delivery step)threads.spec.ts→Legacy two-party thread shows both sides (#287). (iso multi-arrival fold still deferred — see comment.)Offline
email-app.spec.ts→send routes the message into Sent, not Drafts (#291).Process
autowithout a linked literaltest("…")title; audit matrix for remaining false-greens.freenet-mail-qaskill: any folder-mutating behavior (send/delete/archive/draft) requires a reload-and-reassert iso variant before its row may beauto.live-node.spec.tsknown-limitationannotation into a link to Threaded/nested view only shows one side of the conversation #287. — done as PR test(qa): offline send-routing guard + refresh stale thread annotations (#291) #304: Threaded/nested view only shows one side of the conversation #287 is now closed, so the annotation was rewritten to reflect the resolution (correct-by-design) rather than pointing at a closed issue as a live limitation. The staleemail-app.spec.tsannotation got the same refresh.Related