@nocoo/basalt 3.1.0 → 3.2.1

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 (133) hide show
  1. package/ai/DESIGN.md +128 -55
  2. package/ai/INTEGRATION.md +9 -9
  3. package/ai/USAGE.md +1 -1
  4. package/ai/registry.json +166 -124
  5. package/dist/charts/config.d.ts +1 -1
  6. package/dist/charts/config.js +1 -1
  7. package/dist/charts/config.js.map +1 -1
  8. package/dist/charts/frame.js +2 -2
  9. package/dist/charts/frame.js.map +1 -1
  10. package/dist/charts/heatmap-calendar.js +4 -4
  11. package/dist/charts/heatmap-calendar.js.map +1 -1
  12. package/dist/charts/heatmap-matrix.js +5 -5
  13. package/dist/charts/heatmap-matrix.js.map +1 -1
  14. package/dist/charts/sparkline.js +1 -1
  15. package/dist/charts/sparkline.js.map +1 -1
  16. package/dist/charts/stat-card.js +1 -1
  17. package/dist/charts/stat-card.js.map +1 -1
  18. package/dist/components/alert-dialog.js +4 -4
  19. package/dist/components/alert-dialog.js.map +1 -1
  20. package/dist/components/app-header.js +1 -1
  21. package/dist/components/app-header.js.map +1 -1
  22. package/dist/components/app-shell.js +1 -1
  23. package/dist/components/app-shell.js.map +1 -1
  24. package/dist/components/approval-card.js +5 -6
  25. package/dist/components/approval-card.js.map +1 -1
  26. package/dist/components/avatar.js +1 -1
  27. package/dist/components/avatar.js.map +1 -1
  28. package/dist/components/chat-bubble.js +1 -1
  29. package/dist/components/chat-bubble.js.map +1 -1
  30. package/dist/components/chat-composer.js +1 -1
  31. package/dist/components/chat-composer.js.map +1 -1
  32. package/dist/components/chat-header.js +1 -1
  33. package/dist/components/chat-header.js.map +1 -1
  34. package/dist/components/chat-inbox.js +1 -1
  35. package/dist/components/chat-inbox.js.map +1 -1
  36. package/dist/components/chat-markdown.js +1 -1
  37. package/dist/components/chat-markdown.js.map +1 -1
  38. package/dist/components/chat-message.js +2 -2
  39. package/dist/components/chat-message.js.map +1 -1
  40. package/dist/components/code.js +5 -4
  41. package/dist/components/code.js.map +1 -1
  42. package/dist/components/command-palette.js +2 -2
  43. package/dist/components/command-palette.js.map +1 -1
  44. package/dist/components/context-cards.js +6 -6
  45. package/dist/components/context-cards.js.map +1 -1
  46. package/dist/components/context-menu.js +1 -1
  47. package/dist/components/context-menu.js.map +1 -1
  48. package/dist/components/data-table.js +127 -124
  49. package/dist/components/data-table.js.map +1 -1
  50. package/dist/components/dialog.js +2 -2
  51. package/dist/components/dialog.js.map +1 -1
  52. package/dist/components/diff-table.js +3 -3
  53. package/dist/components/diff-table.js.map +1 -1
  54. package/dist/components/dropdown-menu.js +1 -1
  55. package/dist/components/dropdown-menu.js.map +1 -1
  56. package/dist/components/editable-nav-item.js +2 -2
  57. package/dist/components/editable-nav-item.js.map +1 -1
  58. package/dist/components/file-dropzone.js +1 -1
  59. package/dist/components/file-dropzone.js.map +1 -1
  60. package/dist/components/filter-bar.js +1 -1
  61. package/dist/components/filter-bar.js.map +1 -1
  62. package/dist/components/icon-picker.js +3 -3
  63. package/dist/components/icon-picker.js.map +1 -1
  64. package/dist/components/inline-editable.js +1 -1
  65. package/dist/components/inline-editable.js.map +1 -1
  66. package/dist/components/input-area.js +1 -1
  67. package/dist/components/input-area.js.map +1 -1
  68. package/dist/components/input-group.js +2 -2
  69. package/dist/components/input-group.js.map +1 -1
  70. package/dist/components/layer-card.d.ts +6 -4
  71. package/dist/components/layer-card.js +61 -71
  72. package/dist/components/layer-card.js.map +1 -1
  73. package/dist/components/link.js +1 -1
  74. package/dist/components/link.js.map +1 -1
  75. package/dist/components/loader.js +1 -1
  76. package/dist/components/loader.js.map +1 -1
  77. package/dist/components/menu-bar.js +2 -2
  78. package/dist/components/menu-bar.js.map +1 -1
  79. package/dist/components/multi-select.js +1 -1
  80. package/dist/components/multi-select.js.map +1 -1
  81. package/dist/components/overlay.d.ts +3 -6
  82. package/dist/components/overlay.js +3 -3
  83. package/dist/components/overlay.js.map +1 -1
  84. package/dist/components/page-header.d.ts +2 -2
  85. package/dist/components/page-header.js +7 -7
  86. package/dist/components/page-header.js.map +1 -1
  87. package/dist/components/recommendation-card.js +2 -2
  88. package/dist/components/recommendation-card.js.map +1 -1
  89. package/dist/components/responsive-master-detail.js +2 -2
  90. package/dist/components/responsive-master-detail.js.map +1 -1
  91. package/dist/components/scroll-area.js +1 -1
  92. package/dist/components/scroll-area.js.map +1 -1
  93. package/dist/components/section-rule.js +1 -1
  94. package/dist/components/section-rule.js.map +1 -1
  95. package/dist/components/segment-control.js +1 -1
  96. package/dist/components/segment-control.js.map +1 -1
  97. package/dist/components/select.js +2 -2
  98. package/dist/components/select.js.map +1 -1
  99. package/dist/components/sensitive-input.js +1 -1
  100. package/dist/components/sensitive-input.js.map +1 -1
  101. package/dist/components/sheet.js +3 -3
  102. package/dist/components/sheet.js.map +1 -1
  103. package/dist/components/sidebar.js +3 -3
  104. package/dist/components/sidebar.js.map +1 -1
  105. package/dist/components/skeleton-line.js +1 -1
  106. package/dist/components/skeleton-line.js.map +1 -1
  107. package/dist/components/stat-strip.js +1 -1
  108. package/dist/components/stat-strip.js.map +1 -1
  109. package/dist/components/table.d.ts +2 -0
  110. package/dist/components/table.js +10 -12
  111. package/dist/components/table.js.map +1 -1
  112. package/dist/components/tabs.js +2 -2
  113. package/dist/components/tabs.js.map +1 -1
  114. package/dist/components/tag-badge.js +1 -1
  115. package/dist/components/tag-badge.js.map +1 -1
  116. package/dist/components/tag-color-picker.js +2 -2
  117. package/dist/components/tag-color-picker.js.map +1 -1
  118. package/dist/components/text.js +2 -2
  119. package/dist/components/text.js.map +1 -1
  120. package/dist/components/thinking.js +1 -1
  121. package/dist/components/thinking.js.map +1 -1
  122. package/dist/components/toast.js +1 -1
  123. package/dist/components/toast.js.map +1 -1
  124. package/dist/components/tool-chips.js +2 -2
  125. package/dist/components/tool-chips.js.map +1 -1
  126. package/dist/components/toolbar.js +8 -8
  127. package/dist/components/toolbar.js.map +1 -1
  128. package/dist/styles/base.css +71 -0
  129. package/dist/styles/standalone.css +187 -168
  130. package/dist/styles/tailwind.css +9 -2
  131. package/dist/styles/tokens.css +86 -40
  132. package/dist/utils/navigation.d.ts +1 -1
  133. package/package.json +1 -1
