diff --git a/src/app.rs b/src/app.rs index 09cbe0a..28e464c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -472,19 +472,30 @@ fn CallPanel(room: Memo, me: String) -> impl IntoView { } .into_any() } - // Must mirror the hydrate branch's default (not-in-call) markup exactly - - // hydration reconciles this SSR output against what the hydrate branch - // above expects to find, and an empty div here (vs. the button hydrate - // wants) is a hydration mismatch that panics and traps the whole wasm - // instance, killing all reactivity on the page. + // Must mirror the hydrate branch's default (not-in-call) markup AND its + // node structure exactly. The hydrate branch puts the button inside a + // dynamic child (`{move || ...}`), which serializes hydration-marker + // comments around it; if SSR emits a plain static button instead, the + // hydrate walk can't find the dynamic node, so it never attaches + // `on:click` - the call button is dead on the first lobby load until a + // client-side nav re-renders the subtree. So: same `{move || ...}` wrapper, + // and only the attributes the hydrate button actually serializes to + // (class/title/aria-label; `on:click` is a JS listener, not HTML). No + // `disabled` here - the hydrate button has none and hydration won't clear + // a stray SSR-only attribute. #[cfg(not(feature = "hydrate"))] { let _ = (room, me); view! {
- + {move || { + view! { + + } + .into_any() + }}
} .into_any()