aeon-ui-engine 1.3.0 → 1.3.5
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/CHANGELOG.md +42 -0
- package/package.json +7 -2
- package/packages/react/src/combobox/index.tsx +13 -8
- package/packages/react/src/tooltip/index.tsx +48 -22
- package/packages/schemas/dist/index.d.ts +69 -24
- package/packages/schemas/dist/index.js +447 -49
- package/packages/schemas/json/appNav.json +146 -0
- package/packages/schemas/json/appShell.json +151 -0
- package/packages/schemas/json/avatar.json +130 -0
- package/packages/schemas/json/bar.json +126 -0
- package/packages/schemas/json/catalog.json +3163 -15
- package/packages/schemas/json/combobox.json +19 -7
- package/packages/schemas/json/composer.json +125 -0
- package/packages/schemas/json/content.json +167 -0
- package/packages/schemas/json/conversation.json +166 -0
- package/packages/schemas/json/entry.json +182 -0
- package/packages/schemas/json/identity.json +147 -0
- package/packages/schemas/json/listRow.json +131 -0
- package/packages/schemas/json/metricStrip.json +132 -0
- package/packages/schemas/json/nav.json +136 -0
- package/packages/schemas/json/panel.json +143 -0
- package/packages/schemas/json/pinInput.json +112 -0
- package/packages/schemas/json/profileHeader.json +138 -0
- package/packages/schemas/json/prompt.json +155 -0
- package/packages/schemas/json/radioGroup.json +130 -0
- package/packages/schemas/json/separator.json +105 -0
- package/packages/schemas/json/slider.json +147 -0
- package/packages/schemas/json/statusBanner.json +149 -0
- package/packages/schemas/json/stickyBar.json +153 -0
- package/packages/schemas/json/thread.json +129 -0
- package/packages/schemas/json/tooltip.json +21 -7
- package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/schemas/prompts/cursorrules.md +6 -4
- package/packages/schemas/prompts/statecharts.md +23 -2
- package/packages/schemas/prompts/system.md +2 -2
- package/packages/schemas/registry/catalog.json +295 -4
- package/packages/schemas/registry/components/appNav.json +146 -0
- package/packages/schemas/registry/components/appShell.json +151 -0
- package/packages/schemas/registry/components/avatar.json +130 -0
- package/packages/schemas/registry/components/bar.json +126 -0
- package/packages/schemas/registry/components/combobox.json +19 -7
- package/packages/schemas/registry/components/composer.json +125 -0
- package/packages/schemas/registry/components/content.json +167 -0
- package/packages/schemas/registry/components/conversation.json +166 -0
- package/packages/schemas/registry/components/entry.json +182 -0
- package/packages/schemas/registry/components/identity.json +147 -0
- package/packages/schemas/registry/components/listRow.json +131 -0
- package/packages/schemas/registry/components/metricStrip.json +132 -0
- package/packages/schemas/registry/components/nav.json +136 -0
- package/packages/schemas/registry/components/panel.json +143 -0
- package/packages/schemas/registry/components/pinInput.json +112 -0
- package/packages/schemas/registry/components/profileHeader.json +138 -0
- package/packages/schemas/registry/components/prompt.json +155 -0
- package/packages/schemas/registry/components/radioGroup.json +130 -0
- package/packages/schemas/registry/components/separator.json +105 -0
- package/packages/schemas/registry/components/slider.json +147 -0
- package/packages/schemas/registry/components/statusBanner.json +149 -0
- package/packages/schemas/registry/components/stickyBar.json +153 -0
- package/packages/schemas/registry/components/thread.json +129 -0
- package/packages/schemas/registry/components/tooltip.json +21 -7
- package/packages/schemas/registry/machines/accordion.json +58 -0
- package/packages/schemas/registry/machines/appNav.json +108 -0
- package/packages/schemas/registry/machines/appShell.json +128 -0
- package/packages/schemas/registry/machines/contentRegion.json +314 -0
- package/packages/schemas/registry/machines/panel.json +131 -0
- package/packages/schemas/registry/machines/popover.json +143 -0
- package/packages/schemas/registry/machines/slider.json +151 -0
- package/packages/schemas/registry/machines/stickyBar.json +261 -0
- package/packages/schemas/registry/machines/tabs.json +58 -0
- package/packages/schemas/registry/machines/toast.json +89 -0
- package/packages/schemas/registry/machines/toggle.json +134 -0
- package/packages/schemas/scripts/emit.ts +23 -0
- package/packages/schemas/src/components.ts +429 -6
- package/packages/schemas/src/index.test.ts +83 -0
- package/packages/schemas/src/index.ts +1 -0
- package/packages/schemas/src/machine-id.ts +7 -0
- package/packages/schemas/src/prompts.ts +25 -4
- package/packages/schemas/src/registry.ts +28 -39
- package/scripts/bump-version.mjs +93 -0
- package/scripts/require-version-bump.mjs +70 -0
|
@@ -2,10 +2,21 @@ 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
|
+
stickyBarMachine,
|
|
17
|
+
tabsMachine,
|
|
18
|
+
toastMachine,
|
|
19
|
+
toggleMachine,
|
|
9
20
|
} from '@aeon-ui/primitives'
|
|
10
21
|
import { catalog, componentSchemas } from '../src/components.js'
|
|
11
22
|
import { AEON_SYSTEM_PROMPT, CURSOR_RULES_SNIPPET, STATECHART_PROMPTS } from '../src/prompts.js'
|
|
@@ -30,11 +41,23 @@ for (const [id, schema] of Object.entries(componentSchemas)) {
|
|
|
30
41
|
writeFileSync(join(registryDir, 'components', `${id}.json`), `${JSON.stringify(schema, null, 2)}\n`)
|
|
31
42
|
}
|
|
32
43
|
|
|
44
|
+
/** Every primitives machine agents may fetch for validEvents / state trees. */
|
|
33
45
|
const machines = {
|
|
34
46
|
async: asyncMachine,
|
|
35
47
|
buttonLifecycle: buttonLifecycleMachine,
|
|
36
48
|
dialog: dialogMachine,
|
|
37
49
|
field: fieldMachine,
|
|
50
|
+
popover: popoverMachine,
|
|
51
|
+
toggle: toggleMachine,
|
|
52
|
+
tabs: tabsMachine,
|
|
53
|
+
accordion: accordionMachine,
|
|
54
|
+
toast: toastMachine,
|
|
55
|
+
slider: sliderMachine,
|
|
56
|
+
panel: panelMachine,
|
|
57
|
+
appShell: appShellMachine,
|
|
58
|
+
appNav: appNavMachine,
|
|
59
|
+
stickyBar: stickyBarMachine,
|
|
60
|
+
contentRegion: contentRegionMachine,
|
|
38
61
|
} as const
|
|
39
62
|
|
|
40
63
|
for (const [id, machine] of Object.entries(machines)) {
|
|
@@ -222,24 +222,28 @@ export const componentSchemas = {
|
|
|
222
222
|
combobox: componentSchema({
|
|
223
223
|
id: 'https://aeon-ui.com/schemas/combobox.json',
|
|
224
224
|
title: 'Combobox',
|
|
225
|
-
description:
|
|
225
|
+
description:
|
|
226
|
+
'Open/closed via popoverMachine (same family as Select/Menu/Popover). Query, highlight, and item registry stay React-local for typeahead.',
|
|
226
227
|
scope: 'combobox',
|
|
227
228
|
parts: ['root', 'input', 'icon', 'content', 'item', 'empty'],
|
|
228
229
|
states: ['open', 'closed'],
|
|
229
|
-
machine:
|
|
230
|
-
runtime: '
|
|
230
|
+
machine: 'popoverMachine',
|
|
231
|
+
runtime: 'useAeonMachine',
|
|
232
|
+
events: ['OPEN', 'CLOSE', 'TOGGLE', 'ESCAPE', 'POINTER_DOWN_OUTSIDE'],
|
|
231
233
|
slots: { Root: 'combobox.root', Input: 'combobox.input', Content: 'combobox.content', Item: 'combobox.item' },
|
|
232
234
|
}),
|
|
233
235
|
|
|
234
236
|
tooltip: componentSchema({
|
|
235
237
|
id: 'https://aeon-ui.com/schemas/tooltip.json',
|
|
236
238
|
title: 'Tooltip',
|
|
237
|
-
description:
|
|
239
|
+
description:
|
|
240
|
+
'Open/closed via popoverMachine (same family as Popover/Select/Combobox). openDelay / closeDelay / touchDuration stay React-local.',
|
|
238
241
|
scope: 'tooltip',
|
|
239
242
|
parts: ['root', 'trigger', 'positioner', 'content', 'arrow'],
|
|
240
243
|
states: ['open', 'closed'],
|
|
241
|
-
machine:
|
|
242
|
-
runtime: '
|
|
244
|
+
machine: 'popoverMachine',
|
|
245
|
+
runtime: 'useAeonMachine',
|
|
246
|
+
events: ['OPEN', 'CLOSE', 'TOGGLE', 'SET_OPEN', 'ESCAPE', 'POINTER_DOWN_OUTSIDE'],
|
|
243
247
|
slots: { Root: 'tooltip.root', Trigger: 'tooltip.trigger', Content: 'tooltip.content' },
|
|
244
248
|
}),
|
|
245
249
|
|
|
@@ -289,6 +293,425 @@ export const componentSchemas = {
|
|
|
289
293
|
},
|
|
290
294
|
slots: { Root: 'progress.root', Track: 'progress.track', Range: 'progress.range', Label: 'progress.label' },
|
|
291
295
|
}),
|
|
296
|
+
|
|
297
|
+
radioGroup: componentSchema({
|
|
298
|
+
id: 'https://aeon-ui.com/schemas/radioGroup.json',
|
|
299
|
+
title: 'RadioGroup',
|
|
300
|
+
description: 'Single selection via tabsMachine (same chart family as Tabs/Nav).',
|
|
301
|
+
scope: 'radioGroup',
|
|
302
|
+
parts: ['root', 'item', 'itemControl', 'itemIndicator', 'itemLabel'],
|
|
303
|
+
states: ['selected'],
|
|
304
|
+
machine: 'tabsMachine',
|
|
305
|
+
runtime: 'useAeonMachine',
|
|
306
|
+
events: ['SELECT'],
|
|
307
|
+
slots: {
|
|
308
|
+
Root: 'radioGroup.root',
|
|
309
|
+
Item: 'radioGroup.item',
|
|
310
|
+
ItemControl: 'radioGroup.itemControl',
|
|
311
|
+
ItemLabel: 'radioGroup.itemLabel',
|
|
312
|
+
},
|
|
313
|
+
}),
|
|
314
|
+
|
|
315
|
+
slider: componentSchema({
|
|
316
|
+
id: 'https://aeon-ui.com/schemas/slider.json',
|
|
317
|
+
title: 'Slider',
|
|
318
|
+
description: 'Range control via sliderMachine — idle | dragging plus numeric value in context.',
|
|
319
|
+
scope: 'slider',
|
|
320
|
+
parts: ['root', 'track', 'range', 'thumb', 'valueText'],
|
|
321
|
+
states: ['idle', 'dragging'],
|
|
322
|
+
machine: 'sliderMachine',
|
|
323
|
+
runtime: 'useAeonMachine',
|
|
324
|
+
events: ['SET_VALUE', 'STEP', 'POINTER_DOWN', 'POINTER_UP', 'HOME', 'END'],
|
|
325
|
+
slots: {
|
|
326
|
+
Root: 'slider.root',
|
|
327
|
+
Track: 'slider.track',
|
|
328
|
+
Range: 'slider.range',
|
|
329
|
+
Thumb: 'slider.thumb',
|
|
330
|
+
ValueText: 'slider.valueText',
|
|
331
|
+
},
|
|
332
|
+
}),
|
|
333
|
+
|
|
334
|
+
pinInput: componentSchema({
|
|
335
|
+
id: 'https://aeon-ui.com/schemas/pinInput.json',
|
|
336
|
+
title: 'PinInput',
|
|
337
|
+
description: 'OTP / PIN cells — React state for digit buffer; projects idle | complete on root.',
|
|
338
|
+
scope: 'pinInput',
|
|
339
|
+
parts: ['root', 'input'],
|
|
340
|
+
states: ['idle', 'complete'],
|
|
341
|
+
machine: null,
|
|
342
|
+
runtime: 'useState',
|
|
343
|
+
slots: { Root: 'pinInput.root', Input: 'pinInput.input' },
|
|
344
|
+
}),
|
|
345
|
+
|
|
346
|
+
avatar: componentSchema({
|
|
347
|
+
id: 'https://aeon-ui.com/schemas/avatar.json',
|
|
348
|
+
title: 'Avatar',
|
|
349
|
+
description: 'Image load lifecycle in React; badge presence is a prop projection.',
|
|
350
|
+
scope: 'avatar',
|
|
351
|
+
parts: ['root', 'image', 'fallback', 'badge'],
|
|
352
|
+
states: ['idle', 'loading', 'loaded', 'error'],
|
|
353
|
+
machine: null,
|
|
354
|
+
runtime: 'useState',
|
|
355
|
+
slots: {
|
|
356
|
+
Root: 'avatar.root',
|
|
357
|
+
Image: 'avatar.image',
|
|
358
|
+
Fallback: 'avatar.fallback',
|
|
359
|
+
Badge: 'avatar.badge',
|
|
360
|
+
},
|
|
361
|
+
}),
|
|
362
|
+
|
|
363
|
+
content: componentSchema({
|
|
364
|
+
id: 'https://aeon-ui.com/schemas/content.json',
|
|
365
|
+
title: 'Content',
|
|
366
|
+
description:
|
|
367
|
+
'Status-slotted body via contentRegionMachine (or Root state prop projection). Pair with Async for fetch.',
|
|
368
|
+
scope: 'content',
|
|
369
|
+
parts: ['root', 'toolbar', 'body', 'pending', 'empty', 'error', 'success', 'sentinel'],
|
|
370
|
+
states: ['idle', 'pending', 'empty', 'error', 'ready', 'loadingMore', 'success'],
|
|
371
|
+
machine: 'contentRegionMachine',
|
|
372
|
+
runtime: 'useAeonMachine',
|
|
373
|
+
events: ['LOAD', 'RESOLVE', 'REJECT', 'RETRY', 'LOAD_MORE', 'MORE_DONE', 'SUCCEED', 'RESET'],
|
|
374
|
+
slots: {
|
|
375
|
+
Root: 'content.root',
|
|
376
|
+
Pending: 'content.pending',
|
|
377
|
+
Empty: 'content.empty',
|
|
378
|
+
Error: 'content.error',
|
|
379
|
+
Success: 'content.success',
|
|
380
|
+
},
|
|
381
|
+
}),
|
|
382
|
+
|
|
383
|
+
panel: componentSchema({
|
|
384
|
+
id: 'https://aeon-ui.com/schemas/panel.json',
|
|
385
|
+
title: 'Panel',
|
|
386
|
+
description: 'Collapsible layout region via panelMachine — expanded | collapsed.',
|
|
387
|
+
scope: 'panel',
|
|
388
|
+
parts: ['group', 'root', 'trigger', 'label', 'content'],
|
|
389
|
+
states: ['expanded', 'collapsed'],
|
|
390
|
+
machine: 'panelMachine',
|
|
391
|
+
runtime: 'useAeonMachine',
|
|
392
|
+
events: ['EXPAND', 'COLLAPSE', 'TOGGLE', 'SET_EXPANDED'],
|
|
393
|
+
slots: {
|
|
394
|
+
Group: 'panel.group',
|
|
395
|
+
Root: 'panel.root',
|
|
396
|
+
Trigger: 'panel.trigger',
|
|
397
|
+
Label: 'panel.label',
|
|
398
|
+
Content: 'panel.content',
|
|
399
|
+
},
|
|
400
|
+
}),
|
|
401
|
+
|
|
402
|
+
appShell: componentSchema({
|
|
403
|
+
id: 'https://aeon-ui.com/schemas/appShell.json',
|
|
404
|
+
title: 'AppShell',
|
|
405
|
+
description: 'Application chrome frame via appShellMachine — idle | overlayOpen (+ dockVisible).',
|
|
406
|
+
scope: 'appShell',
|
|
407
|
+
parts: ['root', 'header', 'subheader', 'content', 'aside', 'footer', 'dock', 'scrim'],
|
|
408
|
+
states: ['idle', 'overlayOpen'],
|
|
409
|
+
machine: 'appShellMachine',
|
|
410
|
+
runtime: 'useAeonMachine',
|
|
411
|
+
events: ['OPEN_OVERLAY', 'CLOSE_OVERLAY', 'SHOW_DOCK', 'HIDE_DOCK', 'SET_DOCK'],
|
|
412
|
+
slots: {
|
|
413
|
+
Root: 'appShell.root',
|
|
414
|
+
Header: 'appShell.header',
|
|
415
|
+
Content: 'appShell.content',
|
|
416
|
+
Dock: 'appShell.dock',
|
|
417
|
+
Scrim: 'appShell.scrim',
|
|
418
|
+
},
|
|
419
|
+
}),
|
|
420
|
+
|
|
421
|
+
stickyBar: componentSchema({
|
|
422
|
+
id: 'https://aeon-ui.com/schemas/stickyBar.json',
|
|
423
|
+
title: 'StickyBar',
|
|
424
|
+
description:
|
|
425
|
+
'Sticky/fixed chrome band via stickyBarMachine. DOM scope is bar (reuses bar anatomy zones).',
|
|
426
|
+
scope: 'bar',
|
|
427
|
+
parts: ['root', 'leading', 'center', 'trailing', 'seam'],
|
|
428
|
+
states: ['floating', 'docked', 'collapsed', 'hidden'],
|
|
429
|
+
machine: 'stickyBarMachine',
|
|
430
|
+
runtime: 'useAeonMachine',
|
|
431
|
+
events: ['DOCK', 'FLOAT', 'COLLAPSE', 'EXPAND', 'HIDE', 'SHOW', 'SET_OFFSET'],
|
|
432
|
+
slots: {
|
|
433
|
+
Root: 'bar.root',
|
|
434
|
+
Leading: 'bar.leading',
|
|
435
|
+
Center: 'bar.center',
|
|
436
|
+
Trailing: 'bar.trailing',
|
|
437
|
+
Seam: 'bar.seam',
|
|
438
|
+
},
|
|
439
|
+
}),
|
|
440
|
+
|
|
441
|
+
bar: componentSchema({
|
|
442
|
+
id: 'https://aeon-ui.com/schemas/bar.json',
|
|
443
|
+
title: 'Bar',
|
|
444
|
+
description: 'Three-zone toolbar layout (leading / center / trailing / seam). Sticky lifecycle uses StickyBar + stickyBarMachine.',
|
|
445
|
+
scope: 'bar',
|
|
446
|
+
parts: ['root', 'leading', 'center', 'trailing', 'seam'],
|
|
447
|
+
states: ['idle'],
|
|
448
|
+
machine: null,
|
|
449
|
+
runtime: 'layout',
|
|
450
|
+
slots: {
|
|
451
|
+
Root: 'bar.root',
|
|
452
|
+
Leading: 'bar.leading',
|
|
453
|
+
Center: 'bar.center',
|
|
454
|
+
Trailing: 'bar.trailing',
|
|
455
|
+
},
|
|
456
|
+
}),
|
|
457
|
+
|
|
458
|
+
nav: componentSchema({
|
|
459
|
+
id: 'https://aeon-ui.com/schemas/nav.json',
|
|
460
|
+
title: 'Nav',
|
|
461
|
+
description: 'Navigational collection via tabsMachine; items project inactive | active | disabled.',
|
|
462
|
+
scope: 'nav',
|
|
463
|
+
parts: ['root', 'item', 'indicator', 'label', 'icon', 'badge'],
|
|
464
|
+
states: ['inactive', 'active', 'disabled'],
|
|
465
|
+
machine: 'tabsMachine',
|
|
466
|
+
runtime: 'useAeonMachine',
|
|
467
|
+
events: ['SELECT'],
|
|
468
|
+
slots: { Root: 'nav.root', Item: 'nav.item', Label: 'nav.label', Icon: 'nav.icon' },
|
|
469
|
+
}),
|
|
470
|
+
|
|
471
|
+
appNav: componentSchema({
|
|
472
|
+
id: 'https://aeon-ui.com/schemas/appNav.json',
|
|
473
|
+
title: 'AppNav',
|
|
474
|
+
description: 'Section + detail stack via appNavMachine — list→detail, settings drill-in.',
|
|
475
|
+
scope: 'appNav',
|
|
476
|
+
parts: ['root', 'rail', 'section', 'stage', 'breadcrumb', 'crumb', 'back'],
|
|
477
|
+
states: ['section'],
|
|
478
|
+
machine: 'appNavMachine',
|
|
479
|
+
runtime: 'useAeonMachine',
|
|
480
|
+
events: ['SET_SECTION', 'PUSH', 'REPLACE', 'POP', 'POP_TO', 'CLEAR', 'RESET'],
|
|
481
|
+
slots: {
|
|
482
|
+
Root: 'appNav.root',
|
|
483
|
+
Rail: 'appNav.rail',
|
|
484
|
+
Stage: 'appNav.stage',
|
|
485
|
+
Back: 'appNav.back',
|
|
486
|
+
},
|
|
487
|
+
}),
|
|
488
|
+
|
|
489
|
+
prompt: componentSchema({
|
|
490
|
+
id: 'https://aeon-ui.com/schemas/prompt.json',
|
|
491
|
+
title: 'Prompt',
|
|
492
|
+
description:
|
|
493
|
+
'Confirm / permission dialog composed on Dialog. Open/closed via dialogMachine; face via status prop.',
|
|
494
|
+
scope: 'prompt',
|
|
495
|
+
parts: ['root', 'eyebrow', 'meta', 'amount', 'actions', 'primary', 'secondary'],
|
|
496
|
+
states: ['pending', 'confirming', 'dismissed', 'confirmed'],
|
|
497
|
+
machine: 'dialogMachine',
|
|
498
|
+
runtime: 'status prop',
|
|
499
|
+
events: ['OPEN', 'CLOSE', 'TOGGLE'],
|
|
500
|
+
props: {
|
|
501
|
+
status: {
|
|
502
|
+
type: 'string',
|
|
503
|
+
enum: ['pending', 'confirming', 'dismissed', 'confirmed'],
|
|
504
|
+
default: 'pending',
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
slots: {
|
|
508
|
+
Root: 'prompt.root',
|
|
509
|
+
Primary: 'prompt.primary',
|
|
510
|
+
Secondary: 'prompt.secondary',
|
|
511
|
+
Actions: 'prompt.actions',
|
|
512
|
+
},
|
|
513
|
+
}),
|
|
514
|
+
|
|
515
|
+
statusBanner: componentSchema({
|
|
516
|
+
id: 'https://aeon-ui.com/schemas/statusBanner.json',
|
|
517
|
+
title: 'StatusBanner',
|
|
518
|
+
description: 'Non-blocking top-of-app notice — tone + optional status props project to data-aeon-state.',
|
|
519
|
+
scope: 'statusBanner',
|
|
520
|
+
parts: ['root', 'copy', 'title', 'body', 'actions'],
|
|
521
|
+
states: ['info', 'success', 'warning', 'danger', 'neutral'],
|
|
522
|
+
machine: null,
|
|
523
|
+
runtime: 'props',
|
|
524
|
+
props: {
|
|
525
|
+
tone: { type: 'string', enum: ['info', 'success', 'warning', 'danger', 'neutral'] },
|
|
526
|
+
status: { type: 'string', description: 'Optional secondary token appended to data-aeon-state' },
|
|
527
|
+
},
|
|
528
|
+
slots: {
|
|
529
|
+
Root: 'statusBanner.root',
|
|
530
|
+
Title: 'statusBanner.title',
|
|
531
|
+
Body: 'statusBanner.body',
|
|
532
|
+
Actions: 'statusBanner.actions',
|
|
533
|
+
},
|
|
534
|
+
}),
|
|
535
|
+
|
|
536
|
+
separator: componentSchema({
|
|
537
|
+
id: 'https://aeon-ui.com/schemas/separator.json',
|
|
538
|
+
title: 'Separator',
|
|
539
|
+
description: 'Static divider — orientation prop only.',
|
|
540
|
+
scope: 'separator',
|
|
541
|
+
parts: ['root'],
|
|
542
|
+
states: ['horizontal', 'vertical'],
|
|
543
|
+
machine: null,
|
|
544
|
+
runtime: 'props',
|
|
545
|
+
slots: { Root: 'separator.root' },
|
|
546
|
+
}),
|
|
547
|
+
|
|
548
|
+
composer: componentSchema({
|
|
549
|
+
id: 'https://aeon-ui.com/schemas/composer.json',
|
|
550
|
+
title: 'Composer',
|
|
551
|
+
description: 'Compose chrome — root state prop idle | sending | disabled | error.',
|
|
552
|
+
scope: 'composer',
|
|
553
|
+
parts: ['root', 'input', 'actions', 'send'],
|
|
554
|
+
states: ['idle', 'sending', 'disabled', 'error'],
|
|
555
|
+
machine: null,
|
|
556
|
+
runtime: 'props',
|
|
557
|
+
slots: { Root: 'composer.root', Input: 'composer.input', Send: 'composer.send' },
|
|
558
|
+
}),
|
|
559
|
+
|
|
560
|
+
thread: componentSchema({
|
|
561
|
+
id: 'https://aeon-ui.com/schemas/thread.json',
|
|
562
|
+
title: 'Thread',
|
|
563
|
+
description: 'Message stream — item state prop mine | theirs | pending | failed.',
|
|
564
|
+
scope: 'thread',
|
|
565
|
+
parts: ['root', 'list', 'item', 'bubble', 'meta', 'day'],
|
|
566
|
+
states: ['mine', 'theirs', 'pending', 'failed'],
|
|
567
|
+
machine: null,
|
|
568
|
+
runtime: 'props',
|
|
569
|
+
slots: { Root: 'thread.root', Item: 'thread.item', Bubble: 'thread.bubble' },
|
|
570
|
+
}),
|
|
571
|
+
|
|
572
|
+
identity: componentSchema({
|
|
573
|
+
id: 'https://aeon-ui.com/schemas/identity.json',
|
|
574
|
+
title: 'Identity',
|
|
575
|
+
description: 'Avatar + title + subtitle strip — presentational layout, not a niche product scope.',
|
|
576
|
+
scope: 'identity',
|
|
577
|
+
parts: ['root', 'avatar', 'title', 'subtitle', 'meta', 'trailing'],
|
|
578
|
+
states: ['idle'],
|
|
579
|
+
machine: null,
|
|
580
|
+
runtime: 'layout',
|
|
581
|
+
props: {
|
|
582
|
+
size: { type: 'string', enum: ['sm', 'md', 'lg'] },
|
|
583
|
+
},
|
|
584
|
+
slots: {
|
|
585
|
+
Root: 'identity.root',
|
|
586
|
+
Avatar: 'identity.avatar',
|
|
587
|
+
Title: 'identity.title',
|
|
588
|
+
Subtitle: 'identity.subtitle',
|
|
589
|
+
Meta: 'identity.meta',
|
|
590
|
+
Trailing: 'identity.trailing',
|
|
591
|
+
},
|
|
592
|
+
}),
|
|
593
|
+
|
|
594
|
+
profileHeader: componentSchema({
|
|
595
|
+
id: 'https://aeon-ui.com/schemas/profileHeader.json',
|
|
596
|
+
title: 'ProfileHeader',
|
|
597
|
+
description: 'Dense account header — identity · metrics · actions. Universal people chrome.',
|
|
598
|
+
scope: 'profileHeader',
|
|
599
|
+
parts: ['root', 'media', 'identity', 'metrics', 'actions', 'body'],
|
|
600
|
+
states: ['idle'],
|
|
601
|
+
machine: null,
|
|
602
|
+
runtime: 'layout',
|
|
603
|
+
slots: {
|
|
604
|
+
Root: 'profileHeader.root',
|
|
605
|
+
Media: 'profileHeader.media',
|
|
606
|
+
Identity: 'profileHeader.identity',
|
|
607
|
+
Metrics: 'profileHeader.metrics',
|
|
608
|
+
Actions: 'profileHeader.actions',
|
|
609
|
+
Body: 'profileHeader.body',
|
|
610
|
+
},
|
|
611
|
+
}),
|
|
612
|
+
|
|
613
|
+
metricStrip: componentSchema({
|
|
614
|
+
id: 'https://aeon-ui.com/schemas/metricStrip.json',
|
|
615
|
+
title: 'MetricStrip',
|
|
616
|
+
description: 'Dense value|label chips — cluster or loose density. Generic stats only.',
|
|
617
|
+
scope: 'metricStrip',
|
|
618
|
+
parts: ['root', 'chip', 'value', 'label'],
|
|
619
|
+
states: ['idle'],
|
|
620
|
+
machine: null,
|
|
621
|
+
runtime: 'layout',
|
|
622
|
+
props: {
|
|
623
|
+
density: { type: 'string', enum: ['cluster', 'loose'] },
|
|
624
|
+
},
|
|
625
|
+
slots: {
|
|
626
|
+
Root: 'metricStrip.root',
|
|
627
|
+
Chip: 'metricStrip.chip',
|
|
628
|
+
Value: 'metricStrip.value',
|
|
629
|
+
Label: 'metricStrip.label',
|
|
630
|
+
},
|
|
631
|
+
}),
|
|
632
|
+
|
|
633
|
+
listRow: componentSchema({
|
|
634
|
+
id: 'https://aeon-ui.com/schemas/listRow.json',
|
|
635
|
+
title: 'ListRow',
|
|
636
|
+
description: 'Settings / menu row — leading · label · trailing. Layout only.',
|
|
637
|
+
scope: 'listRow',
|
|
638
|
+
parts: ['root', 'leading', 'label', 'description', 'trailing'],
|
|
639
|
+
states: ['idle'],
|
|
640
|
+
machine: null,
|
|
641
|
+
runtime: 'layout',
|
|
642
|
+
slots: {
|
|
643
|
+
Root: 'listRow.root',
|
|
644
|
+
Leading: 'listRow.leading',
|
|
645
|
+
Label: 'listRow.label',
|
|
646
|
+
Description: 'listRow.description',
|
|
647
|
+
Trailing: 'listRow.trailing',
|
|
648
|
+
},
|
|
649
|
+
}),
|
|
650
|
+
|
|
651
|
+
entry: componentSchema({
|
|
652
|
+
id: 'https://aeon-ui.com/schemas/entry.json',
|
|
653
|
+
title: 'Entry',
|
|
654
|
+
description:
|
|
655
|
+
'Universal multi-value card (feed stack or listing split). Root state idle | selected | muted — never invent niche scopes.',
|
|
656
|
+
scope: 'entry',
|
|
657
|
+
parts: [
|
|
658
|
+
'list',
|
|
659
|
+
'root',
|
|
660
|
+
'header',
|
|
661
|
+
'leading',
|
|
662
|
+
'heading',
|
|
663
|
+
'title',
|
|
664
|
+
'subtitle',
|
|
665
|
+
'meta',
|
|
666
|
+
'media',
|
|
667
|
+
'body',
|
|
668
|
+
'values',
|
|
669
|
+
'value',
|
|
670
|
+
'actions',
|
|
671
|
+
'footer',
|
|
672
|
+
],
|
|
673
|
+
states: ['idle', 'selected', 'muted'],
|
|
674
|
+
machine: null,
|
|
675
|
+
runtime: 'props',
|
|
676
|
+
props: {
|
|
677
|
+
state: { type: 'string', enum: ['idle', 'selected', 'muted'], default: 'idle' },
|
|
678
|
+
density: { type: 'string', enum: ['compact', 'cozy'] },
|
|
679
|
+
layout: { type: 'string', enum: ['stack', 'split'] },
|
|
680
|
+
},
|
|
681
|
+
slots: {
|
|
682
|
+
List: 'entry.list',
|
|
683
|
+
Root: 'entry.root',
|
|
684
|
+
Header: 'entry.header',
|
|
685
|
+
Body: 'entry.body',
|
|
686
|
+
Actions: 'entry.actions',
|
|
687
|
+
Footer: 'entry.footer',
|
|
688
|
+
},
|
|
689
|
+
}),
|
|
690
|
+
|
|
691
|
+
conversation: componentSchema({
|
|
692
|
+
id: 'https://aeon-ui.com/schemas/conversation.json',
|
|
693
|
+
title: 'Conversation',
|
|
694
|
+
description:
|
|
695
|
+
'Inbox / DM list — item state prop idle | unread | selected. Universal messaging chrome.',
|
|
696
|
+
scope: 'conversation',
|
|
697
|
+
parts: ['root', 'item', 'leading', 'body', 'title', 'preview', 'meta', 'badge'],
|
|
698
|
+
states: ['idle', 'unread', 'selected'],
|
|
699
|
+
machine: null,
|
|
700
|
+
runtime: 'props',
|
|
701
|
+
props: {
|
|
702
|
+
state: { type: 'string', enum: ['idle', 'unread', 'selected'], default: 'idle' },
|
|
703
|
+
},
|
|
704
|
+
slots: {
|
|
705
|
+
Root: 'conversation.root',
|
|
706
|
+
Item: 'conversation.item',
|
|
707
|
+
Leading: 'conversation.leading',
|
|
708
|
+
Body: 'conversation.body',
|
|
709
|
+
Title: 'conversation.title',
|
|
710
|
+
Preview: 'conversation.preview',
|
|
711
|
+
Meta: 'conversation.meta',
|
|
712
|
+
Badge: 'conversation.badge',
|
|
713
|
+
},
|
|
714
|
+
}),
|
|
292
715
|
} as const satisfies Record<string, JsonSchema>
|
|
293
716
|
|
|
294
717
|
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
|
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Map component schema `machine` export names → registry/machines/{id}.json ids. */
|
|
2
|
+
export function machineRegistryId(machine: string | null | undefined): string | undefined {
|
|
3
|
+
if (machine == null) return undefined
|
|
4
|
+
if (machine === 'createAsyncMachine') return 'async'
|
|
5
|
+
if (machine.endsWith('Machine')) return machine.slice(0, -'Machine'.length)
|
|
6
|
+
return machine
|
|
7
|
+
}
|
|
@@ -15,8 +15,8 @@ You are building UI with **Aeon UI**, a React component library powered by finit
|
|
|
15
15
|
## Non-negotiable rules
|
|
16
16
|
1. Do not duplicate open/checked/lifecycle in \`useState\` when a machine exists in \`@aeon-ui/primitives\`.
|
|
17
17
|
2. **Button \`status\` is a prop** (\`idle|pending|success|failure|disabled\`). Optional: drive it from \`buttonLifecycleMachine\` via \`useAeonMachine\` in app code.
|
|
18
|
-
3. **Select / Menu / Popover** share \`popoverMachine\` — never fork another open/closed chart.
|
|
19
|
-
4. **
|
|
18
|
+
3. **Select / Menu / Popover / Combobox / Tooltip** share \`popoverMachine\` for open/closed — never fork another open/closed chart. Combobox keeps query/highlight in React; Tooltip keeps openDelay/closeDelay/touchDuration in React.
|
|
19
|
+
4. **PinInput** digit buffer is React-local today — do not invent a pin machine without the component checklist.
|
|
20
20
|
5. **Scroll** uses \`getScrollSnapshot()\` — no XState machine.
|
|
21
21
|
6. If a machine lists N stable states, the UI must represent all N (totality).
|
|
22
22
|
7. Prefer \`@aeon-ui/react\` (headless) or \`@aeon-ui/ui\` (styled). Relative TS imports use \`.js\` extensions inside the Aeon monorepo only.
|
|
@@ -58,13 +58,34 @@ Rule: Pending must set aria-busy. Do not use disabled alone to mean "saving".`,
|
|
|
58
58
|
States: open | closed. Events: OPEN, CLOSE, TOGGLE.
|
|
59
59
|
Rule: Backdrop click and Escape are CLOSE transitions. Title/Description are required for a11y when open.`,
|
|
60
60
|
|
|
61
|
-
popoverFamily: `## Popover family (Popover, Menu, Select open state)
|
|
61
|
+
popoverFamily: `## Popover family (Popover, Menu, Select, Combobox, Tooltip open state)
|
|
62
62
|
Machine: popoverMachine — states open | closed.
|
|
63
|
-
Rule: Reuse this chart. Never create selectOpenMachine / menuOpenMachine forks
|
|
63
|
+
Rule: Reuse this chart. Never create selectOpenMachine / menuOpenMachine / comboboxOpenMachine / tooltipOpenMachine forks.
|
|
64
|
+
Combobox: query + highlight stay React-local; open/closed must still be popoverMachine.
|
|
65
|
+
Tooltip: openDelay / closeDelay / touchDuration stay React-local; open/closed must still be popoverMachine.`,
|
|
64
66
|
|
|
65
67
|
toggle: `## Switch / Checkbox
|
|
66
68
|
Machine: toggleMachine — checked | unchecked. Events: TOGGLE, CHECK, UNCHECK.
|
|
67
69
|
Rule: Hidden input stays in sync with snapshot for forms.`,
|
|
70
|
+
|
|
71
|
+
slider: `## Slider statechart
|
|
72
|
+
Machine: sliderMachine — idle | dragging; value/min/max/step in context.
|
|
73
|
+
Events: SET_VALUE, STEP, POINTER_DOWN, POINTER_UP, HOME, END.
|
|
74
|
+
Rule: Disabled blocks SET_VALUE / drag; project idle vs dragging on data-aeon-state.`,
|
|
75
|
+
|
|
76
|
+
contentRegion: `## Content region statechart
|
|
77
|
+
Machine: contentRegionMachine — idle → pending → (empty | error | ready) → loadingMore | success.
|
|
78
|
+
Events: LOAD, RESOLVE, REJECT, RETRY, LOAD_MORE, MORE_DONE, SUCCEED, RESET.
|
|
79
|
+
Rule: Every stable state has a slot. Pair with Async for fetch; do not hide empty/error behind a spinner.`,
|
|
80
|
+
|
|
81
|
+
toast: `## Toast item statechart
|
|
82
|
+
Machine: toastMachine — hidden | visible. Events: SHOW, HIDE, DISMISS.
|
|
83
|
+
Rule: Provider list orchestration may use React; each toast item uses the machine.`,
|
|
84
|
+
|
|
85
|
+
stickyBar: `## StickyBar statechart
|
|
86
|
+
Machine: stickyBarMachine — floating | docked | collapsed | hidden.
|
|
87
|
+
Events: DOCK, FLOAT, COLLAPSE, EXPAND, HIDE, SHOW, SET_OFFSET.
|
|
88
|
+
Rule: DOM scope is bar (bar anatomy zones). Do not invent a parallel sticky useState.`,
|
|
68
89
|
} as const
|
|
69
90
|
|
|
70
91
|
/** Checkout-form example used on the landing "AI playground" proof. */
|