openuispec 0.2.0 → 0.2.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.
Files changed (34) hide show
  1. package/README.md +15 -0
  2. package/examples/social-app/AGENTS.md +1 -1
  3. package/examples/social-app/CLAUDE.md +1 -1
  4. package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +24 -24
  5. package/examples/social-app/generated/web/social-app/src/components/ui.tsx +1 -1
  6. package/examples/social-app/generated/web/social-app/src/styles.css +1 -0
  7. package/examples/social-app/openuispec/contracts/action_trigger.yaml +21 -0
  8. package/examples/social-app/openuispec/contracts/nav_container.yaml +21 -0
  9. package/examples/todo-orbit/openuispec/contracts/action_trigger.yaml +10 -0
  10. package/examples/todo-orbit/openuispec/contracts/nav_container.yaml +25 -5
  11. package/mcp-server/screenshot.ts +11 -5
  12. package/package.json +4 -2
  13. package/schema/contract.schema.json +57 -7
  14. package/schema/custom-contract.schema.json +59 -8
  15. package/schema/defs/action.schema.json +1 -1
  16. package/schema/defs/adaptive.schema.json +1 -1
  17. package/schema/defs/common.schema.json +8 -8
  18. package/schema/defs/data-binding.schema.json +3 -3
  19. package/schema/defs/validation.schema.json +1 -1
  20. package/schema/flow.schema.json +14 -14
  21. package/schema/locale.schema.json +1 -1
  22. package/schema/openuispec.schema.json +5 -5
  23. package/schema/platform.schema.json +2 -2
  24. package/schema/screen.schema.json +23 -23
  25. package/schema/tokens/color.schema.json +9 -9
  26. package/schema/tokens/elevation.schema.json +2 -2
  27. package/schema/tokens/icons.schema.json +1 -1
  28. package/schema/tokens/layout.schema.json +9 -9
  29. package/schema/tokens/motion.schema.json +2 -2
  30. package/schema/tokens/spacing.schema.json +4 -4
  31. package/schema/tokens/themes.schema.json +2 -2
  32. package/schema/tokens/typography.schema.json +4 -4
  33. package/schema/validate.ts +1 -1
  34. package/spec/openuispec-v0.1.md +83 -4
package/README.md CHANGED
@@ -244,6 +244,21 @@ Validate with: `openuispec validate`
244
244
 
245
245
  Use `openuispec validate semantic` to run cross-reference linting for locale keys, formatter refs, mapper refs, contracts, icons, navigation targets, and API endpoint references.
246
246
 
247
+ ### Shared interactive state roles
248
+
249
+ Interactive contracts may optionally express state-specific visual roles inside their token maps with a nested `states:` object. This lets generators use explicit foreground/background/icon/border roles for `default`, `active`, `selected`, `pressed`, `focused`, `disabled`, `loading`, and `error` states instead of inferring them from container colors or platform defaults.
250
+
251
+ Allowed visual role keys (no others are permitted):
252
+
253
+ - `background`
254
+ - `text`
255
+ - `icon`
256
+ - `border`
257
+ - `badge_background`
258
+ - `badge_text`
259
+
260
+ When `states:` is omitted, generators fall back to the token values defined at that level plus the base contract semantics.
261
+
247
262
  ## Output directories
248
263
 
249
264
  By default, drift stores state in `generated/<target>/<project>/`. To point targets to your actual code directories, add `output_dir` to `openuispec.yaml`:
@@ -1,5 +1,5 @@
1
1
  <!-- openuispec-rules-start -->
2
- <!-- openuispec-rules-version: 0.1.47 -->
2
+ <!-- openuispec-rules-version: 0.2.1 -->
3
3
  # OpenUISpec — AI Assistant Rules
4
4
  # ================================
5
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -1,5 +1,5 @@
1
1
  <!-- openuispec-rules-start -->
2
- <!-- openuispec-rules-version: 0.1.47 -->
2
+ <!-- openuispec-rules-version: 0.2.1 -->
3
3
  # OpenUISpec — AI Assistant Rules
4
4
  # ================================
