basalt-ui 1.29.1 → 1.30.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 (119) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/MIGRATING.md +701 -369
  3. package/README.md +23 -2
  4. package/agent/rules/basalt-controls.md +34 -34
  5. package/agent/skills/basalt-app/SKILL.md +4 -2
  6. package/agent/templates/query-client.ts.tpl +18 -4
  7. package/configs/lefthook.yml +51 -19
  8. package/configs/oxlint-plugin.js +210 -84
  9. package/configs/oxlint-plugin.test.ts +182 -5
  10. package/configs/oxlint.json +4 -4
  11. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  12. package/dist/charts/primitives/ChartCard.js +2 -2
  13. package/dist/charts/primitives/ChartFrame.d.ts +23 -3
  14. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  15. package/dist/charts/primitives/ChartFrame.js +4 -4
  16. package/dist/charts/primitives/chart-frame-layout.d.ts +55 -0
  17. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
  18. package/dist/charts/primitives/chart-frame-layout.js +16 -0
  19. package/dist/cli/check-theme.d.ts.map +1 -1
  20. package/dist/cli/check-theme.js +6 -0
  21. package/dist/cli/doctor.d.ts +10 -4
  22. package/dist/cli/doctor.d.ts.map +1 -1
  23. package/dist/cli/doctor.js +28 -1
  24. package/dist/cli/help.d.ts.map +1 -1
  25. package/dist/cli/help.js +2 -1
  26. package/dist/cli/index.d.ts +12 -0
  27. package/dist/cli/index.d.ts.map +1 -1
  28. package/dist/cli/index.js +29 -0
  29. package/dist/cli/init.d.ts.map +1 -1
  30. package/dist/cli/init.js +4 -15
  31. package/dist/cli/sync.d.ts.map +1 -1
  32. package/dist/cli/sync.js +8 -14
  33. package/dist/controls/actions.d.ts.map +1 -1
  34. package/dist/controls/actions.js +12 -2
  35. package/dist/dashboard/section.module.css +15 -7
  36. package/dist/dashboard/stat-card.d.ts.map +1 -1
  37. package/dist/dashboard/stat-card.js +7 -2
  38. package/dist/dashboard/stat-group.d.ts +8 -2
  39. package/dist/dashboard/stat-group.d.ts.map +1 -1
  40. package/dist/dashboard/stat-group.js +23 -15
  41. package/dist/dashboard/stat-group.module.css +85 -9
  42. package/dist/dashboard/stat-group.module.css.d.ts +1 -0
  43. package/dist/dashboard/widget-grid.d.ts.map +1 -1
  44. package/dist/dashboard/widget-grid.js +4 -5
  45. package/dist/dashboard/widget-grid.module.css +64 -21
  46. package/dist/dashboard/widget-grid.module.css.d.ts +1 -0
  47. package/dist/guard/index.d.ts +19 -2
  48. package/dist/guard/index.d.ts.map +1 -1
  49. package/dist/guard/index.js +27 -4
  50. package/dist/index.d.ts +9 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +2 -0
  53. package/dist/query-client.d.ts +20 -2
  54. package/dist/query-client.d.ts.map +1 -1
  55. package/dist/shell/app-brand.module.css +12 -3
  56. package/dist/shell/app-header.module.css +49 -9
  57. package/dist/shell/app-main.module.css +24 -4
  58. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  59. package/dist/shell/app-mobile-nav.js +98 -51
  60. package/dist/shell/app-mobile-nav.module.css +99 -11
  61. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  62. package/dist/shell/app-sidebar.module.css +55 -2
  63. package/dist/shell/index.d.ts.map +1 -1
  64. package/dist/shell/index.js +3 -3
  65. package/dist/shell/mobile-nav-model.d.ts +26 -7
  66. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  67. package/dist/shell/mobile-nav-model.js +12 -3
  68. package/dist/shell/page-aside.module.css +8 -6
  69. package/dist/shell/page-bar.d.ts +13 -0
  70. package/dist/shell/page-bar.d.ts.map +1 -1
  71. package/dist/shell/page-bar.js +13 -3
  72. package/dist/shell/page-bar.module.css +20 -12
  73. package/dist/shell/page-title.module.css +3 -1
  74. package/dist/theme/use-basalt-spacing.d.ts.map +1 -1
  75. package/dist/tokens/index.d.ts.map +1 -1
  76. package/dist/tokens/index.js +51 -7
  77. package/dist/tokens/palette.d.ts +98 -17
  78. package/dist/tokens/palette.d.ts.map +1 -1
  79. package/dist/tokens/palette.js +110 -18
  80. package/dist/tokens.css +17 -5
  81. package/llms.txt +2 -2
  82. package/package.json +1 -1
  83. package/src/charts/primitives/ChartCard.tsx +12 -7
  84. package/src/charts/primitives/ChartFrame.tsx +30 -15
  85. package/src/charts/primitives/chart-frame-layout.ts +86 -0
  86. package/src/cli/check-theme.ts +13 -0
  87. package/src/cli/doctor.ts +49 -8
  88. package/src/cli/help.ts +5 -3
  89. package/src/cli/index.ts +49 -0
  90. package/src/cli/init.ts +6 -27
  91. package/src/cli/sync.ts +11 -22
  92. package/src/controls/actions.tsx +51 -0
  93. package/src/dashboard/section.module.css +15 -7
  94. package/src/dashboard/stat-card.tsx +7 -2
  95. package/src/dashboard/stat-group.module.css +85 -9
  96. package/src/dashboard/stat-group.module.css.d.ts +1 -0
  97. package/src/dashboard/stat-group.tsx +40 -20
  98. package/src/dashboard/widget-grid.module.css +64 -21
  99. package/src/dashboard/widget-grid.module.css.d.ts +1 -0
  100. package/src/dashboard/widget-grid.tsx +30 -12
  101. package/src/guard/index.ts +79 -11
  102. package/src/index.ts +9 -0
  103. package/src/query-client.ts +17 -5
  104. package/src/shell/app-brand.module.css +12 -3
  105. package/src/shell/app-header.module.css +49 -9
  106. package/src/shell/app-main.module.css +24 -4
  107. package/src/shell/app-mobile-nav.module.css +99 -11
  108. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  109. package/src/shell/app-mobile-nav.tsx +225 -83
  110. package/src/shell/app-sidebar.module.css +55 -2
  111. package/src/shell/index.tsx +26 -6
  112. package/src/shell/mobile-nav-model.ts +37 -9
  113. package/src/shell/page-aside.module.css +8 -6
  114. package/src/shell/page-bar.module.css +20 -12
  115. package/src/shell/page-bar.tsx +39 -5
  116. package/src/shell/page-title.module.css +3 -1
  117. package/src/theme/use-basalt-spacing.ts +8 -6
  118. package/src/tokens/index.ts +90 -6
  119. package/src/tokens/palette.ts +111 -18
