@utopia-studio-design/design-system-cli 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/bin/utopia-ds-mcp.mjs +15 -4
  2. package/bin/utopia-ds.mjs +16 -3
  3. package/data/blocks/semantic-pattern-parts/AuthParts.tsx +48 -0
  4. package/data/blocks/semantic-pattern-parts/ChoiceCards.tsx +7 -0
  5. package/data/blocks/semantic-pattern-parts/ListFilters.tsx +9 -0
  6. package/data/blocks/semantic-pattern-parts/OutcomeNotice.tsx +7 -0
  7. package/data/blocks/semantic-pattern-parts/PatternParts.tsx +7 -0
  8. package/data/blocks/semantic-pattern-parts/ProductParts.tsx +38 -0
  9. package/data/blocks/semantic-pattern-parts/ProgressSteps.tsx +6 -0
  10. package/data/blocks/semantic-pattern-parts/ReviewList.tsx +7 -0
  11. package/data/blocks/semantic-pattern-parts/ValidatedField.tsx +8 -0
  12. package/data/blocks/semantic-pattern-parts/WorkflowParts.tsx +339 -0
  13. package/data/blocks/semantic-pattern-parts/auth-parts.css +27 -0
  14. package/data/blocks/semantic-pattern-parts/pattern-parts.css +25 -0
  15. package/data/blocks/semantic-pattern-parts/product-parts.css +25 -0
  16. package/data/blocks/semantic-pattern-parts/types.ts +3 -0
  17. package/data/blocks/semantic-pattern-parts/workflow-parts.css +118 -0
  18. package/data/blocks/ux-pattern-examples/AccessExamples.tsx +46 -0
  19. package/data/blocks/ux-pattern-examples/AccountExamples.tsx +133 -0
  20. package/data/blocks/ux-pattern-examples/OnboardingExample.tsx +15 -0
  21. package/data/blocks/ux-pattern-examples/ProductExamples.tsx +79 -0
  22. package/data/blocks/ux-pattern-examples/ReviewExample.tsx +14 -0
  23. package/data/blocks/ux-pattern-examples/SignupExample.tsx +22 -0
  24. package/data/blocks/ux-pattern-examples/WorkflowExample.tsx +1761 -0
  25. package/data/blocks/ux-pattern-examples/WorkflowExamples.tsx +190 -0
  26. package/data/blocks/ux-pattern-examples/copy.ts +6 -0
  27. package/data/blocks/ux-pattern-examples/ux-examples.css +18 -0
  28. package/data/blocks/ux-pattern-examples/workflow-copy.ts +76 -0
  29. package/data/blocks/ux-pattern-examples/workflow-definitions.ts +1291 -0
  30. package/data/docs/ui-parts-qa.md +172 -0
  31. package/data/docs/ui-parts.md +105 -0
  32. package/data/docs/ux-pattern-adaptation-qa.md +38 -0
  33. package/data/docs/ux-pattern-adaptation.md +41 -0
  34. package/data/docs/ux-pattern-migration.md +111 -0
  35. package/data/docs/ux-pattern-release-2026-09-13.md +26 -0
  36. package/data/docs/ux-source-evidence/account-auth.json +41 -0
  37. package/data/docs/ux-source-evidence/account-forgot-password.json +36 -0
  38. package/data/docs/ux-source-evidence/account-invite.json +63 -0
  39. package/data/docs/ux-source-evidence/account-locked.json +63 -0
  40. package/data/docs/ux-source-evidence/account-login.json +28 -0
  41. package/data/docs/ux-source-evidence/account-mfa.json +62 -0
  42. package/data/docs/ux-source-evidence/account-onboarding-credentials.json +52 -0
  43. package/data/docs/ux-source-evidence/account-onboarding-legal.json +56 -0
  44. package/data/docs/ux-source-evidence/account-onboarding-orientation.json +66 -0
  45. package/data/docs/ux-source-evidence/account-onboarding-profile.json +50 -0
  46. package/data/docs/ux-source-evidence/account-onboarding.json +69 -0
  47. package/data/docs/ux-source-evidence/account-reset-password.json +27 -0
  48. package/data/docs/ux-source-evidence/account-session-expired.json +62 -0
  49. package/data/docs/ux-source-evidence/account-signup.json +33 -0
  50. package/data/docs/ux-source-evidence/account-verify-email.json +34 -0
  51. package/data/docs/ux-source-evidence/account-welcome.json +41 -0
  52. package/data/docs/ux-source-evidence/admin-analytics.json +54 -0
  53. package/data/docs/ux-source-evidence/admin-audit.json +31 -0
  54. package/data/docs/ux-source-evidence/admin-broadcasts-new.json +36 -0
  55. package/data/docs/ux-source-evidence/admin-broadcasts.json +41 -0
  56. package/data/docs/ux-source-evidence/admin-closures.json +52 -0
  57. package/data/docs/ux-source-evidence/admin-devices.json +51 -0
  58. package/data/docs/ux-source-evidence/admin-escalations.json +35 -0
  59. package/data/docs/ux-source-evidence/admin-incidents.json +54 -0
  60. package/data/docs/ux-source-evidence/admin-loading.json +17 -0
  61. package/data/docs/ux-source-evidence/admin-onboarding-detail.json +80 -0
  62. package/data/docs/ux-source-evidence/admin-onboarding.json +62 -0
  63. package/data/docs/ux-source-evidence/admin-payments.json +45 -0
  64. package/data/docs/ux-source-evidence/admin-providers.json +38 -0
  65. package/data/docs/ux-source-evidence/admin-settings.json +18 -0
  66. package/data/docs/ux-source-evidence/admin-subscriptions-detail-allocate.json +42 -0
  67. package/data/docs/ux-source-evidence/admin-subscriptions-detail.json +48 -0
  68. package/data/docs/ux-source-evidence/admin-subscriptions-new.json +43 -0
  69. package/data/docs/ux-source-evidence/admin-subscriptions.json +41 -0
  70. package/data/docs/ux-source-evidence/admin-transactions.json +41 -0
  71. package/data/docs/ux-source-evidence/error.json +30 -0
  72. package/data/docs/ux-source-evidence/loading.json +19 -0
  73. package/data/docs/ux-source-evidence/marketing-blog.json +48 -0
  74. package/data/docs/ux-source-evidence/marketing-contact.json +36 -0
  75. package/data/docs/ux-source-evidence/marketing.json +64 -0
  76. package/data/docs/ux-source-evidence/not-found.json +28 -0
  77. package/data/docs/ux-source-evidence/portal-loading.json +17 -0
  78. package/data/docs/ux-source-evidence/portal-mentor-messages.json +31 -0
  79. package/data/docs/ux-source-evidence/portal-mentor-profile.json +23 -0
  80. package/data/docs/ux-source-evidence/portal-mentor-requests-detail.json +54 -0
  81. package/data/docs/ux-source-evidence/portal-mentor-sessions-detail-assess.json +42 -0
  82. package/data/docs/ux-source-evidence/portal-mentor-settings.json +18 -0
  83. package/data/docs/ux-source-evidence/portal-mentor.json +38 -0
  84. package/data/docs/ux-source-evidence/portal-sessions-detail-live.json +48 -0
  85. package/data/docs/ux-source-evidence/portal-sponsor-billing.json +28 -0
  86. package/data/docs/ux-source-evidence/portal-sponsor-profile.json +22 -0
  87. package/data/docs/ux-source-evidence/portal-sponsor-sessions.json +28 -0
  88. package/data/docs/ux-source-evidence/portal-sponsor-settings.json +18 -0
  89. package/data/docs/ux-source-evidence/portal-sponsor-trainees.json +36 -0
  90. package/data/docs/ux-source-evidence/portal-sponsor.json +52 -0
  91. package/data/docs/ux-source-evidence/portal-trainee-competency.json +42 -0
  92. package/data/docs/ux-source-evidence/portal-trainee-credits.json +40 -0
  93. package/data/docs/ux-source-evidence/portal-trainee-mentors.json +48 -0
  94. package/data/docs/ux-source-evidence/portal-trainee-messages.json +32 -0
  95. package/data/docs/ux-source-evidence/portal-trainee-notifications.json +35 -0
  96. package/data/docs/ux-source-evidence/portal-trainee-profile.json +26 -0
  97. package/data/docs/ux-source-evidence/portal-trainee-request.json +54 -0
  98. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail-feedback.json +36 -0
  99. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail-precheck.json +42 -0
  100. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail.json +55 -0
  101. package/data/docs/ux-source-evidence/portal-trainee-sessions.json +36 -0
  102. package/data/docs/ux-source-evidence/portal-trainee-settings-notifications.json +38 -0
  103. package/data/docs/ux-source-evidence/portal-trainee-settings.json +18 -0
  104. package/data/docs/ux-source-evidence/portal-trainee.json +44 -0
  105. package/data/docs/ux-source-evidence/portal-tv-detail.json +39 -0
  106. package/data/docs/ux-source-evidence/portal-tv.json +44 -0
  107. package/data/docs/ux-source-evidence/shared-admin-admin-shell.json +35 -0
  108. package/data/docs/ux-source-evidence/shared-auth-auth-primitives.json +105 -0
  109. package/data/docs/ux-source-evidence/shared-auth-auth-shell.json +25 -0
  110. package/data/docs/ux-source-evidence/shared-auth-onboarding-primitives.json +52 -0
  111. package/data/docs/ux-source-evidence/shared-auth-onboarding-shell.json +28 -0
  112. package/data/docs/ux-source-evidence/shared-auth-status-indicator.json +41 -0
  113. package/data/docs/ux-source-evidence/shared-portal-empty-state.json +24 -0
  114. package/data/docs/ux-source-evidence/shared-portal-loading-skeleton.json +27 -0
  115. package/data/docs/ux-source-evidence/shared-portal-notifications-dropdown.json +39 -0
  116. package/data/docs/ux-source-evidence/shared-portal-portal-shell.json +36 -0
  117. package/data/docs/ux-source-evidence/shared-portal-profile-form.json +32 -0
  118. package/data/docs/ux-source-evidence/shared-portal-settings-view.json +81 -0
  119. package/data/docs/ux-source-evidence/shared-portal-usage-sparkline.json +18 -0
  120. package/data/docs/ux-source-evidence/shared-portal-user-menu.json +34 -0
  121. package/data/manifests/blocks.json +230 -21
  122. package/data/manifests/patterns.json +330 -0
  123. package/data/manifests/ui-patterns.json +1029 -0
  124. package/data/manifests/ux-examples.json +2177 -0
  125. package/data/manifests/ux-patterns.json +1135 -0
  126. package/data/manifests/ux-source-inventory.json +4669 -0
  127. package/data/ui-parts/blocks/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  128. package/data/ui-parts/blocks/analytics-dashboard/analytics-dashboard.css +92 -0
  129. package/data/ui-parts/blocks/semantic-pattern-parts/AuthParts.tsx +48 -0
  130. package/data/ui-parts/blocks/semantic-pattern-parts/ChoiceCards.tsx +7 -0
  131. package/data/ui-parts/blocks/semantic-pattern-parts/ListFilters.tsx +9 -0
  132. package/data/ui-parts/blocks/semantic-pattern-parts/OutcomeNotice.tsx +7 -0
  133. package/data/ui-parts/blocks/semantic-pattern-parts/ProductParts.tsx +38 -0
  134. package/data/ui-parts/blocks/semantic-pattern-parts/ProgressSteps.tsx +6 -0
  135. package/data/ui-parts/blocks/semantic-pattern-parts/ReviewList.tsx +7 -0
  136. package/data/ui-parts/blocks/semantic-pattern-parts/ValidatedField.tsx +8 -0
  137. package/data/ui-parts/blocks/semantic-pattern-parts/WorkflowParts.tsx +339 -0
  138. package/data/ui-parts/blocks/semantic-pattern-parts/auth-parts.css +27 -0
  139. package/data/ui-parts/blocks/semantic-pattern-parts/pattern-parts.css +25 -0
  140. package/data/ui-parts/blocks/semantic-pattern-parts/product-parts.css +25 -0
  141. package/data/ui-parts/blocks/semantic-pattern-parts/types.ts +3 -0
  142. package/data/ui-parts/blocks/semantic-pattern-parts/workflow-parts.css +118 -0
  143. package/data/ui-parts/blocks/ux-pattern-examples/AccessExamples.tsx +46 -0
  144. package/data/ui-parts/blocks/ux-pattern-examples/AccountExamples.tsx +133 -0
  145. package/data/ui-parts/blocks/ux-pattern-examples/ProductExamples.tsx +79 -0
  146. package/data/ui-parts/blocks/ux-pattern-examples/WorkflowExample.tsx +1761 -0
  147. package/data/ui-parts/blocks/ux-pattern-examples/WorkflowExamples.tsx +190 -0
  148. package/data/ui-parts/blocks/ux-pattern-examples/copy.ts +6 -0
  149. package/data/ui-parts/blocks/ux-pattern-examples/ux-examples.css +18 -0
  150. package/data/ui-parts/blocks/ux-pattern-examples/workflow-copy.ts +76 -0
  151. package/data/ui-parts/blocks/ux-pattern-examples/workflow-definitions.ts +1291 -0
  152. package/data/ux-source-evidence/account-auth.json +41 -0
  153. package/data/ux-source-evidence/account-forgot-password.json +36 -0
  154. package/data/ux-source-evidence/account-invite.json +63 -0
  155. package/data/ux-source-evidence/account-locked.json +63 -0
  156. package/data/ux-source-evidence/account-login.json +28 -0
  157. package/data/ux-source-evidence/account-mfa.json +62 -0
  158. package/data/ux-source-evidence/account-onboarding-credentials.json +52 -0
  159. package/data/ux-source-evidence/account-onboarding-legal.json +56 -0
  160. package/data/ux-source-evidence/account-onboarding-orientation.json +66 -0
  161. package/data/ux-source-evidence/account-onboarding-profile.json +50 -0
  162. package/data/ux-source-evidence/account-onboarding.json +69 -0
  163. package/data/ux-source-evidence/account-reset-password.json +27 -0
  164. package/data/ux-source-evidence/account-session-expired.json +62 -0
  165. package/data/ux-source-evidence/account-signup.json +33 -0
  166. package/data/ux-source-evidence/account-verify-email.json +34 -0
  167. package/data/ux-source-evidence/account-welcome.json +41 -0
  168. package/data/ux-source-evidence/admin-analytics.json +54 -0
  169. package/data/ux-source-evidence/admin-audit.json +31 -0
  170. package/data/ux-source-evidence/admin-broadcasts-new.json +36 -0
  171. package/data/ux-source-evidence/admin-broadcasts.json +41 -0
  172. package/data/ux-source-evidence/admin-closures.json +52 -0
  173. package/data/ux-source-evidence/admin-devices.json +51 -0
  174. package/data/ux-source-evidence/admin-escalations.json +35 -0
  175. package/data/ux-source-evidence/admin-incidents.json +54 -0
  176. package/data/ux-source-evidence/admin-loading.json +17 -0
  177. package/data/ux-source-evidence/admin-onboarding-detail.json +80 -0
  178. package/data/ux-source-evidence/admin-onboarding.json +62 -0
  179. package/data/ux-source-evidence/admin-payments.json +45 -0
  180. package/data/ux-source-evidence/admin-providers.json +38 -0
  181. package/data/ux-source-evidence/admin-settings.json +18 -0
  182. package/data/ux-source-evidence/admin-subscriptions-detail-allocate.json +42 -0
  183. package/data/ux-source-evidence/admin-subscriptions-detail.json +48 -0
  184. package/data/ux-source-evidence/admin-subscriptions-new.json +43 -0
  185. package/data/ux-source-evidence/admin-subscriptions.json +41 -0
  186. package/data/ux-source-evidence/admin-transactions.json +41 -0
  187. package/data/ux-source-evidence/error.json +30 -0
  188. package/data/ux-source-evidence/loading.json +19 -0
  189. package/data/ux-source-evidence/marketing-blog.json +48 -0
  190. package/data/ux-source-evidence/marketing-contact.json +36 -0
  191. package/data/ux-source-evidence/marketing.json +64 -0
  192. package/data/ux-source-evidence/not-found.json +28 -0
  193. package/data/ux-source-evidence/portal-loading.json +17 -0
  194. package/data/ux-source-evidence/portal-mentor-messages.json +31 -0
  195. package/data/ux-source-evidence/portal-mentor-profile.json +23 -0
  196. package/data/ux-source-evidence/portal-mentor-requests-detail.json +54 -0
  197. package/data/ux-source-evidence/portal-mentor-sessions-detail-assess.json +42 -0
  198. package/data/ux-source-evidence/portal-mentor-settings.json +18 -0
  199. package/data/ux-source-evidence/portal-mentor.json +38 -0
  200. package/data/ux-source-evidence/portal-sessions-detail-live.json +48 -0
  201. package/data/ux-source-evidence/portal-sponsor-billing.json +28 -0
  202. package/data/ux-source-evidence/portal-sponsor-profile.json +22 -0
  203. package/data/ux-source-evidence/portal-sponsor-sessions.json +28 -0
  204. package/data/ux-source-evidence/portal-sponsor-settings.json +18 -0
  205. package/data/ux-source-evidence/portal-sponsor-trainees.json +36 -0
  206. package/data/ux-source-evidence/portal-sponsor.json +52 -0
  207. package/data/ux-source-evidence/portal-trainee-competency.json +42 -0
  208. package/data/ux-source-evidence/portal-trainee-credits.json +40 -0
  209. package/data/ux-source-evidence/portal-trainee-mentors.json +48 -0
  210. package/data/ux-source-evidence/portal-trainee-messages.json +32 -0
  211. package/data/ux-source-evidence/portal-trainee-notifications.json +35 -0
  212. package/data/ux-source-evidence/portal-trainee-profile.json +26 -0
  213. package/data/ux-source-evidence/portal-trainee-request.json +54 -0
  214. package/data/ux-source-evidence/portal-trainee-sessions-detail-feedback.json +36 -0
  215. package/data/ux-source-evidence/portal-trainee-sessions-detail-precheck.json +42 -0
  216. package/data/ux-source-evidence/portal-trainee-sessions-detail.json +55 -0
  217. package/data/ux-source-evidence/portal-trainee-sessions.json +36 -0
  218. package/data/ux-source-evidence/portal-trainee-settings-notifications.json +38 -0
  219. package/data/ux-source-evidence/portal-trainee-settings.json +18 -0
  220. package/data/ux-source-evidence/portal-trainee.json +44 -0
  221. package/data/ux-source-evidence/portal-tv-detail.json +39 -0
  222. package/data/ux-source-evidence/portal-tv.json +44 -0
  223. package/data/ux-source-evidence/shared-admin-admin-shell.json +35 -0
  224. package/data/ux-source-evidence/shared-auth-auth-primitives.json +105 -0
  225. package/data/ux-source-evidence/shared-auth-auth-shell.json +25 -0
  226. package/data/ux-source-evidence/shared-auth-onboarding-primitives.json +52 -0
  227. package/data/ux-source-evidence/shared-auth-onboarding-shell.json +28 -0
  228. package/data/ux-source-evidence/shared-auth-status-indicator.json +41 -0
  229. package/data/ux-source-evidence/shared-portal-empty-state.json +24 -0
  230. package/data/ux-source-evidence/shared-portal-loading-skeleton.json +27 -0
  231. package/data/ux-source-evidence/shared-portal-notifications-dropdown.json +39 -0
  232. package/data/ux-source-evidence/shared-portal-portal-shell.json +36 -0
  233. package/data/ux-source-evidence/shared-portal-profile-form.json +32 -0
  234. package/data/ux-source-evidence/shared-portal-settings-view.json +81 -0
  235. package/data/ux-source-evidence/shared-portal-usage-sparkline.json +18 -0
  236. package/data/ux-source-evidence/shared-portal-user-menu.json +34 -0
  237. package/lib/api.mjs +192 -4
  238. package/package.json +2 -2
