Skip to main content

Workspace Composition React Render Runtime

This document is the canonical spec for WCP-006: Write React render runtime spec v0.

It defines how executable React UI is resolved from a WorkspaceComposition without putting React components, lazy imports, or registries inside the headless composition kernel.

Linked Decisions

  • WCP-ADR-001: host, composer, render runtime, product kit, and modules have separate responsibilities.
  • WCP-ADR-003: render handles are opaque.
  • WCP-ADR-004: hosts pass an explicit scoped ReactRenderRuntime.
  • WCP-ADR-012: v0 does not allow default user or assistant text message override.
  • WCP-ADR-014: high-level and low-level facades share one deterministic pipeline.
  • WCP-ADR-015: render runtime diagnostics are structured product inputs.

Scope

WCP-006 owns React execution behavior for @maya/assistant-ui:

  • ReactRenderRuntime behavior and runtime-facing types
  • ReactRenderPack admission, provider lookup, and instance scoping
  • handle coverage validation against WorkspaceComposition.requiredRenderHandles
  • unknown, missing, blocked, unsupported, duplicated, and failed handle behavior
  • render-runtime fallback UI for admission, lookup, preload, load, and component execution failures
  • ErrorBoundary containment policy
  • preload behavior for eager, route, visible, intent, manual, and never modes
  • client and test runtime modes
  • render-runtime diagnostics bridging through WCP-003 diagnostics
  • mapping composed contributions to render props without adding security authority

WCP-006 does not own:

  • @maya/assistant-composition execution, React, DOM, CSS, lazy loading, fallback rendering, or render-pack lookup
  • module package exports, authoring fixtures, render-pack subentry rules, or CSS subentry rules, which belong to WCP-002
  • namespace grammar, schema version semantics, fixture inventory, or diagnostic code registry, which belong to WCP-003
  • trust model, grant vocabulary, secret redaction, prohibited code-loading policy, or security incident process, which belong to WCP-004, SECURITY.md, and .docs/security/supply-chain.md
  • composition input/output shape, policy operations, runtime gating, required render handle collection, or fallback surface materialization, which belong to WCP-005
  • package install docs, export matrices, package README policy, or package consumption guidance, which belong to .docs/packages/assistant-ui.md
  • Claude product defaults, built-in route ownership, or high-level convenience facade behavior, which belong to @maya/claude-workspace

Package Boundary

The v0 React render runtime is specified for @maya/assistant-ui. The executable runtime lands in WCP-015, and composition-based AssistantWorkspace route rendering lands in WCP-018.

@maya/assistant-composition may define serializable composition and render-handle types, but it must not import React, React DOM, browser DOM APIs, CSS, render-pack factories, lazy component loaders, ErrorBoundary code, preload logic, controller objects, or fallback rendering code.

Any design that requires executable UI resolution inside @maya/assistant-composition is out of scope and requires reopening WCP-ADR-002 and WCP-ADR-004.

Explicit Inputs Only

A ReactRenderRuntime is created from host-provided render packs and host-provided runtime policies. It must not discover modules or render packs by scanning packages, reading package metadata at runtime, evaluating descriptors, using import-time registration, or consulting a global registry.

Allowed lazy loading is limited to static lazy imports inside trusted, host-bundled render packs that the host explicitly passes to the runtime.

const renderRuntime = createReactRenderRuntime({
packs: [claudeRenderPack, dataLabProdRenderPack, dataLabStagingRenderPack],
admissionPolicy,
preloadPolicy,
});

<AssistantWorkspace composition={composition} renderRuntime={renderRuntime} />;

The host passes render runtime explicitly. AssistantWorkspace must not create a hidden runtime, resolve grants, install modules, discover packs, or mutate global render state.

For the composition-based target, low-level AssistantWorkspace receives grouped controllers through its existing host-owned controller groups and executable route UI through render packs on the explicit renderRuntime. It does not accept a generic controller map for modules.

