fix(render): resolve repeated element readiness and props in the row's own scope - #1062
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
blove
enabled auto-merge (squash)
September 8, 2026 15:01
blove
force-pushed
the
blove/render-repeat-readiness
branch
from
September 8, 2026 15:46
aa8ddb3 to
34a1215
Compare
Contributor
…s own scope
A repeating element resolved its mount-readiness gate and its mount class
from the NON-repeat prop context: `notReady` and `mountClass` ran
`resolveElementProps` against the parent scope, where every `$item`,
`$index` and `$bindItem` expression resolves to `undefined`. An element
whose props were all `$item`-bound therefore tripped the readiness gate
and rendered its fallback (or nothing) for every row, forever. The
streaming demo hid this because a partially parsed `props: {}` latches
`mountedReal` before the `$item` keys arrive.
Readiness, mount class, input filtering, visibility and action `params`
are now all derived from one per-row prop-resolution context, and the
fallback latch is kept per row index — the identity the `@for` block
already tracks. A row still shows its fallback while its own props are
genuinely undefined or fail the entry's Standard Schema, while its ready
siblings mount the real component.
Each row also gets its own `RENDER_HOST`, so a component that emits
through `injectRenderHost()` inside a repeat resolves `{ $item: … }`
action params against the row that fired the event, matching the `emit`
input path.
The mount-evidence telemetry guard now relies on the outlet identity
check alone: an element-level `visible`/`notReady` pair cannot describe a
list whose rows differ.
The repeat-loops example holds objects and binds `{ $item: 'label' }`,
which the fix makes renderable; docs updated for per-row readiness.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
force-pushed
the
blove/render-repeat-readiness
branch
from
September 8, 2026 16:08
34a1215 to
6034bb1
Compare
Contributor
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.
What was broken
RenderElementComponentrendered each repeat item through a per-item prop context, but computed the mount-readiness gate (notReady) and the mount class (mountClass()) from the NON-repeat prop context.resolveElementPropsresolves{ $item: … }/{ $index: … }/{ $bindItem: … }toundefinedwhen the context carries no repeat scope, andisElementReadytreats anyundefined-valued prop as "still streaming".So a repeating element whose props are all
$item-bound tripped the gate and mounted its fallback (a bare registration meansDefaultFallbackComponent) — or nothing at all — for every row, permanently. The diagnosis was confirmed against the code and then reproduced: the new spec file failed 8/8 before the fix, withexpected [] to deeply equal ['Alpha', 'Beta', 'Gamma']and a$bindItem used outside repeat scope: "label"warning from@json-render/coreproving readiness ran with no scope.The streaming demo hid the defect: partial JSON yields
props: {}before the$itemkeys arrive, which reads as ready and latchesmountedRealfor the whole element. The specs added in #1056 sidestepped it by reading the item fromREPEAT_SCOPEinstead of binding$itemprops.Two adjacent instances of the same root cause were fixed with it:
ActionBinding.paramsresolved throughthis.propCtx(), so{ $item: … }params on a repeated element reached the handler asundefined.RENDER_HOSTwas provided once per element, so a component emitting throughinjectRenderHost()inside a repeat could not say which row fired.What changed
libs/render/src/lib/render-element.component.tsrepeatPropCtxscomputed is now the single source for every per-row derivation: props, bindings, readiness, mount class, input filtering, visibility and action params.repeatRawNotReady/repeatNotReady/repeatMountClassesare the per-row counterparts ofnotReady/mountClass. A row shows its fallback while its own props are undefined or fail the entry's Standard Schema, while its ready siblings mount the real component. Fallback behavior for genuinely-undefined props is unchanged.@forblock already tracks — in a second constructor effect, mirroring the existing single-mount latch.invokeHandlers(event, payload, repeatIndex?)resolvesparamsin the emitting row's context.repeatInputshands each row anemitbound to its index, andrepeatInjectorsprovides a row-scopedRENDER_HOSTsoinjectRenderHost().emit(…)behaves identically.visible()/notReady()precondition and relies on the outlet identity check alone: an element-level readiness flag cannot describe a list whose rows differ. Unknown, hidden, unready and empty-repeat elements are still not reported (covered by the existingdevelopment-mount.spec.tstable).libs/render/src/lib/repeat-scope.spec.ts(new, 8 specs, all failing before the fix)$item-bound prop over an array of objects, and{ $item: '' }over strings$bindItemresolved through each row'sbasePathvisibleper row alongside$item-bound props (the fix(render): honor every ActionBinding field, per-item visible, real element lifecycle and state paths #1056 feature)$item-bound actionparams, through both theemitinput andinjectRenderHost()cockpit/render/repeat-loops— the fix makes the more direct demo possible, so the store now holds[{ label: 'Alpha' }, …]and the repeated element binds{ $item: 'label' }instead of{ $item: '' }over plain strings. The e2e still asserts the rendered rows and passes unchanged.Docs:
render/guides/repeat-loops.mdxgains a "Per-item readiness" section (per-row gating, per-row schema, per-row action params) and its callout now describes the object array;render/guides/registry.mdxsays the fallback switch is one-way per row for a repeating element and that the host is row-scoped inside a repeat;RenderHost's JSDoc says the same, withapi-docs.jsonregenerated.Verification
npx nx run-many -t lint,test,build --projects=render,chat --skip-nx-cache— green (0 lint errors)npx vitest run --root apps/website --reporter=dot— 137 files, 1382 tests passednpx nx run-many -t buildfor all sixcockpit-render-*-angularprojects — greennpx nx e2e cockpit-render-repeat-loops-angular— 3 passed; the other five render e2e suites — 2 passed eachGROWTH_FORM_POLICY=growth_v1 npx nx build website— greenNot run:
nx run render:type-tests— therenderproject has notype-teststarget (onlylanggraph,chatandag-uidefine one).chat:type-testsfails on this branch and onmainalike, with a pre-existingTS5101: Option 'baseUrl' is deprecatederror inlibs/chat/tsconfig.type-tests.json, untouched here.🤖 Generated with Claude Code