restage_a2ui 0.1.1 copy "restage_a2ui: ^0.1.1" to clipboard
restage_a2ui: ^0.1.1 copied to clipboard

Fail-closed pre-render capability checks and the Restage capability sidecar for apps rendering cached A2UI (Google genui) surfaces.

restage_a2ui #

pub package ci license

The app-side half of Restage's A2UI emit target: a fail-closed, pre-render capability check plus a capability sidecar for cached A2UI payloads.

Restage's build-time toolchain can emit your widget catalog as an A2UI component catalog (a catalog of widget schemas a generative-UI model renders against, via Google's genui SDK). restage_a2ui is what an app uses on the other end: it checks an A2UI payload against the catalog the app actually registered before handing it to genui, so a payload your build can't render faithfully fails with a clean, actionable diagnostic instead of throwing mid-render.

What this is — and is not #

  • It is an app-side safety wrapper: a pre-render check + a capability sidecar you can wrap your cached payloads in.
  • It is not an A2UI delivery mechanism. It does not fetch, host, or stream A2UI — your app (or your model session) owns that. It does not generate the catalog either; that is the build-time toolchain.
  • It depends on genui. Your app already depends on genui to render A2UI, so this adds no new render stack — only the check.

Quickstart #

Add the dependency:

dependencies:
  restage_a2ui:
    path: packages/restage_a2ui  # not yet published to pub.dev
  genui: ^0.9.2

Build the check once (it is immutable — reuse it for every payload), then gate each cached payload before handing it to genui:

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:genui/genui.dart';
import 'package:restage_a2ui/restage_a2ui.dart';

// 1. The genui catalog your build emitted — the `CatalogItem` set genui renders
//    against. `buildRestageCatalogItems()` is generated by the toolchain (see
//    "Producing the catalog and stamp" below).
final catalog = Catalog(buildRestageCatalogItems());

// 2. What that catalog PROVIDES, parsed from the `restageCapability` block the
//    toolchain emits next to the catalog. This is the available side of the
//    version check; supply it so Restage-stamped payloads can be verified.
final installed = A2uiInstalledCapability.fromStampJson(restageCapability);

// 3. The check — one instance, reused.
final check = RestageA2uiPreRenderCheck(catalog: catalog, installed: installed);

// 4. Gate every payload before render. `check.check` accepts either a raw A2UI
//    payload or a Restage sidecar wrapping one.
Widget? renderCached(String cachedJson) {
  final cached = jsonDecode(cachedJson) as Map<String, Object?>;
  switch (check.check(cached)) {
    case A2uiRenderable():
      // Safe to render. If you cached the sidecar (recommended — it carries the
      // version stamp), unwrap it and hand the inner payload to genui:
      final payload = RestageA2uiSidecar.isRestageSidecar(cached)
          ? RestageA2uiSidecar.fromJson(cached).a2ui
          : cached;
      return renderWithGenui(payload); // your genui render call
    case A2uiRejected(:final diagnostic, :final gap):
      // Do NOT render. Fall back to a built-in surface and log why.
      debugPrint('A2UI rejected: $diagnostic${gap == null ? '' : ' ($gap)'}');
      return null;
  }
}

The check fails closed at every path: a malformed envelope, an unknown component, an unmet version, or a stamped payload with no installed descriptor all yield an A2uiRejected — never a throw at the render seam.

If you do not cache the sidecar and only have raw A2UI payloads, you can omit installed; then only the existence walk runs and any Restage-stamped payload is rejected as unverifiable (fail-closed). Caching the sidecar is what enables the version check.

Producing the catalog and stamp #

The two inputs above — buildRestageCatalogItems() and the restageCapability block — are emitted by Restage's build-time toolchain (restage_codegen), which projects your widget catalog (the built-ins plus any @RestageWidget libraries) into two artifacts:

Artifact What it is Who consumes it
The generated CatalogItem Dart (buildRestageCatalogItems()) the functional contract genui renders against — one CatalogItem per widget, with its data schema and widget builder genui, at render time
The stamped catalog document ({ restageCapability, a2uiCatalog }) the A2UI catalog JSON plus the two-axis capability stamp (built-in floor + custom-library versions) this package's check (the restageCapability block → A2uiInstalledCapability.fromStampJson)

Both are emitted over the same A2UI-emittable widget set, so they agree by construction — a widget the emitter scopes out is absent from both.

The emit entrypoints in restage_codegen are emitA2uiCatalogDart(catalog) (the CatalogItem Dart source) and emitA2uiCatalog(catalog).toJson() (the stamped { restageCapability, a2uiCatalog } document). A2UI is an opt-in emit target: a turnkey CLI/build wrapper is still landing (see Status), so today the emit is driven through these toolchain APIs. RFW remains Restage's native delivery path; A2UI emission is additive.

Why a pre-render check #

genui resolves a payload's component types against the catalog you registered at render time. A component the catalog lacks throws CatalogItemNotFoundException part-way through building the surface. And a payload your app cached against an older catalog can reference a component whose shape has since changed — a drift the app, not genui, owns (genui has no built-in payload cache; you cache the serializable A2UI JSON yourself).

restage_a2ui moves that check before render and makes it explicit:

  1. Existence walk (any payload). Every component type the payload references must exist in the catalog you registered. Works for any A2UI payload — including one generated live by a model — and catches a missing component before genui would throw.
  2. Version satisfaction (Restage-stamped payloads). A payload wrapped in a Restage sidecar carries the catalog version it was generated against. The check verifies your installed catalog meets that version — across both the built-in widgets and any custom widget libraries — so a payload that needs more than your build provides is rejected up front rather than rendered wrong.

Both fail closed: a malformed payload, an unknown component, or an unmet version yields a Rejected result with a diagnostic. The check never throws at the render seam.

The capability sidecar #

A2UI's envelope has no place for a per-payload version stamp, so Restage wraps a cached payload:

{
  "restageCapability": {
    "builtInFloor": 2,
    "requiredLibraries": [{ "namespace": "acme.widgets", "minVersion": 3 }],
    "perItemSinceVersion": { "Text": 1, "AcmeBanner": 3 }
  },
  "a2ui": { "...": "the A2UI payload" }
}

Cache the wrapper; run the check on it before rendering a2ui. The version comparison rests on the catalog's cumulative-render-support invariant: an incompatible change to a component forks a new identity, so an existence walk plus a version compare together are sound. (Without the stamp, name-existence alone is not — a same-name shape change would slip through.)

App Review #

A2UI emission is declarative-data-only — a catalog of widget schemas plus version metadata, no server-shipped executable code. This package adds only a pre-render check over that data.

Status #

Pre-1.0, tracking genui ^0.9.2 (A2UI protocol v0.9). genui is alpha and its API is expected to change; this package isolates the integration so a churn moves one place.

1
likes
0
points
580
downloads

Publisher

verified publisherrestage.dev

Weekly Downloads

Fail-closed pre-render capability checks and the Restage capability sidecar for apps rendering cached A2UI (Google genui) surfaces.

Homepage
Repository (GitHub)
View/report issues

Topics

#server-driven-ui #remote-ui #flutter

License

unknown (license)

Dependencies

flutter, genui, json_schema_builder, restage_shared

More

Packages that depend on restage_a2ui