view-contracts 0.2.0 → 0.3.2
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 +7 -9
- package/cli-contract.yaml +1 -38
- package/dist/view-contracts.bundle.mjs +208 -254
- package/dist/view-contracts.bundle.mjs.map +4 -4
- package/docs/cli-reference.md +1 -38
- package/package.json +7 -4
- package/renderers/compose/templates/Screen.kt.hbs +15 -0
- package/renderers/compose/templates/VcTheme.kt.hbs +19 -0
- package/renderers/react/README.md +1 -0
- package/renderers/react/defaults/theme.yaml +26 -0
- package/renderers/react/defaults/tokens.yaml +59 -0
- package/renderers/react/element-config.meta.json +2 -1
- package/renderers/react/elementMap.ts +4 -2
- package/renderers/react/elements.yaml +76 -21
- package/renderers/react/lowering/lowerToJsx.ts +183 -28
- package/renderers/react/renderComponents.ts +23 -8
- package/renderers/react/renderRuntime.ts +24 -75
- package/renderers/react/routes.yaml +1 -1
- package/renderers/react/runtime/style.ts +45 -36
- package/renderers/react/runtime/theme.css +51 -54
- package/renderers/react/runtime/types.ts +20 -43
- package/renderers/react/syncGeneratedRuntime.ts +77 -0
- package/renderers/react/templates/components-index.ts.hbs +1 -1
- package/renderers/react/templates/runtime-types.ts.hbs +5 -0
- package/renderers/react/templates/style-helpers.ts.hbs +1 -1
- package/renderers/swiftui/templates/Screen.swift.hbs +21 -0
- package/renderers/swiftui/templates/VcTheme.swift.hbs +21 -0
- package/spec/ui-dsl-extensions.meta.json +62 -0
- package/spec/ui-dsl.schema.json +24 -16
- package/src/generated/schema/allowed-components.ts +1 -1
- package/src/generated/schema/{tokens.ts → dsl-enums.ts} +1 -1
- package/src/generated/schema/dsl-properties.ts +3 -2
- package/src/generated/schema/index.ts +2 -2
- package/src/generated/schema/lowering-style-properties.ts +1 -9
- package/src/generated/schema/react-renderer-element-config.ts +3 -2
- package/src/generated/schema/schema-document.ts +2 -2
package/docs/cli-reference.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Contract-first UI design toolchain. Compiles restricted view-contract TSX to language-neutral View IR and renders platform targets (React, SwiftUI, Compose).
|
|
4
4
|
|
|
5
|
-
**Version:** 0.
|
|
5
|
+
**Version:** 0.3.2
|
|
6
6
|
|
|
7
7
|
## Table of Contents
|
|
8
8
|
|
|
@@ -13,7 +13,6 @@ Contract-first UI design toolchain. Compiles restricted view-contract TSX to lan
|
|
|
13
13
|
- [build](#view-contracts-build)
|
|
14
14
|
- [contracts.generate](#view-contracts-contracts-generate)
|
|
15
15
|
- [contracts.check](#view-contracts-contracts-check)
|
|
16
|
-
- [catalog.build](#view-contracts-catalog-build)
|
|
17
16
|
|
|
18
17
|
---
|
|
19
18
|
|
|
@@ -201,39 +200,3 @@ view-contracts contracts check -c view-contracts.config.yaml
|
|
|
201
200
|
**Exit 1:** Lint failed.
|
|
202
201
|
|
|
203
202
|
---
|
|
204
|
-
|
|
205
|
-
### catalog.build
|
|
206
|
-
|
|
207
|
-
Build static catalog HTML for design review.
|
|
208
|
-
|
|
209
|
-
Renders all registered views with mock ViewModels into a single HTML file for design review, screenshot baselines, and accessibility checks.
|
|
210
|
-
|
|
211
|
-
**Usage:**
|
|
212
|
-
|
|
213
|
-
```
|
|
214
|
-
view-contracts catalog build
|
|
215
|
-
```
|
|
216
|
-
```
|
|
217
|
-
view-contracts catalog build -c view-contracts.config.yaml
|
|
218
|
-
```
|
|
219
|
-
```
|
|
220
|
-
view-contracts catalog build -o dist/catalog.html
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
#### Options
|
|
224
|
-
|
|
225
|
-
| Option | Aliases | Required | Default | Description |
|
|
226
|
-
|---|---|---|---|---|
|
|
227
|
-
| `--config` | -c | No | | Path to view-contracts.config.yaml (discovered upward from cwd when omitted). |
|
|
228
|
-
| `--output` | -o | No | | Output HTML path (defaults to dist/catalog.html next to bindings). |
|
|
229
|
-
|
|
230
|
-
#### Exit Codes
|
|
231
|
-
|
|
232
|
-
**Exit 0:** Catalog built.
|
|
233
|
-
|
|
234
|
-
- **Generated files:**
|
|
235
|
-
- `dist/catalog.html` (text/html) *(optional)*
|
|
236
|
-
|
|
237
|
-
**Exit 1:** Build failed.
|
|
238
|
-
|
|
239
|
-
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "view-contracts",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Contract-first UI design toolchain — restricted TSX to View IR to platform renderers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"dist/view-contracts.bundle.mjs",
|
|
14
14
|
"dist/view-contracts.bundle.mjs.map",
|
|
15
15
|
"renderers/react/runtime",
|
|
16
|
+
"renderers/react/defaults",
|
|
16
17
|
"src/generated/schema",
|
|
17
18
|
"spec",
|
|
18
19
|
"renderers",
|
|
@@ -23,9 +24,9 @@
|
|
|
23
24
|
],
|
|
24
25
|
"scripts": {
|
|
25
26
|
"generate": "cli-contracts generate",
|
|
26
|
-
"dev": "npm run
|
|
27
|
+
"dev": "npm run dev:both --prefix examples/admin-dashboard",
|
|
27
28
|
"dev:preview": "npm run dev:preview --prefix examples/admin-dashboard",
|
|
28
|
-
"dev:app": "npm run dev:app --prefix examples/admin-dashboard",
|
|
29
|
+
"dev:app": "npm run build:views && npm run dev:app --prefix examples/admin-dashboard",
|
|
29
30
|
"dev:both": "npm run build:views && npm run dev:both --prefix examples/admin-dashboard",
|
|
30
31
|
"check:both": "npm run check:both --prefix examples/admin-dashboard",
|
|
31
32
|
"serve:built": "npm run serve:built --prefix examples/admin-dashboard",
|
|
@@ -35,7 +36,6 @@
|
|
|
35
36
|
"bundle": "node esbuild.bundle.mjs",
|
|
36
37
|
"bundle:min": "node esbuild.bundle.mjs --minify",
|
|
37
38
|
"build:views": "tsx src/cli.ts build -c examples/admin-dashboard/view-contracts.config.yaml",
|
|
38
|
-
"build:catalog": "tsx src/cli.ts catalog build -c examples/admin-dashboard/view-contracts.config.yaml",
|
|
39
39
|
"build:example": "vite build --config examples/admin-dashboard/.vite/vite.config.ts",
|
|
40
40
|
"compile": "tsx src/cli.ts compile -c examples/admin-dashboard/view-contracts.config.yaml",
|
|
41
41
|
"render:react": "tsx src/cli.ts render react -c examples/admin-dashboard/view-contracts.config.yaml",
|
|
@@ -93,6 +93,9 @@
|
|
|
93
93
|
"ajv": "^8.18.0",
|
|
94
94
|
"cli-contracts": "^0.33.13",
|
|
95
95
|
"esbuild": "^0.28.0",
|
|
96
|
+
"pixelmatch": "^7.2.0",
|
|
97
|
+
"playwright": "^1.61.0",
|
|
98
|
+
"pngjs": "^7.0.0",
|
|
96
99
|
"tsx": "^4.21.0",
|
|
97
100
|
"vite": "^6.4.1",
|
|
98
101
|
"vitest": "^4.1.9"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// {{screenName}} — Jetpack Compose scaffold (Phase 5)
|
|
2
|
+
// Theme IR: {{themeJsonPath}}
|
|
3
|
+
// Token IR: {{tokensJsonPath}}
|
|
4
|
+
//
|
|
5
|
+
// TODO(Phase C): Load theme.json and emit Modifier.primaryButton(), vcTextMuted(), etc.
|
|
6
|
+
|
|
7
|
+
package {{packageName}}
|
|
8
|
+
|
|
9
|
+
import androidx.compose.material3.Text
|
|
10
|
+
import androidx.compose.runtime.Composable
|
|
11
|
+
|
|
12
|
+
@Composable
|
|
13
|
+
fun {{screenName}}Screen() {
|
|
14
|
+
Text("{{screenName}} — Compose scaffold")
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// VcTheme — Compose theme scaffold (Phase 5)
|
|
2
|
+
// Load Token IR + Theme IR from assets:
|
|
3
|
+
// {{tokensJsonPath}}
|
|
4
|
+
// {{themeJsonPath}}
|
|
5
|
+
//
|
|
6
|
+
// TODO(Phase C): Map Theme IR variants to Modifier extensions and ColorScheme.
|
|
7
|
+
|
|
8
|
+
package {{packageName}}
|
|
9
|
+
|
|
10
|
+
import android.content.Context
|
|
11
|
+
|
|
12
|
+
object VcTheme {
|
|
13
|
+
const val TOKENS_ASSET = "{{tokensJsonPath}}"
|
|
14
|
+
const val THEME_ASSET = "{{themeJsonPath}}"
|
|
15
|
+
|
|
16
|
+
/** Placeholder — read theme.json from assets in Phase C. */
|
|
17
|
+
fun loadThemeJson(context: Context): String? =
|
|
18
|
+
context.assets.open(THEME_ASSET).bufferedReader().use { it.readText() }
|
|
19
|
+
}
|
|
@@ -12,6 +12,7 @@ Per-target mapping for view-contracts UI DSL → React.
|
|
|
12
12
|
|
|
13
13
|
Vocabulary (elements/properties) is defined in `spec/ui-dsl.schema.json` (from `ui-dsl-*.csv`).
|
|
14
14
|
Preview vocabulary stubs are generated to `src/generated/preview/vocabulary.tsx`.
|
|
15
|
+
React `elements.yaml` now covers the full UI DSL vocabulary (33/33 elements).
|
|
15
16
|
Sample-only preview helpers (Sidebar, Card, etc.) are **not** part of the vocabulary — they live in project `src/components/extensions.tsx`.
|
|
16
17
|
|
|
17
18
|
Generic renderer utilities (template engine, YAML element-map validation) live under `src/renderer/`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Default theme variants — generic vocabulary only (no sample-specific layouts).
|
|
2
|
+
variants:
|
|
3
|
+
Button:
|
|
4
|
+
primary:
|
|
5
|
+
background: "{ui.primary}"
|
|
6
|
+
foreground: "{ui.on-primary}"
|
|
7
|
+
secondary:
|
|
8
|
+
background: "{ui.button-secondary}"
|
|
9
|
+
foreground: "{ui.text}"
|
|
10
|
+
ghost:
|
|
11
|
+
background: "{ui.transparent}"
|
|
12
|
+
foreground: "{ui.text}"
|
|
13
|
+
danger:
|
|
14
|
+
background: "{ui.danger}"
|
|
15
|
+
foreground: "{ui.on-primary}"
|
|
16
|
+
Text:
|
|
17
|
+
muted:
|
|
18
|
+
foreground: "{ui.text-muted}"
|
|
19
|
+
primary:
|
|
20
|
+
foreground: "{ui.primary}"
|
|
21
|
+
success:
|
|
22
|
+
foreground: "{ui.success}"
|
|
23
|
+
warning:
|
|
24
|
+
foreground: "{ui.warning}"
|
|
25
|
+
danger:
|
|
26
|
+
foreground: "{ui.danger}"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Default design tokens — mirrors renderers/react/runtime/theme.css :root variables.
|
|
2
|
+
ui:
|
|
3
|
+
bg:
|
|
4
|
+
$type: color
|
|
5
|
+
$value: "#f6f8fb"
|
|
6
|
+
surface:
|
|
7
|
+
$type: color
|
|
8
|
+
$value: "#ffffff"
|
|
9
|
+
surface-muted:
|
|
10
|
+
$type: color
|
|
11
|
+
$value: "#f8fafc"
|
|
12
|
+
text:
|
|
13
|
+
$type: color
|
|
14
|
+
$value: "#111827"
|
|
15
|
+
text-muted:
|
|
16
|
+
$type: color
|
|
17
|
+
$value: "#6b7280"
|
|
18
|
+
primary:
|
|
19
|
+
$type: color
|
|
20
|
+
$value: "#2563eb"
|
|
21
|
+
primary-weak:
|
|
22
|
+
$type: color
|
|
23
|
+
$value: "#dbeafe"
|
|
24
|
+
success:
|
|
25
|
+
$type: color
|
|
26
|
+
$value: "#047857"
|
|
27
|
+
success-weak:
|
|
28
|
+
$type: color
|
|
29
|
+
$value: "#d1fae5"
|
|
30
|
+
warning:
|
|
31
|
+
$type: color
|
|
32
|
+
$value: "#b45309"
|
|
33
|
+
warning-weak:
|
|
34
|
+
$type: color
|
|
35
|
+
$value: "#fef3c7"
|
|
36
|
+
danger:
|
|
37
|
+
$type: color
|
|
38
|
+
$value: "#dc2626"
|
|
39
|
+
danger-weak:
|
|
40
|
+
$type: color
|
|
41
|
+
$value: "#fee2e2"
|
|
42
|
+
on-primary:
|
|
43
|
+
$type: color
|
|
44
|
+
$value: "#ffffff"
|
|
45
|
+
button-secondary:
|
|
46
|
+
$type: color
|
|
47
|
+
$value: "#eef2f7"
|
|
48
|
+
transparent:
|
|
49
|
+
$type: color
|
|
50
|
+
$value: "transparent"
|
|
51
|
+
border:
|
|
52
|
+
$type: color
|
|
53
|
+
$value: "#e5e7eb"
|
|
54
|
+
shadow:
|
|
55
|
+
$type: shadow
|
|
56
|
+
$value: "0 10px 30px rgba(15, 23, 42, 0.08)"
|
|
57
|
+
font:
|
|
58
|
+
$type: typography
|
|
59
|
+
$value: 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
"levelProp": { "type": "string" },
|
|
13
13
|
"defaultLevel": { "type": "number" },
|
|
14
14
|
"actionEvent": { "type": "enum", "values": ["onClick", "onSubmit"] },
|
|
15
|
-
"variantClass": { "type": "boolean" },
|
|
16
15
|
"fieldWrap": { "type": "boolean" },
|
|
17
16
|
"inputType": { "type": "string" },
|
|
18
17
|
"sizeClass": { "type": "string" },
|
|
19
18
|
"optionsProp": { "type": "string" },
|
|
20
19
|
"checkbox": { "type": "boolean" },
|
|
20
|
+
"fieldContainer": { "type": "boolean" },
|
|
21
|
+
"scrollContainer": { "type": "boolean" },
|
|
21
22
|
"defaultRole": { "type": "string" }
|
|
22
23
|
}
|
|
23
24
|
}
|
|
@@ -40,16 +40,18 @@ export function isLowerableVocabularyElement(
|
|
|
40
40
|
return ALLOWED_COMPONENTS.has(name) && !allowlistExtra.includes(name) && name in map.elements;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export async function
|
|
43
|
+
export async function validateElementMapCoversVocabulary(customPath?: string): Promise<void> {
|
|
44
44
|
const [schema, map] = await Promise.all([loadUiDslSchema(), loadReactElementMap(customPath)]);
|
|
45
45
|
const missing = Object.keys(schema.elements).filter((name) => !(name in map.elements));
|
|
46
46
|
if (missing.length > 0) {
|
|
47
|
-
|
|
47
|
+
throw new Error(
|
|
48
48
|
`${SOURCE_LABEL}: ${missing.length} DSL element(s) have no React HTML map yet: ${missing.join(', ')}`,
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export const assertElementMapCoversImplementedVocabulary = validateElementMapCoversVocabulary;
|
|
54
|
+
|
|
53
55
|
export function isVocabularyElement(name: string, map: ReactElementMapFile, allowlistExtra: string[] = []): boolean {
|
|
54
56
|
return name in map.elements && !allowlistExtra.includes(name);
|
|
55
57
|
}
|
|
@@ -3,85 +3,140 @@
|
|
|
3
3
|
# (allowlistExtra) and stay as React component imports — not listed here.
|
|
4
4
|
|
|
5
5
|
elements:
|
|
6
|
+
App:
|
|
7
|
+
tag: div
|
|
8
|
+
className: vc-app
|
|
6
9
|
Page:
|
|
7
10
|
tag: main
|
|
8
|
-
className:
|
|
11
|
+
className: vc-page
|
|
9
12
|
ariaLabel: true
|
|
13
|
+
SafeArea:
|
|
14
|
+
tag: div
|
|
15
|
+
className: vc-safe-area
|
|
10
16
|
Box:
|
|
11
17
|
tag: div
|
|
12
|
-
className:
|
|
18
|
+
className: vc-box
|
|
13
19
|
ariaLabel: true
|
|
14
20
|
Column:
|
|
15
21
|
tag: div
|
|
16
|
-
className:
|
|
22
|
+
className: vc-column
|
|
17
23
|
flex: column
|
|
18
24
|
ariaLabel: true
|
|
19
25
|
Row:
|
|
20
26
|
tag: div
|
|
21
|
-
className:
|
|
27
|
+
className: vc-row
|
|
22
28
|
flex: row
|
|
23
29
|
wrapProp: wrap
|
|
24
30
|
ariaLabel: true
|
|
25
31
|
Stack:
|
|
26
32
|
tag: div
|
|
27
|
-
className:
|
|
33
|
+
className: vc-stack
|
|
28
34
|
ariaLabel: true
|
|
35
|
+
ScrollArea:
|
|
36
|
+
tag: div
|
|
37
|
+
className: vc-scroll-area
|
|
38
|
+
scrollContainer: true
|
|
29
39
|
Spacer:
|
|
30
40
|
tag: div
|
|
31
|
-
className:
|
|
41
|
+
className: vc-spacer
|
|
32
42
|
ariaHidden: true
|
|
33
43
|
flexGrowProp: weight
|
|
34
44
|
Divider:
|
|
35
45
|
tag: hr
|
|
36
|
-
className:
|
|
46
|
+
className: vc-divider
|
|
37
47
|
void: true
|
|
38
48
|
Heading:
|
|
39
49
|
tag: dynamic-heading
|
|
40
|
-
className:
|
|
50
|
+
className: vc-heading
|
|
41
51
|
levelProp: level
|
|
42
52
|
defaultLevel: 2
|
|
43
53
|
Text:
|
|
44
54
|
tag: p
|
|
45
|
-
className:
|
|
55
|
+
className: vc-text
|
|
56
|
+
Image:
|
|
57
|
+
tag: img
|
|
58
|
+
className: vc-image
|
|
59
|
+
void: true
|
|
60
|
+
Icon:
|
|
61
|
+
tag: span
|
|
62
|
+
className: vc-icon
|
|
63
|
+
ariaHidden: true
|
|
46
64
|
Link:
|
|
47
65
|
tag: a
|
|
48
|
-
className:
|
|
66
|
+
className: vc-link
|
|
49
67
|
actionEvent: onClick
|
|
50
68
|
Button:
|
|
51
69
|
tag: button
|
|
52
|
-
className:
|
|
53
|
-
|
|
70
|
+
className: vc-button
|
|
71
|
+
actionEvent: onClick
|
|
72
|
+
IconButton:
|
|
73
|
+
tag: button
|
|
74
|
+
className: vc-icon-button
|
|
54
75
|
actionEvent: onClick
|
|
76
|
+
ariaLabel: true
|
|
55
77
|
Form:
|
|
56
78
|
tag: form
|
|
57
|
-
className:
|
|
79
|
+
className: vc-form
|
|
58
80
|
actionEvent: onSubmit
|
|
59
81
|
ariaLabel: true
|
|
82
|
+
Field:
|
|
83
|
+
tag: div
|
|
84
|
+
className: vc-field-container
|
|
85
|
+
fieldContainer: true
|
|
60
86
|
TextInput:
|
|
61
87
|
tag: input
|
|
62
|
-
className:
|
|
88
|
+
className: vc-text-input
|
|
63
89
|
fieldWrap: true
|
|
64
90
|
inputType: text
|
|
65
|
-
sizeClass:
|
|
91
|
+
sizeClass: vc-field
|
|
66
92
|
TextArea:
|
|
67
93
|
tag: textarea
|
|
68
|
-
className:
|
|
94
|
+
className: vc-text-area
|
|
69
95
|
fieldWrap: true
|
|
70
96
|
Select:
|
|
71
97
|
tag: select
|
|
72
|
-
className:
|
|
98
|
+
className: vc-select
|
|
73
99
|
fieldWrap: true
|
|
74
|
-
sizeClass:
|
|
100
|
+
sizeClass: vc-field
|
|
75
101
|
optionsProp: options
|
|
76
102
|
Checkbox:
|
|
77
103
|
tag: label
|
|
78
|
-
className:
|
|
104
|
+
className: vc-checkbox
|
|
79
105
|
checkbox: true
|
|
106
|
+
Radio:
|
|
107
|
+
tag: label
|
|
108
|
+
className: vc-radio
|
|
109
|
+
Switch:
|
|
110
|
+
tag: label
|
|
111
|
+
className: vc-switch
|
|
112
|
+
Modal:
|
|
113
|
+
tag: dialog
|
|
114
|
+
className: vc-modal
|
|
115
|
+
defaultRole: dialog
|
|
116
|
+
Drawer:
|
|
117
|
+
tag: aside
|
|
118
|
+
className: vc-drawer
|
|
119
|
+
defaultRole: dialog
|
|
120
|
+
Popover:
|
|
121
|
+
tag: div
|
|
122
|
+
className: vc-popover
|
|
123
|
+
defaultRole: dialog
|
|
124
|
+
Tooltip:
|
|
125
|
+
tag: span
|
|
126
|
+
className: vc-tooltip
|
|
127
|
+
defaultRole: tooltip
|
|
128
|
+
Progress:
|
|
129
|
+
tag: progress
|
|
130
|
+
className: vc-progress
|
|
131
|
+
void: true
|
|
132
|
+
Spinner:
|
|
133
|
+
tag: div
|
|
134
|
+
className: vc-spinner
|
|
80
135
|
List:
|
|
81
136
|
tag: ul
|
|
82
|
-
className:
|
|
137
|
+
className: vc-list
|
|
83
138
|
defaultRole: list
|
|
84
139
|
ListItem:
|
|
85
140
|
tag: li
|
|
86
|
-
className:
|
|
141
|
+
className: vc-list-item
|
|
87
142
|
defaultRole: listitem
|