aeon-ui-engine 1.3.5 → 1.5.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.
Files changed (104) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/CONSUMER.md +67 -27
  3. package/INTEGRATION.md +1 -1
  4. package/README.md +17 -8
  5. package/package.json +4 -1
  6. package/packages/cli/README.md +13 -1
  7. package/packages/cli/dist/bin.js +50 -6
  8. package/packages/cli/dist/chunk-JMNRJCWF.js +301 -0
  9. package/packages/cli/dist/index.d.ts +31 -1
  10. package/packages/cli/dist/index.js +7 -3
  11. package/packages/cli/src/bin.ts +50 -4
  12. package/packages/cli/src/index.ts +2 -0
  13. package/packages/cli/src/init.ts +10 -4
  14. package/packages/cli/src/link.ts +61 -0
  15. package/packages/cli/src/migrate.ts +170 -0
  16. package/packages/core/dist/index.d.ts +35 -7
  17. package/packages/core/dist/index.js +102 -2
  18. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  19. package/packages/core/src/anatomy.ts +39 -3
  20. package/packages/core/src/identicon.test.ts +39 -0
  21. package/packages/core/src/identicon.ts +79 -0
  22. package/packages/core/src/index.ts +1 -0
  23. package/packages/panda/panda.config.ts +4 -0
  24. package/packages/panda/src/recipes/composer.ts +58 -4
  25. package/packages/panda/src/recipes/file-upload.ts +100 -0
  26. package/packages/panda/src/recipes/index.ts +2 -0
  27. package/packages/panda/src/recipes/range-slider.ts +59 -0
  28. package/packages/panda/src/recipes/thread.ts +77 -2
  29. package/packages/panda/styled-system/recipes/aeon-composer.d.ts +1 -1
  30. package/packages/panda/styled-system/recipes/aeon-composer.mjs +12 -0
  31. package/packages/panda/styled-system/recipes/aeon-file-upload.d.ts +31 -0
  32. package/packages/panda/styled-system/recipes/aeon-file-upload.mjs +61 -0
  33. package/packages/panda/styled-system/recipes/aeon-range-slider.d.ts +31 -0
  34. package/packages/panda/styled-system/recipes/aeon-range-slider.mjs +49 -0
  35. package/packages/panda/styled-system/recipes/aeon-thread.d.ts +1 -1
  36. package/packages/panda/styled-system/recipes/aeon-thread.mjs +20 -0
  37. package/packages/panda/styled-system/recipes/index.d.ts +2 -0
  38. package/packages/panda/styled-system/recipes/index.mjs +2 -0
  39. package/packages/panda/styled-system/styles.css +292 -0
  40. package/packages/primitives/dist/chunk-CV4UXXZF.js +44 -0
  41. package/packages/primitives/dist/chunk-IZKQSCYU.js +164 -0
  42. package/packages/primitives/dist/{chunk-PB3LFHXU.js → chunk-VUPWYBGU.js} +14 -0
  43. package/packages/primitives/dist/index.d.ts +2 -0
  44. package/packages/primitives/dist/index.js +23 -7
  45. package/packages/primitives/dist/machines/file-upload.d.ts +39 -0
  46. package/packages/primitives/dist/machines/file-upload.js +8 -0
  47. package/packages/primitives/dist/machines/range-slider.d.ts +129 -0
  48. package/packages/primitives/dist/machines/range-slider.js +12 -0
  49. package/packages/primitives/dist/machines/slider.d.ts +22 -0
  50. package/packages/primitives/dist/machines/slider.js +1 -1
  51. package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  52. package/packages/primitives/src/__tests__/file-upload.test.ts +25 -0
  53. package/packages/primitives/src/__tests__/range-slider.test.ts +39 -0
  54. package/packages/primitives/src/index.ts +2 -0
  55. package/packages/primitives/src/machines/file-upload.ts +55 -0
  56. package/packages/primitives/src/machines/range-slider.ts +209 -0
  57. package/packages/primitives/src/machines/slider.ts +17 -0
  58. package/packages/react/dist/index.d.ts +132 -26
  59. package/packages/react/dist/index.js +1822 -1029
  60. package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  61. package/packages/react/src/button/index.tsx +50 -15
  62. package/packages/react/src/composer/index.tsx +58 -3
  63. package/packages/react/src/file-upload/index.tsx +322 -0
  64. package/packages/react/src/floating/FloatingPositioner.tsx +8 -0
  65. package/packages/react/src/hooks/use-anchor-position.ts +88 -17
  66. package/packages/react/src/index.ts +3 -0
  67. package/packages/react/src/menu/index.tsx +37 -3
  68. package/packages/react/src/popover/index.tsx +7 -1
  69. package/packages/react/src/prompt/index.tsx +45 -0
  70. package/packages/react/src/range-slider/index.tsx +322 -0
  71. package/packages/react/src/slider/index.tsx +54 -14
  72. package/packages/react/src/thread/index.tsx +74 -3
  73. package/packages/react/src/tooltip/index.tsx +7 -1
  74. package/packages/react/src/utils/slot.tsx +47 -0
  75. package/packages/schemas/dist/index.d.ts +4 -0
  76. package/packages/schemas/dist/index.js +67 -1
  77. package/packages/schemas/json/button.json +6 -0
  78. package/packages/schemas/json/catalog.json +333 -0
  79. package/packages/schemas/json/fileUpload.json +168 -0
  80. package/packages/schemas/json/rangeSlider.json +157 -0
  81. package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  82. package/packages/schemas/prompts/cursorrules.md +3 -1
  83. package/packages/schemas/prompts/statecharts.md +1 -1
  84. package/packages/schemas/registry/catalog.json +18 -0
  85. package/packages/schemas/registry/components/button.json +6 -0
  86. package/packages/schemas/registry/components/fileUpload.json +168 -0
  87. package/packages/schemas/registry/components/rangeSlider.json +157 -0
  88. package/packages/schemas/registry/machines/fileUpload.json +182 -0
  89. package/packages/schemas/registry/machines/rangeSlider.json +201 -0
  90. package/packages/schemas/scripts/emit.ts +4 -0
  91. package/packages/schemas/src/components.ts +70 -0
  92. package/packages/schemas/src/prompts.ts +3 -1
  93. package/packages/schemas/tsconfig.json +1 -1
  94. package/packages/ui/dist/index.d.ts +100 -46
  95. package/packages/ui/dist/index.js +463 -341
  96. package/packages/ui/src/composer.tsx +37 -11
  97. package/packages/ui/src/file-upload.tsx +36 -0
  98. package/packages/ui/src/index.ts +2 -0
  99. package/packages/ui/src/prompt.tsx +3 -0
  100. package/packages/ui/src/range-slider.tsx +24 -0
  101. package/packages/ui/src/thread.tsx +40 -0
  102. package/scripts/link-scoped-packages.mjs +206 -0
  103. package/scripts/vite-aliases.mjs +6 -8
  104. package/packages/cli/dist/chunk-IW5NT2Z2.js +0 -103
