view-contracts 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,19 +78,17 @@ A `.view.tsx` file defines:
78
78
  - component hierarchy
79
79
  - layout structure
80
80
  - visual composition
81
+ - `variant` names (appearance resolved from `views/design/theme.yaml`)
81
82
 
82
- Example:
83
+ ### Design tokens and theme own appearance
83
84
 
84
- ```tsx
85
- export default screen<AdminDashboardViewModel>(({ vm }) => (
86
- <Page id={vm.screenId} title={vm.title}>
87
- <Heading level={1}>{vm.title}</Heading>
88
- <Table rows={vm.projects} />
89
- </Page>
90
- ));
91
- ```
85
+ Color, spacing, typography, and component variants are defined in project YAML — not in view source:
92
86
 
93
- The view describes **structure only** not API calls, routing logic, or styling implementation details.
87
+ - `views/design/tokens.yaml`DTCG design tokens
88
+ - `views/design/theme.yaml` — `Button.primary`, `Text.muted`, etc.
89
+ - `views/design/extensions.css` — optional `ext-*` styles for extension components
90
+
91
+ See [docs/design-tokens-and-theme.md](./docs/design-tokens-and-theme.md).
94
92
 
95
93
  ### Compile instead of hand-coding
96
94
 
@@ -112,8 +110,10 @@ The generated output is deterministic.
112
110
 
113
111
  ### Supported
114
112
 
115
- - React generation (`generated/react/`, `generated/components/`)
116
- - DSL preview runtime (`src/preview/`)
113
+ - React generation (`generated/react/`, `generated/components/`, `generated/theme.css`)
114
+ - Design tokens + theme variants (`views/design/tokens.yaml`, `theme.yaml`)
115
+ - Live DSL preview (`dev:preview`) with `views/preview/*.mock.yaml` sample data
116
+ - Extension registry (`extensions.yaml`) with plain HTML/CSS implementations
117
117
  - In-memory View IR compilation
118
118
  - Standalone app scaffold (`.vite/` per example project)
119
119
 
@@ -138,7 +138,9 @@ flowchart TB
138
138
  end
139
139
 
140
140
  subgraph design ["Visual design"]
141
- viewTsx["views/*.view.tsx<br/>layout · composition"]
141
+ viewTsx["views/*.view.tsx<br/>layout · composition · variant names"]
142
+ tokens["views/design/tokens.yaml"]
143
+ theme["views/design/theme.yaml"]
142
144
  end
143
145
 
144
146
  openapi --> microContracts["micro-contracts generate"]
@@ -146,6 +148,8 @@ flowchart TB
146
148
 
147
149
  openapi --> bindings["view-bindings.yaml"]
148
150
  viewTsx --> bindings
151
+ tokens --> build
152
+ theme --> build
149
153
 
150
154
  contractsTs -->|"imports ViewModel type<br/>validates vm.* and operationId"| viewTsx
151
155
 
@@ -154,14 +158,15 @@ flowchart TB
154
158
 
155
159
  build --> viewIr["View IR (in-memory)"]
156
160
  viewIr --> reactRenderer["renderComponents"]
161
+ reactRenderer --> generatedReact["generated/react/*.generated.tsx"]
157
162
  build --> runtimeCopy["renderRuntime → generated/"]
158
163
  build --> bridgeGen["render bridge"]
159
-
160
- reactRenderer --> generatedReact["generated/react/*.generated.tsx"]
164
+ runtimeCopy --> generatedTheme["generated/theme.css"]
161
165
  bridgeGen --> bridgeTs["generated/view-bridge.generated.ts"]
162
166
  runtimeCopy --> generatedRuntime["generated/components + runtime/"]
163
167
 
164
168
  generatedReact --> app["Standalone app (src/main.tsx)"]
169
+ generatedTheme --> app
165
170
  bridgeTs --> app
166
171
  generatedRuntime --> app
167
172
  ```
@@ -173,6 +178,7 @@ flowchart TB
173
178
  | Authoring | OpenAPI types flow into `.view.tsx` via `generated/contracts.ts` | Type-safe view source |
174
179
  | Binding | `view-bindings.yaml` links a screen (`operationId`, `ViewModel`) to a view file | No schema duplication |
175
180
  | Compilation | Compiler reads layout from TSX and lowers `vm.*` / `action` to language-neutral IR | In-memory View IR |
181
+ | Design | `tokens.yaml` + `theme.yaml` validate and emit `generated/theme.css` | CSS variables + `.vc-*` variant rules |
176
182
  | Generation | Renderer copies runtime + emits React from IR + bridge registry | `generated/` (no `@view-contracts` imports) |
177
183
 
178
184
  ### Preview path (no compile step)
@@ -192,7 +198,7 @@ flowchart LR
192
198
 
193
199
  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.
194
200
 
195
- See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) (including [conditional display patterns](./docs/ARCHITECTURE.md#conditional-display-non-goals-and-patterns)) and [docs/SPEC_COVERAGE.md](./docs/SPEC_COVERAGE.md) for implementation details.
201
+ See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md), [docs/design-tokens-and-theme.md](./docs/design-tokens-and-theme.md), [docs/extensions.md](./docs/extensions.md), and [docs/SPEC_COVERAGE.md](./docs/SPEC_COVERAGE.md).
196
202
 
197
203
  ---
198
204
 
@@ -227,10 +233,13 @@ The repository includes an example admin dashboard:
227
233
 
228
234
  ```txt
