Skip to content

edit_room_modal closes via an undeferred GlobalSignal write in onclick (dioxus-signal-safety violation) #539

Description

@sanity

Problem

ui/src/components/room_list/edit_room_modal.rs closes the room-details panel
by writing a GlobalSignal directly inside an onclick, with no
crate::util::defer():

  • edit_room_modal.rs:66 — the backdrop overlay
  • edit_room_modal.rs:375 — the ✕ close button
onclick: move |_| {
    EDIT_ROOM_MODAL.write().room = None;
},

.claude/rules/dioxus-signal-safety.md names this shape explicitly as wrong:

ALSO WRONG — onclick handlers trigger the same RefCell panic

onclick: move |_| {
    ROOMS.write().map.remove(&key);
};

The rationale is that a signal write's Drop fires subscriber notifications
synchronously, and Firefox mobile runs those during Drop, so a memo doing
try_read() on the same signal can hit a re-entrant RefCell already borrowed
panic. defer() also supplies the Dioxus runtime + root scope.

The leave-room path in the same file (line 333) already does this correctly,
inside a crate::util::defer() block — so this is an inconsistency within one
component, not a deliberate exemption.

Evidence / severity

No symptom reproduced. An instrumented 25-cycle open/copy/close/reopen loop
against both WebKit and Firefox (alternating close-button and backdrop routes)
produced no panic and no stale render. So this is filed as a latent
rule violation, not a known-live bug — severity low, correctness risk real
but unobserved.

Deliberately not fixed in #536 (the Firefox selection fix): making the close
deferred changes modal-close timing app-wide, which is a bigger blast radius
than that PR warrants without evidence of harm. Noting it here so it is not
silently lost.

Suggested fix

Wrap both writes:

onclick: move |_| {
    crate::util::defer(move || {
        EDIT_ROOM_MODAL.write().room = None;
    });
},

Then confirm copy-clipboard-feedback.spec.ts (which closes this class of
modal via the backdrop) and the room-details specs still pass — the close
becomes asynchronous, so any assertion that races it would surface there.

Worth grepping for the same shape in the other modals while in there.

[AI-assisted - Claude]

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions