@signal9/era-ui 4.7.2 → 4.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 (48) hide show
  1. package/dist/ai/swarm/index.d.ts +2 -1
  2. package/dist/ai/swarm/index.js +2 -1
  3. package/dist/ai/swarm/swarm-actions.svelte +86 -0
  4. package/dist/ai/swarm/swarm-actions.svelte.d.ts +31 -0
  5. package/dist/ai/swarm/swarm-agent.svelte +9 -43
  6. package/dist/ai/swarm/swarm-artifact.svelte +2 -6
  7. package/dist/ai/swarm/swarm-artifacts.svelte +1 -10
  8. package/dist/ai/swarm/swarm-artifacts.svelte.d.ts +0 -2
  9. package/dist/ai/swarm/swarm-barriers.svelte +39 -0
  10. package/dist/ai/swarm/swarm-barriers.svelte.d.ts +13 -0
  11. package/dist/ai/swarm/swarm-counts.svelte +1 -4
  12. package/dist/ai/swarm/swarm-counts.svelte.d.ts +0 -2
  13. package/dist/ai/swarm/swarm-footer.svelte +20 -13
  14. package/dist/ai/swarm/swarm-header.svelte +3 -5
  15. package/dist/ai/swarm/swarm-node.svelte +8 -64
  16. package/dist/ai/swarm/swarm-role.svelte +2 -9
  17. package/dist/ai/swarm/swarm-sandbox.svelte +45 -0
  18. package/dist/ai/swarm/swarm-sandbox.svelte.d.ts +10 -0
  19. package/dist/ai/swarm/swarm-tree.svelte +22 -8
  20. package/dist/ai/swarm/swarm.d.ts +8 -0
  21. package/dist/ai/swarm/swarm.js +11 -2
  22. package/dist/ai/swarm/swarm.svelte.d.ts +0 -1
  23. package/dist/ai/swarm/swarm.svelte.js +10 -12
  24. package/dist/apps/llm-shell/llm-shell.svelte +2 -36
  25. package/dist/apps/llm-shell/llm-swarm.svelte +64 -0
  26. package/dist/apps/llm-shell/llm-swarm.svelte.d.ts +23 -0
  27. package/dist/apps/llm-shell/types.d.ts +2 -0
  28. package/dist/apps/notes/editor/bubble-toolbar.svelte +2 -1
  29. package/dist/apps/notes/notes.svelte +13 -10
  30. package/dist/dev/audit/audits/clipped-glyphs.js +33 -11
  31. package/dist/docs/llms-txt.js +1 -1
  32. package/dist/era-ui.css +1 -1
  33. package/dist/generated-docs/bar.md +3 -0
  34. package/dist/generated-docs/input.md +3 -1
  35. package/dist/generated-docs/llms-full.txt +15 -6
  36. package/dist/generated-docs/llms.txt +1 -1
  37. package/dist/generated-docs/manifest.json +5 -5
  38. package/dist/generated-docs/utilities.json +2 -2
  39. package/dist/generated-docs/utilities.md +8 -4
  40. package/dist/styles/density.css +42 -9
  41. package/dist/styles/index.css +8 -4
  42. package/dist/ui/bar/bar.svelte +37 -3
  43. package/dist/ui/bar/bar.svelte.d.ts +61 -1
  44. package/dist/ui/input/input.svelte +24 -4
  45. package/dist/ui/input/input.svelte.d.ts +6 -1
  46. package/dist/ui/input/variants.d.ts +99 -30
  47. package/dist/ui/input/variants.js +45 -19
  48. package/package.json +1 -1
@@ -10,12 +10,14 @@ import { Input } from "$lib/ui/input";
10
10
 
11
11
  ## Props
12
12
 
13
- Inherits all props from `HTMLInputAttributes`.
13
+ Inherits all props from `Omit<HTMLInputAttributes, 'size'>`.
14
14
 
15
15
  | Prop | Type | Default | Notes |
16
16
  |------|------|---------|-------|
17
17
  | `ref?` | `HTMLInputElement \| null` | `null` | bindable |
18
18
  | `icon?` | `Component<IconProps> \| null` | `null` | Lucide glyph rendered inside the field, left of the text. |
19
+ | `size?` | `'md' \| 'xxs'` | `'md'` | Field tier. `xxs` is badge height — for compact chrome where the field
20
+ sits level with xxs buttons. See inputVariants. |
19
21
  | `bare?` | `boolean` | `false` | Drop the field chrome — for an input inside a container that is already the field. |
20
22
  | `reveal?` | `boolean` | `false` | Hold the field chrome back until hover — for a title or label that IS the content. |
21
23
  | `value?` | `(forwarded)` | `''` | bindable |
@@ -15,7 +15,7 @@ Named exports wrap a single primitive (`Button`, `Badge`, `Chip`, `Input`, …).
15
15
 
16
16
  Five orthogonal axes are exposed as HTML attributes on any ancestor. The entire subtree re-tiers through CSS inheritance — no JS, no context provider.
17
17
 
18
- - `data-mode`: **dense** (default) · balanced · spacious · touch — adjusts the spacing atom (`--era-sp`).
18
+ - `data-mode`: **dense** (default) · balanced · spacious · touch — adjusts the spacing atom (`--era-sp`) and the type size (`--era-text`: 12px at dense, 14px above).
19
19
  - `data-surface`: **flat** (default) · glass · bevel — swaps the background treatment.
20
20
  - `data-corners`: **rounded** (default) · square — scales all radii toward 0.
21
21
  - `data-font`: **mono** (default) · sans · serif — switches the font stack.
@@ -249,7 +249,9 @@ centering the x-height band — the visual mass of MIXED-CASE text (caps and
249
249
  descenders cancel above/below). cap alphabetic is the spec-example recipe
