@signal9/era-ui 2.1.0 → 2.3.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 (213) hide show
  1. package/dist/ai/actions/action.svelte +41 -0
  2. package/dist/ai/actions/action.svelte.d.ts +12 -0
  3. package/dist/ai/actions/actions.svelte +15 -0
  4. package/dist/ai/actions/actions.svelte.d.ts +8 -0
  5. package/dist/ai/actions/index.d.ts +2 -0
  6. package/dist/ai/actions/index.js +2 -0
  7. package/dist/ai/artifact/artifact-actions.svelte +7 -0
  8. package/dist/ai/artifact/artifact-actions.svelte.d.ts +8 -0
  9. package/dist/ai/artifact/artifact-close.svelte +9 -0
  10. package/dist/ai/artifact/artifact-close.svelte.d.ts +6 -0
  11. package/dist/ai/artifact/artifact-content.svelte +7 -0
  12. package/dist/ai/artifact/artifact-content.svelte.d.ts +8 -0
  13. package/dist/ai/artifact/artifact-description.svelte +7 -0
  14. package/dist/ai/artifact/artifact-description.svelte.d.ts +8 -0
  15. package/dist/ai/artifact/artifact-header.svelte +16 -0
  16. package/dist/ai/artifact/artifact-header.svelte.d.ts +8 -0
  17. package/dist/ai/artifact/artifact-title.svelte +7 -0
  18. package/dist/ai/artifact/artifact-title.svelte.d.ts +8 -0
  19. package/dist/ai/artifact/artifact.svelte +16 -0
  20. package/dist/ai/artifact/artifact.svelte.d.ts +8 -0
  21. package/dist/ai/artifact/index.d.ts +8 -0
  22. package/dist/ai/artifact/index.js +8 -0
  23. package/dist/ai/chain-of-thought/chain-of-thought-step.svelte +39 -0
  24. package/dist/ai/chain-of-thought/chain-of-thought-step.svelte.d.ts +11 -0
  25. package/dist/ai/chain-of-thought/chain-of-thought.svelte +30 -0
  26. package/dist/ai/chain-of-thought/chain-of-thought.svelte.d.ts +11 -0
  27. package/dist/ai/chain-of-thought/index.d.ts +3 -0
  28. package/dist/ai/chain-of-thought/index.js +3 -0
  29. package/dist/ai/checkpoint.svelte +27 -0
  30. package/dist/ai/checkpoint.svelte.d.ts +10 -0
  31. package/dist/ai/confirmation/confirmation-actions.svelte +9 -0
  32. package/dist/ai/confirmation/confirmation-actions.svelte.d.ts +8 -0
  33. package/dist/ai/confirmation/confirmation.svelte +36 -0
  34. package/dist/ai/confirmation/confirmation.svelte.d.ts +11 -0
  35. package/dist/ai/confirmation/index.d.ts +2 -0
  36. package/dist/ai/confirmation/index.js +2 -0
  37. package/dist/ai/context/context.svelte +49 -0
  38. package/dist/ai/context/context.svelte.d.ts +9 -0
  39. package/dist/ai/context/index.d.ts +1 -0
  40. package/dist/ai/context/index.js +1 -0
  41. package/dist/ai/conversation/context.d.ts +3 -0
  42. package/dist/ai/conversation/context.js +12 -0
  43. package/dist/ai/conversation/conversation-content.svelte +32 -0
  44. package/dist/ai/conversation/conversation-content.svelte.d.ts +8 -0
  45. package/dist/ai/conversation/conversation-empty-state.svelte +32 -0
  46. package/dist/ai/conversation/conversation-empty-state.svelte.d.ts +11 -0
  47. package/dist/ai/conversation/conversation-scroll-button.svelte +27 -0
  48. package/dist/ai/conversation/conversation-scroll-button.svelte.d.ts +6 -0
  49. package/dist/ai/conversation/conversation.svelte +21 -0
  50. package/dist/ai/conversation/conversation.svelte.d.ts +8 -0
  51. package/dist/ai/conversation/index.d.ts +5 -0
  52. package/dist/ai/conversation/index.js +5 -0
  53. package/dist/ai/copy-button.svelte +42 -0
  54. package/dist/ai/copy-button.svelte.d.ts +8 -0
  55. package/dist/ai/image.svelte +19 -0
  56. package/dist/ai/image.svelte.d.ts +8 -0
  57. package/dist/ai/index.d.ts +29 -0
  58. package/dist/ai/index.js +31 -0
  59. package/dist/ai/inline-citation/index.d.ts +1 -0
  60. package/dist/ai/inline-citation/index.js +1 -0
  61. package/dist/ai/inline-citation/inline-citation.svelte +66 -0
  62. package/dist/ai/inline-citation/inline-citation.svelte.d.ts +11 -0
  63. package/dist/ai/loader.svelte +8 -0
  64. package/dist/ai/loader.svelte.d.ts +6 -0
  65. package/dist/ai/message/branch.svelte.d.ts +10 -0
  66. package/dist/ai/message/branch.svelte.js +28 -0
  67. package/dist/ai/message/context.d.ts +6 -0
  68. package/dist/ai/message/context.js +1 -0
  69. package/dist/ai/message/index.d.ts +8 -0
  70. package/dist/ai/message/index.js +7 -0
  71. package/dist/ai/message/message-branch-content.svelte +24 -0
  72. package/dist/ai/message/message-branch-content.svelte.d.ts +10 -0
  73. package/dist/ai/message/message-branch-selector.svelte +25 -0
  74. package/dist/ai/message/message-branch-selector.svelte.d.ts +6 -0
  75. package/dist/ai/message/message-branch.svelte +25 -0
  76. package/dist/ai/message/message-branch.svelte.d.ts +9 -0
  77. package/dist/ai/message/message-content.svelte +24 -0
  78. package/dist/ai/message/message-content.svelte.d.ts +8 -0
  79. package/dist/ai/message/message-toolbar.svelte +23 -0
  80. package/dist/ai/message/message-toolbar.svelte.d.ts +8 -0
  81. package/dist/ai/message/message.svelte +27 -0
  82. package/dist/ai/message/message.svelte.d.ts +10 -0
  83. package/dist/ai/model-selector/index.d.ts +1 -0
  84. package/dist/ai/model-selector/index.js +1 -0
  85. package/dist/ai/model-selector/model-selector.svelte +61 -0
  86. package/dist/ai/model-selector/model-selector.svelte.d.ts +15 -0
  87. package/dist/ai/open-in-chat/index.d.ts +1 -0
  88. package/dist/ai/open-in-chat/index.js +1 -0
  89. package/dist/ai/open-in-chat/open-in-chat.svelte +41 -0
  90. package/dist/ai/open-in-chat/open-in-chat.svelte.d.ts +32 -0
  91. package/dist/ai/plan/index.d.ts +3 -0
  92. package/dist/ai/plan/index.js +3 -0
  93. package/dist/ai/plan/plan-content.svelte +13 -0
  94. package/dist/ai/plan/plan-content.svelte.d.ts +8 -0
  95. package/dist/ai/plan/plan-header.svelte +22 -0
  96. package/dist/ai/plan/plan-header.svelte.d.ts +9 -0
  97. package/dist/ai/plan/plan.svelte +23 -0
  98. package/dist/ai/plan/plan.svelte.d.ts +10 -0
  99. package/dist/ai/prompt-input/attachments.svelte.d.ts +27 -0
  100. package/dist/ai/prompt-input/attachments.svelte.js +60 -0
  101. package/dist/ai/prompt-input/index.d.ts +7 -0
  102. package/dist/ai/prompt-input/index.js +7 -0
  103. package/dist/ai/prompt-input/prompt-input-add-attachments.svelte +12 -0
  104. package/dist/ai/prompt-input/prompt-input-add-attachments.svelte.d.ts +6 -0
  105. package/dist/ai/prompt-input/prompt-input-attachments.svelte +40 -0
  106. package/dist/ai/prompt-input/prompt-input-attachments.svelte.d.ts +18 -0
  107. package/dist/ai/prompt-input/prompt-input-submit.svelte +49 -0
  108. package/dist/ai/prompt-input/prompt-input-submit.svelte.d.ts +10 -0
  109. package/dist/ai/prompt-input/prompt-input-textarea.svelte +57 -0
  110. package/dist/ai/prompt-input/prompt-input-textarea.svelte.d.ts +7 -0
  111. package/dist/ai/prompt-input/prompt-input-toolbar.svelte +15 -0
  112. package/dist/ai/prompt-input/prompt-input-toolbar.svelte.d.ts +8 -0
  113. package/dist/ai/prompt-input/prompt-input.svelte +98 -0
  114. package/dist/ai/prompt-input/prompt-input.svelte.d.ts +17 -0
  115. package/dist/ai/queue/index.d.ts +2 -0
  116. package/dist/ai/queue/index.js +2 -0
  117. package/dist/ai/queue/queue-item.svelte +26 -0
  118. package/dist/ai/queue/queue-item.svelte.d.ts +9 -0
  119. package/dist/ai/queue/queue.svelte +14 -0
  120. package/dist/ai/queue/queue.svelte.d.ts +8 -0
  121. package/dist/ai/reasoning/index.d.ts +3 -0
  122. package/dist/ai/reasoning/index.js +3 -0
  123. package/dist/ai/reasoning/reasoning-trigger.svelte +22 -0
  124. package/dist/ai/reasoning/reasoning-trigger.svelte.d.ts +9 -0
  125. package/dist/ai/reasoning/reasoning.svelte +52 -0
  126. package/dist/ai/reasoning/reasoning.svelte.d.ts +13 -0
  127. package/dist/ai/response/index.d.ts +2 -0
  128. package/dist/ai/response/index.js +2 -0
  129. package/dist/ai/response/repair.d.ts +7 -0
  130. package/dist/ai/response/repair.js +26 -0
  131. package/dist/ai/response/response.svelte +112 -0
  132. package/dist/ai/response/response.svelte.d.ts +10 -0
  133. package/dist/ai/shimmer.svelte +15 -0
  134. package/dist/ai/shimmer.svelte.d.ts +9 -0
  135. package/dist/ai/sources/index.d.ts +4 -0
  136. package/dist/ai/sources/index.js +4 -0
  137. package/dist/ai/sources/source.svelte +16 -0
  138. package/dist/ai/sources/source.svelte.d.ts +7 -0
  139. package/dist/ai/sources/sources-trigger.svelte +12 -0
  140. package/dist/ai/sources/sources-trigger.svelte.d.ts +8 -0
  141. package/dist/ai/sources/sources.svelte +15 -0
  142. package/dist/ai/sources/sources.svelte.d.ts +9 -0
  143. package/dist/ai/stick-to-bottom.svelte.d.ts +38 -0
  144. package/dist/ai/stick-to-bottom.svelte.js +133 -0
  145. package/dist/ai/suggestion/index.d.ts +2 -0
  146. package/dist/ai/suggestion/index.js +2 -0
  147. package/dist/ai/suggestion/suggestion.svelte +25 -0
  148. package/dist/ai/suggestion/suggestion.svelte.d.ts +10 -0
  149. package/dist/ai/suggestion/suggestions.svelte +18 -0
  150. package/dist/ai/suggestion/suggestions.svelte.d.ts +8 -0
  151. package/dist/ai/task/index.d.ts +5 -0
  152. package/dist/ai/task/index.js +5 -0
  153. package/dist/ai/task/task-content.svelte +14 -0
  154. package/dist/ai/task/task-content.svelte.d.ts +8 -0
  155. package/dist/ai/task/task-item-file.svelte +8 -0
  156. package/dist/ai/task/task-item-file.svelte.d.ts +7 -0
  157. package/dist/ai/task/task-item.svelte +8 -0
  158. package/dist/ai/task/task-item.svelte.d.ts +8 -0
  159. package/dist/ai/task/task-trigger.svelte +12 -0
  160. package/dist/ai/task/task-trigger.svelte.d.ts +8 -0
  161. package/dist/ai/task/task.svelte +17 -0
  162. package/dist/ai/task/task.svelte.d.ts +11 -0
  163. package/dist/ai/tool/index.d.ts +5 -0
  164. package/dist/ai/tool/index.js +5 -0
  165. package/dist/ai/tool/tool-header.svelte +20 -0
  166. package/dist/ai/tool/tool-header.svelte.d.ts +9 -0
  167. package/dist/ai/tool/tool-input.svelte +10 -0
  168. package/dist/ai/tool/tool-input.svelte.d.ts +6 -0
  169. package/dist/ai/tool/tool-output.svelte +20 -0
  170. package/dist/ai/tool/tool-output.svelte.d.ts +7 -0
  171. package/dist/ai/tool/tool.svelte +41 -0
  172. package/dist/ai/tool/tool.svelte.d.ts +12 -0
  173. package/dist/ai/web-preview/index.d.ts +1 -0
  174. package/dist/ai/web-preview/index.js +1 -0
  175. package/dist/ai/web-preview/web-preview.svelte +48 -0
  176. package/dist/ai/web-preview/web-preview.svelte.d.ts +8 -0
  177. package/dist/ai/workflow/canvas.svelte +19 -0
  178. package/dist/ai/workflow/canvas.svelte.d.ts +11 -0
  179. package/dist/ai/workflow/index.d.ts +2 -0
  180. package/dist/ai/workflow/index.js +2 -0
  181. package/dist/ai/workflow/node.svelte +27 -0
  182. package/dist/ai/workflow/node.svelte.d.ts +9 -0
  183. package/dist/era-ui.css +1 -1
  184. package/dist/generated-docs/llms-full.txt +56 -0
  185. package/dist/generated-docs/llms.txt +1 -1
  186. package/dist/generated-docs/manifest.json +14 -0
  187. package/dist/generated-docs/step.md +51 -0
  188. package/dist/index.d.ts +1 -0
  189. package/dist/index.js +1 -0
  190. package/dist/os/motion.d.ts +1 -19
  191. package/dist/os/motion.js +3 -44
  192. package/dist/os/taskbar.svelte +6 -3
  193. package/dist/styles/index.css +26 -0
  194. package/dist/ui/command-bar/command-bar.svelte +8 -6
  195. package/dist/ui/index.d.ts +1 -0
  196. package/dist/ui/index.js +1 -0
  197. package/dist/ui/step/context.d.ts +8 -0
  198. package/dist/ui/step/context.js +8 -0
  199. package/dist/ui/step/index.d.ts +5 -0
  200. package/dist/ui/step/index.js +4 -0
  201. package/dist/ui/step/step-content.svelte +55 -0
  202. package/dist/ui/step/step-content.svelte.d.ts +5 -0
  203. package/dist/ui/step/step-root.svelte +31 -0
  204. package/dist/ui/step/step-root.svelte.d.ts +9 -0
  205. package/dist/ui/step/step-section.svelte +23 -0
  206. package/dist/ui/step/step-section.svelte.d.ts +10 -0
  207. package/dist/ui/step/step-summary.svelte +55 -0
  208. package/dist/ui/step/step-summary.svelte.d.ts +5 -0
  209. package/dist/utils/index.d.ts +1 -0
  210. package/dist/utils/index.js +1 -0
  211. package/dist/utils/motion.d.ts +19 -0
  212. package/dist/utils/motion.js +44 -0
  213. package/package.json +14 -1
@@ -0,0 +1,51 @@
1
+ # Step
2
+
3
+ Expandable inline step — tool calls, thinking, agent actions.
4
+
5
+ ## Import
6
+
7
+ ```ts
8
+ import { Step } from "$lib/ui/step";
9
+ ```
10
+
11
+ Composed from sub-components — compose explicitly at the call site:
12
+
13
+ - `Step.Root`
14
+ - `Step.Summary`
15
+ - `Step.Content`
16
+ - `Step.Section`
17
+
18
+ ## Step.Root
19
+
20
+ Inherits all props from `Collapsible.RootProps`.
21
+
22
+ | Prop | Type | Default | Notes |
23
+ |------|------|---------|-------|
24
+ | `status?` | `StepStatus` | `'done'` | Drives the summary's icon and shimmer; exposed as data-status. |
25
+ | `ref?` | `(forwarded)` | `null` | bindable |
26
+ | `open?` | `(forwarded)` | `false` | bindable |
27
+
28
+ ## Step.Summary
29
+
30
+ Inherits all props from `PartProps<Collapsible.TriggerProps>`.
31
+
32
+ | Prop | Type | Default | Notes |
33
+ |------|------|---------|-------|
34
+ | `ref?` | `(forwarded)` | `null` | bindable |
35
+
36
+ ## Step.Content
37
+
38
+ Inherits all props from `PartProps<Collapsible.ContentProps>`.
39
+
40
+ | Prop | Type | Default | Notes |
41
+ |------|------|---------|-------|
42
+ | `ref?` | `(forwarded)` | `null` | bindable |
43
+
44
+ ## Step.Section
45
+
46
+ Inherits all props from `HTMLAttributes<HTMLDivElement>`.
47
+
48
+ | Prop | Type | Default | Notes |
49
+ |------|------|---------|-------|
50
+ | `label?` | `string` | — | Tiny uppercase section header — INPUT, OUTPUT, … |
51
+ | `children?` | `Snippet` | — | — |
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './ui/index.js';
2
2
  export * as OS from './os/index.js';