package/ai/DESIGN.md CHANGED
@@ -46,18 +46,30 @@ or content surface. Card and layout boundaries use their own four-step roles:
46
46
  | Large | `--basalt-space-card-lg` | `--basalt-space-layout-lg` | `1.5rem` | 24px |
47
47
  | Extra large | `--basalt-space-card-xl` | `--basalt-space-layout-xl` | `2rem` | 32px |
48
48
 
49
- Card padding defaults to medium. LayerCard header/footer insets are 16px horizontal
50
- and 12px vertical; Body/Well use 16px. `padding="sm|md|lg|xl"` selects unstructured
49
+ Card padding defaults to medium. LayerCard headers flow into Body by default:
50
+ 16px horizontal/top insets, no bottom inset and an 8px internal gap, without a divider.
51
+ `Header divided` uses 16px on all sides and a bottom rule. Disclosure headers
52
+ (`Header asChild`) keep 16px horizontal / 12px vertical insets and show their
53
+ bottom rule only when open. Footer keeps 16px horizontal / 12px vertical insets;
54
+ Body/Well use 16px. `padding="sm|md|lg|xl"` selects unstructured
51
55
  card padding; structured cards keep their slot-owned insets. Compact composite
52
56
  card panels use the same 16/12px panel aliases. Their internal buttons and rows
53
57
  keep control spacing; do not stretch action rows to the card scale.
54
58
 
59
+ Compact card/panel headers (`CodeBlock` header, `ContextCards` chunk header and
60
+ `LayerCard.Header asChild`) share a 44px single-line reference height: a 20px line
61
+ box plus 12px vertical panel inset on each side. Header action buttons use
62
+ `size="sm"` and `-my-basalt-space-sm` so they never inflate the header bar.
63
+ Wrapping and enlarged text may grow the header; do not impose a fixed height.
64
+
55
65
  Layout grids default to medium (16px). ContentIsland owns 16px insets on mobile,
56
66
  24px on tablets and 24px vertical / 32px horizontal from 1024px. The catalog shell
57
- keeps 8px outside the island on mobile and 12px from 768px. Ordinary application
58
- sections use 24px gaps, including catalog documentation. SectionRule
59
- uses 12px content separation for compact labels and 16px for reading headings. `Grid gap="sm|md|lg|xl"`
60
- and semantic utilities such as `gap-basalt-layout-xl` select these tiers.
67
+ keeps 8px outside the island on mobile and 12px from 768px; AppHeader uses the
68
+ same 8px / 12px horizontal inset to align with that outer island boundary, not its
69
+ inner content edge. Ordinary application sections use 24px gaps, including catalog
70
+ documentation. SectionRule uses 12px content separation for compact labels and
71
+ 16px for reading headings. `Grid gap="sm|md|lg|xl"` and semantic utilities such as
72
+ `gap-basalt-layout-xl` select these tiers.
61
73
  There is no universal height for either category.
62
74
 
63
75
  Dialog and AlertDialog panels use 16px outer insets on mobile and 24px from