type AssistantWorkspaceCompositionInput = AssistantWorkspaceGroupedProps & {
composition: WorkspaceComposition;
renderRuntime: ReactRenderRuntime;
activeRouteKey?: CanonicalContributionKey;
activeRoutePath?: RoutePath;
};

AssistantWorkspace must not accept a generic controllers prop or moduleState prop. Product-level helpers may build render packs from controller maps before calling the low-level renderer.

Core Identity

Provider identity is the owner plus the WCP-005 stable render handle identity:

type StableRenderHandleIdentity = string;

type ReactRenderProviderKey =
`${ContributionOwner['ownerKind']}:${ContributionOwner['ownerId']}:${StableRenderHandleIdentity}`;

StableRenderHandleIdentity is an opaque string produced by the WCP-005 stable render handle serialization algorithm for renderHandleVersion, kind, id, and sorted data.

The same package can provide the same handle for two module instances. The provider for module:data-lab-prod must never satisfy a handle owned by module:data-lab-staging, even when packageId, handle kind, and handle id match.

Runtime Contract

type ReactRenderRuntimeSchemaVersion = '0.1';
type ReactRenderRuntimeMode = 'client' | 'test';

type ReactRenderRuntime = {
schemaVersion: ReactRenderRuntimeSchemaVersion;
mode: ReactRenderRuntimeMode;

validateCoverage(composition: WorkspaceComposition): ReactRenderCoverageResult;
resolve(target: ReactRenderTarget): ReactRenderResolution;
render(target: ReactRenderTarget): React.ReactNode;
preload(request: ReactRenderPreloadRequest): Promise<ReactRenderPreloadResult>;

diagnostics(): WorkspaceDiagnostic[];
events(): ReactRenderRuntimeEvent[];
subscribe(listener: ReactRenderRuntimeListener): () => void;
};

type CreateReactRenderRuntimeOptions = {
mode?: ReactRenderRuntimeMode;
packs: readonly ReactRenderPack[];
admissionPolicy?: ReactRenderAdmissionPolicy;
preloadPolicy?: ReactRenderPreloadPolicy;
fallback?: React.ComponentType<ReactRenderFallbackProps>;
onDiagnostic?: (diagnostic: WorkspaceDiagnostic) => void;
onRenderError?: (event: ReactRenderErrorEvent) => void;
test?: ReactRenderTestOptions;
};

Runtime methods are deterministic for the same composition, render packs, policies, and mode. Diagnostics are append-only within a runtime instance. Events are isolated from other runtime instances and retained according to the mode-specific event policy.

WCP-006 must define every public type referenced by the runtime contract. Implementations must not introduce private substitutes for ReactRenderResolution, ReactRenderAdmissionPolicy, ReactRenderRuntimeListener, ReactRenderFallbackProps, ReactRenderErrorEvent, ReactRenderProviderSummary, ReactRenderDuplicateProvider, ReactRenderBlockedHandle, ReactRenderUnsupportedProvider, or ReactRenderPreloadProps.

Supporting public types:

type ReactRenderResolution<TController = unknown> =
| {
status: 'resolved';
target: ReactRenderTarget;
provider: ReactRenderProviderSummary;
controller: TController;
component: ReactRenderComponent<TController>;
}
| {
status: 'lazy';
target: ReactRenderTarget;
provider: ReactRenderProviderSummary;
controller: TController;
load: Promise<ReactRenderComponent<TController>>;
}
| {
status: 'fallback';
reason: ReactRenderFallbackReason;
target: ReactRenderTarget;
diagnostic: WorkspaceDiagnostic;
};

type ReactRenderFallbackReason =
| 'missing-handle'
| 'duplicate-provider'
| 'blocked-by-admission-policy'
| 'unsupported-version'
| 'load-failed'
| 'render-failed'
| 'unknown-target';

type ReactRenderAdmissionPolicy = {
admit?: (input: ReactRenderAdmissionInput) => ReactRenderAdmissionDecision;
};

