fix(calendar): hoist timezone reads out of use_signal initialisers (MAPPS-299) #339

Merged
YousifShkara merged 1 commit from fix/MAPPS-299-calendar-hook-violation into main 2026-06-25 10:46:10 +02:00
Owner

QA repro nailed the Calendar "+ New Appointment" crash to:

  • Primary: BorrowMutError at dioxus-core scope_context.rs:343 with
    "using a hook inside a hook" (rules-of-hooks violation).
  • Secondary: panic at hooks/unsaved_guard.rs:56 unwrapping a dropped
    signal during teardown.

Root cause of the primary panic. AppointmentFormModal had four
use_signal initialisers that called timezone-aware conversion helpers
(utc_to_datetime_local_value, utc_to_date_value). Those helpers
reach into user_timezone(), which calls
try_use_context::<Signal<AuthContext>>() - itself a hook. Running a
hook inside another hook's initialiser is the panic the trace
described. Dispatch's parent happens to dodge it because its mount path
hits the modal with the auth context in a different borrow state, so
the same code looked fine in one surface and crashed in the other.

Fix: hoist the four timezone conversions above the use_signal calls
into local bindings (init_start_local, init_end_local,
init_start_date, init_end_date) and pass plain Strings into the
initialisers. The hooks now run with no inner hook calls.

Root cause of the secondary panic. use_unsaved_guard installs a
beforeunload closure that captures dirty: ReadSignal<bool> and
calls dirty.read() when the browser fires the event. The listener
stays installed across component teardown (wasm cannot detach a
Closure-backed listener once a sibling component has already
panicked), so the signal can be dropped by the time the event fires;
Signal::read then panics with ValueDroppedError, which surfaces as
the secondary trace QA captured.

Fix: use try_read instead of read and treat a dropped signal as
"not dirty" - no prompt, no panic.

Acceptance criteria from MAPPS-299:

  • "Schedule Appointment" opens the modal without crashing the
    wasm runtime (Calendar).
  • Same path from Dispatch still works (no regression).
  • Wasm-level panics still surface readable messages via
    console_error_panic_hook (PR #284, unchanged).
  • Root cause documented in the commit body.

#MAPPS-299

QA repro nailed the Calendar "+ New Appointment" crash to: - Primary: `BorrowMutError` at dioxus-core scope_context.rs:343 with "using a hook inside a hook" (rules-of-hooks violation). - Secondary: panic at hooks/unsaved_guard.rs:56 unwrapping a dropped signal during teardown. Root cause of the primary panic. AppointmentFormModal had four `use_signal` initialisers that called timezone-aware conversion helpers (`utc_to_datetime_local_value`, `utc_to_date_value`). Those helpers reach into `user_timezone()`, which calls `try_use_context::<Signal<AuthContext>>()` - itself a hook. Running a hook inside another hook's initialiser is the panic the trace described. Dispatch's parent happens to dodge it because its mount path hits the modal with the auth context in a different borrow state, so the same code looked fine in one surface and crashed in the other. Fix: hoist the four timezone conversions above the `use_signal` calls into local bindings (`init_start_local`, `init_end_local`, `init_start_date`, `init_end_date`) and pass plain `String`s into the initialisers. The hooks now run with no inner hook calls. Root cause of the secondary panic. `use_unsaved_guard` installs a `beforeunload` closure that captures `dirty: ReadSignal<bool>` and calls `dirty.read()` when the browser fires the event. The listener stays installed across component teardown (wasm cannot detach a `Closure`-backed listener once a sibling component has already panicked), so the signal can be dropped by the time the event fires; `Signal::read` then panics with `ValueDroppedError`, which surfaces as the secondary trace QA captured. Fix: use `try_read` instead of `read` and treat a dropped signal as "not dirty" - no prompt, no panic. Acceptance criteria from MAPPS-299: - [x] "Schedule Appointment" opens the modal without crashing the wasm runtime (Calendar). - [x] Same path from Dispatch still works (no regression). - [x] Wasm-level panics still surface readable messages via `console_error_panic_hook` (PR #284, unchanged). - [x] Root cause documented in the commit body. #MAPPS-299
fix(calendar): hoist timezone reads out of use_signal initialisers (MAPPS-299)
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m12s
Create release / Create release from merged PR (pull_request) Has been skipped
237117346c
QA repro nailed the Calendar "+ New Appointment" crash to:
- Primary: `BorrowMutError` at dioxus-core scope_context.rs:343 with
  "using a hook inside a hook" (rules-of-hooks violation).
- Secondary: panic at hooks/unsaved_guard.rs:56 unwrapping a dropped
  signal during teardown.

Root cause of the primary panic. AppointmentFormModal had four
`use_signal` initialisers that called timezone-aware conversion helpers
(`utc_to_datetime_local_value`, `utc_to_date_value`). Those helpers
reach into `user_timezone()`, which calls
`try_use_context::<Signal<AuthContext>>()` - itself a hook. Running a
hook inside another hook's initialiser is the panic the trace
described. Dispatch's parent happens to dodge it because its mount path
hits the modal with the auth context in a different borrow state, so
the same code looked fine in one surface and crashed in the other.

Fix: hoist the four timezone conversions above the `use_signal` calls
into local bindings (`init_start_local`, `init_end_local`,
`init_start_date`, `init_end_date`) and pass plain `String`s into the
initialisers. The hooks now run with no inner hook calls.

Root cause of the secondary panic. `use_unsaved_guard` installs a
`beforeunload` closure that captures `dirty: ReadSignal<bool>` and
calls `dirty.read()` when the browser fires the event. The listener
stays installed across component teardown (wasm cannot detach a
`Closure`-backed listener once a sibling component has already
panicked), so the signal can be dropped by the time the event fires;
`Signal::read` then panics with `ValueDroppedError`, which surfaces as
the secondary trace QA captured.

Fix: use `try_read` instead of `read` and treat a dropped signal as
"not dirty" - no prompt, no panic.

Acceptance criteria from MAPPS-299:
- [x] "Schedule Appointment" opens the modal without crashing the
      wasm runtime (Calendar).
- [x] Same path from Dispatch still works (no regression).
- [x] Wasm-level panics still surface readable messages via
      `console_error_panic_hook` (PR #284, unchanged).
- [x] Root cause documented in the commit body.

#MAPPS-299
YousifShkara deleted branch fix/MAPPS-299-calendar-hook-violation 2026-06-25 10:46:10 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!339
No description provided.