@signal9/era-ui 2.8.2 → 2.9.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 (56) hide show
  1. package/dist/ai/confirmation/approval.svelte.d.ts +42 -0
  2. package/dist/ai/confirmation/approval.svelte.js +122 -0
  3. package/dist/ai/confirmation/confirmation-args.svelte +67 -0
  4. package/dist/ai/confirmation/confirmation-args.svelte.d.ts +8 -0
  5. package/dist/ai/confirmation/index.d.ts +2 -0
  6. package/dist/ai/confirmation/index.js +2 -0
  7. package/dist/ai/conversations/conversations-group.svelte +28 -0
  8. package/dist/ai/conversations/conversations-group.svelte.d.ts +10 -0
  9. package/dist/ai/conversations/conversations-item.svelte +208 -0
  10. package/dist/ai/conversations/conversations-item.svelte.d.ts +28 -0
  11. package/dist/ai/conversations/conversations-root.svelte +27 -0
  12. package/dist/ai/conversations/conversations-root.svelte.d.ts +10 -0
  13. package/dist/ai/conversations/conversations.svelte.d.ts +38 -0
  14. package/dist/ai/conversations/conversations.svelte.js +73 -0
  15. package/dist/ai/conversations/index.d.ts +5 -0
  16. package/dist/ai/conversations/index.js +4 -0
  17. package/dist/ai/error-panel/categorize-error.d.ts +32 -0
  18. package/dist/ai/error-panel/categorize-error.js +106 -0
  19. package/dist/ai/error-panel/error-panel.svelte +130 -0
  20. package/dist/ai/error-panel/error-panel.svelte.d.ts +17 -0
  21. package/dist/ai/error-panel/index.d.ts +2 -0
  22. package/dist/ai/error-panel/index.js +2 -0
  23. package/dist/ai/index.d.ts +11 -0
  24. package/dist/ai/index.js +7 -0
  25. package/dist/ai/message/index.d.ts +2 -0
  26. package/dist/ai/message/index.js +1 -0
  27. package/dist/ai/message/message-usage.svelte +126 -0
  28. package/dist/ai/message/message-usage.svelte.d.ts +22 -0
  29. package/dist/ai/project.d.ts +64 -0
  30. package/dist/ai/project.js +70 -0
  31. package/dist/ai/run-status/index.d.ts +2 -0
  32. package/dist/ai/run-status/index.js +1 -0
  33. package/dist/ai/run-status/run-status.svelte +132 -0
  34. package/dist/ai/run-status/run-status.svelte.d.ts +26 -0
  35. package/dist/ai/runtime-feed/index.d.ts +2 -0
  36. package/dist/ai/runtime-feed/index.js +2 -0
  37. package/dist/ai/runtime-feed/runtime-feed-item.svelte +64 -0
  38. package/dist/ai/runtime-feed/runtime-feed-item.svelte.d.ts +27 -0
  39. package/dist/ai/runtime-feed/runtime-feed-root.svelte +35 -0
  40. package/dist/ai/runtime-feed/runtime-feed-root.svelte.d.ts +13 -0
  41. package/dist/ai/structured-value/index.d.ts +1 -0
  42. package/dist/ai/structured-value/index.js +1 -0
  43. package/dist/ai/structured-value/structured-value.svelte +130 -0
  44. package/dist/ai/structured-value/structured-value.svelte.d.ts +10 -0
  45. package/dist/ai/tool/index.d.ts +3 -0
  46. package/dist/ai/tool/index.js +3 -0
  47. package/dist/ai/tool/tool-chip.svelte +66 -0
  48. package/dist/ai/tool/tool-chip.svelte.d.ts +17 -0
  49. package/dist/ai/tool/tool-chips.svelte +56 -0
  50. package/dist/ai/tool/tool-chips.svelte.d.ts +13 -0
  51. package/dist/ai/tool/tool-exec.svelte +49 -0
  52. package/dist/ai/tool/tool-exec.svelte.d.ts +11 -0
  53. package/dist/era-ui.css +1 -1
  54. package/dist/ui/badge/badge.svelte +3 -2
  55. package/dist/ui/badge/badge.svelte.d.ts +4 -1
  56. package/package.json +1 -1