type ReactRenderAdmissionInput = {
owner: ContributionOwner;
renderHandle: RenderHandle;
provider?: ReactRenderProviderSummary;
trustTier?: 'core' | 'trusted' | 'external';
phase: 'validate' | 'preload' | 'render';
};

type ReactRenderAdmissionDecision =
| { status: 'allow' }
| { status: 'block'; reason: string; diagnostic?: WorkspaceDiagnostic };

type ReactRenderRuntimeListener = (event: ReactRenderRuntimeEvent) => void;

type ReactRenderFallbackProps = {
reason: ReactRenderFallbackReason;
target: ReactRenderTarget;
diagnostic: WorkspaceDiagnostic;
retry?: () => void;
};

type ReactRenderErrorEvent = {
target: ReactRenderEventTargetSummary;
owner: ContributionOwner;
renderHandle: ReactRenderEventHandleSummary;
error: ReactRenderErrorSummary;
componentStack?: ReactRenderComponentStackSummary;
};

type ReactRenderErrorSummary = {
redacted: true;
thrown: true;
};

type ReactRenderComponentStackSummary = {
redacted: true;
present: true;
};

type ReactRenderProviderSummary = {
owner: ContributionOwner;
packageId: string;
packageVersion?: string;
handle: RenderHandle;
loadKind: 'eager' | 'lazy';
moduleId?: string;
trustTier?: 'core' | 'trusted' | 'external';
};

type ReactRenderDuplicateProvider = {
owner: ContributionOwner;
handle: RenderHandle;
providers: readonly ReactRenderProviderSummary[];
};

type ReactRenderBlockedHandle = {
owner: ContributionOwner;
handle: RenderHandle;
reason: string;
provider?: ReactRenderProviderSummary;
};

type ReactRenderUnsupportedProvider = {
owner: ContributionOwner;
handle: RenderHandle;
reason: 'runtime-schema-version' | 'render-handle-version' | 'provider-shape';
provider?: ReactRenderProviderSummary;
};

type ReactRenderPreloadProps<TController = unknown> = {
target: ReactRenderTarget;
controller: TController;
trigger: Exclude<ReactPreloadMode, 'never'>;
};

type ReactRenderContext = {
reason?: 'route' | 'visible' | 'intent' | 'manual';
routePath?: RoutePath;
};

Render Pack Contract

type ReactRenderPack<TController = unknown> = {
renderRuntimeSchemaVersion: ReactRenderRuntimeSchemaVersion;
owner: ContributionOwner;
packageId: string;
packageVersion?: string;
controller: TController;
trustTier?: 'core' | 'trusted' | 'external';
providers: readonly ReactRenderProvider<TController>[];
};

type ReactRenderProvider<TController = unknown> = {
handle: RenderHandle;
load:
| { kind: 'eager'; component: ReactRenderComponent<TController> }
| { kind: 'lazy'; load: () => Promise<ReactRenderModule<TController>>; moduleId: string };
preloadHint?: ReactPreloadMode;
preload?: (props: ReactRenderPreloadProps<TController>) => Promise<void> | void;
};

type ReactRenderModule<TController = unknown> =
| ReactRenderComponent<TController>
| { default: ReactRenderComponent<TController> };

type ReactRenderComponent<TController = unknown> = (
props: ReactRenderProps<TController>,
) => React.ReactNode;

Render pack rules:

  • every pack belongs to exactly one ContributionOwner
  • module render packs use ownerKind: 'module' and ownerId equal to instanceId
  • built-in render packs use ownerKind: 'builtin'
  • the pack-level controller is passed only to components resolved from that pack
  • a controller must not be shared across different instanceId values
  • eager providers carry an already imported component
  • lazy providers carry a static host-bundled loader declared inside the trusted render pack
  • lazy loaders must not be derived from module descriptors, render handles, user input, network data, composed output, or runtime policy
  • render packs must not register providers globally or mutate runtime state at import time

