Repository navigation
fix!: omit a null or undefined attribute hole in SSR, matching the client - #1574
Merged
Merged
Conversation
A plain attribute hole holding null rendered attr="" on the server while the
client removed it, so a layout's aria-current=${active ? 'page' : null}
served an empty aria-current on every inactive link, and layouts never
hydrate to repair it. false disagreed the same way (server "false", client
removed).
Both renderers now go through one helper, attrHoleValue: null and undefined
omit the attribute, false omits it except on an aria-* name, where it writes
"false", because a tri-state ARIA attribute set to "false" means something
different from an absent one. That is a markup change on both sides: the
server stops emitting attr="" and attr="false", and the client now writes
aria-*="false" where it used to remove it.
The client's form-action model follows, since it predicts what SSR emitted.
A bound form's method=${null} is no longer refused for an empty method; it is
omitted on both sides and the framework supplies method="post", as it already
did for ?method=${false}. A bound submitter's name=${null} likewise leaves the
identity's channel free, while name=${''} is still refused.
Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo
Every surface that documented the server and client disagreeing on a nullish
or false attribute hole told agents to branch templates around it. With one
rule in both renderers, null is the omit value and aria-* false is "false",
so the docs now teach the conditional-attribute idiom directly and the bound
form's method=${null} as supplied rather than refused.
Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo
|
Preview for Next: fix the listed keys in the compose file |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1573
A plain attribute hole holding
nullorundefinedis now omitted by the server, matching the client, soaria-current=${active ? 'page' : null}no longer servesaria-current=""on every inactive link.falsefollows one rule on both renderers too: it omits the attribute, except on anaria-*name, where it is written as"false".What changed
attrHoleValue(name, value)inpackages/core/src/binding-prefixes.jsis the single rule. Both server machines (buffered and streaming) and the clientapplyPartuse it.effectiveFormAttr, the submitteremitspredicate) predicts SSR output, so it follows the same rule.title="${x}",class="a ${x}") are unchanged: the author wrote the attribute statically, so it stays.Breaking
attr=${null}/attr=${undefined}no longer emitattr="", andattr=${false}on a non-aria attribute no longer emitsattr="false".aria-x=${false}now writesaria-x="false"instead of removing it.method=${null}/enctype=${null}is no longer refused; it is omitted and the framework suppliesmethod="post", exactly as for?method=${false}. A bound submitter'sname=${null}is accepted (the identity takes the channel);name=${''}is still refused.Decision on
falseOmit everywhere except
aria-*, where both renderers write"false". For a tri-state ARIA attribute (aria-expanded,aria-pressed,aria-checked)"false"and absent mean different things, so passing the boolean straight through is the useful behaviour. For every other attribute,falsereading as "omit" matches what the client already did and what?attrdoes.Test plan
packages/core/test/rendering/attr-hole-omission.test.js: the helper, both server machines, and the client (null, undefined, false, aria false, quoted and mixed unchanged). Counterfactual: restoring either the old server renderer or the oldparts.jsturns 2 of its 6 tests red.method=${null}getsmethod="post".ssr-client-parity.test.js, Chromium/Firefox/WebKit, 78 passed): new attribute-set parity test and three ACCEPTS rows moved from REFUSES.npm test: no new failures (the same 7 pre-existing linked-worktree failures as on main).bun test.webjs checkon the website.Docs
Updated AGENTS.md (hole paragraph and invariant 12),
.agents/skills/webjs/references/components.mdandmuscle-memory-gotchas.md,packages/ui/AGENTS.md, and the docs sitecomponentsandssrpages. The kit components that branch around the old behaviour keep their branches, since an app may run an older core.https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo