Skip to content

fix(web): toast + log on handler failures#443

Merged
intendednull merged 1 commit into
claude/adoring-euler-DvNnkfrom
auto-fix/issue-350-handler-toasts
Apr 27, 2026
Merged

fix(web): toast + log on handler failures#443
intendednull merged 1 commit into
claude/adoring-euler-DvNnkfrom
auto-fix/issue-350-handler-toasts

Conversation

@intendednull
Copy link
Copy Markdown
Owner

Summary

Handler closures in crates/web/src/handlers.rs ate every async error
via let _ = h.<action>(...).await. Typed message vanish from input,
no log, no toast. Refs #350 [GEN-07].

New warn_and_toast_with(action, e, toasts) helper:

  • tracing::warn!(error = ?e, action, "ui handler failed")
  • Push Toast::err("Couldn't {action}. Try again.") with per-action
    dedup key — back-to-back fail coalesce, no stack pile-up.
  • Mirror lazy use_context::<ToastStack>() pattern from PR fix(web): busy gate + toast on mark-read #411.

Each handler grabs toast stack on synchronous frame (reactive owner
intact) before spawn_local, then forwards into helper from async
path. wasm_bindgen_futures::spawn_local strip owner — context lookup
must happen earlier.

Sites patched (7)

  • send_message
  • send_reply
  • edit_message
  • delete_message
  • react
  • pin_message
  • unpin_message

switch_channel / switch_server return () — nothing to swallow,
left as-is.

Overlap with PR #411

PR #411 toast inbound mark-read fail in sync_queue_view.rs. Different
file, different action namespace (mark as read vs send message etc.),
different dedup keys. No double-toast.

Verify

rg "let _ = h\." crates/web/src/handlers.rs

Empty.

Test plan

  • cargo fmt --check clean
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo check -p willow-web --target wasm32-unknown-unknown clean
  • cargo test -p willow-web --lib 72 pass
  • cargo test --workspace --exclude willow-web all green
  • Browser test handler_error_toasts::* (3 cases: happy-path,
    dedup coalesce, no-stack no-panic) compile clean. Firefox +
    wasm-pack not on this box — browser tier runs in CI.

Follow-ups

None. switch_channel/switch_server return (); if they grow a
fallible variant later, plumb same helper.

https://claude.ai/code/session_016cmtqT7yEQUgjcLgz4pARP


Generated by Claude Code

Handler closures in `crates/web/src/handlers.rs` swallowed every async
error with `let _ = h.<action>(...).await`. Send/edit/delete/react/
pin/unpin failures vanished — typed message disappeared from input,
no log, no toast. Refs #350 [GEN-07].

New `warn_and_toast_with(action, e, toasts)` helper:
- `tracing::warn!(error = ?e, action, "ui handler failed")`
- Pushes `Toast::err("Couldn't {action}. Try again.")` with a per-
  action `dedup` key so back-to-back failures coalesce instead of
  stacking. Mirrors the lazy `use_context::<ToastStack>()` pattern
  PR #411 introduced for the inbound mark-read button.

Each handler now captures the toast stack on the synchronous frame
(reactive owner intact) before `spawn_local`, then forwards into the
helper from the async path — `wasm_bindgen_futures::spawn_local`
strips the owner, so context lookup must happen earlier.

7 sites patched: send_message, send_reply, edit_message, delete_
message, react, pin_message, unpin_message. switch_channel /
switch_server stay as-is — they return `()` so there was nothing to
swallow.

Browser test `handler_error_toasts::*` covers helper happy-path,
dedup coalescing, and missing-stack no-panic. Test exercises the
exact production code path; the handler closures themselves are
pinned to ClientHandle<IrohNetwork> with no trait seam to inject a
failing double, so going one level deeper would buy nothing.

`rg "let _ = h\." crates/web/src/handlers.rs` returns empty.
fmt + clippy + native tests + wasm check all green; browser tier
verified locally via `cargo check -p willow-web --tests` + manual
review of the test mod (Firefox/wasm-pack not on this box, browser
suite runs in CI).
@intendednull intendednull merged commit 5a5d1df into claude/adoring-euler-DvNnk Apr 27, 2026
intendednull added a commit that referenced this pull request Apr 28, 2026
…480)

PR #443 closed #350 by routing client-mutation errors in handlers.rs through warn_and_toast / warn_and_toast_with so the user sees a toast instead of the action silently vanishing. The pattern was not propagated to component-internal handlers — 11 sites across roles.rs, settings.rs, sync_queue_view.rs, and channel_sidebar.rs still discarded the anyhow::Result with `let _ = h.foo(...).await`.

Migrate all 11 sites:

- roles.rs: create role, set permission, assign role, delete role
- settings.rs: set server display name, mute server
- sync_queue_view.rs: retry queue
- channel_sidebar.rs: create channel, create voice channel, delete channel, mute channel

Each site now captures `let toasts = use_context::<ToastStack>()` on the outer reactive frame (before `wasm_bindgen_futures::spawn_local`, which strips the reactive owner), moves it into the async block, and dispatches `crate::handlers::warn_and_toast_with("<action>", &e, toasts.as_ref())` on Err.

`rg -n 'let _ = h\.[a-z_]+\(.*\)\.await' crates/web/src/components/` goes from 11 hits to 0.

Refs #476
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.

2 participants