aeon-ui-engine 1.3.1 → 1.4.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 (153) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/package.json +1 -1
  3. package/packages/cli/README.md +13 -1
  4. package/packages/cli/dist/bin.js +23 -5
  5. package/packages/cli/dist/chunk-5L47SLNV.js +247 -0
  6. package/packages/cli/dist/index.d.ts +17 -1
  7. package/packages/cli/dist/index.js +5 -3
  8. package/packages/cli/src/bin.ts +21 -3
  9. package/packages/cli/src/index.ts +1 -0
  10. package/packages/cli/src/migrate.ts +170 -0
  11. package/packages/core/dist/index.d.ts +35 -7
  12. package/packages/core/dist/index.js +102 -2
  13. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  14. package/packages/core/src/anatomy.ts +39 -3
  15. package/packages/core/src/identicon.test.ts +39 -0
  16. package/packages/core/src/identicon.ts +79 -0
  17. package/packages/core/src/index.ts +1 -0
  18. package/packages/panda/panda.config.ts +4 -0
  19. package/packages/panda/src/recipes/composer.ts +58 -4
  20. package/packages/panda/src/recipes/file-upload.ts +100 -0
  21. package/packages/panda/src/recipes/index.ts +2 -0
  22. package/packages/panda/src/recipes/range-slider.ts +59 -0
  23. package/packages/panda/src/recipes/thread.ts +77 -2
  24. package/packages/panda/styled-system/recipes/aeon-composer.d.ts +1 -1
  25. package/packages/panda/styled-system/recipes/aeon-composer.mjs +12 -0
  26. package/packages/panda/styled-system/recipes/aeon-file-upload.d.ts +31 -0
  27. package/packages/panda/styled-system/recipes/aeon-file-upload.mjs +61 -0
  28. package/packages/panda/styled-system/recipes/aeon-range-slider.d.ts +31 -0
  29. package/packages/panda/styled-system/recipes/aeon-range-slider.mjs +49 -0
  30. package/packages/panda/styled-system/recipes/aeon-thread.d.ts +1 -1
  31. package/packages/panda/styled-system/recipes/aeon-thread.mjs +20 -0
  32. package/packages/panda/styled-system/recipes/index.d.ts +2 -0
  33. package/packages/panda/styled-system/recipes/index.mjs +2 -0
  34. package/packages/panda/styled-system/styles.css +292 -0
  35. package/packages/primitives/dist/chunk-CV4UXXZF.js +44 -0
  36. package/packages/primitives/dist/chunk-IZKQSCYU.js +164 -0
  37. package/packages/primitives/dist/index.d.ts +2 -0
  38. package/packages/primitives/dist/index.js +22 -6
  39. package/packages/primitives/dist/machines/file-upload.d.ts +39 -0
  40. package/packages/primitives/dist/machines/file-upload.js +8 -0
  41. package/packages/primitives/dist/machines/range-slider.d.ts +129 -0
  42. package/packages/primitives/dist/machines/range-slider.js +12 -0
  43. package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  44. package/packages/primitives/src/__tests__/file-upload.test.ts +25 -0
  45. package/packages/primitives/src/__tests__/range-slider.test.ts +39 -0
  46. package/packages/primitives/src/index.ts +2 -0
  47. package/packages/primitives/src/machines/file-upload.ts +55 -0
  48. package/packages/primitives/src/machines/range-slider.ts +209 -0
  49. package/packages/react/dist/index.d.ts +117 -25
  50. package/packages/react/dist/index.js +1731 -1006
  51. package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  52. package/packages/react/src/button/index.tsx +50 -15
  53. package/packages/react/src/combobox/index.tsx +13 -8
  54. package/packages/react/src/composer/index.tsx +58 -3
  55. package/packages/react/src/file-upload/index.tsx +322 -0
  56. package/packages/react/src/index.ts +3 -0
  57. package/packages/react/src/prompt/index.tsx +45 -0
  58. package/packages/react/src/range-slider/index.tsx +322 -0
  59. package/packages/react/src/thread/index.tsx +74 -3
  60. package/packages/react/src/tooltip/index.tsx +48 -22
  61. package/packages/react/src/utils/slot.tsx +47 -0
  62. package/packages/schemas/dist/index.d.ts +73 -24
  63. package/packages/schemas/dist/index.js +514 -50
  64. package/packages/schemas/json/appNav.json +146 -0
  65. package/packages/schemas/json/appShell.json +151 -0
  66. package/packages/schemas/json/avatar.json +130 -0
  67. package/packages/schemas/json/bar.json +126 -0
  68. package/packages/schemas/json/button.json +6 -0
  69. package/packages/schemas/json/catalog.json +3496 -15
  70. package/packages/schemas/json/combobox.json +19 -7
  71. package/packages/schemas/json/composer.json +125 -0
  72. package/packages/schemas/json/content.json +167 -0
  73. package/packages/schemas/json/conversation.json +166 -0
  74. package/packages/schemas/json/entry.json +182 -0
  75. package/packages/schemas/json/fileUpload.json +168 -0
  76. package/packages/schemas/json/identity.json +147 -0
  77. package/packages/schemas/json/listRow.json +131 -0
  78. package/packages/schemas/json/metricStrip.json +132 -0
  79. package/packages/schemas/json/nav.json +136 -0
  80. package/packages/schemas/json/panel.json +143 -0
  81. package/packages/schemas/json/pinInput.json +112 -0
  82. package/packages/schemas/json/profileHeader.json +138 -0
  83. package/packages/schemas/json/prompt.json +155 -0
  84. package/packages/schemas/json/radioGroup.json +130 -0
  85. package/packages/schemas/json/rangeSlider.json +157 -0
  86. package/packages/schemas/json/separator.json +105 -0
  87. package/packages/schemas/json/slider.json +147 -0
  88. package/packages/schemas/json/statusBanner.json +149 -0
  89. package/packages/schemas/json/stickyBar.json +153 -0
  90. package/packages/schemas/json/thread.json +129 -0
  91. package/packages/schemas/json/tooltip.json +21 -7
  92. package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  93. package/packages/schemas/prompts/cursorrules.md +6 -4
  94. package/packages/schemas/prompts/statecharts.md +23 -2
  95. package/packages/schemas/prompts/system.md +2 -2
  96. package/packages/schemas/registry/catalog.json +313 -4
  97. package/packages/schemas/registry/components/appNav.json +146 -0
  98. package/packages/schemas/registry/components/appShell.json +151 -0
  99. package/packages/schemas/registry/components/avatar.json +130 -0
  100. package/packages/schemas/registry/components/bar.json +126 -0
  101. package/packages/schemas/registry/components/button.json +6 -0
  102. package/packages/schemas/registry/components/combobox.json +19 -7
  103. package/packages/schemas/registry/components/composer.json +125 -0
  104. package/packages/schemas/registry/components/content.json +167 -0
  105. package/packages/schemas/registry/components/conversation.json +166 -0
  106. package/packages/schemas/registry/components/entry.json +182 -0
  107. package/packages/schemas/registry/components/fileUpload.json +168 -0
  108. package/packages/schemas/registry/components/identity.json +147 -0
  109. package/packages/schemas/registry/components/listRow.json +131 -0
  110. package/packages/schemas/registry/components/metricStrip.json +132 -0
  111. package/packages/schemas/registry/components/nav.json +136 -0
  112. package/packages/schemas/registry/components/panel.json +143 -0
  113. package/packages/schemas/registry/components/pinInput.json +112 -0
  114. package/packages/schemas/registry/components/profileHeader.json +138 -0
  115. package/packages/schemas/registry/components/prompt.json +155 -0
  116. package/packages/schemas/registry/components/radioGroup.json +130 -0
  117. package/packages/schemas/registry/components/rangeSlider.json +157 -0
  118. package/packages/schemas/registry/components/separator.json +105 -0
  119. package/packages/schemas/registry/components/slider.json +147 -0
  120. package/packages/schemas/registry/components/statusBanner.json +149 -0
  121. package/packages/schemas/registry/components/stickyBar.json +153 -0
  122. package/packages/schemas/registry/components/thread.json +129 -0
  123. package/packages/schemas/registry/components/tooltip.json +21 -7
  124. package/packages/schemas/registry/machines/accordion.json +58 -0
  125. package/packages/schemas/registry/machines/appNav.json +108 -0
  126. package/packages/schemas/registry/machines/appShell.json +128 -0
  127. package/packages/schemas/registry/machines/contentRegion.json +314 -0
  128. package/packages/schemas/registry/machines/fileUpload.json +182 -0
  129. package/packages/schemas/registry/machines/panel.json +131 -0
  130. package/packages/schemas/registry/machines/popover.json +143 -0
  131. package/packages/schemas/registry/machines/rangeSlider.json +201 -0
  132. package/packages/schemas/registry/machines/slider.json +151 -0
  133. package/packages/schemas/registry/machines/stickyBar.json +261 -0
  134. package/packages/schemas/registry/machines/tabs.json +58 -0
  135. package/packages/schemas/registry/machines/toast.json +89 -0
  136. package/packages/schemas/registry/machines/toggle.json +134 -0
  137. package/packages/schemas/scripts/emit.ts +27 -0
  138. package/packages/schemas/src/components.ts +499 -6
  139. package/packages/schemas/src/index.test.ts +83 -0
  140. package/packages/schemas/src/index.ts +1 -0
  141. package/packages/schemas/src/machine-id.ts +7 -0
  142. package/packages/schemas/src/prompts.ts +25 -4
  143. package/packages/schemas/src/registry.ts +28 -39
  144. package/packages/schemas/tsconfig.json +1 -1
  145. package/packages/ui/dist/index.d.ts +100 -46
  146. package/packages/ui/dist/index.js +463 -341
  147. package/packages/ui/src/composer.tsx +37 -11
  148. package/packages/ui/src/file-upload.tsx +36 -0
  149. package/packages/ui/src/index.ts +2 -0
  150. package/packages/ui/src/prompt.tsx +3 -0
  151. package/packages/ui/src/range-slider.tsx +24 -0
  152. package/packages/ui/src/thread.tsx +40 -0
  153. package/packages/cli/dist/chunk-IW5NT2Z2.js +0 -103