@@ -67,24 +79,32 @@ small floating menus have separate inset contracts.
67
79
  Form rhythm distinguishes a field from a group: label/control/hint spacing stays
68
80
  8px, related fields use 16px layout gaps, and separate sections use 24px. A form's
69
81
  stack owns those gaps; individual Field components do not add outer margins.
70
- DialogFooter owns 16px separation from content and keeps 8px between its actions.
71
- Do not add a matching margin at every caller or enlarge controls to open a form.
82
+ DialogFooter, AlertDialogFooter and SheetFooter own 16px separation from content
83
+ and keep 8px between actions. Do not add a matching margin at every caller or
84
+ enlarge controls to open a form.
72
85
 
73
86
  Metric cards use the shared `StatCard`, not a hand-built Header/Body split. A single
74
87
  16px inset owns the title, value, comparison and supporting chart; 12px separates
75
88
  the heading, metric and content groups, and 4px separates value/supporting text.
76
89
  Keep the comparison next to the metric in reading order, before the chart. Icons
77
- are small unboxed heading cues, not a second painted surface. Values use tabular
78
- display numerals and wrap naturally. Compact trend plots use 4rem geometry, not
79
- `flex-1` to fill the card; analytical charts with axes remain separate surfaces.
90
+ are small unboxed heading cues, not a second painted surface. `StatCard` titles
91
+ use 14px (`text-basalt-base`); primary metric numerals in `StatCard`, `StatStrip`
92
+ and dashboard KPIs use 24px (`text-basalt-3xl`), tabular alignment and natural
93
+ wrapping. Compact trend plots use 4rem geometry, not `flex-1` to fill the card;
94
+ analytical charts with axes remain separate surfaces.
80
95
  Show the time window and disclose a nonzero line-chart scale. Bar charts keep
81
96
  their zero baseline. Metric cards have no internal divider or decorative border.
82
97
 
98
+ Action horizontal insets use `--basalt-space-control-x-sm` (10px),
99
+ `--basalt-space-control-x` (12px) and `--basalt-space-control-x-lg` (16px).
100
+ Vertical padding and icon gaps stay compact; Select uses the shared control gap.
101
+ TagBadge uses 6px horizontal padding at `sm` and 8px at `md`.
83
102
  All nonzero padding, margin and gaps use the appropriate category scale or aliases.
84
103
  Zero, auto margins and hairline seams are structural exceptions, not new density
85
104
  levels. Negative margins may cancel an owned boundary, not invent a new gap.
86
- Use `p-basalt-card`, `gap-basalt-layout`, `gap-basalt-space-sm`, etc. Default nav
87
- insets and field gaps remain 8px. Selectable rows use 8px horizontal / 6px vertical
105
+ Container spacing uses 4px multiples (4/8/12/16/24/32px); 2px and 6px
106
+ control-internal spacing remains unchanged. Use `p-basalt-card`, `gap-basalt-layout`,
107
+ `gap-basalt-space-sm`, etc. Default nav insets and field gaps remain 8px. Selectable rows use 8px horizontal / 6px vertical
88
108
  insets and an 8px icon gap. Sidebar destinations alone use 8px vertical padding;
89
109
  menus, editable navigation and chat rows retain their compact role. One component
90
110
  owns each boundary: nested sections must not accidentally double the same inset.
@@ -140,8 +160,10 @@ The shared classes calculate natural size as follows:
140
160
  Descriptions, wrapping and actions can make a banner taller.
141
161
  - Action rows: 22px text line (`--basalt-line-row`) + 6px vertical padding each
142
162
  side = 34px. Body prose/code retain the 20px line box. Compound inputs own one
143
- outer border; inset children do not add another. Segmented tracks keep 12px text
144
- on a 20px line box: 30px items + 2px track inset per side = 34px.
163
+ outer border; inset children do not add another. Segmented tracks keep 14px text
164
+ on a 20px line box: 30px items + 2px track inset per side = 34px. A horizontally
165
+ scrolling SegmentControl viewport adds 4px clearance on all four edges and uses
166
+ matching scroll-padding; this does not enlarge the ToggleGroup track.
145
167
  - Sidebar destination and disclosure rows use the same 22px line box with 8px
146
168
  vertical padding, yielding 38px reference rows without changing shared controls.
147
169
  - Cards, dialogs and layout containers have no mandatory height. Textareas and
@@ -165,12 +187,26 @@ interaction contracts; do not change their containment as a global workaround.
165
187
  - Radius choices: `--basalt-radius-sm` (.25rem), `-md` (.5rem), `-lg` (.75rem)
166
188
  and `-full` (pill/circle); `none` and `inherit` are structural choices. Widget,
167
189
  card and island aliases reference those choices; no per-component radii.