229
235
  examples/admin-dashboard/
230
- ├── views/ # restricted .view.tsx (may import @view-contracts/*)
231
- ├── src/ # app wiring, extension components, handlers
236
+ ├── views/ # restricted .view.tsx (preview compiles live)
237
+ ├── design/ # tokens.yaml, theme.yaml, extensions.css
238
+ │ └── preview/ # *.mock.yaml — preview sample ViewModels only
239
+ ├── src/ # app wiring, extension components (HTML/CSS), handlers
232
240
  ├── generated/ # build output (standalone — no @view-contracts imports)
233
241
  ├── contracts/ # OpenAPI + micro-contracts config
242
+ ├── extensions.yaml # extension registry (Grid, Table, …)
234
243
  ├── dev.ports.ts # fixed dev server ports
235
244
  ├── scripts/ # dev-both.sh, check-both.sh, ports.sh
236
245
  └── .vite/ # auto-generated Vite configs (gitignored)
@@ -238,7 +247,9 @@ examples/admin-dashboard/
238
247
 
239
248
  See [examples/admin-dashboard/README.md](./examples/admin-dashboard/README.md) for local verification steps.
240
249
 
241
- Project-specific extension components (e.g. `Grid`, `Table`) are declared in `extensions.yaml` and documented in [docs/extensions.md](./docs/extensions.md). Vocabulary elements come from `spec/ui-dsl.schema.json`; extensions are React-only pass-through components.
250
+ **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](./docs/extensions.md).
251
+
252
+ **Appearance** for vocabulary elements comes from `views/design/theme.yaml` and `tokens.yaml`. See [docs/design-tokens-and-theme.md](./docs/design-tokens-and-theme.md).
242
253
 
243
254
  ---
244
255
 
@@ -247,23 +258,26 @@ Project-specific extension components (e.g. `Grid`, `Table`) are declared in `ex
247
258
  ```txt
248
259
  src/
249
260
  ├── compiler/ # TSX → View IR
250
- ├── renderer/
251
- │ └── react/
252
- ├── renderComponents.ts # IR generated/react/*.tsx
253
- │ ├── renderRuntime.ts # runtime SSoT → example/generated/
254
- │ └── runtime/ # React primitives (@view-contracts/components SSoT)
255
- ├── preview/ # DSL preview helpers (@view-contracts/preview)
261
+ ├── design/ # tokens/theme parse, validate, CSS emit
262
+ ├── extensions/ # extension registry load + validation
263
+ ├── preview/ # Vite plugins (live views, theme, mock YAML)
256
264
  ├── scaffold/ # .vite/ + package.json for example projects
257
265
  └── commands/ # CLI handlers (build orchestrator)
258
266
 
259
267
  packages/
260
268
  ├── core/ # screen(), types (@view-contracts/core)
261
- └── runtime/ # action dispatcher (@view-contracts/runtime)
269
+ └── runtime/ # legacy preview shim
262
270
 
263
271
  renderers/
264
- ├── react/ # element/property maps, routes.yaml
272
+ ├── react/ # lowering, element map, runtime, templates
265
273
  ├── swiftui/ # (planned)
266
274
  └── compose/ # (planned)