@@ -0,0 +1,132 @@
1
+ <script lang="ts" module>
2
+ /** Aggregate run lifecycle. `running` pulses; the three active states
3
+ * (queued/running/awaiting) surface the Cancel affordance. */
4
+ export type RunState = 'idle' | 'queued' | 'running' | 'awaiting' | 'done' | 'error';
5
+
6
+ /** Tone for the latest-log line — reuses the era semantic palette. */
7
+ export type LogTone = 'default' | 'muted' | 'success' | 'warning' | 'destructive';
8
+
9
+ export interface RunTools {
10
+ complete: number;
11
+ total: number;
12
+ running?: number;
13
+ failed?: number;
14
+ }
15
+
16
+ interface StatusStyle {
17
+ label: string;
18
+ dot: string;
19
+ text: string;
20
+ active: boolean;
21
+ pulse: boolean;
22
+ }
23
+
24
+ const STATUS: Record<RunState, StatusStyle> = {
25
+ idle: { label: 'idle', dot: 'bg-muted', text: 'text-muted', active: false, pulse: false },
26
+ queued: { label: 'queued', dot: 'bg-muted', text: 'text-muted', active: true, pulse: false },
27
+ running: { label: 'running', dot: 'bg-primary', text: 'text-fg', active: true, pulse: true },
28
+ awaiting: {
29
+ label: 'awaiting',
30
+ dot: 'bg-warning',
31
+ text: 'text-warning',
32
+ active: true,
33
+ pulse: false
34
+ },
35
+ done: { label: 'done', dot: 'bg-success', text: 'text-success', active: false, pulse: false },
36
+ error: {
37
+ label: 'error',
38
+ dot: 'bg-destructive',
39
+ text: 'text-destructive',
40
+ active: false,
41
+ pulse: false
42
+ }
43
+ };
44
+
45
+ const LOG_TONE: Record<LogTone, string> = {
46
+ default: 'text-fg',
47
+ muted: 'text-muted',
48
+ success: 'text-success',
49
+ warning: 'text-warning',
50
+ destructive: 'text-destructive'
51
+ };
52
+ </script>
53
+
54
+ <script lang="ts">
55
+ import type { HTMLAttributes } from 'svelte/elements';
56
+ import X from '@lucide/svelte/icons/x';
57
+ import { Badge } from '../../ui/badge/index.js';
58
+ import { Button } from '../../ui/button/index.js';
59
+ import { cn } from '../../utils/index.js';
60
+
61
+ let {
62
+ status,
63
+ tools,
64
+ approvals,
65
+ agents,
66
+ latest,
67
+ oncancel,
68
+ class: className,
69
+ ...restProps
70
+ }: HTMLAttributes<HTMLDivElement> & {
71
+ status: RunState;
72
+ tools?: RunTools;
73
+ approvals?: number;
74
+ agents?: number;
75
+ latest?: { tone: LogTone; text: string };
76
+ oncancel?: () => void;
77
+ } = $props();
78
+
79
+ const style = $derived(STATUS[status]);
80
+ </script>
81
+
82
+ <div
83
+ role="status"
84
+ aria-live="polite"
85
+ class={cn(
86
+ 'flex flex-col rounded-(--era-rd-lg) bg-(--era-surface-bg) shadow-(--era-shadow)',
87
+ className
88
+ )}
89
+ {...restProps}
90
+ >
91
+ <div class="flex h-(--era-h-md) items-center gap-(--era-gap) px-(--era-inset-md)">
92
+ <!-- leading status dot + label -->
93
+ <span class="flex items-center gap-(--era-inset-xxs) font-mono text-body {style.text} era-text-trim">
94
+ <span class={cn('size-1.5 shrink-0 rounded-full', style.dot, style.pulse && 'animate-pulse')}></span>
95
+ {style.label}
96
+ </span>
97
+
98
+ {#if tools}
99
+ <Badge>{tools.complete}/{tools.total} tools</Badge>
100
+ {#if tools.running}<Badge tone="accent">+{tools.running}</Badge>{/if}
101
+ {#if tools.failed}<Badge tone="destructive">{tools.failed} failed</Badge>{/if}
102
+ {/if}
103
+
104
+ {#if approvals}
105
+ <Badge tone="warning">{approvals} approval</Badge>
106
+ {/if}
107
+
108
+ {#if agents}
109
+ <Badge tone="accent">{agents} agents</Badge>
110
+ {/if}
111
+
112
+ <div class="flex-1"></div>
113
+
114
+ {#if style.active && oncancel}
115
+ <Button size="xs" tone="destructive" onclick={() => oncancel?.()}>
116
+ <X class="size-(--era-h-xs)" aria-hidden="true" />
117
+ Cancel
118
+ </Button>
119
+ {/if}
120
+ </div>
121
+
122
+ {#if latest}
123
+ <div
124
+ class={cn(
125
+ 'truncate px-(--era-inset-md) pb-(--era-inset-sm) font-mono text-body era-text-trim',
126
+ LOG_TONE[latest.tone]
127
+ )}
128
+ >
129
+ {latest.text}
130
+ </div>
131
+ {/if}
132
+ </div>
@@ -0,0 +1,26 @@
1
+ /** Aggregate run lifecycle. `running` pulses; the three active states
2
+ * (queued/running/awaiting) surface the Cancel affordance. */
3
+ export type RunState = 'idle' | 'queued' | 'running' | 'awaiting' | 'done' | 'error';
4
+ /** Tone for the latest-log line — reuses the era semantic palette. */
5
+ export type LogTone = 'default' | 'muted' | 'success' | 'warning' | 'destructive';
6
+ export interface RunTools {
7
+ complete: number;
8
+ total: number;
9
+ running?: number;
10
+ failed?: number;
11
+ }
12
+ import type { HTMLAttributes } from 'svelte/elements';
13
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
14
+ status: RunState;
15
+ tools?: RunTools;
16
+ approvals?: number;
17
+ agents?: number;
18
+ latest?: {
19
+ tone: LogTone;
20
+ text: string;
21
+ };
22
+ oncancel?: () => void;
23
+ };
24
+ declare const RunStatus: import("svelte").Component<$$ComponentProps, {}, "">;
25
+ type RunStatus = ReturnType<typeof RunStatus>;
26
+ export default RunStatus;
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './runtime-feed-root.svelte';
2
+ export { default as Item, type RuntimeTone, type RuntimeFeedItemData } from './runtime-feed-item.svelte';
@@ -0,0 +1,2 @@
1
+ export { default as Root } from './runtime-feed-root.svelte';
2
+ export { default as Item } from './runtime-feed-item.svelte';
@@ -0,0 +1,64 @@
1
+ <script lang="ts" module>
2
+ /** Tone of a runtime-feed line — maps to a text/dot color. */
3
+ export type RuntimeTone = 'log' | 'progress' | 'complete' | 'warn' | 'error';
4
+
5
+ /** A single line in a RuntimeFeed when driven by the `items` prop. */
6
+ export interface RuntimeFeedItemData {
7
+ /** Stable key for the `{#each}`. */
8
+ id?: string | number;
9
+ /** Tone color of the dot + line. */
10
+ tone?: RuntimeTone;
11
+ /** Optional trailing relative-time / label. */
12
+ label?: string;
13
+ /** The line text. */
14
+ text: string;
15
+ }
16
+
17
+ const TONE: Record<RuntimeTone, string> = {
18
+ log: 'text-muted',
19
+ progress: 'text-fg',
20
+ complete: 'text-success',
21
+ warn: 'text-warning',
22
+ error: 'text-destructive'
23
+ };
24
+ </script>
25
+
26
+ <script lang="ts">
27
+ import type { Snippet } from 'svelte';
28
+ import type { HTMLAttributes } from 'svelte/elements';
29
+ import { cn } from '../../utils/index.js';
30
+
31
+ let {
32
+ tone = 'log',
33
+ label,
34
+ text,
35
+ children,
36
+ class: className,
37
+ ...restProps
38
+ }: HTMLAttributes<HTMLDivElement> & {
39
+ /** Tone color of the leading dot and the line. */
40
+ tone?: RuntimeTone;
41
+ /** Optional trailing relative-time / label. */
42
+ label?: string;
43
+ /** Line text (alternative to `children`). */
44
+ text?: string;
45
+ children?: Snippet;
46
+ } = $props();
47
+ </script>
48
+
49
+ <!-- One live line: a tone dot that inherits the row color via bg-current, the
50
+ truncated message, and an optional trailing timestamp/label. Single-line by
51
+ design (the tail scrolls, it doesn't wrap) so items-center + era-text-trim
52
+ keep the dot and text on one optical baseline. -->
53
+ <div
54
+ class={cn('flex items-center gap-(--era-gap) text-body', TONE[tone], className)}
55
+ {...restProps}
56
+ >
57
+ <span class="size-(--era-h-xxs) shrink-0 rounded-full bg-current" aria-hidden="true"></span>
58
+ <span class="min-w-0 flex-1 truncate era-text-trim">
59
+ {#if children}{@render children()}{:else}{text}{/if}
60
+ </span>
61
+ {#if label}
62
+ <span class="shrink-0 text-muted tabular-nums era-text-trim">{label}</span>
63
+ {/if}
64
+ </div>
@@ -0,0 +1,27 @@
1
+ /** Tone of a runtime-feed line — maps to a text/dot color. */
2
+ export type RuntimeTone = 'log' | 'progress' | 'complete' | 'warn' | 'error';
3
+ /** A single line in a RuntimeFeed when driven by the `items` prop. */
4
+ export interface RuntimeFeedItemData {
5
+ /** Stable key for the `{#each}`. */
6
+ id?: string | number;
7
+ /** Tone color of the dot + line. */
8
+ tone?: RuntimeTone;
9
+ /** Optional trailing relative-time / label. */
10
+ label?: string;
11
+ /** The line text. */
12
+ text: string;
13
+ }
14
+ import type { Snippet } from 'svelte';
15
+ import type { HTMLAttributes } from 'svelte/elements';
16
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
17
+ /** Tone color of the leading dot and the line. */
18
+ tone?: RuntimeTone;
19
+ /** Optional trailing relative-time / label. */
20
+ label?: string;
21
+ /** Line text (alternative to `children`). */
22
+ text?: string;
23
+ children?: Snippet;
24
+ };
25
+ declare const RuntimeFeedItem: import("svelte").Component<$$ComponentProps, {}, "">;
26
+ type RuntimeFeedItem = ReturnType<typeof RuntimeFeedItem>;
27
+ export default RuntimeFeedItem;
@@ -0,0 +1,35 @@
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
+ import Item, { type RuntimeFeedItemData } from './runtime-feed-item.svelte';
6
+
7
+ let {
8
+ items,
9
+ limit,
10
+ children,
11
+ class: className,
12
+ ...restProps
13
+ }: HTMLAttributes<HTMLDivElement> & {
14
+ /** Drive the feed declaratively; alternatively compose `Item` children. */
15
+ items?: RuntimeFeedItemData[];
16
+ /** Cap the tail to the most recent N lines. */
17
+ limit?: number;
18
+ children?: Snippet;
19
+ } = $props();
20
+
21
+ // The running-tail feel: keep only the last `limit` lines when capped.
22
+ const visible = $derived(
23
+ items && limit !== undefined ? items.slice(-limit) : (items ?? [])
24
+ );
25
+ </script>
26
+
27
+ <div class={cn('flex min-w-0 flex-col gap-(--era-gap)', className)} {...restProps}>
28
+ {#if items}
29
+ {#each visible as item, i (item.id ?? i)}
30
+ <Item tone={item.tone} label={item.label} text={item.text} />
31
+ {/each}
32
+ {:else}
33
+ {@render children?.()}
34
+ {/if}
35
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { type RuntimeFeedItemData } from './runtime-feed-item.svelte';
4
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
5
+ /** Drive the feed declaratively; alternatively compose `Item` children. */
6
+ items?: RuntimeFeedItemData[];
7
+ /** Cap the tail to the most recent N lines. */
8
+ limit?: number;
9
+ children?: Snippet;
10
+ };
11
+ declare const RuntimeFeedRoot: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type RuntimeFeedRoot = ReturnType<typeof RuntimeFeedRoot>;
13
+ export default RuntimeFeedRoot;
@@ -0,0 +1 @@
1
+ export { default as Root } from './structured-value.svelte';
@@ -0,0 +1 @@
1
+ export { default as Root } from './structured-value.svelte';
@@ -0,0 +1,130 @@
1
+ <script lang="ts" module>
2
+ /** Beyond this depth the tree stops recursing and shows an ellipsis. */
3
+ const MAX_DEPTH = 16;
4
+
5
+ type Primitive = string | number | boolean | null | undefined;
6
+
7
+ function isRecord(input: unknown): input is Record<string, unknown> {
8
+ return typeof input === 'object' && input !== null && !Array.isArray(input);
9
+ }
10
+
11
+ function isPrimitive(input: unknown): input is Primitive {
12
+ return (
13
+ input === null || input === undefined || ['string', 'number', 'boolean'].includes(typeof input)
14
+ );
15
+ }
16
+
17
+ /** An array is "simple" when every entry is a primitive — rendered inline. */
18
+ function isSimpleArray(input: unknown[]): boolean {
19
+ return input.every((entry) => isPrimitive(entry));
20
+ }
21
+
22
+ /** Values that sit on the same line as their key/index rather than nesting. */
23
+ function isInline(input: unknown): boolean {
24
+ if (isPrimitive(input)) return true;
25
+ if (Array.isArray(input)) return input.length === 0 || isSimpleArray(input);
26
+ if (isRecord(input)) return Object.keys(input).length === 0;
27
+ return true;
28
+ }
29
+
30
+ function formatPrimitive(input: unknown): string {
31
+ if (input === null || input === undefined) return 'null';
32
+ if (typeof input === 'boolean') return input ? 'true' : 'false';
33
+ return String(input);
34
+ }
35
+
36
+ function summarize(input: unknown[]): string {
37
+ return input.map(formatPrimitive).join(', ');
38
+ }
39
+ </script>
40
+
41
+ <script lang="ts">
42
+ import type { HTMLAttributes } from 'svelte/elements';
43
+ import { cn } from '../../utils/index.js';
44
+ import Self from './structured-value.svelte';
45
+
46
+ let {
47
+ value,
48
+ depth = 0,
49
+ class: className,
50
+ ...restProps
51
+ }: HTMLAttributes<HTMLDivElement> & {
52
+ /** Arbitrary JSON-ish value to render as a key/value tree. */
53
+ value: unknown;
54
+ /** Internal recursion counter — leave unset at the call site. */
55
+ depth?: number;
56
+ } = $props();
57
+ </script>
58
+
59
+ {#snippet tag(kind: 'key' | 'index', label: string)}
60
+ {#if kind === 'index'}
61
+ <span
62
+ class="shrink-0 text-[0.625rem] leading-none tracking-[0.15em] text-muted uppercase"
63
+ >{label}</span
64
+ >
65
+ {:else}
66
+ <span class="shrink-0 text-muted era-text-trim">{label}:</span>
67
+ {/if}
68
+ {/snippet}
69
+
70
+ {#snippet leaf(v: unknown)}
71
+ {#if v === null || v === undefined}
72
+ <span class="min-w-0 truncate text-muted era-text-trim">null</span>
73
+ {:else if typeof v === 'boolean'}
74
+ <span class="min-w-0 truncate text-warning era-text-trim">{v ? 'true' : 'false'}</span>
75
+ {:else if typeof v === 'number'}
76
+ <span class="min-w-0 truncate text-fg tabular-nums era-text-trim">{v}</span>
77
+ {:else if typeof v === 'string'}
78
+ <span class="min-w-0 truncate text-fg era-text-trim" title={v}>{v}</span>
79
+ {:else if Array.isArray(v)}
80
+ {#if v.length === 0}
81
+ <span class="min-w-0 truncate text-muted era-text-trim">empty</span>
82
+ {:else}
83
+ <span class="min-w-0 truncate text-fg era-text-trim" title={summarize(v)}>{summarize(v)}</span>
84
+ {/if}
85
+ {:else}
86
+ <span class="min-w-0 truncate text-muted era-text-trim">empty</span>
87
+ {/if}
88
+ {/snippet}
89
+
90
+ {#snippet row(kind: 'key' | 'index', label: string, v: unknown)}
91
+ {#if isInline(v)}
92
+ <div class="flex min-w-0 items-baseline gap-(--era-gap)">
93
+ {@render tag(kind, label)}
94
+ {@render leaf(v)}
95
+ </div>
96
+ {:else}
97
+ <div class="flex min-w-0 flex-col gap-(--era-gap)">
98
+ {@render tag(kind, label)}
99
+ <div class="border-l border-divider pl-(--era-inset-md)">
100
+ <Self value={v} depth={depth + 1} />
101
+ </div>
102
+ </div>
103
+ {/if}
104
+ {/snippet}
105
+
106
+ <div class={cn('flex min-w-0 flex-col gap-(--era-gap) text-body', className)} {...restProps}>
107
+ {#if depth > MAX_DEPTH}
108
+ <span class="text-muted era-text-trim">…</span>
109
+ {:else if isRecord(value)}
110
+ {#if Object.keys(value).length === 0}
111
+ <span class="text-muted era-text-trim">empty</span>
112
+ {:else}
113
+ {#each Object.entries(value) as [key, entry] (key)}
114
+ {@render row('key', key, entry)}
115
+ {/each}
116
+ {/if}
117
+ {:else if Array.isArray(value)}
118
+ {#if value.length === 0}
119
+ <span class="text-muted era-text-trim">empty</span>
120
+ {:else if isSimpleArray(value)}
121
+ {@render leaf(value)}
122
+ {:else}
123
+ {#each value as item, index (index)}
124
+ {@render row('index', String(index), item)}
125
+ {/each}
126
+ {/if}
127
+ {:else}
128
+ {@render leaf(value)}
129
+ {/if}
130
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
3
+ /** Arbitrary JSON-ish value to render as a key/value tree. */
4
+ value: unknown;
5
+ /** Internal recursion counter — leave unset at the call site. */
6
+ depth?: number;
7
+ };
8
+ declare const StructuredValue: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type StructuredValue = ReturnType<typeof StructuredValue>;
10
+ export default StructuredValue;
@@ -2,4 +2,7 @@ export { default as Root, type ToolState } from './tool.svelte';
2
2
  export { default as Header } from './tool-header.svelte';
3
3
  export { default as Input } from './tool-input.svelte';
4
4
  export { default as Output } from './tool-output.svelte';
5
+ export { default as Chip } from './tool-chip.svelte';
6
+ export { default as Chips } from './tool-chips.svelte';
7
+ export { default as Exec } from './tool-exec.svelte';
5
8
  export { Content } from '../../ui/step/index.js';
@@ -2,4 +2,7 @@ export { default as Root } from './tool.svelte';
2
2
  export { default as Header } from './tool-header.svelte';
3
3
  export { default as Input } from './tool-input.svelte';
4
4
  export { default as Output } from './tool-output.svelte';
5
+ export { default as Chip } from './tool-chip.svelte';
6
+ export { default as Chips } from './tool-chips.svelte';
7
+ export { default as Exec } from './tool-exec.svelte';
5
8
  export { Content } from '../../ui/step/index.js';
@@ -0,0 +1,66 @@
1
+ <script lang="ts">
2
+ import type { Component, Snippet } from 'svelte';
3
+ import Wrench from '@lucide/svelte/icons/wrench';
4
+ import { cn } from '../../utils/index.js';
5
+ import type { ToolState } from './tool.svelte';
6
+
7
+ let {
8
+ state: toolState = 'input-streaming',
9
+ active = false,
10
+ icon: Icon = Wrench,
11
+ onclick,
12
+ children,
13
+ class: className,
14
+ ...restProps
15
+ }: {
16
+ /** AI-SDK tool lifecycle — drives the pulse and the failure dot. */
17
+ state?: ToolState;
18
+ /** Selected: this chip owns the shared expansion area. */
19
+ active?: boolean;
20
+ /** Leading glyph; defaults to a wrench. */
21
+ icon?: Component;
22
+ onclick?: () => void;
23
+ /** The chip label — the tool name. */
24
+ children?: Snippet;
25
+ class?: string;
26
+ } = $props();
27
+
28
+ // Same mapping tool.svelte uses to drive Step status.
29
+ const status = $derived(
30
+ (
31
+ {
32
+ 'input-streaming': 'pending',
33
+ 'input-available': 'running',
34
+ 'output-available': 'done',
35
+ 'output-error': 'error'
36
+ } as const
37
+ )[toolState]
38
+ );
39
+ </script>
40
+
41
+ <!-- Compact horizontal tool call: a quiet pill that brightens on hover and
42
+ seats an active state (bg-hover) when it owns the expansion. While running
43
+ the icon pulses and the label shimmers — both collapse to static at instant
44
+ motion for free. A trailing destructive dot marks a failed call. -->
45
+ <button
46
+ type="button"
47
+ data-status={status}
48
+ {onclick}
49
+ class={cn(
50
+ 'inline-flex h-(--era-h-xxs) min-w-0 max-w-full shrink-0 cursor-pointer era-interactive items-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) text-body text-muted transition-colors duration-(--era-duration) ease-(--era-ease) hover:bg-(--era-highlight) hover:text-fg era-text-trim',
51
+ active && 'bg-hover text-fg',
52
+ className
53
+ )}
54
+ {...restProps}
55
+ >
56
+ <Icon
57
+ class={cn('size-(--era-h-xs) shrink-0', status === 'running' && 'animate-pulse')}
58
+ aria-hidden="true"
59
+ />
60
+ <span class={cn('min-w-0 truncate', status === 'running' && 'era-shimmer')}>
61
+ {@render children?.()}
62
+ </span>
63
+ {#if status === 'error'}
64
+ <span class="size-(--era-gap) shrink-0 rounded-full bg-destructive" aria-hidden="true"></span>
65
+ {/if}
66
+ </button>
@@ -0,0 +1,17 @@
1
+ import type { Component, Snippet } from 'svelte';
2
+ import type { ToolState } from './tool.svelte';
3
+ type $$ComponentProps = {
4
+ /** AI-SDK tool lifecycle — drives the pulse and the failure dot. */
5
+ state?: ToolState;
6
+ /** Selected: this chip owns the shared expansion area. */
7
+ active?: boolean;
8
+ /** Leading glyph; defaults to a wrench. */
9
+ icon?: Component;
10
+ onclick?: () => void;
11
+ /** The chip label — the tool name. */
12
+ children?: Snippet;
13
+ class?: string;
14
+ };
15
+ declare const ToolChip: Component<$$ComponentProps, {}, "">;
16
+ type ToolChip = ReturnType<typeof ToolChip>;
17
+ export default ToolChip;
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../../utils/index.js';
4
+
5
+ let {
6
+ streaming = false,
7
+ children,
8
+ expanded,
9
+ class: className
10
+ }: {
11
+ /** Appends a pulsing accent cursor after the last chip while streaming. */
12
+ streaming?: boolean;
13
+ /** The chips — a run of `Tool.Chip`s. */
14
+ children?: Snippet;
15
+ /** Shared detail area, rendered below the row when a chip is active. */
16
+ expanded?: Snippet;
17
+ class?: string;
18
+ } = $props();
19
+ </script>
20
+
21
+ <!-- Alternate compact layout to the vertical Step expander: chips wrap in a row,
22
+ hairline-separated (a faded left rule on each chip after the first — the
23
+ cursor is excluded), with one shared expansion area below. -->
24
+ <div class={cn('flex min-w-0 flex-col', className)}>
25
+ <div
26
+ class="flex flex-wrap items-center gap-(--era-gap) [&>*:not(:first-child):not([data-chips-cursor])]:border-l [&>*:not(:first-child):not([data-chips-cursor])]:border-divider-faded [&>*:not(:first-child):not([data-chips-cursor])]:pl-(--era-gap)"
27
+ >
28
+ {@render children?.()}
29
+ {#if streaming}
30
+ <span
31
+ data-chips-cursor
32
+ class="size-(--era-gap) shrink-0 rounded-full bg-primary"
33
+ aria-hidden="true"
34
+ ></span>
35
+ {/if}
36
+ </div>
37
+ {#if expanded}
38
+ <div class="mt-(--era-gap) min-w-0">
39
+ {@render expanded()}
40
+ </div>
41
+ {/if}
42
+ </div>
43
+
44
+ <style>
45
+ /* Stream-head cursor pulse — timing derives from the motion axis (6×
46
+ --era-duration). At instant the duration is 0s and the dot freezes fully
47
+ opaque, so reduced-motion and data-motion="instant" both still it. */
48
+ [data-chips-cursor] {
49
+ animation: era-chips-cursor calc(var(--era-duration) * 6) steps(2, start) infinite;
50
+ }
51
+ @keyframes era-chips-cursor {
52
+ 50% {
53
+ opacity: 0;
54
+ }
55
+ }
56
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ /** Appends a pulsing accent cursor after the last chip while streaming. */
4
+ streaming?: boolean;
5
+ /** The chips — a run of `Tool.Chip`s. */
6
+ children?: Snippet;
7
+ /** Shared detail area, rendered below the row when a chip is active. */
8
+ expanded?: Snippet;
9
+ class?: string;
10
+ };
11
+ declare const ToolChips: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type ToolChips = ReturnType<typeof ToolChips>;
13
+ export default ToolChips;