The external trust tier is reserved for future untrusted plugin work. In v0, executable external packs must be rejected or treated as non-renderable by host policy.

If admissionPolicy is omitted, the default v0 policy treats an omitted trustTier as trusted, allows core and trusted, blocks external, blocks owner mismatches before loader execution, and rejects unsupported render runtime schema versions before provider preload, lazy load, or component execution.

Render Target And Props

type ReactRenderableContribution =
| ComposedRoute
| ComposedSurface
| ComposedRenderer
| ComposedMessageAppender
| ComposedFallbackSurface;

type ReactRenderTarget = {
contribution: ReactRenderableContribution;
context?: ReactRenderContext;
};

type ReactRenderProps<TController = unknown> = {
contribution: ReactRenderableContribution;
owner: ContributionOwner;
contributionKey: CanonicalContributionKey;
contributionKind: ContributionKind;
renderHandle: RenderHandle;
runtime?: ComposedRuntimeSummary;
metadata?: CompositionJsonObject;
controller: TController;
context?: ReactRenderContext;
};

Render props are an adapter view of the composed contribution. They do not grant authority, resolve runtime, authorize backend access, or expose secrets. WCP-004 redaction rules apply before a target or prop object reaches module code.

Coverage Validation

Coverage validation answers whether the supplied render packs can satisfy the active handles required by a composition.

type ReactRenderCoverageResult = {
ok: boolean;
checked: readonly RequiredRenderHandle[];
provided: readonly ReactRenderProviderSummary[];
missing: readonly RequiredRenderHandle[];
duplicates: readonly ReactRenderDuplicateProvider[];
blocked: readonly ReactRenderBlockedHandle[];
unsupported: readonly ReactRenderUnsupportedProvider[];
diagnostics: readonly WorkspaceDiagnostic[];
};

Rules:

  • validation can run without mounting React
  • validation is metadata-only: it must not call render(), mount React, execute provider components, invoke provider preload, or invoke lazy load() functions
  • validation compares providers by owner key plus WCP-005 stable render handle identity
  • composition.requiredRenderHandles is the only required-handle manifest
  • providers not required by the current composition are allowed but do not affect ok
  • missing providers produce structured diagnostics and production fallback remains possible
  • duplicate providers for the same owner and handle produce diagnostics and are not resolved by array order
  • unsupported render runtime schema versions produce diagnostics
  • blocked providers are rejected by admission policy before lazy loader execution
  • provider owner mismatch produces diagnostics and must not fall back to a same-package provider from another instance

WCP-003 owns concrete diagnostic codes, severities, required details, and cross-platform fixture inventory. WCP-006 defines the render-runtime situations that need diagnostics, but it must not create private code strings outside the WCP-003 registry.

Resolution Rules

resolve(target) runs in this order:

  1. Read target.contribution.owner and target.contribution.renderHandle.
  2. Build provider key from owner plus stable handle identity.
  3. Apply admission policy before loading or rendering.
  4. Require exactly one provider for the key.
  5. For eager providers, return the component and controller.
  6. For lazy providers, return a lazy resolution that caches the loader promise.
  7. Attach per-surface ErrorBoundary metadata.
  8. On missing, duplicate, blocked, unsupported, load failure, or render failure, emit structured diagnostics and return fallback resolution.

resolve(target) is a low-level API for tests, diagnostics, preloading integrations, and advanced host adapters. Product renderers should call render(target) for executable UI so per-target ErrorBoundary containment and host fallback behavior are applied. A host that calls resolve() and then executes returned components directly owns that containment and must not treat it as the default workspace rendering path.

render(target) calls resolve(target) and returns either the resolved React node or host-owned fallback UI. It must never throw for ordinary missing, duplicate, blocked, or unsupported handle cases in production mode. Test mode may rethrow render failures after recording diagnostics when configured to do so.

Unknown, Blocked, And Failed Handles