275
+
276
+ docs/
277
+ ├── design-tokens-and-theme.md
278
+ ├── extensions.md
279
+ ├── ARCHITECTURE.md
280
+ └── SPEC_COVERAGE.md # auto-generated vocabulary reference
267
281
  ```
268
282
 
269
283
  ---
package/cli-contract.yaml CHANGED
@@ -3,7 +3,7 @@ cli_contracts: 0.1.0
3
3
 
4
4
  info:
5
5
  title: view-contracts CLI
6
- version: 0.3.2
6
+ version: 0.3.3
7
7
  description: >-
8
8
  Contract-first UI design toolchain. Compiles restricted view-contract TSX
9
9
  to language-neutral View IR and renders platform targets (React, SwiftUI, Compose).
@@ -60,7 +60,7 @@ cli_contracts: 0.1.0
60
60
 
61
61
  info:
62
62
  title: view-contracts CLI
63
- version: 0.3.2
63
+ version: 0.3.3
64
64
  description: >-
65
65
  Contract-first UI design toolchain. Compiles restricted view-contract TSX
66
66
  to language-neutral View IR and renders platform targets (React, SwiftUI, Compose).
@@ -223,7 +223,7 @@ command_sets:
223
223
  "cli_contracts": "0.1.0",
224
224
  "info": {
225
225
  "title": "view-contracts CLI",
226
- "version": "0.3.2",
226
+ "version": "0.3.3",
227
227
  "description": "Contract-first UI design toolchain. Compiles restricted view-contract TSX to language-neutral View IR and renders platform targets (React, SwiftUI, Compose)."
228
228
  },
229
229
  "artifact_slots": {
@@ -748,7 +748,7 @@ export default defineConfig({
748
748
  `}async function gg(t,e,r){let n=$e(t),i=Ue(n,".vite");await Ys(i,{recursive:!0});let s=Ue(n,"package.json");await Ll(s)?await rt(s,`${mg}
749
749
  `,"utf-8"):await rt(s,`${mg}
750
750
  `,"utf-8"),await rt(Ue(i,"index.html"),_C,"utf-8"),await rt(Ue(i,"preview.html"),wC,"utf-8"),await rt(Ue(i,"preview-main.tsx"),bC(r),"utf-8"),await rt(Ue(i,"app-main.tsx"),vC,"utf-8"),await rt(Ue(i,"vite.config.ts"),kC(),"utf-8"),await rt(Ue(i,"vite.preview.config.ts"),xC(),"utf-8"),await rt(Ue(n,"dev.ports.ts"),$C(),"utf-8");let o=Ue(n,"scripts");await Ys(o,{recursive:!0}),await rt(Ue(o,"ports.sh"),CC(),"utf-8");let a=Ue(n,"src/main.tsx");await Ll(a)||(await Ys(hg(a),{recursive:!0}),await rt(a,SC(r),"utf-8"));let l=Ue(n,"src/runtime/handlers.ts");await Ll(l)||(await Ys(hg(l),{recursive:!0}),await rt(l,EC,"utf-8")),console.log(` scaffold \u2192 ${i}/`)}import{mkdir as yg,writeFile as Xs}from"node:fs/promises";import{relative as vg,resolve as pt}from"node:path";function _g(){return pt(Xe(),"renderers/swiftui/templates")}function wg(){return pt(Xe(),"renderers/compose/templates")}async function bg(t,e,r){let n=$e(t),i=pt(e.generated.reactDir,"../theme"),s=vg(n,pt(i,"theme.json")).replace(/\\/g,"/"),o=vg(n,pt(i,"tokens.json")).replace(/\\/g,"/"),a=pt(e.generated.reactDir,"../swiftui"),l=pt(e.generated.reactDir,"../compose");await yg(a,{recursive:!0}),await yg(l,{recursive:!0});let u={themeJsonPath:s,tokensJsonPath:o};await Xs(pt(a,"VcTheme.swift"),await Be(_g(),"VcTheme.swift.hbs",u),"utf-8"),await Xs(pt(l,"VcTheme.kt"),await Be(wg(),"VcTheme.kt.hbs",{...u,packageName:"generated.compose"}),"utf-8");for(let d of r.views){if(d.targets.includes("swiftui")){let f=pt(a,`${d.screenName}.swift`);await Xs(f,await Be(_g(),"Screen.swift.hbs",{...u,screenName:d.screenName}),"utf-8"),console.log(` swiftui \u2192 ${f}`)}if(d.targets.includes("compose")){let f=pt(l,`${d.screenName}Screen.kt`);await Xs(f,await Be(wg(),"Screen.kt.hbs",{...u,screenName:d.screenName,packageName:"generated.compose"}),"utf-8"),console.log(` compose \u2192 ${f}`)}}console.log(` native theme hooks \u2192 ${a}/VcTheme.swift, ${l}/VcTheme.kt`)}async function Cg(t){if(!t.extensions)return{allowlistExtra:t.allowlistExtra??[]};let e=await Hp(t.extensions),r=[...new Set([...t.allowlistExtra??[],...Object.keys(e.extensions)])];return{extensionRegistry:e,allowlistExtra:r}}async function Sg(t){await new Promise((e,r)=>{let n=RC("npx",["micro-contracts",...t],{stdio:"inherit"});n.on("error",r),n.on("close",i=>{i===0?e():r(new Error(`micro-contracts exited with code ${i}`))})})}async function jl(t){let e=await Jt(t),r=await un(e.bindings),{extensionRegistry:n,allowlistExtra:i}=await Cg(e),s=$e(t),o=await pu(r.views.map(a=>({sourcePath:Dl(s,a.source),exportName:a.exportName,screenName:a.screenName})),{allowlistExtra:i,extensionRegistry:n});return new Map(r.views.map((a,l)=>[a.screenName,o[l]]))}async function Eg(t,e){let r=await Jt(t),{allowlistExtra:n}=await Cg(r),i=await un(r.bindings);for(let s of i.views){if(!s.targets.includes("react"))continue;let o=e.get(s.screenName);if(!o)throw new Error(`Missing View IR for ${s.screenName}`);let a=Nu(r,s),l=Mu(t,r,a);await nh(o,a,{componentsImportFrom:l,allowlistExtra:n}),console.log(` ${s.exportName} \u2192 ${a}`)}}async function $g(t){let e=await Jt(t),r=await un(e.bindings),n=$e(t),s=["// Auto-generated view bridge registry \u2014 DO NOT EDIT",`// Generated from ${Lu(n,e.bindings)}`,"","export const viewBridgeRegistry = {"];for(let o of r.views)s.push(` ${o.screenConst}: {`),s.push(` id: ${JSON.stringify(o.id)},`),s.push(` screenId: ${JSON.stringify(o.screenId)},`),s.push(` screenName: ${JSON.stringify(o.screenName)},`),s.push(` route: ${JSON.stringify(o.route)},`),s.push(` operationId: ${JSON.stringify(o.operationId)},`),s.push(` viewModel: ${JSON.stringify(o.viewModel)},`),s.push(` source: ${JSON.stringify(o.source)},`),s.push(` targets: ${JSON.stringify(o.targets)},`),s.push(" },");s.push("} as const;",""),s.push("export type ViewBridgeKey = keyof typeof viewBridgeRegistry;",""),await OC(AC(Dl(e.generated.bridge)),{recursive:!0}),await PC(Dl(e.generated.bridge),s.join(`
751
- `),"utf-8"),console.log(`Bridge registry \u2192 ${e.generated.bridge} (${r.views.length} view(s))`)}var kg={compile:async(t,e)=>{let r=await jl(await Kt(t.config));console.log(`Compiled ${r.size} view(s) to View IR (in-memory)`)},renderReact:async(t,e)=>{let r=await Kt(t.config),n=await jl(r);await Eg(r,n)},renderBridge:async(t,e)=>{await $g(await Kt(t.config))},build:async(t,e)=>{let r=await Kt(t.config),n=await Jt(r),i=await un(n.bindings),s=await jl(r);console.log(`Compiled ${s.size} view(s) to View IR (in-memory)`);let{tokensPath:o,themePath:a}=wr(n),{themeIR:l}=await Hr(o,a);await zn(n,r),qc(s.values(),l),await pg(r,n),await Eg(r,s),await $g(r),await bg(r,n,i),await gg(r,n,i)},contractsGenerate:async(t,e)=>{let r=await Jt(await Kt(t.config));await Sg(["generate","-c",r.microContractsConfig]),console.log("Contracts generated via micro-contracts")},contractsCheck:async(t,e)=>{let r=await Jt(await Kt(t.config));await Sg(["lint","-c",r.microContractsConfig])}};var IC={version:"0.3.2"};nu(kg,IC.version).parseAsync(process.argv).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1)});
751
+ `),"utf-8"),console.log(`Bridge registry \u2192 ${e.generated.bridge} (${r.views.length} view(s))`)}var kg={compile:async(t,e)=>{let r=await jl(await Kt(t.config));console.log(`Compiled ${r.size} view(s) to View IR (in-memory)`)},renderReact:async(t,e)=>{let r=await Kt(t.config),n=await jl(r);await Eg(r,n)},renderBridge:async(t,e)=>{await $g(await Kt(t.config))},build:async(t,e)=>{let r=await Kt(t.config),n=await Jt(r),i=await un(n.bindings),s=await jl(r);console.log(`Compiled ${s.size} view(s) to View IR (in-memory)`);let{tokensPath:o,themePath:a}=wr(n),{themeIR:l}=await Hr(o,a);await zn(n,r),qc(s.values(),l),await pg(r,n),await Eg(r,s),await $g(r),await bg(r,n,i),await gg(r,n,i)},contractsGenerate:async(t,e)=>{let r=await Jt(await Kt(t.config));await Sg(["generate","-c",r.microContractsConfig]),console.log("Contracts generated via micro-contracts")},contractsCheck:async(t,e)=>{let r=await Jt(await Kt(t.config));await Sg(["lint","-c",r.microContractsConfig])}};var IC={version:"0.3.3"};nu(kg,IC.version).parseAsync(process.argv).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1)});
752
752
  /*! Bundled license information:
753
753
 
754
754
  js-yaml/dist/js-yaml.mjs: