🔒 VhyxSeal
GitHub

React Adapter

@vhyxseal/react is a thin adapter on top of @vhyxseal/core. It provides six headless UI components, three hooks, a context provider, and a higher-order component for wrapping existing components. All business logic lives in core — this package is pure React plumbing.

What the Adapter Provides

  • SealProvider — context provider that collects contracts and generates the manifest
  • withAgentContract() — HOC to add a contract to any existing component without modifying it
  • useContract(id) — reads a registered contract by component id
  • useCapability() — reads the full capability map with counts
  • useAgentAction() — tracks agent action lifecycle (standalone, no provider needed)
  • Button, Input, Form, Nav, Display, Confirmation — headless components with contract registration built in

Installation

npm install @vhyxseal/react @vhyxseal/core

React ≥ 18 required. Works with React 19. Fully compatible with Next.js App Router (including Server Components) and Pages Router.

Full Setup

Place SealProvider at the root of your component tree. Every VhyxSeal component in the subtree automatically registers its contract.

app/layout.tsx
// app/layout.tsx — wrap your application
import { SealProvider } from '@vhyxseal/react'
import type { ManifestConfig } from '@vhyxseal/core'

const config: ManifestConfig = {
  domain: "example.com",
  domainVerified: false,
  verificationToken: "",
  // Optional — all have defaults
  agentPolicy: {
    allowedAgents: ["*"],
    requiresConfirmation: ["place-order", "delete-account"],
  },
  cacheDurationSeconds: 3600,
  schemaVersion: "1.0.0",
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <SealProvider
          config={config}
          onManifestGenerated={(manifest) => {
            console.log('[VhyxSeal] Manifest updated:', manifest.fingerprint)
          }}
        >
          {children}
        </SealProvider>
      </body>
    </html>
  )
}

SealProvider Configuration

The SealProvider config maps directly to ManifestConfig from @vhyxseal/core.

PropTypeDescription
configManifestConfigRequired. domain, domainVerified, verificationToken, and optional policy/cache
devbooleanOptional. Enable verbose logging. Defaults to NODE_ENV !== production
onManifestGenerated(manifest) => voidOptional. Called each time a new manifest is generated
childrenReactNodeRequired. The component tree that can register contracts

withAgentContract HOC

Wraps any existing component without modifying it. The contract is a completely separate concern — existing component code is untouched.

// HOC — wrap any existing component
import { withAgentContract } from '@vhyxseal/react'
import { defineContract } from '@vhyxseal/core'

const contract = defineContract({
  id: "checkout-submit-btn",
  type: "action",
  intent: "place-order",
  description: "Submits the cart as a purchase order",
  requires: [
    { field: "user.authenticated", operator: "===", value: true, description: "Must be logged in" },
    { field: "cart.hasItems", operator: "===", value: true, description: "Cart must have items" },
    { field: "user.hasPaymentMethod", operator: "===", value: true, description: "Payment method required" },
  ],
  requiredPermissions: ["write:orders"],
  consequence: "Creates order, charges payment method, triggers fulfillment",
  affects: ["cart", "orders", "payment", "inventory"],
  reversible: true,
  reversibleWindow: 300,
  safetyLevel: "high",
  requiresConfirmation: true,
  destructive: false,
  contractVersion: "1.0.0",
})

// Wrap any existing component without modifying it
export const CheckoutButton = withAgentContract(
  ({ onClick, disabled }: { onClick: () => void; disabled?: boolean }) => (
    <button onClick={onClick} disabled={disabled}>
      Place Order
    </button>
  ),
  contract,
)

Hooks Reference

import { useContract, useCapability, useAgentAction } from '@vhyxseal/react'

// useContract — read a registered contract by id
function ContractDebugger({ id }: { id: string }) {
  const contract = useContract(id)
  return <pre>{JSON.stringify(contract, null, 2)}</pre>
}

// useCapability — read the full capability map
function ManifestStats() {
  const capability = useCapability()
  return (
    <div>
      <p>Total contracts: {capability.counts.total}</p>
      <p>Full: {capability.counts.full}</p>
      <p>Inferred: {capability.counts.inferred}</p>
    </div>
  )
}

// useAgentAction — track agent action lifecycle (standalone, no provider needed)
function ActionTracker() {
  const { record, initiate, complete, cancel } = useAgentAction()
  return (
    <button onClick={() => {
      initiate("checkout-submit-btn", "place-order")
      // ... perform action ...
      complete()
    }}>
      Place Order
    </button>
  )
}

Available Components

// VhyxSeal components — drop-in replacements with contract support
import { Button, Input, Form, Nav, Display, Confirmation } from '@vhyxseal/react'
import { defineContract } from '@vhyxseal/core'

