feat(bindx-react): createComponent().use() + crash-proof static selection analysis#62
Open
matej21 wants to merge 1 commit into
Open
feat(bindx-react): createComponent().use() + crash-proof static selection analysis#62matej21 wants to merge 1 commit into
matej21 wants to merge 1 commit into
Conversation
…tion analysis Fixes #57 — using a scalar prop value (a translator call, a labels object) in a createComponent render body crashed implicit-selection collection with "t is not a function", and one crashing component silently dropped its siblings' selections from the fetch plan. Implicit collection is an abstract interpretation of the render body; this makes it total and confines it to the static-analysis phase: - Scalar props get a tolerant stand-in during collection (callable, property-safe, primitive-coercible, iterable) instead of undefined, so render bodies using them survive and entity fields keep being captured. - ComponentImpl no longer triggers collection — it runs only from the static surface (getSelection, $propName fragment getters, interfaces proxy). Render bodies are pure runtime code; components whose selection is never consumed statically are never collected. - A throw during collection no longer costs the whole component (scopes capture eagerly; the partial selection is finalized on the throw path) and is reported loudly with component attribution instead of being swallowed. analyzeJsx catches per node, so a broken component no longer drops sibling selections. - New .use(fn) builder step: runtime-only values with hooks allowed (useT(), useContext(), ...). Runs inside the React render and merges into render props; chainable, later fns see earlier values. Static analysis skips it entirely — its outputs are covered by the scalar stand-ins. This removes the need for thin wrapper components that only thread hook-derived values in as props (and which also broke selection discovery, because the JSX walk cannot see through plain components). Repro test from the issue branch + .use() test suite included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016eijo87HdS4vA6D2ZcS6KL
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.
Fixes #57.
Problem
createComponent(...).render(...)crashed implicit-selection collection whenever the render body used a scalar (.props<>()) prop value — e.g. calling a translatort(key)or readinglabels.heading. The collection pass fedundefinedfor every scalar prop and ran the render unguarded, so the call/dereference threw.Worse than the crash itself: when triggered through a fetching
<Entity>'s walk, the throw was swallowed by a coarsetry/catchinuseSelectionCollection, silently dropping the whole JSX-derived selection (including sibling components' fields) from the fetch plan — a real data-loss (noindexand friends never fetched, downstream shippedcollectionSafeTworkarounds).Design
Implicit collection is an abstract interpretation of the render body. This PR makes it total (never crashes out) and confines it to the static-analysis phase:
ComponentImplno longer triggers it; it runs solely fromgetSelection,$propNamefragment getters, and the interfaces proxy. Render bodies are pure runtime code. Components whose selection nobody statically consumes are never collected (no dead work, no spurious dev errors). The guard is tri-state (collectingterminates self-recursive components).console.errorwith component attribution (displayName).analyzeJsxcatches per node — one broken component no longer costs its siblings their selection..use(fn)— runtime-only values, hooks allowed:Runs inside the React render (hooks, context all work) and merges into render props; chainable — later
use()fns see earlier values. Static analysis skips it entirely; its outputs fall through to the scalar stand-ins. Type-wise the values exist only in render props, not on the public component props. This removes the thin wrapper components that existed only to thread hook-derived values in as props — wrappers that also broke selection discovery, since the JSX walk cannot see through plain components.Verification
createComponentScalarPropCollection.test.tsx) now passes.createComponentUse.test.tsx: analysis skipsuse()while still collecting fields; hooks insideuse(); chaining; sibling isolation on analysis crash; partial capture before a throw.mainwith the playground running;bindx-formformRelations dirty-tracking fails onmaintoo).collectionSafeTworkarounds and thePageFormwrapper removed, full admin suite 288/288 green (companion PR in that repo).🤖 Generated with Claude Code
https://claude.ai/code/session_016eijo87HdS4vA6D2ZcS6KL