2 Commits
Author SHA1 Message Date
Bendik Aagaard LynghaugandClaude Opus 4.8 8ab1b50310 chore: Release cnats version 0.3.2
Release / build (x86_64, ubuntu-latest, /sccache) (push) Successful in 1m30s
Release / docker (push) Successful in 13s
Release / build (aarch64, aarch64, /var/lib/gitea-runner/sccache) (push) Successful in 12m52s
Release / update-aur (push) Successful in 28s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-03 10:17:22 +02:00
Bendik Aagaard LynghaugandClaude Opus 4.8 ede1b8497f fix(call): mirror SSR node structure so the lobby call button hydrates
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 <noreply@anthropic.com>
2026-10-03 10:16:50 +02:00
3 changed files with 21 additions and 10 deletions
Generated
+1 -1
View File
@@ -345,7 +345,7 @@ dependencies = [
[[package]] [[package]]
name = "cnats" name = "cnats"
version = "0.3.1" version = "0.3.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-nats", "async-nats",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "cnats" name = "cnats"
version = "0.3.1" version = "0.3.2"
edition = "2021" edition = "2021"
[lib] [lib]
+17 -6
View File
@@ -472,19 +472,30 @@ fn CallPanel(room: Memo<String>, me: String) -> impl IntoView {
} }
.into_any() .into_any()
} }
// Must mirror the hydrate branch's default (not-in-call) markup exactly - // Must mirror the hydrate branch's default (not-in-call) markup AND its
// hydration reconciles this SSR output against what the hydrate branch // node structure exactly. The hydrate branch puts the button inside a
// above expects to find, and an empty div here (vs. the button hydrate // dynamic child (`{move || ...}`), which serializes hydration-marker
// wants) is a hydration mismatch that panics and traps the whole wasm // comments around it; if SSR emits a plain static button instead, the
// instance, killing all reactivity on the page. // 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"))] #[cfg(not(feature = "hydrate"))]
{ {
let _ = (room, me); let _ = (room, me);
view! { view! {
<div class="call-panel"> <div class="call-panel">
<button class="call-join" title="join call" aria-label="join call" disabled=true> {move || {
view! {
<button class="call-join" title="join call" aria-label="join call">
"☎" "☎"
</button> </button>
}
.into_any()
}}
</div> </div>
} }
.into_any() .into_any()