5
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -91,23 +91,11 @@ export function AppShell() {
91
91
 
92
92
  {sizeClass !== "expanded" ? <BottomTabBar unreadCount={unreadCount} /> : null}
93
93
 
94
- {location.pathname.startsWith("/home") || location.pathname === "/" ? (
95
- <Link
96
- to="/create"
97
- className={cn(
98
- "interactive-press fixed z-30 flex h-14 w-14 items-center justify-center rounded-cap-primary bg-[var(--color-brand-primary)] text-white shadow-md",
99
- sizeClass === "expanded" ? "bottom-8 right-8" : "bottom-20 right-4",
100
- )}
101
- aria-label={t("nav.create")}
102
- >
103
- <Icon name="create_post" className="h-6 w-6" />
104
- </Link>
105
- ) : null}
106
94
  </div>
107
95
 
108
96
  {toast ? (
109
97
  <div className="pointer-events-none fixed inset-x-0 top-4 z-50 flex justify-center px-4">
110
- <div className="rounded-cap-primary bg-[var(--color-brand-primary)] px-5 py-3 text-sm font-medium text-white shadow-md">
98
+ <div className="rounded-cap-primary bg-[var(--color-brand-primary)] px-5 py-3 text-sm font-medium text-[var(--color-brand-primary-on)] shadow-md">
111
99
  {toast.message}
112
100
  </div>
113
101
  </div>
@@ -160,7 +148,7 @@ function BottomTabBar({ unreadCount }: { unreadCount: number }) {
160
148
  <Icon name={route.icon} className="h-5 w-5" />
161
149
  <span>{t(route.labelKey)}</span>
162
150
  {route.to === "/notifications" && unreadCount > 0 ? (
163
- <span className="absolute right-3 top-2 inline-flex min-h-5 min-w-5 items-center justify-center rounded-full bg-[var(--color-brand-accent)] px-1.5 text-[10px] font-semibold text-white">
151
+ <span className="absolute right-3 top-2 inline-flex min-h-5 min-w-5 items-center justify-center rounded-full bg-[var(--color-brand-accent)] px-1.5 text-[10px] font-semibold text-[var(--color-brand-accent-on)]">
164
152
  {unreadCount}
165
153
  </span>
166
154
  ) : null}
@@ -187,20 +175,32 @@ function DesktopSidebar({ unreadCount }: { unreadCount: number }) {
187
175
  to={route.to}
188
176
  className={({ isActive }) =>
189
177
  cn(
190
- "relative flex items-center gap-3 rounded-cap-primary px-4 py-3 text-sm font-semibold transition",
178
+ "relative rounded-cap-primary px-4 py-3 text-sm font-semibold transition",
191
179
  isActive
192
- ? "bg-[var(--color-brand-primary)] text-white shadow-sm"
193
- : "text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-tertiary)]",
180
+ ? "bg-[var(--color-brand-primary)] shadow-sm"
181
+ : "hover:bg-[var(--color-surface-tertiary)]",
194
182
  )
195
183
  }
196
184
  >
197
- <Icon name={route.icon} className="h-5 w-5" />
198
- <span>{t(route.labelKey)}</span>
199
- {route.to === "/notifications" && unreadCount > 0 ? (
200
- <span className="ml-auto rounded-full bg-[var(--color-brand-accent)] px-2 py-1 text-[10px] font-semibold text-white">
201
- {unreadCount}
202
- </span>
203
- ) : null}
185
+ {({ isActive }) => (
186
+ <div className="flex items-center gap-3">
187
+ <Icon
188
+ name={route.icon}
189
+ className={cn(
190
+ "h-5 w-5 shrink-0",
191
+ isActive ? "text-[var(--color-brand-primary-on)]" : "text-[var(--color-text-secondary)]",
192
+ )}
193
+ />
194
+ <span className={cn(isActive ? "text-[var(--color-brand-primary-on)]" : "text-[var(--color-text-secondary)]")}>
195
+ {t(route.labelKey)}
196
+ </span>
197
+ {route.to === "/notifications" && unreadCount > 0 ? (
198
+ <span className="ml-auto rounded-full bg-[var(--color-brand-accent)] px-2 py-1 text-[10px] font-semibold text-[var(--color-brand-accent-on)]">
199
+ {unreadCount}
200
+ </span>
201
+ ) : null}
202
+ </div>
203
+ )}
204
204
  </NavLink>
205
205
  ))}
206
206
  </nav>
@@ -73,7 +73,7 @@ export function ActionButton({
73
73
  : variant === "secondary"
74
74
  ? "rounded-cap-alternate border-[var(--color-border-strong)] bg-transparent text-[var(--color-text-primary)]"
75
75
  : variant === "destructive"
76
- ? "rounded-cap-primary border-transparent bg-[var(--color-semantic-danger)] text-white"
76
+ ? "rounded-cap-primary border-transparent bg-[var(--color-semantic-danger)] text-[var(--color-semantic-danger-on)]"
77
77
  : variant === "fab"
78
78
  ? "rounded-cap-primary border-transparent bg-[var(--color-brand-primary)] text-[var(--color-brand-primary-on)] shadow-md"
79
79
  : selected
@@ -16,6 +16,7 @@
16
16
  --color-border-default: #e0dcd6;
17
17
  --color-border-strong: #c5c0b8;
18
18
  --color-semantic-danger: #d43b3b;
19
+ --color-semantic-danger-on: #ffffff;
19
20
  color-scheme: light;
20
21
  }
21
22
 
@@ -15,6 +15,10 @@ action_trigger:
15
15
  text: "color.brand.primary.on_color"
16
16
  border_width: 0
17
17
  shape: "2px 24px 2px 24px"
18
+ states:
19
+ disabled:
20
+ background: "color.surface.tertiary"
21
+ text: "color.text.tertiary"
18
22
  platform_mapping:
19
23
  ios: { shape: "RoundedCapShape(diagonal: .primary)" }
20
24
  android: { shape: "RoundedCapShape(diagonal = Primary)" }
@@ -36,6 +40,10 @@ action_trigger:
36
40
  text: "color.text.primary"
37
41
  border: { color: "color.border.strong", width: 1 }
38
42
  shape: "24px 2px 24px 2px"
43
+ states:
44
+ disabled:
45
+ text: "color.text.tertiary"
46
+ border: { color: "color.border.default", width: 1 }
39
47
  platform_mapping:
40
48
  ios: { shape: "RoundedCapShape(diagonal: .alternate)" }
41
49
  android: { shape: "RoundedCapShape(diagonal = Alternate)" }
@@ -56,6 +64,15 @@ action_trigger:
56
64
  text_selected: "color.brand.accent.on_color"
57
65
  border: { color: "color.border.default", width: 1 }
58
66
  shape: "2px 24px 2px 24px"
67
+ states:
68
+ default:
69
+ background: "transparent"
70
+ text: "color.text.primary"
71
+ border: { color: "color.border.default", width: 1 }
72
+ selected:
73
+ background: "color.brand.accent"
74
+ text: "color.brand.accent.on_color"
75
+ border: { color: "color.brand.accent", width: 1 }
59
76
  platform_mapping:
60
77
  ios: { shape: "RoundedCapShape(diagonal: .primary)" }
61
78
  android: { shape: "RoundedCapShape(diagonal = Primary)" }
@@ -71,6 +88,10 @@ action_trigger:
71
88
  background: "color.semantic.danger"
72
89
  text: "color.semantic.danger.on_color"
73
90
  shape: "2px 24px 2px 24px"
91
+ states:
92
+ disabled:
93
+ background: "color.surface.tertiary"
94
+ text: "color.text.tertiary"
74
95
  platform_mapping:
75
96
  ios: { shape: "RoundedCapShape(diagonal: .primary)", role: "destructive" }
76
97
  android: { shape: "RoundedCapShape(diagonal = Primary)" }
@@ -9,6 +9,16 @@ nav_container:
9
9
  background: "color.surface.primary"
10
10
  tint_active: "color.brand.primary"
11
11
  tint_inactive: "color.text.tertiary"
12
+ item:
13
+ states:
14
+ default:
15
+ text: "color.text.tertiary"
16
+ icon: "color.text.tertiary"
17
+ active:
18
+ text: "color.brand.primary"
19
+ icon: "color.brand.primary"
20
+ badge_background: "color.brand.accent"
21
+ badge_text: "color.brand.accent.on_color"
12
22
  platform_mapping:
13
23
  ios: { uses_system_tab_bar: true, height: 49 }
14
24
  android: { component: "NavigationBar" }
@@ -23,6 +33,17 @@ nav_container:
23
33
  tokens:
24
34
  background: "color.surface.secondary"
25
35
  width: 280
36
+ item:
37
+ states:
38
+ default:
39
+ text: "color.text.secondary"
40
+ icon: "color.text.secondary"
41
+ active:
42
+ background: "color.brand.primary"
43
+ text: "color.brand.primary.on_color"
44
+ icon: "color.brand.primary.on_color"
45
+ badge_background: "color.brand.accent"
46
+ badge_text: "color.brand.accent.on_color"
26
47
  platform_mapping:
27
48
  ios: { style: "sidebar" }
28
49
  android: { component: "NavigationRail" }
@@ -5,11 +5,21 @@ action_trigger:
5
5
  text: "color.brand.primary.on_color"
6
6
  border: { width: 2, color: "color.brand.primary" }
7
7
  cut_size: "spacing.sm"
8
+ states:
9
+ disabled:
10
+ background: "color.surface.secondary"
11
+ text: "color.text.tertiary"
12
+ border: { width: 2, color: "color.border.default" }
8
13
  ghost:
9
14
  background: "color.surface.secondary"
10
15
  text: "color.text.primary"
11
16
  border: { width: 2, color: "color.brand.primary" }
12
17
  cut_size: "spacing.sm"
18
+ states:
19
+ disabled:
20
+ background: "color.surface.secondary"
21
+ text: "color.text.tertiary"
22
+ border: { width: 2, color: "color.border.default" }
13
23
  platform_mapping:
14
24
  ios:
15
25
  primary: { shape: "CutCornerShape", clip: true, style: ".plain" }
@@ -6,24 +6,44 @@ nav_container:
6
6
  border_top: { width: 1, color: "color.border.default" }
7
7
  icon_size: 22
8
8
  label_style: "typography.caption"
9
- active_color: "color.brand.primary"
10
- inactive_color: "color.text.tertiary"
9
+ item:
10
+ states:
11
+ default:
12
+ text: "color.text.tertiary"
13
+ icon: "color.text.tertiary"
14
+ active:
15
+ text: "color.brand.primary"
16
+ icon: "color.brand.primary"
11
17
  rail:
12
18
  width: 76
13
19
  background: "color.surface.secondary"
14
20
  icon_size: 22
15
21
  label_style: "typography.caption"
16
- active_color: "color.brand.primary"
22
+ item:
23
+ states:
24
+ default:
25
+ text: "color.text.secondary"
26
+ icon: "color.text.secondary"
27
+ active:
28
+ text: "color.brand.primary"
29
+ icon: "color.brand.primary"
17
30
  sidebar:
18
31
  width: { collapsed: 72, expanded: 240 }
19
32
  background: "color.surface.secondary"
20
33
  item_height: 48
21
34
  item_radius: "spacing.sm"
22
35
  item_padding_h: "spacing.md"
23
- active_background: "color.brand.primary"
24
- active_text: "color.brand.primary.on_color"
25
36
  icon_size: 20
26
37
  label_style: "typography.body_sm"
38
+ item:
39
+ states:
40
+ default:
41
+ text: "color.text.secondary"
42
+ icon: "color.text.secondary"
43
+ active:
44
+ background: "color.brand.primary"
45
+ text: "color.brand.primary.on_color"
46
+ icon: "color.brand.primary.on_color"
27
47
  platform_mapping:
28
48
  ios:
29
49
  tab_bar: { widget: "TabView" }
@@ -246,17 +246,23 @@ export async function takeScreenshot(
246
246
 
247
247
  // ── cleanup ─────────────────────────────────────────────────────────
248
248
 
249
- export function shutdownAll() {
249
+ export async function shutdownAll() {
250
250
  for (const [, instance] of servers) {
251
251
  try { instance.process.kill(); } catch { /* already dead */ }
252
252
  }
253
253
  servers.clear();
254
254
  if (browserInstance) {
255
- try { browserInstance.close(); } catch { /* ignore */ }
255
+ try { await browserInstance.close(); } catch { /* ignore */ }
256
256
  browserInstance = null;
257
257
  }
258
258
  }
259
259
 
260
- process.on("exit", shutdownAll);
261
- process.on("SIGINT", () => { shutdownAll(); process.exit(0); });
262
- process.on("SIGTERM", () => { shutdownAll(); process.exit(0); });
260
+ process.on("exit", () => {
261
+ // Sync-only fallback for process exit
262
+ for (const [, instance] of servers) {
263
+ try { instance.process.kill(); } catch { /* already dead */ }
264
+ }
265
+ servers.clear();
266
+ });
267
+ process.on("SIGINT", () => { shutdownAll().then(() => process.exit(0)); });
268
+ process.on("SIGTERM", () => { shutdownAll().then(() => process.exit(0)); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",
@@ -28,7 +28,9 @@
28
28
  "openuispec-mcp": "./mcp-server/index.ts"
29
29
  },
30
30
  "scripts": {
31
- "test": "node --import tsx --test tests/*.test.ts",
31
+ "test": "node --import tsx --test tests/check.test.ts tests/configure-target.test.ts tests/drift-prepare.test.ts tests/init.test.ts tests/mcp-tools.test.ts tests/semantic-lint.test.ts tests/status.test.ts",
32
+ "test:screenshot": "node --import tsx --test tests/mcp-screenshot.test.ts",
33
+ "test:all": "node --import tsx --test tests/*.test.ts",
32
34
  "validate": "tsx schema/validate.ts",
33
35
  "validate:manifest": "tsx schema/validate.ts manifest",
34
36
  "validate:tokens": "tsx schema/validate.ts tokens",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://openuispec.org/schema/contract.schema.json",
3
+ "$id": "https://openuispec.rsteam.uz/schema/contract.schema.json",
4
4
  "title": "OpenUISpec Standard Contract Extension",
5
5
  "description": "Extension file for a standard contract — add variants, override tokens, platform mapping, and generation hints",
6
6
  "type": "object",
@@ -36,13 +36,12 @@
36
36
  "type": "object",
37
37
  "description": "Additional props beyond the spec definition",
38
38
  "additionalProperties": {
39
- "$ref": "https://openuispec.org/schema/custom-contract.schema.json#/$defs/prop_def"
39
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/prop_def"
40
40
  }
41
41
  },
42
42
  "tokens": {
43
- "type": "object",
44
43
  "description": "Token overrides at the contract level",
45
- "additionalProperties": true
44
+ "$ref": "#/$defs/token_object"
46
45
  },
47
46
  "platform_mapping": {
48
47
  "type": "object",
@@ -80,7 +79,7 @@
80
79
  "type": "array",
81
80
  "description": "Additional behavioral test cases",
82
81
  "items": {
83
- "$ref": "https://openuispec.org/schema/custom-contract.schema.json#/$defs/test_case"
82
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/test_case"
84
83
  }
85
84
  }
86
85
  },
@@ -95,9 +94,8 @@
95
94
  "description": "Human-readable description of this variant"
96
95
  },
97
96
  "tokens": {
98
- "type": "object",
99
97
  "description": "Visual token bindings for this variant",
100
- "additionalProperties": true
98
+ "$ref": "#/$defs/token_object"
101
99
  },
102
100
  "platform_mapping": {
103
101
  "type": "object",
@@ -124,6 +122,58 @@
124
122
  }
125
123
  },
126
124
  "additionalProperties": false
125
+ },
126
+ "token_object": {
127
+ "type": "object",
128
+ "description": "Recursive token object. If a `states` key is present, it must use the documented interactive state-role shape.",
129
+ "properties": {
130
+ "states": {
131
+ "$ref": "#/$defs/state_role_map"
132
+ }
133
+ },
134
+ "additionalProperties": {
135
+ "$ref": "#/$defs/token_value"
136
+ }
137
+ },
138
+ "token_value": {
139
+ "description": "Any token value, including nested token objects and arrays.",
140
+ "oneOf": [
141
+ {
142
+ "type": ["string", "number", "boolean", "null"]
143
+ },
144
+ {
145
+ "type": "array",
146
+ "items": {
147
+ "$ref": "#/$defs/token_value"
148
+ }
149
+ },
150
+ {
151
+ "$ref": "#/$defs/token_object"
152
+ }
153
+ ]
154
+ },
155
+ "state_role_map": {
156
+ "type": "object",
157
+ "description": "Map of state names to visual-role overrides.",
158
+ "propertyNames": {
159
+ "pattern": "^[a-z][a-z0-9_]*$"
160
+ },
161
+ "additionalProperties": {
162
+ "$ref": "#/$defs/state_visual_roles"
163
+ }
164
+ },
165
+ "state_visual_roles": {
166
+ "type": "object",
167
+ "description": "Visual-role overrides available inside token `states` maps.",
168
+ "properties": {
169
+ "background": { "$ref": "#/$defs/token_value" },
170
+ "text": { "$ref": "#/$defs/token_value" },
171
+ "icon": { "$ref": "#/$defs/token_value" },
172
+ "border": { "$ref": "#/$defs/token_value" },
173
+ "badge_background": { "$ref": "#/$defs/token_value" },
174
+ "badge_text": { "$ref": "#/$defs/token_value" }
175
+ },
176
+ "additionalProperties": false
127
177
  }
128
178
  }
129
179
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://openuispec.org/schema/custom-contract.schema.json",
3
+ "$id": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json",
4
4
  "title": "OpenUISpec Custom Contract",
5
5
  "description": "Custom contract extension — root key must be x_ prefixed, value follows the standard contract anatomy",
6
6
  "type": "object",
@@ -10,7 +10,7 @@
10
10
  "pattern": "^x_[a-z][a-z0-9_]*$"
11
11
  },
