npm.io
0.5.4 • Published 5d agoCLI

view-contracts

Licence
MIT
Version
0.5.4
Deps
8
Size
4.5 MB
Vulns
0
Weekly
2.1K

view-contracts

Contract-first UI development for AI-native applications.

3-platform visual parity — iOS (SwiftUI) / Android (Jetpack Compose) / React

view-contracts is a UI definition system that separates:

  • Screen semantics → OpenAPI
  • Visual structure → Restricted TSX

Instead of mixing API contracts, business logic, and UI implementation inside React components, view-contracts treats UI as a compilable contract.


Why?

Modern UI development has a structural problem.

React Component
 ├─ API knowledge
 ├─ ViewModel mapping
 ├─ Business rules
 ├─ Layout
 └─ Styling

As applications grow:

  • UI becomes difficult to regenerate
  • AI-generated code drifts from design rules
  • Multi-platform support becomes expensive
  • Design systems become guidelines instead of contracts

view-contracts introduces a contract-first approach:

OpenAPI
    ↓
Screen Contract
Restricted TSX
    ↓
View Definition
Compiler
    ↓
Generated UI

The goal is to make UI generation deterministic.


Core Concepts

OpenAPI owns semantics

OpenAPI defines:

  • screens
  • actions
  • navigation
  • ViewModels

Example:

/admin/dashboard:
  get:
    operationId: getAdminDashboard
    x-screen-id: SCR-ADMIN-DASHBOARD

This is the source of truth for screen behavior.

TSX owns layout

A .view.tsx file defines:

  • component hierarchy
  • layout structure
  • visual composition
  • variant names (appearance resolved from views/design/theme.yaml)
Design tokens and theme own appearance

Color, spacing, typography, and component variants are defined in project YAML — not in view source:

  • views/design/tokens.yaml — DTCG design tokens
  • views/design/theme.yamlButton.primary, Text.muted, etc.
  • views/design/extensions.css — optional ext-* styles for extension components

See docs/design-tokens-and-theme.md.

Forms

Form inputs (TextInput, TextArea, Select) accept value and onChange:

<TextArea
  name="notes"
  value={vm.notes}
  onChange={{ name: "fieldChange", params: { field: "notes" } }}
  rows={3}
/>

<TextInput name="subject" value={vm.subject} />
  • value + onChange → controlled input. The action handler receives a value param with the new input value automatically.
  • value only → read-only / uncontrolled initial value.
  • Neither → empty input (backward-compatible).
Compile instead of hand-coding
.view.tsx
    ↓
Compiler (in-memory View IR)
    ↓
Renderer
    ↓
generated/react/   (*.generated.tsx, components/, runtime/)

The generated output is deterministic.


Current Status

