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 +41 -27
- package/cli-contract.yaml +1 -1
- package/dist/view-contracts.bundle.mjs +3 -3
- package/dist/view-contracts.bundle.mjs.map +1 -1
- package/docs/cli-reference.md +1 -1
- package/package.json +1 -1
- package/src/generated/schema/allowed-components.ts +1 -1
- package/src/generated/schema/dsl-enums.ts +1 -1
- package/src/generated/schema/dsl-properties.ts +1 -1
- package/src/generated/schema/index.ts +1 -1
- package/src/generated/schema/lowering-style-properties.ts +1 -1
- package/src/generated/schema/react-renderer-element-config.ts +1 -1
- package/src/generated/schema/schema-document.ts +1 -1
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
|
-
|
|
83
|
+
### Design tokens and theme own appearance
|
|
83
84
|
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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)
|
|
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 (
|
|
231
|
-
├──
|
|
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
|
-
|
|
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
|
-
├──
|
|
251
|
-
|
|
252
|
-
|
|
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/ #
|
|
269
|
+
└── runtime/ # legacy preview shim
|
|
262
270
|
|
|
263
271
|
renderers/
|
|
264
|
-
├── react/ # element
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|