openuispec 0.2.1 → 0.2.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 +15 -0
- package/examples/social-app/AGENTS.md +1 -1
- package/examples/social-app/CLAUDE.md +1 -1
- package/examples/social-app/generated/web/social-app/src/components/Shell.tsx +24 -24
- package/examples/social-app/generated/web/social-app/src/components/ui.tsx +1 -1
- package/examples/social-app/generated/web/social-app/src/styles.css +1 -0
- package/examples/social-app/openuispec/contracts/action_trigger.yaml +21 -0
- package/examples/social-app/openuispec/contracts/nav_container.yaml +21 -0
- package/examples/todo-orbit/openuispec/contracts/action_trigger.yaml +10 -0
- package/examples/todo-orbit/openuispec/contracts/nav_container.yaml +25 -5
- package/mcp-server/index.ts +52 -0
- package/package.json +1 -1
- package/schema/contract.schema.json +57 -7
- package/schema/custom-contract.schema.json +59 -8
- package/schema/defs/action.schema.json +1 -1
- package/schema/defs/adaptive.schema.json +1 -1
- package/schema/defs/common.schema.json +8 -8
- package/schema/defs/data-binding.schema.json +3 -3
- package/schema/defs/validation.schema.json +1 -1
- package/schema/flow.schema.json +14 -14
- package/schema/locale.schema.json +1 -1
- package/schema/openuispec.schema.json +5 -5
- package/schema/platform.schema.json +2 -2
- package/schema/screen.schema.json +23 -23
- package/schema/tokens/color.schema.json +9 -9
- package/schema/tokens/elevation.schema.json +2 -2
- package/schema/tokens/icons.schema.json +1 -1
- package/schema/tokens/layout.schema.json +9 -9
- package/schema/tokens/motion.schema.json +2 -2
- package/schema/tokens/spacing.schema.json +4 -4
- package/schema/tokens/themes.schema.json +2 -2
- package/schema/tokens/typography.schema.json +4 -4
- package/schema/validate.ts +1 -1
- 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`:
|
|
@@ -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-
|
|
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-
|
|
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
|
|
178
|
+
"relative rounded-cap-primary px-4 py-3 text-sm font-semibold transition",
|
|
191
179
|
isActive
|
|
192
|
-
? "bg-[var(--color-brand-primary)]
|
|
193
|
-
: "
|
|
180
|
+
? "bg-[var(--color-brand-primary)] shadow-sm"
|
|
181
|
+
: "hover:bg-[var(--color-surface-tertiary)]",
|
|
194
182
|
)
|
|
195
183
|
}
|
|
196
184
|
>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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-
|
|
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
|
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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" }
|
package/mcp-server/index.ts
CHANGED
|
@@ -65,6 +65,39 @@ function toolError(err: unknown): { content: [{ type: "text"; text: string }]; i
|
|
|
65
65
|
return { content: [{ type: "text" as const, text: `Error: ${formatError(err)}` }], isError: true };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function formatAuditValue(value: unknown): string {
|
|
69
|
+
if (typeof value === "string") return value;
|
|
70
|
+
return JSON.stringify(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function collectStateRoleAuditItems(node: unknown, prefix = ""): string[] {
|
|
74
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const items: string[] = [];
|
|
79
|
+
const record = node as Record<string, unknown>;
|
|
80
|
+
|
|
81
|
+
const states = record.states;
|
|
82
|
+
if (states && typeof states === "object" && !Array.isArray(states)) {
|
|
83
|
+
for (const [stateName, roles] of Object.entries(states as Record<string, unknown>)) {
|
|
84
|
+
if (!roles || typeof roles !== "object" || Array.isArray(roles)) continue;
|
|
85
|
+
for (const [roleName, roleValue] of Object.entries(roles as Record<string, unknown>)) {
|
|
86
|
+
const statePath = prefix ? `${prefix}.states.${stateName}.${roleName}` : `states.${stateName}.${roleName}`;
|
|
87
|
+
items.push(`${statePath} = ${formatAuditValue(roleValue)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const [key, value] of Object.entries(record)) {
|
|
93
|
+
if (key === "states" || !value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
94
|
+
const childPrefix = prefix ? `${prefix}.${key}` : key;
|
|
95
|
+
items.push(...collectStateRoleAuditItems(value, childPrefix));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return items;
|
|
99
|
+
}
|
|
100
|
+
|
|
68
101
|
// ── create server ────────────────────────────────────────────────────
|
|
69
102
|
|
|
70
103
|
export const server = new McpServer(
|
|
@@ -173,6 +206,17 @@ function buildAuditChecklist(projectDir: string, target: string, screenFilter?:
|
|
|
173
206
|
lines.push(`- [ ] ${item}`);
|
|
174
207
|
}
|
|
175
208
|
}
|
|
209
|
+
|
|
210
|
+
const stateRoleItems = collectStateRoleAuditItems(variant?.tokens);
|
|
211
|
+
if (stateRoleItems.length) {
|
|
212
|
+
if (!mustHandle?.length) {
|
|
213
|
+
lines.push(`\n### ${contractName}.${variantName}`);
|
|
214
|
+
}
|
|
215
|
+
lines.push(`- [ ] Explicit state-role tokens are implemented for ${contractName}.${variantName}`);
|
|
216
|
+
for (const item of stateRoleItems) {
|
|
217
|
+
lines.push(`- [ ] ${item}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
176
220
|
}
|
|
177
221
|
|
|
178
222
|
// Top-level generation.must_handle
|
|
@@ -183,6 +227,14 @@ function buildAuditChecklist(projectDir: string, target: string, screenFilter?:
|
|
|
183
227
|
lines.push(`- [ ] ${item}`);
|
|
184
228
|
}
|
|
185
229
|
}
|
|
230
|
+
|
|
231
|
+
const topLevelStateRoleItems = collectStateRoleAuditItems(contract?.tokens);
|
|
232
|
+
if (topLevelStateRoleItems.length) {
|
|
233
|
+
lines.push(`\n### ${contractName} (state-role tokens)`);
|
|
234
|
+
for (const item of topLevelStateRoleItems) {
|
|
235
|
+
lines.push(`- [ ] ${item}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
186
238
|
} catch { /* skip unparseable files */ }
|
|
187
239
|
}
|
|
188
240
|
lines.push("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://openuispec.
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|