Skip to content

[live_ui] build_render_assigns hardcodes __changed__: %{} — slot comprehensions don't re-evaluate on widget update #160

Description

@ty13r

[live_ui] build_render_assigns hardcodes __changed__: %{} — slot comprehensions don't re-evaluate when a widget's live_component updates

Summary

LiveUi.Widget.build_render_assigns/1 sets __changed__: %{} (Phoenix: "no wrapper assigns changed") in the render assigns passed to wrapper modules. The wrapper is invoked manually (@widget_wrapper_module.render(@render_assigns)), so Phoenix doesn't rebuild change-tracking for that call — %{} suppresses re-evaluation of HEEx for comprehensions inside render_slot(@inner_block) when the widget's live_component receives update/2 with new slot content. A conditionally-added child (e.g. an active-dispatch indicator appended after a PubSub broadcast) is never mounted.

Evidence (verified via Codex deep-review against Phoenix 1.1.27 source)

  • packages/live_ui/lib/live_ui/widget.exbuild_render_assignsMap.put(:__changed__, %{}).
  • Slot path: LiveUi.Renderer (renderer.ex:200) passes a for child <- child_elements(...) comprehension slot into SidebarSection.component, which calls render_slot(@inner_block) (sidebar_section.ex:82).
  • Phoenix 1.1.27: %{} = unchanged, nil = changed (utils.ex:132, engine.ex:1399); slot fns receive the caller's __changed__ (phoenix_component.ex:1137).
  • Downstream failing case (ariston-ui): operator_surface_live_test.exs MAP-3 active-dispatch indicator — currently @tag :skip pending this fix.

Candidate hotfix (correct but blunt — NOT the recommended fix)

Branch The-Metagraph/ash_ui:claude/live-ui-changed-nil flips %{}nil (always-changed / first-mount equivalent). Codex review: correct + safer than %{}, no correctness regression (phx-update="ignore" intact, streams unaffected, stable LiveComponent IDs prevent duplicate mounts), BUT it defeats wrapper-level diff minimization — meaningful extra server CPU + larger diffs on large composed trees (sidebars/rails/tables; ~69 modules use LiveUi.Component). Negligible for simple widgets.

Recommended architect fix

Don't hardcode either extreme. In build_render_assigns/1: preserve/thread the ACTUAL wrapper-level __changed__ when present (it's available in the assigns LiveUi.Component.component/1 copies into widget_assigns, component.ex:275), fall back to nil only when absent, and mark derived :metadata changed when wrapper/identity metadata (esp. inner_block) changes. Add a focused LiveView regression test: a SidebarSection receives updated slot children → the new child component mounts.

Ariston-side status

MAP-3 test is @tag :skip with a ref to this issue + the candidate branch, so the suite stays honest. Un-skip once the framework fix lands.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions