@utopia-studio-design/design-system-cli 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) 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-publish-2026-09-13.md +22 -0
  36. package/data/docs/ux-pattern-release-2026-09-13.md +26 -0
  37. package/data/docs/ux-source-evidence/account-auth.json +41 -0
  38. package/data/docs/ux-source-evidence/account-forgot-password.json +36 -0
  39. package/data/docs/ux-source-evidence/account-invite.json +63 -0
  40. package/data/docs/ux-source-evidence/account-locked.json +63 -0
  41. package/data/docs/ux-source-evidence/account-login.json +28 -0
  42. package/data/docs/ux-source-evidence/account-mfa.json +62 -0
  43. package/data/docs/ux-source-evidence/account-onboarding-credentials.json +52 -0
  44. package/data/docs/ux-source-evidence/account-onboarding-legal.json +56 -0
  45. package/data/docs/ux-source-evidence/account-onboarding-orientation.json +66 -0
  46. package/data/docs/ux-source-evidence/account-onboarding-profile.json +50 -0
  47. package/data/docs/ux-source-evidence/account-onboarding.json +69 -0
  48. package/data/docs/ux-source-evidence/account-reset-password.json +27 -0
  49. package/data/docs/ux-source-evidence/account-session-expired.json +62 -0
  50. package/data/docs/ux-source-evidence/account-signup.json +33 -0
  51. package/data/docs/ux-source-evidence/account-verify-email.json +34 -0
  52. package/data/docs/ux-source-evidence/account-welcome.json +41 -0
  53. package/data/docs/ux-source-evidence/admin-analytics.json +54 -0
  54. package/data/docs/ux-source-evidence/admin-audit.json +31 -0
  55. package/data/docs/ux-source-evidence/admin-broadcasts-new.json +36 -0
  56. package/data/docs/ux-source-evidence/admin-broadcasts.json +41 -0
  57. package/data/docs/ux-source-evidence/admin-closures.json +52 -0
  58. package/data/docs/ux-source-evidence/admin-devices.json +51 -0
  59. package/data/docs/ux-source-evidence/admin-escalations.json +35 -0
  60. package/data/docs/ux-source-evidence/admin-incidents.json +54 -0
  61. package/data/docs/ux-source-evidence/admin-loading.json +17 -0
  62. package/data/docs/ux-source-evidence/admin-onboarding-detail.json +80 -0
  63. package/data/docs/ux-source-evidence/admin-onboarding.json +62 -0
  64. package/data/docs/ux-source-evidence/admin-payments.json +45 -0
  65. package/data/docs/ux-source-evidence/admin-providers.json +38 -0
  66. package/data/docs/ux-source-evidence/admin-settings.json +18 -0
  67. package/data/docs/ux-source-evidence/admin-subscriptions-detail-allocate.json +42 -0
  68. package/data/docs/ux-source-evidence/admin-subscriptions-detail.json +48 -0
  69. package/data/docs/ux-source-evidence/admin-subscriptions-new.json +43 -0
  70. package/data/docs/ux-source-evidence/admin-subscriptions.json +41 -0
  71. package/data/docs/ux-source-evidence/admin-transactions.json +41 -0
  72. package/data/docs/ux-source-evidence/error.json +30 -0
  73. package/data/docs/ux-source-evidence/loading.json +19 -0
  74. package/data/docs/ux-source-evidence/marketing-blog.json +48 -0
  75. package/data/docs/ux-source-evidence/marketing-contact.json +36 -0
  76. package/data/docs/ux-source-evidence/marketing.json +64 -0
  77. package/data/docs/ux-source-evidence/not-found.json +28 -0
  78. package/data/docs/ux-source-evidence/portal-loading.json +17 -0
  79. package/data/docs/ux-source-evidence/portal-mentor-messages.json +31 -0
  80. package/data/docs/ux-source-evidence/portal-mentor-profile.json +23 -0
  81. package/data/docs/ux-source-evidence/portal-mentor-requests-detail.json +54 -0
  82. package/data/docs/ux-source-evidence/portal-mentor-sessions-detail-assess.json +42 -0
  83. package/data/docs/ux-source-evidence/portal-mentor-settings.json +18 -0
  84. package/data/docs/ux-source-evidence/portal-mentor.json +38 -0
  85. package/data/docs/ux-source-evidence/portal-sessions-detail-live.json +48 -0
  86. package/data/docs/ux-source-evidence/portal-sponsor-billing.json +28 -0
  87. package/data/docs/ux-source-evidence/portal-sponsor-profile.json +22 -0
  88. package/data/docs/ux-source-evidence/portal-sponsor-sessions.json +28 -0
  89. package/data/docs/ux-source-evidence/portal-sponsor-settings.json +18 -0
  90. package/data/docs/ux-source-evidence/portal-sponsor-trainees.json +36 -0
  91. package/data/docs/ux-source-evidence/portal-sponsor.json +52 -0
  92. package/data/docs/ux-source-evidence/portal-trainee-competency.json +42 -0
  93. package/data/docs/ux-source-evidence/portal-trainee-credits.json +40 -0
  94. package/data/docs/ux-source-evidence/portal-trainee-mentors.json +48 -0
  95. package/data/docs/ux-source-evidence/portal-trainee-messages.json +32 -0
  96. package/data/docs/ux-source-evidence/portal-trainee-notifications.json +35 -0
  97. package/data/docs/ux-source-evidence/portal-trainee-profile.json +26 -0
  98. package/data/docs/ux-source-evidence/portal-trainee-request.json +54 -0
  99. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail-feedback.json +36 -0
  100. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail-precheck.json +42 -0
  101. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail.json +55 -0
  102. package/data/docs/ux-source-evidence/portal-trainee-sessions.json +36 -0
  103. package/data/docs/ux-source-evidence/portal-trainee-settings-notifications.json +38 -0
  104. package/data/docs/ux-source-evidence/portal-trainee-settings.json +18 -0
  105. package/data/docs/ux-source-evidence/portal-trainee.json +44 -0
  106. package/data/docs/ux-source-evidence/portal-tv-detail.json +39 -0
  107. package/data/docs/ux-source-evidence/portal-tv.json +44 -0
  108. package/data/docs/ux-source-evidence/shared-admin-admin-shell.json +35 -0
  109. package/data/docs/ux-source-evidence/shared-auth-auth-primitives.json +105 -0
  110. package/data/docs/ux-source-evidence/shared-auth-auth-shell.json +25 -0
  111. package/data/docs/ux-source-evidence/shared-auth-onboarding-primitives.json +52 -0
  112. package/data/docs/ux-source-evidence/shared-auth-onboarding-shell.json +28 -0
  113. package/data/docs/ux-source-evidence/shared-auth-status-indicator.json +41 -0
  114. package/data/docs/ux-source-evidence/shared-portal-empty-state.json +24 -0
  115. package/data/docs/ux-source-evidence/shared-portal-loading-skeleton.json +27 -0
  116. package/data/docs/ux-source-evidence/shared-portal-notifications-dropdown.json +39 -0
  117. package/data/docs/ux-source-evidence/shared-portal-portal-shell.json +36 -0
  118. package/data/docs/ux-source-evidence/shared-portal-profile-form.json +32 -0
  119. package/data/docs/ux-source-evidence/shared-portal-settings-view.json +81 -0
  120. package/data/docs/ux-source-evidence/shared-portal-usage-sparkline.json +18 -0
  121. package/data/docs/ux-source-evidence/shared-portal-user-menu.json +34 -0
  122. package/data/manifests/blocks.json +230 -21
  123. package/data/manifests/patterns.json +330 -0
  124. package/data/manifests/ui-patterns.json +1029 -0
  125. package/data/manifests/ux-examples.json +2177 -0
  126. package/data/manifests/ux-patterns.json +1135 -0
  127. package/data/manifests/ux-source-inventory.json +4669 -0
  128. package/data/ui-parts/blocks/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  129. package/data/ui-parts/blocks/analytics-dashboard/analytics-dashboard.css +92 -0
  130. package/data/ui-parts/blocks/semantic-pattern-parts/AuthParts.tsx +48 -0
  131. package/data/ui-parts/blocks/semantic-pattern-parts/ChoiceCards.tsx +7 -0
  132. package/data/ui-parts/blocks/semantic-pattern-parts/ListFilters.tsx +9 -0
  133. package/data/ui-parts/blocks/semantic-pattern-parts/OutcomeNotice.tsx +7 -0
  134. package/data/ui-parts/blocks/semantic-pattern-parts/ProductParts.tsx +38 -0
  135. package/data/ui-parts/blocks/semantic-pattern-parts/ProgressSteps.tsx +6 -0
  136. package/data/ui-parts/blocks/semantic-pattern-parts/ReviewList.tsx +7 -0
  137. package/data/ui-parts/blocks/semantic-pattern-parts/ValidatedField.tsx +8 -0
  138. package/data/ui-parts/blocks/semantic-pattern-parts/WorkflowParts.tsx +339 -0
  139. package/data/ui-parts/blocks/semantic-pattern-parts/auth-parts.css +27 -0
  140. package/data/ui-parts/blocks/semantic-pattern-parts/pattern-parts.css +25 -0
  141. package/data/ui-parts/blocks/semantic-pattern-parts/product-parts.css +25 -0
  142. package/data/ui-parts/blocks/semantic-pattern-parts/types.ts +3 -0
  143. package/data/ui-parts/blocks/semantic-pattern-parts/workflow-parts.css +118 -0
  144. package/data/ui-parts/blocks/ux-pattern-examples/AccessExamples.tsx +46 -0
  145. package/data/ui-parts/blocks/ux-pattern-examples/AccountExamples.tsx +133 -0
  146. package/data/ui-parts/blocks/ux-pattern-examples/ProductExamples.tsx +79 -0
  147. package/data/ui-parts/blocks/ux-pattern-examples/WorkflowExample.tsx +1761 -0
  148. package/data/ui-parts/blocks/ux-pattern-examples/WorkflowExamples.tsx +190 -0
  149. package/data/ui-parts/blocks/ux-pattern-examples/copy.ts +6 -0
  150. package/data/ui-parts/blocks/ux-pattern-examples/ux-examples.css +18 -0
  151. package/data/ui-parts/blocks/ux-pattern-examples/workflow-copy.ts +76 -0
  152. package/data/ui-parts/blocks/ux-pattern-examples/workflow-definitions.ts +1291 -0
  153. package/data/ux-source-evidence/account-auth.json +41 -0
  154. package/data/ux-source-evidence/account-forgot-password.json +36 -0
  155. package/data/ux-source-evidence/account-invite.json +63 -0
  156. package/data/ux-source-evidence/account-locked.json +63 -0
  157. package/data/ux-source-evidence/account-login.json +28 -0
  158. package/data/ux-source-evidence/account-mfa.json +62 -0
  159. package/data/ux-source-evidence/account-onboarding-credentials.json +52 -0
  160. package/data/ux-source-evidence/account-onboarding-legal.json +56 -0
  161. package/data/ux-source-evidence/account-onboarding-orientation.json +66 -0
  162. package/data/ux-source-evidence/account-onboarding-profile.json +50 -0
  163. package/data/ux-source-evidence/account-onboarding.json +69 -0
  164. package/data/ux-source-evidence/account-reset-password.json +27 -0
  165. package/data/ux-source-evidence/account-session-expired.json +62 -0
  166. package/data/ux-source-evidence/account-signup.json +33 -0
  167. package/data/ux-source-evidence/account-verify-email.json +34 -0
  168. package/data/ux-source-evidence/account-welcome.json +41 -0
  169. package/data/ux-source-evidence/admin-analytics.json +54 -0
  170. package/data/ux-source-evidence/admin-audit.json +31 -0
  171. package/data/ux-source-evidence/admin-broadcasts-new.json +36 -0
  172. package/data/ux-source-evidence/admin-broadcasts.json +41 -0
  173. package/data/ux-source-evidence/admin-closures.json +52 -0
  174. package/data/ux-source-evidence/admin-devices.json +51 -0
  175. package/data/ux-source-evidence/admin-escalations.json +35 -0
  176. package/data/ux-source-evidence/admin-incidents.json +54 -0
  177. package/data/ux-source-evidence/admin-loading.json +17 -0
  178. package/data/ux-source-evidence/admin-onboarding-detail.json +80 -0
  179. package/data/ux-source-evidence/admin-onboarding.json +62 -0
  180. package/data/ux-source-evidence/admin-payments.json +45 -0
  181. package/data/ux-source-evidence/admin-providers.json +38 -0
  182. package/data/ux-source-evidence/admin-settings.json +18 -0
  183. package/data/ux-source-evidence/admin-subscriptions-detail-allocate.json +42 -0
  184. package/data/ux-source-evidence/admin-subscriptions-detail.json +48 -0
  185. package/data/ux-source-evidence/admin-subscriptions-new.json +43 -0
  186. package/data/ux-source-evidence/admin-subscriptions.json +41 -0
  187. package/data/ux-source-evidence/admin-transactions.json +41 -0
  188. package/data/ux-source-evidence/error.json +30 -0
  189. package/data/ux-source-evidence/loading.json +19 -0
  190. package/data/ux-source-evidence/marketing-blog.json +48 -0
  191. package/data/ux-source-evidence/marketing-contact.json +36 -0
  192. package/data/ux-source-evidence/marketing.json +64 -0
  193. package/data/ux-source-evidence/not-found.json +28 -0
  194. package/data/ux-source-evidence/portal-loading.json +17 -0
  195. package/data/ux-source-evidence/portal-mentor-messages.json +31 -0
  196. package/data/ux-source-evidence/portal-mentor-profile.json +23 -0
  197. package/data/ux-source-evidence/portal-mentor-requests-detail.json +54 -0
  198. package/data/ux-source-evidence/portal-mentor-sessions-detail-assess.json +42 -0
  199. package/data/ux-source-evidence/portal-mentor-settings.json +18 -0
  200. package/data/ux-source-evidence/portal-mentor.json +38 -0
  201. package/data/ux-source-evidence/portal-sessions-detail-live.json +48 -0
  202. package/data/ux-source-evidence/portal-sponsor-billing.json +28 -0
  203. package/data/ux-source-evidence/portal-sponsor-profile.json +22 -0
  204. package/data/ux-source-evidence/portal-sponsor-sessions.json +28 -0
  205. package/data/ux-source-evidence/portal-sponsor-settings.json +18 -0
  206. package/data/ux-source-evidence/portal-sponsor-trainees.json +36 -0
  207. package/data/ux-source-evidence/portal-sponsor.json +52 -0
  208. package/data/ux-source-evidence/portal-trainee-competency.json +42 -0
  209. package/data/ux-source-evidence/portal-trainee-credits.json +40 -0
  210. package/data/ux-source-evidence/portal-trainee-mentors.json +48 -0
  211. package/data/ux-source-evidence/portal-trainee-messages.json +32 -0
  212. package/data/ux-source-evidence/portal-trainee-notifications.json +35 -0
  213. package/data/ux-source-evidence/portal-trainee-profile.json +26 -0
  214. package/data/ux-source-evidence/portal-trainee-request.json +54 -0
  215. package/data/ux-source-evidence/portal-trainee-sessions-detail-feedback.json +36 -0
  216. package/data/ux-source-evidence/portal-trainee-sessions-detail-precheck.json +42 -0
  217. package/data/ux-source-evidence/portal-trainee-sessions-detail.json +55 -0
  218. package/data/ux-source-evidence/portal-trainee-sessions.json +36 -0
  219. package/data/ux-source-evidence/portal-trainee-settings-notifications.json +38 -0
  220. package/data/ux-source-evidence/portal-trainee-settings.json +18 -0
  221. package/data/ux-source-evidence/portal-trainee.json +44 -0
  222. package/data/ux-source-evidence/portal-tv-detail.json +39 -0
  223. package/data/ux-source-evidence/portal-tv.json +44 -0
  224. package/data/ux-source-evidence/shared-admin-admin-shell.json +35 -0
  225. package/data/ux-source-evidence/shared-auth-auth-primitives.json +105 -0
  226. package/data/ux-source-evidence/shared-auth-auth-shell.json +25 -0
  227. package/data/ux-source-evidence/shared-auth-onboarding-primitives.json +52 -0
  228. package/data/ux-source-evidence/shared-auth-onboarding-shell.json +28 -0
  229. package/data/ux-source-evidence/shared-auth-status-indicator.json +41 -0
  230. package/data/ux-source-evidence/shared-portal-empty-state.json +24 -0
  231. package/data/ux-source-evidence/shared-portal-loading-skeleton.json +27 -0
  232. package/data/ux-source-evidence/shared-portal-notifications-dropdown.json +39 -0
  233. package/data/ux-source-evidence/shared-portal-portal-shell.json +36 -0
  234. package/data/ux-source-evidence/shared-portal-profile-form.json +32 -0
  235. package/data/ux-source-evidence/shared-portal-settings-view.json +81 -0
  236. package/data/ux-source-evidence/shared-portal-usage-sparkline.json +18 -0
  237. package/data/ux-source-evidence/shared-portal-user-menu.json +34 -0
  238. package/lib/api.mjs +193 -4
  239. package/package.json +2 -2
