view-contracts 0.4.5 → 0.5.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.
@@ -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.4.5
5
+ **Version:** 0.5.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.4.5",
3
+ "version": "0.5.0",
4
4
  "description": "Contract-first UI design toolchain — restricted TSX to View IR to platform renderers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,15 +1,14 @@
1
- # Compose renderer scaffold
1
+ # Compose renderer
2
2
 
3
- **Not implemented yet** beyond theme / screen stubs. View IR lowering and modifier emission are future work.
3
+ Jetpack Compose renderer implemented as a SwiftUI-parity lowering path:
4
4
 
5
- Compose `Modifier` chains are **compositional** (not CSS-style override). view-contracts may later introduce a separate **modifier pipeline** concept (wrapper-style transforms, analogous to nested HTML layout shells) — that is **out of scope for the current phase**. When styling is implemented, defaults + variant + instance props will be **folded at compile time** into resolved values, not chained for cascade-like override. See [docs/ARCHITECTURE.md](../../docs/ARCHITECTURE.md) § Native renderers.
5
+ - Full vocabulary map in `elements.yaml` (schema-backed).
6
+ - Lowering implementation in `lowering/lowerToCompose.ts`.
7
+ - Modifier synthesis in `lowering/modifiers.ts`.
8
+ - Component emitter in `renderComponents.ts`.
9
+ - Runtime emitter (screens, partials, theme, sample JSON, dispatch stub) in `renderRuntime.ts`.
6
10
 
7
11
  - Maps: `elements.yaml`, `properties.yaml` (hand-maintained under this directory)
8
12
  - Routes: `routes.yaml`
9
13
  - Templates: `templates/`
10
14
  - Theme codegen spec: [../NATIVE_THEME_EMIT.md](../NATIVE_THEME_EMIT.md)
