@signal9/era-ui 2.21.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.
Files changed (67) hide show
  1. package/dist/ai/artifact/artifact-actions.svelte +3 -1
  2. package/dist/ai/artifact/artifact-content.svelte +3 -1
  3. package/dist/ai/artifact/artifact-header.svelte +5 -1
  4. package/dist/ai/artifact/artifact.svelte +5 -1
  5. package/dist/ai/chain-of-thought/chain-of-thought-step.svelte +5 -1
  6. package/dist/ai/chain-of-thought/chain-of-thought.svelte +1 -1
  7. package/dist/ai/confirmation/confirmation-args.svelte +7 -7
  8. package/dist/ai/conversation/conversation-content.svelte +4 -1
  9. package/dist/ai/conversations/conversations-group.svelte +1 -1
  10. package/dist/ai/conversations/conversations.svelte.js +5 -1
  11. package/dist/ai/error-panel/error-panel.svelte +1 -1
  12. package/dist/ai/inline-citation/inline-citation.svelte +22 -5
  13. package/dist/ai/loader.svelte +4 -1
  14. package/dist/ai/message/message-usage.svelte +4 -2
  15. package/dist/ai/open-in-chat/open-in-chat.svelte +16 -4
  16. package/dist/ai/plan/plan-header.svelte +1 -1
  17. package/dist/ai/project.js +10 -2
  18. package/dist/ai/prompt-input/prompt-input-attachments.svelte +1 -1
  19. package/dist/ai/prompt-input/prompt-input.svelte +2 -6
  20. package/dist/ai/run-status/run-status.svelte +5 -2
  21. package/dist/ai/runtime-feed/runtime-feed-item.svelte +1 -1
  22. package/dist/ai/runtime-feed/runtime-feed-root.svelte +1 -3
  23. package/dist/ai/structured-value/structured-value.svelte +8 -7
  24. package/dist/ai/suggestion/suggestions.svelte +4 -1
  25. package/dist/ai/task/task-content.svelte +4 -1
  26. package/dist/ai/tool/tool-chip.svelte +1 -1
  27. package/dist/ai/tool/tool-exec.svelte +1 -3
  28. package/dist/ai/tool/tool.svelte +1 -4
  29. package/dist/ai/web-preview/web-preview.svelte +1 -1
  30. package/dist/ai/workflow/node.svelte +3 -1
  31. package/dist/dev/audit/audits/_helpers.d.ts +19 -0
  32. package/dist/dev/audit/audits/_helpers.js +68 -0
  33. package/dist/dev/audit/audits/collapsed-text-trim.d.ts +2 -0
  34. package/dist/dev/audit/audits/collapsed-text-trim.js +52 -0
  35. package/dist/dev/audit/audits/flat-chrome.d.ts +2 -0
  36. package/dist/dev/audit/audits/flat-chrome.js +46 -0
  37. package/dist/dev/audit/audits/icon-flush.d.ts +2 -0
  38. package/dist/dev/audit/audits/icon-flush.js +51 -0
  39. package/dist/dev/audit/audits/index.d.ts +7 -1
  40. package/dist/dev/audit/audits/index.js +20 -1
  41. package/dist/dev/audit/audits/off-axis-motion.d.ts +2 -0
  42. package/dist/dev/audit/audits/off-axis-motion.js +60 -0
  43. package/dist/dev/audit/audits/off-tier-height.d.ts +2 -0
  44. package/dist/dev/audit/audits/off-tier-height.js +47 -0
  45. package/dist/dev/audit/audits/single-glyph-square.d.ts +2 -0
  46. package/dist/dev/audit/audits/single-glyph-square.js +50 -0
  47. package/dist/dev/axis-signature.d.ts +17 -0
  48. package/dist/dev/axis-signature.js +87 -0
  49. package/dist/dev/index.d.ts +1 -0
  50. package/dist/dev/index.js +1 -0
  51. package/dist/era-ui.css +1 -1
  52. package/dist/os/window.svelte +1 -1
  53. package/dist/styles/typography.css +9 -1
  54. package/dist/ui/button/button.svelte +10 -0
  55. package/dist/ui/card/card.svelte +3 -1
  56. package/dist/ui/command/command-input.svelte +1 -1
  57. package/dist/ui/command/command-item.svelte +1 -1
  58. package/dist/ui/command/command-link-item.svelte +1 -1
  59. package/dist/ui/command-bar/command-bar.svelte +2 -2
  60. package/dist/ui/copy-button/copy-button.svelte +3 -1
  61. package/dist/ui/input/input.svelte +1 -1
  62. package/dist/ui/kv/kv.svelte +1 -1
  63. package/dist/ui/pane/pane-close.svelte +9 -1
  64. package/dist/ui/select/select-trigger.svelte +1 -1
  65. package/dist/ui/step/step-summary.svelte +1 -1
  66. package/package.json +1 -1
  67. 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)}>{@render children?.()}</div>
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)}>{@render children?.()}</div>
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 { children, class: className, ...restProps }: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
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 { children, class: className, ...restProps }: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
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' ? 'text-bright' : status === 'pending' ? 'text-muted opacity-50' : 'text-muted'
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
  />
