@signal9/era-ui 2.22.0 → 2.23.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.
- package/dist/ai/artifact/artifact-actions.svelte +3 -1
- package/dist/ai/artifact/artifact-content.svelte +3 -1
- package/dist/ai/artifact/artifact-header.svelte +5 -1
- package/dist/ai/artifact/artifact.svelte +5 -1
- package/dist/ai/chain-of-thought/chain-of-thought-step.svelte +5 -1
- package/dist/ai/chain-of-thought/chain-of-thought.svelte +1 -1
- package/dist/ai/confirmation/confirmation-args.svelte +7 -7
- package/dist/ai/conversation/conversation-content.svelte +4 -1
- package/dist/ai/conversations/conversations-group.svelte +1 -1
- package/dist/ai/conversations/conversations.svelte.js +5 -1
- package/dist/ai/error-panel/error-panel.svelte +1 -1
- package/dist/ai/inline-citation/inline-citation.svelte +22 -5
- package/dist/ai/loader.svelte +4 -1
- package/dist/ai/message/message-usage.svelte +4 -2
- package/dist/ai/open-in-chat/open-in-chat.svelte +16 -4
- package/dist/ai/plan/plan-header.svelte +1 -1
- package/dist/ai/project.js +10 -2
- package/dist/ai/prompt-input/prompt-input-attachments.svelte +1 -1
- package/dist/ai/prompt-input/prompt-input.svelte +2 -6
- package/dist/ai/run-status/run-status.svelte +5 -2
- package/dist/ai/runtime-feed/runtime-feed-item.svelte +1 -1
- package/dist/ai/runtime-feed/runtime-feed-root.svelte +1 -3
- package/dist/ai/structured-value/structured-value.svelte +8 -7
- package/dist/ai/suggestion/suggestions.svelte +4 -1
- package/dist/ai/task/task-content.svelte +4 -1
- package/dist/ai/tool/tool-chip.svelte +1 -1
- package/dist/ai/tool/tool-exec.svelte +1 -3
- package/dist/ai/tool/tool.svelte +1 -4
- package/dist/ai/web-preview/web-preview.svelte +1 -1
- package/dist/ai/workflow/node.svelte +3 -1
- package/dist/dev/axis-signature.d.ts +17 -0
- package/dist/dev/axis-signature.js +87 -0
- package/dist/dev/index.d.ts +1 -0
- package/dist/dev/index.js +1 -0
- package/dist/os/window.svelte +1 -1
- package/dist/styles/typography.css +9 -1
- package/dist/ui/button/button.svelte +10 -0
- package/dist/ui/card/card.svelte +3 -1
- package/dist/ui/command/command-input.svelte +1 -1
- package/dist/ui/command/command-item.svelte +1 -1
- package/dist/ui/command/command-link-item.svelte +1 -1
- package/dist/ui/command-bar/command-bar.svelte +2 -2
- package/dist/ui/copy-button/copy-button.svelte +3 -1
- package/dist/ui/input/input.svelte +1 -1
- package/dist/ui/kv/kv.svelte +1 -1
- package/dist/ui/pane/pane-close.svelte +9 -1
- package/dist/ui/select/select-trigger.svelte +1 -1
- package/dist/ui/step/step-summary.svelte +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +9 -9
|
@@ -4,4 +4,6 @@
|
|
|
4
4
|
let { children, class: className }: { children?: Snippet; class?: string } = $props();
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
|
-
<div class={cn('flex shrink-0 items-center gap-(--era-gap)', className)}>
|
|
7
|
+
<div class={cn('flex shrink-0 items-center gap-(--era-gap)', className)}>
|
|
8
|
+
{@render children?.()}
|
|
9
|
+
</div>
|
|
@@ -4,4 +4,6 @@
|
|
|
4
4
|
let { children, class: className }: { children?: Snippet; class?: string } = $props();
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
|
-
<div class={cn('min-h-0 flex-1 overflow-auto p-(--era-pad-md)', className)}>
|
|
7
|
+
<div class={cn('min-h-0 flex-1 overflow-auto p-(--era-pad-md)', className)}>
|
|
8
|
+
{@render children?.()}
|
|
9
|
+
</div>
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { cn } from '../../utils/index.js';
|
|
5
|
-
let {
|
|
5
|
+
let {
|
|
6
|
+
children,
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
|
|
6
10
|
</script>
|
|
7
11
|
|
|
8
12
|
<div
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { cn } from '../../utils/index.js';
|
|
5
|
-
let {
|
|
5
|
+
let {
|
|
6
|
+
children,
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
|
|
6
10
|
</script>
|
|
7
11
|
|
|
8
12
|
<div
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
<Icon
|
|
27
27
|
class={cn(
|
|
28
28
|
'size-(--era-h-xs) shrink-0',
|
|
29
|
-
status === 'active'
|
|
29
|
+
status === 'active'
|
|
30
|
+
? 'text-bright'
|
|
31
|
+
: status === 'pending'
|
|
32
|
+
? 'text-muted opacity-50'
|
|
33
|
+
: 'text-muted'
|
|
30
34
|
)}
|
|
31
35
|
aria-hidden="true"
|
|
32
36
|
/>
|
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
import { Switch } from '../../ui/switch/index.js';
|
|
5
5
|
import { ApprovalController, getApprovalContext } from './approval.svelte.js';
|
|
6
6
|
|
|
7
|
-
let {
|
|
8
|
-
|
|
9
|
-
class: className
|
|
10
|
-
}: { controller?: ApprovalController; class?: string } = $props();
|
|
7
|
+
let { controller, class: className }: { controller?: ApprovalController; class?: string } =
|
|
8
|
+
$props();
|
|
11
9
|
|
|
12
10
|
const ctrl = $derived(controller ?? getApprovalContext());
|
|
13
11
|
|
|
@@ -27,10 +25,12 @@
|
|
|
27
25
|
<div
|
|
28
26
|
class={cn(
|
|
29
27
|
'flex min-w-0 flex-col gap-(--era-gap)',
|
|
30
|
-
ctrl.isDirty(key) && 'border-s-2
|
|
28
|
+
ctrl.isDirty(key) && 'border-warning border-s-2 ps-(--era-pad-sm)'
|
|
31
29
|
)}
|
|
32
30
|
>
|
|
33
|
-
<span class="text-[0.625rem] leading-none tracking-[0.15em] text-muted uppercase"
|
|
31
|
+
<span class="text-[0.625rem] leading-none tracking-[0.15em] text-muted uppercase"
|
|
32
|
+
>{key}</span
|
|
33
|
+
>
|
|
34
34
|
|
|
35
35
|
{#if kind === 'boolean'}
|
|
36
36
|
<Switch
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
)}
|
|
52
52
|
/>
|
|
53
53
|
{#if !ctrl.isValid(key)}
|
|
54
|
-
<span class="text-body text-
|
|
54
|
+
<span class="text-body era-text-trim text-destructive">Invalid JSON</span>
|
|
55
55
|
{/if}
|
|
56
56
|
{:else}
|
|
57
57
|
<Input
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
>
|
|
25
25
|
<div
|
|
26
26
|
{@attach stick.content}
|
|
27
|
-
class={cn(
|
|
27
|
+
class={cn(
|
|
28
|
+
'mx-auto flex w-full max-w-2xl flex-col gap-(--era-pad-lg) p-(--era-pad-md)',
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
28
31
|
{...restProps}
|
|
29
32
|
>
|
|
30
33
|
{@render children?.()}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<div class={cn('flex min-w-0 flex-col gap-(--era-gap)', className)} {...restProps}>
|
|
20
20
|
{#if label}
|
|
21
21
|
<span
|
|
22
|
-
class="px-(--era-inset-md) text-[0.625rem] leading-none tracking-[0.15em] text-muted uppercase
|
|
22
|
+
class="px-(--era-inset-md) text-[0.625rem] era-text-trim-caps leading-none tracking-[0.15em] text-muted uppercase"
|
|
23
23
|
>
|
|
24
24
|
{label}
|
|
25
25
|
</span>
|
|
@@ -46,7 +46,11 @@ export function bucketConversations(items, now) {
|
|
|
46
46
|
const newestFirst = (a, b) => b.updatedAt - a.updatedAt;
|
|
47
47
|
const groups = [];
|
|
48
48
|
if (starred.length > 0) {
|
|
49
|
-
groups.push({
|
|
49
|
+
groups.push({
|
|
50
|
+
bucket: 'starred',
|
|
51
|
+
label: BUCKET_LABELS.starred,
|
|
52
|
+
items: starred.sort(newestFirst)
|
|
53
|
+
});
|
|
50
54
|
}
|
|
51
55
|
for (const bucket of AGE_ORDER) {
|
|
52
56
|
if (byAge[bucket].length > 0) {
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
{#if showDetail}
|
|
74
74
|
<!-- The whole summary row toggles the raw detail; quiet until hovered. -->
|
|
75
75
|
<Collapsible.Trigger
|
|
76
|
-
class="flex w-full min-w-0 cursor-pointer items-center gap-(--era-gap) text-body text-muted
|
|
76
|
+
class="flex w-full min-w-0 era-interactive cursor-pointer items-center gap-(--era-gap) text-body text-muted"
|
|
77
77
|
>
|
|
78
78
|
{@render head()}
|
|
79
79
|
<ChevronDown
|
|
@@ -36,28 +36,45 @@
|
|
|
36
36
|
target="_blank"
|
|
37
37
|
rel="noreferrer"
|
|
38
38
|
>
|
|
39
|
-
<Badge class="ml-1"
|
|
39
|
+
<Badge class="ml-1"
|
|
40
|
+
>{host(sources[0]?.href ?? '')}{sources.length > 1 ? ` +${sources.length - 1}` : ''}</Badge
|
|
41
|
+
>
|
|
40
42
|
</LinkPreview.Trigger>
|
|
41
43
|
<LinkPreview.Content class="flex w-72 flex-col gap-(--era-gap)">
|
|
42
44
|
{#if sources.length > 1}
|
|
43
45
|
<div class="flex items-center justify-between">
|
|
44
46
|
<span class="text-body text-muted tabular-nums">{index + 1} of {sources.length}</span>
|
|
45
47
|
<div class="flex gap-(--era-gap)">
|
|
46
|
-
<Button
|
|
48
|
+
<Button
|
|
49
|
+
icon
|
|
50
|
+
size="icon"
|
|
51
|
+
aria-label="Previous source"
|
|
52
|
+
onclick={() => (index = (index - 1 + sources.length) % sources.length)}
|
|
53
|
+
>
|
|
47
54
|
<ChevronLeft class="size-(--era-h-xs)" />
|
|
48
55
|
</Button>
|
|
49
|
-
<Button
|
|
56
|
+
<Button
|
|
57
|
+
icon
|
|
58
|
+
size="icon"
|
|
59
|
+
aria-label="Next source"
|
|
60
|
+
onclick={() => (index = (index + 1) % sources.length)}
|
|
61
|
+
>
|
|
50
62
|
<ChevronRight class="size-(--era-h-xs)" />
|
|
51
63
|
</Button>
|
|
52
64
|
</div>
|
|
53
65
|
</div>
|
|
54
66
|
{/if}
|
|
55
67
|
{#if active}
|
|
56
|
-
<a
|
|
68
|
+
<a
|
|
69
|
+
href={active.href}
|
|
70
|
+
target="_blank"
|
|
71
|
+
rel="noreferrer"
|
|
72
|
+
class="truncate text-body text-primary hover:underline"
|
|
73
|
+
>
|
|
57
74
|
{active.title ?? active.href}
|
|
58
75
|
</a>
|
|
59
76
|
{#if active.quote}
|
|
60
|
-
<blockquote class="border-
|
|
77
|
+
<blockquote class="border-divider-faded border-s-2 ps-(--era-gap) text-body text-muted">
|
|
61
78
|
{active.quote}
|
|
62
79
|
</blockquote>
|
|
63
80
|
{/if}
|
package/dist/ai/loader.svelte
CHANGED
|
@@ -5,4 +5,7 @@
|
|
|
5
5
|
let { class: className }: { class?: string } = $props();
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
|
-
<LoaderCircle
|
|
8
|
+
<LoaderCircle
|
|
9
|
+
class={cn('size-(--era-h-xs) animate-spin text-muted', className)}
|
|
10
|
+
aria-label="Loading"
|
|
11
|
+
/>
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
|
|
85
85
|
<div
|
|
86
86
|
class={cn(
|
|
87
|
-
'flex items-center gap-(--era-inset-sm) font-mono text-body
|
|
87
|
+
'flex items-center gap-(--era-inset-sm) font-mono text-body era-text-trim text-muted tabular-nums',
|
|
88
88
|
className
|
|
89
89
|
)}
|
|
90
90
|
{...restProps}
|
|
@@ -109,7 +109,9 @@
|
|
|
109
109
|
class="h-auto flex-col items-start gap-(--era-inset-xxs) py-(--era-inset-xxs) whitespace-normal"
|
|
110
110
|
>
|
|
111
111
|
{#each rows as [label, value] (label)}
|
|
112
|
-
<span
|
|
112
|
+
<span
|
|
113
|
+
class="flex w-full items-center justify-between gap-(--era-inset-md) tabular-nums"
|
|
114
|
+
>
|
|
113
115
|
<span class="text-muted">{label}</span>
|
|
114
116
|
<span>{formatTokens(value)}</span>
|
|
115
117
|
</span>
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
/** Deep-link builders for "continue this prompt elsewhere". */
|
|
3
3
|
export const providers = {
|
|
4
|
-
chatgpt: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
chatgpt: {
|
|
5
|
+
title: 'ChatGPT',
|
|
6
|
+
url: (q: string) => `https://chatgpt.com/?${new URLSearchParams({ q })}`
|
|
7
|
+
},
|
|
8
|
+
claude: {
|
|
9
|
+
title: 'Claude',
|
|
10
|
+
url: (q: string) => `https://claude.ai/new?${new URLSearchParams({ q })}`
|
|
11
|
+
},
|
|
12
|
+
t3: {
|
|
13
|
+
title: 'T3 Chat',
|
|
14
|
+
url: (q: string) => `https://t3.chat/new?${new URLSearchParams({ q })}`
|
|
15
|
+
},
|
|
16
|
+
scira: {
|
|
17
|
+
title: 'Scira',
|
|
18
|
+
url: (q: string) => `https://scira.ai/?${new URLSearchParams({ q })}`
|
|
19
|
+
},
|
|
8
20
|
v0: { title: 'v0', url: (q: string) => `https://v0.dev/chat?${new URLSearchParams({ q })}` }
|
|
9
21
|
} as const;
|
|
10
22
|
export type ProviderId = keyof typeof providers;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
<Collapsible.Trigger
|
|
15
15
|
class={cn(
|
|
16
|
-
'flex h-(--era-h-md) w-full cursor-pointer
|
|
16
|
+
'flex h-(--era-h-md) w-full era-interactive cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body text-bright hover:bg-(--era-highlight)',
|
|
17
17
|
className
|
|
18
18
|
)}
|
|
19
19
|
>
|
package/dist/ai/project.js
CHANGED
|
@@ -29,7 +29,11 @@ export function projectMessages(messages) {
|
|
|
29
29
|
const flushRow = () => {
|
|
30
30
|
if (row.length === 0)
|
|
31
31
|
return;
|
|
32
|
-
blocks.push({
|
|
32
|
+
blocks.push({
|
|
33
|
+
type: 'tool-row',
|
|
34
|
+
key: `${message.id}:tools:${row[0].toolCallId}`,
|
|
35
|
+
tools: row
|
|
36
|
+
});
|
|
33
37
|
row = [];
|
|
34
38
|
};
|
|
35
39
|
for (const [i, part] of message.parts.entries()) {
|
|
@@ -42,7 +46,11 @@ export function projectMessages(messages) {
|
|
|
42
46
|
case 'reasoning':
|
|
43
47
|
flushRow();
|
|
44
48
|
if (part.text)
|
|
45
|
-
blocks.push({
|
|
49
|
+
blocks.push({
|
|
50
|
+
type: 'reasoning',
|
|
51
|
+
key: `${message.id}:reasoning:${i}`,
|
|
52
|
+
text: part.text
|
|
53
|
+
});
|
|
46
54
|
break;
|
|
47
55
|
case 'tool-call': {
|
|
48
56
|
if (seenTools.has(part.toolCallId))
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<!-- md control geometry, like a Button with an icon: wall inset and
|
|
23
23
|
icon gap both --era-inset-md, ink centered by era-text-trim. -->
|
|
24
24
|
<div
|
|
25
|
-
class="flex h-(--era-h-md) items-center gap-(--era-inset-md) rounded-(--era-rd-md) bg-(--era-surface-bg) px-(--era-inset-md) text-body text-muted shadow-(--era-shadow)
|
|
25
|
+
class="flex h-(--era-h-md) items-center gap-(--era-inset-md) rounded-(--era-rd-md) bg-(--era-surface-bg) px-(--era-inset-md) text-body era-text-trim text-muted shadow-(--era-shadow)"
|
|
26
26
|
>
|
|
27
27
|
<Paperclip class="size-(--era-h-xs) shrink-0" />
|
|
28
28
|
<span class="max-w-32 truncate">{a.filename}</span>
|
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLFormAttributes } from 'svelte/elements';
|
|
4
4
|
import { cn } from '../../utils/index.js';
|
|
5
|
-
import {
|
|
6
|
-
Attachments,
|
|
7
|
-
setAttachmentsContext,
|
|
8
|
-
type PromptMessage
|
|
9
|
-
} from './attachments.svelte.js';
|
|
5
|
+
import { Attachments, setAttachmentsContext, type PromptMessage } from './attachments.svelte.js';
|
|
10
6
|
|
|
11
7
|
let {
|
|
12
8
|
accept = '',
|
|
@@ -88,7 +84,7 @@
|
|
|
88
84
|
// base fill while a button inside is hovered, so the highlight lands on the
|
|
89
85
|
// button, not the bar. (General rule for any highlighting container with
|
|
90
86
|
// interactive children.)
|
|
91
|
-
'flex w-full flex-col
|
|
87
|
+
'flex w-full era-interactive flex-col rounded-(--era-rd-lg) bg-(--era-surface-bg-elevated) p-(--era-gap) text-body text-fg shadow-(--era-shadow-well) focus-within:bg-(--era-highlight) hover:bg-(--era-highlight) has-[button:hover]:bg-(--era-surface-bg-elevated)',
|
|
92
88
|
className
|
|
93
89
|
)}
|
|
94
90
|
{...restProps}
|
|
@@ -90,8 +90,11 @@
|
|
|
90
90
|
>
|
|
91
91
|
<div class="flex h-(--era-h-md) items-center gap-(--era-gap) px-(--era-inset-md)">
|
|
92
92
|
<!-- leading status dot + label -->
|
|
93
|
-
<span
|
|
94
|
-
|
|
93
|
+
<span
|
|
94
|
+
class="flex items-center gap-(--era-inset-xxs) font-mono text-body {style.text} era-text-trim"
|
|
95
|
+
>
|
|
96
|
+
<span class={cn('size-1.5 shrink-0 rounded-full', style.dot, style.pulse && 'animate-pulse')}
|
|
97
|
+
></span>
|
|
95
98
|
{style.label}
|
|
96
99
|
</span>
|
|
97
100
|
|
|
@@ -19,9 +19,7 @@
|
|
|
19
19
|
} = $props();
|
|
20
20
|
|
|
21
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
|
-
);
|
|
22
|
+
const visible = $derived(items && limit !== undefined ? items.slice(-limit) : (items ?? []));
|
|
25
23
|
</script>
|
|
26
24
|
|
|
27
25
|
<div class={cn('flex min-w-0 flex-col gap-(--era-gap)', className)} {...restProps}>
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
function isPrimitive(input: unknown): input is Primitive {
|
|
12
12
|
return (
|
|
13
|
-
input === null ||
|
|
13
|
+
input === null ||
|
|
14
|
+
input === undefined ||
|
|
15
|
+
['string', 'number', 'boolean'].includes(typeof input)
|
|
14
16
|
);
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -58,12 +60,11 @@
|
|
|
58
60
|
|
|
59
61
|
{#snippet tag(kind: 'key' | 'index', label: string)}
|
|
60
62
|
{#if kind === 'index'}
|
|
61
|
-
<span
|
|
62
|
-
class="shrink-0 text-[0.625rem] leading-none tracking-[0.15em] text-muted uppercase"
|
|
63
|
+
<span class="shrink-0 text-[0.625rem] leading-none tracking-[0.15em] text-muted uppercase"
|
|
63
64
|
>{label}</span
|
|
64
65
|
>
|
|
65
66
|
{:else}
|
|
66
|
-
<span class="shrink-0 text-
|
|
67
|
+
<span class="shrink-0 era-text-trim text-muted">{label}:</span>
|
|
67
68
|
{/if}
|
|
68
69
|
{/snippet}
|
|
69
70
|
|
|
@@ -105,10 +106,10 @@
|
|
|
105
106
|
|
|
106
107
|
<div class={cn('flex min-w-0 flex-col gap-(--era-gap) text-body', className)} {...restProps}>
|
|
107
108
|
{#if depth > MAX_DEPTH}
|
|
108
|
-
<span class="text-
|
|
109
|
+
<span class="era-text-trim text-muted">…</span>
|
|
109
110
|
{:else if isRecord(value)}
|
|
110
111
|
{#if Object.keys(value).length === 0}
|
|
111
|
-
<span class="text-
|
|
112
|
+
<span class="era-text-trim text-muted">empty</span>
|
|
112
113
|
{:else}
|
|
113
114
|
{#each Object.entries(value) as [key, entry] (key)}
|
|
114
115
|
{@render row('key', key, entry)}
|
|
@@ -116,7 +117,7 @@
|
|
|
116
117
|
{/if}
|
|
117
118
|
{:else if Array.isArray(value)}
|
|
118
119
|
{#if value.length === 0}
|
|
119
|
-
<span class="text-
|
|
120
|
+
<span class="era-text-trim text-muted">empty</span>
|
|
120
121
|
{:else if isSimpleArray(value)}
|
|
121
122
|
{@render leaf(value)}
|
|
122
123
|
{:else}
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<div
|
|
14
|
-
class={cn(
|
|
14
|
+
class={cn(
|
|
15
|
+
'scrollbar-none flex w-full flex-nowrap items-center gap-(--era-gap) overflow-x-auto',
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
15
18
|
{...restProps}
|
|
16
19
|
>
|
|
17
20
|
{@render children?.()}
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
<!-- The task log rail: items hang off a left border, terminal-trace style. -->
|
|
10
10
|
<Step.Content
|
|
11
|
-
class={cn(
|
|
11
|
+
class={cn(
|
|
12
|
+
'border-divider-faded border-s-2 bg-transparent p-0 ps-(--era-pad-sm) shadow-none',
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
12
15
|
>
|
|
13
16
|
{@render children?.()}
|
|
14
17
|
</Step.Content>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
data-status={status}
|
|
48
48
|
{onclick}
|
|
49
49
|
class={cn(
|
|
50
|
-
'inline-flex h-(--era-h-xxs)
|
|
50
|
+
'inline-flex h-(--era-h-xxs) max-w-full min-w-0 shrink-0 era-interactive cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-xxs) px-(--era-pill-xxs) text-body era-text-trim text-muted transition-colors duration-(--era-duration) ease-(--era-ease) hover:bg-(--era-highlight) hover:text-fg',
|
|
51
51
|
active && 'bg-hover text-fg',
|
|
52
52
|
className
|
|
53
53
|
)}
|
|
@@ -23,9 +23,7 @@
|
|
|
23
23
|
{#if command || failed}
|
|
24
24
|
<div class="flex min-w-0 items-center gap-(--era-gap)">
|
|
25
25
|
{#if command}
|
|
26
|
-
<code class="min-w-0 flex-1 truncate font-mono text-body text-muted"
|
|
27
|
-
>{command}</code
|
|
28
|
-
>
|
|
26
|
+
<code class="min-w-0 flex-1 truncate font-mono text-body text-muted">{command}</code>
|
|
29
27
|
{/if}
|
|
30
28
|
{#if failed}
|
|
31
29
|
<Badge tone="destructive" class="ml-auto shrink-0">exit {exitCode}</Badge>
|
package/dist/ai/tool/tool.svelte
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
/** AI-SDK tool part lifecycle. */
|
|
3
3
|
export type ToolState =
|
|
4
|
-
| 'input-
|
|
5
|
-
| 'input-available'
|
|
6
|
-
| 'output-available'
|
|
7
|
-
| 'output-error';
|
|
4
|
+
'input-streaming' | 'input-available' | 'output-available' | 'output-error';
|
|
8
5
|
</script>
|
|
9
6
|
|
|
10
7
|
<script lang="ts">
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
className
|
|
22
22
|
)}
|
|
23
23
|
>
|
|
24
|
-
<Bar size="md" class="shrink-0
|
|
24
|
+
<Bar size="md" class="shrink-0 gap-(--era-gap) rounded-none">
|
|
25
25
|
<Button icon size="sm" aria-label="Reload" onclick={() => (nonce += 1)}>
|
|
26
26
|
<RotateCw class="size-(--era-h-xs)" />
|
|
27
27
|
</Button>
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
>
|
|
19
19
|
<Handle type="target" position={Position.Left} />
|
|
20
20
|
{#if title}
|
|
21
|
-
<div
|
|
21
|
+
<div
|
|
22
|
+
class="flex h-(--era-h-md) items-center border-b border-divider-faded px-(--era-inset-md) font-medium text-bright"
|
|
23
|
+
>
|
|
22
24
|
{title}
|
|
23
25
|
</div>
|
|
24
26
|
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** What one component looks like, on the axes, at one setting. */
|
|
2
|
+
export interface AxisSignature {
|
|
3
|
+
slug: string;
|
|
4
|
+
/** Unique non-zero transition/animation durations in the subtree, in ms. */
|
|
5
|
+
durations: number[];
|
|
6
|
+
/** Names of running keyframe animations (a spinner is allowed to keep spinning). */
|
|
7
|
+
animations: string[];
|
|
8
|
+
/** Unique painted box-shadows — the surface system's whole output. */
|
|
9
|
+
shadows: string[];
|
|
10
|
+
/** The font actually resolved for the component's text. */
|
|
11
|
+
fontFamily: string;
|
|
12
|
+
/** Elements whose text is being cut off — the font-swap failure mode. */
|
|
13
|
+
clipped: string[];
|
|
14
|
+
/** Interactive controls, for the hover-state sweep the harness drives. */
|
|
15
|
+
controls: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function captureSignatures(root?: ParentNode): AxisSignature[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Axis signatures — the fingerprint a component leaves at one axis setting.
|
|
3
|
+
*
|
|
4
|
+
* Every audit in ./audit answers a question about a SINGLE render: is this gap
|
|
5
|
+
* even, is this radius concentric, does this panel have a shadow. But the most
|
|
6
|
+
* important promises era makes are about the axes themselves, and they are
|
|
7
|
+
* comparative:
|
|
8
|
+
*
|
|
9
|
+
* MOTION does this component actually respond to instant / normal / extra —
|
|
10
|
+
* freezing at one, moving at the next, and moving MORE at the third?
|
|
11
|
+
* SURFACE does its chrome actually change between flat / glass / bevel /
|
|
12
|
+
* cartoon, or does it render the same rectangle on all four?
|
|
13
|
+
* FONT does it survive mono / sans / serif without clipping its own text?
|
|
14
|
+
*
|
|
15
|
+
* None of those can be answered by looking at one DOM. A component that hardcodes
|
|
16
|
+
* `duration-150` looks perfectly correct at data-motion="normal" — it is only
|
|
17
|
+
* wrong RELATIVE to the other two levels, where it fails to change. So the check
|
|
18
|
+
* has to be: render at each level, fingerprint what should have moved, and diff.
|
|
19
|
+
*
|
|
20
|
+
* This module captures the fingerprint. The comparison lives in the harness that
|
|
21
|
+
* drives the renders (tests/visual/axis-conformance.spec.ts), because only it can
|
|
22
|
+
* visit more than one axis at a time.
|
|
23
|
+
*/
|
|
24
|
+
const num = (v) => v
|
|
25
|
+
.split(',')
|
|
26
|
+
.map((s) => s.trim())
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.map((s) => (s.endsWith('ms') ? parseFloat(s) : parseFloat(s) * 1000))
|
|
29
|
+
.filter((n) => !Number.isNaN(n) && n > 0);
|
|
30
|
+
const uniq = (xs) => [...new Set(xs)];
|
|
31
|
+
/**
|
|
32
|
+
* Is this element's text being cut off by its own box?
|
|
33
|
+
*
|
|
34
|
+
* The font axis is the one that breaks silently: a serif or sans face is WIDER
|
|
35
|
+
* than the mono default at the same size, so a label that fits in mono can
|
|
36
|
+
* overflow its control in serif — and because the control clips, the text simply
|
|
37
|
+
* disappears rather than reflowing. Nothing in a single-font render can predict
|
|
38
|
+
* that; you have to swap the font and look.
|
|
39
|
+
*/
|
|
40
|
+
function isClipped(el) {
|
|
41
|
+
const s = getComputedStyle(el);
|
|
42
|
+
if (!/hidden|clip/.test(s.overflowX))
|
|
43
|
+
return false;
|
|
44
|
+
if (!el.textContent?.trim())
|
|
45
|
+
return false;
|
|
46
|
+
// A visually-hidden element (sr-only) is a 1px box with its overflow clipped —
|
|
47
|
+
// that IS the technique, not a defect. It reports a wildly overflowing
|
|
48
|
+
// scrollWidth by construction (the calendar's screen-reader heading measured
|
|
49
|
+
// 144px of text in a 1px box), so exclude the degenerate boxes.
|
|
50
|
+
if (el.clientWidth <= 1 || el.clientHeight <= 1)
|
|
51
|
+
return false;
|
|
52
|
+
// 1px of slack: sub-pixel text metrics routinely round a hair over.
|
|
53
|
+
return el.scrollWidth > el.clientWidth + 1;
|
|
54
|
+
}
|
|
55
|
+
export function captureSignatures(root = document) {
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const host of root.querySelectorAll('[data-specimen]')) {
|
|
58
|
+
const slug = host.getAttribute('data-specimen') ?? '';
|
|
59
|
+
const durations = [];
|
|
60
|
+
const animations = [];
|
|
61
|
+
const shadows = [];
|
|
62
|
+
const clipped = [];
|
|
63
|
+
let controls = 0;
|
|
64
|
+
for (const el of host.querySelectorAll('*')) {
|
|
65
|
+
const s = getComputedStyle(el);
|
|
66
|
+
durations.push(...num(s.transitionDuration), ...num(s.animationDuration));
|
|
67
|
+
if (s.animationName && s.animationName !== 'none')
|
|
68
|
+
animations.push(s.animationName);
|
|
69
|
+
if (s.boxShadow && s.boxShadow !== 'none')
|
|
70
|
+
shadows.push(s.boxShadow);
|
|
71
|
+
if (isClipped(el))
|
|
72
|
+
clipped.push(`${el.tagName.toLowerCase()}"${el.textContent?.trim().slice(0, 24)}"`);
|
|
73
|
+
if (el.matches('button, input, [role="switch"], [role="checkbox"], a[href]'))
|
|
74
|
+
controls++;
|
|
75
|
+
}
|
|
76
|
+
out.push({
|
|
77
|
+
slug,
|
|
78
|
+
durations: uniq(durations).sort((a, b) => a - b),
|
|
79
|
+
animations: uniq(animations).sort(),
|
|
80
|
+
shadows: uniq(shadows).sort(),
|
|
81
|
+
fontFamily: getComputedStyle(host).fontFamily,
|
|
82
|
+
clipped: uniq(clipped),
|
|
83
|
+
controls
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
package/dist/dev/index.d.ts
CHANGED
package/dist/dev/index.js
CHANGED
package/dist/os/window.svelte
CHANGED
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
// mouse events, so the browser's autoscroll never engages.
|
|
102
102
|
if (e.button === 1) e.preventDefault();
|
|
103
103
|
armed =
|
|
104
|
-
|
|
104
|
+
e.button === 2 && win.minimizable ? 'minimize' : e.button === 1 ? 'maximize' : null;
|
|
105
105
|
}}
|
|
106
106
|
onpointerup={(e: PointerEvent) => {
|
|
107
107
|
if (e.button === 2 && armed === 'minimize') wm.minimize(win.id);
|
|
@@ -14,7 +14,15 @@
|
|
|
14
14
|
|
|
15
15
|
[data-font='mono'],
|
|
16
16
|
[data-font='mono'] * {
|
|
17
|
-
font-family: var(
|
|
17
|
+
font-family: var(
|
|
18
|
+
--font-mono,
|
|
19
|
+
ui-monospace,
|
|
20
|
+
'SF Mono',
|
|
21
|
+
Menlo,
|
|
22
|
+
Consolas,
|
|
23
|
+
'Liberation Mono',
|
|
24
|
+
monospace
|
|
25
|
+
) !important;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
[data-font='sans'],
|
|
@@ -38,10 +38,20 @@
|
|
|
38
38
|
const effectiveDisabled = $derived(disabled || loading);
|
|
39
39
|
</script>
|
|
40
40
|
|
|
41
|
+
<!--
|
|
42
|
+
`active` is the latched state — a held-down key. Every OTHER latched control in
|
|
43
|
+
the library (Toggle, Tabs, ToggleGroup, Switch) announces that with
|
|
44
|
+
data-state="on"; Button expressed it through classes alone, so nothing could
|
|
45
|
+
SEE it: not a consumer styling off the state, not a test, not an audit. The
|
|
46
|
+
hover sweep is what surfaced this — a latched button correctly shows no hover
|
|
47
|
+
delta (it must keep its pressed face), but with no attribute to read, that is
|
|
48
|
+
indistinguishable from a control with no hover state at all.
|
|
49
|
+
-->
|
|
41
50
|
<Button.Root
|
|
42
51
|
bind:ref
|
|
43
52
|
disabled={effectiveDisabled}
|
|
44
53
|
aria-busy={loading || undefined}
|
|
54
|
+
data-state={active ? 'on' : undefined}
|
|
45
55
|
class={cn(buttonVariants({ variant, tone, size, icon, active }), className)}
|
|
46
56
|
{...restProps}
|
|
47
57
|
>
|
package/dist/ui/card/card.svelte
CHANGED
|
@@ -93,7 +93,9 @@
|
|
|
93
93
|
{#snippet body()}
|
|
94
94
|
{#if sectioned}
|
|
95
95
|
{#if header}
|
|
96
|
-
<div
|
|
96
|
+
<div
|
|
97
|
+
class="border-b border-divider-faded px-(--era-pad-md) pt-(--era-gap) pb-(--era-pad-md) text-bright"
|
|
98
|
+
>
|
|
97
99
|
{@render header()}
|
|
98
100
|
</div>
|
|
99
101
|
{/if}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
{@const Icon = icon}
|
|
21
21
|
<div
|
|
22
22
|
class={cn(
|
|
23
|
-
'flex h-(--era-h-md) w-full era-interactive items-center gap-(--era-gap) rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) px-(--era-inset-md) text-body text-fg shadow-(--era-shadow-well)
|
|
23
|
+
'flex h-(--era-h-md) w-full era-interactive items-center gap-(--era-gap) rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) px-(--era-inset-md) text-body era-text-trim text-fg shadow-(--era-shadow-well) focus-within:bg-(--era-highlight) hover:bg-(--era-highlight)',
|
|
24
24
|
className
|
|
25
25
|
)}
|
|
26
26
|
>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<Command.Item
|
|
9
9
|
bind:ref
|
|
10
10
|
class={cn(
|
|
11
|
-
'flex h-(--era-h-md) cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body text-fg outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[selected]:bg-hover
|
|
11
|
+
'flex h-(--era-h-md) cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body era-text-trim text-fg outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[selected]:bg-hover',
|
|
12
12
|
className
|
|
13
13
|
)}
|
|
14
14
|
{...restProps}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<Command.LinkItem
|
|
9
9
|
bind:ref
|
|
10
10
|
class={cn(
|
|
11
|
-
'flex h-(--era-h-md) cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body text-fg no-underline outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[selected]:bg-hover
|
|
11
|
+
'flex h-(--era-h-md) cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body era-text-trim text-fg no-underline outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[selected]:bg-hover',
|
|
12
12
|
className
|
|
13
13
|
)}
|
|
14
14
|
{...restProps}
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
role="listbox"
|
|
215
215
|
tabindex="-1"
|
|
216
216
|
aria-label="Commands"
|
|
217
|
-
class="
|
|
217
|
+
class="absolute top-full left-0 z-50 max-h-64 w-full overflow-x-hidden overflow-y-auto bg-(--era-surface-bg-elevated) shadow-(--era-shadow-lg) glass-blur"
|
|
218
218
|
onmousedown={(e) => e.preventDefault()}
|
|
219
219
|
>
|
|
220
220
|
{#if !filtered.length}
|
|
@@ -234,7 +234,7 @@
|
|
|
234
234
|
tabindex={-1}
|
|
235
235
|
aria-selected={i === selectedIndex}
|
|
236
236
|
class={cn(
|
|
237
|
-
'flex h-(--era-h-md) cursor-default
|
|
237
|
+
'flex h-(--era-h-md) cursor-default items-center gap-(--era-gap) px-(--era-inset-md) text-body era-text-trim whitespace-nowrap',
|
|
238
238
|
i === selectedIndex ? 'bg-3 text-bright' : 'text-fg'
|
|
239
239
|
)}
|
|
240
240
|
onclick={() => choose(item)}
|
|
@@ -92,6 +92,8 @@
|
|
|
92
92
|
<span class="relative block size-(--era-h-xs) shrink-0">
|
|
93
93
|
<IdleIcon class={cn(iconBase, status === 'idle' ? iconIn : iconOut)} />
|
|
94
94
|
<SuccessIcon class={cn(iconBase, 'text-success', status === 'success' ? iconIn : iconOut)} />
|
|
95
|
-
<FailureIcon
|
|
95
|
+
<FailureIcon
|
|
96
|
+
class={cn(iconBase, 'text-destructive', status === 'failure' ? iconIn : iconOut)}
|
|
97
|
+
/>
|
|
96
98
|
</span>
|
|
97
99
|
</Button>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
bind:this={ref}
|
|
17
17
|
bind:value
|
|
18
18
|
class={cn(
|
|
19
|
-
'flex h-(--era-h-md) w-full era-interactive rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) px-(--era-inset-md) text-body text-fg shadow-(--era-shadow-well) placeholder:text-muted hover:bg-(--era-highlight) focus:bg-(--era-highlight)
|
|
19
|
+
'flex h-(--era-h-md) w-full era-interactive rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) px-(--era-inset-md) text-body era-text-trim text-fg shadow-(--era-shadow-well) placeholder:text-muted hover:bg-(--era-highlight) focus:bg-(--era-highlight)',
|
|
20
20
|
className
|
|
21
21
|
)}
|
|
22
22
|
{...restProps}
|
package/dist/ui/kv/kv.svelte
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<span
|
|
22
22
|
bind:this={ref}
|
|
23
23
|
class={cn(
|
|
24
|
-
'inline-flex h-(--era-h-xxs) items-center overflow-hidden rounded-(--era-rd-xxs) bg-hover font-mono text-body leading-none shadow-(--era-shadow)
|
|
24
|
+
'inline-flex h-(--era-h-xxs) items-center overflow-hidden rounded-(--era-rd-xxs) bg-hover font-mono text-body era-text-trim leading-none shadow-(--era-shadow)',
|
|
25
25
|
className
|
|
26
26
|
)}
|
|
27
27
|
{...restProps}
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
movement and suppresses the trailing click after a real drag). Its natural
|
|
19
19
|
rd-xs is what the PANE's corner radius conforms to (rd-sm — see Pane.Root),
|
|
20
20
|
not the other way round. -->
|
|
21
|
-
<Button
|
|
21
|
+
<Button
|
|
22
|
+
bind:ref
|
|
23
|
+
icon
|
|
24
|
+
size="icon"
|
|
25
|
+
tone="destructive"
|
|
26
|
+
aria-label="Close"
|
|
27
|
+
class={className}
|
|
28
|
+
{...restProps}
|
|
29
|
+
>
|
|
22
30
|
<X class="size-(--era-h-xs)" />
|
|
23
31
|
</Button>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<Select.Trigger
|
|
15
15
|
bind:ref
|
|
16
16
|
class={cn(
|
|
17
|
-
'flex h-(--era-h-md) w-full items-center justify-between gap-(--era-xs-inset-sm) rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) px-(--era-inset-md) text-body text-fg shadow-(--era-shadow) hover:bg-(--era-highlight) focus:bg-(--era-highlight) focus:outline-none disabled:opacity-50 data-[state=open]:rounded-b-none data-[state=open]:shadow-(--era-shadow-pressed)
|
|
17
|
+
'flex h-(--era-h-md) w-full items-center justify-between gap-(--era-xs-inset-sm) rounded-(--era-rd-md) bg-(--era-surface-bg-elevated) px-(--era-inset-md) text-body era-text-trim text-fg shadow-(--era-shadow) hover:bg-(--era-highlight) focus:bg-(--era-highlight) focus:outline-none disabled:opacity-50 data-[state=open]:rounded-b-none data-[state=open]:shadow-(--era-shadow-pressed)',
|
|
18
18
|
className
|
|
19
19
|
)}
|
|
20
20
|
{...restProps}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<Collapsible.Trigger
|
|
27
27
|
bind:ref
|
|
28
28
|
class={cn(
|
|
29
|
-
'flex h-(--era-h-md) w-full min-w-0 cursor-pointer
|
|
29
|
+
'flex h-(--era-h-md) w-full min-w-0 era-interactive cursor-pointer items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body era-text-trim text-muted transition-colors duration-(--era-duration) ease-(--era-ease) hover:text-fg',
|
|
30
30
|
className
|
|
31
31
|
)}
|
|
32
32
|
{...restProps}
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -34,13 +34,13 @@ to ~13px, silently shrinking every rem-derived token).
|
|
|
34
34
|
|
|
35
35
|
## The tier ladder
|
|
36
36
|
|
|
37
|
-
| Tier | Height token
|
|
38
|
-
| ----- |
|
|
39
|
-
| `xs` | `--era-h-xs`
|
|
40
|
-
| `xxs` | `--era-h-xxs`
|
|
41
|
-
| `sm` | `--era-h-sm`
|
|
42
|
-
| `md` | `--era-h-md`
|
|
43
|
-
| `lg` | `--era-h-lg`
|
|
37
|
+
| Tier | Height token | Radius token | Use for |
|
|
38
|
+
| ----- | ------------- | -------------- | ----------------------------------------------- |
|
|
39
|
+
| `xs` | `--era-h-xs` | `--era-rd-xs` | icons (THE icon size), switch thumbs |
|
|
40
|
+
| `xxs` | `--era-h-xxs` | `--era-rd-xxs` | pills nested inside md (badge/chip in a button) |
|
|
41
|
+
| `sm` | `--era-h-sm` | `--era-rd-sm` | standalone pills, switch tracks, KV |
|
|
42
|
+
| `md` | `--era-h-md` | `--era-rd-md` | buttons, inputs, selects, triggers |
|
|
43
|
+
| `lg` | `--era-h-lg` | `--era-rd-lg` | bars, containers, dialog headers |
|
|
44
44
|
|
|
45
45
|
Usage: `h-(--era-h-md)`, `rounded-(--era-rd-md)`, icons `size-(--era-h-xs)`.
|
|
46
46
|
Always the paren shorthand — never `h-[var(--era-h-md)]`.
|
|
@@ -119,8 +119,8 @@ motion-axis-aware for free.
|
|
|
119
119
|
|
|
120
120
|
```svelte
|
|
121
121
|
<script lang="ts">
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
import { Button, Badge, OS, AI } from '@sig-nine/era-ui';
|
|
123
|
+
import * as Select from '@sig-nine/era-ui'; // namespaced parts: Select.Root…
|
|
124
124
|
</script>
|
|
125
125
|
```
|
|
126
126
|
|