@@ -312,8 +312,15 @@ const SPACE_ANCHORS_BASE = {
312
312
  controlHeightWidget: 24,
313
313
  /** The `size="ctl"` Mantine tier's resolved height (`docs/CONTROLS-SPEC.md` §5, C5) —
314
314
  * `PageBar`/`Section`/table toolbar/sidebar-block controls. Floored at 28px in
315
- * {@link deriveSpacing}. */
316
- controlHeightCtl: 30,
315
+ * {@link deriveSpacing}.
316
+ *
317
+ * 32, up from 30. At 30 this tier was the ONLY chrome number in the shell that did not agree with
318
+ * its neighbours: the sidebar search trigger is 32 (`sidebarSearchTriggerHeight`), a sidebar block
319
+ * row is 32 (`sidebarBlockRowHeight`), and a NavLink row resolves to ~32 (`2 * rowInsetY +
320
+ * rowLineHeight * text.md`) — so a `size="ctl"` button sitting beside any of them read 2px short
321
+ * and the whole band looked unresolved. 32 lands the entire chrome tier on ONE number, which is
322
+ * also the smallest square that still holds a 16px icon with a 8px surround. */
323
+ controlHeightCtl: 32,
317
324
  /** The touch hit-area size (`::before`) every home control expands to below `sm` (C15,
318
325
  * `docs/CONTROLS-SPEC.md` §5) — independent of the visible `controlHeightCtl` box, same shape as
319
326
  * `mobileNavBarHeight`/`mobileNavRowHeight`'s Apple HIG 44pt / WCAG 2.5.5 floors elsewhere in this
@@ -378,7 +385,7 @@ const SPACE_STEP_BASE = {
378
385
  // literal could drift from that header, which is exactly what happened. `SpaceValues.step` still
379
386
  // carries the key — see that type's own doc — it is just not part of this BASE table. The
380
387
  // `stickyHeaderClearanceMobile` sibling went with the two-row mobile header in 1.26.0: the header
381
- // is one 48px row at every viewport now, so one clearance covers both.
388
+ // is one `appShellHeaderHeight` row at every viewport now, so one clearance covers both.
382
389
  /** NavLink leftSection icon-to-label gap — shared by `theme/nav-link.module.css` (every render
383
390
  * path) and `shell/app-sidebar.module.css`'s own `.link` rule (belt-and-suspenders on the same
384
391
  * DOM), and reused verbatim for `.footerBtn`/`.accountRow`'s icon gap (their own doc comments call
@@ -386,8 +393,14 @@ const SPACE_STEP_BASE = {
386
393
  navIconGap: 10,
387
394
  /** Sidebar region-to-region gap: `.brand`'s bottom padding and `.searchSlot`'s bottom padding (in
388
395
  * both the expanded and collapsed rail variants) — the searchSlot comment explicitly says its gap
389
- * opens the nav region "the same way the brand row's own bottom padding does". */
390
- sidebarRegionGap: 10,
396
+ * opens the nav region "the same way the brand row's own bottom padding does".
397
+ *
398
+ * 12, up from 10. This is the ONE gap the sidebar spends at all three of its seams (brand → search,
399
+ * search → nav, nav → account), so it is what separates the column's regions from each other; at 10
400
+ * it was indistinguishable from the gaps WITHIN a region (`sidebarSectionLabelGap`, the child-list
401
+ * margins), and the column read as one undifferentiated list of rows. 12 is the first step that
402
+ * reads as a region break without opening a hole. */
403
+ sidebarRegionGap: 12,
391
404
 
392
405
  // ── content/prose.module.css ──────────────────────────────────────────────────────────────────
393
406
  /** Blockquote rail's own vertical inset. */
@@ -549,8 +562,11 @@ const SPACE_STEP_BASE = {
549
562
  // ── shell/app-sidebar.module.css (beyond the shared/reused anchors above) ────────────────────
550
563
  /** Brand row's horizontal inset. */
551
564
  sidebarBrandInsetX: 7,
552
- /** Gap between nav sections. */
553
- sidebarSectionGap: 12,
565
+ /** Gap between nav sections. Paired with `sidebarSectionLabelGap` below: together they are the
566
+ * section's proximity law — MORE space above a section label than below it, so the label reads as
567
+ * belonging to the list it heads. 16/6, up from 12/2: at 12 above and 2 below the pair was
568
+ * INVERTED, gluing each label to the last row of the section before it. */
569
+ sidebarSectionGap: 16,
554
570
  /** Account row's top inset. */
555
571
  sidebarAccountInsetTop: 9,
556
572
  /** Account row's horizontal inset. */
@@ -559,8 +575,10 @@ const SPACE_STEP_BASE = {
559
575
  sidebarAccountInsetBottom: 2,
560
576
  /** Identity-initials avatar block's fixed size (width and height). */
561
577
  sidebarAvatarSize: 28,
562
- /** Section-label row's bottom gap (before the first nav row). */
563
- sidebarSectionLabelGap: 2,
578
+ /** Section-label row's bottom gap (before the first nav row) — the smaller half of the
579
+ * proximity pair documented on `sidebarSectionGap` above. 6, up from 2: 2 was hairline enough that
580
+ * the label looked welded to the first row rather than titling the list. */
581
+ sidebarSectionLabelGap: 6,
564
582
  /** Collapsible child-list wrapper's top margin. */
565
583
  sidebarChildListGapTop: 2,
566
584
  /** Collapsible child-list wrapper's bottom margin. */
@@ -588,15 +606,22 @@ const SPACE_STEP_BASE = {
588
606
 
589
607
  // ── shell/index.tsx (AppShell dimensions) ──────────────────────────────────────────────
590
608
  /** AppShell desktop header bar height — sized to hold one row of `size="md"` controls, so it
591
- * tracks `controlHeight`. Its level-0 coincidence with `appShellNavbarRailWidth` below is a
592
- * coincidence, not a law: this is a horizontal bar's HEIGHT (governed by control height), that
593
- * is a vertical rail's WIDTH (governed by icon footprint). They stay separate entries. */
609
+ * tracks `controlHeight`. It no longer coincides with `appShellNavbarRailWidth` below, which is
610
+ * the point that entry's own note was always making: this is a horizontal bar's HEIGHT (governed
611
+ * by control height), that is a vertical rail's WIDTH (governed by icon footprint). Two separate
612
+ * entries, and now visibly so. */
594
613
  /** AppShell header bar height — ONE value at every viewport since 1.26.0 (law C14,
595
614
  * `docs/CONTROLS-SPEC.md` §2.1). The pre-1.26.0 `appShellHeaderMobileHeight` (97) existed only
596
615
  * because the mobile header wrapped to a second, always-reserved page-actions row; `PageBar`
597
616
  * folds that row's overflow into a kebab and moves its filters/tabs into the page flow, so the
598
- * bar is 48px on a phone too and there is no sum to keep in step any more. */
599
- appShellHeaderHeight: 48,
617
+ * bar is one row on a phone too and there is no sum to keep in step any more.
618
+ *
619
+ * 44, down from 48. The band holds ONE `ctl`-tier row (32 since this pass), so 48 left 8px of
620
+ * dead air above and below it — the "too much spacing at the top bar" the header reads as. 44
621
+ * keeps 6px of breathing room a side, still clears `touchControlHeight` (36) below `sm` and
622
+ * `controlHeight` (42) for a `size="md"` control, and is the Apple HIG 44pt figure the rest of
623
+ * this table already floors its touch targets at. */
624
+ appShellHeaderHeight: 44,
600
625
  /** AppShell navbar width, expanded — ONE entry for both the `base` (mobile, where the navbar is
601
626
  * permanently collapsed and unused — see `MobileNav`) and the expanded `sm` value: the same
602
627
  * `.root` at full width, genuinely one concept.
@@ -608,8 +633,9 @@ const SPACE_STEP_BASE = {
608
633
  * (`sidebarAccountMenuWidth` 220 + the row inset), so the menu no longer overhangs the column it
609
634
  * belongs to. */
610
635
  appShellNavbarWidth: 256,
611
- /** AppShell navbar width, collapsed icon rail. Separate from `appShellHeaderHeight` above
612
- * despite sharing 48 at level 0 (see that entry's note). */
636
+ /** AppShell navbar width, collapsed icon rail. Separate from `appShellHeaderHeight` above, which
637
+ * used to share 48 at level 0 and no longer does (see that entry's note) — the two were always
638
+ * independent concepts and the shared literal was only ever hiding it. */
613
639
  appShellNavbarRailWidth: 48,
614
640
  /** AppShell ASIDE width, unfolded — the right-hand panel region a route claims by rendering
615
641
  * `PageAside` (`docs/ASIDE-SPEC.md` §0). ONE entry for every viewport, same reasoning as
@@ -624,6 +650,36 @@ const SPACE_STEP_BASE = {
624
650
  * `appShellNavbarRailWidth` (48) on purpose: the navbar rail hosts a full icon nav row and its
625
651
  * hover targets, this one hosts exactly one `ctl`-tier icon button and nothing else. */
626
652
  appShellAsideRailWidth: 36,
653
+ /** The PAGE GUTTER — `AppShell.Main`'s own horizontal inset, i.e. the distance from the sidebar
654
+ * seam (and from the viewport's right edge) to the first pixel of page content. Unlike its
655
+ * `appShell*` neighbours above this one IS emitted as a `--vx-*` var (see `spaceDecls`'s doc in
656
+ * `tokens/index.ts` for why), because it is the single most visible spacing number the framework
657
+ * ships and a consumer retuning its identity has to be able to reach it.
658
+ *
659
+ * 20, up from the 13 (`SPACE_SCALE.sm`) the main column used to borrow. The sidebar's nav ICON
660
+ * column already sits at x=20 — 10px of `.root` frame inset plus 10px of NavLink inset
661
+ * (`rowInsetX`) — and the brand row claims to align with it while sitting at 13. 20 makes that
662
+ * claim true across the seam: brand mark, nav icons and the page's first column all start on one
663
+ * vertical line, and the content gets a seam-side gutter that reads deliberate instead of
664
+ * incidental. */
665
+ appShellInset: 20,
666
+ /** The page gutter BELOW `sm` — the phone half of `appShellInset` above. A separate entry rather
667
+ * than one responsive value for the same reason `appShellNavbarWidth` is one: there is no sidebar
668
+ * seam to align to on a phone, so the number is answering a different question (how much of a
669
+ * 390px viewport to spend on margin) and must be free to move alone.
670
+ *
671
+ * 8, down from the same borrowed 13. 13 a side spends 26 of 390px — 6.7% of the viewport — on
672
+ * margin before a single card is drawn, which is the "content spacing is too big" a phone reads
673
+ * as. 8 keeps the content off the bezel without competing with the cards' own insets. */
674
+ appShellInsetMobile: 8,
675
+ /** The app header's BREADCRUMB FLOOR — `shell/app-header.module.css`'s `.lead` `min-width`, the
676
+ * width below which the row's one elastic side may not shrink. A step, not the raw `96px` it
677
+ * shipped as: every other dimension of that row (its height, its control tier, its gaps) tracks
678
+ * density, so a frozen floor would grow relatively narrower at every level up — and it is a floor
679
+ * measured in CHARACTERS of the crumb (~13 at level 0), which is exactly the quantity that moves
680
+ * when the type and the row around it move. The full derivation of 96 is on the `.lead` rule
681
+ * itself; this entry only owns the number. */
682
+ appShellHeaderLeadMin: 96,
627
683
 
628
684
  // ── shell/app-mobile-nav.module.css ───────────────────────────────────────────────────────────
629
685
  /** Tab's icon-to-label gap. */
@@ -709,11 +765,48 @@ const SPACE_STEP_BASE = {
709
765
  /** `data/virtual-list.tsx`'s skeleton row horizontal inset. */
710
766
  virtualRowInsetX: 12,
711
767
 
768
+ // ── The card/section inset ladder (`tokens/index.ts`'s `spaceMobileDecls` is its ONE home) ────
769
+ // The three numbers a page's CONTENT spends on itself, as opposed to the shell gutter around it
770
+ // (`appShellInset`/`appShellInsetMobile` above). They are grouped, and named, because they step
771
+ // TOGETHER below `sm` — the gutter went 20 -> 8 in the desktop-shell pass while the ladder inside
772
+ // it never moved, so a 390px phone still read "content spacing too big" after the gutter fix.
773
+ //
774
+ // Each value is deliberately the rung of an existing ladder rather than a fresh literal: the card
775
+ // inset IS `SPACE_SCALE.xs`/`.sm` (which is what `StatCard`/`ChartCard` spelled as
776
+ // `--mantine-spacing-xs`/`-sm` before they were named here) and the Section gap IS
777
+ // `SPACE.stackMd`. Naming them buys the ROLE, not a new number — a role the `< sm` override can
778
+ // then re-point without touching the scale every other consumer of `xs`/`sm` reads.
779
+ //
780
+ // There is deliberately NO `*Mobile` sibling for the two that step, unlike `appShellInsetMobile`:
781
+ // that one is a separate entry because a phone gutter answers a different question from a desktop
782
+ // one (how much of 390px to spend on margin, with no sidebar seam to align to). These two answer
783
+ // the SAME question one rung down, so the override reads the neighbouring rung (`scale.xs`,
784
+ // `anchors.stackSm`) and a second literal would only be a place for the ladder to drift apart.
785
+ /** Card inset, VERTICAL — `dashboard/stat-card.tsx` and `charts/primitives/ChartCard.tsx`, which
786
+ * must render identically (a StatCard and a ChartCard sit in the same grid row). Does NOT step
787
+ * below `sm`: the phone is short of WIDTH, and shaving the vertical inset of a card that already
788
+ * hugs its content buys a few pixels at the cost of the card's own breathing room. */
789
+ cardInsetY: 11,
790
+ /** Card inset, HORIZONTAL — same two files, same must-match reason. This IS the half that steps
791
+ * below `sm` (to `SPACE_SCALE.xs`), because horizontal is where a 390px viewport is short. */
792
+ cardInsetX: 13,
793
+ /** `dashboard/section.module.css`'s header→body and body-child gap — a page-level GROUPING
794
+ * rhythm, which is why it reads the stack scale rather than the card-inset one. Steps to
795
+ * `SPACE.stackSm` below `sm`. Distinct from `sidebarSectionGap` above, which is the gap between
796
+ * two NAV sections inside the sidebar column. */
797
+ sectionGap: 12,
798
+
712
799
  // ── Controls — homes/tiers (`docs/CONTROLS-SPEC.md` §5) ──────────────────────────────────────
713
800
  /** `PageBar` row height (row 1 or row 2, each measured independently — the bar's own overall
714
801
  * height is the sum published as `--basalt-page-bar-h`, computed by `PageBar` itself, not a
715
- * static token). */
716
- pageBarRowHeight: 40,
802
+ * static token).
803
+ *
804
+ * 36, down from 40. Row 1 portals into a shell-owned band directly under the app header
805
+ * (`shell/page-bar.tsx`), so the two stack as visible chrome: at 40 against a 44 header the
806
+ * secondary row all but matched the primary one, and the eye could not tell which band owned the
807
+ * page. 36 puts it a clear step below the header while still holding a 32px `ctl` row with 2px a
808
+ * side. */
809
+ pageBarRowHeight: 36,
717
810
  /** `WidgetHeader tier="section"` row height. */
718
811
  sectionHeaderHeight: 36,
719
812
  /** `WidgetHeader tier="widget"` row height. */