@nocoo/basalt 3.0.0 → 3.2.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 (83) hide show
  1. package/ai/DESIGN.md +157 -39
  2. package/ai/INTEGRATION.md +83 -8
  3. package/ai/USAGE.md +2 -2
  4. package/ai/registry.json +302 -79
  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/heatmap-matrix.js +2 -2
  9. package/dist/charts/heatmap-matrix.js.map +1 -1
  10. package/dist/charts/sparkline.js +1 -1
  11. package/dist/charts/sparkline.js.map +1 -1
  12. package/dist/charts/stat-card.js +1 -1
  13. package/dist/charts/stat-card.js.map +1 -1
  14. package/dist/components/alert-dialog.js +4 -4
  15. package/dist/components/alert-dialog.js.map +1 -1
  16. package/dist/components/approval-card.js +5 -6
  17. package/dist/components/approval-card.js.map +1 -1
  18. package/dist/components/avatar.js +1 -1
  19. package/dist/components/avatar.js.map +1 -1
  20. package/dist/components/chat-bubble.js +1 -1
  21. package/dist/components/chat-bubble.js.map +1 -1
  22. package/dist/components/chat-header.js +1 -1
  23. package/dist/components/chat-header.js.map +1 -1
  24. package/dist/components/chat-inbox.js +1 -1
  25. package/dist/components/chat-inbox.js.map +1 -1
  26. package/dist/components/chat-markdown.js +1 -1
  27. package/dist/components/chat-markdown.js.map +1 -1
  28. package/dist/components/chat-message.js +1 -1
  29. package/dist/components/chat-message.js.map +1 -1
  30. package/dist/components/code.js +4 -3
  31. package/dist/components/code.js.map +1 -1
  32. package/dist/components/context-cards.js +5 -5
  33. package/dist/components/context-cards.js.map +1 -1
  34. package/dist/components/data-table.d.ts +7 -1
  35. package/dist/components/data-table.js +127 -132
  36. package/dist/components/data-table.js.map +1 -1
  37. package/dist/components/dialog.js +3 -3
  38. package/dist/components/dialog.js.map +1 -1
  39. package/dist/components/diff-table.js +20 -15
  40. package/dist/components/diff-table.js.map +1 -1
  41. package/dist/components/input-area.js +1 -1
  42. package/dist/components/input-area.js.map +1 -1
  43. package/dist/components/layer-card.d.ts +6 -4
  44. package/dist/components/layer-card.js +60 -71
  45. package/dist/components/layer-card.js.map +1 -1
  46. package/dist/components/loader.js +1 -1
  47. package/dist/components/loader.js.map +1 -1
  48. package/dist/components/page-header.d.ts +2 -2
  49. package/dist/components/page-header.js +7 -7
  50. package/dist/components/page-header.js.map +1 -1
  51. package/dist/components/recommendation-card.js +2 -2
  52. package/dist/components/recommendation-card.js.map +1 -1
  53. package/dist/components/section-rule.js +1 -1
  54. package/dist/components/section-rule.js.map +1 -1
  55. package/dist/components/select.js +1 -1
  56. package/dist/components/select.js.map +1 -1
  57. package/dist/components/sheet.js +3 -3
  58. package/dist/components/sheet.js.map +1 -1
  59. package/dist/components/sidebar.js +19 -16
  60. package/dist/components/sidebar.js.map +1 -1
  61. package/dist/components/skeleton-line.js +1 -1
  62. package/dist/components/skeleton-line.js.map +1 -1
  63. package/dist/components/stat-strip.js +1 -1
  64. package/dist/components/stat-strip.js.map +1 -1
  65. package/dist/components/table.d.ts +41 -2
  66. package/dist/components/table.js +44 -15
  67. package/dist/components/table.js.map +1 -1
  68. package/dist/components/tabs.js +1 -1
  69. package/dist/components/tabs.js.map +1 -1
  70. package/dist/components/tag-badge.js +1 -1
  71. package/dist/components/tag-badge.js.map +1 -1
  72. package/dist/components/text.js +2 -2
  73. package/dist/components/text.js.map +1 -1
  74. package/dist/components/thinking.js +1 -1
  75. package/dist/components/thinking.js.map +1 -1
  76. package/dist/components/tool-chips.js +2 -2
  77. package/dist/components/tool-chips.js.map +1 -1
  78. package/dist/styles/standalone.css +145 -89
  79. package/dist/styles/tailwind.css +2 -0
  80. package/dist/styles/tokens.css +84 -39
  81. package/dist/utils/use-hover-highlight.js +38 -36
  82. package/dist/utils/use-hover-highlight.js.map +1 -1
  83. package/package.json +28 -28
package/ai/DESIGN.md CHANGED
@@ -46,12 +46,22 @@ 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
67
  keeps 8px outside the island on mobile and 12px from 768px. Ordinary application
@@ -64,24 +74,40 @@ Dialog and AlertDialog panels use 16px outer insets on mobile and 24px from
64
74
  640px. Their buttons and fields retain compact control spacing. Sheets and