package/lib/api.mjs CHANGED
@@ -58,6 +58,186 @@ export function listTemplates() {
58
58
  return readJson(join(paths().manifests, 'templates.json')).templates
59
59
  }
60
60
 
61
+ export function getUxPatternRegistry() {
62
+ return readJson(join(paths().manifests, 'ux-patterns.json'))
63
+ }
64
+
65
+ export function listUxPatterns() {
66
+ return getUxPatternRegistry().patterns
67
+ }
68
+
69
+ export function listUiParts() {
70
+ return readJson(join(paths().manifests, 'ui-patterns.json')).parts
71
+ }
72
+
73
+ export function listUxExamples() {
74
+ return readJson(join(paths().manifests, 'ux-examples.json')).examples
75
+ }
76
+
77
+ /** Complete source evidence, distinct from runnable Ceramic examples. */
78
+ export function listUxSourcePatterns() {
79
+ const inventory = readJson(join(paths().manifests, 'ux-source-inventory.json'))
80
+ const examples = listUxExamples()
81
+ return inventory.entries.map(({ evidence, ...entry }) => ({ ...entry,
82
+ examples: examples.filter(example => (example.sourcePatterns ?? [example.sourcePattern]).includes(entry.id)).map(example => example.id),
83
+ }))
84
+ }
85
+
86
+ export function findUxSourcePatterns(goal) {
87
+ if (typeof goal !== 'string' || !goal.trim() || goal.length > 2000) return { ok: false, code: 'INVALID_GOAL' }
88
+ const words = goal.toLowerCase().split(/\s+/).filter(word => word.length > 1)
89
+ const candidates = listUxSourcePatterns().map(entry => {
90
+ const text = [entry.id, entry.category, ...Object.values(entry.title), ...(entry.keywords ?? []), ...entry.fragments.map(f => f.id)].join(' ').toLowerCase()
91
+ const aliases = (entry.keywords ?? []).filter(keyword => goal.toLowerCase().includes(keyword.toLowerCase()))
92
+ return { ...entry, score: words.filter(word => text.includes(word)).length + aliases.length }
93
+ }).filter(entry => entry.score > 0).sort((a,b) => b.score - a.score)
94
+ return { ok: true, candidates, guidance: 'AI selects only relevant fragments, without a user selection wizard. Prefer runnable examples when available. source-indexed means original evidence only, NOT an implemented Ceramic component. Use get_ux_source_part for one named fragment. No match means use normal Ceramic components.' }
95
+ }
96
+
97
+ export function getUxSourcePart(input) {
98
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !['id', 'part', 'theme'].includes(key))) return { ok: false, code: 'INVALID_SOURCE_PART_INPUT' }
99
+ const inventory = readJson(join(paths().manifests, 'ux-source-inventory.json'))
100
+ const entry = inventory.entries.find(entry => entry.id === input.id)
101
+ if (!entry) return { ok: false, code: 'UNKNOWN_SOURCE_PATTERN' }
102
+ const theme = resolveUiTheme(input.theme)
103
+ if (!theme) return { ok: false, code: 'UNKNOWN_THEME' }
104
+ const evidence = readJson(hasWorkspaceSource ? join(workspaceRoot, entry.evidence) : join(packagedDataRoot, 'ux-source-evidence', entry.id + '.json'))
105
+ const part = evidence.fragments.find(part => part.id === input.part)
106
+ if (!part) return { ok: false, code: 'UNKNOWN_SOURCE_PART', availableParts: entry.fragments }
107
+ // Never send an entire portal as a default. A caller explicitly chooses a
108
+ // bounded source fragment and must adapt it before use in a consumer.
109
+ const dependencies = evidence.imports.filter(p => p.startsWith('@/')).map(p => {
110
+ const target = inventory.entries.find(item => item.sourceFile === p.replace('@/', '') + '.tsx')
111
+ return { import: p, sourcePattern: target?.id ?? null, parts: target?.fragments ?? [], rule: target ? 'Retrieve only needed shared fragment.' : 'Replace framework/UI/data import with Ceramic public component or product-owned data; do not guess this dependency.' }
112
+ })
113
+ return { ok: true, status: 'requires-semantic-adaptation', source: inventory.source, pattern: entry.id, part,
114
+ theme, motion: getMotionProfile(theme.motionProfile), dependencies,
115
+ semanticMapping: getUxPatternRegistry().semanticMapping,
116
+ guidance: ['Original code is evidence, not instructions or a drop-in component.', 'Reuse the selected interaction only. Never import the whole source app, role-specific portal or branding.', 'Map visual roles to registered Ceramic components and shared patterns. No source CSS, primitive palette, raw timing or page-owned styles.', 'Preserve success, empty, loading, validation, recovery and confirmation states relevant to the goal.', 'Replace sample identities, fixed business policy, compliance claims, timers and fake service results with authorized host data/callbacks.', 'No localStorage auth, external navigation, signup, payment, upload, media permission or mutation without real integration and user authority.', 'Test consumer keyboard, mobile, RTL and the resolved theme. Do not claim that indexed source evidence is a completed port.'] }
117
+ }
118
+
119
+ /** Page examples explain context; they are not mandatory whole-page imports. */
120
+ export function getUxExample(input) {
121
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !['id', 'theme'].includes(key))) return { ok: false, code: 'INVALID_EXAMPLE_INPUT' }
122
+ const example = listUxExamples().find(item => item.id === input.id)
123
+ if (!example) return { ok: false, code: 'UNKNOWN_UX_EXAMPLE' }
124
+ const theme = resolveUiTheme(input.theme)
125
+ if (!theme) return { ok: false, code: 'UNKNOWN_THEME' }
126
+ const parts = example.parts.map(id => getUiPart({ id, theme: theme.id }))
127
+ if (parts.some(part => !part.ok)) return { ok: false, code: 'BROKEN_EXAMPLE_CONTRACT' }
128
+ const files = [...new Set([example.source, ...(example.files ?? []), 'blocks/ux-pattern-examples/copy.ts', 'blocks/ux-pattern-examples/ux-examples.css'])]
129
+ const code = files.map(path => ({ path, content: readFileSync(hasWorkspaceSource ? join(workspaceRoot, path) : join(packagedDataRoot, 'ui-parts', path), 'utf8') }))
130
+ for (const file of parts.flatMap(part => part.code)) if (!code.some(item => item.path === file.path)) code.push(file)
131
+ return { ok: true, example, theme, motion: getMotionProfile(theme.motionProfile), parts, code,
132
+ install: 'npm install @utopia-studio-design/design-system',
133
+ availability: 'Example bundles and MCP tools are available in Ceramic CLI 0.8.0 and later.',
134
+ guidance: 'Inspect the contextual page example, then reuse only the parts that fit the user goal. Never require the user to browse or select examples first. Keep shared semantic CSS and the resolved user/room theme; Cloudblur is the fallback. Replace fixture data with authorized product data and callbacks. Demo signup, consent, completion and review do not create accounts, save, approve or deploy anything.' }
135
+ }
136
+
137
+ /** Candidate discovery, not a command to add every matching part. The coding
138
+ * agent decides fit in context; the user never has to choose these parts. */
139
+ export function findUiParts(goal) {
140
+ if (typeof goal !== 'string' || !goal.trim() || goal.length > 2000) return { ok: false, code: 'INVALID_GOAL' }
141
+ const text = goal.toLowerCase()
142
+ const candidates = listUiParts().map(part => {
143
+ const matches = part.keywords.filter(keyword => text.includes(keyword.toLowerCase()))
144
+ return { ...part, score: matches.length, matchedIntents: matches }
145
+ }).filter(part => part.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
146
+ const sourceCandidates = findUxSourcePatterns(goal).candidates.slice(0, 8)
147
+ return { ok: true, candidates, sourceCandidates, guidance: 'The AI must check useWhen/avoidWhen against the goal and use only needed parts. Never ask the user to pick parts. Prefer ready Ceramic parts. sourceCandidates expose additional original interactions requiring semantic adaptation via get_ux_source_part, not whole-page import. If nothing fits, use ordinary components; never force a pattern.' }
148
+ }
149
+
150
+ function resolveUiTheme(theme) {
151
+ const room = getRoomContext()
152
+ const id = theme ?? room?.themeId ?? room?.theme?.id ?? 'utopia-cloudblur'
153
+ // An invalid explicit/custom theme is not permission to silently rebrand.
154
+ return typeof id === 'string' && id.trim() ? getTheme(id) : null
155
+ }
156
+
157
+ export function getUiPart(input) {
158
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !['id', 'theme'].includes(key))) return { ok: false, code: 'INVALID_PART_INPUT' }
159
+ const part = listUiParts().find(part => part.id === input.id)
160
+ if (!part) return { ok: false, code: 'UNKNOWN_UI_PART' }
161
+ const theme = resolveUiTheme(input.theme)
162
+ if (!theme) return { ok: false, code: 'UNKNOWN_THEME' }
163
+ const files = [...new Set([part.source, part.styles, ...(part.files ?? []), ...(part.id === 'choice-cards' || part.id === 'list-filters' || part.id === 'review-list' ? ['blocks/semantic-pattern-parts/types.ts'] : [])])]
164
+ const code = files.map(path => ({ path, content: readFileSync(hasWorkspaceSource ? join(workspaceRoot, path) : join(packagedDataRoot, 'ui-parts', path), 'utf8') }))
165
+ return { ok: true, part, theme, motion: getMotionProfile(theme.motionProfile), components: part.components.map(getComponent), code, guidance: 'Copy only this reusable export and its dependencies. All labels, data and callbacks are product-owned. Do not copy a screen shell or sample identities. CSS belongs to the shared composition, not the page. Apply the resolved theme to the common host. Never turn fixture results into real authorization.' }
166
+ }
167
+
168
+ export function prepareUiComposition(input) {
169
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !['goal', 'theme'].includes(key))) return { ok: false, code: 'INVALID_COMPOSITION_INPUT' }
170
+ const found = findUiParts(input.goal)
171
+ if (!found.ok) return found
172
+ const theme = resolveUiTheme(input.theme)
173
+ if (!theme) return { ok: false, code: 'UNKNOWN_THEME' }
174
+ const selected = found.candidates.slice(0, 4)
175
+ const parts = selected.map(part => getUiPart({ id: part.id, theme: theme.id }))
176
+ const ids = selected.map(part => part.id)
177
+ const bindings = []
178
+ if (ids.includes('list-filters') && ids.includes('review-list')) bindings.push({ from: 'ListFilters.query/status', via: 'Host-owned state and authorized filtered query', to: 'ReviewList.rows', rule: 'Reset both filter values; render empty/loading/error states from the query result.' })
179
+ if (ids.includes('metric-summary') && ids.includes('review-list')) bindings.push({ from: 'Host-authorized rows and scope', to: 'KPIGrid.metrics', rule: 'Calculate counts for the same filter scope. Never invent totals from unloaded/paginated rows.' })
180
+ if (ids.includes('outcome-notice')) bindings.push({ from: 'Confirmed service result', to: 'OutcomeNotice', rule: 'Show success only after confirmation; retry must call a real recovery callback.' })
181
+ return { ok: true, status: selected.length ? 'composition-proposal' : found.sourceCandidates.length ? 'source-adaptation-candidates' : 'no-pattern-needed', goal: input.goal.trim(), theme: theme.id, parts, sourceCandidates: found.sourceCandidates, bindings, guidance: found.guidance, nextSteps: ['AI checks whether each candidate fits; discard unrelated parts without making the user choose.', 'When a relevant interaction exists only in sourceCandidates, use get_ux_source_part to inspect just that fragment and adapt its semantics; do not call it a ready Ceramic port.', 'Compose the selected exports around host-owned product data and callbacks. No complete portal or page import.', 'Use existing Ceramic components for anything not covered; patterns are optional.', 'Confirm external side effects separately; this read-only proposal does not build, save, approve or deploy.', 'Verify keyboard, responsive layout, loading/empty/error/retry, RTL and theme tokens in the consuming product.'] }
182
+ }
183
+
184
+ export function getUxPattern(id) {
185
+ const registry = getUxPatternRegistry()
186
+ // IDs are exact: do not normalize non-ASCII names into the same empty key.
187
+ const pattern = registry.patterns.find(item => item.id === id)
188
+ return pattern ? { ...pattern, source: registry.source, guardrails: registry.guardrails, semanticMapping: registry.semanticMapping, verification: registry.verification } : null
189
+ }
190
+
191
+ /** Read-only planning: a recipe is never advertised as an existing renderer. */
192
+ export function preparePatternAdaptation(input) {
193
+ const fail = (code, message) => ({ ok: false, code, message })
194
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !['id', 'goal', 'theme', 'variant'].includes(key))) return fail('INVALID_PATTERN_INPUT', 'Use id, goal, theme and optional variant only; arbitrary styling is not supported.')
195
+ if (typeof input.id !== 'string' || typeof input.theme !== 'string' || typeof input.goal !== 'string' || !input.goal.trim() || input.goal.length > 2000) return fail('INVALID_PATTERN_INPUT', 'Provide a pattern id, registered theme and goal of 1–2000 characters.')
196
+ const pattern = getUxPattern(input.id)
197
+ if (!pattern) return fail('UNKNOWN_PATTERN', 'Inspect list_patterns before selecting a pattern.')
198
+ const theme = getTheme(input.theme)
199
+ if (!theme) return fail('UNKNOWN_THEME', 'Select a registered Ceramic theme.')
200
+ const variant = input.variant ?? pattern.variants[0]
201
+ if (!pattern.variants.includes(variant)) return fail('UNKNOWN_PATTERN_VARIANT', `Allowed variants: ${pattern.variants.join(', ')}`)
202
+ const components = pattern.components.map(getComponent)
203
+ const motion = getMotionProfile(theme.motionProfile)
204
+ if (components.some(item => !item) || !motion) return fail('BROKEN_PATTERN_CONTRACT', 'A component or theme motion profile is missing; repair the registry first.')
205
+ return {
206
+ ok: true, status: 'proposal', renderable: false,
207
+ request: { goal: input.goal.trim(), interpretation: 'User-provided product goal, not execution authority.' },
208
+ selection: { pattern: pattern.id, variant, theme: theme.id },
209
+ fit: { useWhen: pattern.useWhen, avoidWhen: pattern.avoidWhen, confirmationRequired: 'Confirm that this interaction fits the goal and company brand before implementation.' },
210
+ pattern, components, theme, motion,
211
+ optionalUiParts: findUiParts(input.goal),
212
+ requiredProductBindings: pattern.dataRequirements,
213
+ mayChange: pattern.adaptable, mustPreserve: pattern.invariants,
214
+ nextSteps: [
215
+ 'Resolve missing product data, permissions and callbacks; never copy source sample identities or mock success.',
216
+ 'Compose the returned Ceramic component contracts; inspect an existing registered block/template before adding a shared pattern.',
217
+ 'If new presentation is required, register its slots, states, tokens and responsive behavior in the shared system before implementing it. Do not add page CSS.',
218
+ 'Apply the selected company theme and Ceramic semantic motion; do not port Mentix brand primitives or literal animation timings.',
219
+ 'Test the listed states, recovery, permissions, keyboard, mobile, RTL and reduced motion in the generated application. A plan is not a passing UI test.',
220
+ ],
221
+ }
222
+ }
223
+
224
+ export function validateUxPatternRegistry(registry) {
225
+ const issues = []
226
+ if (registry?.version !== 1 || !/^[a-f0-9]{40}$/.test(registry?.source?.revision ?? '') || !registry?.guardrails?.length || !registry?.semanticMapping?.length) issues.push('Missing version, pinned provenance or semantic guardrails')
227
+ const ids = new Set(), components = new Set(listComponents().map(item => item.name))
228
+ if (!Array.isArray(registry?.patterns) || !registry.patterns.length) return { ok: false, issues: [...issues, 'No UX patterns'] }
229
+ for (const item of registry.patterns) {
230
+ if (!item || typeof item.id !== 'string' || ids.has(item.id)) { issues.push('Missing or duplicate pattern id'); continue }
231
+ ids.add(item.id)
232
+ if (item.availability !== 'recipe' || item.renderable !== false || !item.useWhen || !item.avoidWhen) issues.push(`${item.id}: recipe status or selection guidance missing`)
233
+ for (const field of ['variants', 'components', 'slots', 'states', 'dataRequirements', 'invariants', 'adaptable', 'sourceFiles', 'keywords']) {
234
+ if (!Array.isArray(item[field]) || !item[field].length || item[field].some(value => typeof value !== 'string' || !value)) issues.push(`${item.id}: ${field} incomplete`)
235
+ }
236
+ if (Array.isArray(item.components) && item.components.some(name => !components.has(name))) issues.push(`${item.id}: unknown Ceramic component`)
237
+ }
238
+ return { ok: issues.length === 0, issues }
239
+ }
240
+
61
241
  export function listBlocks() {
62
242
  return readJson(join(paths().manifests, 'blocks.json')).blocks
63
243
  }
