Skip to content

Add shared renderer fallback mechanism: web_core contract + React implementation (#2013) - #2088

Open
matthewvilaysack wants to merge 1 commit into
a2ui-project:mainfrom
matthewvilaysack:fallback-2013
Open

Add shared renderer fallback mechanism: web_core contract + React implementation (#2013)#2088
matthewvilaysack wants to merge 1 commit into
a2ui-project:mainfrom
matthewvilaysack:fallback-2013

Conversation

@matthewvilaysack

@matthewvilaysack matthewvilaysack commented Jul 24, 2026

Copy link
Copy Markdown

Description

The React renderer shows developer debug placeholders ([Loading {id}...] divs and red Unknown 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 error state and an optional parentComponentType to address feedback on the issue.

What is in so far:

  • web_core: src/v0_9/rendering/fallback.ts, a types-only discriminated union with three states (loading, unknownComponent, error), an info payload carrying componentId, optional componentType and parentComponentType, and reason for 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).
  • React: renders nothing by default in both fallback states, keeps developer signal via console.warn (same message text as the Lit renderer), and adds an optional fallbacks prop on A2uiSurface (ReactNode or (info) => ReactNode per state) delivered through a context so nested DeferredChild children receive it. The unknownComponent path also dispatches surface.dispatchError({code: 'COMPONENT_NOT_FOUND', message, componentId, componentType}) once per distinct unknown type, deferred to a microtask so an onError listener can safely set state.
  • Angular: same defaults, nothing renders and the existing console warnings still fire. Consumers pass optional loadingTemplate and unknownComponentTemplate TemplateRef inputs on SurfaceComponent, rendered through ngTemplateOutlet with component context. An optional A2UI_FALLBACK_TEMPLATES injection token, mirroring A2UI_RENDERER_CONFIG, carries the templates to nested component hosts. The unknownComponent path dispatches the same COMPONENT_NOT_FOUND as React.
  • Lit: the visible 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/context so nested elements receive them, and the unknownComponent and 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 fallbacks prop. 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.md documents the shared fallback contract so other renderers can adopt it.

Pre-launch Checklist

One time:

For this PR:

  • I have updated the relevant CHANGELOG.md file.
  • I updated/added relevant documentation.
  • My code changes (if any) have tests.
  • If my branch is on a fork, I have verified that scripts/e2e_test.sh passes.

(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 fallbacks prop. The gallery wiring and demo example are local harness only, not part of this PR.

fallback demo

Default (renders nothing, warns once + dispatchError) Custom fallbacks prop
default custom

@google-cla

google-cla Bot commented Jul 24, 2026

Copy link
Copy Markdown

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread renderers/react/src/v0_9/A2uiSurface.tsx
Comment thread renderers/react/src/v0_9/A2uiSurface.tsx Outdated
Comment thread renderers/react/src/v0_9/A2uiSurface.tsx
@matthewvilaysack
matthewvilaysack force-pushed the fallback-2013 branch 2 times, most recently from 5160124 to 0ff3aca Compare July 24, 2026 14:47
retz8 added a commit to retz8/a2ui-github that referenced this pull request Jul 25, 2026
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>
@matthewvilaysack

Copy link
Copy Markdown
Author

Added the Angular renderer. Same idea as React underneath. loadingTemplate and unknownComponentTemplate are TemplateRef inputs on SurfaceComponent, and there is an optional A2UI_FALLBACK_TEMPLATES token so the templates reach nested hosts too, mirroring how A2UI_RENDERER_CONFIG already works. Defaults do not change. Nothing renders and the existing console warnings still fire. unknownComponent also dispatches COMPONENT_NOT_FOUND with the same shape and message as React. Added Karma specs for all of it, suite is green. Lit is next on this same PR.

@matthewvilaysack

Copy link
Copy Markdown
Author

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.

@github-actions github-actions Bot added the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Jul 26, 2026
@matthewvilaysack

Copy link
Copy Markdown
Author

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.

@github-actions github-actions Bot removed the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Jul 26, 2026
@github-actions github-actions Bot added the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Jul 28, 2026
@github-actions github-actions Bot removed the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Jul 28, 2026
… 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
@matthewvilaysack

Copy link
Copy Markdown
Author

@ditman this is the implementation of what we discussed on #2013, all four packages are in and the branch is cleaned up to a single commit with a demo in the description. Ready for your pass whenever you get a chance.

@github-actions github-actions Bot added the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Jul 30, 2026
@nan-yu

nan-yu commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

@ditman Can you take a look at this PR when you get a chance? Thanks!

@nan-yu nan-yu added status: needs review and removed status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs labels Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: React renderer shows developer [Loading {id}...] placeholders to end users

2 participants