12
12
  "additionalProperties": {
13
- "$ref": "https://openuispec.org/schema/custom-contract.schema.json#/$defs/contract_def"
13
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/contract_def"
14
14
  },
15
15
  "$defs": {
16
16
  "contract_def": {
@@ -25,14 +25,14 @@
25
25
  "type": "object",
26
26
  "description": "Typed property definitions for this contract",
27
27
  "additionalProperties": {
28
- "$ref": "https://openuispec.org/schema/custom-contract.schema.json#/$defs/prop_def"
28
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/prop_def"
29
29
  }
30
30
  },
31
31
  "states": {
32
32
  "type": "object",
33
33
  "description": "State machine definition — each key is a state name",
34
34
  "additionalProperties": {
35
- "$ref": "https://openuispec.org/schema/custom-contract.schema.json#/$defs/state_def"
35
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/state_def"
36
36
  }
37
37
  },
38
38
  "a11y": {
@@ -62,9 +62,8 @@
62
62
  "additionalProperties": true
63
63
  },
64
64
  "tokens": {
65
- "type": "object",
66
65
  "description": "Per-variant visual token bindings",
67
- "additionalProperties": true
66
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_object"
68
67
  },
69
68
  "platform_mapping": {
70
69
  "type": "object",
@@ -92,7 +91,7 @@
92
91
  "type": "object",
93
92
  "description": "Per-platform library/framework requirements",
94
93
  "additionalProperties": {
95
- "$ref": "https://openuispec.org/schema/custom-contract.schema.json#/$defs/dependency_def"
94
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/dependency_def"
96
95
  }
97
96
  },
98
97
  "generation": {
@@ -121,7 +120,7 @@
121
120
  "type": "array",
122
121
  "description": "Behavioral verification scenarios",
123
122
  "items": {
124
- "$ref": "https://openuispec.org/schema/custom-contract.schema.json#/$defs/test_case"
123
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/test_case"
125
124
  }
126
125
  }
127
126
  },
