view-contracts 0.1.0 → 0.3.0

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.
Files changed (47) hide show
  1. package/cli-contract.yaml +1 -1
  2. package/dist/view-contracts.bundle.mjs +161 -153
  3. package/dist/view-contracts.bundle.mjs.map +4 -4
  4. package/docs/cli-reference.md +1 -1
  5. package/package.json +14 -4
  6. package/renderers/compose/templates/Screen.kt.hbs +15 -0
  7. package/renderers/compose/templates/VcTheme.kt.hbs +19 -0
  8. package/renderers/react/README.md +19 -3
  9. package/renderers/react/defaults/theme.yaml +41 -0
  10. package/renderers/react/defaults/tokens.yaml +59 -0
  11. package/renderers/react/element-config.meta.json +22 -0
  12. package/renderers/react/elementMap.ts +55 -0
  13. package/renderers/react/elements.yaml +86 -0
  14. package/renderers/react/index.ts +2 -0
  15. package/renderers/react/lowering/lowerToJsx.ts +433 -0
  16. package/renderers/react/paths.ts +10 -0
  17. package/renderers/react/renderComponents.ts +65 -0
  18. package/renderers/react/renderRuntime.ts +157 -0
  19. package/renderers/react/routes.yaml +3 -2
  20. package/{src/renderer → renderers}/react/runtime/actionRuntime.ts +3 -3
  21. package/renderers/react/runtime/dispatch.ts +21 -0
  22. package/renderers/react/runtime/index.ts +5 -0
  23. package/renderers/react/runtime/style.ts +101 -0
  24. package/renderers/react/runtime/theme.css +83 -0
  25. package/renderers/react/runtime/types.ts +63 -0
  26. package/renderers/react/templates/components-index.ts.hbs +5 -0
  27. package/renderers/react/templates/dispatch.ts.hbs +22 -0
  28. package/renderers/react/templates/handlers.ts.hbs +4 -0
  29. package/renderers/react/templates/runtime-types.ts.hbs +10 -0
  30. package/renderers/react/templates/style-helpers.ts.hbs +2 -0
  31. package/renderers/react/templates/theme.css.hbs +2 -0
  32. package/renderers/swiftui/templates/Screen.swift.hbs +21 -0
  33. package/renderers/swiftui/templates/VcTheme.swift.hbs +21 -0
  34. package/spec/ui-dsl.schema.json +24 -16
  35. package/src/generated/schema/allowed-components.ts +1 -1
  36. package/src/generated/schema/{tokens.ts → dsl-enums.ts} +1 -1
  37. package/src/generated/schema/dsl-properties.ts +3 -2
  38. package/src/generated/schema/index.ts +2 -2
  39. package/src/generated/schema/lowering-style-properties.ts +28 -0
  40. package/src/generated/schema/react-renderer-element-config.ts +25 -0
  41. package/src/generated/schema/schema-document.ts +2 -2
  42. package/src/renderer/react/runtime/components.tsx +0 -216
  43. package/src/renderer/react/runtime/index.ts +0 -4
  44. package/src/renderer/react/runtime/shared.tsx +0 -53
  45. package/src/renderer/react/runtime/style.ts +0 -69
  46. package/src/renderer/react/runtime/theme.css +0 -68
  47. package/src/renderer/react/runtime/types.ts +0 -46
@@ -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.1.0
5
+ **Version:** 0.3.0
6
6
 
