openuispec 0.2.14 → 0.2.16
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 +4 -2
- package/check/audit.ts +251 -0
- package/check/index.ts +19 -3
- package/cli/init.ts +1 -0
- package/docs/cli.md +82 -3
- package/docs/file-formats.md +83 -0
- package/docs/implementation-notes.md +8 -0
- package/examples/social-app/openuispec/contracts/action_trigger.yaml +8 -0
- package/examples/social-app/openuispec/contracts/collection.yaml +8 -0
- package/examples/social-app/openuispec/contracts/data_display.yaml +8 -0
- package/examples/social-app/openuispec/contracts/feedback.yaml +8 -0
- package/examples/social-app/openuispec/contracts/input_field.yaml +8 -0
- package/examples/social-app/openuispec/contracts/nav_container.yaml +9 -0
- package/examples/social-app/openuispec/contracts/surface.yaml +8 -0
- package/examples/social-app/openuispec/openuispec.yaml +40 -0
- package/examples/social-app/openuispec/tokens/color.yaml +4 -0
- package/examples/social-app/openuispec/tokens/motion.yaml +4 -0
- package/examples/social-app/openuispec/tokens/typography.yaml +11 -0
- package/examples/taskflow/openuispec/contracts/action_trigger.yaml +9 -1
- package/examples/taskflow/openuispec/contracts/collection.yaml +9 -1
- package/examples/taskflow/openuispec/contracts/data_display.yaml +9 -1
- package/examples/taskflow/openuispec/contracts/feedback.yaml +9 -1
- package/examples/taskflow/openuispec/contracts/input_field.yaml +8 -0
- package/examples/taskflow/openuispec/contracts/nav_container.yaml +10 -1
- package/examples/taskflow/openuispec/contracts/surface.yaml +9 -1
- package/examples/taskflow/openuispec/openuispec.yaml +40 -0
- package/examples/taskflow/openuispec/tokens/color.yaml +4 -0
- package/examples/taskflow/openuispec/tokens/motion.yaml +4 -0
- package/examples/taskflow/openuispec/tokens/typography.yaml +11 -0
- package/examples/todo-orbit/openuispec/contracts/action_trigger.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/collection.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/data_display.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/feedback.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/input_field.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/nav_container.yaml +8 -0
- package/examples/todo-orbit/openuispec/contracts/surface.yaml +7 -0
- package/examples/todo-orbit/openuispec/openuispec.yaml +40 -0
- package/examples/todo-orbit/openuispec/tokens/color.yaml +4 -0
- package/examples/todo-orbit/openuispec/tokens/motion.yaml +4 -0
- package/examples/todo-orbit/openuispec/tokens/typography.yaml +11 -0
- package/mcp-server/index.ts +22 -6
- package/mcp-server/screenshot-shared.ts +3 -4
- package/mcp-server/screenshot.ts +285 -70
- package/package.json +1 -1
- package/prepare/index.ts +96 -0
- package/schema/component.schema.json +5 -0
- package/schema/contract.schema.json +11 -1
- package/schema/custom-contract.schema.json +5 -0
- package/schema/openuispec.schema.json +47 -0
- package/schema/semantic-lint.ts +5 -3
- package/schema/tokens/color.schema.json +5 -0
- package/schema/tokens/motion.schema.json +5 -0
- package/schema/tokens/typography.schema.json +10 -0
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
# Rounded Cap: surfaces use 3px 24px 3px 24px (primary diagonal, largest scale)
|
|
4
4
|
|
|
5
5
|
surface:
|
|
6
|
+
generation:
|
|
7
|
+
must_avoid:
|
|
8
|
+
- "Do not use the same overlay opacity for modal, sheet, and popover — each has a distinct tokens definition"
|
|
9
|
+
- "Do not omit the drag indicator on sheet variants — it signals that the surface is interactive and dismissible"
|
|
10
|
+
- "Do not use glassmorphism or frosted-glass effects unless the project's design personality explicitly calls for it"
|
|
11
|
+
- "Do not skip focus trapping on modal and sheet variants — keyboard users must not be able to tab behind the surface"
|
|
12
|
+
- "Do not render popover without an arrow/caret pointing to its trigger element"
|
|
13
|
+
- "Do not present fullscreen surfaces without a clear close affordance"
|
|
6
14
|
tokens:
|
|
7
15
|
shape: "3px 24px 3px 24px"
|
|
8
16
|
background: "color.surface.secondary"
|
|
@@ -32,6 +32,46 @@ generation:
|
|
|
32
32
|
android: { language: kotlin, framework: compose, min_sdk: 26 }
|
|
33
33
|
web: { language: typescript, framework: react, bundler: vite }
|
|
34
34
|
|
|
35
|
+
generation_guidance:
|
|
36
|
+
universal_anti_patterns:
|
|
37
|
+
typography:
|
|
38
|
+
- "Do not fall back to Inter, Roboto, Arial, or system defaults when the spec defines a custom font_family"
|
|
39
|
+
- "Do not use a single font weight throughout — the typography scale defines different weights per level"
|
|
40
|
+
- "Do not use monospace fonts as shorthand for 'technical' aesthetic unless the spec explicitly calls for it"
|
|
41
|
+
color:
|
|
42
|
+
- "Do not use pure black (#000000) or pure white (#FFFFFF) — always resolve through the token layer"
|
|
43
|
+
- "Do not use the AI-default palette: cyan-on-dark, purple-to-blue gradients, neon accents"
|
|
44
|
+
- "Do not use gray text on colored backgrounds — derive text color from the on_color token or a darkened shade"
|
|
45
|
+
- "Do not apply the same opacity to all border tokens — border.default and border.emphasis have distinct opacity values"
|
|
46
|
+
spacing:
|
|
47
|
+
- "Do not use the same spacing value everywhere — the spec defines xxs through xxxl for a reason"
|
|
48
|
+
- "Do not ignore the page_margin and card_padding aliases — they exist to enforce consistent spatial rhythm"
|
|
49
|
+
- "Do not add spacing that the spec doesn't define — if a gap isn't in the token scale, it shouldn't exist in output"
|
|
50
|
+
motion:
|
|
51
|
+
- "Do not use bounce or elastic easing — these feel dated and the spec defines specific easing curves"
|
|
52
|
+
- "Do not apply the same duration to all animations — instant, quick, normal, and slow serve different purposes"
|
|
53
|
+
- "Do not ignore reduced_motion — when the user prefers reduced motion, animations must be removed entirely"
|
|
54
|
+
elevation:
|
|
55
|
+
- "Do not add shadows to elements that don't specify an elevation token"
|
|
56
|
+
- "Do not use the same shadow depth for cards and modals — cards use elevation.md, modals use elevation.lg"
|
|
57
|
+
layout:
|
|
58
|
+
- "Do not assume large-screen layouts work on compact — every multi-pane pattern needs an explicit compact fallback"
|
|
59
|
+
- "Do not use pixel breakpoints — reference size classes by name (compact, regular, expanded)"
|
|
60
|
+
accessibility:
|
|
61
|
+
- "Do not use color as the only differentiator between states — combine with icon, border, or text changes"
|
|
62
|
+
- "Do not skip focus ring styles — keyboard users must see where focus is at all times"
|
|
63
|
+
- "Do not set tab order manually unless the contract explicitly requires it — use document order"
|
|
64
|
+
audit_threshold: 70
|
|
65
|
+
|
|
66
|
+
design:
|
|
67
|
+
personality: "Vibrant, social, content-rich — engagement and discovery are primary, utility is secondary"
|
|
68
|
+
complexity: "elaborate"
|
|
69
|
+
audience: "Mobile-first social media users who expect rich media, smooth animations, and expressive interactions"
|
|
70
|
+
avoid:
|
|
71
|
+
- "Do not use muted or desaturated brand colors — the palette should feel energetic"
|
|
72
|
+
- "[ios] Do not skip spring animations on content reveal — they are expected in this context"
|
|
73
|
+
- "[android] Do not use non-Material motion patterns — stick to the platform's shared element transitions"
|
|
74
|
+
|
|
35
75
|
data_model:
|
|
36
76
|
user:
|
|
37
77
|
id: { type: string, required: true }
|
|
@@ -10,6 +10,10 @@ color:
|
|
|
10
10
|
on_color:
|
|
11
11
|
reference: "#FFFFFF"
|
|
12
12
|
contrast_min: 7.0
|
|
13
|
+
generation_notes:
|
|
14
|
+
- "This is the most important brand color — use consistently for primary actions, active states, and key interactive elements"
|
|
15
|
+
- "Do not dilute by applying it to backgrounds, borders, and text simultaneously — pick one dominant usage per surface"
|
|
16
|
+
- "The on_color token exists specifically for text/icons placed on this background — do not calculate contrast manually"
|
|
13
17
|
accent:
|
|
14
18
|
semantic: "Accent highlights — muted indigo"
|
|
15
19
|
reference: "#5B52A3"
|
|
@@ -18,6 +18,10 @@ motion:
|
|
|
18
18
|
duration: "instant"
|
|
19
19
|
easing: "default"
|
|
20
20
|
property: "transform"
|
|
21
|
+
generation_notes:
|
|
22
|
+
- "The scale value is subtle by design — do not exaggerate to 0.9 or lower"
|
|
23
|
+
- "[ios] Complement with haptic feedback (.impact(style: .light))"
|
|
24
|
+
- "[android] Use ripple effect instead of or in addition to scale"
|
|
21
25
|
slide_up:
|
|
22
26
|
duration: "normal"
|
|
23
27
|
easing: "enter"
|
|
@@ -6,6 +6,11 @@ typography:
|
|
|
6
6
|
platform:
|
|
7
7
|
ios: { system_alternative: "SF Pro" }
|
|
8
8
|
android: { system_alternative: "Google Sans" }
|
|
9
|
+
generation_notes:
|
|
10
|
+
- "If the primary font is unavailable on a platform, choose a geometric sans with similar x-height — never fall back to Inter or Roboto"
|
|
11
|
+
- "[web] Use font-display: swap and a size-adjust fallback to prevent FOUT (Flash of Unstyled Text)"
|
|
12
|
+
- "[ios] Register the font in Info.plist and verify it loads before first render"
|
|
13
|
+
- "[android] Place the font in res/font/ and reference via XML font family — do not load programmatically"
|
|
9
14
|
|
|
10
15
|
scale:
|
|
11
16
|
display:
|
|
@@ -33,6 +38,9 @@ typography:
|
|
|
33
38
|
size: { base: 16, range: [14, 16] }
|
|
34
39
|
weight: 400
|
|
35
40
|
line_height: 1.5
|
|
41
|
+
generation_notes:
|
|
42
|
+
- "This is the most-used text style — ensure line_height is comfortable for reading (1.5 default)"
|
|
43
|
+
- "On compact screens: the lower end of the size range is acceptable — do not go below 14pt/px"
|
|
36
44
|
body_sm:
|
|
37
45
|
semantic: "Secondary body text"
|
|
38
46
|
size: { base: 14, range: [13, 15] }
|
|
@@ -43,6 +51,9 @@ typography:
|
|
|
43
51
|
size: { base: 12, range: [11, 13] }
|
|
44
52
|
weight: 400
|
|
45
53
|
line_height: 1.4
|
|
54
|
+
generation_notes:
|
|
55
|
+
- "Caption text must still meet the contrast_min requirement from the text token it pairs with"
|
|
56
|
+
- "Do not use caption size for primary content — it is for metadata, timestamps, and labels only"
|
|
46
57
|
button:
|
|
47
58
|
semantic: "Button labels"
|
|
48
59
|
size: { base: 16, range: [14, 16] }
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
# action_trigger contract extension
|
|
2
2
|
# Base definition: spec Section 4.1
|
|
3
3
|
|
|
4
|
-
action_trigger:
|
|
4
|
+
action_trigger:
|
|
5
|
+
generation:
|
|
6
|
+
must_avoid:
|
|
7
|
+
- "Do not apply gradient backgrounds to buttons — use flat token-defined colors"
|
|
8
|
+
- "Do not make primary and secondary variants visually identical — they must be immediately distinguishable by background, border, or text color"
|
|
9
|
+
- "Do not use bounce or elastic easing on press feedback — use the motion.easing tokens"
|
|
10
|
+
- "Do not add drop shadows to every button variant — only elevated contexts use elevation tokens"
|
|
11
|
+
- "Do not use pure black (#000) or pure white (#fff) — always resolve through color.text and color.surface tokens"
|
|
12
|
+
- "Do not set identical min_height across all size variants — sm, md, lg must feel proportionally different"
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
# collection contract extension
|
|
2
2
|
# Base definition: spec Section 4.7
|
|
3
3
|
|
|
4
|
-
collection:
|
|
4
|
+
collection:
|
|
5
|
+
generation:
|
|
6
|
+
must_avoid:
|
|
7
|
+
- "Do not omit empty states — every collection must render the empty_state component when data is empty"
|
|
8
|
+
- "Do not use infinite scroll without a visible loading indicator at the bottom"
|
|
9
|
+
- "Do not render loading skeletons that mismatch the actual item layout — skeleton shapes must reflect the item_contract variant"
|
|
10
|
+
- "Do not use identical item spacing for list and grid variants — grid uses gap, list uses separator"
|
|
11
|
+
- "Do not render table headers with the same visual weight as table rows — headers use header_background and header_weight tokens"
|
|
12
|
+
- "[web] Do not skip keyboard navigation support — lists need ArrowUp/ArrowDown, grids need 2D arrow navigation"
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
# data_display contract extension
|
|
2
2
|
# Base definition: spec Section 4.2
|
|
3
3
|
|
|
4
|
-
data_display:
|
|
4
|
+
data_display:
|
|
5
|
+
generation:
|
|
6
|
+
must_avoid:
|
|
7
|
+
- "Do not nest cards inside cards — use flat hierarchy with spacing and subtle background shifts"
|
|
8
|
+
- "Do not give every card variant the same border-radius and shadow — vary by emphasis level"
|
|
9
|
+
- "Do not use gray text on colored backgrounds — use a darker shade of the background color or the on_color token"
|
|
10
|
+
- "Do not make all stat cards identical size with centered text — vary layout to match the data shape"
|
|
11
|
+
- "Do not use the same skeleton shape for all variants — skeleton must match the specific variant layout (card vs compact vs hero)"
|
|
12
|
+
- "Do not omit the highlighted state visual differentiation — unread/new items must be visually distinct from default items"
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
# feedback contract extension
|
|
2
2
|
# Base definition: spec Section 4.5
|
|
3
3
|
|
|
4
|
-
feedback:
|
|
4
|
+
feedback:
|
|
5
|
+
generation:
|
|
6
|
+
must_avoid:
|
|
7
|
+
- "Do not use the same visual treatment for all severity levels — info, success, warning, and error must be immediately distinguishable by color, icon, and border"
|
|
8
|
+
- "Do not stack multiple toasts without spacing or queue management — overlapping toasts are a usability failure"
|
|
9
|
+
- "Do not use alert dialogs for non-blocking informational feedback — use toasts or banners per the variant definition"
|
|
10
|
+
- "Do not skip enter/exit animations — feedback appearing or disappearing instantly feels broken"
|
|
11
|
+
- "Do not use the same position for toasts and banners — toasts float, banners are inline"
|
|
12
|
+
- "Do not auto-dismiss error-severity feedback with a short timer — errors need manual dismissal or a longer duration"
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
# Add project-specific variants, token overrides, and generation hints.
|
|
4
4
|
|
|
5
5
|
input_field:
|
|
6
|
+
generation:
|
|
7
|
+
must_avoid:
|
|
8
|
+
- "Do not use placeholder text as the only label — always render a persistent visible label"
|
|
9
|
+
- "Do not use a single generic gray border for all states — focused, error, and disabled must each have distinct visual treatment"
|
|
10
|
+
- "Do not hardcode red for error states — use color.semantic.danger which may differ from pure red per the token definition"
|
|
11
|
+
- "Do not skip the label animation between resting and active positions — this is a must_handle requirement"
|
|
12
|
+
- "Do not use the same visual weight for required and optional fields — required fields need a visible indicator"
|
|
13
|
+
- "Do not render select fields identically across platforms — respect the render_hint and platform_mapping"
|
|
6
14
|
variants:
|
|
7
15
|
cut_corner:
|
|
8
16
|
semantic: "Angled corner input for branded forms"
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
# nav_container contract extension
|
|
2
2
|
# Base definition: spec Section 4.4
|
|
3
3
|
|
|
4
|
-
nav_container:
|
|
4
|
+
nav_container:
|
|
5
|
+
generation:
|
|
6
|
+
must_avoid:
|
|
7
|
+
- "Do not use the same icon treatment for active and inactive items — differentiate with fill/outline variants, color, or both"
|
|
8
|
+
- "Do not center-align sidebar items — use leading alignment with consistent padding per the item_padding_h token"
|
|
9
|
+
- "Do not ignore the collapsed state for sidebar and rail variants — collapsed mode must show only icons with proper spacing"
|
|
10
|
+
- "Do not use a generic hamburger menu icon when the spec defines a specific drawer trigger"
|
|
11
|
+
- "Do not render tab_bar and sidebar with the same visual weight — tab_bar is compact and subordinate, sidebar is prominent"
|
|
12
|
+
- "[ios] Do not forget safe area insets on tab_bar — iOS bottom inset must be respected"
|
|
13
|
+
- "[android] Do not ignore gesture navigation bar insets on tab_bar and bottom navigation"
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
# surface contract extension
|
|
2
2
|
# Base definition: spec Section 4.6
|
|
3
3
|
|
|
4
|
-
surface:
|
|
4
|
+
surface:
|
|
5
|
+
generation:
|
|
6
|
+
must_avoid:
|
|
7
|
+
- "Do not use the same overlay opacity for modal, sheet, and popover — each has a distinct tokens definition"
|
|
8
|
+
- "Do not omit the drag indicator on sheet variants — it signals that the surface is interactive and dismissible"
|
|
9
|
+
- "Do not use glassmorphism or frosted-glass effects unless the project's design personality explicitly calls for it"
|
|
10
|
+
- "Do not skip focus trapping on modal and sheet variants — keyboard users must not be able to tab behind the surface"
|
|
11
|
+
- "Do not render popover without an arrow/caret pointing to its trigger element"
|
|
12
|
+
- "Do not present fullscreen surfaces without a clear close affordance"
|
|
@@ -39,6 +39,46 @@ generation:
|
|
|
39
39
|
android: { language: kotlin, framework: compose, min_sdk: 26 }
|
|
40
40
|
web: { language: typescript, framework: react, bundler: vite }
|
|
41
41
|
|
|
42
|
+
generation_guidance:
|
|
43
|
+
universal_anti_patterns:
|
|
44
|
+
typography:
|
|
45
|
+
- "Do not fall back to Inter, Roboto, Arial, or system defaults when the spec defines a custom font_family"
|
|
46
|
+
- "Do not use a single font weight throughout — the typography scale defines different weights per level"
|
|
47
|
+
- "Do not use monospace fonts as shorthand for 'technical' aesthetic unless the spec explicitly calls for it"
|
|
48
|
+
color:
|
|
49
|
+
- "Do not use pure black (#000000) or pure white (#FFFFFF) — always resolve through the token layer"
|
|
50
|
+
- "Do not use the AI-default palette: cyan-on-dark, purple-to-blue gradients, neon accents"
|
|
51
|
+
- "Do not use gray text on colored backgrounds — derive text color from the on_color token or a darkened shade"
|
|
52
|
+
- "Do not apply the same opacity to all border tokens — border.default and border.emphasis have distinct opacity values"
|
|
53
|
+
spacing:
|
|
54
|
+
- "Do not use the same spacing value everywhere — the spec defines xxs through xxxl for a reason"
|
|
55
|
+
- "Do not ignore the page_margin and card_padding aliases — they exist to enforce consistent spatial rhythm"
|
|
56
|
+
- "Do not add spacing that the spec doesn't define — if a gap isn't in the token scale, it shouldn't exist in output"
|
|
57
|
+
motion:
|
|
58
|
+
- "Do not use bounce or elastic easing — these feel dated and the spec defines specific easing curves"
|
|
59
|
+
- "Do not apply the same duration to all animations — instant, quick, normal, and slow serve different purposes"
|
|
60
|
+
- "Do not ignore reduced_motion — when the user prefers reduced motion, animations must be removed entirely"
|
|
61
|
+
elevation:
|
|
62
|
+
- "Do not add shadows to elements that don't specify an elevation token"
|
|
63
|
+
- "Do not use the same shadow depth for cards and modals — cards use elevation.md, modals use elevation.lg"
|
|
64
|
+
layout:
|
|
65
|
+
- "Do not assume large-screen layouts work on compact — every multi-pane pattern needs an explicit compact fallback"
|
|
66
|
+
- "Do not use pixel breakpoints — reference size classes by name (compact, regular, expanded)"
|
|
67
|
+
accessibility:
|
|
68
|
+
- "Do not use color as the only differentiator between states — combine with icon, border, or text changes"
|
|
69
|
+
- "Do not skip focus ring styles — keyboard users must see where focus is at all times"
|
|
70
|
+
- "Do not set tab order manually unless the contract explicitly requires it — use document order"
|
|
71
|
+
audit_threshold: 70
|
|
72
|
+
|
|
73
|
+
design:
|
|
74
|
+
personality: "Clean, focused, productivity-first — no decorative flourishes. Color is used for priority and status clarity, not aesthetics."
|
|
75
|
+
complexity: "balanced"
|
|
76
|
+
audience: "Individual contributors and small teams managing personal and shared task lists"
|
|
77
|
+
avoid:
|
|
78
|
+
- "Do not use playful or rounded UI patterns — this is a professional productivity tool"
|
|
79
|
+
- "Do not use color gradients — flat, token-driven color only"
|
|
80
|
+
- "[web] Do not use CSS animations for non-interactive decorative purposes"
|
|
81
|
+
|
|
42
82
|
# ============================================================
|
|
43
83
|
# Custom formatters & mappers (see spec Section 10.5)
|
|
44
84
|
# ============================================================
|
|
@@ -13,6 +13,10 @@ color:
|
|
|
13
13
|
on_color:
|
|
14
14
|
reference: "#FFFFFF"
|
|
15
15
|
contrast_min: 4.5
|
|
16
|
+
generation_notes:
|
|
17
|
+
- "This is the most important brand color — use consistently for primary actions, active states, and key interactive elements"
|
|
18
|
+
- "Do not dilute by applying it to backgrounds, borders, and text simultaneously — pick one dominant usage per surface"
|
|
19
|
+
- "The on_color token exists specifically for text/icons placed on this background — do not calculate contrast manually"
|
|
16
20
|
|
|
17
21
|
secondary:
|
|
18
22
|
semantic: "Accent for highlights, badges, urgency"
|
|
@@ -20,6 +20,10 @@ motion:
|
|
|
20
20
|
duration: "instant"
|
|
21
21
|
property: "scale"
|
|
22
22
|
value: 0.97
|
|
23
|
+
generation_notes:
|
|
24
|
+
- "The scale value is subtle by design — do not exaggerate to 0.9 or lower"
|
|
25
|
+
- "[ios] Complement with haptic feedback (.impact(style: .light))"
|
|
26
|
+
- "[android] Use ripple effect instead of or in addition to scale"
|
|
23
27
|
state_change:
|
|
24
28
|
duration: "quick"
|
|
25
29
|
property: "opacity, background"
|
|
@@ -10,6 +10,11 @@ typography:
|
|
|
10
10
|
ios: { system_alternative: "SF Pro" }
|
|
11
11
|
android: { system_alternative: "Google Sans" }
|
|
12
12
|
web: { load_strategy: "swap", source: "google_fonts" }
|
|
13
|
+
generation_notes:
|
|
14
|
+
- "If the primary font is unavailable on a platform, choose a geometric sans with similar x-height — never fall back to Inter or Roboto"
|
|
15
|
+
- "[web] Use font-display: swap and a size-adjust fallback to prevent FOUT (Flash of Unstyled Text)"
|
|
16
|
+
- "[ios] Register the font in Info.plist and verify it loads before first render"
|
|
17
|
+
- "[android] Place the font in res/font/ and reference via XML font family — do not load programmatically"
|
|
13
18
|
|
|
14
19
|
scale:
|
|
15
20
|
display:
|
|
@@ -40,6 +45,9 @@ typography:
|
|
|
40
45
|
size: { base: 16, range: [14, 16] }
|
|
41
46
|
weight: 400
|
|
42
47
|
line_height: 1.5
|
|
48
|
+
generation_notes:
|
|
49
|
+
- "This is the most-used text style — ensure line_height is comfortable for reading (1.5 default)"
|
|
50
|
+
- "On compact screens: the lower end of the size range is acceptable — do not go below 14pt/px"
|
|
43
51
|
body_sm:
|
|
44
52
|
semantic: "Secondary text, descriptions"
|
|
45
53
|
size: { base: 14, range: [13, 15] }
|
|
@@ -52,6 +60,9 @@ typography:
|
|
|
52
60
|
weight: 400
|
|
53
61
|
tracking: 0.02
|
|
54
62
|
line_height: 1.35
|
|
63
|
+
generation_notes:
|
|
64
|
+
- "Caption text must still meet the contrast_min requirement from the text token it pairs with"
|
|
65
|
+
- "Do not use caption size for primary content — it is for metadata, timestamps, and labels only"
|
|
55
66
|
overline:
|
|
56
67
|
semantic: "Section tags, category labels"
|
|
57
68
|
size: { base: 11, range: [10, 12] }
|
|
@@ -31,6 +31,13 @@ action_trigger:
|
|
|
31
31
|
primary: { style: "clip-path" }
|
|
32
32
|
ghost: { style: "clip-path" }
|
|
33
33
|
generation:
|
|
34
|
+
must_avoid:
|
|
35
|
+
- "Do not apply gradient backgrounds to buttons — use flat token-defined colors"
|
|
36
|
+
- "Do not make primary and secondary variants visually identical — they must be immediately distinguishable by background, border, or text color"
|
|
37
|
+
- "Do not use bounce or elastic easing on press feedback — use the motion.easing tokens"
|
|
38
|
+
- "Do not add drop shadows to every button variant — only elevated contexts use elevation tokens"
|
|
39
|
+
- "Do not use pure black (#000) or pure white (#fff) — always resolve through color.text and color.surface tokens"
|
|
40
|
+
- "Do not set identical min_height across all size variants — sm, md, lg must feel proportionally different"
|
|
34
41
|
must_handle:
|
|
35
42
|
- "Cut top-left and bottom-right corners by cut_size for primary and ghost"
|
|
36
43
|
- "Preserve button hit area and loading state inside the cut shape"
|
|
@@ -23,6 +23,13 @@ collection:
|
|
|
23
23
|
list: { element: "ul", class: "todo-list" }
|
|
24
24
|
chips: { element: "div", class: "chip-row" }
|
|
25
25
|
generation:
|
|
26
|
+
must_avoid:
|
|
27
|
+
- "Do not omit empty states — every collection must render the empty_state component when data is empty"
|
|
28
|
+
- "Do not use infinite scroll without a visible loading indicator at the bottom"
|
|
29
|
+
- "Do not render loading skeletons that mismatch the actual item layout — skeleton shapes must reflect the item_contract variant"
|
|
30
|
+
- "Do not use identical item spacing for list and grid variants — grid uses gap, list uses separator"
|
|
31
|
+
- "Do not render table headers with the same visual weight as table rows — headers use header_background and header_weight tokens"
|
|
32
|
+
- "[web] Do not skip keyboard navigation support — lists need ArrowUp/ArrowDown, grids need 2D arrow navigation"
|
|
26
33
|
must_handle:
|
|
27
34
|
- "Preserve empty_state and pull_to_refresh behavior for task lists"
|
|
28
35
|
- "Render chips as single-select filters bound to state.active_filter"
|
|
@@ -29,6 +29,13 @@ data_display:
|
|
|
29
29
|
compact: { element: "li", class: "todo-row" }
|
|
30
30
|
inline: { element: "div", class: "section-heading" }
|
|
31
31
|
generation:
|
|
32
|
+
must_avoid:
|
|
33
|
+
- "Do not nest cards inside cards — use flat hierarchy with spacing and subtle background shifts"
|
|
34
|
+
- "Do not give every card variant the same border-radius and shadow — vary by emphasis level"
|
|
35
|
+
- "Do not use gray text on colored backgrounds — use a darker shade of the background color or the on_color token"
|
|
36
|
+
- "Do not make all stat cards identical size with centered text — vary layout to match the data shape"
|
|
37
|
+
- "Do not use the same skeleton shape for all variants — skeleton must match the specific variant layout (card vs compact vs hero)"
|
|
38
|
+
- "Do not omit the highlighted state visual differentiation — unread/new items must be visually distinct from default items"
|
|
32
39
|
must_handle:
|
|
33
40
|
- "Render compact task rows with clear title/subtitle hierarchy"
|
|
34
41
|
- "Support inline title/subtitle blocks for section headers"
|
|
@@ -23,6 +23,13 @@ feedback:
|
|
|
23
23
|
toast: { pattern: "toast container", position: "top-right" }
|
|
24
24
|
banner: { element: "div", role: "alert", position: "inline-top" }
|
|
25
25
|
generation:
|
|
26
|
+
must_avoid:
|
|
27
|
+
- "Do not use the same visual treatment for all severity levels — info, success, warning, and error must be immediately distinguishable by color, icon, and border"
|
|
28
|
+
- "Do not stack multiple toasts without spacing or queue management — overlapping toasts are a usability failure"
|
|
29
|
+
- "Do not use alert dialogs for non-blocking informational feedback — use toasts or banners per the variant definition"
|
|
30
|
+
- "Do not skip enter/exit animations — feedback appearing or disappearing instantly feels broken"
|
|
31
|
+
- "Do not use the same position for toasts and banners — toasts float, banners are inline"
|
|
32
|
+
- "Do not auto-dismiss error-severity feedback with a short timer — errors need manual dismissal or a longer duration"
|
|
26
33
|
must_handle:
|
|
27
34
|
- "Map success and error feedback to distinct visuals and live-region behavior"
|
|
28
35
|
- "Auto-dismiss toast feedback after the declared duration"
|
|
@@ -45,6 +45,13 @@ input_field:
|
|
|
45
45
|
date: { style: "clip-path" }
|
|
46
46
|
number: { style: "clip-path" }
|
|
47
47
|
generation:
|
|
48
|
+
must_avoid:
|
|
49
|
+
- "Do not use placeholder text as the only label — always render a persistent visible label"
|
|
50
|
+
- "Do not use a single generic gray border for all states — focused, error, and disabled must each have distinct visual treatment"
|
|
51
|
+
- "Do not hardcode red for error states — use color.semantic.danger which may differ from pure red per the token definition"
|
|
52
|
+
- "Do not skip the label animation between resting and active positions — this is a must_handle requirement"
|
|
53
|
+
- "Do not use the same visual weight for required and optional fields — required fields need a visible indicator"
|
|
54
|
+
- "Do not render select fields identically across platforms — respect the render_hint and platform_mapping"
|
|
48
55
|
must_handle:
|
|
49
56
|
- "Cut top-left and bottom-right corners by cut_size for text-like inputs"
|
|
50
57
|
- "Keep focus, error, and disabled borders aligned with the cut shape"
|
|
@@ -58,6 +58,14 @@ nav_container:
|
|
|
58
58
|
rail: { element: "aside", pattern: "icon rail" }
|
|
59
59
|
sidebar: { element: "aside", pattern: "collapsible sidebar" }
|
|
60
60
|
generation:
|
|
61
|
+
must_avoid:
|
|
62
|
+
- "Do not use the same icon treatment for active and inactive items — differentiate with fill/outline variants, color, or both"
|
|
63
|
+
- "Do not center-align sidebar items — use leading alignment with consistent padding per the item_padding_h token"
|
|
64
|
+
- "Do not ignore the collapsed state for sidebar and rail variants — collapsed mode must show only icons with proper spacing"
|
|
65
|
+
- "Do not use a generic hamburger menu icon when the spec defines a specific drawer trigger"
|
|
66
|
+
- "Do not render tab_bar and sidebar with the same visual weight — tab_bar is compact and subordinate, sidebar is prominent"
|
|
67
|
+
- "[ios] Do not forget safe area insets on tab_bar — iOS bottom inset must be respected"
|
|
68
|
+
- "[android] Do not ignore gesture navigation bar insets on tab_bar and bottom navigation"
|
|
61
69
|
must_handle:
|
|
62
70
|
- "Switch between tab_bar, rail, and sidebar according to adaptive screen config"
|
|
63
71
|
- "Keep the selected item visually distinct with active icon/text treatment"
|
|
@@ -21,6 +21,13 @@ surface:
|
|
|
21
21
|
sheet: { style: "clip-path" }
|
|
22
22
|
modal: { style: "clip-path" }
|
|
23
23
|
generation:
|
|
24
|
+
must_avoid:
|
|
25
|
+
- "Do not use the same overlay opacity for modal, sheet, and popover — each has a distinct tokens definition"
|
|
26
|
+
- "Do not omit the drag indicator on sheet variants — it signals that the surface is interactive and dismissible"
|
|
27
|
+
- "Do not use glassmorphism or frosted-glass effects unless the project's design personality explicitly calls for it"
|
|
28
|
+
- "Do not skip focus trapping on modal and sheet variants — keyboard users must not be able to tab behind the surface"
|
|
29
|
+
- "Do not render popover without an arrow/caret pointing to its trigger element"
|
|
30
|
+
- "Do not present fullscreen surfaces without a clear close affordance"
|
|
24
31
|
must_handle:
|
|
25
32
|
- "Apply the cut-corner silhouette to sheet and modal surfaces"
|
|
26
33
|
- "Maintain shadows and overlays without square corners bleeding through"
|
|
@@ -38,6 +38,46 @@ generation:
|
|
|
38
38
|
android: { language: kotlin, framework: compose, min_sdk: 26 }
|
|
39
39
|
web: { language: typescript, framework: react, bundler: vite }
|
|
40
40
|
|
|
41
|
+
generation_guidance:
|
|
42
|
+
universal_anti_patterns:
|
|
43
|
+
typography:
|
|
44
|
+
- "Do not fall back to Inter, Roboto, Arial, or system defaults when the spec defines a custom font_family"
|
|
45
|
+
- "Do not use a single font weight throughout — the typography scale defines different weights per level"
|
|
46
|
+
- "Do not use monospace fonts as shorthand for 'technical' aesthetic unless the spec explicitly calls for it"
|
|
47
|
+
color:
|
|
48
|
+
- "Do not use pure black (#000000) or pure white (#FFFFFF) — always resolve through the token layer"
|
|
49
|
+
- "Do not use the AI-default palette: cyan-on-dark, purple-to-blue gradients, neon accents"
|
|
50
|
+
- "Do not use gray text on colored backgrounds — derive text color from the on_color token or a darkened shade"
|
|
51
|
+
- "Do not apply the same opacity to all border tokens — border.default and border.emphasis have distinct opacity values"
|
|
52
|
+
spacing:
|
|
53
|
+
- "Do not use the same spacing value everywhere — the spec defines xxs through xxxl for a reason"
|
|
54
|
+
- "Do not ignore the page_margin and card_padding aliases — they exist to enforce consistent spatial rhythm"
|
|
55
|
+
- "Do not add spacing that the spec doesn't define — if a gap isn't in the token scale, it shouldn't exist in output"
|
|
56
|
+
motion:
|
|
57
|
+
- "Do not use bounce or elastic easing — these feel dated and the spec defines specific easing curves"
|
|
58
|
+
- "Do not apply the same duration to all animations — instant, quick, normal, and slow serve different purposes"
|
|
59
|
+
- "Do not ignore reduced_motion — when the user prefers reduced motion, animations must be removed entirely"
|
|
60
|
+
elevation:
|
|
61
|
+
- "Do not add shadows to elements that don't specify an elevation token"
|
|
62
|
+
- "Do not use the same shadow depth for cards and modals — cards use elevation.md, modals use elevation.lg"
|
|
63
|
+
layout:
|
|
64
|
+
- "Do not assume large-screen layouts work on compact — every multi-pane pattern needs an explicit compact fallback"
|
|
65
|
+
- "Do not use pixel breakpoints — reference size classes by name (compact, regular, expanded)"
|
|
66
|
+
accessibility:
|
|
67
|
+
- "Do not use color as the only differentiator between states — combine with icon, border, or text changes"
|
|
68
|
+
- "Do not skip focus ring styles — keyboard users must see where focus is at all times"
|
|
69
|
+
- "Do not set tab order manually unless the contract explicitly requires it — use document order"
|
|
70
|
+
audit_threshold: 70
|
|
71
|
+
|
|
72
|
+
design:
|
|
73
|
+
personality: "Minimal and calm — orbital metaphor suggests clarity and organization without visual noise"
|
|
74
|
+
complexity: "restrained"
|
|
75
|
+
audience: "Bilingual individuals who prefer a clean, low-distraction task environment"
|
|
76
|
+
avoid:
|
|
77
|
+
- "Do not add decorative illustrations or background patterns"
|
|
78
|
+
- "Do not use more than 2 accent colors on any screen"
|
|
79
|
+
- "Do not animate list items on page load — only animate user-triggered state changes"
|
|
80
|
+
|
|
41
81
|
formatters:
|
|
42
82
|
priority_label:
|
|
43
83
|
input: enum
|
|
@@ -10,6 +10,10 @@ color:
|
|
|
10
10
|
on_color:
|
|
11
11
|
reference: "#FFFFFF"
|
|
12
12
|
contrast_min: 4.5
|
|
13
|
+
generation_notes:
|
|
14
|
+
- "This is the most important brand color — use consistently for primary actions, active states, and key interactive elements"
|
|
15
|
+
- "Do not dilute by applying it to backgrounds, borders, and text simultaneously — pick one dominant usage per surface"
|
|
16
|
+
- "The on_color token exists specifically for text/icons placed on this background — do not calculate contrast manually"
|
|
13
17
|
secondary:
|
|
14
18
|
semantic: "Warm accent for highlights and due-date emphasis"
|
|
15
19
|
reference: "#F59E0B"
|
|
@@ -18,6 +18,10 @@ motion:
|
|
|
18
18
|
duration: "instant"
|
|
19
19
|
property: "scale"
|
|
20
20
|
value: 0.98
|
|
21
|
+
generation_notes:
|
|
22
|
+
- "The scale value is subtle by design — do not exaggerate to 0.9 or lower"
|
|
23
|
+
- "[ios] Complement with haptic feedback (.impact(style: .light))"
|
|
24
|
+
- "[android] Use ripple effect instead of or in addition to scale"
|
|
21
25
|
state_change:
|
|
22
26
|
duration: "quick"
|
|
23
27
|
property: "opacity, border-color, background"
|
|
@@ -8,6 +8,11 @@ typography:
|
|
|
8
8
|
ios: { system_alternative: "SF Pro" }
|
|
9
9
|
android: { system_alternative: "Google Sans" }
|
|
10
10
|
web: { load_strategy: "swap", source: "google_fonts" }
|
|
11
|
+
generation_notes:
|
|
12
|
+
- "If the primary font is unavailable on a platform, choose a geometric sans with similar x-height — never fall back to Inter or Roboto"
|
|
13
|
+
- "[web] Use font-display: swap and a size-adjust fallback to prevent FOUT (Flash of Unstyled Text)"
|
|
14
|
+
- "[ios] Register the font in Info.plist and verify it loads before first render"
|
|
15
|
+
- "[android] Place the font in res/font/ and reference via XML font family — do not load programmatically"
|
|
11
16
|
|
|
12
17
|
scale:
|
|
13
18
|
display:
|
|
@@ -38,6 +43,9 @@ typography:
|
|
|
38
43
|
size: { base: 16, range: [15, 17] }
|
|
39
44
|
weight: 400
|
|
40
45
|
line_height: 1.5
|
|
46
|
+
generation_notes:
|
|
47
|
+
- "This is the most-used text style — ensure line_height is comfortable for reading (1.5 default)"
|
|
48
|
+
- "On compact screens: the lower end of the size range is acceptable — do not go below 14pt/px"
|
|
41
49
|
body_sm:
|
|
42
50
|
semantic: "Supporting text and subtitles"
|
|
43
51
|
size: { base: 14, range: [13, 15] }
|
|
@@ -50,3 +58,6 @@ typography:
|
|
|
50
58
|
weight: 500
|
|
51
59
|
tracking: 0.015
|
|
52
60
|
line_height: 1.35
|
|
61
|
+
generation_notes:
|
|
62
|
+
- "Caption text must still meet the contrast_min requirement from the text token it pairs with"
|
|
63
|
+
- "Do not use caption size for primary content — it is for metadata, timestamps, and labels only"
|
package/mcp-server/index.ts
CHANGED
|
@@ -127,7 +127,12 @@ SPEC AUTHORING: spec_types → spec_schema(type, summary?) → write YAML
|
|
|
127
127
|
PREVIEW: openuispec_preview(screen) → render spec as HTML with mock data, returns screenshot (no app needed)
|
|
128
128
|
SCREENSHOTS: screenshot (web), screenshot_android, screenshot_ios — single + batch variants
|
|
129
129
|
|
|
130
|
-
Skip only for purely non-UI requests
|
|
130
|
+
Skip only for purely non-UI requests.
|
|
131
|
+
|
|
132
|
+
When generating UI code:
|
|
133
|
+
1. Check anti_patterns in the prepare result — hard constraints on what NOT to produce
|
|
134
|
+
2. Check design_context — match the complexity level and personality description
|
|
135
|
+
3. Apply the AI Fingerprint Test: would a viewer immediately say "AI made this"? If yes, revise.`,
|
|
131
136
|
}
|
|
132
137
|
);
|
|
133
138
|
|
|
@@ -319,7 +324,7 @@ function buildAuditChecklist(projectDir: string, target: string, screenFilter?:
|
|
|
319
324
|
server.registerTool(
|
|
320
325
|
"openuispec_check",
|
|
321
326
|
{
|
|
322
|
-
description: "Validate spec files (schema + semantic lint) and check prepare readiness. Does NOT inspect generated code. With audit=true, returns a spec-derived checklist of must_handle items, screen sections, and locale files — use it as a guide when YOU manually review the generated code.",
|
|
327
|
+
description: "Validate spec files (schema + semantic lint) and check prepare readiness. Does NOT inspect generated code. With audit=true, returns a spec-derived checklist of must_handle items, must_avoid anti-patterns, screen sections, and locale files — use it as a guide when YOU manually review the generated code. design_quality_score and audit_findings are included when audit=true.",
|
|
323
328
|
inputSchema: {
|
|
324
329
|
target: targetSchema,
|
|
325
330
|
audit: z.boolean().optional().default(false).describe("Include the full audit checklist. Omit for a compact pass/fail summary."),
|
|
@@ -329,7 +334,7 @@ server.registerTool(
|
|
|
329
334
|
},
|
|
330
335
|
async ({ target, audit: includeAudit, screens, contracts }) => {
|
|
331
336
|
try {
|
|
332
|
-
const result = buildCheckResult(target, projectCwd);
|
|
337
|
+
const result = buildCheckResult(target, projectCwd, includeAudit);
|
|
333
338
|
const totalErrors = result.validation.total_errors + result.semantic.total_errors;
|
|
334
339
|
const passing = totalErrors === 0 && result.prepare.ready;
|
|
335
340
|
|
|
@@ -358,6 +363,13 @@ server.registerTool(
|
|
|
358
363
|
hints.push(buildAuditChecklist(projectDir, target, screenFilter, contractFilter));
|
|
359
364
|
}
|
|
360
365
|
|
|
366
|
+
if (includeAudit && result.audit) {
|
|
367
|
+
hints.push(`DESIGN QUALITY SCORE: ${result.audit.score}/100 (${result.audit.errors} errors, ${result.audit.warnings} warnings)`);
|
|
368
|
+
if (!result.audit.passed && result.audit.threshold > 0) {
|
|
369
|
+
hints.push(`SCORE BELOW THRESHOLD: ${result.audit.score} < ${result.audit.threshold}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
361
373
|
if (baselineHint) hints.push(baselineHint);
|
|
362
374
|
hints.push(passing ? "next_tool: openuispec_drift --snapshot (to create/update baseline)" : "Fix validation errors, then re-run openuispec_check.");
|
|
363
375
|
|
|
@@ -813,9 +825,10 @@ server.registerTool(
|
|
|
813
825
|
full_page: z.boolean().optional().default(false).describe("Capture the full scrollable page instead of just the viewport"),
|
|
814
826
|
selector: z.string().optional().describe("CSS selector to screenshot a specific element instead of the full page"),
|
|
815
827
|
output_dir: z.string().optional().describe("Directory to save the screenshot PNG (relative to web app root). E.g. 'screenshots'. If omitted, only returns base64 in response."),
|
|
828
|
+
init_script: z.string().optional().describe("JavaScript to run before the page renders. Passed to the app via ?__ous_init=<base64> query param. The app's bootstrapper decodes and executes it — use for auth injection, role switching, or session setup."),
|
|
816
829
|
},
|
|
817
830
|
},
|
|
818
|
-
async ({ route, viewport, scale, theme, wait_for, full_page, selector, output_dir }) => {
|
|
831
|
+
async ({ route, viewport, scale, theme, wait_for, full_page, selector, output_dir, init_script }) => {
|
|
819
832
|
try {
|
|
820
833
|
return await takeScreenshot(projectCwd, {
|
|
821
834
|
route,
|
|
@@ -826,6 +839,7 @@ server.registerTool(
|
|
|
826
839
|
full_page,
|
|
827
840
|
selector,
|
|
828
841
|
output_dir,
|
|
842
|
+
init_script,
|
|
829
843
|
});
|
|
830
844
|
} catch (err) {
|
|
831
845
|
return toolError(err);
|
|
@@ -894,6 +908,7 @@ const webBatchCaptureSchema = z.object({
|
|
|
894
908
|
selector: z.string().optional().describe("CSS selector to screenshot a specific element"),
|
|
895
909
|
full_page: z.boolean().optional().describe("Capture full scrollable page"),
|
|
896
910
|
wait_for: z.number().optional().describe("Per-capture wait time in ms"),
|
|
911
|
+
init_script: z.string().optional().describe("Per-capture init script (overrides shared init_script for this capture)"),
|
|
897
912
|
});
|
|
898
913
|
|
|
899
914
|
server.registerTool(
|
|
@@ -906,11 +921,12 @@ server.registerTool(
|
|
|
906
921
|
scale: z.number().optional().default(2).describe("Device pixel ratio for all captures (default 2)"),
|
|
907
922
|
theme: z.enum(["light", "dark"]).optional().describe("Force color scheme for all captures"),
|
|
908
923
|
output_dir: z.string().optional().describe("Directory to save all PNGs (relative to web app root)"),
|
|
924
|
+
init_script: z.string().optional().describe("Shared init script for all captures. Passed via ?__ous_init=<base64>. Per-capture init_script overrides this."),
|
|
909
925
|
},
|
|
910
926
|
},
|
|
911
|
-
async ({ captures, viewport, scale, theme, output_dir }) => {
|
|
927
|
+
async ({ captures, viewport, scale, theme, output_dir, init_script }) => {
|
|
912
928
|
try {
|
|
913
|
-
return await takeScreenshotBatch(projectCwd, { captures, viewport, scale, theme, output_dir });
|
|
929
|
+
return await takeScreenshotBatch(projectCwd, { captures, viewport, scale, theme, output_dir, init_script });
|
|
914
930
|
} catch (err) {
|
|
915
931
|
return toolError(err);
|
|
916
932
|
}
|