Add shared renderer fallback mechanism: web_core contract + React implementation (#2013) - #2088
Add shared renderer fallback mechanism: web_core contract + React implementation (#2013)#2088matthewvilaysack wants to merge 1 commit into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request introduces a shared fallback contract (A2uiFallbackState, A2uiFallbackInfo) in web_core and implements it in the React renderer via a new FallbackContext to handle loading and unknownComponent states consistently. By default, pending or unknown components now render nothing, with unknown types logging a warning and dispatching a COMPONENT_NOT_FOUND error. The reviewer feedback highlights several important React best practices: moving side effects (warnings and error dispatches) from the render phase into a useEffect to maintain render purity, lazily initializing the warnedTypesRef to avoid redundant Set allocations on every render, and defensively memoizing the fallbacks prop in A2uiSurface to prevent unnecessary re-renders when inline object literals are used.
5160124 to
0ff3aca
Compare
Record in a2ui-findings §7 that a2ui-project/a2ui#2088 adds a shared renderer fallback mechanism upstream; once it ships in an official @a2ui/react release, drop our local yarn patch in favor of it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0ff3aca to
d8fda9d
Compare
|
Added the Angular renderer. Same idea as React underneath. |
|
Thanks for the review. Moved the warn and dispatch into a useEffect so render stays pure, and made the warned-types Set lazy so it is not reallocated on each render. Both are in the latest push. On memoizing the fallbacks prop, I left that to the consumer on purpose. It is documented to be hoisted or memoized like any context value or render prop, and I would rather keep that explicit than hide it behind an internal memo. |
d8fda9d to
f5dcfe0
Compare
|
Added the Lit renderer, so all four packages are now in: web_core, React, Angular, and Lit. Lit keeps the loading slot but drops the visible default text, guards the nested pending path that used to throw and re-renders when the child arrives, and delivers consumer fallbacks through @lit/context so nested elements receive them. Same COMPONENT_NOT_FOUND dispatch as the other renderers, deferred out of render. The cross renderer test matrix is complete now. Ready for review whenever you have a chance. |
7054264 to
d7ee9fb
Compare
d7ee9fb to
b10d353
Compare
b10d353 to
201122a
Compare
… Angular, and Lit implementations
The React renderer showed developer debug placeholders ([Loading {id}...]
divs and red Unknown component text) to end users. This adds the shared
fallback contract agreed in the a2ui-project#2013 discussion: a types-only contract in
web_core, silent defaults in all three web renderers (warn once and
dispatchError instead of user-visible debug text), and a consumer-facing
fallbacks API per renderer (React prop, Angular TemplateRef inputs plus DI
token, Lit slots plus @lit/context), with tests, docs, and changelog
entries for each package.
Fixes a2ui-project#2013
201122a to
b80bb46
Compare
|
@ditman Can you take a look at this PR when you get a chance? Thanks! |
Description
The React renderer shows developer debug placeholders (
[Loading {id}...]divs and redUnknown component:text) to end users. This PR adds the shared fallback mechanism designed and agreed on in #2013: a small types-only contract in web_core, plus all three web renderers (React, Angular, Lit) implementing it. Fixes #2013.Per the discussion with @ditman, this PR grew renderer by renderer so review could start early. All four packages are in now: web_core, React, Angular, and Lit, each following the shared contract with its own idiomatic delivery mechanism. Docs and the framework adapter blueprint are included too.
the design comment sketched a single info interface with two states, and the landed version is a three arm discriminated union that adds an
errorstate and an optionalparentComponentTypeto address feedback on the issue.What is in so far:
src/v0_9/rendering/fallback.ts, a types-only discriminated union with three states (loading,unknownComponent,error), an info payload carryingcomponentId, optionalcomponentTypeandparentComponentType, andreasonfor the error state. Exported from the v0_9 barrel, additive and non-breaking (verified: existing build and all 271 web_core tests pass unchanged, and the react package typechecks against the new types with zero edits to its own declarations).console.warn(same message text as the Lit renderer), and adds an optionalfallbacksprop onA2uiSurface(ReactNodeor(info) => ReactNodeper state) delivered through a context so nestedDeferredChildchildren receive it. TheunknownComponentpath also dispatchessurface.dispatchError({code: 'COMPONENT_NOT_FOUND', message, componentId, componentType})once per distinct unknown type, deferred to a microtask so anonErrorlistener can safely set state.loadingTemplateandunknownComponentTemplateTemplateRefinputs onSurfaceComponent, rendered throughngTemplateOutletwith component context. An optionalA2UI_FALLBACK_TEMPLATESinjection token, mirroringA2UI_RENDERER_CONFIG, carries the templates to nested component hosts. TheunknownComponentpath dispatches the sameCOMPONENT_NOT_FOUNDas React.Loading surface...default is removed while the<slot name="loading">is preserved for consumer projection. A nested child referenced before it streams no longer throws; it renders the fallback or nothing and re-renders when the component arrives via a targeted subscription that is cleaned up on disconnect and surface change. Consumer fallbacks are delivered through@lit/contextso nested elements receive them, and theunknownComponentand error paths dispatch through the same shared channel, deferred out of render.To be explicit about the behavior change: removing the visible React debug divs is deliberate and is the fix this issue asks for. It is a rendering default change, not an API break; every existing prop and export keeps working, and consumers who want visible fallbacks get them via the new
fallbacksprop. I know a v1.0 restructure is in flight; this diff is small and additive on the v0_9 surface, and I am happy to rebase or adapt it if the restructure moves these files.Tests: each renderer adds a matrix asserting render-nothing defaults for both states and consumer-provided fallbacks including nested delivery, plus dispatch dedup and, where relevant, subscription cleanup and cross-surface isolation. The six legacy placeholder-text assertions across the renderers are updated. React suite 462 passing, Angular suite 269 passing, Lit suite 109 passing, web_core suite 271 passing.
Docs: each renderer README gains a copy-paste custom-fallback example, and
blueprints/modules/a2ui_framework_adapter.blueprint.mddocuments the shared fallback contract so other renderers can adopt it.Pre-launch Checklist
One time:
For this PR:
(The diff is web only, so
e2e_test.sh's Flutter path is not exercised; the four web packages build and their test suites pass locally.)If you need help, consider asking for advice on the discussion board.
Demo
Recorded against the React gallery app with a demo surface containing a child that never arrives (loading) and an unregistered component type (unknownComponent). First half shows the new silent defaults, second half the same surface with a consumer-provided
fallbacksprop. The gallery wiring and demo example are local harness only, not part of this PR.fallbacksprop