3
+ export * as AI from './ai/index.js';
3
4
  export type { Point, Size, Rect, Insets, Frame, Workspace, WindowState, SnapZone, AppWindow, AppDefinition, AppComponent, AppProps } from './os/index.js';
4
5
  export { cn, tv, hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey, type KeyBindingMap, type KeyBindingPress, type KeyBindingOptions, type PartProps, type VariantProps } from './utils/index.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './ui/index.js';
2
2
  export * as OS from './os/index.js';
3
+ export * as AI from './ai/index.js';
3
4
  export { cn, tv, hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey } from './utils/index.js';
@@ -1,19 +1 @@
1
- import type { EasingFunction, TransitionConfig } from 'svelte/transition';
2
- type EraFlyParams = {
3
- x?: number | string;
4
- y?: number | string;
5
- opacity?: number;
6
- delay?: number;
7
- duration?: number;
8
- easing?: EasingFunction;
9
- };
10
- /**
11
- * Fly transition that reads Era's motion tokens from the rendered element.
12
- *
13
- * Svelte transition durations are JavaScript parameters, so hard-coded numbers
14
- * bypass the runtime `data-motion` axis. Reading `--era-duration` here keeps OS
15
- * overlays in sync with docs settings and library consumers that set motion on
16
- * an ancestor.
17
- */
18
- export declare function eraFly(node: Element, { x, y, opacity, delay, duration, easing }?: EraFlyParams): TransitionConfig;
19
- export {};
1
+ export { eraFly } from '../utils/motion.js';
package/dist/os/motion.js CHANGED
@@ -1,44 +1,3 @@
1
- const durationUnits = {
2
- ms: 1,
3
- s: 1000
4
- };
5
- function parseDuration(value) {
6
- const match = value.trim().match(/^(-?\d*\.?\d+)(ms|s)$/i);
7
- if (!match)
8
- return undefined;
9
- return Math.max(0, Number(match[1]) * durationUnits[match[2].toLowerCase()]);
10
- }
11
- function eraDuration(node, fallback) {
12
- const token = getComputedStyle(node).getPropertyValue('--era-duration');
13
- return parseDuration(token) ?? fallback;
14
- }
15
- function offset(value, progress) {
16
- return typeof value === 'number' ? `${value * progress}px` : `calc(${value} * ${progress})`;
17
- }
18
- function eraEase(node) {
19
- return getComputedStyle(node).getPropertyValue('--era-ease').trim() || undefined;
20
- }
21
- /**
22
- * Fly transition that reads Era's motion tokens from the rendered element.
23
- *
24
- * Svelte transition durations are JavaScript parameters, so hard-coded numbers
25
- * bypass the runtime `data-motion` axis. Reading `--era-duration` here keeps OS
26
- * overlays in sync with docs settings and library consumers that set motion on
27
- * an ancestor.
28
- */
29
- export function eraFly(node, { x = 0, y = 0, opacity = 0, delay = 0, duration = 150, easing } = {}) {
30
- const style = getComputedStyle(node);
31
- const targetOpacity = Number(style.opacity);
32
- const transform = style.transform === 'none' ? '' : style.transform;
33
- const cssEase = eraEase(node);
34
- return {
35
- delay,
36
- duration: eraDuration(node, duration),
37
- easing,
38
- css: (t, u) => `
39
- transform: ${transform} translate(${offset(x, u)}, ${offset(y, u)});
40
- opacity: ${targetOpacity - (targetOpacity - opacity) * u};
41
- ${cssEase ? `animation-timing-function: ${cssEase};` : ''}
42
- `
43
- };
44
- }
1
+ // Motion helpers moved to utils (they're layer-agnostic — the ui/ layer needs
2
+ // them too). Re-exported here so os-internal imports keep working.
3
+ export { eraFly } from '../utils/motion.js';
@@ -80,8 +80,10 @@
80
80
  // every registered app launches from the root view, `windows` drills into