Unknown or missing handles fail closed to host-owned fallback UI plus structured diagnostics. The fallback may name the owning module instance, contribution kind, and handle summary, but it must not include raw props, controller objects, thrown error payloads, secrets, or stack traces.

blocked means rejected by the render runtime admission policy before component execution. It does not mean frontend grant denial or backend authorization failure.

Render failures are isolated to the owning surface. A route render failure must not crash the entire workspace shell. A renderer failure must not corrupt the surrounding transcript, tool list, or activity list. An appender failure must not hide the base message.

ErrorBoundary Policy

Every rendered route, configuration surface, integration surface, custom renderer, message appender, tool renderer, activity renderer, and fallback surface is wrapped by a per-surface ErrorBoundary.

ErrorBoundary behavior:

  • records a redacted structured diagnostic
  • calls onRenderError with redacted target, handle, thrown-error, and component-stack summaries
  • renders host-owned fallback UI for that surface
  • keeps sibling composed surfaces rendering
  • provides retry only when the host fallback component supplies it
  • never serializes raw error objects or component stacks into WorkspaceDiagnostic.details, runtime events, or onRenderError

onRenderError is for host-owned logging hooks and must remain redacted: it may report that a throw occurred and that a component stack was present, but it must not include the thrown value, error message, stack text, raw ReactRenderTarget, raw RenderHandle.data, props, runtime payloads, or controller objects.

Preload Policy

type ReactPreloadMode = 'eager' | 'route' | 'visible' | 'intent' | 'manual' | 'never';

type ReactRenderPreloadPolicy = {
defaultByKind?: Partial<Record<ContributionKind, ReactPreloadMode>>;
overrides?: readonly ReactRenderPreloadOverride[];
};

type ReactRenderPreloadOverride = {
target:
| CanonicalContributionKey
| { owner: ContributionOwner; handle: RenderHandle };
mode: ReactPreloadMode;
};

type ReactRenderPreloadRequest = {
trigger: Exclude<ReactPreloadMode, 'never'>;
target: ReactRenderTarget;
};

type ReactRenderPreloadResult = {
status: 'loaded' | 'already-loaded' | 'skipped' | 'blocked' | 'missing' | 'failed';
diagnostics: readonly WorkspaceDiagnostic[];
};

Default eager versus lazy policy:

  • product kits may choose eager providers for first-paint shell, navigation, or default transcript surfaces
  • built-in product pages may be eager when they are needed for first paint or stable navigation
  • module routes default to lazy with route preload
  • configuration, integration, and fallback surfaces default to lazy with visible preload
  • message, tool, activity, and appender renderers default to lazy with visible preload
  • hover, focus, command-menu, or sidebar intent may trigger intent preload
  • manual preload is called directly by host code
  • never disables proactive preload but still permits loading during render

Effective preload mode is resolved deterministically in this order: matching overrides, provider preloadHint, defaultByKind[contributionKind], then the WCP-006 default for that contribution kind. Override arrays are explicit host policy; host conformance should keep them unambiguous before runtime creation.

never skips proactive eager, route, visible, and intent triggers. manual is host-directed and may preload a never target unless admission policy blocks it.

preload() does not resolve CanonicalContributionKey by itself. AssistantWorkspace or host code may keep a composition-local key index and convert keys into full ReactRenderTarget objects before calling the runtime.

preload() caches provider preload work and provider loader promises. Repeated preload for an already preloaded and loaded provider returns already-loaded without calling provider preload again. It must not execute remote code, install packages, derive imports from handles, or use module-owned raw network clients. Provider preload functions may warm host-owned controller data only through the same intentional controller boundary used for rendering.

Client And Test Modes

Runtime modes in v0:

  • client: normal browser rendering, preload, Suspense, ErrorBoundary, and event behavior
  • test: deterministic event log, optional throw-after-diagnostic render failures, and no dependency on timers or DOM observers
type ReactRenderTestOptions = {
throwOnRenderFailure?: boolean;
throwOnDiagnostic?: boolean;
};