Supported
  • React generation (generated/react/ — screens, runtime.ts, theme.css, …)
  • SwiftUI generation (generated/swiftui/ — full DSL element coverage, theme variant dictionary, SampleData.json from mock YAML SSoT)
  • Jetpack Compose generation (generated/compose/ — full DSL element coverage, theme variant system, Android preview app)
  • Design tokens + theme variants (views/design/tokens.yaml, theme.yaml)
  • Live DSL preview (dev:preview) with views/preview/*.mock.yaml sample data
  • Extension registry (extensions.yaml) with plain HTML/CSS implementations
  • In-memory View IR compilation
  • Standalone app scaffold (.vite/ per example project)
  • Table DSL with configurable separator / separatorColor / border / borderColor
  • Dynamic variant resolution via IrConcat (runtime theme dictionary in SwiftUI)
  • 3-platform visual parity (iOS / Android / React from same DSL definition)
Planned
  • PDF renderer

The contract model is designed for multi-target generation. React, SwiftUI, and Jetpack Compose are production-ready targets.


Architecture

Two sources of truth converge through bindings, type-checking, and View IR.

flowchart TB
  subgraph ia ["Information architecture"]
    openapi["contracts/openapi.yaml<br/>screens · ViewModels · actions"]
  end

  subgraph design ["Visual design"]
    viewTsx["views/*.view.tsx<br/>layout · composition · variant names"]
    tokens["views/design/tokens.yaml"]
    theme["views/design/theme.yaml"]
  end

  openapi --> microContracts["micro-contracts generate"]
  microContracts --> contractsTs["generated/contracts.ts"]

  openapi --> bindings["view-bindings.yaml"]
  viewTsx --> bindings
  tokens --> build
  theme --> build

  contractsTs -->|"imports ViewModel type<br/>validates vm.* and operationId"| viewTsx

  bindings -->|"screen ↔ view mapping"| build["view-contracts build"]
  viewTsx --> build

  build --> viewIr["View IR (in-memory)"]
  viewIr --> reactRenderer["renderComponents"]
  reactRenderer --> generatedReact["generated/react/*.generated.tsx"]
  build --> runtimeCopy["renderRuntime → generated/"]
  build --> bridgeGen["render bridge"]
  runtimeCopy --> generatedTheme["generated/react/theme.css"]
  bridgeGen --> bridgeTs["generated/react/view-bridge.generated.ts"]
  runtimeCopy --> generatedRuntime["generated/react/runtime.ts"]

  generatedReact --> app["Standalone app (src/main.tsx)"]
  generatedTheme --> app
  bridgeTs --> app
  generatedRuntime --> app

Where the two designs meet

Stage What merges Output
Authoring OpenAPI types flow into .view.tsx via generated/contracts.ts Type-safe view source
Binding view-bindings.yaml links a screen (operationId, ViewModel) to a view file No schema duplication
Compilation Compiler reads layout from TSX and lowers vm.* / action to language-neutral IR In-memory View IR
Design tokens.yaml + theme.yaml validate and emit generated/react/theme.css CSS variables + .vc-* variant rules
Generation Renderer copies runtime + emits React from IR + bridge registry generated/ (no @view-contracts imports)
Preview path (no compile step)
flowchart LR
  openapi["openapi.yaml"] --> microContracts["micro-contracts"]
  microContracts --> contractsTs["contracts.ts"]

  contractsTs --> viewTsx["*.view.tsx"]
  mockYaml["views/preview/*.mock.yaml"] --> previewPlugin["preview mock plugin"]
  viewTsx --> preview["Vite preview"]
  previewPlugin --> preview

  preview --> reactDev["Vite dev server<br/>design verification"]

Designers and developers validate layouts without running the full build. Preview loads sample ViewModels from views/preview/*.mock.yaml (not from generated/). Production apps load real data via operationId in view-bindings.

See docs/ARCHITECTURE.md, docs/design-tokens-and-theme.md, docs/extensions.md, and docs/SPEC_COVERAGE.md.


Quick Start

npm install
npm run build:views   # compile + generate + scaffold .vite/
npm run dev           # preview (5173) + dev app (5174)
URL Purpose
http://localhost:5173/.vite/preview.html DSL preview — views/*.view.tsx only
http://localhost:5174/.vite/index.html Dev app — generated/ + src/main.tsx

Ports are fixed in examples/admin-dashboard/dev.ports.ts (strictPort: true).

Other useful commands:

npm run dev:preview     # preview only
npm run dev:app         # standalone dev app only
npm run check:both      # build + preview dev + production bundle (4174)

Example Project

The repository includes an example admin dashboard:

examples/admin-dashboard/
├── views/              # restricted .view.tsx (preview compiles live)
│   ├── design/         # tokens.yaml, theme.yaml, extensions.css
│   └── preview/        # *.mock.yaml — preview sample ViewModels only
├── src/                # app wiring, extension components (HTML/CSS), handlers
├── generated/          # build output (standalone — no @view-contracts imports)
│   ├── react/          # React generated code
│   └── swiftui/        # SwiftUI generated code + SampleData.json
├── ios-preview/        # SwiftUI preview app (XcodeGen project)
│   ├── Sources/        # ContentView, ViewModels (Codable, loads SampleData.json)
│   └── project.yml     # XcodeGen definition
├── contracts/          # OpenAPI + micro-contracts config
├── extensions.yaml     # extension registry (Grid, Table, …)
├── dev.ports.ts        # fixed dev server ports
├── scripts/            # dev-both.sh, check-both.sh, ports.sh
└── .vite/              # auto-generated Vite configs (gitignored)

See examples/admin-dashboard/README.md for local verification steps.

Extensions (Grid, Table, …) are declared in extensions.yaml and implemented in src/components/extensions.tsx with plain HTML/CSS — not DSL. See docs/extensions.md.

Appearance for vocabulary elements comes from views/design/theme.yaml and tokens.yaml. See docs/design-tokens-and-theme.md.


Project Layout

src/
├── compiler/           # TSX → View IR
├── design/             # tokens/theme parse, validate, CSS emit
├── extensions/         # extension registry load + validation
├── preview/            # Vite plugins (live views, theme, mock YAML)
├── scaffold/           # .vite/ + package.json for example projects
└── commands/           # CLI handlers (build orchestrator)

packages/
├── core/               # screen(), types (@view-contracts/core)
└── runtime/            # legacy preview shim

renderers/
├── react/              # lowering, element map, runtime, templates
├── swiftui/            # lowering, theme dictionary, modifier cascade
└── compose/            # lowering, theme system, modifier pipeline

docs/
├── design-tokens-and-theme.md
├── extensions.md
├── ARCHITECTURE.md
└── SPEC_COVERAGE.md    # auto-generated vocabulary reference

Vision

UI should be treated like an API contract.

The long-term goal of view-contracts is to make UI:

  • deterministic
  • compilable
  • AI-friendly
  • portable across platforms

while keeping designers and developers working from the same source of truth.

view-contracts is not a React replacement — it is contract-first UI for the AI era.


npm

npm install -D view-contracts
npx view-contracts build -c view-contracts.config.yaml

Releases are published to npm when a GitHub Release is published (v* tag).

Keywords