@@ -15,7 +15,7 @@
15
15
  streaming?: boolean;
16
16
  label?: string;
17
17
  children?: Snippet;
18
- class?: string;
18
+ class?: string;
19
19
  } = $props();
20
20
  </script>
21
21
 
@@ -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
- controller,
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 border-warning ps-(--era-pad-sm)'
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">{key}</span>
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-destructive era-text-trim">Invalid JSON</span>
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('mx-auto flex w-full max-w-2xl flex-col gap-(--era-pad-lg) p-(--era-pad-md)', className)}
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 era-text-trim-caps"
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({ bucket: 'starred', label: BUCKET_LABELS.starred, items: starred.sort(newestFirst) });
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 era-interactive"
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">{host(sources[0]?.href ?? '')}{sources.length > 1 ? ` +${sources.length - 1}` : ''}</Badge>
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 icon size="icon" aria-label="Previous source" onclick={() => (index = (index - 1 + sources.length) % sources.length)}>
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 icon size="icon" aria-label="Next source" onclick={() => (index = (index + 1) % sources.length)}>
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 href={active.href} target="_blank" rel="noreferrer" class="truncate text-body text-primary hover:underline">
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-s-2 border-divider-faded ps-(--era-gap) text-body text-muted">
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}
@@ -5,4 +5,7 @@
5
5
  let { class: className }: { class?: string } = $props();
6
6
  </script>
7
7
 
8
- <LoaderCircle class={cn('size-(--era-h-xs) animate-spin text-muted', className)} aria-label="Loading" />
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 tabular-nums text-muted era-text-trim',
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 class="flex w-full items-center justify-between gap-(--era-inset-md) tabular-nums">
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: { title: 'ChatGPT', url: (q: string) => `https://chatgpt.com/?${new URLSearchParams({ q })}` },
5
- claude: { title: 'Claude', url: (q: string) => `https://claude.ai/new?${new URLSearchParams({ q })}` },
6
- t3: { title: 'T3 Chat', url: (q: string) => `https://t3.chat/new?${new URLSearchParams({ q })}` },
7
- scira: { title: 'Scira', url: (q: string) => `https://scira.ai/?${new URLSearchParams({ q })}` },
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 era-interactive items-center gap-(--era-gap) rounded-(--era-rd-md) px-(--era-inset-md) text-body text-bright hover:bg-(--era-highlight)',
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
  >
@@ -29,7 +29,11 @@ export function projectMessages(messages) {
29
29
  const flushRow = () => {
30
30
  if (row.length === 0)
31
31
  return;
32
- blocks.push({ type: 'tool-row', key: `${message.id}:tools:${row[0].toolCallId}`, tools: row });
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({ type: 'reasoning', key: `${message.id}:reasoning:${i}`, text: part.text });
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) era-text-trim"
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 era-interactive rounded-(--era-rd-lg) bg-(--era-surface-bg-elevated) p-(--era-gap) text-body text-fg shadow-(--era-shadow-well) hover:bg-(--era-highlight) focus-within:bg-(--era-highlight) has-[button:hover]:bg-(--era-surface-bg-elevated)',
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 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>
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
 
@@ -59,6 +59,6 @@
59
59
  {#if children}{@render children()}{:else}{text}{/if}
60
60
  </span>
61
61
  {#if label}
62
- <span class="shrink-0 text-muted tabular-nums era-text-trim">{label}</span>
62
+ <span class="shrink-0 era-text-trim text-muted tabular-nums">{label}</span>
63
63
  {/if}
64
64
  </div>
@@ -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 || input === undefined || ['string', 'number', 'boolean'].includes(typeof input)
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-muted era-text-trim">{label}:</span>
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-muted era-text-trim">…</span>
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-muted era-text-trim">empty</span>
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-muted era-text-trim">empty</span>
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('scrollbar-none flex w-full flex-nowrap items-center gap-(--era-gap) overflow-x-auto', className)}
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('border-s-2 border-divider-faded bg-transparent p-0 ps-(--era-pad-sm) shadow-none', className)}
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) 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',
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>
@@ -1,10 +1,7 @@
1
1
  <script lang="ts" module>
2
2
  /** AI-SDK tool part lifecycle. */
3
3
  export type ToolState =
4
- | 'input-streaming'
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 rounded-none gap-(--era-gap)">
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 class="flex h-(--era-h-md) items-center border-b border-divider-faded px-(--era-inset-md) font-medium text-bright">
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}
@@ -27,3 +27,22 @@ export declare function siblingGap(a: HTMLElement, b: HTMLElement): number;
27
27
  export declare function wallGap(container: HTMLElement, first: HTMLElement): number;
28
28
  export declare function isFlex(style: CSSStyleDeclaration): boolean;
29
29
  export declare function isHorizontalFlex(style: CSSStyleDeclaration): boolean;