package/lib/api.mjs CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  validateAgentLayoutMutation,
7
7
  validateLayoutDocument,
8
8
  } from './composition-runtime.mjs'
9
+
9
10
  export { validateBrandbook } from './brandbook-validation.mjs'
10
11
 
11
12
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
@@ -59,6 +60,186 @@ export function listTemplates() {
59
60
  return readJson(join(paths().manifests, 'templates.json')).templates
60
61
  }
61
62
 
63
+ export function getUxPatternRegistry() {
64
+ return readJson(join(paths().manifests, 'ux-patterns.json'))
65
+ }
66
+
67
+ export function listUxPatterns() {
68
+ return getUxPatternRegistry().patterns
69
+ }
70
+
71
+ export function listUiParts() {
72
+ return readJson(join(paths().manifests, 'ui-patterns.json')).parts
73
+ }
74
+
75
+ export function listUxExamples() {
76
+ return readJson(join(paths().manifests, 'ux-examples.json')).examples
77
+ }
78
+
79
+ /** Complete source evidence, distinct from runnable Ceramic examples. */
80
+ export function listUxSourcePatterns() {
81
+ const inventory = readJson(join(paths().manifests, 'ux-source-inventory.json'))
82
+ const examples = listUxExamples()
83
+ return inventory.entries.map(({ evidence, ...entry }) => ({ ...entry,
84
+ examples: examples.filter(example => (example.sourcePatterns ?? [example.sourcePattern]).includes(entry.id)).map(example => example.id),
85
+ }))
86
+ }
87
+
88
+ export function findUxSourcePatterns(goal) {
89
+ if (typeof goal !== 'string' || !goal.trim() || goal.length > 2000) return { ok: false, code: 'INVALID_GOAL' }
90
+ const words = goal.toLowerCase().split(/\s+/).filter(word => word.length > 1)
91
+ const candidates = listUxSourcePatterns().map(entry => {
92
+ const text = [entry.id, entry.category, ...Object.values(entry.title), ...(entry.keywords ?? []), ...entry.fragments.map(f => f.id)].join(' ').toLowerCase()
93
+ const aliases = (entry.keywords ?? []).filter(keyword => goal.toLowerCase().includes(keyword.toLowerCase()))
94
+ return { ...entry, score: words.filter(word => text.includes(word)).length + aliases.length }
95
+ }).filter(entry => entry.score > 0).sort((a,b) => b.score - a.score)
96
+ 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.' }
97
+ }
98
+
99
+ export function getUxSourcePart(input) {
100
+ 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' }
101
+ const inventory = readJson(join(paths().manifests, 'ux-source-inventory.json'))
102
+ const entry = inventory.entries.find(entry => entry.id === input.id)
103
+ if (!entry) return { ok: false, code: 'UNKNOWN_SOURCE_PATTERN' }
104
+ const theme = resolveUiTheme(input.theme)
105
+ if (!theme) return { ok: false, code: 'UNKNOWN_THEME' }
106
+ const evidence = readJson(hasWorkspaceSource ? join(workspaceRoot, entry.evidence) : join(packagedDataRoot, 'ux-source-evidence', entry.id + '.json'))
107
+ const part = evidence.fragments.find(part => part.id === input.part)
108
+ if (!part) return { ok: false, code: 'UNKNOWN_SOURCE_PART', availableParts: entry.fragments }
109
+ // Never send an entire portal as a default. A caller explicitly chooses a
110
+ // bounded source fragment and must adapt it before use in a consumer.
111
+ const dependencies = evidence.imports.filter(p => p.startsWith('@/')).map(p => {
112
+ const target = inventory.entries.find(item => item.sourceFile === p.replace('@/', '') + '.tsx')
113
+ 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.' }
114
+ })
115
+ return { ok: true, status: 'requires-semantic-adaptation', source: inventory.source, pattern: entry.id, part,
116
+ theme, motion: getMotionProfile(theme.motionProfile), dependencies,
117
+ semanticMapping: getUxPatternRegistry().semanticMapping,
118
+ 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.'] }
119
+ }
120
+
121
+ /** Page examples explain context; they are not mandatory whole-page imports. */
122
+ export function getUxExample(input) {
123
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !['id', 'theme'].includes(key))) return { ok: false, code: 'INVALID_EXAMPLE_INPUT' }
124
+ const example = listUxExamples().find(item => item.id === input.id)
125
+ if (!example) return { ok: false, code: 'UNKNOWN_UX_EXAMPLE' }
126
+ const theme = resolveUiTheme(input.theme)
127
+ if (!theme) return { ok: false, code: 'UNKNOWN_THEME' }
128
+ const parts = example.parts.map(id => getUiPart({ id, theme: theme.id }))
129
+ if (parts.some(part => !part.ok)) return { ok: false, code: 'BROKEN_EXAMPLE_CONTRACT' }
130
+ const files = [...new Set([example.source, ...(example.files ?? []), 'blocks/ux-pattern-examples/copy.ts', 'blocks/ux-pattern-examples/ux-examples.css'])]
131
+ const code = files.map(path => ({ path, content: readFileSync(hasWorkspaceSource ? join(workspaceRoot, path) : join(packagedDataRoot, 'ui-parts', path), 'utf8') }))
132
+ for (const file of parts.flatMap(part => part.code)) if (!code.some(item => item.path === file.path)) code.push(file)
133
+ return { ok: true, example, theme, motion: getMotionProfile(theme.motionProfile), parts, code,
134
+ install: 'npm install @utopia-studio-design/design-system',
135
+ availability: 'Example bundles and MCP tools are available in Ceramic CLI 0.8.0 and later.',
136
+ 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.' }
137
+ }
138
+
139
+ /** Candidate discovery, not a command to add every matching part. The coding
140
+ * agent decides fit in context; the user never has to choose these parts. */
141
+ export function findUiParts(goal) {
142
+ if (typeof goal !== 'string' || !goal.trim() || goal.length > 2000) return { ok: false, code: 'INVALID_GOAL' }
143
+ const text = goal.toLowerCase()
144
+ const candidates = listUiParts().map(part => {
145
+ const matches = part.keywords.filter(keyword => text.includes(keyword.toLowerCase()))
146
+ return { ...part, score: matches.length, matchedIntents: matches }
147
+ }).filter(part => part.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
148
+ const sourceCandidates = findUxSourcePatterns(goal).candidates.slice(0, 8)
149
+ 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.' }
150
+ }
151
+
152
+ function resolveUiTheme(theme) {
153
+ const room = getRoomContext()
154
+ const id = theme ?? room?.themeId ?? room?.theme?.id ?? 'utopia-cloudblur'
155
+ // An invalid explicit/custom theme is not permission to silently rebrand.
156
+ return typeof id === 'string' && id.trim() ? getTheme(id) : null
157
+ }
158
+
159
+ export function getUiPart(input) {
160
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !['id', 'theme'].includes(key))) return { ok: false, code: 'INVALID_PART_INPUT' }
161
+ const part = listUiParts().find(part => part.id === input.id)
162
+ if (!part) return { ok: false, code: 'UNKNOWN_UI_PART' }
163
+ const theme = resolveUiTheme(input.theme)
164
+ if (!theme) return { ok: false, code: 'UNKNOWN_THEME' }
165
+ 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'] : [])])]
166
+ const code = files.map(path => ({ path, content: readFileSync(hasWorkspaceSource ? join(workspaceRoot, path) : join(packagedDataRoot, 'ui-parts', path), 'utf8') }))
167
+ 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.' }
168
+ }
169
+
170
+ export function prepareUiComposition(input) {
171
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(key => !['goal', 'theme'].includes(key))) return { ok: false, code: 'INVALID_COMPOSITION_INPUT' }
172
+ const found = findUiParts(input.goal)
173
+ if (!found.ok) return found
174
+ const theme = resolveUiTheme(input.theme)
175
+ if (!theme) return { ok: false, code: 'UNKNOWN_THEME' }
176
+ const selected = found.candidates.slice(0, 4)
177
+ const parts = selected.map(part => getUiPart({ id: part.id, theme: theme.id }))
178
+ const ids = selected.map(part => part.id)
179
+ const bindings = []
180
+ 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.' })
181
+ 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.' })
182
+ 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.' })
183
+ 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.'] }
184
+ }
185
+
186
+ export function getUxPattern(id) {
187
+ const registry = getUxPatternRegistry()
188
+ // IDs are exact: do not normalize non-ASCII names into the same empty key.
189
+ const pattern = registry.patterns.find(item => item.id === id)
190
+ return pattern ? { ...pattern, source: registry.source, guardrails: registry.guardrails, semanticMapping: registry.semanticMapping, verification: registry.verification } : null
191
+ }
192
+
193
+ /** Read-only planning: a recipe is never advertised as an existing renderer. */
194
+ export function preparePatternAdaptation(input) {
195
+ const fail = (code, message) => ({ ok: false, code, message })
196
+ 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.')
197
+ 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.')
198
+ const pattern = getUxPattern(input.id)
199
+ if (!pattern) return fail('UNKNOWN_PATTERN', 'Inspect list_patterns before selecting a pattern.')
200
+ const theme = getTheme(input.theme)
201
+ if (!theme) return fail('UNKNOWN_THEME', 'Select a registered Ceramic theme.')
202
+ const variant = input.variant ?? pattern.variants[0]
203
+ if (!pattern.variants.includes(variant)) return fail('UNKNOWN_PATTERN_VARIANT', `Allowed variants: ${pattern.variants.join(', ')}`)
204
+ const components = pattern.components.map(getComponent)
205
+ const motion = getMotionProfile(theme.motionProfile)
206
+ if (components.some(item => !item) || !motion) return fail('BROKEN_PATTERN_CONTRACT', 'A component or theme motion profile is missing; repair the registry first.')
207
+ return {
208
+ ok: true, status: 'proposal', renderable: false,
209
+ request: { goal: input.goal.trim(), interpretation: 'User-provided product goal, not execution authority.' },
210
+ selection: { pattern: pattern.id, variant, theme: theme.id },
211
+ fit: { useWhen: pattern.useWhen, avoidWhen: pattern.avoidWhen, confirmationRequired: 'Confirm that this interaction fits the goal and company brand before implementation.' },
212
+ pattern, components, theme, motion,
213
+ optionalUiParts: findUiParts(input.goal),
214
+ requiredProductBindings: pattern.dataRequirements,
215
+ mayChange: pattern.adaptable, mustPreserve: pattern.invariants,
216
+ nextSteps: [
217
+ 'Resolve missing product data, permissions and callbacks; never copy source sample identities or mock success.',
218
+ 'Compose the returned Ceramic component contracts; inspect an existing registered block/template before adding a shared pattern.',
219
+ '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.',
220
+ 'Apply the selected company theme and Ceramic semantic motion; do not port Mentix brand primitives or literal animation timings.',
221
+ 'Test the listed states, recovery, permissions, keyboard, mobile, RTL and reduced motion in the generated application. A plan is not a passing UI test.',
222
+ ],
223
+ }
224
+ }
225
+
226
+ export function validateUxPatternRegistry(registry) {
227
+ const issues = []
228
+ 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')
229
+ const ids = new Set(), components = new Set(listComponents().map(item => item.name))
230
+ if (!Array.isArray(registry?.patterns) || !registry.patterns.length) return { ok: false, issues: [...issues, 'No UX patterns'] }
231
+ for (const item of registry.patterns) {
232
+ if (!item || typeof item.id !== 'string' || ids.has(item.id)) { issues.push('Missing or duplicate pattern id'); continue }
233
+ ids.add(item.id)
234
+ if (item.availability !== 'recipe' || item.renderable !== false || !item.useWhen || !item.avoidWhen) issues.push(`${item.id}: recipe status or selection guidance missing`)
235
+ for (const field of ['variants', 'components', 'slots', 'states', 'dataRequirements', 'invariants', 'adaptable', 'sourceFiles', 'keywords']) {
236
+ if (!Array.isArray(item[field]) || !item[field].length || item[field].some(value => typeof value !== 'string' || !value)) issues.push(`${item.id}: ${field} incomplete`)
237
+ }
238
+ if (Array.isArray(item.components) && item.components.some(name => !components.has(name))) issues.push(`${item.id}: unknown Ceramic component`)
239
+ }
240
+ return { ok: issues.length === 0, issues }
241
+ }
242
+
62
243
  export function listBlocks() {
63
244
  return readJson(join(paths().manifests, 'blocks.json')).blocks
64
245
  }