@@ -2,10 +2,23 @@ import { mkdirSync, writeFileSync, cpSync, existsSync } from 'node:fs'
2
2
  import { dirname, join } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import {
5
+ accordionMachine,
6
+ appNavMachine,
7
+ appShellMachine,
5
8
  asyncMachine,
6
9
  buttonLifecycleMachine,
10
+ contentRegionMachine,
7
11
  dialogMachine,
8
12
  fieldMachine,
13
+ panelMachine,
14
+ popoverMachine,
15
+ sliderMachine,
16
+ rangeSliderMachine,
17
+ fileUploadMachine,
18
+ stickyBarMachine,
19
+ tabsMachine,
20
+ toastMachine,
21
+ toggleMachine,
9
22
  } from '@aeon-ui/primitives'
10
23
  import { catalog, componentSchemas } from '../src/components.js'
11
24
  import { AEON_SYSTEM_PROMPT, CURSOR_RULES_SNIPPET, STATECHART_PROMPTS } from '../src/prompts.js'
@@ -30,11 +43,25 @@ for (const [id, schema] of Object.entries(componentSchemas)) {
30
43
  writeFileSync(join(registryDir, 'components', `${id}.json`), `${JSON.stringify(schema, null, 2)}\n`)
31
44
  }
32
45
 