81
81
  // the open windows (focus/restore — the recovery path when the window tabs
82
82
  // are hidden), and `workspace` drills into numbered workspaces + `new`.
83
- // Consumer `commands` append after the built-ins.
84
- const commandItems = $derived.by((): CommandBarItem[] => {
83
+ // Consumer `commands` append after the built-ins (merged in a thin outer
84
+ // derived, so a consumer-commands change doesn't rebuild the wm closures
85
+ // and vice-versa).
86
+ const builtinItems = $derived.by((): CommandBarItem[] => {
85
87
  const apps: CommandBarItem[] = Object.values(wm.apps).map((app) => ({
86
88
  value: `app:${app.id}`,
87
89
  label: app.title,
@@ -125,8 +127,9 @@
125
127
  }
126
128
  ]
127
129
  };
128
- return [...apps, windows, workspaces, ...commands];
130
+ return [...apps, windows, workspaces];
129
131
  });
132
+ const commandItems = $derived([...builtinItems, ...commands]);
130
133
  </script>
131
134
 
132
135
  <!-- Flush: three edges touch the shell; the exposed OUTER corners are the two
@@ -56,6 +56,32 @@
56
56
  }
57
57
  }
58
58
 
59
+ /* In-progress text shimmer — a bright sweep through muted text (the standard
60
+ * "working…" label treatment in streaming UIs). Timing derives from the motion
61
+ * axis (10× --era-duration ≈ 1.5s at normal, 2.6s at extra); at instant the
62
+ * duration is 0s and the sweep freezes on its base frame — plain muted text —
63
+ * so reduced-motion and data-motion="instant" both kill it for free. */
64
+ @utility era-shimmer {
65
+ background: linear-gradient(
66
+ 110deg,
67
+ var(--color-muted) 0% 43%,
68
+ var(--color-bright) 50%,
69
+ var(--color-muted) 57% 100%
70
+ );
71
+ background-size: 200% 100%;
72
+ background-clip: text;
73
+ -webkit-text-fill-color: transparent;
74
+ animation: era-shimmer calc(var(--era-duration) * 10) cubic-bezier(0.7, 0, 1, 0.4) infinite;
75
+ }
76
+ @keyframes era-shimmer {
77
+ 0% {
78
+ background-position: 100% 0;
79
+ }
80
+ 100% {
81
+ background-position: -100% 0;
82
+ }
83
+ }
84
+
59
85
  /* Register color tokens as Tailwind theme values for autocomplete and bare utility usage */