168
- - Typography uses `--basalt-text-*` and `--basalt-leading-*`. Ordinary text is
169
- at least 11px reference, body/action 14px, code content 12px (`text-basalt-sm`),
170
- code panel titles 13px (`text-basalt-code`). Display headings use
171
- the shared larger scale. Do not shrink text to fit a fixed box.
172
- - Surfaces preserve the matte L0/L1/L2/L3 hierarchy. Controls use the
173
- surface-relative control fill. Hover uses `--basalt-hover`,
190
+ - Typography is positional, not component-specific. Use only the four font-size
191
+ tokens below, with `--basalt-leading-*` for line boxes. Do not shrink text to
192
+ fit a fixed box or introduce responsive intermediate sizes.
193
+
194
+ | Tier | Reference | Token / utility | Roles |
195
+ | --- | --- | --- | --- |
196
+ | Auxiliary | 12px / `.75rem` | `--basalt-text-sm` / `text-basalt-sm` | Badges, table column headers, field hints/errors, sidebar uppercase group/partition labels, secondary metadata, chart axes/legends/tooltips, inline code and code block source (`font-mono`) |
197
+ | Body | 14px / `.875rem` | `--basalt-text-base` / `text-basalt-base` | Body prose, all interactive controls and option rows (including ApprovalCard, RecommendationCard, Table cells and ContextCards), card/panel titles (LayerCard.Header, CodeBlock title, ContextCards header, StatCard title, Popover title and Empty title), PageHeader descriptions and modal/sheet descriptions (DialogDescription, AlertDialogDescription and SheetDescription) |
198
+ | Section | 18px / `1.125rem` | `--basalt-text-xl` / `text-basalt-xl` | Section/category h2/h3 headings, SectionRule variant="heading" h2, modal/drawer titles (DialogTitle, AlertDialogTitle and SheetTitle) |
199
+ | Page / metric | 24px / `1.5rem` | `--basalt-text-3xl` / `text-basalt-3xl` | Page main title (PageHeader h1) and primary KPI numerals (StatCard value, StatStrip value and dashboard metrics) |
200
+
201
+ The only independent display-scale exemptions are LandingPage
202
+ (`src/styles/landing.css`), LoginPage (`src/pages/LoginPage.tsx`) and the 404
203
+ display in NotFound (`src/pages/NotFound.tsx`, `text-basalt-10xl` only). Every
204
+ other `src/` font-size class must use one of the four canonical utilities.
205
+ - Surfaces preserve the matte L0/L1/L2/L3 hierarchy. LayerCard adds a subtle
206
+ `ring-1 ring-basalt-border/50` perimeter; `outlined` strengthens it. Headers
207
+ use 14px medium-weight foreground text with tight tracking. Controls contrast
208
+ with their parent: document/surface roots use `--basalt-secondary`, while cards
209
+ and deeper surfaces use `--basalt-bright` for their control fill. Hover uses `--basalt-hover`,
174
210
  `--basalt-primary-hover` or `--basalt-destructive-hover`, not caller opacity.
175
211
  - Semantic states, tags, data series and brand imagery may choose semantic
176
212
  palettes; keep foreground/background pairs and verify both themes. Centralize
@@ -184,12 +220,13 @@ interaction contracts; do not change their containment as a global workaround.
184
220
  lightness in dark mode), never the darker decorative `accent` or `muted` fill.
185
221
  Do not add decorative side bars, selection outlines, shadows or pseudo-element
186
222
  frames. Navigation uses foreground contrast and medium weight alongside fill;
187
- existing semantic checkmarks and tab underlines remain. Both primary and
188
- secondary text retain at least 4.5:1 contrast.
189
- - Hover is transient and follows the configured primary. `--basalt-hover` and
190
- `--basalt-hover-end` are complete CSS colors, mixed against the theme popover
191
- surface at 9.6%/4.8% in light mode and 16%/9.6% in dark mode. Moving menu and command
192
- highlights use a subtle gradient between them, without decorative borders.
223
+ existing semantic checkmarks and tab underlines remain. Underline tabs use
224
+ active foreground text and the bottom indicator only, without a rounded box
225
+ or selected background. Both primary and secondary text retain at least 4.5:1 contrast.
226
+ - Hover is transient and follows the configured primary. `--basalt-hover` is
227
+ a complete CSS color, mixed against `--basalt-surface-elevated` at 18% primary
228
+ in both light and dark modes. Moving menu and command highlights use
229
+ this uniform matte fill, without gradients or decorative borders.
193
230
  SegmentControl and ToggleGroup selected items use `--basalt-primary` and its
194
231
  contrast-corrected foreground; they are accent controls, not neutral navigation.
195
232
  Neutral row selection retains its independent selected tokens.
@@ -203,20 +240,30 @@ interaction contracts; do not change their containment as a global workaround.
203
240
  expose that state instead of switching to a gray secondary variant alone.
204
241
  Checkbox/radio/switch glyphs, primary/destructive actions and semantic diff
205
242
  colors keep their semantic fills. Color swatches retain their actual palette.
206
- - Keyboard focus is separate from selection. Navigation uses an inset focus ring
207
- so overflow and collapsed groups cannot clip it. Disabled controls retain
243
+ - Keyboard focus is separate from selection. Ordinary focus uses a 1px theme
244
+ `--basalt-ring` stroke with a 2px outer offset; flush structural targets use the
245
+ same stroke inset with a 2px negative offset (`basalt-focus-inset`). External
246
+ focus targets need 4px clearance inside their nearest clipping container;
247
+ existing padding of at least 4px counts and is not stacked. Forced-colors mode
248
+ may use a 2px system outline. Focus causes no layout shift, and error indicators
249
+ remain independent. Navigation uses inset focus so overflow and collapsed groups
250
+ cannot clip it. Disabled controls retain
208
251
  subdued opacity and cannot become moving-hover targets; selection never lowers
209
252
  opacity or mutes its primary label.
210
- - SidebarPartition is a major section label (12px, semibold, uppercase, 16px top
211
- spacing). SidebarGroup is a compact disclosure label (11px, semibold, uppercase,
212
- 22px line box, 8px inset, 12px group separation), not a destination or
213
- hover-highlight target. Ordinary sidebar destinations use 14px text and 8px
214
- row insets. Groups preserve keyboard expansion and chevron motion; collapsed
215
- rails omit labels. Icons stay centered on the 68px collapsed rail, and the brand
253
+ - SidebarPartition is a major section label (12px / `text-basalt-sm`, semibold,
254
+ uppercase, 16px top spacing). SidebarGroup is a compact disclosure label
255
+ (12px / `text-basalt-sm`, semibold, uppercase, 22px line box, 8px inset, 12px
256
+ group separation), not a destination or hover-highlight target. Ordinary sidebar
257
+ destinations use 14px (`text-basalt-base`) text and 8px row insets. Groups preserve
258
+ keyboard expansion and chevron motion; collapsed rails omit labels. Icons stay centered on the 68px collapsed rail, and the brand
216
259
  mark keeps the same center when expanded. Do not trade that axis for a global
217
260
  left-edge alignment.
218
261
  - Content wells use the actual surface hierarchy, not selected tokens or muted
219
262
  fills. Skeletons, progress tracks and decorative code marks may remain subdued.
263
+ Skeleton shimmer uses foreground at 10% opacity so it remains visible in both themes.
264
+ - The first eight numbered chart tokens resolve to distinct colors in both themes;
265
+ supplementary series use matte HSL values independently of the control accent.
266
+ Sparkline bars use uniform full opacity, never positional dimming.
220
267
 
221
268
  ## Motion and performance
222
269
 
@@ -259,19 +306,20 @@ catalog states and source viewers. Do not add a second `main`, viewport height,
259
306
  page background, wrapper card or page padding inside the island. A bounded chat
260
307
  workspace can use `h-full min-h-0`; it must not create a nested viewport.
261
308
 
262
- Every catalog route uses the same ShowcasePage header: 30px display titles,
263
- 16px descriptions on a 24px line box, 8px title separation and a 65ch maximum
264
- reading width. Page sections use 24px separation regardless of route category.
265
- The standalone PageHeader API retains its size choices for consumers, but catalog
266
- pages do not override the shared scale. ShowcasePage marks `variant="document"`
267
- for the library index, category guides, component documentation and source viewers.
309
+ Every catalog route uses the same ShowcasePage header: 24px main titles
310
+ (`text-basalt-3xl`), 14px descriptions (`text-basalt-base`) on a 20px line box,
311
+ 8px title separation and a 65ch maximum reading width. Page sections use 24px
312
+ separation regardless of route category. PageHeader size choices must preserve
313
+ the same positional font sizes; catalog pages do not override the shared scale.
314
+ ShowcasePage marks `variant="document"` for the library index, category guides,
315
+ component documentation and source viewers.
268
316
  The shell owns the complete breadcrumb trail from Dashboard through the library,
269
317
  category and component to the current route. Ancestors are links; the current
270
318
  route appears once as nonlinked text with aria-current. Source views include their
271
319
  component parent. Narrow headers keep every segment reachable by scrolling the
272
320
  breadcrumb region without overflowing the page.
273
- Document sections use `SectionRule variant="heading"`: 20px sentence-case
274
- headings without the compact dashed rule. Component previews retain their own
321
+ Document sections use `SectionRule variant="heading"`: 18px (`text-basalt-xl`)
322
+ sentence-case headings without the compact dashed rule. Component previews retain their own
275
323
  scope; document typography must not cascade into copied controls. Headers have
276
324
  natural height, no scenic wrappers, negative insets or fixed empty openings.
277
325
 
@@ -286,8 +334,10 @@ category uses LayerCard.Header above its grid, not individual cards per componen
286
334
  Its light surface is white-toned; dark mode uses the same surface hierarchy rather
287
335
  than a fixed white fill.
288
336
 
289
- Cards with headings use `LayerCard.Header` and `LayerCard.Body`; use one Body for
290
- related content with a shared internal gap, not a padded Body for every line.
337
+ Cards with distinct heading/body regions use `LayerCard.Header` and
338
+ `LayerCard.Body`; use one Body for related content with a shared internal gap,
339
+ not a padded Body for every line. Short overview prose blocks may keep their
340
+ heading and text in one unstructured card inset without Header/Body slots.
291
341
  Structured roots and slots must not receive extra outer padding or margins.
292
342
  Unstructured cards select `padding` rather than overriding it with utilities.
293
343
  Do not paint a fixed surface onto LayerCard or recreate cards with rounded,
@@ -295,6 +345,21 @@ painted divs. Use library controls and tables instead of rebuilding their spacin
295
345
  focus, selection and motion in page CSS. Semantic color overrides and purposeful
296
346
  chart/canvas geometry remain separate from shared control geometry.
297
347
 
348
+ ### Island hierarchy
349
+
350
+ The shell is L0 (`--basalt-background`). `ContentIsland` is the L1 gray canvas
351
+ (`--basalt-card`); its first `LayerCard` islands are L2 (`--basalt-secondary`),
352
+ white-toned in light mode and elevated in both themes.
353
+
354
+ Inside `ShowcasePage` / `ContentIsland`, only structural chrome sits directly on
355
+ L1: `PageHeader`, `SectionRule` headings, and top-level navigation tabs or filter
356
+ bars that are not attached to a single card. All substantive content blocks MUST
357
+ be encapsulated inside L2 `LayerCard` islands: rationales, guidelines, lists,
358
+ forms, interactive demos, tables, and empty or loading states. A region heading
359
+ does not make its content a surface. Revealed filter forms belong in a `LayerCard`,
360
+ not directly on the L1 canvas. Keep page and region headings outside the content
361
+ islands rather than wrapping the whole page in another card.
362
+
298
363
  ### Alignment hierarchy