@@ -228,6 +408,10 @@ export function search(query) {
228
408
  const terms = String(query).toLowerCase().split(/\s+/).filter(Boolean)
229
409
  const score = (text) => terms.reduce((total, term) => total + (text.toLowerCase().includes(term) ? 1 : 0), 0)
230
410
  const results = [
411
+ ...listUiParts().map((item) => ({ kind: 'ui-part', id: item.id, title: item.title.en, summary: [Object.values(item.title).join(' '), item.useWhen, ...item.keywords].join(' '), item })),
412
+ ...listUxExamples().map(item => ({ kind: 'ux-example', id: item.id, title: item.title.en, summary: [...Object.values(item.title), ...Object.values(item.description), ...item.parts].join(' '), item })),
413
+ ...listUxSourcePatterns().map(item => ({ kind: 'ux-source-pattern', id: item.id, title: item.title.en, summary: [item.category, ...Object.values(item.title), ...item.fragments.map(f => f.id)].join(' '), item })),
414
+ ...listUxPatterns().map((item) => ({ kind: 'ux-pattern', id: item.id, title: item.title, summary: [item.useWhen, ...item.keywords].join(' '), item })),
231
415
  ...listComponents().map((item) => ({ kind: 'component', id: item.name, title: item.name, summary: [...item.useWhen, item.category].join(' '), item })),
232
416
  ...listBlockDefinitions().map((item) => ({ kind: 'block-definition', id: item.id, title: item.title, summary: `${item.category} ${item.description} ${item.availability}`, item })),
233
417
  ...listBlocks().map((item) => ({ kind: 'block', id: item.id, title: item.title, summary: `${item.category} ${item.purpose}`, item })),
@@ -239,7 +423,7 @@ export function search(query) {
239
423
  ...listDocs().map((topic) => ({ kind: 'docs', id: topic, title: topic, summary: getDoc(topic)?.content.slice(0, 500) ?? '', item: { topic } })),
240
424
  ]
241
425
  return results
242
- .map((result) => ({ ...result, score: score(`${result.title} ${result.summary}`) }))
426
+ .map((result) => ({ ...result, score: score(`${result.id} ${result.title} ${result.summary}`) }))
243
427
  .filter((result) => result.score > 0)
244
428
  .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
245
429
  .map(({ summary: _summary, ...result }) => result)
@@ -259,6 +443,7 @@ export function capabilityManifest() {
259
443
  { name: 'init', args: ['[directory]'], flags: ['--yes', '--theme <id>'], responseType: 'init-result' },
260
444
  { name: 'room', args: ['connect'], flags: ['--id <id>', '--slug <slug>', '--name <room>', '--brand <brand>', '--source <mode>', '--client <client>', '--directory <path>'], responseType: 'room-connect-result' },
261
445
  { name: 'search', args: ['<query>'], flags: [], responseType: 'search-results' },
446
+ { name: 'pattern', args: ['<id>|--list'], flags: ['--goal <text>', '--theme <id>', '--variant <id>'], responseType: 'ux-pattern|ux-pattern-list|pattern-adaptation-plan' },
262
447
  { name: 'component', args: ['<name>|--list'], flags: [], responseType: 'component|component-list' },
263
448
  { name: 'registry', args: ['<block-id>|--list'], flags: ['--renderable'], responseType: 'block-definition|block-definition-list' },
264
449
  { name: 'layout', args: ['validate <file>', 'serialize <file>'], flags: [], responseType: 'layout-validation-result|layout-serialization-result' },
@@ -274,14 +459,14 @@ export function capabilityManifest() {
274
459
  ],
275
460
  mcp: {
276
461
  ...mcpLaunch,
277
- tools: ['get_room_context', 'search', 'list_components', 'get_component', 'list_block_definitions', 'get_block_definition', 'validate_layout', 'validate_agent_layout_mutation', 'list_blocks', 'get_block', 'list_templates', 'get_template', 'list_themes', 'get_theme', 'list_motion_profiles', 'get_motion_profile', 'list_logo_skills', 'get_logo_skill', 'prepare_logo_maker_run', 'get_brandbook_skill', 'prepare_brandbook_run', 'list_docs', 'get_docs', 'doctor'],
462
+ tools: ['get_room_context', 'search', 'list_ux_source_patterns', 'find_ux_source_patterns', 'get_ux_source_part', 'list_ux_examples', 'get_ux_example', 'find_ui_parts', 'get_ui_part', 'prepare_ui_composition', 'list_patterns', 'get_pattern', 'prepare_pattern_adaptation', 'list_components', 'get_component', 'list_block_definitions', 'get_block_definition', 'validate_layout', 'validate_agent_layout_mutation', 'list_blocks', 'get_block', 'list_templates', 'get_template', 'list_themes', 'get_theme', 'list_motion_profiles', 'get_motion_profile', 'list_logo_skills', 'get_logo_skill', 'prepare_logo_maker_run', 'get_brandbook_skill', 'prepare_brandbook_run', 'list_docs', 'get_docs', 'doctor'],
278
463
  },
279
464
  }
280
465
  }
281
466
 
282
467
  export function repositoryDoctor() {
283
468
  const required = [
284
- 'catalog.json', 'components.json', 'blocks.json', 'composition-registry.json', 'templates.json', 'themes.json', 'motion-profiles.json', 'logo-skills.json', 'brandbook-skill.json',
469
+ 'catalog.json', 'components.json', 'blocks.json', 'composition-registry.json', 'templates.json', 'themes.json', 'motion-profiles.json', 'logo-skills.json', 'ux-patterns.json', 'brandbook-skill.json',
285
470
  ].map((name) => join(paths().manifests, name))
286
471
  const docs = ['quick-start-ai', 'arabic-friendly', 'theme-authoring', 'composition-registry'].map((name) => join(paths().docs, `${name}.md`))
287
472
  const missing = [...required, ...docs].filter((path) => !existsSync(path))
@@ -306,8 +491,10 @@ export function repositoryDoctor() {
306
491
  && definition.agentPermissions
307
492
  ) === true
308
493
  if (!compositionContract) contractIssues.push('composition-registry.json: incomplete block or layout contract')
494
+ const uxPatterns = missing.includes(join(paths().manifests, 'ux-patterns.json')) ? { ok: false, issues: ['Missing UX pattern registry'] } : validateUxPatternRegistry(getUxPatternRegistry())
495
+ contractIssues.push(...uxPatterns.issues)
309
496
  return {
310
- ok: missing.length === 0 && motionContract && compositionContract,
497
+ ok: missing.length === 0 && motionContract && compositionContract && uxPatterns.ok,
311
498
  checks: {
312
499
  manifests: required.every(existsSync),
313
500
  docs: docs.every(existsSync),
@@ -315,6 +502,7 @@ export function repositoryDoctor() {
315
502
  blocks: listBlocks().length,
316
503
  blockDefinitions: compositionRegistry?.definitions?.length ?? 0,
317
504
  compositionContract,
505
+ uxPatternContract: uxPatterns.ok,
318
506
  layoutSchemaVersion: compositionRegistry?.layoutSchemaVersion ?? null,
319
507
  templates: listTemplates().length,
320
508
  themes: listThemes().length,
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@utopia-studio-design/design-system-cli",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "AI-readable CLI and MCP server for Ceramic Design System",
5
5
  "ceramic": {
6
- "designSystem": "^0.10.0"
6
+ "designSystem": "^0.11.0"
7
7
  },
8
8
  "type": "module",
9
9
  "license": "MIT",