Repository navigation
fix(iur_hydration): expand_list_repeat traverse wrapper :child relationship for two-level row templates (#114) - #118
Merged
pcharbon70 merged 2 commits intoMay 18, 2026
Conversation
…onship for two-level row templates (#114) Extends AshUI.LiveView.IURHydration to fan-out repeat-marked elements whose widget type is NOT "list_repeat" (e.g. "custom:doc_block_numbered", "artifact_row") but which carry repeat metadata in metadata.composition.repeat.binding_id via the LiveView compile_node helper. Previously only list_repeat-typed nodes were expanded by expand_list_repeat/1. For screens like ProcessManuscriptScreen whose composition roots are typed as custom:* or other widget kinds (with the repeat directive on the ui_relationships entry, compiled as metadata.composition.repeat), the hydration pass never cloned the template per row. The fix adds two private helpers: - expand_repeat_template/2: called after expand_list_repeat/1 on every hydrated node; detects metadata.composition.repeat.binding_id, looks up the matching binding from the full (unfiltered) binding state list including screen-scoped bindings excluded from element-level hydration, and materialises one cloned row per item. Returns a synthetic list_repeat wrapper so the LiveUIAdapter renders the rows as a contiguous group. The hydrated? flag prevents double-expansion. - strip_repeat_metadata/1: removes repeat from the composition metadata of each cloned template row so they do not re-trigger expand_repeat_template/2 on subsequent hydrate passes. hydrate/2 now preserves all non-action binding states and threads them through hydrate_node/3 as all_states for use by expand_repeat_template/2. Adds a positive unit test in iur_hydration_test.exs exercising the two-level IUR shape: screen-scoped list binding + repeat-marked custom:* template -> synthetic wrapper + 3 cloned rows with row-scoped field resolution + repeat metadata stripped. Addresses #114. References ariston-ui PR #388 as the concrete consumer. DRAFT: Pascal-review REQUIRED. Happy to revise to Option B or Option C if preferred. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pcharbon70
marked this pull request as ready for review
May 18, 2026 07:58
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.
Summary
Addresses #114. References ariston-ui PR #388 as the concrete consumer.
Pascal-review REQUIRED — DRAFT — no auto-merge regardless of Codex/CI verdict. Happy to revise to Option B or Option C if preferred.
Problem
AshUI.LiveView.IURHydration.expand_list_repeat/1only pattern-matches on%{"type" => "list_repeat"}nodes. For screens whose composition roots have types like"custom:doc_block_numbered","artifact_row", or other widget kinds — where therepeatdirective lives on the screen'sui_relationshipsentry and is compiled intometadata.composition.repeatby the LiveView'scompile_nodehelper — the hydration pass never fires and 0 rows are rendered instead of N.This is the substrate gap discovered during Wave AshUI-3.9 Item 2. ariston-ui PR #388 restructured Screens to use the canonical
:list_repeatwrapper pattern (clearing 12:missing_list_bindingvalidator failures), but the 5 DSL Screen LiveViews in ariston-ui that use screen-scoped list bindings (manuscript, swimlane, research page, conversation, document chat workspace) still fail because their composition-root template nodes are not typedlist_repeat.A second factor: screen-scoped bindings (those with
metadata.owner_scope: "screen") are filtered out ofhydrate/2's element-scoped pass, so the list items never reach the template node even if we widened the type check.Implementation chosen: Option A (smallest delta)
Modified files (only two, per brief constraint):
lib/ash_ui/liveview/iur_hydration.extest/ash_ui/liveview/iur_hydration_test.exsChanges
hydrate/2now separates binding states intoelement_states(element-scoped, existing behaviour) andall_states(full non-action set including screen-scoped). Both are threaded intohydrate_node/3.hydrate_node/3addsall_statesas a third parameter (defaulting to[]for backward compat). Afterexpand_list_repeat/1runs, it calls the newexpand_repeat_template/2.expand_repeat_template/2— fires when a node hasmetadata.composition.repeat.binding_idANDprops["hydrated?"]is NOT already true. Looks up the binding inall_statesby id OR target. On match, clones the node once per row using the existingmaterialize_repeat_template/6. Returns a synthetic"list_repeat"wrapper withhydrated?: trueso the LiveUIAdapter renders the children as a contiguous group without double-expansion.strip_repeat_metadata/1— removes the"repeat"key frommetadata.compositionin each cloned row to prevent re-triggeringexpand_repeat_template/2on that row.New unit test in
iur_hydration_test.exscovering the exact two-level IUR shape: screen-scoped list binding (owner_scope: "screen") + repeat-markedcustom:*template → synthetic wrapper + 3 cloned rows with row-scoped field resolution + repeat metadata stripped from rows.What is NOT changed
packages/live_ui/lib/live_ui/renderer.ex— not touchedpackages/live_ui/lib/live_ui/widgets/*— not touchedAlternative options (for Pascal's consideration)
Option B — find_repeat_row_templates helper
Extract a
find_repeat_row_templates/1helper that walks the IUR tree until it finds the actual renderable templates, then call it in place ofMap.get(node, "children", [])insideexpand_list_repeat. Cleaner separation: the expansion logic stays in one place; only the template-finding step is generalised. No change tohydrate/2or the binding-scope threading. Downside: the screen-scoped binding lookup problem still needs solving separately.Option C — IUR-generation refactor (most invasive)
Refactor the IUR-generation step upstream in the LiveView
compile_nodehelpers and/or the Authority payload compiler so the wrapper'sui_relationships kind: :childdirectly inlines the row template as thelist_repeat's immediate child at IUR time, eliminating the relationship-only wrapper entirely. This makes hydration a pure implementation of thelist_repeatcontract with no special-casing, but requires coordinating changes across the ariston-ui LiveViews and potentially the Authority payload format. Most future-proof but the highest Pascal-coordination cost.Test plan
mix format --check-formattedon both changed files — cleanmix compile— cleaniur_hydration_test.exstests still passlist_repeat_hydration_test.exs— 7 passing (canonical list_repeat + Phase31 fixtures)phase_31_integration_test.exs— 3 passing (resource-authority + compilation + hydration)expands repeat-marked non-list_repeat nodes using screen-scoped list binding— exercises the two-level IUR shape directlyCross-references