The React Renderer
@ausus/renderer-react is the React client for AUSUS. It fetches a
ViewSchema from the HTTP API and
renders it. It is the L6 layer of the stack.
React is treated as a rendering engine only — the renderer holds no domain knowledge. Everything it draws comes from the ViewSchema.
Install
npm install @ausus/renderer-react react@18 react-dom@18
# React 19 is also supported:
# npm install @ausus/renderer-react react@^19 react-dom@^19
react and react-dom are peer dependencies (^18 || ^19). The package
is ESM-only and ships no bundled dependencies.
Public API
import {
AususProvider, useAusus,
useViewSchema, useAction,
ViewSchemaConsumer,
ListView, DetailView, ActionModal, WorkflowBadge, FieldDisplay,
inputDefault, isRequired, shapeValue, validateInputs,
} from "@ausus/renderer-react";
| Export | Kind | Purpose |
|---|---|---|
AususProvider | component | injects API base URL, tenant, and fetcher |
useAusus | hook | reads the provider context |
useViewSchema | hook | fetches a projection's ViewSchema |
useAction | hook | invokes an action |
ViewSchemaConsumer | component | fetches a projection and dispatches to a view |
ListView / DetailView | components | render a list / detail ViewSchema |
ActionModal | component | confirmation + dynamic input form for an action |
WorkflowBadge | component | colored badge for a workflow state |
FieldDisplay | component | renders one field cell by type |
inputDefault, isRequired, shapeValue, validateInputs | functions | pure form helpers — exposed for consumers building custom action UIs against the same payload contract |
Data flow
What a single rendered page does, end-to-end:
The renderer never inspects domain types directly — every choice is made from the ViewSchema. Adding a field to your plugin shows up as a new column or a new form control on the next request, with no UI change.
Usage
Wrap your app once in AususProvider, then render a projection:
import { AususProvider, ViewSchemaConsumer } from "@ausus/renderer-react";
function App() {
return (
<AususProvider apiBaseUrl="http://localhost:8080/api" tenant="acme">
<ViewSchemaConsumer projection="billing.invoice.summary" />
</AususProvider>
);
}
ViewSchemaConsumer fetches the ViewSchema, then:
- renders
ListViewif the schema'sdata.itemsis present; - renders
DetailViewifdata.itemis present (asubjectprop is required); - shows a loading state while fetching and an error state with a retry button on failure.
The provider
<AususProvider
apiBaseUrl="http://localhost:8080/api"
tenant="acme"
fetcher={customFetch} // optional — defaults to window.fetch
/>
The optional fetcher lets you inject auth headers, retries, or a test double.
It is the seam where you add the authentication the backend does not provide.
Hooks directly
const { schema, loading, error, refetch } = useViewSchema("billing.invoice.summary");
const { invoke, pending, lastError } = useAction("billing.invoice.issue");
await invoke({ subject: ref, inputs: {} });
useAction always awaits the server — there is no optimistic UI in v0.1.0.
Action forms
ActionModal builds its form entirely from action.inputs — the
ActionDescriptor.inputs array the runtime
emits. There is no entity-specific code in the renderer: pointing it at any
action that declares inputs renders the matching form.
type | Control | Submitted shape |
|---|---|---|
string | <input type="text"> (honors typeOptions.maxLength) | string |
integer | <input type="number" step="1"> | number (truncated) |
money | <input type="number" step="0.01"> + fixed currency label | { amount, currency } |
enum | <select> populated from typeOptions.options | string |
boolean | <input type="checkbox"> | boolean |
datetime | <input type="datetime-local"> | string |
| unknown | text input fallback | string |
Required inputs (required: true in the descriptor, i.e. no default and not
nullable) are marked with a * indicator and validated before submit. A
failed required-field check shows an inline ausus-input-error next to the
control and blocks submission; a server-side failure populates the modal's
top-level error block.
Actions whose inputs are empty (transitions like issue / cancel) skip
the form and show the confirmation prompt instead, matching the existing
behavior.
Update actions and prefill
When an ActionDescriptor carries initialValues (v0.2, see
Action::update(...)), ActionModal
treats the modal as an update form:
- Form state is seeded from
initialValues[name]via theinitialForhelper (compoundmoneyflattens to its amount string for the input box; the submit reconstitutes the tuple). - The submit handler builds a diff payload: only inputs whose shaped
current value differs from
initialValuesare sent. Unchanged keys are omitted, so the wire request is exactly the patch the user typed — matching the partial-PATCH semantics enforced server-side byUpdateEffect. - Required, nullable, default and
typeOptionscontinue to come from the sameFieldDescriptorshape; nothing changes for the per-input control itself.
// The renderer makes no distinction at the component level — same modal,
// same inputs, same submit hook. The presence of initialValues alone
// switches the payload-building strategy.
<ActionModal action={renameDescriptor} subject={issueRef} onClose={...} />
The exported helpers — inputDefault, isRequired, shapeValue,
validateInputs plus the v0.2 additions initialFor, isUnchanged,
buildCreatePayload, buildUpdatePayload — are the same pure functions
ActionModal uses internally. A consumer building a custom form widget
can reuse them to stay on the runtime's payload contract without
re-implementing the type-shaping rules.
Styling
The renderer emits semantic class names (ausus-table, ausus-badge,
ausus-modal, ausus-btn, …) but ships no CSS file. You provide the
stylesheet. The class names are stable and documented by their use in the
components.
Current v0.1.0 limitations
- No bundled CSS — you supply styling for the
ausus-*class names. - No router —
ViewSchemaConsumerrenders one projection; wiring list → detail navigation is the host application's job. - No optimistic UI — every action awaits the server response.
ActionModalexposes one control per field type —string/integer/money/enum/boolean/datetime. Richer editors (rich text, file uploads, related-record pickers, validation rules beyond required) are not in v0.1.0.WorkflowBadgecolours are a fixed palette keyed on common state names (DRAFT,ISSUED,PAID,CANCELLED); other states get a default colour.- The workflow-state field is detected by a heuristic (an
enumfield namedstatus).
Renderer v0.2.0-alpha.4 — peerSchemaVersion contract
Starting at @ausus/renderer-react@0.2.0-alpha.4, the package declares
a peerSchemaVersion field at the top of its package.json:
{
"peerSchemaVersion": "^1.0.0"
}
This formalises the renderer's ViewSchema compatibility window. A backend
release whose emitted schemaVersion satisfies this semver range is
consumable by the renderer. The current backend (v0.2.0-alpha.4) emits
schemaVersion: "1.0.0", which satisfies ^1.0.0.
Compatibility rules
- A backend release that does NOT change
schemaVersiondoes NOT require a renderer release. - A renderer release that adds new optional widgets/props does NOT require a backend bump.
- A
schemaVersionmajor bump (e.g.,1.x→2.x) requires a synchronised renderer release expandingpeerSchemaVersionto include the new range.
The contract is enforced by scripts/check-renderer-alignment.sh (CI
step in release-gate.yml). PRs that desynchronize the renderer and the
backend wire format fail the release gate.
npm dist-tag policy
The renderer is published to npm with this dist-tag rule:
- Pre-release tags (
-alpha.*,-beta.*,-rc.*) publish to@next. - Until a stable
1.x.xexists on npm, the latest pre-release is also promoted to@latest, sonpm install @ausus/renderer-react(default dist-tag) returns something installable. - Once a stable
1.x.xis published,@latestmoves to it and stays there; subsequent pre-releases only update@next.
Practical install commands during alpha:
# Default — gets the current alpha (because @latest is still the alpha)
npm install @ausus/renderer-react
# Explicit alpha
npm install @ausus/renderer-react@next
Related
- ViewSchema — the format this renders.
- The HTTP API — where ViewSchemas come from.
- Packages — the npm package entry.
- Release notes v0.2.0-alpha.4 — full release context.