@@ -208,6 +207,58 @@
208
207
  },
209
208
  "additionalProperties": false
210
209
  },
210
+ "token_object": {
211
+ "type": "object",
212
+ "description": "Recursive token object. If a `states` key is present, it must use the documented interactive state-role shape.",
213
+ "properties": {
214
+ "states": {
215
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/state_role_map"
216
+ }
217
+ },
218
+ "additionalProperties": {
219
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_value"
220
+ }
221
+ },
222
+ "token_value": {
223
+ "description": "Any token value, including nested token objects and arrays.",
224
+ "oneOf": [
225
+ {
226
+ "type": ["string", "number", "boolean", "null"]
227
+ },
228
+ {
229
+ "type": "array",
230
+ "items": {
231
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_value"
232
+ }
233
+ },
234
+ {
235
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_object"
236
+ }
237
+ ]
238
+ },
239
+ "state_role_map": {
240
+ "type": "object",
241
+ "description": "Map of state names to visual-role overrides.",
242
+ "propertyNames": {
243
+ "pattern": "^[a-z][a-z0-9_]*$"
244
+ },
245
+ "additionalProperties": {
246
+ "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/state_visual_roles"
247
+ }
248
+ },
249
+ "state_visual_roles": {
250
+ "type": "object",
251
+ "description": "Visual-role overrides available inside token `states` maps.",
252
+ "properties": {
253
+ "background": { "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_value" },
254
+ "text": { "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_value" },
255
+ "icon": { "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_value" },
256
+ "border": { "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_value" },
257
+ "badge_background": { "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_value" },
258
+ "badge_text": { "$ref": "https://openuispec.rsteam.uz/schema/custom-contract.schema.json#/$defs/token_value" }
259
+ },
260
+ "additionalProperties": false
261
+ },
211
262
  "dependency_def": {
212
263
  "type": "object",
213
264
  "description": "Platform-specific dependencies",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://openuispec.org/schema/defs/action.schema.json",
3
+ "$id": "https://openuispec.rsteam.uz/schema/defs/action.schema.json",
4
4
  "title": "OpenUISpec Action",
5
5
  "description": "Discriminated union of the 14 action types in OpenUISpec",
6
6
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://openuispec.org/schema/defs/adaptive.schema.json",
3
+ "$id": "https://openuispec.rsteam.uz/schema/defs/adaptive.schema.json",
4
4
  "title": "OpenUISpec Adaptive Override",
5
5
  "description": "Adaptive overrides keyed by size class (compact, regular, expanded). Values are override objects whose shape depends on context.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://openuispec.org/schema/defs/common.schema.json",
3
+ "$id": "https://openuispec.rsteam.uz/schema/defs/common.schema.json",
4
4
  "title": "OpenUISpec Common Definitions",
5
5
  "description": "Shared type definitions used across OpenUISpec schemas",
6
6
  "$defs": {
@@ -130,7 +130,7 @@
130
130
  "type": "number"
131
131
  },
132
132
  {
133
- "$ref": "https://openuispec.org/schema/defs/common.schema.json#/$defs/range_object"
133
+ "$ref": "https://openuispec.rsteam.uz/schema/defs/common.schema.json#/$defs/range_object"
134
134
  }
135
135
  ]