250
250
  but is descender-blind (reads high; CSSWG #9148) — measured 4x worse here.
251
251
  Where text-box is supported it fully determines the box, so the two
252
- declarations never fight. Worst-case residual: 0.44px.
252
+ declarations never fight. Worst-case residual: 0.44px (measured at 14px; the
253
+ mechanism is font-relative, so dense's 12px inherits the same guarantee with
254
+ a proportionally smaller residual).
253
255
 
254
256
  The hard rules concern overflow clipping, and there are TWO of them.
255
257
 
@@ -371,9 +373,11 @@ The class also owns `overflow: hidden` — animating height without it just
371
373
  spills the content out of the shrinking box.
372
374
 
373
375
  Timing rides the motion axis, so `instant` collapses it to a cut (0s) and
374
- `extra` stretches it, for free. Three components had a byte-identical copy of
375
- these keyframes under three different names before this existed; the panel
376
- that needs different motion should override `animation`, not re-declare it.
376
+ `extra` stretches it, for free. Two components carried a byte-identical copy of
377
+ these keyframes under two different names before this existed (a Step's body
378
+ and the error panel's detail), and the swarm tree would have made a third; the
379
+ panel that needs different motion should override `animation`, not re-declare
380
+ it.
377
381
 
378
382
  Declared in `index.css`.
379
383
 
@@ -908,6 +912,9 @@ Inherits all props from `HTMLAttributes<HTMLDivElement>`.
908
912
  |------|------|---------|-------|
909
913
  | `ref?` | `HTMLDivElement \| null` | `null` | bindable |
910
914
  | `size?` | `'sm' \| 'md' \| 'lg'` | `'lg'` | Height tier (scales with density). Default `lg` — a full toolbar. |
915
+ | `content?` | `'tier' \| 'xxs'` | `'tier'` | Tier of the controls inside — drives the bar's padding and gap so they
916
+ stay concentric to them. `tier` (default) is one tier smaller; `xxs` is
917
+ the compact pairing (xxs pills/fields in an sm or md bar). |
911
918
  | `children?` | `Snippet` | — | — |
912
919
 
913
920
  <!-- end: bar -->
@@ -2848,12 +2855,14 @@ import { Input } from "$lib/ui/input";
2848
2855
 
2849
2856
  ## Props
2850
2857
 
2851
- Inherits all props from `HTMLInputAttributes`.
2858
+ Inherits all props from `Omit<HTMLInputAttributes, 'size'>`.
2852
2859
 
2853
2860
  | Prop | Type | Default | Notes |
2854
2861
  |------|------|---------|-------|
2855
2862
  | `ref?` | `HTMLInputElement \| null` | `null` | bindable |
2856
2863
  | `icon?` | `Component<IconProps> \| null` | `null` | Lucide glyph rendered inside the field, left of the text. |
2864
+ | `size?` | `'md' \| 'xxs'` | `'md'` | Field tier. `xxs` is badge height — for compact chrome where the field
2865
+ sits level with xxs buttons. See inputVariants. |
2857
2866
  | `bare?` | `boolean` | `false` | Drop the field chrome — for an input inside a container that is already the field. |
2858
2867
  | `reveal?` | `boolean` | `false` | Hold the field chrome back until hover — for a title or label that IS the content. |
2859
2868
  | `value?` | `(forwarded)` | `''` | bindable |
@@ -15,7 +15,7 @@ Named exports wrap a single primitive (`Button`, `Badge`, `Chip`, `Input`, …).
15
15
 
16
16
  Five orthogonal axes are exposed as HTML attributes on any ancestor. The entire subtree re-tiers through CSS inheritance — no JS, no context provider.
17
17
 
18
- - `data-mode`: **dense** (default) · balanced · spacious · touch — adjusts the spacing atom (`--era-sp`).
18
+ - `data-mode`: **dense** (default) · balanced · spacious · touch — adjusts the spacing atom (`--era-sp`) and the type size (`--era-text`: 12px at dense, 14px above).
19
19
  - `data-surface`: **flat** (default) · glass · bevel — swaps the background treatment.
20
20
  - `data-corners`: **rounded** (default) · square — scales all radii toward 0.
21
21
  - `data-font`: **mono** (default) · sans · serif — switches the font stack.
@@ -44,7 +44,7 @@
44
44
  "slug": "utilities",
45
45
  "title": "Utilities",
46
46
  "summary": "The CSS classes era ships alongside the components — links, ink-centred text, shimmer, hidden scrollbars.",
47
- "tokenEstimate": 5277,
47
+ "tokenEstimate": 5333,
48
48
  "sections": [
49
49
  "Overview",
50
50
  "era-interactive",
@@ -159,7 +159,7 @@
159
159
  "slug": "bar",
160
160
  "title": "Bar",
161
161
  "summary": "A flex container for toolbars and headers, in three heights.",
162
- "tokenEstimate": 118,
162
+ "tokenEstimate": 181,
163
163
  "sections": [
164
164
  "Import",
165
165
  "Props"
@@ -634,7 +634,7 @@
634
634
  "slug": "input",
635
635
  "title": "Input",
636
636
  "summary": "A text input field, with textarea variant.",
637
- "tokenEstimate": 176,
637
+ "tokenEstimate": 219,
638
638
  "sections": [
639
639
  "Import",
640
640
  "Props"
@@ -997,7 +997,7 @@
997
997
  ],
998
998
  "file": "index.css",
999
999
  "useCase": "vertically center single-line control text (a label beside an icon, a button's own text) without a translateY nudge — fixed-height, non-editable elements only; see era-text-trim-caps for ALL-CAPS strings.",
1000
- "description": "Font-agnostic vertical ink-centering for single-line control text.\nMeasured verdict (4 fonts x 2 dpr, ink from device-pixel screenshots):\nthe offset has TWO causes — a fractional line box (14px x 1.5lh = 21px in\na 24px row places ink on a half pixel, ~1px error) and font ascent/descent\nasymmetry (+-1px, sign flips per font). line-height: 1 fixes the first and\nis the Firefox fallback; text-box ex/alphabetic fixes the second by\ncentering the x-height band — the visual mass of MIXED-CASE text (caps and\ndescenders cancel above/below). cap alphabetic is the spec-example recipe\nbut is descender-blind (reads high; CSSWG #9148) — measured 4x worse here.\nWhere text-box is supported it fully determines the box, so the two\ndeclarations never fight. Worst-case residual: 0.44px.\n\nThe hard rules concern overflow clipping, and there are TWO of them.\n\n1. NEVER on a NATIVE EDITABLE — <input>, <textarea>, <select>. These clip to\n a UA-owned inner editor box that IS the trimmed line box, so a fixed\n height does NOT hold it open: whatever you type gets its ascenders and\n descenders sliced off (a \"d\" renders as an \"o\"). This rule used to read\n the other way round — it claimed a fixed-height <input> was the trim's\n intended home — and the Input component shipped chopped text as a result.\n A tier-height input centres its own text; it needs no trim.\n\n2. NEVER on an AUTO-height element that clips — a `truncate` child, an\n auto-height box with overflow-hidden. With no height to hold it open the\n box collapses to the x-height band (~7.5px at 14px) and the clip chops the\n glyphs into a middle stripe. Put the trim on the fixed-height ROW instead\n and let the child centre via the row's items-center.\n\nA fixed-height NON-editable element is the safe home: the ink overflows the\ntrimmed LINE box but stays inside the padding box, which is what actually\nclips. Both rules are enforced at runtime by typography/collapsed-text-trim.",
1000
+ "description": "Font-agnostic vertical ink-centering for single-line control text.\nMeasured verdict (4 fonts x 2 dpr, ink from device-pixel screenshots):\nthe offset has TWO causes — a fractional line box (14px x 1.5lh = 21px in\na 24px row places ink on a half pixel, ~1px error) and font ascent/descent\nasymmetry (+-1px, sign flips per font). line-height: 1 fixes the first and\nis the Firefox fallback; text-box ex/alphabetic fixes the second by\ncentering the x-height band — the visual mass of MIXED-CASE text (caps and\ndescenders cancel above/below). cap alphabetic is the spec-example recipe\nbut is descender-blind (reads high; CSSWG #9148) — measured 4x worse here.\nWhere text-box is supported it fully determines the box, so the two\ndeclarations never fight. Worst-case residual: 0.44px (measured at 14px; the\nmechanism is font-relative, so dense's 12px inherits the same guarantee with\na proportionally smaller residual).\n\nThe hard rules concern overflow clipping, and there are TWO of them.\n\n1. NEVER on a NATIVE EDITABLE — <input>, <textarea>, <select>. These clip to\n a UA-owned inner editor box that IS the trimmed line box, so a fixed\n height does NOT hold it open: whatever you type gets its ascenders and\n descenders sliced off (a \"d\" renders as an \"o\"). This rule used to read\n the other way round — it claimed a fixed-height <input> was the trim's\n intended home — and the Input component shipped chopped text as a result.\n A tier-height input centres its own text; it needs no trim.\n\n2. NEVER on an AUTO-height element that clips — a `truncate` child, an\n auto-height box with overflow-hidden. With no height to hold it open the\n box collapses to the x-height band (~7.5px at 14px) and the clip chops the\n glyphs into a middle stripe. Put the trim on the fixed-height ROW instead\n and let the child centre via the row's items-center.\n\nA fixed-height NON-editable element is the safe home: the ink overflows the\ntrimmed LINE box but stays inside the padding box, which is what actually\nclips. Both rules are enforced at runtime by typography/collapsed-text-trim.",
1001
1001
  "css": "@utility era-text-trim {\n\tline-height: 1;\n\ttext-box: trim-both ex alphabetic;\n}"
1002
1002
  },
1003
1003
  {
@@ -1037,7 +1037,7 @@
1037
1037
  ],
1038
1038
  "file": "index.css",
1039
1039
  "useCase": "the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).",
1040
- "description": "The open/close animation for a bits-ui Collapsible.Content — the height\nsweep every disclosure in the library uses (a Step's body, an error panel's\ndetail, a swarm node's children).\n\nIt has to be a keyframe animation rather than a transition because the\nnatural height is only knowable at open time: bits measures the content and\npublishes `--bits-collapsible-content-height`, which the `to` frame reads.\nThe class also owns `overflow: hidden` — animating height without it just\nspills the content out of the shrinking box.\n\nTiming rides the motion axis, so `instant` collapses it to a cut (0s) and\n`extra` stretches it, for free. Three components had a byte-identical copy of\nthese keyframes under three different names before this existed; the panel\nthat needs different motion should override `animation`, not re-declare it.",
1040
+ "description": "The open/close animation for a bits-ui Collapsible.Content — the height\nsweep every disclosure in the library uses (a Step's body, an error panel's\ndetail, a swarm node's children).\n\nIt has to be a keyframe animation rather than a transition because the\nnatural height is only knowable at open time: bits measures the content and\npublishes `--bits-collapsible-content-height`, which the `to` frame reads.\nThe class also owns `overflow: hidden` — animating height without it just\nspills the content out of the shrinking box.\n\nTiming rides the motion axis, so `instant` collapses it to a cut (0s) and\n`extra` stretches it, for free. Two components carried a byte-identical copy of\nthese keyframes under two different names before this existed (a Step's body\nand the error panel's detail), and the swarm tree would have made a third; the\npanel that needs different motion should override `animation`, not re-declare\nit.",
1041
1041
  "css": "@utility era-collapse {\n\toverflow: hidden;\n\t&[data-state='open'] {\n\t\tanimation: era-collapse-open var(--era-duration) var(--era-ease);\n\t}\n\t&[data-state='closed'] {\n\t\tanimation: era-collapse-close var(--era-duration) var(--era-ease);\n\t}\n}"
1042
1042
  },
1043
1043
  {
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "file": "index.css",
39
39
  "useCase": "vertically center single-line control text (a label beside an icon, a button's own text) without a translateY nudge — fixed-height, non-editable elements only; see era-text-trim-caps for ALL-CAPS strings.",
40
- "description": "Font-agnostic vertical ink-centering for single-line control text.\nMeasured verdict (4 fonts x 2 dpr, ink from device-pixel screenshots):\nthe offset has TWO causes — a fractional line box (14px x 1.5lh = 21px in\na 24px row places ink on a half pixel, ~1px error) and font ascent/descent\nasymmetry (+-1px, sign flips per font). line-height: 1 fixes the first and\nis the Firefox fallback; text-box ex/alphabetic fixes the second by\ncentering the x-height band — the visual mass of MIXED-CASE text (caps and\ndescenders cancel above/below). cap alphabetic is the spec-example recipe\nbut is descender-blind (reads high; CSSWG #9148) — measured 4x worse here.\nWhere text-box is supported it fully determines the box, so the two\ndeclarations never fight. Worst-case residual: 0.44px.\n\nThe hard rules concern overflow clipping, and there are TWO of them.\n\n1. NEVER on a NATIVE EDITABLE — <input>, <textarea>, <select>. These clip to\n a UA-owned inner editor box that IS the trimmed line box, so a fixed\n height does NOT hold it open: whatever you type gets its ascenders and\n descenders sliced off (a \"d\" renders as an \"o\"). This rule used to read\n the other way round — it claimed a fixed-height <input> was the trim's\n intended home — and the Input component shipped chopped text as a result.\n A tier-height input centres its own text; it needs no trim.\n\n2. NEVER on an AUTO-height element that clips — a `truncate` child, an\n auto-height box with overflow-hidden. With no height to hold it open the\n box collapses to the x-height band (~7.5px at 14px) and the clip chops the\n glyphs into a middle stripe. Put the trim on the fixed-height ROW instead\n and let the child centre via the row's items-center.\n\nA fixed-height NON-editable element is the safe home: the ink overflows the\ntrimmed LINE box but stays inside the padding box, which is what actually\nclips. Both rules are enforced at runtime by typography/collapsed-text-trim.",
40
+ "description": "Font-agnostic vertical ink-centering for single-line control text.\nMeasured verdict (4 fonts x 2 dpr, ink from device-pixel screenshots):\nthe offset has TWO causes — a fractional line box (14px x 1.5lh = 21px in\na 24px row places ink on a half pixel, ~1px error) and font ascent/descent\nasymmetry (+-1px, sign flips per font). line-height: 1 fixes the first and\nis the Firefox fallback; text-box ex/alphabetic fixes the second by\ncentering the x-height band — the visual mass of MIXED-CASE text (caps and\ndescenders cancel above/below). cap alphabetic is the spec-example recipe\nbut is descender-blind (reads high; CSSWG #9148) — measured 4x worse here.\nWhere text-box is supported it fully determines the box, so the two\ndeclarations never fight. Worst-case residual: 0.44px (measured at 14px; the\nmechanism is font-relative, so dense's 12px inherits the same guarantee with\na proportionally smaller residual).\n\nThe hard rules concern overflow clipping, and there are TWO of them.\n\n1. NEVER on a NATIVE EDITABLE — <input>, <textarea>, <select>. These clip to\n a UA-owned inner editor box that IS the trimmed line box, so a fixed\n height does NOT hold it open: whatever you type gets its ascenders and\n descenders sliced off (a \"d\" renders as an \"o\"). This rule used to read\n the other way round — it claimed a fixed-height <input> was the trim's\n intended home — and the Input component shipped chopped text as a result.\n A tier-height input centres its own text; it needs no trim.\n\n2. NEVER on an AUTO-height element that clips — a `truncate` child, an\n auto-height box with overflow-hidden. With no height to hold it open the\n box collapses to the x-height band (~7.5px at 14px) and the clip chops the\n glyphs into a middle stripe. Put the trim on the fixed-height ROW instead\n and let the child centre via the row's items-center.\n\nA fixed-height NON-editable element is the safe home: the ink overflows the\ntrimmed LINE box but stays inside the padding box, which is what actually\nclips. Both rules are enforced at runtime by typography/collapsed-text-trim.",
41
41
  "css": "@utility era-text-trim {\n\tline-height: 1;\n\ttext-box: trim-both ex alphabetic;\n}"
42
42
  },
43
43
  {
@@ -77,7 +77,7 @@
77
77
  ],
78
78
  "file": "index.css",
79
79
  "useCase": "the height sweep for a bits-ui Collapsible.Content — put it on the Content element itself (it carries its own overflow: hidden).",
80
- "description": "The open/close animation for a bits-ui Collapsible.Content — the height\nsweep every disclosure in the library uses (a Step's body, an error panel's\ndetail, a swarm node's children).\n\nIt has to be a keyframe animation rather than a transition because the\nnatural height is only knowable at open time: bits measures the content and\npublishes `--bits-collapsible-content-height`, which the `to` frame reads.\nThe class also owns `overflow: hidden` — animating height without it just\nspills the content out of the shrinking box.\n\nTiming rides the motion axis, so `instant` collapses it to a cut (0s) and\n`extra` stretches it, for free. Three components had a byte-identical copy of\nthese keyframes under three different names before this existed; the panel\nthat needs different motion should override `animation`, not re-declare it.",
80
+ "description": "The open/close animation for a bits-ui Collapsible.Content — the height\nsweep every disclosure in the library uses (a Step's body, an error panel's\ndetail, a swarm node's children).\n\nIt has to be a keyframe animation rather than a transition because the\nnatural height is only knowable at open time: bits measures the content and\npublishes `--bits-collapsible-content-height`, which the `to` frame reads.\nThe class also owns `overflow: hidden` — animating height without it just\nspills the content out of the shrinking box.\n\nTiming rides the motion axis, so `instant` collapses it to a cut (0s) and\n`extra` stretches it, for free. Two components carried a byte-identical copy of\nthese keyframes under two different names before this existed (a Step's body\nand the error panel's detail), and the swarm tree would have made a third; the\npanel that needs different motion should override `animation`, not re-declare\nit.",
81
81
  "css": "@utility era-collapse {\n\toverflow: hidden;\n\t&[data-state='open'] {\n\t\tanimation: era-collapse-open var(--era-duration) var(--era-ease);\n\t}\n\t&[data-state='closed'] {\n\t\tanimation: era-collapse-close var(--era-duration) var(--era-ease);\n\t}\n}"
82
82
  },
83
83
  {
@@ -146,7 +146,9 @@ centering the x-height band — the visual mass of MIXED-CASE text (caps and
146
146
  descenders cancel above/below). cap alphabetic is the spec-example recipe
147
147
  but is descender-blind (reads high; CSSWG #9148) — measured 4x worse here.
148
148
  Where text-box is supported it fully determines the box, so the two
149
- declarations never fight. Worst-case residual: 0.44px.
149
+ declarations never fight. Worst-case residual: 0.44px (measured at 14px; the
150
+ mechanism is font-relative, so dense's 12px inherits the same guarantee with
151
+ a proportionally smaller residual).
150
152
 
151
153
  The hard rules concern overflow clipping, and there are TWO of them.
152
154
 
@@ -268,9 +270,11 @@ The class also owns `overflow: hidden` — animating height without it just
268
270
  spills the content out of the shrinking box.
269
271
 
270
272
  Timing rides the motion axis, so `instant` collapses it to a cut (0s) and
271
- `extra` stretches it, for free. Three components had a byte-identical copy of
272
- these keyframes under three different names before this existed; the panel
273
- that needs different motion should override `animation`, not re-declare it.
273
+ `extra` stretches it, for free. Two components carried a byte-identical copy of
274
+ these keyframes under two different names before this existed (a Step's body
275
+ and the error panel's detail), and the swarm tree would have made a third; the
276
+ panel that needs different motion should override `animation`, not re-declare
277
+ it.
274
278
 
275
279
  Declared in `index.css`.
276
280
 
@@ -13,7 +13,7 @@
13
13
  * lg = 4·sp (bars, containers)
14
14
  *
15
15
  * xs carries a +6 floor so icons stay legible (≥14px) at every sp. xxs sits
16
- * 4px above xs so 14px text has just enough pill around it while fitting
16
+ * 4px above xs so body text has just enough pill around it while fitting
17
17
  * inside an md container with a visible concentric gap (≥3px at dense). sm
18
18
  * keeps the generous 2·sp + 6 that gives switch thumbs their sp/2 slot and
19
19
  * KV rows their padded feel. md and lg are pure sp multiples so button and
@@ -43,6 +43,7 @@
43
43
  * --era-px-{sm,md,lg} sp/2 — tier-to-tier nesting padding
44
44
  * --era-gap sp/2 — inter-element gap
45
45
  * --era-inset-{xxs,sm,md} (T − text)/2 — text-symmetric content padding
46
+ * (text is 12px at dense, 14 above)
46
47
  * --era-xs-inset-sm (sm − xs)/2 = sp/2 — centering an xs element inside sm
47
48
  *
48
49
  * OPTICAL PADDING (unbounded content containers)
@@ -52,7 +53,8 @@
52
53
  * height C inside a container of height T is (T − C)/2, linear in sp. But
53
54
  * cards, dialogs, and content panels have no bounding tier — their padding
54
55
  * is optical, and linear-in-sp padding falls behind as modes loosen: controls
55
- * grow linearly while text stays 14px, so whitespace must grow SUPER-linearly
56
+ * grow linearly while text barely moves (12px at dense, 14 above), so
57
+ * whitespace must grow SUPER-linearly
56
58
  * to hold the same perceived balance. (The tokens that already feel right
57
59
  * across modes — inset-md 5→17, content-p 16→48 — are exactly the convex
58
60
  * ones; the cramped ones — card's old sp/2 = 4→8 — are the linear ones.)
@@ -154,9 +156,9 @@
154
156
 
155
157
  /* Content insets — text-symmetric padding (vertical centering, reused as
156
158
  horizontal only where the content is a single square glyph). */
157
- --era-inset-xxs: max(0px, calc((var(--era-h-xxs) - var(--era-text)) / 2)); /* 2/3/4 */
158
- --era-inset-sm: max(0px, calc((var(--era-h-sm) - var(--era-text)) / 2)); /* 4/6/8 */
159
- --era-inset-md: max(0px, calc((var(--era-h-md) - var(--era-text)) / 2)); /* 5/8/11 */
159
+ --era-inset-xxs: max(0px, calc((var(--era-h-xxs) - var(--era-text)) / 2)); /* 3/3/4/6 */
160
+ --era-inset-sm: max(0px, calc((var(--era-h-sm) - var(--era-text)) / 2)); /* 5/6/8/12 */
161
+ --era-inset-md: max(0px, calc((var(--era-h-md) - var(--era-text)) / 2)); /* 6/8/11/17 */
160
162
 
161
163
  /* Pill side padding — horizontal breathing room for badge/chip text.
162
164
  Text wants MORE horizontal than vertical padding: line-height adds
@@ -167,8 +169,8 @@
167
169
  is narrower than the line, this comes out ~2× the vertical inset on its
168
170
  own — and a lone glyph still measures exactly the tier width, so the
169
171
  "single glyph = square" floor (min-w = h) is preserved, not broken. */
170
- --era-pill-xxs: max(0px, calc((var(--era-h-xxs) - 0.6 * var(--era-text)) / 2)); /* 4.8/5.8/6.8 */
171
- --era-pill-sm: max(0px, calc((var(--era-h-sm) - 0.6 * var(--era-text)) / 2)); /* 6.8/8.8/10.8 */
172
+ --era-pill-xxs: max(0px, calc((var(--era-h-xxs) - 0.6 * var(--era-text)) / 2)); /* 5.4/5.8/6.8 */
173
+ --era-pill-sm: max(0px, calc((var(--era-h-sm) - 0.6 * var(--era-text)) / 2)); /* 7.4/8.8/10.8 */
172
174
 
173
175
  /* Text-field side padding — the horizontal room where typed text starts in an
174
176
  md-tier input (Input, Textarea, combobox/command inputs, date/time fields).
@@ -261,7 +263,8 @@
261
263
  }
262
264
 
263
265
  /* -------------------------------------------------- */
264
- /* Mode inputs — just --era-sp (and static --era-text). */
266
+ /* Mode inputs — --era-sp, and --era-text (12 at dense, */
267
+ /* 14 above; see the dense block). */
265
268
  /* The derivation above re-runs per element wherever */
266
269
  /* --era-sp is overridden. Animation timing is NOT here */
267
270
  /* — it's the motion axis (motion.css), orthogonal to */
@@ -286,7 +289,37 @@
286
289
  [data-mode='dense'] {
287
290
  --era-sp: 0.5rem; /* 8px */
288
291
  --era-density: 1; /* d = sp / 8px */
289
- --era-text: 0.875rem; /* 14px static across modes */
292
+ /* 12px at dense, 14px from balanced up (set in the block below).
293
+ *
294
+ * Type is the SECOND density input, and for a long time it was not one: the
295
+ * token read "14px — static across modes", so dense and balanced differed in
296
+ * their boxes and not in their type. That is what made dense feel like
297
+ * balanced-with-tighter-gaps rather than a genuinely dense mode — the chrome
298
+ * shrank while the thing inside it did not.
299
+ *
300
+ * 12px is the size the reference terminal app is built at, and it makes the
301
+ * whole ladder line up with it: --era-inset-xxs becomes (18−12)/2 = 3px,
302
+ * which is exactly the 3px that app hardcodes for the wall and gap of an
303
+ * 18px control in a 24px bar. Same numbers, derived instead of typed.
304
+ *
305
+ * Everything text-derived follows: inset-xxs 2→3, inset-sm 4→5, inset-md
306
+ * 5→6, pill-xxs 4.8→5.4. Heights do NOT — they are sp-derived, so the tier
307
+ * ladder (14/18/22/24/32) is unchanged and dense stays dimensionally the
308
+ * same mode it was.
309
+ *
310
+ * Only dense moves. Balanced/spacious/touch keep 14px: they exist for
311
+ * reading comfort and pointer size, and shrinking their type would undo the
312
+ * thing they are for. */
313
+ --era-text: 0.75rem; /* 12px */
314
+ }
315
+
316
+ /* Balanced and up: 14px, the comfortable reading size. Declared once here and
317
+ * inherited by spacious/touch so there is a single place the non-dense size
318
+ * lives. */
319
+ [data-mode='balanced'],
320
+ [data-mode='spacious'],
321
+ [data-mode='touch'] {
322
+ --era-text: 0.875rem; /* 14px */
290
323
  }
291
324
 
292
325
  [data-mode='balanced'] {
@@ -126,7 +126,9 @@
126
126
  * descenders cancel above/below). cap alphabetic is the spec-example recipe
127
127
  * but is descender-blind (reads high; CSSWG #9148) — measured 4x worse here.
128
128
  * Where text-box is supported it fully determines the box, so the two
129
- * declarations never fight. Worst-case residual: 0.44px.
129
+ * declarations never fight. Worst-case residual: 0.44px (measured at 14px; the
130
+ * mechanism is font-relative, so dense's 12px inherits the same guarantee with
131
+ * a proportionally smaller residual).
130
132
  *
131
133
  * The hard rules concern overflow clipping, and there are TWO of them.
132
134
  *
@@ -234,9 +236,11 @@
234
236
  * spills the content out of the shrinking box.
235
237
  *
236
238
  * Timing rides the motion axis, so `instant` collapses it to a cut (0s) and
237
- * `extra` stretches it, for free. Three components had a byte-identical copy of
238
- * these keyframes under three different names before this existed; the panel
239
- * that needs different motion should override `animation`, not re-declare it.
239
+ * `extra` stretches it, for free. Two components carried a byte-identical copy of
240
+ * these keyframes under two different names before this existed (a Step's body
241
+ * and the error panel's detail), and the swarm tree would have made a third; the
242
+ * panel that needs different motion should override `animation`, not re-declare
243
+ * it.
240
244
  *
241
245
  * @use the height sweep for a bits-ui Collapsible.Content — put it on the
242
246
  * Content element itself (it carries its own overflow: hidden). */
@@ -10,15 +10,42 @@
10
10
  // Radius stays concentric to the height; gap/px are sp/2 (xs-inset-sm),
11
11
  // the even gap for one-tier-smaller content at every size.
12
12
  export const barVariants = tv({
13
- base: 'flex items-center gap-(--era-xs-inset-sm) px-(--era-xs-inset-sm) era-text-trim',
13
+ base: 'flex items-center era-text-trim',
14
14
  variants: {
15
15
  size: {
16
16
  sm: 'h-(--era-h-sm) rounded-(--era-rd-sm)',
17
17
  md: 'h-(--era-h-md) rounded-(--era-rd-md)',
18
18
  lg: 'h-(--era-h-lg) rounded-(--era-rd-lg)'
19
+ },
20
+ /**
21
+ * The tier of the CONTROLS inside, because that is what the bar's own
22
+ * padding and gap have to be concentric to — (bar − content)/2 on every
23
+ * side. It is a variant rather than a caller's class because the ladder
24
+ * math belongs next to the heights it is derived from: three call sites
25
+ * had each restated the two-tier value by hand, and the geometry spec had
26
+ * been rewritten twice chasing which token they were restating.
27
+ *
28
+ * - `tier` (default) — one tier smaller, the pairing above. sm⊃xs, md⊃sm
29
+ * and lg⊃md all equal sp/2, which is why one token covers every size.
30
+ * - `xxs` — the compact pairing: xxs pills and fields (a filter beside an
31
+ * xxs icon button, a title bar). Two tiers down, so the inset differs
32
+ * per size and comes from the compounds below.
33
+ */
34
+ content: {
35
+ tier: 'gap-(--era-xs-inset-sm) px-(--era-xs-inset-sm)',
36
+ xxs: ''
19
37
  }
20
38
  },
21
- defaultVariants: { size: 'lg' }
39
+ compoundVariants: [
40
+ // md ⊃ xxs = (3sp − sp − 10)/2 — the named token.
41
+ { size: 'md', content: 'xxs', class: 'gap-(--era-xxs-inset-md) px-(--era-xxs-inset-md)' },
42
+ // sm ⊃ xxs = (2sp + 6 − sp − 10)/2 = (sp − 4)/2, which is ALSO (h-xxs − text)/2
43
+ // at 14px text — the same number --era-inset-xxs already carries, so the
44
+ // identity is reused rather than a fifth token invented. (lg ⊃ xxs has no
45
+ // token because nothing pairs a page header with pill-tier controls.)
46
+ { size: 'sm', content: 'xxs', class: 'gap-(--era-inset-xxs) px-(--era-inset-xxs)' }
47
+ ],
48
+ defaultVariants: { size: 'lg', content: 'tier' }
22
49
  });
23
50
  </script>
24
51
 
@@ -29,6 +56,7 @@
29
56
  let {
30
57
  ref = $bindable(null),
31
58
  size = 'lg',
59
+ content = 'tier',
32
60
  children,
33
61
  class: className,
34
62
  ...restProps
@@ -36,10 +64,16 @@
36
64
  ref?: HTMLDivElement | null;
37
65
  /** Height tier (scales with density). Default `lg` — a full toolbar. */
38
66
  size?: 'sm' | 'md' | 'lg';
67
+ /**
68
+ * Tier of the controls inside — drives the bar's padding and gap so they
69
+ * stay concentric to them. `tier` (default) is one tier smaller; `xxs` is
70
+ * the compact pairing (xxs pills/fields in an sm or md bar).
71
+ */
72
+ content?: 'tier' | 'xxs';
39
73
  children?: Snippet;
40
74
  } = $props();
41
75
  </script>
42
76
 
43
- <div bind:this={ref} class={cn(barVariants({ size }), className)} {...restProps}>
77
+ <div bind:this={ref} class={cn(barVariants({ size, content }), className)} {...restProps}>
44
78
  {@render children?.()}
45
79
  </div>
@@ -4,18 +4,72 @@ export declare const barVariants: import("tailwind-variants").TVReturnType<{
4
4
  md: "h-(--era-h-md) rounded-(--era-rd-md)";
5
5
  lg: "h-(--era-h-lg) rounded-(--era-rd-lg)";
6
6
  };
7
- }, undefined, "flex items-center gap-(--era-xs-inset-sm) px-(--era-xs-inset-sm) era-text-trim", {
7
+ /**
8
+ * The tier of the CONTROLS inside, because that is what the bar's own
9
+ * padding and gap have to be concentric to — (bar − content)/2 on every
10
+ * side. It is a variant rather than a caller's class because the ladder
11
+ * math belongs next to the heights it is derived from: three call sites
12
+ * had each restated the two-tier value by hand, and the geometry spec had
13
+ * been rewritten twice chasing which token they were restating.
14
+ *
15
+ * - `tier` (default) — one tier smaller, the pairing above. sm⊃xs, md⊃sm
16
+ * and lg⊃md all equal sp/2, which is why one token covers every size.
17
+ * - `xxs` — the compact pairing: xxs pills and fields (a filter beside an
18
+ * xxs icon button, a title bar). Two tiers down, so the inset differs
19
+ * per size and comes from the compounds below.
20
+ */
21
+ content: {
22
+ tier: "gap-(--era-xs-inset-sm) px-(--era-xs-inset-sm)";
23
+ xxs: "";
24
+ };
25
+ }, undefined, "flex items-center era-text-trim", {
8
26
  size: {
9
27
  sm: "h-(--era-h-sm) rounded-(--era-rd-sm)";
10
28
  md: "h-(--era-h-md) rounded-(--era-rd-md)";
11
29
  lg: "h-(--era-h-lg) rounded-(--era-rd-lg)";
12
30
  };
31
+ /**
32
+ * The tier of the CONTROLS inside, because that is what the bar's own
33
+ * padding and gap have to be concentric to — (bar − content)/2 on every
34
+ * side. It is a variant rather than a caller's class because the ladder
35
+ * math belongs next to the heights it is derived from: three call sites
36
+ * had each restated the two-tier value by hand, and the geometry spec had
37
+ * been rewritten twice chasing which token they were restating.
38
+ *
39
+ * - `tier` (default) — one tier smaller, the pairing above. sm⊃xs, md⊃sm
40
+ * and lg⊃md all equal sp/2, which is why one token covers every size.
41
+ * - `xxs` — the compact pairing: xxs pills and fields (a filter beside an
42
+ * xxs icon button, a title bar). Two tiers down, so the inset differs
43
+ * per size and comes from the compounds below.
44
+ */
45
+ content: {
46
+ tier: "gap-(--era-xs-inset-sm) px-(--era-xs-inset-sm)";
47
+ xxs: "";
48
+ };
13
49
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
14
50
  size: {
15
51
  sm: "h-(--era-h-sm) rounded-(--era-rd-sm)";
16
52
  md: "h-(--era-h-md) rounded-(--era-rd-md)";
17
53
  lg: "h-(--era-h-lg) rounded-(--era-rd-lg)";
18
54
  };
55
+ /**
56
+ * The tier of the CONTROLS inside, because that is what the bar's own
57
+ * padding and gap have to be concentric to — (bar − content)/2 on every
58
+ * side. It is a variant rather than a caller's class because the ladder
59
+ * math belongs next to the heights it is derived from: three call sites
60
+ * had each restated the two-tier value by hand, and the geometry spec had
61
+ * been rewritten twice chasing which token they were restating.
62
+ *
63
+ * - `tier` (default) — one tier smaller, the pairing above. sm⊃xs, md⊃sm
64
+ * and lg⊃md all equal sp/2, which is why one token covers every size.
65
+ * - `xxs` — the compact pairing: xxs pills and fields (a filter beside an
66
+ * xxs icon button, a title bar). Two tiers down, so the inset differs
67
+ * per size and comes from the compounds below.
68
+ */
69
+ content: {
70
+ tier: "gap-(--era-xs-inset-sm) px-(--era-xs-inset-sm)";
71
+ xxs: "";
72
+ };
19
73
  }, undefined>>;
20
74
  import type { HTMLAttributes } from 'svelte/elements';
21
75
  import type { Snippet } from 'svelte';
@@ -23,6 +77,12 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
23
77
  ref?: HTMLDivElement | null;
24
78
  /** Height tier (scales with density). Default `lg` — a full toolbar. */
25
79
  size?: 'sm' | 'md' | 'lg';
80
+ /**
81
+ * Tier of the controls inside — drives the bar's padding and gap so they
82
+ * stay concentric to them. `tier` (default) is one tier smaller; `xxs` is
83
+ * the compact pairing (xxs pills/fields in an sm or md bar).
84
+ */
85
+ content?: 'tier' | 'xxs';
26
86
  children?: Snippet;
27
87
  };
28
88
  declare const Bar: import("svelte").Component<$$ComponentProps, {}, "ref">;
@@ -9,14 +9,25 @@
9
9
  ref = $bindable(null),
10
10
  value = $bindable(''),
11
11
  icon = null,
12
+ size = 'md',
12
13
  bare = false,
13
14
  reveal = false,
14
15
  class: className,
15
16
  ...restProps
16
- }: HTMLInputAttributes & {
17
+ // The native `size` attribute (visible width in characters) is shadowed on
18
+ // purpose: era fields size by tier and by flex, never by character count,
19
+ // and `size` is the name every other component in the library uses for its
20
+ // tier. Consumers who genuinely want the HTML attribute can set it via
21
+ // {...restProps} spread from an object.
22
+ }: Omit<HTMLInputAttributes, 'size'> & {
17
23
  ref?: HTMLInputElement | null;
18
24
  /** Lucide glyph rendered inside the field, left of the text. */
19
25
  icon?: Component<IconProps> | null;
26
+ /**
27
+ * Field tier. `xxs` is badge height — for compact chrome where the field
28
+ * sits level with xxs buttons. See inputVariants.
29
+ */
30
+ size?: 'md' | 'xxs';
20
31
  /** Drop the field chrome — for an input inside a container that is already the field. */
21
32
  bare?: boolean;
22
33
  /** Hold the field chrome back until hover — for a title or label that IS the content. */
@@ -34,6 +45,13 @@
34
45
  // chrome" cannot tell a sanctioned exception from a regression — which is the
35
46
  // hole the Notes fields sat in. Naming the kind closes it.
36
47
  const kind = $derived(reveal ? 'reveal' : bare ? 'bare' : '');
48
+
49
+ // The field's glyph is the icon tier OF ITS OWN TIER, which is what puts it on
50
+ // the same optical footing as the icon button beside it: an md field pairs
51
+ // with a default button (h-xs glyph), an xxs field with an xxs button
52
+ // (icon-xxs). One size for both would leave the two glyphs in one bar drawn at
53
+ // different weights.
54
+ const glyphClass = $derived(size === 'xxs' ? 'size-(--era-icon-xxs)' : 'size-(--era-h-xs)');
37
55
  </script>
38
56
 
39
57
  {#if icon}
@@ -48,9 +66,10 @@
48
66
  arbitrary container that may carry its own fill. -->
49
67
  <div
50
68
  data-input-field={kind}
51
- class={cn(inputVariants({ bare, reveal }), 'items-center gap-(--era-gap)', className)}
69
+ data-input-size={size}
70
+ class={cn(inputVariants({ bare, reveal, size }), 'items-center gap-(--era-gap)', className)}
52
71
  >
53
- <Icon class="size-(--era-h-xs) shrink-0 text-muted" />
72
+ <Icon class={cn('shrink-0 text-muted', glyphClass)} />
54
73
  <input
55
74
  bind:this={ref}
56
75
  bind:value
@@ -63,7 +82,8 @@
63
82
  bind:this={ref}
64
83
  bind:value
65
84
  data-input-field={kind}
66
- class={cn(inputVariants({ bare, reveal }), textClass, className)}
85
+ data-input-size={size}
86
+ class={cn(inputVariants({ bare, reveal, size }), textClass, className)}
67
87
  {...restProps}
68
88
  />
69
89
  {/if}
@@ -1,10 +1,15 @@
1
1
  import type { Component } from 'svelte';
2
2
  import type { IconProps } from '@lucide/svelte';
3
3
  import type { HTMLInputAttributes } from 'svelte/elements';
4
- type $$ComponentProps = HTMLInputAttributes & {
4
+ type $$ComponentProps = Omit<HTMLInputAttributes, 'size'> & {
5
5
  ref?: HTMLInputElement | null;
6
6
  /** Lucide glyph rendered inside the field, left of the text. */
7
7
  icon?: Component<IconProps> | null;
8
+ /**
9
+ * Field tier. `xxs` is badge height — for compact chrome where the field
10
+ * sits level with xxs buttons. See inputVariants.
11
+ */
12
+ size?: 'md' | 'xxs';
8
13
  /** Drop the field chrome — for an input inside a container that is already the field. */
9
14
  bare?: boolean;
10
15
  /** Hold the field chrome back until hover — for a title or label that IS the content. */