Skip to content

fix(ui): decode UpdateNotification(Delta) as UpdateInbox#104

Merged
iduartgomez merged 3 commits intomainfrom
fix/update-notification-decode
May 4, 2026
Merged

fix(ui): decode UpdateNotification(Delta) as UpdateInbox#104
iduartgomez merged 3 commits intomainfrom
fix/update-notification-decode

Conversation

@iduartgomez
Copy link
Copy Markdown
Contributor

Summary

api.rs:1085 was unwrapping serde_json::from_slice::<StoredInbox>(delta) on the wire payload of an inbox UpdateNotification. The sender writes UpdateInbox::AddMessages (an enum), not the full Inbox struct, so the decode panicked with `missing field "messages"` and the executor task died. The visible symptom: cross-node sends required a hard refresh on the receiver to surface, and reverse-direction sends never surfaced because the panic killed the WS task before the second arm could run.

Add `InboxModel::apply_delta` that handles `AddMessages` (decrypt + push, de-dupe by `assignment_hash`) and `RemoveMessages` (filter by hash). Drop the now-dead `merge` helper and the disabled "Unarchive" affordance (#60 is still pending; the dead button was confusing per user feedback).

Browser console error this fixes:

```
WASM PANIC: panicked at ui/src/api.rs:1085:76:
called `Result::unwrap()` on an `Err` value: Error("missing field `messages`", line: 1, column: 31)
```

Caveat — cross-node E2E still flaky

The new `multi-round + read + archive` test (and the pre-existing `alice → bob` test from #94) reproduce a deeper cross-node delivery flake on the iso harness even with this decode fix in place. Both are gated behind `FREENET_LIVE_E2E_SEND=1`. Manual repro on a real browser does work post-fix; iso harness coverage is being chased separately.

Test plan

  • `cargo make clippy` — clean
  • `cargo test --workspace --lib` — 26/26 passing (incl. the testid-drift guard)
  • `cargo make test-e2e-real-node` — 1/1 passing (cross-node send tests gated, identity-create + reload-persist passes)
  • Cross-node send end-to-end: manual repro green, iso harness flake tracked separately

api.rs:1085 was unwrapping `serde_json::from_slice::<StoredInbox>(delta)`
on the wire payload of an inbox `UpdateNotification`. The sender writes
`UpdateInbox::AddMessages` (an enum), not the full `Inbox` struct, so
the decode panicked with `missing field "messages"` and the executor
died. The only path that surfaced an incoming cross-node message was a
hard refresh that re-issued a `Get` and got a fresh `StoredInbox` via
`UpdateData::State`. Reverse-direction sends never surfaced at all
because the panic killed the WS task before the second arm could run.

Add `InboxModel::apply_delta` that handles `AddMessages` (decrypt + push,
de-dupe by `assignment_hash`) and `RemoveMessages` (filter by hash).
Drop the now-dead `merge` helper.

Also flip the cross-node send test's `FREENET_LIVE_E2E_SEND` gate off
(panic was the root cause of the harness flake we were debugging),
and hide the disabled "Unarchive" button (#60 still pending; the dead
affordance was confusing per user feedback).

Add a multi-round live-node test exercising send → click-to-read →
reply → archive with a console-panic assertion that fails fast on
WASM unwrap regressions.
Focused Playwright spec that drives the iso 2-node harness with a
verified contact import, captures both browser consoles, and probes
alice's Drafts immediately post-Send (#107) plus bob's inbox-after-
click flow (#106). Not part of CI; runs manually against an iso
harness with the contract URL in FREENET_EMAIL_BASE_URL.

Current findings:
- #107 (drafts/sent leak) does NOT reproduce on a clean post-#104
  iso run. \`delete_draft_now\` runs to completion on the verified-
  send success path; alice's Drafts is empty.
- #106 (disappear-on-click) is blocked: bob's inbox never gets the
  message in the harness even with verified contact + AFT auto-allow
  (#105). Bob can't click a row that doesn't exist.

Spec is structured so #106/#107 will fall out automatically once #105
is unblocked enough to deliver across the iso harness.
@iduartgomez iduartgomez merged commit a1e2b0e into main May 4, 2026
5 checks passed
iduartgomez added a commit that referenced this pull request May 4, 2026
…108)

* fix(ui): make local_state GENERATION reactive + tombstone draft deletes

Two related local_state correctness bugs surfaced during cross-node
manual testing post-#102/#104:

#106 — clicking an inbox row removed it from the list. `mark_as_read`
moves the row from the live `messages` Vec into the per-alias `kept`
HashMap and bumps `GENERATION`, but `GENERATION` was an `AtomicUsize`
and components called `.load(Relaxed)` on it. Atomic loads do NOT
register a Dioxus reactive dep, so the kept-merge loop in `MessageList`
did not re-run after the bump and the row stayed gone until something
else dirtied the component. Switched `GENERATION` to a `GlobalSignal<usize>`;
readers use `GENERATION()` to subscribe properly.

#107 — sent messages re-appeared in Drafts. `delete_draft_now` removes
the draft locally and asynchronously asks the delegate to delete. If a
debounced `save_draft` was already past the autosave-token guard and
in-flight when Send fires, its delegate-echoed snapshot (with the draft
still present) lands AFTER the local delete and `replace_snapshot`
overwrites the optimistic delete. Added a `DELETED_DRAFTS` tombstone
set keyed by `(alias, draft_id)`; `replace_snapshot` strips matching
ids from incoming snapshots and clears the tombstone once the delegate
echo no longer contains the id.

* fmt
iduartgomez added a commit that referenced this pull request May 4, 2026
Cross-node send fails on iso harness because both contact imports
left `verified: false`, and `verify_on_send` defaults to true. The
send_msg path silently rejects (toast: "Recipient is not verified")
so bob never receives — root cause of the harness-only failure
tracked in #105.

The repro spec added in #104 already had this fix; live-node.spec.ts
just hadn't been updated. Tick `fm-verify-check` after filling the
local label so the import lands verified.
iduartgomez added a commit that referenced this pull request May 4, 2026
Cross-node send fails on iso harness because both contact imports
left `verified: false`, and `verify_on_send` defaults to true. The
send_msg path silently rejects (toast: "Recipient is not verified")
so bob never receives — root cause of the harness-only failure
tracked in #105.

The repro spec added in #104 already had this fix; live-node.spec.ts
just hadn't been updated. Tick `fm-verify-check` after filling the
local label so the import lands verified.
iduartgomez added a commit that referenced this pull request May 4, 2026
* test(e2e): tick verify checkbox on contact import (#105)

Cross-node send fails on iso harness because both contact imports
left `verified: false`, and `verify_on_send` defaults to true. The
send_msg path silently rejects (toast: "Recipient is not verified")
so bob never receives — root cause of the harness-only failure
tracked in #105.

The repro spec added in #104 already had this fix; live-node.spec.ts
just hadn't been updated. Tick `fm-verify-check` after filling the
local label so the import lands verified.

* docs(qa): add manual-test inventory + freenet-mail-qa skill

`docs/qa/manual-test-inventory.md` catalogs every behavior across
9 feature areas (Identity, Contacts, Inbox, Compose, Drafts, Sent,
Archive, Settings, Layout, Network) with a status of `auto` /
`manual` / `automatable` / `blocked` and either a link to the
covering test or a manual repro recipe.

`.claude/skills/freenet-mail-qa/SKILL.md` instructs agents on when
to consult the matrix (pre-merge, post-bug-fix, pre-release) and
how to update it when tests are added.

AGENTS.md gains a pointer at the top of the End-to-end testing
section so this is discoverable without explicit invocation.

Surfaced gaps for future automation:
- multi-recipient send
- verify_on_send toast on unverified contact
- search filter
- auto-sign idempotent on Resend
- settings round-trip across reload
- backup → restore round-trip
- 3+ identity switching
- reply from Archive folder
- Sent Pending → Failed on send error (needs failure-injection harness)
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