You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
state.rs format_value doesn't handle NaN-boxed strings on Windows / Android / GTK4 — string-typed states render as 'NaN' through stateBindTextNumeric / TEXT_BINDINGS #443
On Windows / Android / GTK4, binding a Text widget to a string-typed State via stateBindTextNumeric (or anything that flows through TEXT_BINDINGS / MULTI_TEXT_BINDINGS) renders the literal text "NaN" (or some bit-pattern double) rather than the actual string content. Apple platforms (macOS / iOS / tvOS / visionOS / watchOS) work correctly.
Found while investigating discussion #428 — would have been the recommended workaround for that user's reactive-Text question, but it silently breaks on Windows.
Repro
import{App,State,Text,TextField,VStack,HStack,stateBindTextfield,stateBindTextNumeric}from"perry/ui";consttext=State("");constecho=Text("You typed: ");constfield=TextField("Placeholder...",(v: string)=>text.set(v));stateBindTextfield(text,field);// works on every platform (separate path)stateBindTextNumeric(text,echo,"You typed: ","");App({title: "demo",width: 400,height: 200,body: VStack(8,[HStack(8,[field,echo])])});
Type "hello" into the field. On macOS/iOS/etc the echo shows "You typed: hello". On Windows / Android / GTK4 the echo shows "You typed: NaN".
Root cause
format_value(value: f64) in each backend's state.rs. Apple platforms have a NaN-boxed-string check; the others don't:
Apple (correct) — crates/perry-ui-{macos,ios,tvos,visionos,watchos}/src/state.rs:
Same shape in crates/perry-ui-android/src/state.rs and crates/perry-ui-gtk4/src/state.rs.
A NaN-boxed string handle is an f64 whose top 16 bits are 0x7FFF (STRING_TAG, see crates/perry-runtime/src/value.rs). For such a value, value.fract() is NaN — neither branch matches cleanly — so format!("{}", value) runs and Rust formats it as "NaN".
Affected paths
In Windows state_set (and the equivalents in android / gtk4), formatted = format_value(value) flows into:
TEXT_BINDINGS (single-state Text — stateBindTextNumeric)
rebuild_multi_text recursive use of format_value for each StateRef
The TEXTFIELD_BINDINGS path on Windows happens to work because it uses js_get_string_pointer_unified(value) separately (crates/perry-ui-windows/src/state.rs:244) — so stateBindTextfield is fine for string states even on the broken platforms. That's why two-way input binding looks correct in screenshots while bound-Text binding silently produces "NaN".
Fix
Port the macOS is_nanboxed_string / extract_nanboxed_string helpers + the extra branch in format_value to:
crates/perry-ui-windows/src/state.rs
crates/perry-ui-android/src/state.rs
crates/perry-ui-gtk4/src/state.rs
The helpers themselves are tiny (~10 LOC each) and depend only on js_nanbox_get_pointer and the StringHeader layout — both already available on every platform.
A regression test that verifies a string-typed state bound via stateBindTextNumeric renders the actual string (not "NaN") would catch any future per-platform divergence.
Workaround for users
Drop State for the simple "echo what was typed" pattern and call textSetString(echoTextWidget, "..." + value) directly from the TextFieldonChange. textSetString's FFI takes the string pointer raw (no NaN-box round-trip), so it works identically on every backend.
Activity
fix(ui): #443 — NaN-boxed string in state.rs format_value on Windows/…