7
7
  ## Table of Contents
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "view-contracts",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Contract-first UI design toolchain — restricted TSX to View IR to platform renderers",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -12,7 +12,8 @@
12
12
  "files": [
13
13
  "dist/view-contracts.bundle.mjs",
14
14
  "dist/view-contracts.bundle.mjs.map",
15
- "src/renderer/react/runtime",
15
+ "renderers/react/runtime",
16
+ "renderers/react/defaults",
16
17
  "src/generated/schema",
17
18
  "spec",
18
19
  "renderers",
@@ -43,7 +44,10 @@
43
44
  "contracts:check": "tsx src/cli.ts contracts check -c examples/admin-dashboard/view-contracts.config.yaml",
44
45
  "schema:generate": "tsx scripts/schema-generate.ts",
45
46
  "cli-contracts:validate": "cli-contracts validate",
46
- "test:ci": "node dist/view-contracts.bundle.mjs --version && node dist/view-contracts.bundle.mjs --help",
47
+ "test": "vitest run",
48
+ "test:unit": "vitest run",
49
+ "test:smoke": "node dist/view-contracts.bundle.mjs --version && node dist/view-contracts.bundle.mjs --help",
50
+ "test:ci": "npm run test:unit && npm run build:publish && npm run test:smoke",
47
51
  "version": "node -e \"const fs=require('fs');const f='cli-contract.yaml';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace(/^ version: .*/m,' version: '+process.env.npm_package_version))\" && git add cli-contract.yaml",
48
52
  "prepublishOnly": "npm run build:publish && npm run test:ci"
49
53
  },
@@ -73,6 +77,7 @@
73
77
  },
74
78
  "dependencies": {
75
79
  "commander": "^14.0.3",
80
+ "handlebars": "^4.7.9",
76
81
  "js-yaml": "^4.1.1",
77
82
  "micro-contracts": "^0.17.10",
78
83
  "react": "^19.2.0",
@@ -80,6 +85,7 @@
80
85
  "typescript": "^5.9.3"
81
86
  },
82
87
  "devDependencies": {
88
+ "@types/handlebars": "^4.1.0",
83
89
  "@types/js-yaml": "^4.0.9",
84
90
  "@types/node": "^22.19.21",
85
91
  "@types/react": "^19.2.17",
@@ -88,7 +94,11 @@
88
94
  "ajv": "^8.18.0",
89
95
  "cli-contracts": "^0.33.13",
90
96
  "esbuild": "^0.28.0",
97
+ "pixelmatch": "^7.2.0",
98
+ "playwright": "^1.61.0",
99
+ "pngjs": "^7.0.0",
91
100
  "tsx": "^4.21.0",
92
- "vite": "^6.4.1"
101
+ "vite": "^6.4.1",
102
+ "vitest": "^4.1.9"
93
103
  }
94
104
  }
@@ -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
+ }
@@ -3,8 +3,24 @@
3
3
  Per-target mapping for view-contracts UI DSL → React.
4
4
 
5
5
  - `routes.yaml` — generated output file routing
6
- - `elements.yaml` — element → React component (hand-maintained)
7
- - `properties.yaml` — property style/attr (hand-maintained)
6
+ - `elements.yaml` — vocabulary element → HTML tag (SSoT for compile-time lowering)
7
+ - `element-config.meta.json` — shape of `elements.yaml` entries (drives schema codegen)
8
+ - `templates/*.hbs` — Handlebars templates for generated runtime files (dispatch, types, theme, etc.)
9
+ - `renderComponents.ts` / `renderRuntime.ts` — compile-time codegen (IR → JSX, runtime bundle)
10
+ - `lowering/lowerToJsx.ts` — element-map-driven JSX emission
11
+ - `runtime/` — preview/toolchain primitives (style, dispatch) — **not** copied to `generated/`
8
12
 
9
13
  Vocabulary (elements/properties) is defined in `spec/ui-dsl.schema.json` (from `ui-dsl-*.csv`).
