From ede1b8497f59dac54089e4ba18eee1d5d2a2f94d Mon Sep 17 00:00:00 2001 From: Bendik Aagaard Lynghaug Date: Sat, 3 Oct 2026 10:16:50 +0200 Subject: [PATCH] fix(call): mirror SSR node structure so the lobby call button hydrates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CallPanel's hydrate body wraps the join button in a dynamic child (`{move || if in_call {..} else {button}}`), which serializes hydration marker comments; the SSR body rendered a *static* button (plus a `disabled=true` the hydrate button lacks). That structural mismatch meant `on:click` never attached on the first lobby load — the call button was dead until a client-side nav re-rendered the subtree. Mirror the dynamic-child wrapper and the button's serialized attributes in the SSR branch. Co-Authored-By: Claude Opus 4.8 --- src/app.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) 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()