299
364
 
300
365
  Alignment is local to a meaningful scope, not a requirement that every text node
@@ -309,20 +374,25 @@ start at the page edge. Establish the outer rail before composing the inner one:
309
374
 
310
375
  Related text keeps its rail across sorting, selection and missing optional icons.
311
376
  Numeric headings and values share the end rail; a sort glyph sits inward of that
312
- label rather than pushing it away from the values. Reserve the same glyph slot
377
+ label with a 4px gap rather than pushing it away from the values. Reserve the same glyph slot
313
378
  for Keep, Add and Remove instead of indenting only changed rows. Ordinary button
314
379
  padding is not a table-header inset. Preserve the focus target through a shared
315
380
  header control rather than compensating with negative page margins.
316
381
 
317
- Table cells keep 8px horizontal / 6px vertical insets and 36px reference rows.
382
+ Table cells keep 12px horizontal / 8px vertical insets and 40px reference rows.
383
+ Headers use medium weight and 0.04em tracking. Body rows have a subtle bottom
384
+ hairline except the last row; the native separate-border layout paints that
385
+ hairline on cells as well. Striping is off by default; `Table striped` opts into
386
+ alternating current-surface row fills, independently of hover and selection.
318
387
  An edge attached to a panel uses `--basalt-space-table-edge` (the panel's 16px
319
- horizontal inset), independently of the space between columns. The owner of the
320
- column structure marks the actual edge cells; DOM first/last-child selectors
321
- cannot infer that boundary across rowSpan. DataTable owns this through
322
- `edge="attached"`; native Table compositions use `ps-basalt-table-edge` and
323
- `pe-basalt-table-edge` only on the corresponding edge cells. A full-width cell
324
- uses both. Row fills and header rules remain full width, and nested tables keep
325
- their own insets. An already inset table retains the default cell spacing.
388
+ horizontal inset), independently of the space between columns. DataTable owns
389
+ this through `edge="attached"`; native Table compositions can use
390
+ `ps-basalt-table-edge` and `pe-basalt-table-edge` on the corresponding edge cells.
391
+ A full-width cell uses both. Simple tables without rowSpan or nested tables may
392
+ instead opt into automatic first/last-cell alignment through a wrapper marked
393
+ `data-basalt-table-container` and `data-edge="attached"`. The column owner must
394
+ mark actual edge cells explicitly for rowSpan or nested tables. Row fills and
395
+ header rules remain full width. An already inset table retains the default cell spacing.
326
396
  TableCaption owns its 16px horizontal and 12px vertical panel insets, without an
327
397
  outside margin. Its text must not touch an enclosing table frame's bottom edge.
328
398
 
@@ -379,7 +449,10 @@ to recreate ordinary actions, selects or inputs.
379
449
  `bun run design:check` scans library components/helpers and catalog examples,
380
450
  including CSS, for raw spacing/type/radius/motion values, fixed action overrides
381
451
  and inline design repairs. The landing route's own CSS is exempt from the spacing,
382
- radius and type rules and still keeps motion tokens. It derives dashboard page requirements from App routes
452
+ radius and type rules and still keeps motion tokens. Noncanonical Basalt font-size
453
+ classes are rejected, including responsive and important variants; only NotFound's
454
+ `text-basalt-10xl` and LoginPage's independent scale are exempt from the source
455
+ font-size check. It derives dashboard page requirements from App routes
383
456
  and checks template ownership, duplicate slot spacing, recreated card surfaces,
384
457
  ordinary native controls in pages and page-level control padding overrides.
385
458
  It rejects compact control padding on content panels, raw application disclosures,
package/ai/INTEGRATION.md CHANGED
@@ -45,7 +45,7 @@ AppShell ← dashboard and catalog routes
45
45
  ├── Sidebar ← product title + version + nav
46
46
  └── AppMain ← id="main-content"
47
47
  ├── AppHeader ← crumbs + current page | top-right
48
- └── island wrap ← px-basalt-space-lg pb-basalt-space-lg md:px-basalt-space-lg md:pb-basalt-space-lg
48
+ └── island wrap ← px-basalt-space-lg pb-basalt-space-lg md:px-basalt-layout-sm md:pb-basalt-layout-sm
49
49
  └── ContentIsland
50
50
  ├── PageHeader ← title + subtitle | create / short filters
51
51
  │ ← optional own-row complex filters