46
+ /** Every primitives machine agents may fetch for validEvents / state trees. */
33
47
  const machines = {
34
48
  async: asyncMachine,
35
49
  buttonLifecycle: buttonLifecycleMachine,
36
50
  dialog: dialogMachine,
37
51
  field: fieldMachine,
52
+ popover: popoverMachine,
53
+ toggle: toggleMachine,
54
+ tabs: tabsMachine,
55
+ accordion: accordionMachine,
56
+ toast: toastMachine,
57
+ slider: sliderMachine,
58
+ rangeSlider: rangeSliderMachine,
59
+ fileUpload: fileUploadMachine,
60
+ panel: panelMachine,
61
+ appShell: appShellMachine,
62
+ appNav: appNavMachine,
63
+ stickyBar: stickyBarMachine,
64
+ contentRegion: contentRegionMachine,
38
65
  } as const
39
66
 
40
67
  for (const [id, machine] of Object.entries(machines)) {
@@ -21,10 +21,15 @@ export const componentSchemas = {
21
21
  variant: { type: 'string', enum: ['solid', 'outline', 'ghost', 'link'] },
22
22
  size: { type: 'string', enum: ['sm', 'md', 'lg'] },
23
23
  disabled: { type: 'boolean' },
24
+ asChild: {
25
+ type: 'boolean',
26
+ description: 'Merge button attrs onto a single child element (Link, a).',
27
+ },
24
28
  },
25
29
  slots: { Root: 'button.root', Group: 'button.group', Label: 'button.label', Icon: 'button.icon' },
26
30
  examples: [
27
31
  '<Button.Root status="pending" aria-busy>Saving…</Button.Root>',
32
+ '<Button.Root asChild variant="solid"><a href="/upload">Upload</a></Button.Root>',
28
33
  "const [snap, send] = useAeonMachine(buttonLifecycleMachine)\n<Button.Root status={snap.value as ButtonStatus} onClick={() => send({ type: 'PRESS' })} />",
29
34
  ],
30
35
  }),
@@ -222,24 +227,28 @@ export const componentSchemas = {
222
227
  combobox: componentSchema({
223
228
  id: 'https://aeon-ui.com/schemas/combobox.json',
224
229
  title: 'Combobox',
225
- description: 'Typeahead — React useState for open/query/highlight today. Do not add a duplicate floating machine without COMPONENT_CHECKLIST.',
230
+ description:
231
+ 'Open/closed via popoverMachine (same family as Select/Menu/Popover). Query, highlight, and item registry stay React-local for typeahead.',
226
232
  scope: 'combobox',
227
233
  parts: ['root', 'input', 'icon', 'content', 'item', 'empty'],
228
234
  states: ['open', 'closed'],
229
- machine: null,
230
- runtime: 'useState',
235
+ machine: 'popoverMachine',
236
+ runtime: 'useAeonMachine',
237
+ events: ['OPEN', 'CLOSE', 'TOGGLE', 'ESCAPE', 'POINTER_DOWN_OUTSIDE'],
231
238
  slots: { Root: 'combobox.root', Input: 'combobox.input', Content: 'combobox.content', Item: 'combobox.item' },
232
239
  }),
233
240
 
234
241
  tooltip: componentSchema({
235
242
  id: 'https://aeon-ui.com/schemas/tooltip.json',
236
243
  title: 'Tooltip',
237
- description: 'Open via React useState. Touch tap toggles.',
244
+ description:
245
+ 'Open/closed via popoverMachine (same family as Popover/Select/Combobox). openDelay / closeDelay / touchDuration stay React-local.',
238
246
  scope: 'tooltip',
239
247
  parts: ['root', 'trigger', 'positioner', 'content', 'arrow'],
240
248
  states: ['open', 'closed'],
241
- machine: null,
242
- runtime: 'useState',
249
+ machine: 'popoverMachine',
250
+ runtime: 'useAeonMachine',
251
+ events: ['OPEN', 'CLOSE', 'TOGGLE', 'SET_OPEN', 'ESCAPE', 'POINTER_DOWN_OUTSIDE'],
243
252
  slots: { Root: 'tooltip.root', Trigger: 'tooltip.trigger', Content: 'tooltip.content' },
244
253
  }),
245
254
 
@@ -289,6 +298,490 @@ export const componentSchemas = {
289
298
  },
290
299
  slots: { Root: 'progress.root', Track: 'progress.track', Range: 'progress.range', Label: 'progress.label' },
291
300
  }),