30
+ export declare function tokenPx(el: HTMLElement, name: string): number;
31
+ /** The five tier heights, in px, in the element's density context. */
32
+ export declare function tiers(el: HTMLElement): {
33
+ name: string;
34
+ px: number;
35
+ }[];
36
+ /**
37
+ * Does this element paint chrome of its own — a fill, a border, or a shadow?
38
+ *
39
+ * The distinction decides whether a geometry rule is OBSERVABLE. A radius on a
40
+ * box that paints nothing is invisible, so holding it to the concentricity or
41
+ * single-glyph rules reports a defect nobody can see (the ":" literal in a time
42
+ * field is exactly this: a bare text span that happens to carry a radius).
43
+ */
44
+ export declare function paintsChrome(el: HTMLElement): boolean;
45
+ /** The motion axis's own duration, in ms, as the element sees it (0 at data-motion="instant"). */
46
+ export declare function eraDurationMs(el: HTMLElement): number;
47
+ /** Parse a computed time list ("0.15s, 0s") to ms. */
48
+ export declare function timesMs(value: string): number[];
@@ -46,3 +46,71 @@ export function isFlex(style) {
46
46
  export function isHorizontalFlex(style) {
47
47
  return isFlex(style) && !style.flexDirection.startsWith('column');
48
48
  }
49
+ /**
50
+ * Resolve an --era-* length token to px, in the element's own context.
51
+ *
52
+ * Every tier token is a calc() over --era-sp, and a computed custom property
53
+ * keeps its calc() UNEVALUATED — getComputedStyle hands back the expression, not
54
+ * a number. The only way to learn what a tier is actually worth is to make the
55
+ * browser lay something out at that height and measure it.
56
+ *
57
+ * Context matters: density can be scoped (a pinned-dense Pane handle inside a
58
+ * spacious page), so the probe is appended to the element's own density host,
59
+ * never to <html>. The cache is keyed by the host's --era-sp, so it invalidates
60
+ * itself the moment the density axis flips.
61
+ */
62
+ const tokenCache = new WeakMap();
63
+ export function tokenPx(el, name) {
64
+ const host = (el.closest('[data-mode]') ?? document.documentElement);
65
+ const sp = getComputedStyle(host).getPropertyValue('--era-sp').trim();
66
+ const key = `${name}@${sp}`;
67
+ let cache = tokenCache.get(host);
68
+ if (!cache)
69
+ tokenCache.set(host, (cache = new Map()));
70
+ const hit = cache.get(key);
71
+ if (hit !== undefined)
72
+ return hit;
73
+ const probe = document.createElement('div');
74
+ probe.style.cssText = `position:absolute;visibility:hidden;height:var(${name})`;
75
+ host.appendChild(probe);
76
+ const px = probe.getBoundingClientRect().height;
77
+ probe.remove();
78
+ cache.set(key, px);
79
+ return px;
80
+ }
81
+ /** The five tier heights, in px, in the element's density context. */
82
+ export function tiers(el) {
83
+ return ['--era-h-xs', '--era-h-xxs', '--era-h-sm', '--era-h-md', '--era-h-lg'].map((name) => ({
84
+ name,
85
+ px: tokenPx(el, name)
86
+ }));
87
+ }
88
+ /**
89
+ * Does this element paint chrome of its own — a fill, a border, or a shadow?
90
+ *
91
+ * The distinction decides whether a geometry rule is OBSERVABLE. A radius on a
92
+ * box that paints nothing is invisible, so holding it to the concentricity or
93
+ * single-glyph rules reports a defect nobody can see (the ":" literal in a time
94
+ * field is exactly this: a bare text span that happens to carry a radius).
95
+ */
96
+ export function paintsChrome(el) {
97
+ const s = getComputedStyle(el);
98
+ const transparentBg = s.backgroundColor === 'transparent' || /,\s*0\)$/.test(s.backgroundColor);
99
+ return !transparentBg || s.boxShadow !== 'none' || parseFloat(s.borderTopWidth) > 0;
100
+ }
101
+ /** The motion axis's own duration, in ms, as the element sees it (0 at data-motion="instant"). */
102
+ export function eraDurationMs(el) {
103
+ const raw = getComputedStyle(el).getPropertyValue('--era-duration').trim();
104
+ if (!raw)
105
+ return 0;
106
+ return raw.endsWith('ms') ? parseFloat(raw) : parseFloat(raw) * 1000;
107
+ }
108
+ /** Parse a computed time list ("0.15s, 0s") to ms. */
109
+ export function timesMs(value) {
110
+ return value
111
+ .split(',')
112
+ .map((v) => v.trim())
113
+ .filter(Boolean)
114
+ .map((v) => (v.endsWith('ms') ? parseFloat(v) : parseFloat(v) * 1000))
115
+ .filter((n) => !Number.isNaN(n));
116
+ }
@@ -0,0 +1,2 @@
1
+ import type { Audit } from '../types.js';
2
+ export declare const collapsedTextTrim: Audit;