@@ -390,7 +390,7 @@ Bounded shells use border-box `height/max-height: 100dvh`. Document shells use `
390
390
  <AppSkipLink>Skip to content</AppSkipLink>
391
391
  <AppMain>
392
392
  <AppHeader title="Field notes" actions={actions} />
393
- <div className="flex min-h-0 min-w-0 flex-1 flex-col md:px-basalt-space-lg md:pb-basalt-space-lg">
393
+ <div className="flex min-h-0 min-w-0 flex-1 flex-col md:px-basalt-layout-sm md:pb-basalt-layout-sm">
394
394
  <ContentIsland mobileSurface="edge-to-edge">
395
395
  <div className="mx-auto max-w-prose px-basalt-space-lg py-basalt-space-lg md:p-0">{content}</div>
396
396
  </ContentIsland>
@@ -428,11 +428,11 @@ Do not wrap those regions in another full-viewport column. Do not set `h-screen`
428
428
 
429
429
  ### Shared navigation geometry
430
430
 
431
- `SidebarNav` and `NavigationMenuList` own one `--basalt-space-nav-inset` (8px) boundary and `--basalt-space-nav-gap` (2px) between rows. Sidebar groups do not add another horizontal inset. Sidebar destinations and search use 8px horizontal/vertical padding, an 8px icon gap and a 22px line box for a 38px reference row. This is local to Sidebar: editable/folder rows, horizontal navigation links and menus retain 6px vertical padding and 34px rows. Header, search wrapper and footer share the same navigation inset. Do not add caller-owned `px-basalt-space-lg`, `pt-basalt-space-sm` or item-stack padding to compensate for missing package geometry.
431
+ `SidebarNav` and `NavigationMenuList` own one `--basalt-space-nav-inset` (8px) boundary and `--basalt-space-nav-gap` (4px) between rows. Sidebar groups do not add another horizontal inset. Sidebar destinations and search use 8px horizontal/vertical padding, an 8px icon gap and a 22px line box for a 38px reference row. This is local to Sidebar: editable/folder rows, horizontal navigation links and menus retain 6px vertical padding and 34px rows. Header and search wrapper use the navigation inset. SidebarFooter instead owns 16px horizontal and bottom padding plus 8px top padding; keep the same 68px rail centering and expanded layout without duplicating that inset. Do not add caller-owned padding to compensate for missing package geometry.
432
432
 
433
433
  Use `SidebarNav` for vertical lists, including standalone lists of `EditableNavItem` / `FolderNavItem`; `NavigationMenuList` owns horizontal/vertical Radix navigation. Both compose the existing list-local hover adapter. One background moves with transform between enabled rows. Sidebar hover appears at the actual pointer row and disappears there on leave or over a gap/disclosure label; it never launches from or returns to the active route or stale pointer focus. Only continuous row-to-row hover animates. Keyboard focus remains independent. NavigationMenuList retains its focus/selection-following behavior. Nested lists own separate highlights. Hidden, disabled and actively renamed rows are excluded. Geometry reads are batched per animation frame, same-row pointer motion causes no updates, and reduced motion disables transitions. Navigation state, routes and rename operations remain application-owned; the adapter is View-only.
434
434
 
435
- `SidebarHeader` and `SidebarFooter` already pad horizontally. Brand, version pill, and collapse control go directly in the header. The version string is read from the app `package.json` at build time. Never hardcode it. `SidebarPartition` aligns with row text. `SidebarGroup` adds only section spacing, never a second item gutter.
435
+ `SidebarHeader` pads horizontally by the navigation inset; `SidebarFooter` owns its larger footer boundary. Brand, version pill, and collapse control go directly in the header. The version string is read from the app `package.json` at build time. Never hardcode it. `SidebarPartition` aligns with row text. `SidebarGroup` adds only section spacing, never a second item gutter.
436
436
 
437
437
  ```tsx excerpt:sidebar-expanded-structure
438
438
  <Sidebar collapsed={collapsed}>