@@ -229,6 +410,10 @@ export function search(query) {
229
410
  const terms = String(query).toLowerCase().split(/\s+/).filter(Boolean)
230
411
  const score = (text) => terms.reduce((total, term) => total + (text.toLowerCase().includes(term) ? 1 : 0), 0)
231
412
  const results = [
413
+ ...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 })),
414
+ ...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 })),
415
+ ...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 })),
416
+ ...listUxPatterns().map((item) => ({ kind: 'ux-pattern', id: item.id, title: item.title, summary: [item.useWhen, ...item.keywords].join(' '), item })),
232
417
  ...listComponents().map((item) => ({ kind: 'component', id: item.name, title: item.name, summary: [...item.useWhen, item.category].join(' '), item })),
233
418
  ...listBlockDefinitions().map((item) => ({ kind: 'block-definition', id: item.id, title: item.title, summary: `${item.category} ${item.description} ${item.availability}`, item })),
234
419
  ...listBlocks().map((item) => ({ kind: 'block', id: item.id, title: item.title, summary: `${item.category} ${item.purpose}`, item })),
@@ -240,7 +425,7 @@ export function search(query) {
240
425
  ...listDocs().map((topic) => ({ kind: 'docs', id: topic, title: topic, summary: getDoc(topic)?.content.slice(0, 500) ?? '', item: { topic } })),
241
426
  ]
