Skip to content

fix: derive dead render state without requiring socket - #58

Merged
mrdotb merged 1 commit into
mainfrom
fix/props-diff-without-explicit-socket
Aug 29, 2026
Merged

fix: derive dead render state without requiring socket#58
mrdotb merged 1 commit into
mainfrom
fix/props-diff-without-explicit-socket

Conversation

@mrdotb

@mrdotb mrdotb commented Aug 29, 2026

Copy link
Copy Markdown
Owner

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.

Contributor checklist

  • My commit messages follow the Conventional Commit Message Format
    For example: fix: Multiply by appropriate coefficient, or
    feat(Calculator): Correctly preserve history
    Any explanation or long form information in your commit message should be
    in a separate paragraph, separated by a blank line from the primary message

`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>
@mrdotb
mrdotb merged commit 6a463b7 into main Aug 29, 2026
1 check passed
@mrdotb
mrdotb deleted the fix/props-diff-without-explicit-socket branch August 29, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant