SIGN IN SIGN UP

feat(react): rebuild @domainkit/react on the 0.9 transport (#90)

## Summary

`@domainkit/react` 0.9: two components for the whole domain lifecycle,
over the capability-gated
transport from `domainkit/client`.

```tsx
const transport = Transport.fromFetch("/api/domainkit")

<DomainKit.Root transport={transport} colorScheme="inherit">
  <Domain.Flow domain="app.example.com" requirements={requirements} />
</DomainKit.Root>
```

Own one piece and keep the rest:

```tsx
<Domain.Flow
  domain="app.example.com"
  requirements={requirements}
  slots={{ records: ({ records, readiness }) => <MyTable records={records} readiness={readiness} /> }}
  onApplied={(receipt) => track(receipt.id)}
/>
```

## Controllers

Every controller takes one options object and returns a named
`Controller` whose `State` is a
`Data.taggedEnum` carrying the core models: `Plan.Model`,
`Approval.Model`, `Receipt.Model`,
and `DomainKit.Error`.

| Hook | Options | Verbs |
| --- | --- | --- |
| `Connect.useController` | `{ domain }` | `connect`, `reuse`, `select`,
`detach`, `disconnect`, `refresh`, `retry` |
| `Provision.useController` | `{ domain, requirements, onApplied? }` |
`plan`, `approve`, `reject`, `apply`, `retry`, `reset` |
| `Cleanup.useController` | `{ domain, receiptId?, onCleaned? }` | same
|
| `Verify.useController` | `{ domain, polling? }` | `observe`, `retry` |

`approve` authorizes the digest and applies it. `reject` records the
refusal through
`POST /plans/:planId/rejections`; the attempt is terminal and the flow
shows it. `retry` re-plans
when the reason says the plan is gone (`Stale`, `Expired`, `Conflict`)
and re-runs the failed step
otherwise.

## Transport by value

`DomainKit.Root` takes a `Transport.Interface` by value and keeps its
identity for the whole
mount, so this does not restart the controllers on every render:

```tsx
<DomainKit.Root transport={Transport.fromFetch("/api/domainkit")}>
```

Each method reads the newest transport when it runs; only a change to
the declared capability
groups rebuilds it. `revision` re-inspects every mounted domain.

## Rendering from the wire

- Token forms come from `Snapshot.providers[].methods[].fields`: one
input per declared field,
`type="password"` when the descriptor says `secret`. No per-provider
form code.
- `connection.discover` runs on mount for a domain with no connection,
and the form offers the
  connection that already serves the zone.
- Failures render through `Messages.Catalog`, one sentence per
`DomainKit.Error` reason, keyed by
  the `Reason` tags. No `_tag` reaches a customer.
- Parts render only what `Transport.capabilities()` declares.

## Slots

| Slot | Receives | Default |
| --- | --- | --- |
| `connection` | `{ controller, domain }` | `Connect.Card` once
connected, `Connect.Dialog` until then |
| `records` | `{ records, readiness, controller, domain }` |
`Records.Table` |
| `verification` | `{ controller, domain }` | `Verify.Status` with
per-requirement evidence |
| `actions` | `{ connection, provisioning, cleanup, domain }` | Review
changes, Approve, Decline, Remove records |

Slot output is a direct child of the flow root, so a host grid places it
without
`display: contents`. Every dialog and popover takes a `render` prop that
swaps the surface.

## Screenshots

| Requirements | Connect |
| --- | --- |
|
![Requirements](https://github.com/user-attachments/assets/6db3d707-0f1d-4c98-b87c-96a3727a90a5)
| ![Connect
dialog](https://github.com/user-attachments/assets/003071e3-06e0-41a6-bab2-d498a7e6d3c6)
|

| Plan review | Applied |
| --- | --- |
| ![Plan
review](https://github.com/user-attachments/assets/d4087379-ff63-486c-870e-584f755e77bb)
|
![Applied](https://github.com/user-attachments/assets/9999eb7e-7aad-4bb4-a93b-9a24fe5434d2)
|

## Tests

- `test` — 26 cases over `Testing.transport()`: an in-memory
`domainkit/server` with memory
  storage, not a stubbed `fetch`.
- `test:browser` — 5 Playwright cases against a Vite fixture in
`tests/browser/app`, replacing the
  docs-app dependency.
- `test:artifact` — packed Vite and Next App Router consumers, plus the
packed file list and peer
  ranges.

`bun run --filter @domainkit/react release:check` is green, and the root
`release:check` now runs
it as its last leg and passes end to end.
S
Saatvik Arya committed
d8bcdaeccbe547cfe3462e5c16798881643d00c8
Parent: 26d180d
Committed by GitHub <noreply@github.com> on 9/3/2026, 6:54:04 PM