301
+
302
+ radioGroup: componentSchema({
303
+ id: 'https://aeon-ui.com/schemas/radioGroup.json',
304
+ title: 'RadioGroup',
305
+ description: 'Single selection via tabsMachine (same chart family as Tabs/Nav).',
306
+ scope: 'radioGroup',
307
+ parts: ['root', 'item', 'itemControl', 'itemIndicator', 'itemLabel'],
308
+ states: ['selected'],
309
+ machine: 'tabsMachine',
310
+ runtime: 'useAeonMachine',
311
+ events: ['SELECT'],
312
+ slots: {
313
+ Root: 'radioGroup.root',
314
+ Item: 'radioGroup.item',
315
+ ItemControl: 'radioGroup.itemControl',
316
+ ItemLabel: 'radioGroup.itemLabel',
317
+ },
318
+ }),
319
+
320
+ slider: componentSchema({
321
+ id: 'https://aeon-ui.com/schemas/slider.json',
322
+ title: 'Slider',
323
+ description: 'Range control via sliderMachine — idle | dragging plus numeric value in context.',
324
+ scope: 'slider',
325
+ parts: ['root', 'track', 'range', 'thumb', 'valueText'],
326
+ states: ['idle', 'dragging'],
327
+ machine: 'sliderMachine',
328
+ runtime: 'useAeonMachine',
329
+ events: ['SET_VALUE', 'STEP', 'POINTER_DOWN', 'POINTER_UP', 'HOME', 'END'],
330
+ slots: {
331
+ Root: 'slider.root',
332
+ Track: 'slider.track',
333
+ Range: 'slider.range',
334
+ Thumb: 'slider.thumb',
335
+ ValueText: 'slider.valueText',
336
+ },
337
+ }),
338
+
339
+ rangeSlider: componentSchema({
340
+ id: 'https://aeon-ui.com/schemas/rangeSlider.json',
341
+ title: 'RangeSlider',
342
+ description:
343
+ 'Two-thumb range via rangeSliderMachine — idle | dragging with values pair and minStepsBetween gap.',
344
+ scope: 'rangeSlider',
345
+ parts: ['root', 'track', 'range', 'thumb', 'valueText'],
346
+ states: ['idle', 'dragging'],
347
+ machine: 'rangeSliderMachine',
348
+ runtime: 'useAeonMachine',
349
+ events: [
350
+ 'SET_VALUES',
351
+ 'SET_BOUNDS',
352
+ 'SET_DISABLED',
353
+ 'POINTER_DOWN',
354
+ 'POINTER_MOVE',
355
+ 'POINTER_UP',
356
+ 'STEP',
357
+ 'HOME',
358
+ 'END',
359
+ ],
360
+ slots: {
361
+ Root: 'rangeSlider.root',
362
+ Track: 'rangeSlider.track',
363
+ Range: 'rangeSlider.range',
364
+ Thumb: 'rangeSlider.thumb',
365
+ ValueText: 'rangeSlider.valueText',
366
+ },
367
+ examples: [
368
+ '<RangeSlider.Root defaultValue={[20, 80]} minStepsBetween={1}>\n <RangeSlider.Track /><RangeSlider.Range />\n <RangeSlider.Thumb index={0} /><RangeSlider.Thumb index={1} />\n</RangeSlider.Root>',
369
+ ],
370
+ }),
371
+
372
+ fileUpload: componentSchema({
373
+ id: 'https://aeon-ui.com/schemas/fileUpload.json',
374
+ title: 'FileUpload',
375
+ description:
376
+ 'Drag-drop surface via fileUploadMachine — idle | dragging | rejected. File bytes stay in React callbacks.',
377
+ scope: 'fileUpload',
378
+ parts: [
379
+ 'root',
380
+ 'dropzone',
381
+ 'input',
382
+ 'label',
383
+ 'hint',
384
+ 'preview',
385
+ 'clearTrigger',
386
+ 'rejectionMessage',
387
+ ],
388
+ states: ['idle', 'dragging', 'rejected'],
389
+ machine: 'fileUploadMachine',
390
+ runtime: 'useAeonMachine',
391
+ events: ['DRAG_OVER', 'DRAG_LEAVE', 'DROP_ACCEPT', 'DROP_REJECT', 'RESET'],
392
+ slots: {
393
+ Root: 'fileUpload.root',
394
+ Dropzone: 'fileUpload.dropzone',
395
+ Input: 'fileUpload.input',
396
+ Label: 'fileUpload.label',
397
+ Hint: 'fileUpload.hint',
398
+ Preview: 'fileUpload.preview',
399
+ ClearTrigger: 'fileUpload.clearTrigger',
400
+ RejectionMessage: 'fileUpload.rejectionMessage',
401
+ },
402
+ }),
403
+
404
+ pinInput: componentSchema({
405
+ id: 'https://aeon-ui.com/schemas/pinInput.json',
406
+ title: 'PinInput',
407
+ description: 'OTP / PIN cells — React state for digit buffer; projects idle | complete on root.',
408
+ scope: 'pinInput',
409
+ parts: ['root', 'input'],
410
+ states: ['idle', 'complete'],
411
+ machine: null,
412
+ runtime: 'useState',
413
+ slots: { Root: 'pinInput.root', Input: 'pinInput.input' },
414
+ }),
415
+
416
+ avatar: componentSchema({
417
+ id: 'https://aeon-ui.com/schemas/avatar.json',
418
+ title: 'Avatar',
419
+ description: 'Image load lifecycle in React; badge presence is a prop projection.',
420
+ scope: 'avatar',
421
+ parts: ['root', 'image', 'fallback', 'badge'],
422
+ states: ['idle', 'loading', 'loaded', 'error'],
423
+ machine: null,
424
+ runtime: 'useState',
425
+ slots: {
426
+ Root: 'avatar.root',
427
+ Image: 'avatar.image',
428
+ Fallback: 'avatar.fallback',
429
+ Badge: 'avatar.badge',
430
+ },
431
+ }),
432
+
433
+ content: componentSchema({
434
+ id: 'https://aeon-ui.com/schemas/content.json',
435
+ title: 'Content',
436
+ description:
437
+ 'Status-slotted body via contentRegionMachine (or Root state prop projection). Pair with Async for fetch.',
438
+ scope: 'content',
439
+ parts: ['root', 'toolbar', 'body', 'pending', 'empty', 'error', 'success', 'sentinel'],
440
+ states: ['idle', 'pending', 'empty', 'error', 'ready', 'loadingMore', 'success'],
441
+ machine: 'contentRegionMachine',
442
+ runtime: 'useAeonMachine',
443
+ events: ['LOAD', 'RESOLVE', 'REJECT', 'RETRY', 'LOAD_MORE', 'MORE_DONE', 'SUCCEED', 'RESET'],
444
+ slots: {
445
+ Root: 'content.root',
446
+ Pending: 'content.pending',
447
+ Empty: 'content.empty',
448
+ Error: 'content.error',
449
+ Success: 'content.success',
450
+ },
451
+ }),
452
+
453
+ panel: componentSchema({
454
+ id: 'https://aeon-ui.com/schemas/panel.json',
455
+ title: 'Panel',
456
+ description: 'Collapsible layout region via panelMachine — expanded | collapsed.',
457
+ scope: 'panel',
458
+ parts: ['group', 'root', 'trigger', 'label', 'content'],
459
+ states: ['expanded', 'collapsed'],
460
+ machine: 'panelMachine',
461
+ runtime: 'useAeonMachine',
462
+ events: ['EXPAND', 'COLLAPSE', 'TOGGLE', 'SET_EXPANDED'],
463
+ slots: {
464
+ Group: 'panel.group',
465
+ Root: 'panel.root',
466
+ Trigger: 'panel.trigger',
467
+ Label: 'panel.label',
468
+ Content: 'panel.content',
469
+ },
470
+ }),
471
+
472
+ appShell: componentSchema({
473
+ id: 'https://aeon-ui.com/schemas/appShell.json',
474
+ title: 'AppShell',
475
+ description: 'Application chrome frame via appShellMachine — idle | overlayOpen (+ dockVisible).',
476
+ scope: 'appShell',
477
+ parts: ['root', 'header', 'subheader', 'content', 'aside', 'footer', 'dock', 'scrim'],
478
+ states: ['idle', 'overlayOpen'],
479
+ machine: 'appShellMachine',
480
+ runtime: 'useAeonMachine',
481
+ events: ['OPEN_OVERLAY', 'CLOSE_OVERLAY', 'SHOW_DOCK', 'HIDE_DOCK', 'SET_DOCK'],
482
+ slots: {
483
+ Root: 'appShell.root',
484
+ Header: 'appShell.header',
485
+ Content: 'appShell.content',
486
+ Dock: 'appShell.dock',
487
+ Scrim: 'appShell.scrim',
488
+ },
489
+ }),
490
+
491
+ stickyBar: componentSchema({
492
+ id: 'https://aeon-ui.com/schemas/stickyBar.json',
493
+ title: 'StickyBar',
494
+ description:
495
+ 'Sticky/fixed chrome band via stickyBarMachine. DOM scope is bar (reuses bar anatomy zones).',
496
+ scope: 'bar',
497
+ parts: ['root', 'leading', 'center', 'trailing', 'seam'],
498
+ states: ['floating', 'docked', 'collapsed', 'hidden'],
499
+ machine: 'stickyBarMachine',
500
+ runtime: 'useAeonMachine',
501
+ events: ['DOCK', 'FLOAT', 'COLLAPSE', 'EXPAND', 'HIDE', 'SHOW', 'SET_OFFSET'],
502
+ slots: {
503
+ Root: 'bar.root',
504
+ Leading: 'bar.leading',
505
+ Center: 'bar.center',
506
+ Trailing: 'bar.trailing',
507
+ Seam: 'bar.seam',
508
+ },
509
+ }),
510
+
511
+ bar: componentSchema({
512
+ id: 'https://aeon-ui.com/schemas/bar.json',
513
+ title: 'Bar',
514
+ description: 'Three-zone toolbar layout (leading / center / trailing / seam). Sticky lifecycle uses StickyBar + stickyBarMachine.',
515
+ scope: 'bar',
516
+ parts: ['root', 'leading', 'center', 'trailing', 'seam'],
517
+ states: ['idle'],
518
+ machine: null,
519
+ runtime: 'layout',
520
+ slots: {
521
+ Root: 'bar.root',
522
+ Leading: 'bar.leading',
523
+ Center: 'bar.center',
524
+ Trailing: 'bar.trailing',
525
+ },
526
+ }),
527
+
528
+ nav: componentSchema({
529
+ id: 'https://aeon-ui.com/schemas/nav.json',
530
+ title: 'Nav',
531
+ description: 'Navigational collection via tabsMachine; items project inactive | active | disabled.',
532
+ scope: 'nav',
533
+ parts: ['root', 'item', 'indicator', 'label', 'icon', 'badge'],
534
+ states: ['inactive', 'active', 'disabled'],
535
+ machine: 'tabsMachine',
536
+ runtime: 'useAeonMachine',
537
+ events: ['SELECT'],
538
+ slots: { Root: 'nav.root', Item: 'nav.item', Label: 'nav.label', Icon: 'nav.icon' },
539
+ }),
540
+
541
+ appNav: componentSchema({
542
+ id: 'https://aeon-ui.com/schemas/appNav.json',
543
+ title: 'AppNav',
544
+ description: 'Section + detail stack via appNavMachine — list→detail, settings drill-in.',
545
+ scope: 'appNav',
546
+ parts: ['root', 'rail', 'section', 'stage', 'breadcrumb', 'crumb', 'back'],
547
+ states: ['section'],
548
+ machine: 'appNavMachine',
549
+ runtime: 'useAeonMachine',
550
+ events: ['SET_SECTION', 'PUSH', 'REPLACE', 'POP', 'POP_TO', 'CLEAR', 'RESET'],
551
+ slots: {
552
+ Root: 'appNav.root',
553
+ Rail: 'appNav.rail',
554
+ Stage: 'appNav.stage',
555
+ Back: 'appNav.back',
556
+ },
557
+ }),
558
+
559
+ prompt: componentSchema({
560
+ id: 'https://aeon-ui.com/schemas/prompt.json',
561
+ title: 'Prompt',
562
+ description:
563
+ 'Confirm / permission dialog composed on Dialog. Open/closed via dialogMachine; face via status prop.',
564
+ scope: 'prompt',
565
+ parts: ['root', 'eyebrow', 'meta', 'amount', 'actions', 'primary', 'secondary'],
566
+ states: ['pending', 'confirming', 'dismissed', 'confirmed'],
567
+ machine: 'dialogMachine',
568
+ runtime: 'status prop',
569
+ events: ['OPEN', 'CLOSE', 'TOGGLE'],
570
+ props: {
571
+ status: {
572
+ type: 'string',
573
+ enum: ['pending', 'confirming', 'dismissed', 'confirmed'],
574
+ default: 'pending',
575
+ },
576
+ },
577
+ slots: {
578
+ Root: 'prompt.root',
579
+ Primary: 'prompt.primary',
580
+ Secondary: 'prompt.secondary',
581
+ Actions: 'prompt.actions',
582
+ },
583
+ }),
584
+
585
+ statusBanner: componentSchema({
586
+ id: 'https://aeon-ui.com/schemas/statusBanner.json',
587
+ title: 'StatusBanner',
588
+ description: 'Non-blocking top-of-app notice — tone + optional status props project to data-aeon-state.',
589
+ scope: 'statusBanner',
590
+ parts: ['root', 'copy', 'title', 'body', 'actions'],
591
+ states: ['info', 'success', 'warning', 'danger', 'neutral'],
592
+ machine: null,
593
+ runtime: 'props',
594
+ props: {
595
+ tone: { type: 'string', enum: ['info', 'success', 'warning', 'danger', 'neutral'] },
596
+ status: { type: 'string', description: 'Optional secondary token appended to data-aeon-state' },
597
+ },
598
+ slots: {
599
+ Root: 'statusBanner.root',
600
+ Title: 'statusBanner.title',
601
+ Body: 'statusBanner.body',
602
+ Actions: 'statusBanner.actions',
603
+ },
604
+ }),
605
+
606
+ separator: componentSchema({
607
+ id: 'https://aeon-ui.com/schemas/separator.json',
608
+ title: 'Separator',
609
+ description: 'Static divider — orientation prop only.',
610
+ scope: 'separator',
611
+ parts: ['root'],
612
+ states: ['horizontal', 'vertical'],
613
+ machine: null,
614
+ runtime: 'props',
615
+ slots: { Root: 'separator.root' },
616
+ }),
617
+
618
+ composer: componentSchema({
619
+ id: 'https://aeon-ui.com/schemas/composer.json',
620
+ title: 'Composer',
621
+ description: 'Compose chrome — root state prop idle | sending | disabled | error.',
622
+ scope: 'composer',
623
+ parts: ['root', 'input', 'actions', 'send'],
624
+ states: ['idle', 'sending', 'disabled', 'error'],
625
+ machine: null,
626
+ runtime: 'props',
627
+ slots: { Root: 'composer.root', Input: 'composer.input', Send: 'composer.send' },
628
+ }),
629
+
630
+ thread: componentSchema({
631
+ id: 'https://aeon-ui.com/schemas/thread.json',
632
+ title: 'Thread',
633
+ description: 'Message stream — item state prop mine | theirs | pending | failed.',
634
+ scope: 'thread',
635
+ parts: ['root', 'list', 'item', 'bubble', 'meta', 'day'],
636
+ states: ['mine', 'theirs', 'pending', 'failed'],
637
+ machine: null,
638
+ runtime: 'props',
639
+ slots: { Root: 'thread.root', Item: 'thread.item', Bubble: 'thread.bubble' },
640
+ }),
641
+
642
+ identity: componentSchema({
643
+ id: 'https://aeon-ui.com/schemas/identity.json',
644
+ title: 'Identity',
645
+ description: 'Avatar + title + subtitle strip — presentational layout, not a niche product scope.',
646
+ scope: 'identity',
647
+ parts: ['root', 'avatar', 'title', 'subtitle', 'meta', 'trailing'],
648
+ states: ['idle'],
649
+ machine: null,
650
+ runtime: 'layout',
651
+ props: {
652
+ size: { type: 'string', enum: ['sm', 'md', 'lg'] },
653
+ },
654
+ slots: {
655
+ Root: 'identity.root',
656
+ Avatar: 'identity.avatar',
657
+ Title: 'identity.title',
658
+ Subtitle: 'identity.subtitle',
659
+ Meta: 'identity.meta',
660
+ Trailing: 'identity.trailing',
661
+ },
662
+ }),
663
+
664
+ profileHeader: componentSchema({
665
+ id: 'https://aeon-ui.com/schemas/profileHeader.json',
666
+ title: 'ProfileHeader',
667
+ description: 'Dense account header — identity · metrics · actions. Universal people chrome.',
668
+ scope: 'profileHeader',
669
+ parts: ['root', 'media', 'identity', 'metrics', 'actions', 'body'],
670
+ states: ['idle'],
671
+ machine: null,
672
+ runtime: 'layout',
673
+ slots: {
674
+ Root: 'profileHeader.root',
675
+ Media: 'profileHeader.media',
676
+ Identity: 'profileHeader.identity',
677
+ Metrics: 'profileHeader.metrics',
678
+ Actions: 'profileHeader.actions',
679
+ Body: 'profileHeader.body',
680
+ },
681
+ }),
682
+
683
+ metricStrip: componentSchema({
684
+ id: 'https://aeon-ui.com/schemas/metricStrip.json',
685
+ title: 'MetricStrip',
686
+ description: 'Dense value|label chips — cluster or loose density. Generic stats only.',
687
+ scope: 'metricStrip',
688
+ parts: ['root', 'chip', 'value', 'label'],
689
+ states: ['idle'],
690
+ machine: null,
691
+ runtime: 'layout',
692
+ props: {
693
+ density: { type: 'string', enum: ['cluster', 'loose'] },
694
+ },
695
+ slots: {
696
+ Root: 'metricStrip.root',
697
+ Chip: 'metricStrip.chip',
698
+ Value: 'metricStrip.value',
699
+ Label: 'metricStrip.label',
700
+ },
701
+ }),
702
+
703
+ listRow: componentSchema({
704
+ id: 'https://aeon-ui.com/schemas/listRow.json',
705
+ title: 'ListRow',
706
+ description: 'Settings / menu row — leading · label · trailing. Layout only.',
707
+ scope: 'listRow',
708
+ parts: ['root', 'leading', 'label', 'description', 'trailing'],
709
+ states: ['idle'],
710
+ machine: null,
711
+ runtime: 'layout',
712
+ slots: {
713
+ Root: 'listRow.root',
714
+ Leading: 'listRow.leading',
715
+ Label: 'listRow.label',
716
+ Description: 'listRow.description',
717
+ Trailing: 'listRow.trailing',
718
+ },
719
+ }),
720
+
721
+ entry: componentSchema({
722
+ id: 'https://aeon-ui.com/schemas/entry.json',
723
+ title: 'Entry',
724
+ description:
725
+ 'Universal multi-value card (feed stack or listing split). Root state idle | selected | muted — never invent niche scopes.',
726
+ scope: 'entry',
727
+ parts: [
728
+ 'list',
729
+ 'root',
730
+ 'header',
731
+ 'leading',
732
+ 'heading',
733
+ 'title',
734
+ 'subtitle',
735
+ 'meta',
736
+ 'media',
737
+ 'body',
738
+ 'values',
739
+ 'value',
740
+ 'actions',
741
+ 'footer',
742
+ ],
743
+ states: ['idle', 'selected', 'muted'],
744
+ machine: null,
745
+ runtime: 'props',
746
+ props: {
747
+ state: { type: 'string', enum: ['idle', 'selected', 'muted'], default: 'idle' },
748
+ density: { type: 'string', enum: ['compact', 'cozy'] },
749
+ layout: { type: 'string', enum: ['stack', 'split'] },
750
+ },
751
+ slots: {
752
+ List: 'entry.list',
753
+ Root: 'entry.root',
754
+ Header: 'entry.header',
755
+ Body: 'entry.body',
756
+ Actions: 'entry.actions',
757
+ Footer: 'entry.footer',
758
+ },
759
+ }),
760
+
761
+ conversation: componentSchema({
762
+ id: 'https://aeon-ui.com/schemas/conversation.json',
763
+ title: 'Conversation',
764
+ description:
765
+ 'Inbox / DM list — item state prop idle | unread | selected. Universal messaging chrome.',
766
+ scope: 'conversation',
767
+ parts: ['root', 'item', 'leading', 'body', 'title', 'preview', 'meta', 'badge'],
768
+ states: ['idle', 'unread', 'selected'],
769
+ machine: null,
770
+ runtime: 'props',
771
+ props: {
772
+ state: { type: 'string', enum: ['idle', 'unread', 'selected'], default: 'idle' },
773
+ },
774
+ slots: {
775
+ Root: 'conversation.root',
776
+ Item: 'conversation.item',
777
+ Leading: 'conversation.leading',
778
+ Body: 'conversation.body',
779
+ Title: 'conversation.title',
780
+ Preview: 'conversation.preview',
781
+ Meta: 'conversation.meta',
782
+ Badge: 'conversation.badge',
783
+ },
784
+ }),
292
785
  } as const satisfies Record<string, JsonSchema>
