diff --git a/CHANGELOG.md b/CHANGELOG.md index 28a98332..4df1856d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## v3.5.2 - 2026-06-25 + +### Fixed + +- **`defaultValue` / `defaultChecked` rendered as inert attributes in SSR.** An + uncontrolled form field set via `defaultValue` (or `defaultChecked`) serialized + the prop literally — `` — which the browser ignores, so + the field rendered empty/unchecked server-side. The SSR serializer now maps + `defaultValue` → the element's controlled value and `defaultChecked` → `checked` + (only when the controlled prop is not already set, so an explicit `value` wins), + in both the buffered and streaming renderers. Because the mapping happens before + the controlled-value logic, it flows through every form element: `` gets + a `value` attribute, ``, + }, + { + "select-defaultValue-selects-option", + html.Select(html.Props{Raw: map[string]any{"defaultValue": "b"}}, + html.Option(html.Props{Value: "a"}, ui.Text("A")), + html.Option(html.Props{Value: "b"}, ui.Text("B"))), + ``, + }, + { + "no-default-unchanged", + html.Input(html.Props{Type: "text", Value: "v"}), + ``, + }, + } + for _, parseCase := range cases { + out, err := ui.RenderToString(parseCase.node) + if err != nil { + t.Fatalf("%s: render error: %v", parseCase.name, err) + } + if out != parseCase.want { + t.Errorf("%s:\n got %q\nwant %q", parseCase.name, out, parseCase.want) + } + } +}