const searchContract = defineContract({
  id: "site-search-input",
  type: "input",
  intent: "search",
  description: "Search across all site content",
  requires: [],
  requiredPermissions: [],
  consequence: "Filters visible content to match query",
  affects: ["search-results"],
  reversible: true,
  safetyLevel: "low",
  requiresConfirmation: false,
  destructive: false,
  contractVersion: "1.0.0",
})

export function SearchBar() {
  return (
    <Input
      contract={searchContract}
      placeholder="Search..."
      onChange={(e) => performSearch(e.target.value)}
    />
  )
}

TypeScript Types

// Key TypeScript types exported from @vhyxseal/react
import type {
  SealProviderProps,    // Props for <SealProvider />
  SealContextValue,     // Value of SealContext — accessed via hooks
  ButtonProps,          // Props for <Button />
  InputProps,           // Props for <Input />
  FormProps,            // Props for <Form />
  NavProps,             // Props for <Nav />
  DisplayProps,         // Props for <Display />
  ConfirmationProps,    // Props for <Confirmation />
  ConfirmationState,    // State passed to Confirmation render prop
  CapabilityMap,        // Return type of useCapability()
  AgentActionStatus,    // "idle" | "initiated" | "confirmed" | "completed" | "failed" | "cancelled"
  AgentActionRecord,    // Full record with timestamps and metadata
  UseAgentActionReturn, // Return type of useAgentAction()
} from '@vhyxseal/react'

Common Patterns

Checkout flow with confirmation gate

// Example 1 — Checkout flow with confirmation gate
import { Button, Confirmation } from '@vhyxseal/react'
import { defineContract } from '@vhyxseal/core'

const placeOrderContract = defineContract({
  id: "place-order-btn", type: "action", intent: "place-order",
  description: "Places the order",
  requires: [
    { field: "user.authenticated", operator: "===", value: true, description: "Must be logged in" },
  ],
  requiredPermissions: ["write:orders"],
  consequence: "Creates order record and charges payment",
  affects: ["orders", "payment"],
  reversible: true, reversibleWindow: 300,
  safetyLevel: "high", requiresConfirmation: true, destructive: false,
  contractVersion: "1.0.0",
})

export function CheckoutFlow() {
  return (
    <Confirmation contract={placeOrderContract}>
      {({ confirmed, isPending, confirm, cancel }) => (
        <div>
          {isPending && (
            <dialog open>
              <p>Place order for $49.99?</p>
              <button onClick={confirm}>Confirm</button>
              <button onClick={cancel}>Cancel</button>
            </dialog>
          )}
          <Button
            contract={placeOrderContract}
            onClick={confirmed ? () => submitOrder() : undefined}
            disabled={isPending}
          >
            {confirmed ? "Placing Order..." : "Place Order"}
          </Button>
        </div>
      )}
    </Confirmation>
  )
}

Search form with dependency relationship

// Example 2 — Search form with dependency relationship
import { Input, Button, Form } from '@vhyxseal/react'
import { defineContract, defineRelationship } from '@vhyxseal/core'

const queryInputContract = defineContract({
  id: "search-query-input", type: "input", intent: "search",
  description: "Enter search query",
  requires: [], requiredPermissions: [],
  consequence: "Enables search button when non-empty",
  affects: ["search-state"],
  reversible: true, safetyLevel: "low",
  requiresConfirmation: false, destructive: false,
  contractVersion: "1.0.0",
})

const searchButtonContract = defineContract({
  id: "search-submit-btn", type: "action", intent: "search",
  description: "Submits the search query",
  requires: [{ field: "search.query.length", operator: ">", value: 0, description: "Query must not be empty" }],
  requiredPermissions: [],
  consequence: "Fetches and displays search results",
  affects: ["search-results"],
  reversible: true, safetyLevel: "low",
  requiresConfirmation: false, destructive: false,
  contractVersion: "1.0.0",
})

// Declare the dependency relationship
defineRelationship({
  type: "dependency",
  id: "search-query-enables-button",
  source: "search-query-input",
  target: "search-submit-btn",
  condition: { field: "search.query.length", operator: ">", value: 0, description: "Query non-empty" },
  effect: "enables",
  description: "Non-empty query enables the search button",
})

export function SearchForm() {
  const [query, setQuery] = React.useState("")
  return (
    <Form contract={searchFormContract}>
      <Input contract={queryInputContract} value={query} onChange={e => setQuery(e.target.value)} />
      <Button contract={searchButtonContract} type="submit" disabled={query.length === 0}>
        Search
      </Button>
    </Form>
  )
}

Known Limitations

  • Server Components cannot use hooks — use withAgentContract or component-level contract prop for RSC-compatible components
  • SealProvider uses React Context — wrap it in a Client Component boundary in App Router
  • useAgentAction is standalone and does not require SealProvider
  • All six components (Button, Input, Form, Nav, Display, Confirmation) gracefully degrade outside SealProvider — they render without contract registration and do not throw