@utopia-studio-design/design-system-cli 0.2.0 → 0.3.1

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 CHANGED
@@ -9,6 +9,8 @@ npx utopia-ds search "Arabic data table" --json
9
9
  npx utopia-ds component DataTable --json
10
10
  npx utopia-ds template template-saas-solution-homepage --copy ./saas-website
11
11
  npx utopia-ds template template-saas-solution-homepage --theme dextrum --copy ./dextrum-website
12
+ npx utopia-ds template validate ./my-community-template
13
+ npx utopia-ds template submit ./my-community-template
12
14
  npx utopia-ds theme create nova
13
15
  npx utopia-ds manifest --json
14
16
  npx utopia-ds mcp
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- capabilityManifest, getComponent, getDoc, getTemplate, getTheme,
4
- listComponents, listDocs, listTemplates, listThemes, repositoryDoctor, search,
3
+ capabilityManifest, getComponent, getDoc, getMotionProfile, getTemplate, getTheme,
4
+ listComponents, listDocs, listMotionProfiles, listTemplates, listThemes, repositoryDoctor, search,
5
5
  } from '../lib/api.mjs'
6
6
 
7
7
  const tools = [
8
- ['search', 'Search components, docs, templates, and themes.', { query: { type: 'string' } }, ({ query }) => search(query)],
8
+ ['search', 'Search components, docs, templates, themes, and motion profiles.', { query: { type: 'string' } }, ({ query }) => search(query)],
9
9
  ['list_components', 'List every Ceramic component contract.', {}, () => listComponents()],
10
10
  ['get_component', 'Get one component including imports, tokens, usage, and Arabic rules.', { name: { type: 'string' } }, ({ name }) => getComponent(name)],
11
11
  ['list_templates', 'List Ceramic starter templates.', {}, () => listTemplates()],
12
12
  ['get_template', 'Get a template contract and sections.', { id: { type: 'string' } }, ({ id }) => getTemplate(id)],
13
13
  ['list_themes', 'List themes that implement the semantic contract.', {}, () => listThemes()],
14
14
  ['get_theme', 'Get a theme policy and semantic mappings.', { id: { type: 'string' } }, ({ id }) => getTheme(id)],
15
+ ['list_motion_profiles', 'List theme-owned semantic motion personalities.', {}, () => listMotionProfiles()],
16
+ ['get_motion_profile', 'Get a motion profile, its rules, contract, and compatible runtime adapters.', { id: { type: 'string' } }, ({ id }) => getMotionProfile(id)],
15
17
  ['list_docs', 'List AI-readable design-system documents.', {}, () => listDocs()],
16
18
  ['get_docs', 'Read one design-system document.', { topic: { type: 'string' } }, ({ topic }) => getDoc(topic)],
17
19
  ['doctor', 'Validate the Ceramic source of truth.', {}, () => repositoryDoctor()],
package/bin/utopia-ds.mjs CHANGED
@@ -3,9 +3,10 @@ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, wri
3
3
  import { dirname, join, resolve } from 'node:path'
4
4
  import { spawn } from 'node:child_process'
5
5
  import {
6
- capabilityManifest, envelope, getComponent, getDoc, getTemplate, getTheme,
7
- listComponents, listDocs, listTemplates, listThemes, repositoryDoctor, search,
6
+ capabilityManifest, envelope, getComponent, getDoc, getMotionProfile, getTemplate, getTheme,
7
+ listComponents, listDocs, listMotionProfiles, listTemplates, listThemes, mcpLaunch, repositoryDoctor, search,
8
8
  } from '../lib/api.mjs'
9
+ import { createTemplateSubmissionUrl, validateTemplateSubmission } from '../lib/template-submission.mjs'
9
10
 
10
11
  const args = process.argv.slice(2)
11
12
  const command = args.find((arg) => !arg.startsWith('--')) ?? 'help'
@@ -43,8 +44,11 @@ Commands:
43
44
  template <id>|--list [--skeleton] Inspect starter structures
44
45
  template <id> --copy <directory> [--theme <id>]
45
46
  Generate a themed runnable project
47
+ template validate [directory] Validate a community submission
48
+ template submit [directory] Open a validated GitHub review request
46
49
  theme <id>|--list Inspect theme contracts
47
50
  theme create <id> [directory] Scaffold and register a theme
51
+ motion <id>|--list Inspect motion personalities and adapters
48
52
  docs <topic>|--list Read design-system guidance
49
53
  manifest Print the self-describing CLI contract
50
54
  doctor Validate the design-system source
@@ -96,40 +100,42 @@ function scaffoldTheme() {
96
100
  }
97
101
 
98
102
  const themeValues = {
99
- '--background': '#15171C', '--foreground': '#F7F7F5', '--card': '#1D2027', '--card-foreground': '#F7F7F5',
100
- '--primary': '#6F5CFF', '--primary-foreground': '#FFFFFF', '--secondary': '#292D37', '--secondary-foreground': '#F7F7F5',
101
- '--muted': '#222630', '--muted-foreground': '#A8ADB8', '--border': 'rgba(247,247,245,0.16)', '--input': 'rgba(247,247,245,0.24)',
102
- '--ring': '#6F5CFF', '--radius': '8px', '--radius-control': '8px', '--radius-surface': '12px', '--radius-chat-bubble': '18px',
103
- '--radius-chat-composer': '24px', '--radius-chat-token': '14px', '--shadow-control': 'none', '--motion-duration-press': '160ms',
104
- '--motion-duration-page': '200ms', '--motion-duration-expand': '320ms', '--motion-duration-reveal': '520ms', '--motion-duration-icon': '640ms',
103
+ '--background': '#F2EFE8', '--foreground': '#292725', '--card': '#FAF8F3', '--card-foreground': '#292725',
104
+ '--primary': '#4A514B', '--primary-foreground': '#FAF8F3', '--secondary': '#DFDBD1', '--secondary-foreground': '#292725',
105
+ '--muted': '#E8E4DB', '--muted-foreground': '#66625C', '--border': 'rgba(41,39,37,0.18)', '--input': 'rgba(41,39,37,0.28)',
106
+ '--ring': '#4A514B', '--radius': '2px', '--radius-control': '2px', '--radius-surface': '4px', '--radius-chat-bubble': '4px',
107
+ '--radius-chat-composer': '6px', '--radius-chat-token': '4px', '--shadow-control': 'none', '--motion-duration-press': '100ms',
108
+ '--motion-duration-page': '200ms', '--motion-duration-expand': '220ms', '--motion-duration-reveal': '240ms', '--motion-duration-icon': '320ms',
105
109
  '--motion-ease-standard': 'cubic-bezier(0.2, 0, 0, 1)', '--motion-ease-emphasized': 'cubic-bezier(0.16, 1, 0.3, 1)',
106
- '--motion-ease-icon': 'cubic-bezier(0.16, 1, 0.3, 1)', '--motion-press-scale': '0.97', '--motion-distance-page': '8px',
107
- '--motion-distance-reveal': '8px', '--font-sans': 'Inter', '--font-display': 'Inter', '--font-ui-support': 'Inter',
108
- '--font-marketing-display': 'Inter', '--font-arabic': 'IBM Plex Sans Arabic', '--font-arabic-body': 'IBM Plex Sans Arabic',
110
+ '--motion-ease-icon': 'cubic-bezier(0.16, 1, 0.3, 1)', '--motion-press-scale': '0.985', '--motion-distance-page': '0px',
111
+ '--motion-distance-reveal': '0px', '--font-sans': 'ui-sans-serif, sans-serif', '--font-display': 'ui-sans-serif, sans-serif', '--font-ui-support': 'ui-sans-serif, sans-serif',
112
+ '--font-marketing-display': 'ui-sans-serif, sans-serif', '--font-arabic': 'IBM Plex Sans Arabic', '--font-arabic-body': 'IBM Plex Sans Arabic',
109
113
  '--font-arabic-display': 'IBM Plex Sans Arabic', '--font-weight-arabic-body': '400', '--font-weight-arabic-display': '700',
110
114
  '--font-size-display': 'clamp(2.75rem, 7vw, 5.5rem)', '--font-size-arabic-body': '16px', '--font-size-arabic-body-lg': '18px',
111
115
  '--font-size-arabic-display': 'clamp(2.625rem, 6.6vw, 5.25rem)', '--line-height-arabic': '1.75', '--line-height-arabic-body': '1.75',
112
116
  '--line-height-arabic-display': '1.14', '--tracking-arabic': '0', '--tracking-arabic-display': '0', '--sidebar-width': '17rem',
113
117
  '--control-height-sm': '2rem', '--sidebar-width-collapsed': '4rem', '--sidebar-min-block-size': '30rem', '--sidebar-rail-size': '8px',
114
- '--button-height': '40px', '--card-padding': '24px', '--overlay': 'rgba(21,23,28,0.86)', '--modal-surface': '#1D2027', '--popover-surface': '#1D2027',
118
+ '--button-height': '40px', '--card-padding': '24px', '--overlay': 'rgba(41,39,37,0.72)', '--modal-surface': '#FAF8F3', '--popover-surface': '#FAF8F3',
115
119
  }
116
120
  const declarations = Object.entries(themeValues).map(([token, value]) => ` ${token}: ${value};`).join('\n')
117
- const css = `[data-theme="${id}"] {\n color-scheme: dark;\n${declarations}\n}\n\n[data-theme="${id}"][data-color-mode="light"] {\n color-scheme: light;\n --background: #F7F7F5;\n --foreground: #15171C;\n --card: #FFFFFF;\n --card-foreground: #15171C;\n --secondary: #ECECF0;\n --secondary-foreground: #15171C;\n --muted: #E5E6EA;\n --muted-foreground: #5F6470;\n --border: rgb(21 23 28 / 16%);\n --input: rgb(21 23 28 / 24%);\n --surface: rgb(21 23 28 / 4.5%);\n --surface-strong: rgb(21 23 28 / 8%);\n --surface-elevated: #FFFFFF;\n --overlay: rgb(247 247 245 / 86%);\n --modal-surface: #FFFFFF;\n --popover-surface: #FFFFFF;\n}\n\n[data-theme="${id}"] :where(:lang(ar), [lang|="ar"]) {\n font-family: var(--font-arabic-body);\n line-height: var(--line-height-arabic-body);\n letter-spacing: var(--tracking-arabic);\n text-transform: none;\n}\n`
121
+ const css = `[data-theme="${id}"] {\n color-scheme: light;\n${declarations}\n}\n\n[data-theme="${id}"][data-color-mode="dark"] {\n color-scheme: dark;\n --background: #252523;\n --foreground: #EEEAE1;\n --card: #2D2D2A;\n --card-foreground: #EEEAE1;\n --primary: #B8C0B8;\n --primary-foreground: #252523;\n --secondary: #3A3935;\n --secondary-foreground: #EEEAE1;\n --muted: #33322F;\n --muted-foreground: #B9B4AA;\n --border: rgb(238 234 225 / 18%);\n --input: rgb(238 234 225 / 28%);\n --surface: rgb(238 234 225 / 4%);\n --surface-strong: rgb(238 234 225 / 8%);\n --surface-elevated: #2D2D2A;\n --overlay: rgb(37 37 35 / 82%);\n --modal-surface: #2D2D2A;\n --popover-surface: #2D2D2A;\n}\n\n[data-theme="${id}"] :where(:lang(ar), [lang|="ar"]) {\n font-family: var(--font-arabic-body);\n line-height: var(--line-height-arabic-body);\n letter-spacing: var(--tracking-arabic);\n text-transform: none;\n}\n`
118
122
  const policy = {
119
123
  '$schema': 'https://utopia-studio.co/design-system/theme.schema.json', id, name, type: 'theme-policy', locked: false,
120
- summary: `${name} brand theme mapped to the Ceramic semantic contract.`,
124
+ motionProfile: 'precise',
125
+ summary: `${name} neutral theme scaffold mapped to the Ceramic semantic contract; replace placeholder primitives before release.`,
121
126
  sourceFiles: [`packages/design-system/src/themes/${id}.css`, `packages/design-system/src/manifests/theme-${id}.json`],
122
- brandPrimitives: { colors: { background: themeValues['--background'], foreground: themeValues['--foreground'], accent: themeValues['--primary'] }, typography: { latin: 'Inter', arabic: 'IBM Plex Sans Arabic' }, geometry: { controlRadius: themeValues['--radius-control'], surfaceRadius: themeValues['--radius-surface'] } },
127
+ brandPrimitives: { colors: { background: themeValues['--background'], foreground: themeValues['--foreground'], accent: themeValues['--primary'] }, typography: { latin: 'Unassigned', arabic: 'IBM Plex Sans Arabic' }, geometry: { controlRadius: themeValues['--radius-control'], surfaceRadius: themeValues['--radius-surface'] } },
123
128
  semanticMappings: Object.fromEntries(Object.entries(themeValues).map(([token, value]) => [token, value])),
124
- visualPolicy: { tone: ['clear', 'intentional', 'product-focused'], allow: ['semantic color roles', 'logical layout properties', 'theme-owned brand expression'], avoid: ['Utopia brand primitives', 'component-specific color literals', 'left/right-only layout APIs'] },
129
+ visualPolicy: { tone: ['neutral', 'restrained', 'unfinished-by-design'], allow: ['semantic color roles', 'logical layout properties', 'theme-owned brand expression'], avoid: ['shipping placeholder primitives', 'generic SaaS gradients', 'default purple accents', 'component-specific color literals', 'left/right-only layout APIs'] },
125
130
  arabicFriendly: { direction: 'Support dir="rtl" with logical CSS properties.', typography: 'Use the declared Arabic family without Latin tracking or casing.' },
126
131
  translations: { ar: { summary: `ثيم ${name} مبني على عقد Ceramic الدلالي.`, iconPolicy: { description: 'استخدم أيقونات واضحة ومحايدة الاتجاه، واعكس الأيقونات الاتجاهية في RTL.', allow: ['أيقونات Lucide', 'أيقونات محايدة الاتجاه', 'انعكاس الأيقونات الاتجاهية في RTL'] } } },
127
132
  }
128
133
  const entry = {
129
134
  id, name, shortName: name, locked: false, role: 'brand theme', description: policy.summary,
135
+ motionProfile: 'precise',
130
136
  bestFor: [`${name} product interfaces`], principles: policy.visualPolicy.tone,
131
137
  translations: { ar: { name, role: 'ثيم علامة', description: `ثيم ${name} مبني على عقد Ceramic الدلالي.`, bestFor: [`واجهات ${name}`], principles: ['وضوح', 'اتساق', 'دعم العربية'] } },
132
- policyManifest: `packages/design-system/src/manifests/theme-${id}.json`, css: `packages/design-system/src/themes/${id}.css`, iconSystem: 'lucide', values: themeValues,
138
+ policyManifest: `packages/design-system/src/manifests/theme-${id}.json`, css: `packages/design-system/src/themes/${id}.css`, iconSystem: 'unassigned', values: themeValues,
133
139
  }
134
140
 
135
141
  write(cssPath, css)
@@ -157,16 +163,17 @@ function init() {
157
163
  2. Run \`npm run ceramic -- search <intent> --json\`, then inspect the selected component or template.
158
164
  3. Prefer \`@utopia-studio-design/design-system\` exports over raw shadcn/ui source.
159
165
  4. Components consume semantic tokens only. The active theme is \`${theme}\`.
160
- 5. Read \`npm run ceramic -- docs arabic-friendly --dense\` before Arabic or RTL work.
161
- 6. Never invent component props, import paths, tokens, Arabic product copy, or left/right-only APIs.
162
- 7. Validate with \`npm run ceramic:doctor\` before handoff.
166
+ 5. Request semantic motion only; inspect \`npm run ceramic -- motion ${getTheme(theme)?.motionProfile} --json\` before choosing an application runtime adapter.
167
+ 6. Read \`npm run ceramic -- docs arabic-friendly --dense\` before Arabic or RTL work.
168
+ 7. Never invent component props, import paths, tokens, Arabic product copy, or left/right-only APIs.
169
+ 8. Validate with \`npm run ceramic:doctor\` before handoff.
163
170
  `
164
171
  write(join(target, 'AGENTS.md'), rules)
165
172
  write(join(target, 'CLAUDE.md'), rules)
166
173
  write(join(target, '.cursor/rules/ceramic-design-system.mdc'), `---\ndescription: Ceramic Design System rules\nalwaysApply: true\n---\n\n${rules}`)
167
174
  write(join(target, '.github/copilot-instructions.md'), rules)
168
- write(join(target, '.ceramic/config.json'), `${JSON.stringify({ apiVersion: 1, theme, arabicFriendly: true, source: '@utopia-studio-design/design-system' }, null, 2)}\n`)
169
- write(join(target, '.mcp.json'), `${JSON.stringify({ mcpServers: { ceramic: { command: 'npx', args: ['-y', '@utopia-studio-design/design-system-cli', 'mcp'] } } }, null, 2)}\n`)
175
+ write(join(target, '.ceramic/config.json'), `${JSON.stringify({ apiVersion: 1, theme, motionProfile: getTheme(theme)?.motionProfile, arabicFriendly: true, source: '@utopia-studio-design/design-system' }, null, 2)}\n`)
176
+ write(join(target, '.mcp.json'), `${JSON.stringify({ mcpServers: { ceramic: mcpLaunch } }, null, 2)}\n`)
170
177
  output('init-result', { ok: true, target, theme, files: ['AGENTS.md', 'CLAUDE.md', '.cursor/rules/ceramic-design-system.mdc', '.github/copilot-instructions.md', '.ceramic/config.json', '.mcp.json'] },
171
178
  (data) => `Ceramic initialized in ${data.target}.\nCreated ${data.files.join(', ')}.`)
172
179
  }
@@ -207,10 +214,10 @@ function copyTemplateProject(entry) {
207
214
  const workspaceCli = resolve(process.cwd(), 'packages/design-system-cli')
208
215
  const designSystemDependency = existsSync(join(workspacePackage, 'package.json'))
209
216
  ? `file:${workspacePackage}`
210
- : '^0.2.0'
217
+ : '^0.3.0'
211
218
  const cliDependency = existsSync(join(workspaceCli, 'package.json'))
212
219
  ? `file:${workspaceCli}`
213
- : '^0.2.0'
220
+ : '^0.3.0'
214
221
  write(join(target, 'package.json'), `${JSON.stringify({
215
222
  name: packageName,
216
223
  private: true,
@@ -226,13 +233,40 @@ function copyTemplateProject(entry) {
226
233
  },
227
234
  devDependencies: { '@utopia-studio-design/design-system-cli': cliDependency, '@vitejs/plugin-react': '^4.3.4', typescript: '^5.7.2', vite: '^6.0.7' },
228
235
  }, null, 2)}\n`)
229
- write(join(target, '.ceramic/config.json'), `${JSON.stringify({ apiVersion: 1, theme, arabicFriendly: true, source: '@utopia-studio-design/design-system' }, null, 2)}\n`)
236
+ write(join(target, '.ceramic/config.json'), `${JSON.stringify({ apiVersion: 1, theme, motionProfile: getTheme(theme)?.motionProfile, motionAdapter: 'framer-motion', arabicFriendly: true, source: '@utopia-studio-design/design-system' }, null, 2)}\n`)
230
237
  write(join(target, 'vite.config.ts'), `import { resolve } from 'node:path'\nimport react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n plugins: [react()],\n build: {\n rollupOptions: {\n input: {\n home: resolve('index.html'),\n product: resolve('product/index.html'),\n agents: resolve('agents/index.html'),\n integrations: resolve('integrations/index.html'),\n integrationDetail: resolve('integrations/slack/index.html'),\n customers: resolve('customers/index.html'),\n customerStory: resolve('customers/aster-labs/index.html'),\n pricing: resolve('pricing/index.html'),\n changelog: resolve('changelog/index.html'),\n contactSales: resolve('contact-sales/index.html'),\n },\n },\n },\n})\n`)
231
238
  const existingReadme = readFileSync(join(target, 'README.md'), 'utf8')
232
239
  write(join(target, 'README.md'), `# Generated Ceramic SaaS website\n\nActive theme: \`${theme}\`\n\n\`\`\`sh\nnpm install\nnpm run dev\n\`\`\`\n\nOpen \`http://localhost:5173/?seed=1974341818\`. All ten page entries share the same seed, theme, and locale state.\n\n${existingReadme}`)
233
240
  output('template-copy-result', { ok: true, id: entry.id, target, theme, pages: entry.pages ?? [] }, (data) => `Generated ${data.id} with theme ${data.theme} in ${data.target}.\nNext: cd ${data.target} && npm install && npm run dev`)
234
241
  }
235
242
 
243
+ function validateCommunityTemplate({ submit = false } = {}) {
244
+ const target = resolve(values[1] ?? process.cwd())
245
+ const result = validateTemplateSubmission(target)
246
+ if (!result.ok) {
247
+ if (json) return fail('Community template validation failed.', 'ERR_TEMPLATE_SUBMISSION', result.errors)
248
+ console.error(`Community template validation failed with ${result.errors.length} error(s):`)
249
+ for (const item of result.errors) console.error(`- ${item.code}: ${item.message}${item.path ? ` (${item.path})` : ''}`)
250
+ for (const item of result.warnings) console.error(`- warning ${item.code}: ${item.message}`)
251
+ process.exitCode = 1
252
+ return
253
+ }
254
+ const submissionUrl = submit ? createTemplateSubmissionUrl(result) : null
255
+ output(submit ? 'template-submit-result' : 'template-validation-result', {
256
+ ok: true,
257
+ id: result.manifest.id,
258
+ version: result.manifest.version,
259
+ repository: result.manifest.repository,
260
+ files: result.files.length,
261
+ warnings: result.warnings,
262
+ submissionUrl,
263
+ }, (data) => [
264
+ `Community template ${data.id}@${data.version} passed validation (${data.files} files).`,
265
+ ...data.warnings.map((item) => `Warning ${item.code}: ${item.message}`),
266
+ ...(data.submissionUrl ? [`Submit for review: ${data.submissionUrl}`] : []),
267
+ ].join('\n'))
268
+ }
269
+
236
270
  if (command === 'help' || args.includes('--help')) help()
237
271
  else if (command === 'init') init()
238
272
  else if (command === 'manifest') output('manifest', capabilityManifest())
@@ -246,7 +280,9 @@ else if (command === 'search') {
246
280
  else { const item = getComponent(name); item ? output('component', item, formatComponent) : fail(`Unknown component "${name}".`, 'ERR_COMPONENT', search(name).slice(0, 5).map((result) => result.id)) }
247
281
  } else if (command === 'template') {
248
282
  const name = values[0]
249
- if (!name || args.includes('--list')) output('template-list', listTemplates(), (rows) => rows.map((item) => `${item.id}|${item.category}|${item.title}|${item.purpose}`).join('\n'))
283
+ if (name === 'validate') validateCommunityTemplate()
284
+ else if (name === 'submit') validateCommunityTemplate({ submit: true })
285
+ else if (!name || args.includes('--list')) output('template-list', listTemplates(), (rows) => rows.map((item) => `${item.id}|${item.category}|${item.title}|${item.purpose}`).join('\n'))
250
286
  else {
251
287
  const item = getTemplate(name)
252
288
  if (!item) fail(`Unknown template "${name}".`, 'ERR_TEMPLATE')
@@ -258,11 +294,15 @@ else if (command === 'search') {
258
294
  if (name === 'create') scaffoldTheme()
259
295
  else if (!name || args.includes('--list')) output('theme-list', listThemes(), (rows) => rows.map((item) => `${item.id}|${item.name}|${item.role}|${item.policyManifest}`).join('\n'))
260
296
  else { const item = getTheme(name); item ? output('theme', item) : fail(`Unknown theme "${name}".`, 'ERR_THEME') }
297
+ } else if (command === 'motion') {
298
+ const name = values[0]
299
+ if (!name || args.includes('--list')) output('motion-profile-list', listMotionProfiles(), (rows) => rows.map((item) => `${item.id}|${item.themes.join(',')}|${item.label}|${item.description}`).join('\n'))
300
+ else { const item = getMotionProfile(name); item ? output('motion-profile', item) : fail(`Unknown motion profile "${name}".`, 'ERR_MOTION_PROFILE', listMotionProfiles().map((item) => item.id)) }
261
301
  } else if (command === 'docs') {
262
302
  const topic = values[0]
263
303
  if (!topic || args.includes('--list')) output('docs-list', listDocs(), (rows) => rows.join('\n'))
264
304
  else { const item = getDoc(topic); item ? output('docs', item, (doc) => dense ? doc.content.replace(/\n{2,}/g, '\n').trim() : doc.content) : fail(`Unknown docs topic "${topic}".`, 'ERR_DOCS', listDocs()) }
265
305
  } else if (command === 'doctor') {
266
- const result = repositoryDoctor(); output('doctor-result', result, (data) => data.ok ? `Ceramic doctor passed. ${data.checks.components} components, ${data.checks.templates} templates, ${data.checks.themes} themes, MCP ready.` : `Ceramic doctor failed: ${data.missing.join(', ')}`); if (!result.ok) process.exitCode = 1
306
+ const result = repositoryDoctor(); output('doctor-result', result, (data) => data.ok ? `Ceramic doctor passed. ${data.checks.components} components, ${data.checks.templates} templates, ${data.checks.themes} themes, ${data.checks.motionProfiles} motion profiles, MCP ready.` : `Ceramic doctor failed: ${data.missing.join(', ')}`); if (!result.ok) process.exitCode = 1
267
307
  } else if (command === 'mcp') runMcp()
268
308
  else fail(`Unknown command "${command}".`, 'ERR_COMMAND', capabilityManifest().commands.map((item) => item.name))
@@ -37,7 +37,7 @@ Every authoring command must support `--json`, `--dry-run`, deterministic output
37
37
  The generated `.mcp.json` works for clients that support project-local MCP configuration. Codex CLI can register the same stdio server explicitly:
38
38
 
39
39
  ```sh
40
- codex mcp add ceramic -- npx -y @utopia-studio-design/design-system-cli mcp
40
+ codex mcp add ceramic -- npx -y --package @utopia-studio-design/design-system-cli utopia-ds mcp
41
41
  ```
42
42
 
43
43
  The server exposes read-only design-system discovery. File-writing operations remain CLI commands with dry-run and explicit user control.
@@ -0,0 +1,85 @@
1
+ # Share a community template
2
+
3
+ Ceramic community templates stay in the creator's public GitHub repository. Ceramic reviews a pinned version before listing it in the catalog; submitting a repository does not execute its code on the documentation website.
4
+
5
+ ## What the designer prepares
6
+
7
+ 1. A public GitHub repository containing the runnable template source.
8
+ 2. A working preview URL when possible.
9
+ 3. A license covering the source and included assets.
10
+ 4. A `ceramic.template.json` file at the repository root.
11
+ 5. Responsive, dark-mode, keyboard, and RTL coverage.
12
+
13
+ Do not include credentials, `.env` files, private keys, production customer data, unlicensed media, or install lifecycle scripts.
14
+
15
+ ## Manifest
16
+
17
+ Create `ceramic.template.json` in the repository root:
18
+
19
+ ```json
20
+ {
21
+ "$schema": "https://raw.githubusercontent.com/The-Utopia-Studio/Ceramic-Design-System/main/packages/design-system/src/manifests/community-template.schema.json",
22
+ "schemaVersion": 1,
23
+ "id": "operations-dashboard",
24
+ "name": "Operations Dashboard",
25
+ "version": "1.0.0",
26
+ "summary": "A responsive operations dashboard for queues, incidents, and team handoffs.",
27
+ "category": "dashboard",
28
+ "author": {
29
+ "name": "Example Studio",
30
+ "url": "https://example.com"
31
+ },
32
+ "license": "MIT",
33
+ "repository": "https://github.com/example/operations-dashboard",
34
+ "preview": "https://operations-dashboard.example.com",
35
+ "designSystem": {
36
+ "package": "@utopia-studio-design/design-system",
37
+ "version": "^0.4.4"
38
+ },
39
+ "entry": "src/main.tsx",
40
+ "files": ["src", "index.html", "package.json"],
41
+ "themes": ["utopia-default", "dextrum"],
42
+ "features": {
43
+ "responsive": true,
44
+ "darkMode": true,
45
+ "rtl": true
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Validate
51
+
52
+ Install the public CLI and run validation from the template repository root:
53
+
54
+ ```sh
55
+ npm install -D @utopia-studio-design/design-system-cli
56
+ npx utopia-ds template validate .
57
+ ```
58
+
59
+ Warnings identify raw color values or physical left/right CSS. Explain intentional theme-owned media values in the review request. Validation errors must be fixed before submission.
60
+
61
+ ## Submit
62
+
63
+ Either use the form in the Ceramic Templates page or run:
64
+
65
+ ```sh
66
+ npx utopia-ds template submit .
67
+ ```
68
+
69
+ The command returns a GitHub review URL populated from the manifest. Open it, verify the declaration, and submit the issue. Pin a release or commit for review instead of asking maintainers to review a moving branch.
70
+
71
+ ## Review and publication
72
+
73
+ Ceramic maintainers verify:
74
+
75
+ - public component APIs and package imports;
76
+ - semantic-token usage and theme compatibility;
77
+ - responsive, dark-mode, keyboard, reduced-motion, and RTL behavior;
78
+ - licensing, demo-data labeling, and asset ownership;
79
+ - the absence of secrets and install lifecycle scripts.
80
+
81
+ Approved submissions receive a catalog record under **Community**. The creator continues to own the source and releases. A new published version requires a new review; an existing version is never overwritten.
82
+
83
+ ## Instructions to send to a designer
84
+
85
+ > Put the runnable template in a public GitHub repository and deploy a preview if possible. Add `ceramic.template.json` at the repository root using the example above. Confirm that the template uses `@utopia-studio-design/design-system`, semantic tokens, logical CSS properties, responsive layouts, dark mode, keyboard interaction, and RTL. Remove `.env` files, credentials, private keys, install scripts, private customer data, and unlicensed assets. Run `npx utopia-ds template validate .`, fix every error, then run `npx utopia-ds template submit .` or use the Submit a template form on the Ceramic Templates page. Send the generated GitHub review request with a pinned release or commit.
@@ -69,26 +69,46 @@ Arabic display sizing should follow the Latin display scale at about 95%, rather
69
69
 
70
70
  ## Motion Contract
71
71
 
72
- - Ceramic defines five semantic patterns: `press`, `page`, `expand`, `reveal`, and `icon`.
72
+ - Ceramic separates motion policy from motion execution: the theme selects a personality, the application selects an adapter, and components request semantic motion.
73
+ - Component timing continues to use five semantic patterns: `press`, `page`, `expand`, `reveal`, and `icon`.
74
+ - Runtime recipes use four engine-neutral intents: `feedback`, `page`, `surface`, and `layout`.
73
75
  - Components consume `--motion-duration-*` and `--motion-ease-*` roles rather than hardcoded milliseconds or easing curves.
74
- - `MotionProvider` sets the subtree default. Motion-aware components expose `motion?: boolean` for a local override.
76
+ - `MotionProvider` sets the theme profile and runtime adapter for a subtree. Motion-aware components expose `motion?: boolean` for a local override.
75
77
  - `motion={false}` and `prefers-reduced-motion: reduce` disable decorative movement while preserving state changes and accessibility.
76
78
  - Directional motion follows logical inline start/end and mirrors in RTL when direction carries meaning.
77
79
  - Icon motion follows the action: a bell swings from its top, download moves downward, and copy snaps once. Do not apply a generic bounce.
80
+ - `utopia-default` uses the `ceremonial` profile, `dextrum` uses `swift`, and `barrier-intelligence` uses `precise`.
81
+ - WAAPI is built in. Motion for React, Anime.js, and GSAP are optional peer adapters, so unused engines do not need to ship with an application.
78
82
 
79
83
  ```tsx
80
- import { MotionProvider } from '@utopia-studio-design/design-system/Motion'
84
+ import { getMotionThemeProfile, MotionProvider } from '@utopia-studio-design/design-system/Motion'
85
+ import { animeMotionAdapter } from '@utopia-studio-design/design-system/MotionAnime'
81
86
  import { Button } from '@utopia-studio-design/design-system/Button'
82
87
 
83
88
  export function App() {
84
89
  return (
85
- <MotionProvider motion>
90
+ <MotionProvider
91
+ adapter={animeMotionAdapter}
92
+ themeProfile={getMotionThemeProfile(activeTheme.motionProfile)}
93
+ >
86
94
  <Button motion={false}>Static local action</Button>
87
95
  </MotionProvider>
88
96
  )
89
97
  }
90
98
  ```
91
99
 
100
+ Install only the engine selected by the application:
101
+
102
+ ```bash
103
+ npm install animejs
104
+ # or
105
+ npm install gsap
106
+ # or
107
+ npm install framer-motion
108
+ ```
109
+
110
+ The engine-neutral registry is published as `manifests/motion-profiles.json`. It is the runtime, CLI, and MCP source of truth for timing, easing, semantic recipes, orchestration, theme mappings, and reduced-motion behavior. Resolve custom themes through their declared `motionProfile`; reduced motion applies each semantic final state immediately and clears transform and filter effects. Optional adapters should be loaded with `import()` when they are selected. Motion AI Kit and GSAP Skills may help agents author or audit engine-specific code, but they are authoring tools rather than runtime adapters.
111
+
92
112
  ## Elevation Contract
93
113
 
94
114
  - Elevation is semantic hierarchy, not a mandatory shadow style.
@@ -88,7 +88,7 @@ The MCP server exposes the same API as the CLI. A generated `.mcp.json` uses:
88
88
  "mcpServers": {
89
89
  "ceramic": {
90
90
  "command": "npx",
91
- "args": ["-y", "@utopia-studio-design/design-system-cli", "mcp"]
91
+ "args": ["-y", "--package", "@utopia-studio-design/design-system-cli", "utopia-ds", "mcp"]
92
92
  }
93
93
  }
94
94
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://utopia-studio.co/design-system/catalog.schema.json",
3
3
  "name": "Utopia Design System Catalog",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "purpose": "Standalone source of truth for the Utopia Design System home, docs, component catalog, templates, themes, and AI agent workflow.",
6
6
  "topLevelAreas": [
7
7
  {
@@ -13,8 +13,9 @@
13
13
  "id": "guide",
14
14
  "label": "Guide",
15
15
  "items": [
16
- "Getting Started",
17
- "Quick Start with AI",
16
+ "Getting Started",
17
+ "MCP Playground",
18
+ "Quick Start with AI",
18
19
  "Working with AI",
19
20
  "Principles",
20
21
  "Theme System",
@@ -193,7 +194,8 @@
193
194
  "Skeleton",
194
195
  "Spinner",
195
196
  "Table",
196
- "Typography"
197
+ "Typography",
198
+ "Utopia Wordmark Loader"
197
199
  ]
198
200
  }
199
201
  ]
@@ -306,7 +308,8 @@
306
308
  "Toggle Button",
307
309
  "Toggle Button Group",
308
310
  "Tooltip",
309
- "Typography"
311
+ "Typography",
312
+ "Utopia Wordmark Loader"
310
313
  ],
311
314
  "utopiaWrapped": [
312
315
  "Accordion",
@@ -383,7 +386,8 @@
383
386
  "Toggle Button",
384
387
  "Toggle Button Group",
385
388
  "Tooltip",
386
- "Typography"
389
+ "Typography",
390
+ "Utopia Wordmark Loader"
387
391
  ],
388
392
  "legacy": [
389
393
  "Toast"
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://utopia-studio.co/design-system/community-template.schema.json",
4
+ "title": "Ceramic Community Template",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "id", "name", "version", "summary", "category", "author", "license", "repository", "designSystem", "entry", "files", "themes", "features"],
8
+ "properties": {
9
+ "$schema": { "type": "string", "format": "uri" },
10
+ "schemaVersion": { "const": 1 },
11
+ "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
12
+ "name": { "type": "string", "minLength": 2 },
13
+ "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$" },
14
+ "summary": { "type": "string", "minLength": 20 },
15
+ "category": { "type": "string", "enum": ["application", "dashboard", "website", "workflow", "commerce", "content", "other"] },
16
+ "author": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "required": ["name"],
20
+ "properties": {
21
+ "name": { "type": "string", "minLength": 2 },
22
+ "url": { "type": "string", "format": "uri" }
23
+ }
24
+ },
25
+ "license": { "type": "string", "minLength": 2 },
26
+ "repository": { "type": "string", "pattern": "^https://github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/?$" },
27
+ "preview": { "type": "string", "format": "uri" },
28
+ "designSystem": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["package", "version"],
32
+ "properties": {
33
+ "package": { "const": "@utopia-studio-design/design-system" },
34
+ "version": { "type": "string", "minLength": 1 }
35
+ }
36
+ },
37
+ "entry": { "type": "string", "minLength": 1 },
38
+ "files": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
39
+ "themes": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
40
+ "features": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": ["responsive", "darkMode", "rtl"],
44
+ "properties": {
45
+ "responsive": { "const": true },
46
+ "darkMode": { "const": true },
47
+ "rtl": { "const": true }
48
+ }
49
+ }
50
+ }
51
+ }