242
427
  return results
243
- .map((result) => ({ ...result, score: score(`${result.title} ${result.summary}`) }))
428
+ .map((result) => ({ ...result, score: score(`${result.id} ${result.title} ${result.summary}`) }))
244
429
  .filter((result) => result.score > 0)
245
430
  .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
246
431
  .map(({ summary: _summary, ...result }) => result)
@@ -260,6 +445,7 @@ export function capabilityManifest() {
260
445
  { name: 'init', args: ['[directory]'], flags: ['--yes', '--theme <id>'], responseType: 'init-result' },
261
446
  { name: 'room', args: ['connect'], flags: ['--id <id>', '--slug <slug>', '--name <room>', '--brand <brand>', '--source <mode>', '--client <client>', '--directory <path>'], responseType: 'room-connect-result' },
262
447
  { name: 'search', args: ['<query>'], flags: [], responseType: 'search-results' },
448
+ { name: 'pattern', args: ['<id>|--list'], flags: ['--goal <text>', '--theme <id>', '--variant <id>'], responseType: 'ux-pattern|ux-pattern-list|pattern-adaptation-plan' },
263
449
  { name: 'component', args: ['<name>|--list'], flags: [], responseType: 'component|component-list' },
264
450
  { name: 'registry', args: ['<block-id>|--list'], flags: ['--renderable'], responseType: 'block-definition|block-definition-list' },
265
451
  { name: 'layout', args: ['validate <file>', 'serialize <file>'], flags: [], responseType: 'layout-validation-result|layout-serialization-result' },
@@ -275,14 +461,14 @@ export function capabilityManifest() {
275
461
  ],
276
462
  mcp: {
277
463
  ...mcpLaunch,
278
- 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', 'validate_brandbook', 'list_docs', 'get_docs', 'doctor'],
464
+ 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', 'validate_brandbook', 'list_docs', 'get_docs', 'doctor'],
279
465
  },
280
466
  }
