# CFC UI Helper Contract
**Status:** Draft contract
**Scope:** `packages/api`, `packages/ts-transformers`,
`packages/schema-generator`, runner UI builder/runtime integration
This document specifies the compile-time and builder-time contract for the CFC
UI helpers. These helpers are authoring sugar for trusted
UI contracts; they are not generic component macros. The helpers do not mint
final CFC integrity atoms by themselves. Instead, they lower into schema-time UI
contract hints plus `data-ui-*` runtime markers that trusted renderer/runtime
code later combines with concrete provenance data to mint the normative atoms.
## Source Of Truth
This document is normative for the CFC-specific UI helper surface.
Related contracts:
- `docs/specs/ts-transformer/cfc_authoring_contract.md`
- `docs/specs/ts-transformer/ts_transformers_current_behavior_spec.md`
## Recognized Helpers
The helper set is closed and explicit.
| Helper | Default emitted tag | Semantic props | Emitted data attrs | Emitted schema hint | Normative runtime bridge |
| --- | --- | --- | --- | --- | --- |
| `UiAction` | `ct-button` | `as`, `action` | `data-ui-action` | `{ helper: "UiAction", action }` | lets trusted UI/runtime bind gesture targets to a named action without introducing a helper-specific atom into stored integrity |
| `UiPromptSlot` | `ct-textarea` | `as`, `surface`, `role` | `data-ui-surface`, `data-ui-role` | `{ helper: "UiPromptSlot", surface, role }` | lets trusted UI/runtime mint concrete `UserSurfaceInput` and `PromptSlotBound` atoms when a user submits/binds a value through the slot |
| `UiDisclosure` | `ct-card` | `as`, `kind` | `data-ui-disclosure-kind` | `{ helper: "UiDisclosure", kind }` | lets trusted UI/runtime mint disclosure-related atoms such as rendered-warning / acknowledgment evidence without inventing helper-only atoms |
Adding a new helper requires coordinated changes across API exports, JSX
rewriting, schema hint synthesis, and builder/runtime helper code.
## JSX Rewrite Contract
`CfcJsxTransformer` owns the compile-time rewrite.
Given a recognized helper element:
1. Replace the helper tag with the intrinsic tag from `as` or the helper's
default tag.
2. Remove helper-only props from the semantic prop bag.
3. Preserve all non-helper props and all children.
4. Re-emit the semantic props as `data-ui-*` attributes on the intrinsic node.
Example:
```tsx
// Shown for illustration only.
Go
```
lowers to:
```tsx
// Shown for illustration only.
Go
```
The builder/runtime helper implementation must emit the same runtime shape so
authored use and transformed use converge.
## Schema Hint Contract
The helper rewrite also has a schema side effect.
When the helper's required semantic props are compile-time string literals,
`CfcJsxTransformer` must attach a node-local schema hint of the form:
```ts
// Shown for illustration only.
{
cfcUiContract: {
helper: "UiAction" | "UiPromptSlot" | "UiDisclosure";
// helper-specific literal fields
}
}
```
This is compile-time-only metadata. It does not change the JSX runtime value,
and it is intentionally not itself a final integrity atom. The final atoms are
minted later by trusted runtime code once concrete user, value-digest, source
reference, kernel, render-frame, and acknowledgment details are known.
## Literal Versus Non-Literal Inputs
This distinction is part of the contract:
- If the relevant helper props are present at all, the rewritten JSX still gets
the corresponding `data-ui-*` attributes.
- If those props are not literal strings, no compile-time `uiContract` hint
may be synthesized for the schema.
In other words:
- runtime DOM/VNode tagging works with dynamic expressions
- schema-level UI contract hints only exist for statically known literals
That asymmetry is intentional and must be documented in user-facing guidance.
## Normative Runtime Bridge
The UI helpers exist to seed trusted runtime evidence using the registered CFC
atoms already used elsewhere in the spec.
- `UiPromptSlot(surface, role)` does not directly add integrity. Instead, it
marks a UI subtree so trusted renderer/runtime code can later mint:
- `UserSurfaceInput { user, surface, valueDigest, snapshotDigest? }` when a
user submits a concrete value through that surface
- `PromptSlotBound { source, role, kernelName, subject?, surface }` when a
trusted kernel binds that submitted value into a prompt slot
- `UiAction(action)` marks a concrete gesture/action target. Trusted
renderer/runtime code combines that with render-frame/gesture provenance to
derive the correct semantic event. The helper does not introduce a
helper-specific persistent atom.
- `UiDisclosure(kind)` marks disclosure/acknowledgment UI. Trusted
renderer/runtime code later turns actual rendered and acknowledged disclosures
into the appropriate registered atoms such as rendered-warning,
acknowledgment, or disclaimer-attached evidence.
This bridge is the load-bearing alignment point: the helper contract is only
valid if its emitted schema hints can be consumed to produce the normative atom
shapes that the runner and policy specs already understand.
## `[UI]` Schema Synthesis Contract
`SchemaInjectionTransformer` is responsible for turning returned JSX into a
schema hint for the result type's `[UI]` member.
Required behavior:
1. Find the JSX subtree returned from the pattern builder's `[UI]` property.
2. Find the corresponding `[UI]` property type node on the output type.
3. Synthesize a VNode-like schema for the returned tree.
4. Copy any node-local `cfcUiContract` hints onto the matching synthesized
schema nodes as `ifc.uiContract`.
The synthesized shape is a recursive VNode object with:
- `type`
- `name`
- `props`
- `children`
and render-leaf fallbacks for text/expressions.
## Explicit Output Schema Parity
A failure mode to avoid is inferred output schemas and explicit
`pattern(..., explicitOutputSchema)` calls receiving different `[UI]` helper
hints.
Normative rule:
- if `pattern()` has a statically recoverable output type and a returned JSX
subtree for `[UI]`, then `[UI]` schema hint seeding must happen regardless of
whether the final output schema is inferred inline or supplied explicitly via
a `toSchema