10
- Sample-only preview helpers (Sidebar, Card, etc.) are **not** part of the vocabulary.
14
+ Preview vocabulary stubs are generated to `src/generated/preview/vocabulary.tsx`.
15
+ Sample-only preview helpers (Sidebar, Card, etc.) are **not** part of the vocabulary — they live in project `src/components/extensions.tsx`.
16
+
17
+ Generic renderer utilities (template engine, YAML element-map validation) live under `src/renderer/`.
18
+
19
+ ## Compile-time lowering
20
+
21
+ `renderComponents.ts` reads `elements.yaml` and emits native HTML/JSX into `generated/react/*.generated.tsx`.
22
+ `renderers/react/runtime/` is **not** copied to `generated/`.
23
+
24
+ ## Template customization
25
+
26
+ Set `rendererTemplatesDir` in `view-contracts.config.yaml` to override default templates.
@@ -0,0 +1,41 @@
1
+ # Default theme variants — component appearance via .vc-{element}--{variant} rules.
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
+ Card:
20
+ default:
21
+ background: "{ui.surface}"
22
+ Badge:
23
+ default:
24
+ background: "{ui.surface-muted}"
25
+ primary:
26
+ background: "{ui.primary-weak}"
27
+ success:
28
+ background: "{ui.success-weak}"
29
+ warning:
30
+ background: "{ui.warning-weak}"
31
+ danger:
32
+ background: "{ui.danger-weak}"
33
+ Alert:
34
+ default:
35
+ background: "{ui.surface}"
36
+ success:
37
+ background: "{ui.success-weak}"
38
+ warning:
39
+ background: "{ui.warning-weak}"
40
+ danger:
41
+ background: "{ui.danger-weak}"
@@ -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'
@@ -0,0 +1,22 @@
1
+ {
2
+ "description": "Per-element fields allowed in renderers/react/elements.yaml (React HTML lowering config — not UI DSL vocabulary).",
3
+ "fields": {
4
+ "tag": { "type": "string", "required": true },
5
+ "className": { "type": "string" },
6
+ "ariaLabel": { "type": "boolean" },
7
+ "ariaHidden": { "type": "boolean" },
8
+ "flex": { "type": "enum", "values": ["row", "column"] },
9
+ "wrapProp": { "type": "string" },
10
+ "flexGrowProp": { "type": "string" },
11
+ "void": { "type": "boolean" },
12
+ "levelProp": { "type": "string" },
13
+ "defaultLevel": { "type": "number" },
14
+ "actionEvent": { "type": "enum", "values": ["onClick", "onSubmit"] },
15
+ "fieldWrap": { "type": "boolean" },
16
+ "inputType": { "type": "string" },
17
+ "sizeClass": { "type": "string" },
18
+ "optionsProp": { "type": "string" },
19
+ "checkbox": { "type": "boolean" },
20
+ "defaultRole": { "type": "string" }
21
+ }
22
+ }
@@ -0,0 +1,55 @@
1
+ import { resolve } from 'node:path';
2
+ import { ALLOWED_COMPONENTS } from '../../src/generated/schema/allowed-components.js';
3
+ import type { ReactElementMapFile, ReactRendererElementConfig } from '../../src/generated/schema/react-renderer-element-config.js';
4
+ import { packageRoot } from '../../src/lib/packageRoot.js';
5
+ import {
6
+ loadYamlFile,
7
+ validateMapEntryFields,
8
+ validateVocabularyElementNames,
9
+ } from '../../src/renderer/elementMapValidation.js';
10
+ import { loadUiDslSchema } from '../../src/schema/loadSchema.js';
11
+
12
+ export type { ReactElementMapFile, ReactRendererElementConfig };
13
+ export type ReactElementMap = ReactElementMapFile;
14
+ export type ElementMapEntry = ReactRendererElementConfig;
15
+
16
+ const SOURCE_LABEL = 'renderers/react/elements.yaml';
17
+
18
+ let cachedMap: ReactElementMapFile | null = null;
19
+
20
+ function validateElementMap(map: ReactElementMapFile): void {
21
+ validateVocabularyElementNames(Object.keys(map.elements), ALLOWED_COMPONENTS, SOURCE_LABEL);
22
+ validateMapEntryFields(map.elements, ['tag'], SOURCE_LABEL);
23
+ }
24
+
25
+ export async function loadReactElementMap(customPath?: string): Promise<ReactElementMapFile> {
26
+ if (!customPath && cachedMap) return cachedMap;
27
+ const mapPath = customPath ?? resolve(packageRoot(), 'renderers/react/elements.yaml');
28
+ const raw = await loadYamlFile<ReactElementMapFile>(mapPath);
29
+ validateElementMap(raw);
30
+ if (!customPath) cachedMap = raw;
31
+ return raw;
32
+ }
33
+
34
+ /** DSL vocabulary element with a React HTML lowering entry (excludes project extensions). */
35
+ export function isLowerableVocabularyElement(
36
+ name: string,
37
+ map: ReactElementMapFile,
38
+ allowlistExtra: string[] = [],
39
+ ): boolean {
40
+ return ALLOWED_COMPONENTS.has(name) && !allowlistExtra.includes(name) && name in map.elements;
41
+ }
42
+
43
+ export async function assertElementMapCoversImplementedVocabulary(customPath?: string): Promise<void> {
44
+ const [schema, map] = await Promise.all([loadUiDslSchema(), loadReactElementMap(customPath)]);
45
+ const missing = Object.keys(schema.elements).filter((name) => !(name in map.elements));
46
+ if (missing.length > 0) {
47
+ console.warn(
48
+ `${SOURCE_LABEL}: ${missing.length} DSL element(s) have no React HTML map yet: ${missing.join(', ')}`,
49
+ );
50
+ }
51
+ }
52
+
53
+ export function isVocabularyElement(name: string, map: ReactElementMapFile, allowlistExtra: string[] = []): boolean {
54
+ return name in map.elements && !allowlistExtra.includes(name);
55
+ }
@@ -0,0 +1,86 @@
1
+ # React renderer element → HTML lowering map (SSoT).
2
+ # Vocabulary from spec/ui-dsl.schema.json. Extension components are project-specific
3
+ # (allowlistExtra) and stay as React component imports — not listed here.
4
+
5
+ elements:
6
+ Page:
7
+ tag: main
8
+ className: vc-page
9
+ ariaLabel: true
10
+ Box:
11
+ tag: div
12
+ className: vc-box
13
+ ariaLabel: true
14
+ Column:
15
+ tag: div
16
+ className: vc-column
17
+ flex: column
18
+ ariaLabel: true
19
+ Row:
20
+ tag: div
21
+ className: vc-row
22
+ flex: row
23
+ wrapProp: wrap
24
+ ariaLabel: true
25
+ Stack:
26
+ tag: div
27
+ className: vc-stack
28
+ ariaLabel: true
29
+ Spacer:
30
+ tag: div
31
+ className: vc-spacer
32
+ ariaHidden: true
33
+ flexGrowProp: weight
34
+ Divider:
35
+ tag: hr
36
+ className: vc-divider
37
+ void: true
38
+ Heading:
39
+ tag: dynamic-heading
40
+ className: vc-heading
41
+ levelProp: level
42
+ defaultLevel: 2
43
+ Text:
44
+ tag: p
45
+ className: vc-text
46
+ Link:
47
+ tag: a
48
+ className: vc-link
49
+ actionEvent: onClick
50
+ Button:
51
+ tag: button
52
+ className: vc-button
53
+ actionEvent: onClick
54
+ Form:
55
+ tag: form
56
+ className: vc-form
57
+ actionEvent: onSubmit
58
+ ariaLabel: true
59
+ TextInput:
60
+ tag: input
61
+ className: vc-text-input
62
+ fieldWrap: true
63
+ inputType: text
64
+ sizeClass: vc-field
65
+ TextArea:
66
+ tag: textarea
67
+ className: vc-text-area
68
+ fieldWrap: true
69
+ Select:
70
+ tag: select
71
+ className: vc-select
72
+ fieldWrap: true
73
+ sizeClass: vc-field
74
+ optionsProp: options
75
+ Checkbox:
76
+ tag: label
77
+ className: vc-checkbox
78
+ checkbox: true
79
+ List:
80
+ tag: ul
81
+ className: vc-list
82
+ defaultRole: list
83
+ ListItem:
84
+ tag: li
85
+ className: vc-list-item
86
+ defaultRole: listitem
@@ -0,0 +1,2 @@
1
+ export { renderReactFromIr, renderReactComponent } from './renderComponents.js';
2
+ export { renderReactRuntime } from './renderRuntime.js';