281
467
  }
282
468
 
283
469
  export function repositoryDoctor() {
284
470
  const required = [
285
- 'catalog.json', 'components.json', 'blocks.json', 'composition-registry.json', 'templates.json', 'themes.json', 'motion-profiles.json', 'logo-skills.json', 'brandbook-skill.json',
471
+ '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',
286
472
  ].map((name) => join(paths().manifests, name))
287
473
  const docs = ['quick-start-ai', 'arabic-friendly', 'theme-authoring', 'composition-registry'].map((name) => join(paths().docs, `${name}.md`))
288
474
  const missing = [...required, ...docs].filter((path) => !existsSync(path))
@@ -307,8 +493,10 @@ export function repositoryDoctor() {
307
493
  && definition.agentPermissions
308
494
  ) === true
309
495
  if (!compositionContract) contractIssues.push('composition-registry.json: incomplete block or layout contract')
496
+ const uxPatterns = missing.includes(join(paths().manifests, 'ux-patterns.json')) ? { ok: false, issues: ['Missing UX pattern registry'] } : validateUxPatternRegistry(getUxPatternRegistry())
497
+ contractIssues.push(...uxPatterns.issues)
310
498
  return {
311
- ok: missing.length === 0 && motionContract && compositionContract,
499
+ ok: missing.length === 0 && motionContract && compositionContract && uxPatterns.ok,
312
500
  checks: {
313
501
  manifests: required.every(existsSync),
314
502
  docs: docs.every(existsSync),
@@ -316,6 +504,7 @@ export function repositoryDoctor() {
316
504
  blocks: listBlocks().length,
317
505
  blockDefinitions: compositionRegistry?.definitions?.length ?? 0,
318
506
  compositionContract,
507
+ uxPatternContract: uxPatterns.ok,
319
508
  layoutSchemaVersion: compositionRegistry?.layoutSchemaVersion ?? null,
320
509
  templates: listTemplates().length,
321
510
  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.8.1",
3
+ "version": "0.8.2",
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",