@@ -662,7 +662,7 @@ export function AppFrame() {
662
662
  title={title}
663
663
  actions={<ThemeToggle aria-label={`Toggle theme (now ${theme})`} />}
664
664
  />
665
- <div className="flex min-h-0 flex-1 flex-col px-basalt-space-lg pb-basalt-space-lg md:px-basalt-space-lg md:pb-basalt-space-lg">
665
+ <div className="flex min-h-0 flex-1 flex-col px-basalt-space-lg pb-basalt-space-lg md:px-basalt-layout-sm md:pb-basalt-layout-sm">
666
666
  <ContentIsland>
667
667
  <Outlet />
668
668
  </ContentIsland>
@@ -675,7 +675,7 @@ export function AppFrame() {
675
675
 
676
676
  <a id="appheader-and-breadcrumbs"></a>
677
677
 
678
- `AppHeader` defaults to `h-14`, matching `SidebarHeader`; `density="compact"` uses a 52px row with 44px minimum targets. `sticky` keeps it visible within the current scroll owner; see §6 for safe-area positioning.
678
+ `AppHeader` defaults to `h-14`, matching `SidebarHeader`; `density="compact"` uses a 52px row with 44px minimum targets. Its horizontal inset is 8px on mobile and 12px from 768px, aligning with the outer ContentIsland wrapper rather than the island's inner card edge. `sticky` keeps it visible within the current scroll owner; see §6 for safe-area positioning.
679
679
 
680
680
  ### AppHeader API & Contracts
681
681
 
@@ -759,7 +759,7 @@ The catalog's app-local `RouteScrollReset` wraps each leaf page inside its Suspe
759
759
 
760
760
  The `/forms`, `/settings`, `/data`, and `/chat` examples demonstrate local state and simulated requests, including failure/retry and cancellation. Their viewmodels own data and timers; Views own native FormData, focus, and responsive layout. Replace the local service adapter when integrating a backend. Theme selection uses the shared provider; local profile, uploads, and chat changes do not update a real account.
761
761
 
762
- `PageHeader` is flush on `ContentIsland`. The island owns 16px mobile insets, 24px tablet insets, and 32px horizontal / 24px vertical desktop insets. Do not wrap the header in another card or add page padding. `PageHeader.size` is `"md" | "lg" | "xl"` (default `"md"`); larger headings use 16/24px descriptions, all title/description pairs use an 8px gap. `SectionRule variant="heading"` gives reading sections a 20px sentence-case title without the compact dashed rule. Controls and card slots keep their compact geometry.
762
+ `PageHeader` is flush on `ContentIsland`. The island owns 16px mobile insets, 24px tablet insets, and 32px horizontal / 24px vertical desktop insets. `AppHeader` remains aligned to the outer island boundary (8px mobile, 12px from 768px), not this inner content edge. Do not wrap the header in another card or add page padding. `PageHeader.size` is `"md" | "lg" | "xl"` (default `"md"`); larger headings use 16/24px descriptions, all title/description pairs use an 8px gap. `SectionRule variant="heading"` gives reading sections a 20px sentence-case title without the compact dashed rule. Controls and card slots keep their compact geometry.
763
763
 
764
764
  ### Create and filters
765
765
 
@@ -930,7 +930,7 @@ Input, Select, secondary / outline Button use `bg-basalt-control` (`--basalt-con
930
930
 
931
931
  `Table` sets `data-basalt-table`. Zebra, hover, and selected paint on `td`, from `--basalt-zebra-fill`, `--basalt-hover` and `--basalt-selected`. Selection adds no decorative side bar or frame; semantic add/remove diff tints are preserved.
932
932
 
933
- Neutral selected states use the brighter `--basalt-selected` surface, readable `--basalt-selected-foreground` and navigation font weight, without decorative side bars or selected borders. Never use `accent` or `muted` to signal selection. Existing semantic checkmarks and tab underlines remain; do not add a decorative frame at the white ceiling. A moving hover layer must not erase the selected row. Sidebar, navigation menus, editable navigation and ChatInbox share this behavior and inset keyboard focus; neutral Button variants also honor `aria-pressed` and `aria-current`. Segments and tabs retain their existing measured indicator motion. Primary/destructive actions, check/radio/switch glyphs and semantic diffs keep their semantic colors. See [DESIGN.md](DESIGN.md#selection-and-navigation-hierarchy).
933
+ Neutral selected states use the brighter `--basalt-selected` surface, readable `--basalt-selected-foreground` and navigation font weight, without decorative side bars or selected borders. Never use `accent` or `muted` to signal selection. Existing semantic checkmarks and tab underlines remain; do not add a decorative frame at the white ceiling. A moving hover layer must not erase the selected row. Sidebar, navigation menus, editable navigation and ChatInbox share this behavior and inset keyboard focus; neutral Button variants also honor `aria-pressed` and `aria-current`. Focus is separate from selection: ordinary targets use the 1px theme `--basalt-ring` stroke with a 2px outer offset; flush targets use the same stroke inset with a 2px negative offset. External focus targets need at least 4px clearance inside their nearest clipping container; existing 4px padding counts and is not stacked. Focus does not shift layout, error indicators stay independent, and forced-colors mode may use a 2px system outline. Segments and tabs retain their existing measured indicator motion. Primary/destructive actions, check/radio/switch glyphs and semantic diffs keep their semantic colors. See [DESIGN.md](DESIGN.md#selection-and-navigation-hierarchy).
934
934
 
935
935
  `DescriptionList` is layout and type only. It is not a surface.
936
936
 
@@ -1925,7 +1925,7 @@ Thinking and ToolChips share row padding; disclosures rotate only their own chev
1925
1925
 
1926
1926
  The standalone build generates every Tailwind utility once in canonical order. Do not append handwritten copies of `.p-0`, `.bg-transparent` or other native utilities after generated axis/state rules: a later shorthand can silently erase semantic padding. Browser acceptance measures actual computed insets in both CSS entrypoints, under foreign host spacing and semantic overrides.
1927
1927
 
1928
- Public Radio, Checkbox, Switch and SegmentControl group roots establish the scoped `.basalt-ui` reset. Fieldsets and legends have no browser-default padding/margin inside that boundary; standalone and Tailwind consumers must have identical group insets. Host form groups outside Basalt remain untouched.
1928
+ Public Radio, Checkbox, Switch and SegmentControl group roots establish the scoped `.basalt-ui` reset. Fieldsets and legends have no browser-default padding/margin inside that boundary; standalone and Tailwind consumers must have identical group insets. SegmentControl's horizontal scrolling viewport has 4px focus clearance on every edge and matching scroll-padding so keyboard navigation can reveal end items; the ToggleGroup track retains its 2px compact inset and 34px item geometry. Host form groups outside Basalt remain untouched.
1929
1929
 
1930
1930
  Native form legends do not participate in a fieldset's flex gap. Radio, Checkbox and Switch legends therefore own `margin-bottom: var(--basalt-space-field-gap)` (8px) independently of the group's row layout. Do not replace the semantic legend or add wrapper divs to simulate this spacing. Override the existing field-gap token when a different group density is required.
1931
1931
 
package/ai/USAGE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Basalt AI & Machine Usage Guide
2
2
 
3
- This document describes how AI code assistants, automated agents, and developers can consume `@nocoo/basalt` (v3.1.0).
3
+ This document describes how AI code assistants, automated agents, and developers can consume `@nocoo/basalt` (v3.2.1).
4
4
 
5
5
  ---
6
6