293
786
 
294
787
  export type ComponentSchemaId = keyof typeof componentSchemas
@@ -1,6 +1,44 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { catalog, componentSchemas } from './components.js'
3
+ import { machineRegistryId } from './machine-id.js'
3
4
  import { AEON_SYSTEM_PROMPT } from './prompts.js'
5
+ import { registryCatalog } from './registry.js'
6
+
7
+ /** Machine-backed COMPONENTS / MACHINES rows that must have a schema entry. */
8
+ const MACHINE_BACKED_SCHEMA_IDS = [
9
+ 'async',
10
+ 'field',
11
+ 'button',
12
+ 'switch',
13
+ 'checkbox',
14
+ 'dialog',
15
+ 'tabs',
16
+ 'accordion',
17
+ 'menu',
18
+ 'popover',
19
+ 'select',
20
+ 'combobox',
21
+ 'toast',
22
+ 'radioGroup',
23
+ 'slider',
24
+ 'stickyBar',
25
+ 'appShell',
26
+ 'content',
27
+ 'nav',
28
+ 'panel',
29
+ 'appNav',
30
+ 'prompt',
31
+ 'tooltip',
32
+ ] as const
33
+
34
+ const LAYOUT_SCHEMA_IDS = [
35
+ 'identity',
36
+ 'profileHeader',
37
+ 'metricStrip',
38
+ 'listRow',
39
+ 'entry',
40
+ 'conversation',
41
+ ] as const
4
42
 
