[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.ex — build_render_assigns → Map.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.
[live_ui] build_render_assigns hardcodes
__changed__: %{}— slot comprehensions don't re-evaluate when a widget's live_component updatesSummary
LiveUi.Widget.build_render_assigns/1sets__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 HEExforcomprehensions insiderender_slot(@inner_block)when the widget'slive_componentreceivesupdate/2with 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.ex—build_render_assigns→Map.put(:__changed__, %{}).LiveUi.Renderer(renderer.ex:200) passes afor child <- child_elements(...)comprehension slot intoSidebarSection.component, which callsrender_slot(@inner_block)(sidebar_section.ex:82).%{}= unchanged,nil= changed (utils.ex:132, engine.ex:1399); slot fns receive the caller's__changed__(phoenix_component.ex:1137).operator_surface_live_test.exsMAP-3 active-dispatch indicator — currently@tag :skippending this fix.Candidate hotfix (correct but blunt — NOT the recommended fix)
Branch
The-Metagraph/ash_ui:claude/live-ui-changed-nilflips%{}→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 useLiveUi.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 assignsLiveUi.Component.component/1copies intowidget_assigns, component.ex:275), fall back tonilonly when absent, and mark derived:metadatachanged when wrapper/identity metadata (esp.inner_block) changes. Add a focused LiveView regression test: aSidebarSectionreceives updated slot children → the new child component mounts.Ariston-side status
MAP-3 test is
@tag :skipwith a ref to this issue + the candidate branch, so the suite stays honest. Un-skip once the framework fix lands.