fix: derive dead render state without requiring socket - #58
Merged
Conversation
`dead` was computed as:
assigns[:socket] == nil or not LiveView.connected?(assigns[:socket])
so a call site that did not pass `socket` looked dead on *every* render, not
just the first. With props diffing enabled that silently breaks updates:
* `full_props?` stays true, so `data-props` is resent -- but `base_assigns`
is still filtered to changed keys, so the snapshot degrades to only the
props that changed.
* `props_diff: not full_props?` stays false, so `data-props-diff` is never
marked as changed and freezes at its mount value.
The client is in diff mode (`data-use-diff="true"`), reads only the frozen
diff attribute, and the component silently stops reflecting updates. Nothing
raises and nothing is logged.
A populated `__changed__` means LiveView is re-rendering the component, which
only happens in a connected view, so a render can only be dead on the first
pass. That is also the only place `dead` is used for anything other than
props: the SSR decision is already gated on `init`, so this leaves SSR
behaviour byte-for-byte identical.
The existing props-diff tests could not catch this: they render the component
directly and read the attributes off the HTML, where every attribute is always
present. Marking only decides what LiveView puts on the wire. The new test
asks the Rendered struct for its dynamic parts with change tracking enabled,
so an attribute LiveView would skip comes back as nil -- and it fails without
this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
deadwas computed as:so a call site that did not pass
socketlooked dead on every render, not just the first. With props diffing enabled that silently breaks updates:full_props?stays true, sodata-propsis resent -- butbase_assignsis still filtered to changed keys, so the snapshot degrades to only the props that changed.props_diff: not full_props?stays false, sodata-props-diffis never marked as changed and freezes at its mount value.The client is in diff mode (
data-use-diff="true"), reads only the frozen diff attribute, and the component silently stops reflecting updates. Nothing raises and nothing is logged.A populated
__changed__means LiveView is re-rendering the component, which only happens in a connected view, so a render can only be dead on the first pass. That is also the only placedeadis used for anything other than props: the SSR decision is already gated oninit, so this leaves SSR behaviour byte-for-byte identical.The existing props-diff tests could not catch this: they render the component directly and read the attributes off the HTML, where every attribute is always present. Marking only decides what LiveView puts on the wire. The new test asks the Rendered struct for its dynamic parts with change tracking enabled, so an attribute LiveView would skip comes back as nil -- and it fails without this change.
Contributor checklist
For example:
fix: Multiply by appropriate coefficient, orfeat(Calculator): Correctly preserve historyAny explanation or long form information in your commit message should be
in a separate paragraph, separated by a blank line from the primary message