65
75
  small floating menus have separate inset contracts.
66
76
 
77
+ Form rhythm distinguishes a field from a group: label/control/hint spacing stays
78
+ 8px, related fields use 16px layout gaps, and separate sections use 24px. A form's
79
+ stack owns those gaps; individual Field components do not add outer margins.
80
+ DialogFooter, AlertDialogFooter and SheetFooter own 16px separation from content
81
+ and keep 8px between actions. Do not add a matching margin at every caller or
82
+ enlarge controls to open a form.
83
+
67
84
  Metric cards use the shared `StatCard`, not a hand-built Header/Body split. A single
68
85
  16px inset owns the title, value, comparison and supporting chart; 12px separates
69
86
  the heading, metric and content groups, and 4px separates value/supporting text.
70
87
  Keep the comparison next to the metric in reading order, before the chart. Icons
71
- are small unboxed heading cues, not a second painted surface. Values use tabular
72
- display numerals and wrap naturally. Compact trend plots use 4rem geometry, not
73
- `flex-1` to fill the card; analytical charts with axes remain separate surfaces.
88
+ are small unboxed heading cues, not a second painted surface. `StatCard` titles
89
+ use 14px (`text-basalt-base`); primary metric numerals in `StatCard`, `StatStrip`
90
+ and dashboard KPIs use 24px (`text-basalt-3xl`), tabular alignment and natural
91
+ wrapping. Compact trend plots use 4rem geometry, not `flex-1` to fill the card;
92
+ analytical charts with axes remain separate surfaces.
74
93
  Show the time window and disclose a nonzero line-chart scale. Bar charts keep
75
94
  their zero baseline. Metric cards have no internal divider or decorative border.
76
95
 
96
+ Action horizontal insets use `--basalt-space-control-x-sm` (10px),
97
+ `--basalt-space-control-x` (12px) and `--basalt-space-control-x-lg` (16px).
98
+ Vertical padding and icon gaps stay compact; Select uses the shared control gap.
99
+ TagBadge uses 6px horizontal padding at `sm` and 8px at `md`.
77
100
  All nonzero padding, margin and gaps use the appropriate category scale or aliases.
78
101
  Zero, auto margins and hairline seams are structural exceptions, not new density
79
102
  levels. Negative margins may cancel an owned boundary, not invent a new gap.
80
103
  Use `p-basalt-card`, `gap-basalt-layout`, `gap-basalt-space-sm`, etc. Default nav
81
104
  insets and field gaps remain 8px. Selectable rows use 8px horizontal / 6px vertical
82
- insets and an 8px icon gap. One component owns each boundary: nested sections must
83
- not accidentally double outer padding. Indented detail content may derive its
84
- alignment from the row inset + icon width + gap; this is not another density tier.
105
+ insets and an 8px icon gap. Sidebar destinations alone use 8px vertical padding;
106
+ menus, editable navigation and chat rows retain their compact role. One component
107
+ owns each boundary: nested sections must not accidentally double the same inset.
108
+ Distinct surface, column and semantic-slot boundaries may compose their spacing.
109
+ Indented detail content may derive its alignment from the row inset, icon width
110
+ and gap; this is not another density tier.
85
111
 
86
112
  Existing numbered `--basalt-space-*` tokens are a geometry scale for icons,
87
113
  media and shell widths. They are not an alternative margin/padding scale.
@@ -131,8 +157,10 @@ The shared classes calculate natural size as follows:
131
157
  Descriptions, wrapping and actions can make a banner taller.
132
158
  - Action rows: 22px text line (`--basalt-line-row`) + 6px vertical padding each
133
159
  side = 34px. Body prose/code retain the 20px line box. Compound inputs own one
134
- outer border; inset children do not add another. Segmented tracks keep 12px text
160
+ outer border; inset children do not add another. Segmented tracks keep 14px text
135
161
  on a 20px line box: 30px items + 2px track inset per side = 34px.
162
+ - Sidebar destination and disclosure rows use the same 22px line box with 8px
163
+ vertical padding, yielding 38px reference rows without changing shared controls.
136
164
  - Cards, dialogs and layout containers have no mandatory height. Textareas and
137
165
  chat inputs grow with content (chat: up to five lines, then scroll).
138
166
 
@@ -154,12 +182,26 @@ interaction contracts; do not change their containment as a global workaround.
154
182
  - Radius choices: `--basalt-radius-sm` (.25rem), `-md` (.5rem), `-lg` (.75rem)
155
183
  and `-full` (pill/circle); `none` and `inherit` are structural choices. Widget,
156
184
  card and island aliases reference those choices; no per-component radii.