Test mode must support multiple independent runtime instances in one process.

ErrorBoundary semantics are client-render semantics. Future SSR support requires a separate decision and must use host-owned try/catch containment that emits the same redacted diagnostic and deterministic placeholder or fallback result. SSR mode must not be implied by the v0 client/test runtime contract.

Diagnostics Bridge

Render runtime diagnostics use WCP-003 WorkspaceDiagnostic objects with origin: 'render-runtime' and WCP-004 redaction rules.

Diagnostic situations include:

  • required handle has no provider
  • more than one provider claims the same owner and handle
  • provider is blocked by admission policy
  • provider or runtime schema version is unsupported
  • lazy provider load fails
  • component render fails inside runtime boundary
  • provider owner does not match the target owner
  • fallback UI is used for an unknown target

Human messages are not compatibility contracts. Hosts and tests assert diagnostic origin, category, stable details, subject, linked decision IDs, and WCP-003-registered codes.

Runtime Events

type ReactRenderRuntimeEvent =
| { type: 'coverage:validated'; result: ReactRenderCoverageEventSummary }
| { type: 'preload:scheduled'; trigger: Exclude<ReactPreloadMode, 'never'>; target: ReactRenderEventTargetSummary }
| { type: 'preload:started'; trigger: Exclude<ReactPreloadMode, 'never'>; target: ReactRenderEventTargetSummary }
| { type: 'preload:resolved'; trigger: Exclude<ReactPreloadMode, 'never'>; target: ReactRenderEventTargetSummary }
| { type: 'preload:failed'; trigger: Exclude<ReactPreloadMode, 'never'>; target: ReactRenderEventTargetSummary }
| { type: 'preload:skipped'; trigger: Exclude<ReactPreloadMode, 'never'>; target: ReactRenderEventTargetSummary }
| { type: 'preload:blocked'; trigger: Exclude<ReactPreloadMode, 'never'>; target: ReactRenderEventTargetSummary }
| { type: 'preload:missing'; trigger: Exclude<ReactPreloadMode, 'never'>; target: ReactRenderEventTargetSummary }
| { type: 'resolve:missing'; target: ReactRenderEventTargetSummary }
| { type: 'resolve:blocked'; target: ReactRenderEventTargetSummary }
| { type: 'render:failed'; target: ReactRenderEventTargetSummary };

For a preload call, events are emitted in this order: preload:scheduled, then preload:started only when loading or provider preload work begins, then exactly one terminal event. Terminal events cover preload:resolved, preload:failed, preload:skipped, preload:blocked, and preload:missing; an already-loaded result emits preload:scheduled followed by preload:resolved.

Events are process-local runtime observations. They are useful for tests, development diagnostics, and performance tuning. They are not persisted security evidence and must not contain secrets. Event targets are redacted summaries: they may include owner identity, contribution key/kind/local id, render handle kind/id/version, and handle data key counts, but never raw handle data, contribution metadata, runtime payloads, controller objects, component references, or thrown errors. Coverage events carry counts plus redacted diagnostics instead of raw required-handle arrays. Full event retention is required only in test mode. Outside test mode, runtimes must either retain no events or retain only a bounded recent event buffer, while diagnostics remain available through diagnostics().

Prohibited Loading And Discovery

WCP-006 applies WCP-004 prohibited code-loading policy to React render-runtime execution. It does not define a second forbidden-path list; workspace-composition-security-trust.md remains canonical for security vocabulary and prohibited code-loading paths.

For render-runtime semantics, ReactRenderRuntime must accept only explicit host-provided render packs. Any implementation that requires global render registries, import-time registration, automatic render-pack discovery, package scanning, side-effect render-pack loading, runtime module installation, remote-code loading prohibited by WCP-004, including remote ESM, or dynamic imports derived from descriptors, render handles, user input, network data, runtime policy, or composed output is out of scope and must be rejected before provider load or component execution.

Fixture Map