60
86
  @theme inline {
61
87
  /* Density-derived body size: `text-body` replaces text-[length:var(--era-text)] */
@@ -61,6 +61,12 @@
61
61
  });
62
62
  const selected = $derived(filtered[selectedIndex]);
63
63
  const open = $derived(focused || value !== '');
64
+ const hotkeyList = $derived(hotkey ? (Array.isArray(hotkey) ? hotkey : [hotkey]) : []);
65
+ // The focus hotkeys double as close keys while the bar is focused (a bound
66
+ // key is never part of a command — it toggles). Only single-character
67
+ // bindings qualify; chords/mod combos can't collide with typing. Escape
68
+ // always closes.
69
+ const closeKeys = $derived(new Set(['Escape', ...hotkeyList.filter((b) => b.length === 1)]));
64
70
 
65
71
  // zsh-style ghost completion: the selected item's remaining characters laid
66
72
  // under the caret (or the view placeholder while empty).
@@ -118,9 +124,7 @@
118
124
  e.preventDefault();
119
125
  complete(selected);
120
126
  }
121
- } else if (e.key === 'Escape' || e.key === '`') {
122
- // Backtick closes too: it's the focus hotkey, and a backtick is
123
- // never part of a command — so it toggles rather than types.
127
+ } else if (closeKeys.has(e.key)) {
124
128
  e.preventDefault();
125
129
  e.stopPropagation();
126
130
  reset();
@@ -139,10 +143,8 @@
139
143
  // acts (it blurs the field); every other binding is ignored so typing a
140
144
  // backtick in a text input never steals the caret.
141
145
  const bindings = $derived.by((): KeyBindingMap => {
142
- if (!hotkey) return {};
143
- const list = Array.isArray(hotkey) ? hotkey : [hotkey];
144
146
  return Object.fromEntries(
145
- list.map((b) => [
147
+ hotkeyList.map((b) => [
146
148
  b,
147
149
  (e: KeyboardEvent) => {
148
150
  if (isTextEntryFocused()) {
@@ -11,6 +11,7 @@ export * as Combobox from './combobox/index.js';
11
11
  export * as Collapsible from './collapsible/index.js';
12
12
  export * as Command from './command/index.js';
13
13
  export * from './command-bar/index.js';
14
+ export * as Step from './step/index.js';
14
15
  export * as ContextMenu from './context-menu/index.js';
15
16
  export * as DateField from './date-field/index.js';
16
17
  export * as DatePicker from './date-picker/index.js';
package/dist/ui/index.js CHANGED
@@ -11,6 +11,7 @@ export * as Combobox from './combobox/index.js';
11
11
  export * as Collapsible from './collapsible/index.js';
12
12
  export * as Command from './command/index.js';
13
13
  export * from './command-bar/index.js';
14
+ export * as Step from './step/index.js';
14
15
  export * as ContextMenu from './context-menu/index.js';
15
16
  export * as DateField from './date-field/index.js';
16
17
  export * as DatePicker from './date-picker/index.js';
@@ -0,0 +1,8 @@
1
+ /** The lifecycle of a step: not started, working, finished, failed. */
2
+ export type StepStatus = 'pending' | 'running' | 'done' | 'error';
3
+ type StepContext = {
4
+ readonly status: StepStatus;
5
+ };
6
+ export declare function setStepContext(ctx: StepContext): void;
7
+ export declare function getStepContext(): StepContext;
8
+ export {};
@@ -0,0 +1,8 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const KEY = Symbol('era-step');
3
+ export function setStepContext(ctx) {
4
+ setContext(KEY, ctx);
5
+ }
6
+ export function getStepContext() {
7
+ return getContext(KEY) ?? { status: 'done' };
8
+ }
@@ -0,0 +1,5 @@
1
+ export { default as Root } from './step-root.svelte';
2
+ export { default as Summary } from './step-summary.svelte';
3
+ export { default as Content } from './step-content.svelte';
4
+ export { default as Section } from './step-section.svelte';
5
+ export type { StepStatus } from './context.js';
@@ -0,0 +1,4 @@
1
+ export { default as Root } from './step-root.svelte';
2
+ export { default as Summary } from './step-summary.svelte';
3
+ export { default as Content } from './step-content.svelte';
4
+ export { default as Section } from './step-section.svelte';
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import { Collapsible } from 'bits-ui';
3
+ import { cn, type PartProps } from '../../utils/index.js';
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ children,
8
+ class: className,
9
+ ...restProps
10
+ }: PartProps<Collapsible.ContentProps> = $props();
11
+ </script>
12
+
13
+ <!-- Height animates on the outer (padding-free) element via bits' measured
14
+ content-height var — padding lives on the inner well so the animation
15
+ never fights box metrics. The well is recessed (shadow-well): payload the
16
+ step produced, not raised chrome. -->
17
+ <Collapsible.Content bind:ref data-era-step-content class="overflow-hidden" {...restProps}>
18
+ <div
19
+ class={cn(
20
+ 'mt-(--era-gap) mb-(--era-gap) flex min-w-0 flex-col gap-(--era-pad-sm) rounded-(--era-rd-md) bg-(--era-surface-bg) p-(--era-pad-sm) shadow-(--era-shadow-well)',
21
+ className
22
+ )}
23
+ >
24
+ {@render children?.()}
25
+ </div>
26
+ </Collapsible.Content>
27
+
28
+ <style>
29
+ :global([data-era-step-content][data-state='open']) {
30
+ animation: era-step-expand var(--era-duration) var(--era-ease);
31
+ }
32
+ :global([data-era-step-content][data-state='closed']) {
33
+ animation: era-step-collapse var(--era-duration) var(--era-ease);
34
+ }
35
+ @keyframes -global-era-step-expand {
36
+ from {
37
+ height: 0;
38
+ opacity: 0;
39
+ }
40
+ to {
41
+ height: var(--bits-collapsible-content-height);
42
+ opacity: 1;
43
+ }
44
+ }
45
+ @keyframes -global-era-step-collapse {
46
+ from {
47
+ height: var(--bits-collapsible-content-height);
48
+ opacity: 1;
49
+ }
50
+ to {
51
+ height: 0;
52
+ opacity: 0;
53
+ }
54
+ }
55
+ </style>
@@ -0,0 +1,5 @@
1
+ import { Collapsible } from 'bits-ui';
2
+ import { type PartProps } from '../../utils/index.js';
3
+ declare const StepContent: import("svelte").Component<PartProps<Collapsible.ContentProps>, {}, "ref">;
4
+ type StepContent = ReturnType<typeof StepContent>;
5
+ export default StepContent;
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ import { Collapsible } from 'bits-ui';
3
+ import { cn } from '../../utils/index.js';
4
+ import { setStepContext, type StepStatus } from './context.js';
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ open = $bindable(false),
9
+ status = 'done',
10
+ class: className,
11
+ ...restProps
12
+ }: Collapsible.RootProps & {
13
+ /** Drives the summary's icon and shimmer; exposed as data-status. */
14
+ status?: StepStatus;
15
+ } = $props();
16
+
17
+ // Shared with Summary via context (a getter, so status stays reactive).
18
+ setStepContext({
19
+ get status() {
20
+ return status;
21
+ }
22
+ });
23
+ </script>
24
+
25
+ <Collapsible.Root
26
+ bind:ref
27
+ bind:open
28
+ data-status={status}
29
+ class={cn('group/step flex w-full min-w-0 flex-col', className)}
30
+ {...restProps}
31
+ />
@@ -0,0 +1,9 @@
1
+ import { Collapsible } from 'bits-ui';
2
+ import { type StepStatus } from './context.js';
3
+ type $$ComponentProps = Collapsible.RootProps & {
4
+ /** Drives the summary's icon and shimmer; exposed as data-status. */
5
+ status?: StepStatus;
6
+ };
7
+ declare const StepRoot: import("svelte").Component<$$ComponentProps, {}, "ref" | "open">;
8
+ type StepRoot = ReturnType<typeof StepRoot>;
9
+ export default StepRoot;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '../../utils/index.js';
5
+
6
+ let {
7
+ label,
8
+ children,
9
+ class: className,
10
+ ...restProps
11
+ }: HTMLAttributes<HTMLDivElement> & {
12
+ /** Tiny uppercase section header — INPUT, OUTPUT, … */
13
+ label?: string;
14
+ children?: Snippet;
15
+ } = $props();
16
+ </script>
17
+
18
+ <div class={cn('flex min-w-0 flex-col gap-(--era-gap)', className)} {...restProps}>
19
+ {#if label}
20
+ <span class="text-[0.625rem] leading-none tracking-[0.15em] text-muted uppercase">{label}</span>
21
+ {/if}
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
+ /** Tiny uppercase section header — INPUT, OUTPUT, … */
5
+ label?: string;
6
+ children?: Snippet;
7
+ };
8
+ declare const StepSection: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type StepSection = ReturnType<typeof StepSection>;
10
+ export default StepSection;
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import { Collapsible } from 'bits-ui';
3
+ import LoaderCircle from '@lucide/svelte/icons/loader-circle';
4
+ import CircleCheck from '@lucide/svelte/icons/circle-check';
5
+ import CircleX from '@lucide/svelte/icons/circle-x';
6
+ import CircleDashed from '@lucide/svelte/icons/circle-dashed';
7
+ import ChevronDown from '@lucide/svelte/icons/chevron-down';
8
+ import { cn, type PartProps } from '../../utils/index.js';
9
+ import { getStepContext } from './context.js';
10
+
11
+ let {
12
+ ref = $bindable(null),
13
+ children,
14
+ class: className,
15
+ ...restProps
16
+ }: PartProps<Collapsible.TriggerProps> = $props();
17
+
18
+ const step = getStepContext();
19
+ </script>
20
+
21
+ <!-- The collapsed line: status icon, label (shimmering while running — the
22
+ shimmer freezes to plain muted text at instant motion), rotating chevron.
23
+ Quiet by design — a step reads as an aside in the conversation, not a
24
+ control; it brightens on hover to reveal it's expandable. The spinner is
25
+ a progress indicator, not decoration, so it spins at every motion level. -->
26
+ <Collapsible.Trigger
27
+ bind:ref
28
+ class={cn(
29
+ 'flex h-(--era-h-md) w-full min-w-0 cursor-pointer era-interactive items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body text-muted transition-colors duration-(--era-duration) ease-(--era-ease) hover:text-fg',
30
+ className
31
+ )}
32
+ {...restProps}
33
+ >
34
+ {#if step.status === 'running'}
35
+ <LoaderCircle class="size-(--era-h-xs) shrink-0 animate-spin" aria-hidden="true" />
36
+ {:else if step.status === 'done'}
37
+ <CircleCheck class="size-(--era-h-xs) shrink-0 text-success" aria-hidden="true" />
38
+ {:else if step.status === 'error'}
39
+ <CircleX class="size-(--era-h-xs) shrink-0 text-destructive" aria-hidden="true" />
40
+ {:else}
41
+ <CircleDashed class="size-(--era-h-xs) shrink-0" aria-hidden="true" />
42
+ {/if}
43
+ <span
44
+ class={cn(
45
+ 'flex min-w-0 flex-1 items-center gap-(--era-gap) truncate text-left',
46
+ step.status === 'running' && 'era-shimmer'
47
+ )}
48
+ >
49
+ {@render children?.()}
50
+ </span>
51
+ <ChevronDown
52
+ class="size-(--era-h-xs) shrink-0 transition-transform duration-(--era-duration) ease-(--era-ease) group-data-[state=open]/step:rotate-180"
53
+ aria-hidden="true"
54
+ />
55
+ </Collapsible.Trigger>
@@ -0,0 +1,5 @@
1
+ import { Collapsible } from 'bits-ui';
2
+ import { type PartProps } from '../../utils/index.js';
3
+ declare const StepSummary: import("svelte").Component<PartProps<Collapsible.TriggerProps>, {}, "ref">;
4
+ type StepSummary = ReturnType<typeof StepSummary>;
5
+ export default StepSummary;
@@ -14,3 +14,4 @@ export type PartProps<T> = WithoutChildrenOrChild<T> & {
14
14
  children?: Snippet;
15
15
  };
16
16
  export { hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey, type KeyBindingMap, type KeyBindingPress, type KeyBindingOptions } from './hotkeys.js';
17
+ export { eraFly } from './motion.js';
@@ -23,3 +23,4 @@ export function cn(...inputs) {
23
23
  /** `tv` preconfigured with this library's tailwind-merge settings. */
24
24
  export const tv = createTV({ twMergeConfig });
25
25
  export { hotkeys, keys, parseKeybinding, matchKeyBindingPress, createKeybindingsHandler, formatKeybinding, isApplePlatform, modKey } from './hotkeys.js';
26
+ export { eraFly } from './motion.js';
@@ -0,0 +1,19 @@
1
+ import type { EasingFunction, TransitionConfig } from 'svelte/transition';
2
+ type EraFlyParams = {
3
+ x?: number | string;
4
+ y?: number | string;
5
+ opacity?: number;
6
+ delay?: number;
7
+ duration?: number;
8
+ easing?: EasingFunction;
9
+ };
10
+ /**
11
+ * Fly transition that reads Era's motion tokens from the rendered element.
12
+ *
13
+ * Svelte transition durations are JavaScript parameters, so hard-coded numbers
14
+ * bypass the runtime `data-motion` axis. Reading `--era-duration` here keeps OS
15
+ * overlays in sync with docs settings and library consumers that set motion on
16
+ * an ancestor.
17
+ */
18
+ export declare function eraFly(node: Element, { x, y, opacity, delay, duration, easing }?: EraFlyParams): TransitionConfig;
19
+ export {};
@@ -0,0 +1,44 @@
1
+ const durationUnits = {
2
+ ms: 1,
3
+ s: 1000
4
+ };
5
+ function parseDuration(value) {
6
+ const match = value.trim().match(/^(-?\d*\.?\d+)(ms|s)$/i);
7
+ if (!match)
8
+ return undefined;
9
+ return Math.max(0, Number(match[1]) * durationUnits[match[2].toLowerCase()]);
10
+ }
11
+ function eraDuration(node, fallback) {
12
+ const token = getComputedStyle(node).getPropertyValue('--era-duration');
13
+ return parseDuration(token) ?? fallback;
14
+ }
15
+ function offset(value, progress) {
16
+ return typeof value === 'number' ? `${value * progress}px` : `calc(${value} * ${progress})`;
17
+ }
18
+ function eraEase(node) {
19
+ return getComputedStyle(node).getPropertyValue('--era-ease').trim() || undefined;
20
+ }
21
+ /**
22
+ * Fly transition that reads Era's motion tokens from the rendered element.
23
+ *
24
+ * Svelte transition durations are JavaScript parameters, so hard-coded numbers
25
+ * bypass the runtime `data-motion` axis. Reading `--era-duration` here keeps OS
26
+ * overlays in sync with docs settings and library consumers that set motion on
27
+ * an ancestor.
28
+ */
29
+ export function eraFly(node, { x = 0, y = 0, opacity = 0, delay = 0, duration = 150, easing } = {}) {
30
+ const style = getComputedStyle(node);
31
+ const targetOpacity = Number(style.opacity);
32
+ const transform = style.transform === 'none' ? '' : style.transform;
33
+ const cssEase = eraEase(node);
34
+ return {
35
+ delay,
36
+ duration: eraDuration(node, duration),
37
+ easing,
38
+ css: (t, u) => `
39
+ transform: ${transform} translate(${offset(x, u)}, ${offset(y, u)});
40
+ opacity: ${targetOpacity - (targetOpacity - opacity) * u};
41
+ ${cssEase ? `animation-timing-function: ${cssEase};` : ''}
42
+ `
43
+ };
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",
@@ -47,6 +47,16 @@
47
47
  "svelte": "./dist/ui/provider/index.js",
48
48
  "default": "./dist/ui/provider/index.js"
49
49
  },
50
+ "./ai": {
51
+ "types": "./dist/ai/index.d.ts",
52
+ "svelte": "./dist/ai/index.js",
53
+ "default": "./dist/ai/index.js"
54
+ },
55
+ "./ai/workflow": {
56
+ "types": "./dist/ai/workflow/index.d.ts",
57
+ "svelte": "./dist/ai/workflow/index.js",
58
+ "default": "./dist/ai/workflow/index.js"
59
+ },
50
60
  "./os": {
51
61
  "types": "./dist/os/index.d.ts",
52
62
  "svelte": "./dist/os/index.js",
@@ -77,6 +87,7 @@
77
87
  "@threlte/extras": "^9.21.0",
78
88
  "@types/node": "^24.13.2",
79
89
  "@types/three": "^0.184.1",
90
+ "@xyflow/svelte": "^1.6.2",
80
91
  "eslint": "^9.39.4",
81
92
  "eslint-config-prettier": "^10.1.8",
82
93
  "eslint-plugin-better-tailwindcss": "^4.6.1",
@@ -114,6 +125,8 @@
114
125
  "@neodrag/svelte": "^3.0.0-next.8",
115
126
  "bits-ui": "^2.18.1",
116
127
  "clsx": "^2.1.1",
128
+ "dompurify": "^3.4.12",
129
+ "marked": "^15.0.12",
117
130
  "tailwind-merge": "^3.6.0",
118
131
  "tailwind-variants": "^3.2.2",
119
132
  "vidstack": "^0.6.15"