@@ -29,8 +29,9 @@ declare const dialogAnatomy: Record<"root" | "trigger" | "portal" | "backdrop" |
29
29
  /**
30
30
  * Prompt — confirm / permission / update-style dialog on top of Dialog.
31
31
  * Universal parts: eyebrow, meta, amount, actions (primary + secondary).
32
+ * Command-confirm face (BRC-218 §4): verb, recipient, effect.
32
33
  */
33
- declare const promptAnatomy: Record<"root" | "eyebrow" | "meta" | "amount" | "actions" | "primary" | "secondary", "root" | "eyebrow" | "meta" | "amount" | "actions" | "primary" | "secondary"> & {
34
+ declare const promptAnatomy: Record<"root" | "eyebrow" | "meta" | "amount" | "verb" | "recipient" | "effect" | "actions" | "primary" | "secondary", "root" | "eyebrow" | "meta" | "amount" | "verb" | "recipient" | "effect" | "actions" | "primary" | "secondary"> & {
34
35
  scope: string;
35
36
  };
36
37
  /**
@@ -138,16 +139,21 @@ declare const conversationAnatomy: Record<"root" | "title" | "meta" | "body" | "
138
139
  scope: string;
139
140
  };
140
141
  /**
141
- * Thread — message stream. Items project mine | theirs | pending | failed.
142
+ * Thread — message stream.
143
+ * Item state: mine | theirs | pending | failed | command-result | payment-card | request-card.
144
+ * Bind = reply/message binding (BRC-218 §4.9–4.10, touch-reachable).
145
+ * Card parts = structured in-thread command results (pay / request / escrow / whois).
142
146
  */
143
- declare const threadAnatomy: Record<"root" | "meta" | "list" | "item" | "bubble" | "day", "root" | "meta" | "list" | "item" | "bubble" | "day"> & {
147
+ declare const threadAnatomy: Record<"root" | "meta" | "list" | "item" | "bubble" | "day" | "bind" | "card" | "cardTitle" | "cardBody" | "cardActions", "root" | "meta" | "list" | "item" | "bubble" | "day" | "bind" | "card" | "cardTitle" | "cardBody" | "cardActions"> & {
144
148
  scope: string;
145
149
  };
146
150
  /**
147
- * Composer — bottom compose chrome for threads (input + send).
148
- * Root state: idle | sending | disabled | error.
151
+ * Composer — bottom compose chrome for threads.
152
+ * Root state: idle | sending | disabled | error | chat | command | lookup.
153
+ * Suggestions = slash-command / recipient palette (BRC-218 §4.4).
154
+ * Toolbar = verb shortcuts (/ pay request) without hover-only affordances.
149
155
  */
150
- declare const composerAnatomy: Record<"root" | "actions" | "input" | "send", "root" | "actions" | "input" | "send"> & {
156
+ declare const composerAnatomy: Record<"root" | "actions" | "input" | "send" | "suggestions" | "suggestion" | "toolbar", "root" | "actions" | "input" | "send" | "suggestions" | "suggestion" | "toolbar"> & {
151
157
  scope: string;
152
158
  };
153
159
  declare const separatorAnatomy: Record<"root", "root"> & {
@@ -163,6 +169,12 @@ declare const radioGroupAnatomy: Record<"root" | "item" | "itemIndicator" | "ite
163
169
  declare const sliderAnatomy: Record<"root" | "thumb" | "track" | "range" | "valueText", "root" | "thumb" | "track" | "range" | "valueText"> & {
164
170
  scope: string;
165
171
  };
172
+ declare const rangeSliderAnatomy: Record<"root" | "thumb" | "track" | "range" | "valueText", "root" | "thumb" | "track" | "range" | "valueText"> & {
173
+ scope: string;
174
+ };
175
+ declare const fileUploadAnatomy: Record<"root" | "label" | "hint" | "input" | "preview" | "dropzone" | "clearTrigger" | "rejectionMessage", "root" | "label" | "hint" | "input" | "preview" | "dropzone" | "clearTrigger" | "rejectionMessage"> & {
176
+ scope: string;
177
+ };
166
178
  declare const pinInputAnatomy: Record<"root" | "input", "root" | "input"> & {
167
179
  scope: string;
168
180
  };
@@ -502,4 +514,20 @@ declare function brandPaletteToCssText(input: BrandPaletteInput, options?: {
502
514
  selector?: string;
503
515
  }): string;
504
516
 
505
- export { AEON_PRINCIPLE, AEON_THEME_TRANSITION_CLASS, type AeonActor, type AeonColorMode, type AeonContext, type AeonDataAttrs, type AeonEvent, type AeonPalette, type AeonSnapshot, type AeonStateValue, type AeonThemeDefinition, type ApplyBrandPaletteOptions, type BrandPalette, type BrandPaletteInput, DEFAULT_COMPACT_MQ, type FieldFace, type LayoutComposition, type LayoutCompositionSpec, type LayoutMode, NORM_PLANE, type NormOrigin, type NormPoint, type NormRect, type RegionLayoutSlot, type ResponsiveLayoutSpec, type ScrollOverflow, type ScrollSnapshot, UI_IS_FUNCTION_OF_STATE, accordionAnatomy, anatomy, appNavAnatomy, appShellAnatomy, applyAeonTheme, applyBrandPalette, asyncAnatomy, avatarAnatomy, barAnatomy, brandPaletteToCssText, brandPaletteToCssVars, buttonAnatomy, checkboxAnatomy, clampNorm, clientFromNorm, comboboxAnatomy, composerAnatomy, contentAnatomy, conversationAnatomy, dialogAnatomy, entryAnatomy, fieldAnatomy, fieldFace, fieldRegions, getCompactMq, getRegionValue, getScrollSnapshot, identityAnatomy, initAeonTheme, insetNormCluster, isAeonColorMode, isAeonMode, isAeonThemeId, layoutCompositionForMode, layoutModeAttrs, listRowAnatomy, matchLayoutMode, measureElement, menuAnatomy, metricStripAnatomy, navAnatomy, normFromClient, normalizeBrandPalette, panelAnatomy, partAttrs, partOnlyAttrs, pinInputAnatomy, playSurfaceAnatomy, popoverAnatomy, profileHeaderAnatomy, progressAnatomy, promptAnatomy, radioGroupAnatomy, regionBoundsForMode, regionLeaf, regionMatches, regionPath, registerAeonThemes, resolveAeonColorMode, resolveRegionRect, runAeonThemeTransition, scopeAttrs, scrollAnatomy, selectAnatomy, separatorAnatomy, sliderAnatomy, stateToAttr, statusBannerAnatomy, switchAnatomy, syncAeonColorMode, tabsAnatomy, themeSwitcherAnatomy, threadAnatomy, toastAnatomy, tooltipAnatomy };
517
+ /**
518
+ * GitHub-style mirrored 5×5 identicon from an arbitrary seed.
519
+ * Useful as Avatar fallback when no photo/icon URL is available.
520
+ */
521
+ /** FNV-1a 32-bit → expand to 16 bytes for grid + color. */
522
+ declare function identiconSeedBytes(seed: string): Uint8Array;
523
+ /** 5×5 mirrored boolean grid. */
524
+ declare function identiconGrid(seed: string): boolean[][];
525
+ declare function identiconColor(seed: string): {
526
+ h: number;
527
+ s: number;
528
+ l: number;
529
+ };
530
+ /** SVG data URL for `<img src>` / CSS background. */
531
+ declare function identiconDataUrl(seed: string, size?: number): string;
532
+
533
+ export { AEON_PRINCIPLE, AEON_THEME_TRANSITION_CLASS, type AeonActor, type AeonColorMode, type AeonContext, type AeonDataAttrs, type AeonEvent, type AeonPalette, type AeonSnapshot, type AeonStateValue, type AeonThemeDefinition, type ApplyBrandPaletteOptions, type BrandPalette, type BrandPaletteInput, DEFAULT_COMPACT_MQ, type FieldFace, type LayoutComposition, type LayoutCompositionSpec, type LayoutMode, NORM_PLANE, type NormOrigin, type NormPoint, type NormRect, type RegionLayoutSlot, type ResponsiveLayoutSpec, type ScrollOverflow, type ScrollSnapshot, UI_IS_FUNCTION_OF_STATE, accordionAnatomy, anatomy, appNavAnatomy, appShellAnatomy, applyAeonTheme, applyBrandPalette, asyncAnatomy, avatarAnatomy, barAnatomy, brandPaletteToCssText, brandPaletteToCssVars, buttonAnatomy, checkboxAnatomy, clampNorm, clientFromNorm, comboboxAnatomy, composerAnatomy, contentAnatomy, conversationAnatomy, dialogAnatomy, entryAnatomy, fieldAnatomy, fieldFace, fieldRegions, fileUploadAnatomy, getCompactMq, getRegionValue, getScrollSnapshot, identiconColor, identiconDataUrl, identiconGrid, identiconSeedBytes, identityAnatomy, initAeonTheme, insetNormCluster, isAeonColorMode, isAeonMode, isAeonThemeId, layoutCompositionForMode, layoutModeAttrs, listRowAnatomy, matchLayoutMode, measureElement, menuAnatomy, metricStripAnatomy, navAnatomy, normFromClient, normalizeBrandPalette, panelAnatomy, partAttrs, partOnlyAttrs, pinInputAnatomy, playSurfaceAnatomy, popoverAnatomy, profileHeaderAnatomy, progressAnatomy, promptAnatomy, radioGroupAnatomy, rangeSliderAnatomy, regionBoundsForMode, regionLeaf, regionMatches, regionPath, registerAeonThemes, resolveAeonColorMode, resolveRegionRect, runAeonThemeTransition, scopeAttrs, scrollAnatomy, selectAnatomy, separatorAnatomy, sliderAnatomy, stateToAttr, statusBannerAnatomy, switchAnatomy, syncAeonColorMode, tabsAnatomy, themeSwitcherAnatomy, threadAnatomy, toastAnatomy, tooltipAnatomy };
@@ -42,6 +42,9 @@ var promptAnatomy = anatomy("prompt", [
42
42
  "eyebrow",
43
43
  "meta",
44
44
  "amount",
45
+ "verb",
46
+ "recipient",
47
+ "effect",
45
48
  "actions",
46
49
  "primary",
47
50
  "secondary"
@@ -204,13 +207,21 @@ var threadAnatomy = anatomy("thread", [
204
207
  "item",
205
208
  "bubble",
206
209
  "meta",
207
- "day"
210
+ "day",
211
+ "bind",
212
+ "card",
213
+ "cardTitle",
214
+ "cardBody",
215
+ "cardActions"
208
216
  ]);
209
217
  var composerAnatomy = anatomy("composer", [
210
218
  "root",
211
219
  "input",
212
220
  "actions",
213
- "send"
221
+ "send",
222
+ "suggestions",
223
+ "suggestion",
224
+ "toolbar"
214
225
  ]);
215
226
  var separatorAnatomy = anatomy("separator", ["root"]);
216
227
  var progressAnatomy = anatomy("progress", ["root", "track", "range", "label"]);
@@ -228,6 +239,23 @@ var sliderAnatomy = anatomy("slider", [
228
239
  "thumb",
229
240
  "valueText"
230
241
  ]);
242
+ var rangeSliderAnatomy = anatomy("rangeSlider", [
243
+ "root",
244
+ "track",
245
+ "range",
246
+ "thumb",
247
+ "valueText"
248
+ ]);
249
+ var fileUploadAnatomy = anatomy("fileUpload", [
250
+ "root",
251
+ "dropzone",
252
+ "input",
253
+ "label",
254
+ "hint",
255
+ "preview",
256
+ "clearTrigger",
257
+ "rejectionMessage"
258
+ ]);
231
259
  var pinInputAnatomy = anatomy("pinInput", ["root", "input"]);
232
260
  var barAnatomy = anatomy("bar", [
233
261
  "root",
@@ -673,6 +701,72 @@ function brandPaletteToCssText(input, options = {}) {
673
701
  ${body}
674
702
  }`;
675
703
  }
704
+
705
+ // src/identicon.ts
706
+ function identiconSeedBytes(seed) {
707
+ const s = seed.trim().toLowerCase() || "aeon";
708
+ let h = 2166136261;
709
+ for (let i = 0; i < s.length; i++) {
710
+ h ^= s.charCodeAt(i);
711
+ h = Math.imul(h, 16777619);
712
+ }
713
+ const out = new Uint8Array(16);
714
+ let x = h >>> 0;
715
+ for (let i = 0; i < 16; i++) {
716
+ x ^= x << 13;
717
+ x ^= x >>> 17;
718
+ x ^= x << 5;
719
+ out[i] = x >>> 0 & 255;
720
+ }
721
+ let h2 = 2166136261 ^ s.length;
722
+ for (let i = 0; i < s.length; i++) {
723
+ h2 ^= s.charCodeAt(s.length - 1 - i);
724
+ h2 = Math.imul(h2, 16777619);
725
+ }
726
+ for (let i = 0; i < 8; i++) {
727
+ out[i] ^= h2 >>> i % 4 * 8 & 255;
728
+ }
729
+ return out;
730
+ }
731
+ function identiconGrid(seed) {
732
+ const b = identiconSeedBytes(seed);
733
+ const grid = [];
734
+ let bit = 0;
735
+ for (let row = 0; row < 5; row++) {
736
+ const cells = [];
737
+ for (let col = 0; col < 3; col++) {
738
+ const byte = b[bit >> 3];
739
+ const on = (byte >> (bit & 7) & 1) === 1;
740
+ cells.push(on);
741
+ bit++;
742
+ }
743
+ grid.push([cells[0], cells[1], cells[2], cells[1], cells[0]]);
744
+ }
745
+ return grid;
746
+ }
747
+ function identiconColor(seed) {
748
+ const b = identiconSeedBytes(seed);
749
+ const h = Math.round((b[13] << 8 | b[14]) % 360);
750
+ const s = 55 + b[15] % 30;
751
+ const l = 42 + b[12] % 16;
752
+ return { h, s, l };
753
+ }
754
+ function identiconDataUrl(seed, size = 128) {
755
+ const grid = identiconGrid(seed);
756
+ const { h, s, l } = identiconColor(seed);
757
+ const cell = size / 5;
758
+ const fg = `hsl(${h} ${s}% ${l}%)`;
759
+ const bg = `hsl(${h} ${Math.max(12, s - 35)}% ${Math.min(92, l + 38)}%)`;
760
+ let rects = "";
761
+ for (let r = 0; r < 5; r++) {
762
+ for (let c = 0; c < 5; c++) {
763
+ if (!grid[r][c]) continue;
764
+ rects += `<rect x="${c * cell}" y="${r * cell}" width="${cell}" height="${cell}" fill="${fg}"/>`;
765
+ }
766
+ }
767
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}"><rect width="${size}" height="${size}" fill="${bg}"/>` + rects + `</svg>`;
768
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
769
+ }
676
770
  export {
677
771
  AEON_PRINCIPLE,
678
772
  AEON_THEME_TRANSITION_CLASS,
@@ -704,9 +798,14 @@ export {
704
798
  fieldAnatomy,
705
799
  fieldFace,
706
800
  fieldRegions,
801
+ fileUploadAnatomy,
707
802
  getCompactMq,
708
803
  getRegionValue,
709
804
  getScrollSnapshot,
805
+ identiconColor,
806
+ identiconDataUrl,
807
+ identiconGrid,
808
+ identiconSeedBytes,
710
809
  identityAnatomy,
711
810
  initAeonTheme,
712
811
  insetNormCluster,
@@ -733,6 +832,7 @@ export {
733
832
  progressAnatomy,
734
833
  promptAnatomy,
735
834
  radioGroupAnatomy,
835
+ rangeSliderAnatomy,
736
836
  regionBoundsForMode,
737
837
  regionLeaf,
738
838
  regionMatches,
@@ -1 +1 @@
1
- {"version":"3.2.4","results":[[":src/machine.test.ts",{"duration":2.8135839999999916,"failed":false}],[":src/theme.test.ts",{"duration":2.691708999999989,"failed":false}],[":../../apps/demo/src/aeonSchemaCompiler.test.ts",{"duration":7.737041999999974,"failed":false}]]}
1
+ {"version":"3.2.4","results":[[":src/machine.test.ts",{"duration":7.0908339999999725,"failed":false}],[":src/theme.test.ts",{"duration":3.866333999999995,"failed":false}],[":../../apps/demo/src/aeonSchemaCompiler.test.ts",{"duration":7.737041999999974,"failed":false}],[":src/identicon.test.ts",{"duration":9.875333000000012,"failed":false}]]}
@@ -43,12 +43,16 @@ export const dialogAnatomy = anatomy('dialog', [
43
43
  /**
44
44
  * Prompt — confirm / permission / update-style dialog on top of Dialog.
45
45
  * Universal parts: eyebrow, meta, amount, actions (primary + secondary).
46
+ * Command-confirm face (BRC-218 §4): verb, recipient, effect.
46
47
  */
47
48
  export const promptAnatomy = anatomy('prompt', [
48
49
  'root',
49
50
  'eyebrow',
50
51
  'meta',
51
52
  'amount',
53
+ 'verb',
54
+ 'recipient',
55
+ 'effect',
52
56
  'actions',
53
57
  'primary',
54
58
  'secondary',
@@ -269,7 +273,10 @@ export const conversationAnatomy = anatomy('conversation', [
269
273
  ] as const)
270
274
 
271
275
  /**
272
- * Thread — message stream. Items project mine | theirs | pending | failed.
276
+ * Thread — message stream.
277
+ * Item state: mine | theirs | pending | failed | command-result | payment-card | request-card.
278
+ * Bind = reply/message binding (BRC-218 §4.9–4.10, touch-reachable).
279
+ * Card parts = structured in-thread command results (pay / request / escrow / whois).
273
280
  */
274
281
  export const threadAnatomy = anatomy('thread', [
275
282
  'root',
@@ -278,17 +285,27 @@ export const threadAnatomy = anatomy('thread', [
278
285
  'bubble',
279
286
  'meta',
280
287
  'day',
288
+ 'bind',
289
+ 'card',
290
+ 'cardTitle',
291
+ 'cardBody',
292
+ 'cardActions',
281
293
  ] as const)
282
294
 
283
295
  /**
284
- * Composer — bottom compose chrome for threads (input + send).
285
- * Root state: idle | sending | disabled | error.
296
+ * Composer — bottom compose chrome for threads.
297
+ * Root state: idle | sending | disabled | error | chat | command | lookup.
298
+ * Suggestions = slash-command / recipient palette (BRC-218 §4.4).
299
+ * Toolbar = verb shortcuts (/ pay request) without hover-only affordances.
286
300
  */
287
301
  export const composerAnatomy = anatomy('composer', [
288
302
  'root',
289
303
  'input',
290
304
  'actions',
291
305
  'send',
306
+ 'suggestions',
307
+ 'suggestion',
308
+ 'toolbar',
292
309
  ] as const)
293
310
 
294
311
  export const separatorAnatomy = anatomy('separator', ['root'] as const)
@@ -312,6 +329,25 @@ export const sliderAnatomy = anatomy('slider', [
312
329
  'valueText',
313
330
  ] as const)
314
331
 
332
+ export const rangeSliderAnatomy = anatomy('rangeSlider', [
333
+ 'root',
334
+ 'track',
335
+ 'range',
336
+ 'thumb',
337
+ 'valueText',
338
+ ] as const)
339
+
340
+ export const fileUploadAnatomy = anatomy('fileUpload', [
341
+ 'root',
342
+ 'dropzone',
343
+ 'input',
344
+ 'label',
345
+ 'hint',
346
+ 'preview',
347
+ 'clearTrigger',
348
+ 'rejectionMessage',
349
+ ] as const)
350
+
315
351
  export const pinInputAnatomy = anatomy('pinInput', ['root', 'input'] as const)
316
352
 
317
353
  /**
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ identiconDataUrl,
4
+ identiconGrid,
5
+ identiconSeedBytes,
6
+ } from './identicon.js'
7
+
8
+ describe('identicon', () => {
9
+ it('is stable for the same seed (case-insensitive)', () => {
10
+ const a = identiconGrid('aa'.repeat(32) + '_0')
11
+ const b = identiconGrid('aa'.repeat(32) + '_0')
12
+ expect(a).toEqual(b)
13
+ expect(identiconDataUrl('aa'.repeat(32) + '_0')).toBe(
14
+ identiconDataUrl('AA'.repeat(32) + '_0'),
15
+ )
16
+ })
17
+
18
+ it('differs across seeds', () => {
19
+ const a = identiconDataUrl('aa'.repeat(32) + '_0')
20
+ const b = identiconDataUrl('bb'.repeat(32) + '_0')
21
+ expect(a).not.toBe(b)
22
+ })
23
+
24
+ it('mirrors the left half (GitHub-style)', () => {
25
+ const g = identiconGrid('demo')
26
+ expect(g).toHaveLength(5)
27
+ for (const row of g) {
28
+ expect(row).toHaveLength(5)
29
+ expect(row[0]).toBe(row[4])
30
+ expect(row[1]).toBe(row[3])
31
+ }
32
+ })
33
+
34
+ it('produces a data URL', () => {
35
+ const url = identiconDataUrl('GOON', 48)
36
+ expect(url.startsWith('data:image/svg+xml')).toBe(true)
37
+ expect(identiconSeedBytes('GOON')).toHaveLength(16)
38
+ })
39
+ })
@@ -0,0 +1,79 @@
1
+ /**
2
+ * GitHub-style mirrored 5×5 identicon from an arbitrary seed.
3
+ * Useful as Avatar fallback when no photo/icon URL is available.
4
+ */
5
+
6
+ /** FNV-1a 32-bit → expand to 16 bytes for grid + color. */
7
+ export function identiconSeedBytes(seed: string): Uint8Array {
8
+ const s = seed.trim().toLowerCase() || 'aeon'
9
+ let h = 0x811c9dc5
10
+ for (let i = 0; i < s.length; i++) {
11
+ h ^= s.charCodeAt(i)
12
+ h = Math.imul(h, 0x01000193)
13
+ }
14
+ const out = new Uint8Array(16)
15
+ let x = h >>> 0
16
+ for (let i = 0; i < 16; i++) {
17
+ x ^= x << 13
18
+ x ^= x >>> 17
19
+ x ^= x << 5
20
+ out[i] = (x >>> 0) & 0xff
21
+ }
22
+ let h2 = 0x811c9dc5 ^ s.length
23
+ for (let i = 0; i < s.length; i++) {
24
+ h2 ^= s.charCodeAt(s.length - 1 - i)
25
+ h2 = Math.imul(h2, 0x01000193)
26
+ }
27
+ for (let i = 0; i < 8; i++) {
28
+ out[i] ^= (h2 >>> ((i % 4) * 8)) & 0xff
29
+ }
30
+ return out
31
+ }
32
+
33
+ /** 5×5 mirrored boolean grid. */
34
+ export function identiconGrid(seed: string): boolean[][] {
35
+ const b = identiconSeedBytes(seed)
36
+ const grid: boolean[][] = []
37
+ let bit = 0
38
+ for (let row = 0; row < 5; row++) {
39
+ const cells: boolean[] = []
40
+ for (let col = 0; col < 3; col++) {
41
+ const byte = b[bit >> 3]!
42
+ const on = ((byte >> (bit & 7)) & 1) === 1
43
+ cells.push(on)
44
+ bit++
45
+ }
46
+ grid.push([cells[0]!, cells[1]!, cells[2]!, cells[1]!, cells[0]!])
47
+ }
48
+ return grid
49
+ }
50
+
51
+ export function identiconColor(seed: string): { h: number; s: number; l: number } {
52
+ const b = identiconSeedBytes(seed)
53
+ const h = Math.round(((b[13]! << 8) | b[14]!) % 360)
54
+ const s = 55 + (b[15]! % 30)
55
+ const l = 42 + (b[12]! % 16)
56
+ return { h, s, l }
57
+ }
58
+
59
+ /** SVG data URL for `<img src>` / CSS background. */
60
+ export function identiconDataUrl(seed: string, size = 128): string {
61
+ const grid = identiconGrid(seed)
62
+ const { h, s, l } = identiconColor(seed)
63
+ const cell = size / 5
64
+ const fg = `hsl(${h} ${s}% ${l}%)`
65
+ const bg = `hsl(${h} ${Math.max(12, s - 35)}% ${Math.min(92, l + 38)}%)`
66
+ let rects = ''
67
+ for (let r = 0; r < 5; r++) {
68
+ for (let c = 0; c < 5; c++) {
69
+ if (!grid[r]![c]) continue
70
+ rects += `<rect x="${c * cell}" y="${r * cell}" width="${cell}" height="${cell}" fill="${fg}"/>`
71
+ }
72
+ }
73
+ const svg =
74
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">` +
75
+ `<rect width="${size}" height="${size}" fill="${bg}"/>` +
76
+ rects +
77
+ `</svg>`
78
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
79
+ }
@@ -7,3 +7,4 @@ export * from './scroll.js'
7
7
  export * from './layout.js'
8
8
  export * from './theme.js'
9
9
  export * from './brand-theme.js'
10
+ export * from './identicon.js'
@@ -31,6 +31,8 @@ import {
31
31
  progressRecipe,
32
32
  radioGroupRecipe,
33
33
  sliderRecipe,
34
+ rangeSliderRecipe,
35
+ fileUploadRecipe,
34
36
  pinInputRecipe,
35
37
  menuRecipe,
36
38
  popoverRecipe,
@@ -121,6 +123,8 @@ export default defineConfig({
121
123
  aeonProgress: progressRecipe,
122
124
  aeonRadioGroup: radioGroupRecipe,
123
125
  aeonSlider: sliderRecipe,
126
+ aeonRangeSlider: rangeSliderRecipe,
127
+ aeonFileUpload: fileUploadRecipe,
124
128
  aeonPinInput: pinInputRecipe,
125
129
  aeonSwitch: switchRecipe,
126
130
  aeonCheckbox: checkboxRecipe,
@@ -1,10 +1,19 @@
1
1
  import { defineSlotRecipe } from '@pandacss/dev'
2
2
 
3
- const slots = ['root', 'input', 'actions', 'send'] as const
3
+ const slots = [
4
+ 'root',
5
+ 'input',
6
+ 'actions',
7
+ 'send',
8
+ 'suggestions',
9
+ 'suggestion',
10
+ 'toolbar',
11
+ ] as const
4
12
 
5
13
  /**
6
14
  * Composer — bottom compose chrome for threads.
7
15
  * Hierarchy: input and send share controlMinHSm — input must never dwarf the action.
16
+ * States: idle | sending | disabled | error | chat | command | lookup
8
17
  */
9
18
  export const composerRecipe = defineSlotRecipe({
10
19
  className: 'aeonComposer',
@@ -18,20 +27,25 @@ export const composerRecipe = defineSlotRecipe({
18
27
  width: '100%',
19
28
  pt: '0.45rem',
20
29
  px: '0.1rem',
21
- /* Clearance above dock / home indicator — input must not sit flush on chrome. */
22
30
  pb: '0.75rem',
23
31
  borderTopWidth: '1px',
24
32
  borderTopColor: 'border',
25
33
  bg: 'bg',
34
+ position: 'relative',
26
35
  '&[data-aeon-state=disabled]': { opacity: 0.55, pointerEvents: 'none' },
27
36
  '&[data-aeon-state=sending]': { opacity: 0.85 },
28
37
  '&[data-aeon-state=error]': {
29
38
  borderTopColor: 'danger',
30
39
  },
40
+ '&[data-aeon-state=command]': {
41
+ borderTopColor: 'accent',
42
+ },
43
+ '&[data-aeon-state=lookup]': {
44
+ borderTopColor: 'border',
45
+ },
31
46
  },
32
47
  input: {
33
48
  width: '100%',
34
- /* Locked to Button sm — input must never dwarf the send action. */
35
49
  minH: 'controlMinHSm',
36
50
  h: 'controlMinHSm',
37
51
  maxH: '5.5rem',
@@ -70,12 +84,52 @@ export const composerRecipe = defineSlotRecipe({
70
84
  alignItems: 'stretch',
71
85
  justifyContent: 'center',
72
86
  minH: 'controlMinHSm',
73
- /* Nested Button inherits the same control height — hierarchy stays honest. */
74
87
  '& > [data-aeon-scope=button], & > button': {
75
88
  minH: 'controlMinHSm',
76
89
  h: 'controlMinHSm',
77
90
  alignSelf: 'center',
78
91
  },
79
92
  },
93
+ toolbar: {
94
+ display: 'flex',
95
+ alignItems: 'center',
96
+ gap: '0.25rem',
97
+ gridColumn: '1 / -1',
98
+ order: -1,
99
+ mb: '0.25rem',
100
+ },
101
+ suggestions: {
102
+ listStyle: 'none',
103
+ margin: '0',
104
+ padding: '0.25rem',
105
+ display: 'grid',
106
+ gap: '0.15rem',
107
+ gridColumn: '1 / -1',
108
+ order: -1,
109
+ mb: '0.35rem',
110
+ maxH: '10rem',
111
+ overflowY: 'auto',
112
+ borderRadius: 'lg',
113
+ borderWidth: '1px',
114
+ borderColor: 'border',
115
+ bg: 'surface',
116
+ },
117
+ suggestion: {
118
+ display: 'flex',
119
+ alignItems: 'baseline',
120
+ gap: '0.5rem',
121
+ width: '100%',
122
+ textAlign: 'left',
123
+ px: '0.55rem',
124
+ py: '0.4rem',
125
+ borderRadius: 'md',
126
+ borderWidth: '0',
127
+ bg: 'transparent',
128
+ color: 'fg',
129
+ font: 'inherit',
130
+ fontSize: 'sm',
131
+ cursor: 'pointer',
132
+ _hover: { bg: 'surfaceRaised' },
133
+ },
80
134
  },
81
135
  })
@@ -0,0 +1,100 @@
1
+ import { defineSlotRecipe } from '@pandacss/dev'
2
+
3
+ export const fileUploadRecipe = defineSlotRecipe({
4
+ className: 'aeonFileUpload',
5
+ slots: [
6
+ 'root',
7
+ 'dropzone',
8
+ 'input',
9
+ 'label',
10
+ 'hint',
11
+ 'preview',
12
+ 'clearTrigger',
13
+ 'rejectionMessage',
14
+ ],
15
+ base: {
16
+ root: {
17
+ display: 'flex',
18
+ flexDirection: 'column',
19
+ gap: 'gapSm',
20
+ width: '100%',
21
+ },
22
+ label: {
23
+ fontSize: 'sm',
24
+ fontFamily: 'ui',
25
+ fontWeight: 'medium',
26
+ color: 'fg',
27
+ },
28
+ dropzone: {
29
+ display: 'flex',
30
+ flexDirection: 'column',
31
+ alignItems: 'center',
32
+ justifyContent: 'center',
33
+ gap: 'gapSm',
34
+ width: '100%',
35
+ minH: '8rem',
36
+ px: '4',
37
+ py: '6',
38
+ borderWidth: '1px',
39
+ borderStyle: 'dashed',
40
+ borderColor: 'border',
41
+ borderRadius: 'lg',
42
+ bg: 'surface',
43
+ cursor: 'pointer',
44
+ transitionProperty: 'border-color, background',
45
+ transitionDuration: 'fast',
46
+ _focusVisible: { boxShadow: 'focusRing' },
47
+ '&[data-aeon-state=dragging]': {
48
+ borderColor: 'accent',
49
+ bg: 'accentDim',
50
+ },
51
+ '&[data-aeon-state=rejected]': {
52
+ borderColor: 'danger',
53
+ },
54
+ '&[data-disabled]': {
55
+ opacity: 0.5,
56
+ cursor: 'not-allowed',
57
+ },
58
+ },
59
+ input: {
60
+ position: 'absolute',
61
+ width: '1px',
62
+ height: '1px',
63
+ padding: '0',
64
+ margin: '-1px',
65
+ overflow: 'hidden',
66
+ clip: 'rect(0, 0, 0, 0)',
67
+ whiteSpace: 'nowrap',
68
+ borderWidth: '0',
69
+ },
70
+ hint: {
71
+ fontSize: 'xs',
72
+ fontFamily: 'ui',
73
+ color: 'muted',
74
+ textAlign: 'center',
75
+ },
76
+ preview: {
77
+ fontSize: 'sm',
78
+ fontFamily: 'ui',
79
+ color: 'fg',
80
+ display: 'flex',
81
+ alignItems: 'center',
82
+ gap: 'gapSm',
83
+ },
84
+ clearTrigger: {
85
+ fontSize: 'xs',
86
+ fontFamily: 'ui',
87
+ color: 'muted',
88
+ textDecoration: 'underline',
89
+ cursor: 'pointer',
90
+ bg: 'transparent',
91
+ borderWidth: '0',
92
+ p: '0',
93
+ },
94
+ rejectionMessage: {
95
+ fontSize: 'xs',
96
+ fontFamily: 'ui',
97
+ color: 'danger',
98
+ },
99
+ },
100
+ })
@@ -30,5 +30,7 @@ export { separatorRecipe } from './separator.js'
30
30
  export { progressRecipe } from './progress.js'
31
31
  export { radioGroupRecipe } from './radio-group.js'
32
32
  export { sliderRecipe } from './slider.js'
33
+ export { rangeSliderRecipe } from './range-slider.js'
34
+ export { fileUploadRecipe } from './file-upload.js'
33
35
  export { pinInputRecipe } from './pin-input.js'
34
36
  export { menuRecipe, popoverRecipe, toastRecipe, tooltipRecipe } from './floating.js'