WCP-006-owned fixture IDs:

  • fixture-render-runtime-happy-eager-core
  • fixture-render-runtime-happy-lazy-module-route
  • fixture-render-runtime-handle-coverage-complete
  • fixture-render-runtime-missing-handle
  • fixture-render-runtime-duplicate-provider
  • fixture-render-runtime-blocked-handle
  • fixture-render-runtime-unknown-target-fallback
  • fixture-render-runtime-render-failed
  • fixture-render-runtime-error-boundary-isolation
  • fixture-render-runtime-error-boundary-lifecycle-mount
  • fixture-render-runtime-renderer-appender-isolation
  • fixture-render-runtime-preload-eager-core
  • fixture-render-runtime-preload-default-transcript-eager-policy
  • fixture-render-runtime-preload-route-lazy-module-page
  • fixture-render-runtime-preload-visible-and-intent
  • fixture-render-runtime-preload-never-and-manual
  • fixture-render-runtime-preload-terminal-states
  • fixture-render-runtime-provider-shape-fail-closed
  • fixture-render-runtime-future-schema-provider-shape-fail-closed
  • fixture-render-runtime-preload-failure-phase
  • fixture-render-runtime-events-redact-target-handle-data-values
  • fixture-render-runtime-test-mode-event-log
  • fixture-render-runtime-multi-workspace-isolation
  • assistant-ui-render-runtime-diagnostics--render-runtime-diagnostics-matrix

These fixtures add render-runtime behavior expectations. They reuse WCP-003 diagnostic registration and WCP-005 composition fixtures instead of redefining either.

Future Test Derivation

WCP-015 derives tests for runtime construction, explicit pack input, scoped instances, handle resolution, unknown handle fallback, diagnostics callback, and multi-runtime isolation.

WCP-016 derives tests for validateCoverage, complete coverage, missing providers, duplicate providers, blocked providers, owner mismatch, validation without render, and required handles from WCP-005 fixtures.

WCP-017 derives tests for ErrorBoundary isolation, mounted ErrorBoundary lifecycle fallback, render failure diagnostics, preload event order, eager core/default transcript policy, explicit preload policy resolution, provider-shape fail-closed handling, redacted runtime events, Storybook diagnostics states, and test-mode event logs. SSR placeholders require a later SSR-specific decision.

Future verifier hooks:

  • render runtime fixture verification writes an ignored .tmp report
  • render runtime boundary verification rejects global registries, auto-discovery, URL-derived dynamic imports, and render-pack side-effect registration
  • public API surface verification includes exported runtime types once @maya/assistant-ui exposes them

Acceptance Checklist

  • render handles are opaque and never contain React components, lazy import functions, JSX, or render callbacks
  • host passes render runtime explicitly
  • runtime accepts explicit render packs only
  • unknown handle behavior is defined as redacted host-owned fallback UI plus structured diagnostics
  • eager versus lazy default policy is defined
  • coverage validation runs without rendering
  • missing, duplicate, blocked, unsupported, load-failed, and render-failed cases produce structured diagnostics
  • same package enabled twice resolves each instance to its own controller
  • multiple runtime instances can coexist without shared mutable registries
  • trust policy blocks before lazy loader execution
  • lazy loader promises are cached across preload and render
  • render failure is isolated to the owning surface
  • client and test modes do not require global runtime state

Duplicate Truth Boundary

WCP-006 owns React render runtime execution semantics.

It links instead of copying:

  • WCP-002 for module render-pack authoring, package exports, CSS subentries, and module fixtures
  • WCP-003 for namespace grammar, diagnostic code registry, schema version fields, and cross-platform fixture inventory
  • WCP-004 for build-time trust, redaction, prohibited code loading, and security vocabulary
  • WCP-005 for WorkspaceComposition, RequiredRenderHandle, render handle identity, composition policy, runtime gating, and fallback surface materialization
  • .docs/packages/assistant-ui.md for package consumption guidance
  • future @maya/claude-workspace specs for Claude product defaults and convenience facades