11
-
12
- Implement Compose code generation by reading View IR + renderer maps.
13
- Theme is emitted at build time via `src/design/emit/compose.ts` and `templates/theme/*.hbs` — same Token IR + Theme IR as React `theme.css`, not runtime JSON.
14
-
15
- Vocabulary: `spec/ui-dsl.schema.json` (from `ui-dsl-*.csv`).
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "Compose element map metadata",
4
+ "description": "Auxiliary metadata for renderers/compose/elements.yaml validation and docs.",
5
+ "type": "object",
6
+ "required": ["elements"],
7
+ "properties": {
8
+ "elements": {
9
+ "type": "object",
10
+ "additionalProperties": {
11
+ "type": "object",
12
+ "properties": {
13
+ "container": { "type": "string" },
14
+ "leaf": { "type": "string" },
15
+ "spacingProp": { "type": "string" }
16
+ }
17
+ }
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,44 @@
1
+ import { resolve } from 'node:path';
2
+ import { ALLOWED_COMPONENTS } from '../../src/schema/dsl-catalog.js';
3
+ import { packageRoot } from '../../src/lib/packageRoot.js';
4
+ import { loadYamlFile, validateVocabularyElementNames } from '../../src/renderer/elementMapValidation.js';
5
+
6
+ export interface ComposeElementConfig {
7
+ container?: 'group' | 'vstack' | 'hstack' | 'navstack' | 'scrollview' | 'field' | 'cellgroup' | 'tablestack';
8
+ leaf?: 'text' | 'button' | 'textfield' | 'texteditor' | 'picker' | 'spacer' | 'divider' | 'image' | 'progressview' | 'toggle';
9
+ spacingProp?: string;
10
+ }
11
+
12
+ export interface ComposeElementMap {
13
+ elements: Record<string, ComposeElementConfig>;
14
+ }
15
+
16
+ const SOURCE_LABEL = 'renderers/compose/elements.yaml';
17
+
18
+ let cachedMap: ComposeElementMap | null = null;
19
+
20
+ function validateComposeElementMap(map: ComposeElementMap): void {
21
+ validateVocabularyElementNames(Object.keys(map.elements), ALLOWED_COMPONENTS, SOURCE_LABEL);
22
+ for (const [name, entry] of Object.entries(map.elements)) {
23
+ const hasContainer = entry.container !== undefined && entry.container !== null;
24
+ const hasLeaf = entry.leaf !== undefined && entry.leaf !== null;
25
+ if (hasContainer === hasLeaf) {
26
+ throw new Error(
27
+ `${SOURCE_LABEL}: element "${name}" must have exactly one of "container" or "leaf"`,
28
+ );
29
+ }
30
+ }
31
+ }
32
+
33
+ export async function loadComposeElementMap(customPath?: string): Promise<ComposeElementMap> {
34
+ if (!customPath && cachedMap) return cachedMap;
35
+ const mapPath = customPath ?? resolve(packageRoot(), 'renderers/compose/elements.yaml');
36
+ const raw = await loadYamlFile<ComposeElementMap>(mapPath);
37
+ validateComposeElementMap(raw);
38
+ if (!customPath) cachedMap = raw;
39
+ return raw;
40
+ }
41
+
42
+ export function isComposeVocabulary(name: string, map: ComposeElementMap, allowlistExtra: string[] = []): boolean {
43
+ return name in map.elements && !allowlistExtra.includes(name);
44
+ }
@@ -0,0 +1,82 @@
1
+ # Compose renderer element map — maps DSL elements to Jetpack Compose primitives.
2
+
3
+ elements:
4
+ App:
5
+ container: group
6
+ Page:
7
+ container: navstack
8
+ SafeArea:
9
+ container: group
10
+ Box:
11
+ container: group
12
+ Row:
13
+ container: hstack
14
+ spacingProp: gap
15
+ Column:
16
+ container: vstack
17
+ spacingProp: gap
18
+ Stack:
19
+ container: vstack
20
+ spacingProp: gap
21
+ Spacer:
22
+ leaf: spacer
23
+ Divider:
24
+ leaf: divider
25
+ ScrollArea:
26
+ container: scrollview
27
+ List:
28
+ container: vstack
29
+ spacingProp: gap
30
+ ListItem:
31
+ container: group
32
+ Text:
33
+ leaf: text
34
+ Link:
35
+ leaf: text
36
+ Image:
37
+ leaf: image
38
+ Icon:
39
+ leaf: image
40
+ Button:
41
+ leaf: button
42
+ IconButton:
43
+ leaf: button
44
+ Form:
45
+ container: vstack
46
+ spacingProp: gap
47
+ Field:
48
+ container: field
49
+ TextInput:
50
+ leaf: textfield
51
+ TextArea:
52
+ leaf: texteditor
53
+ Select:
54
+ leaf: picker
55
+ Checkbox:
56
+ leaf: toggle
57
+ Radio:
58
+ leaf: picker
59
+ Switch:
60
+ leaf: toggle
61
+ Modal:
62
+ container: group
63
+ Drawer:
64
+ container: group
65
+ Popover:
66
+ container: group
67
+ Tooltip:
68
+ leaf: text
69
+ Progress:
70
+ leaf: progressview
71
+ Spinner:
72
+ leaf: progressview
73
+ Table:
74
+ container: tablestack
75
+ TableHeader:
76
+ container: hstack
77
+ TableColumn:
78
+ container: cellgroup
79
+ TableRow:
80
+ container: hstack
81
+ TableCell:
82
+ container: cellgroup
@@ -0,0 +1,5 @@
1
+ export { loadComposeElementMap, isComposeVocabulary } from './elementMap.js';
2
+ export type { ComposeElementConfig, ComposeElementMap } from './elementMap.js';
3
+ export { renderComposeComponent } from './renderComponents.js';
4
+ export { mergeVisualProps } from '../../src/renderer/style/mergeVisualProps.js';
5
+ export { visualPropSetToModifiers } from './lowering/modifiers.js';