157
- - Typography uses `--basalt-text-*` and `--basalt-leading-*`. Ordinary text is
158
- at least 11px reference, body/action 14px, code content 12px (`text-basalt-sm`),
159
- code panel titles 13px (`text-basalt-code`). Display headings use
160
- the shared larger scale. Do not shrink text to fit a fixed box.
161
- - Surfaces preserve the matte L0/L1/L2/L3 hierarchy. Controls use the
162
- surface-relative control fill. Hover uses `--basalt-hover`,
185
+ - Typography is positional, not component-specific. Use only the four font-size
186
+ tokens below, with `--basalt-leading-*` for line boxes. Do not shrink text to
187
+ fit a fixed box or introduce responsive intermediate sizes.
188
+
189
+ | Tier | Reference | Token / utility | Roles |
190
+ | --- | --- | --- | --- |
191
+ | 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`) |
192
+ | 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) |
193
+ | 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) |
194
+ | 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) |
195
+
196
+ The only independent display-scale exemptions are LandingPage
197
+ (`src/styles/landing.css`), LoginPage (`src/pages/LoginPage.tsx`) and the 404
198
+ display in NotFound (`src/pages/NotFound.tsx`, `text-basalt-10xl` only). Every
199
+ other `src/` font-size class must use one of the four canonical utilities.
200
+ - Surfaces preserve the matte L0/L1/L2/L3 hierarchy. LayerCard adds a subtle
201
+ `ring-1 ring-basalt-border/50` perimeter; `outlined` strengthens it. Headers
202
+ use 14px medium-weight foreground text with tight tracking. Controls contrast
203
+ with their parent: document/surface roots use `--basalt-secondary`, while cards
204
+ and deeper surfaces use `--basalt-bright` for their control fill. Hover uses `--basalt-hover`,
163
205
  `--basalt-primary-hover` or `--basalt-destructive-hover`, not caller opacity.
164
206
  - Semantic states, tags, data series and brand imagery may choose semantic
165
207
  palettes; keep foreground/background pairs and verify both themes. Centralize
@@ -173,12 +215,13 @@ interaction contracts; do not change their containment as a global workaround.
173
215
  lightness in dark mode), never the darker decorative `accent` or `muted` fill.
174
216
  Do not add decorative side bars, selection outlines, shadows or pseudo-element
175
217
  frames. Navigation uses foreground contrast and medium weight alongside fill;
176
- existing semantic checkmarks and tab underlines remain. Both primary and
177
- secondary text retain at least 4.5:1 contrast.
178
- - Hover is transient and follows the configured primary. `--basalt-hover` and
179
- `--basalt-hover-end` are complete CSS colors, mixed against the theme popover
180
- surface at 9.6%/4.8% in light mode and 16%/9.6% in dark mode. Moving menu and command
181
- highlights use a subtle gradient between them, without decorative borders.
218
+ existing semantic checkmarks and tab underlines remain. Underline tabs use
219
+ active foreground text and the bottom indicator only, without a rounded box
220
+ or selected background. Both primary and secondary text retain at least 4.5:1 contrast.
221
+ - Hover is transient and follows the configured primary. `--basalt-hover` is
222
+ a complete CSS color, mixed against `--basalt-surface-elevated` at 18% primary
223
+ in both light and dark modes. Moving menu and command highlights use
224
+ this uniform matte fill, without gradients or decorative borders.
182
225
  SegmentControl and ToggleGroup selected items use `--basalt-primary` and its
183
226
  contrast-corrected foreground; they are accent controls, not neutral navigation.
184
227
  Neutral row selection retains its independent selected tokens.
@@ -196,13 +239,20 @@ interaction contracts; do not change their containment as a global workaround.
196
239
  so overflow and collapsed groups cannot clip it. Disabled controls retain
197
240
  subdued opacity and cannot become moving-hover targets; selection never lowers
198
241
  opacity or mutes its primary label.
199
- - SidebarPartition is a major section label (12px, semibold, uppercase, 16px top
200
- spacing). SidebarGroup is a compact disclosure label (11px, semibold, uppercase,
201
- 8px inset, 12px group separation), not a destination or hover-highlight target.
202
- Ordinary destinations use 14px text and the shared 8px/6px row insets. Groups
203
- preserve keyboard expansion and chevron motion; collapsed rails omit labels.
242
+ - SidebarPartition is a major section label (12px / `text-basalt-sm`, semibold,
243
+ uppercase, 16px top spacing). SidebarGroup is a compact disclosure label
244
+ (12px / `text-basalt-sm`, semibold, uppercase, 22px line box, 8px inset, 12px
245
+ group separation), not a destination or hover-highlight target. Ordinary sidebar
246
+ destinations use 14px (`text-basalt-base`) text and 8px row insets. Groups preserve
247
+ keyboard expansion and chevron motion; collapsed rails omit labels. Icons stay centered on the 68px collapsed rail, and the brand
248
+ mark keeps the same center when expanded. Do not trade that axis for a global
249
+ left-edge alignment.
204
250
  - Content wells use the actual surface hierarchy, not selected tokens or muted
205
251
  fills. Skeletons, progress tracks and decorative code marks may remain subdued.
252
+ Skeleton shimmer uses foreground at 10% opacity so it remains visible in both themes.
253
+ - The first eight numbered chart tokens resolve to distinct colors in both themes;
254
+ supplementary series use matte HSL values independently of the control accent.
255
+ Sparkline bars use uniform full opacity, never positional dimming.
206
256
 
207
257
  ## Motion and performance
208
258
 
@@ -219,6 +269,10 @@ use linear motion; decorative motion pauses off-screen/in background tabs.
219
269
  do not remeasure the same row for pointer movement, and write geometry without
220
270
  React rerenders. Nested lists are isolated. Disabled and hidden rows cannot
221
271
  become targets; keyboard selection/focus works independently of pointer hover.
272
+ Sidebar hover starts at the pointer's row and disappears there. It never uses
273
+ the current route or stale pointer focus as an entry/exit destination. Only
274
+ continuous movement between hovered rows animates; gaps and disclosure labels
275
+ end that sequence. Keyboard focus retains its independent focus indicator.
222
276
  - Selection indicators snap to the first valid measured selection before paint.
223
277
  Never animate from `(0, 0)`, including controlled initial values, non-first
224
278
  segments, remounts or initially hidden panels. Animate only between established
@@ -241,33 +295,38 @@ catalog states and source viewers. Do not add a second `main`, viewport height,
241
295
  page background, wrapper card or page padding inside the island. A bounded chat
242
296
  workspace can use `h-full min-h-0`; it must not create a nested viewport.
243
297
 
244
- Every catalog route uses the same ShowcasePage header: 30px display titles,
245
- 16px descriptions on a 24px line box, 8px title separation and a 65ch maximum
246
- reading width. Page sections use 24px separation regardless of route category.
247
- The standalone PageHeader API retains its size choices for consumers, but catalog
248
- pages do not override the shared scale. ShowcasePage marks `variant="document"`
249
- for the library index, category guides, component documentation and source viewers.
298
+ Every catalog route uses the same ShowcasePage header: 24px main titles
299
+ (`text-basalt-3xl`), 14px descriptions (`text-basalt-base`) on a 20px line box,
300
+ 8px title separation and a 65ch maximum reading width. Page sections use 24px
301
+ separation regardless of route category. PageHeader size choices must preserve
302
+ the same positional font sizes; catalog pages do not override the shared scale.
303
+ ShowcasePage marks `variant="document"` for the library index, category guides,
304
+ component documentation and source viewers.
250
305
  The shell owns the complete breadcrumb trail from Dashboard through the library,
251
306
  category and component to the current route. Ancestors are links; the current
252
307
  route appears once as nonlinked text with aria-current. Source views include their
253
308
  component parent. Narrow headers keep every segment reachable by scrolling the
254
309
  breadcrumb region without overflowing the page.
255
- Document sections use `SectionRule variant="heading"`: 20px sentence-case
256
- headings without the compact dashed rule. Component previews retain their own
310
+ Document sections use `SectionRule variant="heading"`: 18px (`text-basalt-xl`)
311
+ sentence-case headings without the compact dashed rule. Component previews retain their own
257
312
  scope; document typography must not cascade into copied controls. Headers have
258
313
  natural height, no scenic wrappers, negative insets or fixed empty openings.
259
314
 
260
315
  Scrollable gallery previews reserve 8px of internal space on every edge for
261
316
  control rings, focus outlines and shadows. Padding outside the scroll viewport
262
317
  does not protect that paint. Center previews safely so oversized content keeps
263
- its leading edge reachable.
318
+ its leading edge reachable. Multi-column Table, DataTable and DiffTable examples
319
+ span the category grid; do not squeeze a working data surface into a half-width
320
+ thumbnail. Compact examples such as TablePager retain their smaller grid cells.
264
321
  The component library uses one LayerCard per category, separated by 24px. Each
265
322
  category uses LayerCard.Header above its grid, not individual cards per component.
266
323
  Its light surface is white-toned; dark mode uses the same surface hierarchy rather
267
324
  than a fixed white fill.
268
325
 
269
- Cards with headings use `LayerCard.Header` and `LayerCard.Body`; use one Body for
270
- related content with a shared internal gap, not a padded Body for every line.
326
+ Cards with distinct heading/body regions use `LayerCard.Header` and
327
+ `LayerCard.Body`; use one Body for related content with a shared internal gap,
328
+ not a padded Body for every line. Short overview prose blocks may keep their
329
+ heading and text in one unstructured card inset without Header/Body slots.
271
330
  Structured roots and slots must not receive extra outer padding or margins.
272
331
  Unstructured cards select `padding` rather than overriding it with utilities.
273
332
  Do not paint a fixed surface onto LayerCard or recreate cards with rounded,
@@ -275,6 +334,62 @@ painted divs. Use library controls and tables instead of rebuilding their spacin
275
334
  focus, selection and motion in page CSS. Semantic color overrides and purposeful
276
335
  chart/canvas geometry remain separate from shared control geometry.
277
336
 
337
+ ### Island hierarchy
338
+
339
+ The shell is L0 (`--basalt-background`). `ContentIsland` is the L1 gray canvas
340
+ (`--basalt-card`); its first `LayerCard` islands are L2 (`--basalt-secondary`),
341
+ white-toned in light mode and elevated in both themes.
342
+
343
+ Inside `ShowcasePage` / `ContentIsland`, only structural chrome sits directly on
344
+ L1: `PageHeader`, `SectionRule` headings, and top-level navigation tabs or filter
345
+ bars that are not attached to a single card. All substantive content blocks MUST
346
+ be encapsulated inside L2 `LayerCard` islands: rationales, guidelines, lists,
347
+ forms, interactive demos, tables, and empty or loading states. A region heading
348
+ does not make its content a surface. Revealed filter forms belong in a `LayerCard`,
349
+ not directly on the L1 canvas. Keep page and region headings outside the content
350
+ islands rather than wrapping the whole page in another card.
351
+
352
+ ### Alignment hierarchy
353
+
354
+ Alignment is local to a meaningful scope, not a requirement that every text node
355
+ start at the page edge. Establish the outer rail before composing the inner one:
356
+
357
+ | Scope | Alignment rule |
358
+ | --- | --- |
359
+ | Page | PageHeader and region edges share the ContentIsland rail |
360
+ | Surface | Header, body and footer share their panel inset |
361
+ | Field or column | Labels, controls and values follow that group's start or end rail |
362
+ | Semantic slot | Checkboxes, row numbers and status glyphs reserve their own consistent space |
363
+
364
+ Related text keeps its rail across sorting, selection and missing optional icons.
365
+ Numeric headings and values share the end rail; a sort glyph sits inward of that
366
+ label with a 4px gap rather than pushing it away from the values. Reserve the same glyph slot
367
+ for Keep, Add and Remove instead of indenting only changed rows. Ordinary button
368
+ padding is not a table-header inset. Preserve the focus target through a shared
369
+ header control rather than compensating with negative page margins.
370
+
371
+ Table cells keep 12px horizontal / 8px vertical insets and 40px reference rows.
372
+ Headers use medium weight and 0.04em tracking. Body rows have a subtle bottom
373
+ hairline except the last row; the native separate-border layout paints that
374
+ hairline on cells as well. Striping is off by default; `Table striped` opts into
375
+ alternating current-surface row fills, independently of hover and selection.
376
+ An edge attached to a panel uses `--basalt-space-table-edge` (the panel's 16px
377
+ horizontal inset), independently of the space between columns. DataTable owns
378
+ this through `edge="attached"`; native Table compositions can use
379
+ `ps-basalt-table-edge` and `pe-basalt-table-edge` on the corresponding edge cells.
380
+ A full-width cell uses both. Simple tables without rowSpan or nested tables may
381
+ instead opt into automatic first/last-cell alignment through a wrapper marked
382
+ `data-basalt-table-container` and `data-edge="attached"`. The column owner must
383
+ mark actual edge cells explicitly for rowSpan or nested tables. Row fills and
384
+ header rules remain full width. An already inset table retains the default cell spacing.
385
+ TableCaption owns its 16px horizontal and 12px vertical panel insets, without an
386
+ outside margin. Its text must not touch an enclosing table frame's bottom edge.
387
+
388
+ Purposeful nesting, such as a preview's focus clearance around a self-contained
389
+ surface, remains valid. Increase the separation between groups before increasing
390
+ every inner gap; breathing room comes from distinguishable groups and adequate
391
+ content width, not uniformly loose controls or one global density multiplier.
392
+
278
393
  ### Container boundary ownership
279
394
 
280
395
  Choose the boundary before choosing a spacing token. A valid token on the wrong
@@ -287,7 +402,7 @@ owner is still a design error:
287
402
  | Full-width disclosure heading | `LayerCard.Header asChild` | Slot onto `CollapsibleTrigger`; one hit target, 16/12px insets |
288
403
  | Expanded prose or fields | `LayerCard.Body` | Inside `CollapsibleContent unstyled`; one 16px inset |
289
404
  | Attached code | `CodeBlock attached` / `CodeHighlighted attached` | Direct child of unstyled disclosure; code owns its 16/12px insets |
290
- | Edge-to-edge table/list | Its row/cell components | Direct card child; no Body with `p-0` or compensating margins |
405
+ | Edge-to-edge table/list | Its row/cell components | Direct card child; panel inset on edge cells, no Body with `p-0` or compensating margins |
291
406
  | Compact status/action strip | `Banner` | Control spacing, not a hand-drawn card |
292
407
 
293
408
  Collapsible owns visibility, focus semantics and motion; it does not add another
@@ -323,7 +438,10 @@ to recreate ordinary actions, selects or inputs.
323
438
  `bun run design:check` scans library components/helpers and catalog examples,
324
439
  including CSS, for raw spacing/type/radius/motion values, fixed action overrides
325
440
  and inline design repairs. The landing route's own CSS is exempt from the spacing,
326
- radius and type rules and still keeps motion tokens. It derives dashboard page requirements from App routes
441
+ radius and type rules and still keeps motion tokens. Noncanonical Basalt font-size
442
+ classes are rejected, including responsive and important variants; only NotFound's
443
+ `text-basalt-10xl` and LoginPage's independent scale are exempt from the source
444
+ font-size check. It derives dashboard page requirements from App routes
327
445
  and checks template ownership, duplicate slot spacing, recreated card surfaces,
328
446
  ordinary native controls in pages and page-level control padding overrides.
329
447
  It rejects compact control padding on content panels, raw application disclosures,
package/ai/INTEGRATION.md CHANGED
@@ -428,9 +428,9 @@ 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. Top-level items, group items, editable/folder rows and horizontal navigation links reuse the existing row roles: 8px horizontal, 6px vertical, 8px icon gap, 22px line box and a 34px reference height. 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` (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.
432
432
 
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, follows keyboard focus and returns to selection on pointer leave. 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.
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
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.
436
436
 
@@ -486,13 +486,13 @@ Use `SidebarNav` for vertical lists, including standalone lists of `EditableNavI
486
486
  </Sidebar>
487
487
  ```
488
488
 
489
- SidebarPartition marks a major section; SidebarGroup is a smaller uppercase, semibold disclosure label with additional group spacing. Group labels do not participate in the destination hover layer. Do not restyle them as ordinary SidebarItem rows or add per-page indentation repairs.
489
+ SidebarPartition marks a major section; SidebarGroup is a smaller uppercase, semibold disclosure label with a 22px line box, 8px insets and 12px group separation. Group labels do not participate in the destination hover layer. Do not restyle them as ordinary SidebarItem rows or add per-page indentation repairs.
490
490
 
491
491
  ### Collapsed tree
492
492
 
493
493
  Pass `collapsed` on `Sidebar`. The element becomes 68px. Children are icon-sized and centered. Still no inner width/height wrapper.
494
494
 
495
- The 68px rail uses `--basalt-size-rail`. Header and footer center their content automatically; SidebarIconItem uses the shared 34px control size. The navigation region centers its icon rows on that same rail axis without caller margin overrides. Render icon-only content with an accessible name and tooltip when collapsed; the library does not hide arbitrary application text. Keep a brand mark's expanded inset consistent with the collapsed axis instead of adding collapse-only offsets.
495
+ The 68px rail uses `--basalt-size-rail`. Header and footer center their content automatically; SidebarIconItem keeps the 34px control width and derives its 38px minimum height from the sidebar line box and padding. The navigation region centers its icon rows on that same rail axis without caller margin overrides. Render icon-only content with an accessible name and tooltip when collapsed; the library does not hide arbitrary application text. Keep a brand mark's expanded inset consistent with the collapsed axis instead of adding collapse-only offsets.
496
496
 
497
497
  ```tsx excerpt:sidebar-collapsed-structure
498
498
  <Sidebar collapsed={collapsed}>
@@ -1021,6 +1021,12 @@ Stack one `SectionRule` per region. Live recipe: `/layout`. Catalog: `/ui/page-h
1021
1021
 
1022
1022
  Basalt form controls participate in standard forms and controlled state pipelines.
1023
1023
 
1024
+ Keep the Field's label/control/hint gap at 8px. Compose related fields with
1025
+ `space-y-basalt-layout` or `gap-basalt-layout` (16px); separate sections with the
1026
+ 24px `layout-lg` role. The form owns the group gap, not each Field's margin.
1027
+ Action buttons keep their 8px internal gap. DialogFooter owns 16px separation
1028
+ from preceding content, so callers do not need a repeated margin override.
1029
+
1024
1030
  ### Working Native HTML Form & FormData Pattern
1025
1031
 
1026
1032
  Standard inputs (`Input`, `InputArea`, `Checkbox`, `Radio`, `Switch`) reliably supply their values via standard `FormData(event.currentTarget)`:
@@ -1039,7 +1045,7 @@ export function ProfileForm() {
1039
1045
  };
1040
1046
 
1041
1047
  return (
1042
- <form onSubmit={handleSubmit}>
1048
+ <form onSubmit={handleSubmit} className="space-y-basalt-layout">
1043
1049
  <Field label="Email" hint="Your primary email address">
1044
1050
  <Input name="email" type="email" required />
1045
1051
  </Field>
@@ -1048,7 +1054,7 @@ export function ProfileForm() {
1048
1054
  <Switch name="notifications" defaultChecked />
1049
1055
  </Field>
1050
1056
 
1051
- <div className="flex gap-basalt-space-lg mt-basalt-space-lg">
1057
+ <div className="flex gap-basalt-space-lg">
1052
1058
  <Button type="reset" variant="secondary">Reset</Button>
1053
1059
  <Button type="submit" variant="default">Save Changes</Button>
1054
1060
  </div>
@@ -1203,7 +1209,7 @@ export function ProjectSettingsForm() {
1203
1209
  };
1204
1210
 
1205
1211
  return (
1206
- <form onSubmit={handleSubmit(onSubmit)} className="space-y-basalt-space-lg">
1212
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-basalt-layout">
1207
1213
  <Field
1208
1214
  label="Project Name"
1209
1215
  hint="Unique name for your project workspace"
@@ -1728,7 +1734,7 @@ export function UserPreferencesForm() {
1728
1734
  };
1729
1735
 
1730
1736
  return (
1731
- <form onSubmit={handleSubmit} onReset={handleReset} className="space-y-basalt-space-lg max-w-sm">
1737
+ <form onSubmit={handleSubmit} onReset={handleReset} className="space-y-basalt-layout max-w-sm">
1732
1738
  <Field label="Username" hint="Public profile handle">
1733
1739
  <Input name="username" defaultValue="johndoe" required />
1734
1740
  </Field>
@@ -1807,6 +1813,7 @@ Basalt geometry no longer depends on a host's Tailwind `--spacing`. Both CSS ent
1807
1813
  | Field label gap | `--basalt-space-field-gap` | 8px |
1808
1814
  | Textarea minimum height | `--basalt-size-textarea-sm`, `--basalt-size-textarea`, `--basalt-size-textarea-lg` | 64 / 80 / 96px |
1809
1815
  | Table row / horizontal / vertical inset | `--basalt-size-table-row`, `--basalt-space-table-x`, `--basalt-space-table-y` | 36 / 8 / 6px |
1816
+ | Attached table outer edge | `--basalt-space-table-edge` | Panel horizontal inset, 16px |
1810
1817
 
1811
1818
  Defaults use rem and unitless line-height, preserving browser font-size preferences. [DESIGN.md](DESIGN.md) defines compact 2/4/6/8px control spacing, separate 12/16/24/32px card and layout spacing, and natural 22/34/46px inline/action/banner categories. The size tokens describe reference geometry, not fixed text-control heights. Use the semantic spacing role for the boundary being laid out; numbered geometry tokens are not padding alternatives. LayerCard accepts `padding="sm|md|lg|xl"` for unstructured content; Grid accepts `gap="sm|md|lg|xl"`. Both default to `md`; 32px `xl` is opt-in. ContentIsland owns a 16px mobile, 24px tablet, and 32px horizontal / 24px vertical desktop inset. Examples must use component size props rather than fixed-height overrides. Textareas, avatars and panels retain content-specific geometry.
1812
1819
 
@@ -1814,6 +1821,66 @@ Defaults use rem and unitless line-height, preserving browser font-size preferen
1814
1821
 
1815
1822
  DataTable uses 13px single-line content, 36px minimum rows, tokenized horizontal/vertical insets and a native horizontally scrollable region. A column's `width` is also its minimum width; the browser must not squeeze names/dates into accidental multi-line rows. Use `column.wrap: true` for actual prose fields, or an explicitly sized inner `truncate` element with a title for long record names and URLs. `maxHeight` bounds the existing scroll region, with sticky opaque headers that stay inside it. Sorting uses a fixed 12px icon slot and retains `aria-sort`; row selection uses the shared Checkbox rather than an unstyled native checkbox. Rich `headerContent` composes icons and labels without changing the plain accessible sort name. Empty/loading/error content remains readable and wraps independently of data-cell defaults.
1816
1823
 
1824
+ Use `column.textAlign: "end"` for comparable numbers: the header and values share
1825
+ a logical end rail, with the sort glyph inward of the header label. `"start"` is
1826
+ the other explicit direction; omitted body-cell alignment remains inherited.
1827
+ The same `textAlign` prop is available on TableHead, TableCell and
1828
+ TableSortableHeader from `@nocoo/basalt/components/table`. It does not replace
1829
+ native HTML `align` attributes. Logical directions follow LTR/RTL consistently.
1830
+
1831
+ TableSortableHeader is a flush, ref-forwarding button, not a generic Button with
1832
+ extra padding. Pass its `sorted` direction (`"asc"` / `"desc"`, or omitted) and
1833
+ sort callback; the enclosing TableHead owns `aria-sort`. Unsortable headers remain
1834
+ plain text, not disabled sort controls. DataTable handles those details for its
1835
+ own columns.
1836
+
1837
+ For a DataTable directly attached to a LayerCard, set `edge="attached"`. It uses
1838
+ the 16px panel rail only at the outermost cells, including optional number/select
1839
+ columns and full-width state rows. Intercolumn spacing stays 8px per cell side.
1840
+ Built-in pagination shares that panel rail and its 12px bottom inset.
1841
+ The default `edge="inset"` keeps ordinary table spacing inside an already padded
1842
+ region; do not add a zero-padding Body around an attached table.
1843
+
1844
+ ```tsx compile:integration-attached-table
1845
+ import { DataTable, type DataTableColumn } from "@nocoo/basalt/components/data-table";
1846
+ import { LayerCard } from "@nocoo/basalt/components/layer-card";
1847
+
1848
+ type Invoice = { id: string; amount: number };
1849
+ const columns: DataTableColumn<Invoice>[] = [
1850
+ { id: "id", header: "Invoice", accessor: (row) => row.id },
1851
+ { id: "amount", header: "Amount", accessor: (row) => row.amount, textAlign: "end" },
1852
+ ];
1853
+
1854
+ export function InvoicePanel() {
1855
+ return (
1856
+ <LayerCard>
1857
+ <LayerCard.Header>Invoices</LayerCard.Header>
1858
+ <DataTable
1859
+ aria-label="Invoices"
1860
+ edge="attached"
1861
+ columns={columns}
1862
+ data={[{ id: "INV-01", amount: 1240 }, { id: "INV-02", amount: 80 }]}
1863
+ getRowId={(row) => row.id}
1864
+ />
1865
+ </LayerCard>
1866
+ );
1867
+ }
1868
+ ```
1869
+
1870
+ Native Table does not infer a grid from its children. For an attached native
1871
+ composition, mark the true inline-start cells with `ps-basalt-table-edge`, the
1872
+ inline-end cells with `pe-basalt-table-edge`, and a full-width cell with both.
1873
+ Rows continuing a preceding `rowSpan` do not gain a new edge at their first DOM
1874
+ child. These token utilities keep cell backgrounds, sticky headers and separators
1875
+ full width without affecting nested tables. Use ordinary Table cells when no
1876
+ panel edge is being attached.
1877
+
1878
+ TableCaption owns balanced `--basalt-space-panel-x` / `--basalt-space-panel-y`
1879
+ insets (16px horizontal / 12px vertical), not an outside top margin. Keep it
1880
+ inside the native table; do not pad the entire scroll region or compensate with
1881
+ an extra footer wrapper. This preserves full-width row paint and readable space
1882
+ above and below the description inside an enclosing frame.
1883
+
1817
1884
  ### Recommendations
1818
1885
 
1819
1886
  `RecommendationCard` (`components/recommendation-card`) accepts stable-ID options with a label, plain-text description, confidence (`high`, `review`, `none`) and optional action label/disabled state. Alternatives expand with the existing disclosure and shared hover layer; choosing one changes the recommendation, never executes it. `onAccept` runs only after the explicit action. Promise rejection preserves the selection and shows retry feedback; pending acceptance locks controls and successful acceptance cannot be repeated for the current option. Remount with a new key for a new recommendation request. The application must perform authorization and persist any real action. Private model/ViewModel modules own confidence metadata and state, without View or DOM imports.
@@ -1846,6 +1913,14 @@ All model names, progress summaries, searches and tool outputs on this route are
1846
1913
 
1847
1914
  Use one padding owner per boundary: `--basalt-space-panel-x/y` (16/12px) for composite card/header/body/footer sections, `--basalt-space-row-x/y/gap` (8/6/8px) for selectable rows, and `--basalt-space-content-gap` (8px) between related controls inside a composite. Page section gaps instead use `--basalt-space-layout-lg` (24px). `--basalt-space-row-content` derives the detail inset from row padding + a 16px icon slot + the row gap. Selectable action rows use the 22px `--basalt-line-row`, producing 34px single-line rows and growing by full lines when content wraps. Status icons occupy a first-line-height slot, not an arbitrary top margin. Body prose and code keep the 20px body line-height. Ordinary controls use 34px; inline message actions use the 34px `--basalt-size-action` target and 14px glyphs. Align visible glyphs with prose without removing their pointer target.
1848
1915
 
1916
+ These are local alignment rails, not one universal left edge. Page headings align
1917
+ to the island; card slots align to their panel; columns align their own labels and
1918
+ values. A checkbox, row number or optional status glyph has a consistent semantic
1919
+ slot. Distinct boundaries may add spacing, but the same boundary has one owner.
1920
+ See [alignment hierarchy](DESIGN.md#alignment-hierarchy). Keep nested preview focus
1921
+ clearance and give wide table examples enough columns rather than shrinking all
1922
+ their padding to fit a thumbnail.
1923
+
1849
1924
  Thinking and ToolChips share row padding; disclosures rotate only their own chevron, never content icons. Approval, recommendation, retrieved context and diff sections share panel insets rather than adding another padding layer. ChatComposer owns its field inset, not an outer painted/padded card; the application owns placement in the page. ChatMessage `sources` renders a compact disclosure beside actions and a flat source list, with the same validated URL policy as ContextCards. Use ContextCards only for actual retrieved text chunks, not as a citation footer. No additional remote images are loaded.
1850
1925
 
1851
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.
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.0.0).
3
+ This document describes how AI code assistants, automated agents, and developers can consume `@nocoo/basalt` (v3.2.0).
4
4
 
5
5
  ---
6
6
 
@@ -14,7 +14,7 @@ Document anchors formatted as `ai/registry.json#<slug>` reference specific catal
14
14
  It contains:
15
15
  - **131 Exported Modules** (1 root barrel, components, charts, providers)
16
16
  - **120 Ready Catalog Entries** with full API definitions
17
- - **776 Public Symbols** (405 runtime values, 371 TypeScript types)
17
+ - **781 Public Symbols** (406 runtime values, 375 TypeScript types)
18
18
  - **Detailed Component API**: Props, types, defaults, descriptions, and function callable signatures (including all `toast()` variants)
19
19
  - **Exact Peer Dependency Closures**: Identifies optional peers required per subpath (e.g., Recharts)
20
20