136
136
  },
@@ -226,13 +226,13 @@
226
226
  "type": "string"
227
227
  },
228
228
  {
229
- "$ref": "https://openuispec.org/schema/defs/common.schema.json#/$defs/icon_object"
229
+ "$ref": "https://openuispec.rsteam.uz/schema/defs/common.schema.json#/$defs/icon_object"
230
230
  },
231
231
  {
232
- "$ref": "https://openuispec.org/schema/defs/common.schema.json#/$defs/media_object"
232
+ "$ref": "https://openuispec.rsteam.uz/schema/defs/common.schema.json#/$defs/media_object"
233
233
  },
234
234
  {
235
- "$ref": "https://openuispec.org/schema/defs/common.schema.json#/$defs/nested_contract"
235
+ "$ref": "https://openuispec.rsteam.uz/schema/defs/common.schema.json#/$defs/nested_contract"
236
236
  }
237
237
  ]
238
238
  },
@@ -272,7 +272,7 @@
272
272
  "description": "An inline contract instance used in props (leading/trailing/content)",
273
273
  "properties": {
274
274
  "contract": {
275
- "$ref": "https://openuispec.org/schema/defs/common.schema.json#/$defs/contract_ref"
275
+ "$ref": "https://openuispec.rsteam.uz/schema/defs/common.schema.json#/$defs/contract_ref"
276
276
  },
277
277
  "variant": {
278
278
  "type": "string"
@@ -284,7 +284,7 @@
284
284
  "type": "object"
285
285
  },
286
286
  "action": {
287
- "$ref": "https://openuispec.org/schema/defs/action.schema.json"
287
+ "$ref": "https://openuispec.rsteam.uz/schema/defs/action.schema.json"
288
288
  },
289
289
  "data_binding": {
290
290
  "type": "string"
@@ -293,7 +293,7 @@
293
293
  "type": "object"
294
294
  },
295
295
  "adaptive": {
296
- "$ref": "https://openuispec.org/schema/defs/adaptive.schema.json"
296
+ "$ref": "https://openuispec.rsteam.uz/schema/defs/adaptive.schema.json"
297
297
  },
298
298
  "behavior": {
299
299
  "type": "object"