5
43
  describe('@aeon-ui/schemas', () => {
6
44
  it('exposes a catalog of every component schema', () => {
@@ -22,4 +60,49 @@ describe('@aeon-ui/schemas', () => {
22
60
  expect(AEON_SYSTEM_PROMPT).toContain('Build the state machines first')
23
61
  expect(AEON_SYSTEM_PROMPT).toContain('data-aeon-state')
24
62
  })
63
+
64
+ it('derives registry catalog from componentSchemas (no hand list drift)', () => {
65
+ const schemaIds = Object.keys(componentSchemas).sort()
66
+ const registryIds = registryCatalog.components.map((c) => c.id).sort()
67
+ expect(registryIds).toEqual(schemaIds)
68
+ expect(catalog.components.sort()).toEqual(schemaIds)
69
+ })
70
+
71
+ it('covers machine-backed components in the schema catalog', () => {
72
+ for (const id of MACHINE_BACKED_SCHEMA_IDS) {
73
+ expect(componentSchemas).toHaveProperty(id)
74
+ }
75
+ expect(componentSchemas.combobox['x-aeon']).toMatchObject({
76
+ machine: 'popoverMachine',
77
+ runtime: 'useAeonMachine',
78
+ })
79
+ expect(componentSchemas.tooltip['x-aeon']).toMatchObject({
80
+ machine: 'popoverMachine',
81
+ runtime: 'useAeonMachine',
82
+ })
83
+ })
84
+
85
+ it('covers Instant-critical layout schemas', () => {
86
+ for (const id of LAYOUT_SCHEMA_IDS) {
87
+ expect(componentSchemas).toHaveProperty(id)
88
+ }
89
+ expect(componentSchemas.entry['x-aeon']).toMatchObject({
90
+ scope: 'entry',
91
+ runtime: 'props',
92
+ })
93
+ })
94
+
95
+ it('maps machine export names to registry machine file ids', () => {
96
+ expect(machineRegistryId('popoverMachine')).toBe('popover')
97
+ expect(machineRegistryId('createAsyncMachine')).toBe('async')
98
+ expect(machineRegistryId('buttonLifecycleMachine')).toBe('buttonLifecycle')
99
+ expect(machineRegistryId(null)).toBeUndefined()
100
+ })
101
+
102
+ it('points registry machineUrl at the mapped machine id when present', () => {
103
+ const select = registryCatalog.components.find((c) => c.id === 'select')
104
+ expect(select?.machineUrl).toBe('https://aeon-ui.com/registry/machines/popover.json')
105
+ const scroll = registryCatalog.components.find((c) => c.id === 'scroll')
106
+ expect(scroll?.machineUrl).toBeUndefined()
107
+ })
25
108
  })
@@ -23,3 +23,4 @@ export {
23
23
  type AgentEventBridge,
24
24
  } from './agent-bridge.js'
25
25
  export { registryCatalog, REGISTRY_BASE, type RegistryEntry } from './registry.js'
26
+ export { machineRegistryId } from './machine-id.js'