cursedbelt 5.40.0 → 5.45.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 (499) hide show
  1. package/dist/react/Toast.d.ts +12 -2
  2. package/dist/react/Toast.d.ts.map +1 -1
  3. package/dist/react/Toast.js +32 -3
  4. package/dist/react/Toast.js.map +1 -1
  5. package/dist/react/analytics/MetricDrilldown.js +1 -1
  6. package/dist/react/charts/AreaChart.d.ts.map +1 -1
  7. package/dist/react/charts/AreaChart.js +5 -5
  8. package/dist/react/charts/AreaChart.js.map +1 -1
  9. package/dist/react/charts/BarChart.d.ts.map +1 -1
  10. package/dist/react/charts/BarChart.js +3 -3
  11. package/dist/react/charts/BarChart.js.map +1 -1
  12. package/dist/react/charts/ChartCard.d.ts +53 -0
  13. package/dist/react/charts/ChartCard.d.ts.map +1 -0
  14. package/dist/react/charts/ChartCard.js +46 -0
  15. package/dist/react/charts/ChartCard.js.map +1 -0
  16. package/dist/react/charts/ChartFrame.d.ts +12 -5
  17. package/dist/react/charts/ChartFrame.d.ts.map +1 -1
  18. package/dist/react/charts/ChartFrame.js +12 -5
  19. package/dist/react/charts/ChartFrame.js.map +1 -1
  20. package/dist/react/charts/ChartLegend.js +1 -1
  21. package/dist/react/charts/ChartLegend.js.map +1 -1
  22. package/dist/react/charts/HeatmapChart.js +3 -3
  23. package/dist/react/charts/HeatmapChart.js.map +1 -1
  24. package/dist/react/charts/IntervalTimeline.js +1 -1
  25. package/dist/react/charts/IntervalTimeline.js.map +1 -1
  26. package/dist/react/charts/LineChart.d.ts.map +1 -1
  27. package/dist/react/charts/LineChart.js +3 -3
  28. package/dist/react/charts/LineChart.js.map +1 -1
  29. package/dist/react/charts/TimeSeriesChart.d.ts.map +1 -1
  30. package/dist/react/charts/TimeSeriesChart.js +6 -6
  31. package/dist/react/charts/TimeSeriesChart.js.map +1 -1
  32. package/dist/react/charts/chartBox.d.ts +46 -0
  33. package/dist/react/charts/chartBox.d.ts.map +1 -0
  34. package/dist/react/charts/chartBox.js +45 -0
  35. package/dist/react/charts/chartBox.js.map +1 -0
  36. package/dist/react/charts/index.d.ts +2 -0
  37. package/dist/react/charts/index.d.ts.map +1 -1
  38. package/dist/react/charts/index.js +2 -0
  39. package/dist/react/charts/index.js.map +1 -1
  40. package/dist/react/charts/shared.d.ts +13 -2
  41. package/dist/react/charts/shared.d.ts.map +1 -1
  42. package/dist/react/charts/shared.js +14 -3
  43. package/dist/react/charts/shared.js.map +1 -1
  44. package/dist/react/chat/Bubble.js +4 -4
  45. package/dist/react/chat/Bubble.js.map +1 -1
  46. package/dist/react/chat/MessageScroller.js +1 -1
  47. package/dist/react/chat/MessageScroller.js.map +1 -1
  48. package/dist/react/components/ActionBar.d.ts.map +1 -1
  49. package/dist/react/components/ActionBar.js +63 -1
  50. package/dist/react/components/ActionBar.js.map +1 -1
  51. package/dist/react/components/AddItemsMenu.js +1 -1
  52. package/dist/react/components/AddItemsMenu.js.map +1 -1
  53. package/dist/react/components/Alert.d.ts +16 -2
  54. package/dist/react/components/Alert.d.ts.map +1 -1
  55. package/dist/react/components/Alert.js +35 -2
  56. package/dist/react/components/Alert.js.map +1 -1
  57. package/dist/react/components/AppShell.d.ts +55 -33
  58. package/dist/react/components/AppShell.d.ts.map +1 -1
  59. package/dist/react/components/AppShell.js +121 -31
  60. package/dist/react/components/AppShell.js.map +1 -1
  61. package/dist/react/components/AsyncPanel.d.ts +21 -3
  62. package/dist/react/components/AsyncPanel.d.ts.map +1 -1
  63. package/dist/react/components/AsyncPanel.js +49 -5
  64. package/dist/react/components/AsyncPanel.js.map +1 -1
  65. package/dist/react/components/BannerLane.d.ts +32 -0
  66. package/dist/react/components/BannerLane.d.ts.map +1 -0
  67. package/dist/react/components/BannerLane.js +41 -0
  68. package/dist/react/components/BannerLane.js.map +1 -0
  69. package/dist/react/components/Button.d.ts.map +1 -1
  70. package/dist/react/components/Button.js +2 -1
  71. package/dist/react/components/Button.js.map +1 -1
  72. package/dist/react/components/ChipFilterStrip.js +1 -1
  73. package/dist/react/components/ChipFilterStrip.js.map +1 -1
  74. package/dist/react/components/ComboboxField.d.ts.map +1 -1
  75. package/dist/react/components/ComboboxField.js +14 -4
  76. package/dist/react/components/ComboboxField.js.map +1 -1
  77. package/dist/react/components/CommentThread.js +2 -2
  78. package/dist/react/components/CommentThread.js.map +1 -1
  79. package/dist/react/components/ConfirmDialog.js +1 -1
  80. package/dist/react/components/ConfirmDialog.js.map +1 -1
  81. package/dist/react/components/DataTable/DataTable.d.ts +18 -2
  82. package/dist/react/components/DataTable/DataTable.d.ts.map +1 -1
  83. package/dist/react/components/DataTable/DataTable.js +51 -12
  84. package/dist/react/components/DataTable/DataTable.js.map +1 -1
  85. package/dist/react/components/DataTable/Pagination.js +1 -1
  86. package/dist/react/components/DataTable/Pagination.js.map +1 -1
  87. package/dist/react/components/DataTable/RowActions.d.ts +7 -1
  88. package/dist/react/components/DataTable/RowActions.d.ts.map +1 -1
  89. package/dist/react/components/DataTable/RowActions.js +10 -3
  90. package/dist/react/components/DataTable/RowActions.js.map +1 -1
  91. package/dist/react/components/DataTable/TableFilterBuilder.js +1 -1
  92. package/dist/react/components/DataTable/TableFilterBuilder.js.map +1 -1
  93. package/dist/react/components/DataTable/cells/CodeCell.js +1 -1
  94. package/dist/react/components/DataTable/cells/CodeCell.js.map +1 -1
  95. package/dist/react/components/DataTable/cells/LinkCell.js +1 -1
  96. package/dist/react/components/DataTable/cells/LinkCell.js.map +1 -1
  97. package/dist/react/components/DataTable/index.d.ts +1 -1
  98. package/dist/react/components/DataTable/index.d.ts.map +1 -1
  99. package/dist/react/components/DataTable/index.js.map +1 -1
  100. package/dist/react/components/DataTable/types.d.ts +20 -0
  101. package/dist/react/components/DataTable/types.d.ts.map +1 -1
  102. package/dist/react/components/DataTable/types.js +22 -0
  103. package/dist/react/components/DataTable/types.js.map +1 -1
  104. package/dist/react/components/DateTimeField.d.ts.map +1 -1
  105. package/dist/react/components/DateTimeField.js +4 -1
  106. package/dist/react/components/DateTimeField.js.map +1 -1
  107. package/dist/react/components/DeleteButton.js +2 -0
  108. package/dist/react/components/DeleteButton.js.map +1 -1
  109. package/dist/react/components/Drawer.js +3 -3
  110. package/dist/react/components/Drawer.js.map +1 -1
  111. package/dist/react/components/DropdownMenu.d.ts +16 -0
  112. package/dist/react/components/DropdownMenu.d.ts.map +1 -1
  113. package/dist/react/components/DropdownMenu.js +22 -6
  114. package/dist/react/components/DropdownMenu.js.map +1 -1
  115. package/dist/react/components/EmptyState.d.ts +10 -2
  116. package/dist/react/components/EmptyState.d.ts.map +1 -1
  117. package/dist/react/components/EmptyState.js +10 -5
  118. package/dist/react/components/EmptyState.js.map +1 -1
  119. package/dist/react/components/EventTimeline.js +1 -1
  120. package/dist/react/components/EventTimeline.js.map +1 -1
  121. package/dist/react/components/FileDropZone.js +1 -1
  122. package/dist/react/components/FileDropZone.js.map +1 -1
  123. package/dist/react/components/Grid.d.ts +9 -1
  124. package/dist/react/components/Grid.d.ts.map +1 -1
  125. package/dist/react/components/Grid.js +20 -2
  126. package/dist/react/components/Grid.js.map +1 -1
  127. package/dist/react/components/Guide.d.ts +31 -0
  128. package/dist/react/components/Guide.d.ts.map +1 -0
  129. package/dist/react/components/Guide.js +18 -0
  130. package/dist/react/components/Guide.js.map +1 -0
  131. package/dist/react/components/InfoHintImpl.d.ts +0 -13
  132. package/dist/react/components/InfoHintImpl.d.ts.map +1 -1
  133. package/dist/react/components/InfoHintImpl.js +17 -1
  134. package/dist/react/components/InfoHintImpl.js.map +1 -1
  135. package/dist/react/components/InlineEditableText.js +1 -1
  136. package/dist/react/components/InlineEditableText.js.map +1 -1
  137. package/dist/react/components/IsolatedFrame.js +1 -1
  138. package/dist/react/components/IsolatedFrame.js.map +1 -1
  139. package/dist/react/components/ListToolbar.d.ts.map +1 -1
  140. package/dist/react/components/ListToolbar.js +2 -2
  141. package/dist/react/components/ListToolbar.js.map +1 -1
  142. package/dist/react/components/LogViewer.d.ts +26 -2
  143. package/dist/react/components/LogViewer.d.ts.map +1 -1
  144. package/dist/react/components/LogViewer.js +41 -26
  145. package/dist/react/components/LogViewer.js.map +1 -1
  146. package/dist/react/components/Modal.d.ts +5 -1
  147. package/dist/react/components/Modal.d.ts.map +1 -1
  148. package/dist/react/components/Modal.js +18 -8
  149. package/dist/react/components/Modal.js.map +1 -1
  150. package/dist/react/components/Page.d.ts +51 -7
  151. package/dist/react/components/Page.d.ts.map +1 -1
  152. package/dist/react/components/Page.js +53 -8
  153. package/dist/react/components/Page.js.map +1 -1
  154. package/dist/react/components/PageGuideButton.d.ts +11 -0
  155. package/dist/react/components/PageGuideButton.d.ts.map +1 -0
  156. package/dist/react/components/PageGuideButton.js +13 -0
  157. package/dist/react/components/PageGuideButton.js.map +1 -0
  158. package/dist/react/components/PanelSeparator.js +1 -1
  159. package/dist/react/components/PanelSeparator.js.map +1 -1
  160. package/dist/react/components/Popover.d.ts.map +1 -1
  161. package/dist/react/components/Popover.js +2 -1
  162. package/dist/react/components/Popover.js.map +1 -1
  163. package/dist/react/components/PromptDialog.js +1 -1
  164. package/dist/react/components/PromptDialog.js.map +1 -1
  165. package/dist/react/components/ScrollArea.d.ts +9 -1
  166. package/dist/react/components/ScrollArea.d.ts.map +1 -1
  167. package/dist/react/components/ScrollArea.js +9 -3
  168. package/dist/react/components/ScrollArea.js.map +1 -1
  169. package/dist/react/components/Section.d.ts +5 -2
  170. package/dist/react/components/Section.d.ts.map +1 -1
  171. package/dist/react/components/Section.js +5 -2
  172. package/dist/react/components/Section.js.map +1 -1
  173. package/dist/react/components/Skeleton.d.ts +19 -1
  174. package/dist/react/components/Skeleton.d.ts.map +1 -1
  175. package/dist/react/components/Skeleton.js +24 -2
  176. package/dist/react/components/Skeleton.js.map +1 -1
  177. package/dist/react/components/StatStrip.js +1 -1
  178. package/dist/react/components/StatStrip.js.map +1 -1
  179. package/dist/react/components/StatTile.d.ts +30 -3
  180. package/dist/react/components/StatTile.d.ts.map +1 -1
  181. package/dist/react/components/StatTile.js +35 -9
  182. package/dist/react/components/StatTile.js.map +1 -1
  183. package/dist/react/components/StatTileGrid.d.ts +10 -1
  184. package/dist/react/components/StatTileGrid.d.ts.map +1 -1
  185. package/dist/react/components/StatTileGrid.js +50 -33
  186. package/dist/react/components/StatTileGrid.js.map +1 -1
  187. package/dist/react/components/StatusPill.d.ts.map +1 -1
  188. package/dist/react/components/StatusPill.js +26 -1
  189. package/dist/react/components/StatusPill.js.map +1 -1
  190. package/dist/react/components/Surface.d.ts +1 -1
  191. package/dist/react/components/Surface.d.ts.map +1 -1
  192. package/dist/react/components/Surface.js +20 -11
  193. package/dist/react/components/Surface.js.map +1 -1
  194. package/dist/react/components/SwatchPicker.d.ts.map +1 -1
  195. package/dist/react/components/SwatchPicker.js +5 -1
  196. package/dist/react/components/SwatchPicker.js.map +1 -1
  197. package/dist/react/components/Switch.js +1 -1
  198. package/dist/react/components/Switch.js.map +1 -1
  199. package/dist/react/components/WidgetHeader.js +1 -1
  200. package/dist/react/components/WidgetHeader.js.map +1 -1
  201. package/dist/react/components/calendar/CalendarLegend.js +1 -1
  202. package/dist/react/components/calendar/CalendarLegend.js.map +1 -1
  203. package/dist/react/components/calendar/DayAgenda.js +1 -1
  204. package/dist/react/components/calendar/DayAgenda.js.map +1 -1
  205. package/dist/react/components/calendar/EventChip.js +1 -1
  206. package/dist/react/components/calendar/EventChip.js.map +1 -1
  207. package/dist/react/components/calendar/MonthGrid.js +1 -1
  208. package/dist/react/components/calendar/MonthGrid.js.map +1 -1
  209. package/dist/react/components/calendar/WeekView.js +2 -2
  210. package/dist/react/components/calendar/WeekView.js.map +1 -1
  211. package/dist/react/components/cardFrame.d.ts +16 -0
  212. package/dist/react/components/cardFrame.d.ts.map +1 -0
  213. package/dist/react/components/cardFrame.js +24 -0
  214. package/dist/react/components/cardFrame.js.map +1 -0
  215. package/dist/react/components/dialog.d.ts.map +1 -1
  216. package/dist/react/components/dialog.js +2 -1
  217. package/dist/react/components/dialog.js.map +1 -1
  218. package/dist/react/dashboard-grid/WidgetFrame.js +2 -2
  219. package/dist/react/dashboard-grid/WidgetFrame.js.map +1 -1
  220. package/dist/react/emoji/EmojiPicker.js +1 -1
  221. package/dist/react/emoji/EmojiPicker.js.map +1 -1
  222. package/dist/react/file-tree/FileTree.d.ts +8 -1
  223. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  224. package/dist/react/file-tree/FileTree.js +29 -4
  225. package/dist/react/file-tree/FileTree.js.map +1 -1
  226. package/dist/react/filter-rail/FilterRail.js +1 -1
  227. package/dist/react/filter-rail/FilterRail.js.map +1 -1
  228. package/dist/react/icons.js +1 -1
  229. package/dist/react/icons.js.map +1 -1
  230. package/dist/react/index.d.ts +8 -5
  231. package/dist/react/index.d.ts.map +1 -1
  232. package/dist/react/index.js +5 -2
  233. package/dist/react/index.js.map +1 -1
  234. package/dist/react/lib/menuSections.d.ts +8 -0
  235. package/dist/react/lib/menuSections.d.ts.map +1 -0
  236. package/dist/react/lib/menuSections.js +37 -0
  237. package/dist/react/lib/menuSections.js.map +1 -0
  238. package/dist/react/markdown/Markdown.js +2 -2
  239. package/dist/react/markdown/Markdown.js.map +1 -1
  240. package/dist/react/media/HoverScrubVideoThumb.js +1 -1
  241. package/dist/react/media/HoverScrubVideoThumb.js.map +1 -1
  242. package/dist/react/media/MaskPainter.js +1 -1
  243. package/dist/react/media/MediaUploadControl.js +1 -1
  244. package/dist/react/media/MediaUploadControl.js.map +1 -1
  245. package/dist/react/media/VideoTrimmer.js +1 -1
  246. package/dist/react/media/VideoTrimmer.js.map +1 -1
  247. package/dist/react/media/denseGallery.css +5 -1
  248. package/dist/react/namespace/NamespaceTargetCapsule.js +1 -1
  249. package/dist/react/namespace/NamespaceTargetCapsule.js.map +1 -1
  250. package/dist/react/nav/BreadcrumbTrail.js +1 -1
  251. package/dist/react/nav/BreadcrumbTrail.js.map +1 -1
  252. package/dist/react/nav/CommandBar.d.ts +7 -1
  253. package/dist/react/nav/CommandBar.d.ts.map +1 -1
  254. package/dist/react/nav/CommandBar.js +5 -3
  255. package/dist/react/nav/CommandBar.js.map +1 -1
  256. package/dist/react/nav/PlaceBar.d.ts.map +1 -1
  257. package/dist/react/nav/PlaceBar.js +7 -9
  258. package/dist/react/nav/PlaceBar.js.map +1 -1
  259. package/dist/react/nav/SideNav.d.ts +12 -2
  260. package/dist/react/nav/SideNav.d.ts.map +1 -1
  261. package/dist/react/nav/SideNav.js +42 -22
  262. package/dist/react/nav/SideNav.js.map +1 -1
  263. package/dist/react/nav/TopBar.d.ts +65 -31
  264. package/dist/react/nav/TopBar.d.ts.map +1 -1
  265. package/dist/react/nav/TopBar.js +59 -24
  266. package/dist/react/nav/TopBar.js.map +1 -1
  267. package/dist/react/nav/index.d.ts +1 -1
  268. package/dist/react/nav/index.d.ts.map +1 -1
  269. package/dist/react/nav/index.js +1 -1
  270. package/dist/react/nav/index.js.map +1 -1
  271. package/dist/react/nav/navShared.d.ts +10 -1
  272. package/dist/react/nav/navShared.d.ts.map +1 -1
  273. package/dist/react/nav/navShared.js +23 -0
  274. package/dist/react/nav/navShared.js.map +1 -1
  275. package/dist/react/notifications/NotificationFeed.js +2 -2
  276. package/dist/react/notifications/NotificationFeed.js.map +1 -1
  277. package/dist/react/palette/Command.d.ts.map +1 -1
  278. package/dist/react/palette/Command.js +1 -1
  279. package/dist/react/palette/Command.js.map +1 -1
  280. package/dist/react/palette/CommandPalette.d.ts +13 -1
  281. package/dist/react/palette/CommandPalette.d.ts.map +1 -1
  282. package/dist/react/palette/CommandPalette.js +44 -8
  283. package/dist/react/palette/CommandPalette.js.map +1 -1
  284. package/dist/react/palette/commandSources.d.ts +57 -0
  285. package/dist/react/palette/commandSources.d.ts.map +1 -0
  286. package/dist/react/palette/commandSources.js +129 -0
  287. package/dist/react/palette/commandSources.js.map +1 -0
  288. package/dist/react/palette/index.d.ts +1 -0
  289. package/dist/react/palette/index.d.ts.map +1 -1
  290. package/dist/react/palette/index.js +1 -0
  291. package/dist/react/palette/index.js.map +1 -1
  292. package/dist/react/rich-text/RichTextEditor.js +1 -1
  293. package/dist/react/rich-text/RichTextEditor.js.map +1 -1
  294. package/dist/react/rich-text/RichTextToolbar.js +1 -1
  295. package/dist/react/rich-text/RichTextToolbar.js.map +1 -1
  296. package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
  297. package/dist/styles-areas/analytics.css +16 -16
  298. package/dist/styles-areas/auth.css +3 -3
  299. package/dist/styles-areas/calendar.css +6 -6
  300. package/dist/styles-areas/chart-table.css +1 -1
  301. package/dist/styles-areas/charts.css +3 -3
  302. package/dist/styles-areas/chat.css +7 -6
  303. package/dist/styles-areas/companion-link.css +2 -2
  304. package/dist/styles-areas/core.css +98 -89
  305. package/dist/styles-areas/dashboard-grid.css +4 -4
  306. package/dist/styles-areas/data-table.css +16 -16
  307. package/dist/styles-areas/disk-usage.css +5 -5
  308. package/dist/styles-areas/emoji.css +2 -2
  309. package/dist/styles-areas/fields.css +13 -14
  310. package/dist/styles-areas/filter-rail.css +4 -4
  311. package/dist/styles-areas/folder-tree.css +3 -3
  312. package/dist/styles-areas/markdown.css +2 -1
  313. package/dist/styles-areas/master-detail.css +3 -3
  314. package/dist/styles-areas/media-gallery.css +8 -8
  315. package/dist/styles-areas/media.css +12 -13
  316. package/dist/styles-areas/nav.css +28 -14
  317. package/dist/styles-areas/notifications.css +4 -4
  318. package/dist/styles-areas/overlays.css +4 -4
  319. package/dist/styles-areas/palette.css +4 -3
  320. package/dist/styles-areas/rich-text.css +3 -3
  321. package/dist/styles-areas/sharing.css +4 -3
  322. package/dist/styles-areas/stats.css +9 -8
  323. package/dist/styles-areas/test-report.css +3 -3
  324. package/dist/styles-areas/wizard.css +6 -5
  325. package/dist/styles-areas/workbench.css +10 -9
  326. package/dist/styles-areas/workbook-viewer.css +2 -2
  327. package/dist/styles-utilities.css +272 -187
  328. package/dist/testing/designAudit.d.ts.map +1 -1
  329. package/dist/testing/designAudit.js +4 -0
  330. package/dist/testing/designAudit.js.map +1 -1
  331. package/dist/testing/designBaseline.d.ts.map +1 -1
  332. package/dist/testing/designBaseline.js +4 -0
  333. package/dist/testing/designBaseline.js.map +1 -1
  334. package/dist/theme.css +103 -2
  335. package/package.json +3 -2
  336. package/scripts/guardrails-baseline.json +2 -1
  337. package/scripts/guardrailsEnforce.spec.ts +64 -0
  338. package/scripts/guardrailsEnforce.ts +65 -8
  339. package/scripts/styleAreas.ts +1 -0
  340. package/src/nodeTypesDeclared.spec.ts +47 -0
  341. package/src/react/Toast.spec.tsx +43 -0
  342. package/src/react/Toast.tsx +45 -2
  343. package/src/react/analytics/MetricDrilldown.tsx +1 -1
  344. package/src/react/charts/AreaChart.tsx +9 -8
  345. package/src/react/charts/BarChart.tsx +11 -10
  346. package/src/react/charts/ChartCard.tsx +146 -0
  347. package/src/react/charts/ChartFrame.tsx +28 -10
  348. package/src/react/charts/ChartLegend.tsx +1 -1
  349. package/src/react/charts/HeatmapChart.tsx +5 -5
  350. package/src/react/charts/IntervalTimeline.tsx +2 -2
  351. package/src/react/charts/LineChart.tsx +9 -8
  352. package/src/react/charts/TimeSeriesChart.tsx +11 -10
  353. package/src/react/charts/chartBox.spec.ts +40 -0
  354. package/src/react/charts/chartBox.ts +75 -0
  355. package/src/react/charts/index.ts +2 -0
  356. package/src/react/charts/shared.ts +16 -3
  357. package/src/react/chat/Bubble.tsx +5 -5
  358. package/src/react/chat/MessageScroller.tsx +1 -1
  359. package/src/react/components/ActionBar.spec.tsx +90 -0
  360. package/src/react/components/ActionBar.tsx +82 -2
  361. package/src/react/components/AddItemsMenu.tsx +1 -1
  362. package/src/react/components/Alert.spec.tsx +32 -2
  363. package/src/react/components/Alert.tsx +47 -3
  364. package/src/react/components/AppShell.spec.tsx +199 -13
  365. package/src/react/components/AppShell.tsx +235 -92
  366. package/src/react/components/AsyncPanel.spec.tsx +7 -1
  367. package/src/react/components/AsyncPanel.tsx +85 -5
  368. package/src/react/components/BannerLane.tsx +128 -0
  369. package/src/react/components/Button.tsx +2 -0
  370. package/src/react/components/ChipFilterStrip.tsx +1 -1
  371. package/src/react/components/ComboboxField.tsx +20 -3
  372. package/src/react/components/CommentThread.tsx +2 -2
  373. package/src/react/components/ConfirmDialog.tsx +1 -1
  374. package/src/react/components/DataTable/DataTable.tsx +80 -8
  375. package/src/react/components/DataTable/Pagination.tsx +1 -1
  376. package/src/react/components/DataTable/RowActions.tsx +55 -2
  377. package/src/react/components/DataTable/TableFilterBuilder.tsx +1 -1
  378. package/src/react/components/DataTable/cells/CodeCell.tsx +1 -1
  379. package/src/react/components/DataTable/cells/LinkCell.tsx +1 -1
  380. package/src/react/components/DataTable/index.ts +1 -0
  381. package/src/react/components/DataTable/numericColumn.spec.ts +19 -0
  382. package/src/react/components/DataTable/types.ts +36 -0
  383. package/src/react/components/DateTimeField.tsx +3 -1
  384. package/src/react/components/DeleteButton.tsx +3 -0
  385. package/src/react/components/Drawer.tsx +3 -3
  386. package/src/react/components/DropdownMenu.spec.tsx +55 -0
  387. package/src/react/components/DropdownMenu.tsx +73 -20
  388. package/src/react/components/EmptyState.tsx +26 -7
  389. package/src/react/components/EventTimeline.tsx +1 -1
  390. package/src/react/components/Field.width.spec.tsx +11 -0
  391. package/src/react/components/FileDropZone.tsx +1 -1
  392. package/src/react/components/Grid.spec.tsx +44 -0
  393. package/src/react/components/Grid.tsx +41 -11
  394. package/src/react/components/Guide.tsx +77 -0
  395. package/src/react/components/InfoHint.spec.tsx +21 -0
  396. package/src/react/components/InfoHintImpl.tsx +22 -1
  397. package/src/react/components/InlineEditableText.tsx +1 -1
  398. package/src/react/components/IsolatedFrame.tsx +1 -1
  399. package/src/react/components/ListToolbar.spec.tsx +12 -0
  400. package/src/react/components/ListToolbar.tsx +8 -5
  401. package/src/react/components/LogViewer.tsx +118 -18
  402. package/src/react/components/Modal.spec.tsx +17 -2
  403. package/src/react/components/Modal.tsx +25 -10
  404. package/src/react/components/MultiSelectField.spec.tsx +6 -0
  405. package/src/react/components/Page.spec.tsx +89 -5
  406. package/src/react/components/Page.tsx +100 -16
  407. package/src/react/components/PageGuideButton.tsx +42 -0
  408. package/src/react/components/PanelSeparator.tsx +1 -1
  409. package/src/react/components/Popover.tsx +3 -1
  410. package/src/react/components/PromptDialog.tsx +1 -1
  411. package/src/react/components/ScrollArea.spec.tsx +20 -0
  412. package/src/react/components/ScrollArea.tsx +19 -0
  413. package/src/react/components/Section.spec.tsx +82 -1
  414. package/src/react/components/Section.tsx +10 -4
  415. package/src/react/components/Skeleton.tsx +50 -2
  416. package/src/react/components/StatStrip.tsx +1 -1
  417. package/src/react/components/StatTile.tsx +84 -11
  418. package/src/react/components/StatTileGrid.spec.tsx +6 -1
  419. package/src/react/components/StatTileGrid.tsx +25 -0
  420. package/src/react/components/StatusPill.tsx +26 -1
  421. package/src/react/components/Surface.tsx +22 -14
  422. package/src/react/components/SwatchPicker.tsx +4 -0
  423. package/src/react/components/Switch.tsx +1 -1
  424. package/src/react/components/WidgetHeader.tsx +1 -1
  425. package/src/react/components/calendar/CalendarLegend.tsx +1 -1
  426. package/src/react/components/calendar/DayAgenda.tsx +1 -1
  427. package/src/react/components/calendar/EventChip.tsx +1 -1
  428. package/src/react/components/calendar/MonthGrid.tsx +1 -1
  429. package/src/react/components/calendar/WeekView.tsx +2 -2
  430. package/src/react/components/cardFrame.ts +28 -0
  431. package/src/react/components/dataDisplay.spec.tsx +176 -0
  432. package/src/react/components/dialog.tsx +4 -1
  433. package/src/react/dashboard-grid/WidgetFrame.tsx +2 -2
  434. package/src/react/emoji/EmojiPicker.tsx +1 -1
  435. package/src/react/file-tree/FileTree.spec.tsx +49 -0
  436. package/src/react/file-tree/FileTree.tsx +37 -4
  437. package/src/react/filter-rail/FilterRail.tsx +1 -1
  438. package/src/react/icons.tsx +1 -1
  439. package/src/react/index.ts +28 -3
  440. package/src/react/lib/menuSections.ts +33 -0
  441. package/src/react/markdown/Markdown.tsx +2 -2
  442. package/src/react/media/HoverScrubVideoThumb.tsx +2 -2
  443. package/src/react/media/MaskPainter.tsx +1 -1
  444. package/src/react/media/MediaUploadControl.tsx +1 -1
  445. package/src/react/media/VideoTrimmer.tsx +1 -1
  446. package/src/react/media/denseGallery.css +5 -1
  447. package/src/react/namespace/NamespaceTargetCapsule.tsx +1 -1
  448. package/src/react/nav/BreadcrumbTrail.tsx +2 -2
  449. package/src/react/nav/CommandBar.tsx +14 -3
  450. package/src/react/nav/PlaceBar.tsx +18 -8
  451. package/src/react/nav/SideNav.tsx +99 -17
  452. package/src/react/nav/TopBar.spec.tsx +78 -21
  453. package/src/react/nav/TopBar.tsx +270 -98
  454. package/src/react/nav/index.ts +8 -1
  455. package/src/react/nav/nav.spec.tsx +46 -2
  456. package/src/react/nav/navShared.tsx +23 -2
  457. package/src/react/notifications/NotificationFeed.tsx +2 -2
  458. package/src/react/palette/Command.tsx +1 -1
  459. package/src/react/palette/CommandPalette.spec.tsx +31 -0
  460. package/src/react/palette/CommandPalette.tsx +108 -11
  461. package/src/react/palette/commandSources.tsx +186 -0
  462. package/src/react/palette/index.ts +7 -0
  463. package/src/react/rich-text/RichTextEditor.tsx +1 -1
  464. package/src/react/rich-text/RichTextToolbar.tsx +1 -1
  465. package/src/styles-areas/analytics.css +16 -16
  466. package/src/styles-areas/auth.css +3 -3
  467. package/src/styles-areas/calendar.css +6 -6
  468. package/src/styles-areas/chart-table.css +1 -1
  469. package/src/styles-areas/charts.css +3 -3
  470. package/src/styles-areas/chat.css +7 -6
  471. package/src/styles-areas/companion-link.css +2 -2
  472. package/src/styles-areas/core.css +98 -89
  473. package/src/styles-areas/dashboard-grid.css +4 -4
  474. package/src/styles-areas/data-table.css +16 -16
  475. package/src/styles-areas/disk-usage.css +5 -5
  476. package/src/styles-areas/emoji.css +2 -2
  477. package/src/styles-areas/fields.css +13 -14
  478. package/src/styles-areas/filter-rail.css +4 -4
  479. package/src/styles-areas/folder-tree.css +3 -3
  480. package/src/styles-areas/markdown.css +2 -1
  481. package/src/styles-areas/master-detail.css +3 -3
  482. package/src/styles-areas/media-gallery.css +8 -8
  483. package/src/styles-areas/media.css +12 -13
  484. package/src/styles-areas/nav.css +28 -14
  485. package/src/styles-areas/notifications.css +4 -4
  486. package/src/styles-areas/overlays.css +4 -4
  487. package/src/styles-areas/palette.css +4 -3
  488. package/src/styles-areas/rich-text.css +3 -3
  489. package/src/styles-areas/sharing.css +4 -3
  490. package/src/styles-areas/stats.css +9 -8
  491. package/src/styles-areas/test-report.css +3 -3
  492. package/src/styles-areas/wizard.css +6 -5
  493. package/src/styles-areas/workbench.css +10 -9
  494. package/src/styles-areas/workbook-viewer.css +2 -2
  495. package/src/styles-utilities.css +272 -187
  496. package/src/testing/designAudit.ts +3 -0
  497. package/src/testing/designBaseline.ts +4 -0
  498. package/src/theme.css +103 -2
  499. package/src/themeDarkScopeAliases.spec.ts +65 -0
@@ -1,9 +1,12 @@
1
- import type { AnchorHTMLAttributes, ReactNode } from 'react';
1
+ import type { AnchorHTMLAttributes, ElementType, ReactNode } from 'react';
2
2
  import { ScrollArea } from '../components/ScrollArea.js';
3
3
  import { Tooltip } from '../components/Tooltip.js';
4
+ import { useMediaQuery } from '../hooks/useMediaQuery.js';
4
5
  import { useScrollEdgeFade } from '../hooks/useScrollEdgeFade.js';
5
6
  import { cn } from '../lib/cn.js';
7
+ import { devWarnOnce } from '../lib/devWarn.js';
6
8
  import { TAP_TARGET_SQUARE } from '../lib/tapTarget.js';
9
+ import { NAV_BADGE_TONE, type NavBadgeTone } from './navShared.js';
7
10
 
8
11
  /** What an `<a>` in the bar is handed — `href`, `onClick`, whatever the app's router needs. */
9
12
  export type TopBarLinkProps = Omit<
@@ -14,148 +17,317 @@ export type TopBarLinkProps = Omit<
14
17
  export interface TopBarItem {
15
18
  /** Stable key. */
16
19
  id: string;
17
- /** The place's name. It is the accessible name and the hover hint — it is NOT painted. */
20
+ /** The place's name — painted beside the icon from 768px up, and always its accessible name. */
18
21
  label: string;
19
- /** The glyph that IS the item. Required: an item without one cannot be drawn in this bar. */
22
+ /** The place's glyph. Alone below 768px (or in an `iconOnly` utility bar), beside the label above. */
20
23
  icon: ReactNode;
21
- /** The place the user is in. Lit, and `aria-current="page"`. */
24
+ /** The place the user is in. Marked three ways, and `aria-current="page"`. */
22
25
  active?: boolean;
23
26
  /** Where it goes. A real link, so it still copies and middle-clicks. */
24
27
  href: string;
25
28
  /** `onClick` and whatever else the app's router needs on the `<a>`. */
26
29
  linkProps?: TopBarLinkProps;
27
30
  /**
28
- * 🔴 The purposefully made exception: paint `label` beside the icon. Takes the REASON,
29
- * not a boolean — the owner's rule (2026-09-25) is icons, not words, "unless there is a
30
- * purposefully made exception", and a `showLabel` flag is an exception nobody had to make.
31
- * Even then the words drop below `sm`, because a phone is where they overflowed.
31
+ * A count beside the label (unread, waiting), on the icon's corner where the icon stands alone.
32
+ * Clamped to 99+, and read as part of the place's name (`Duplicates · 7`).
32
33
  */
33
- wordsBecause?: string;
34
+ badge?: number | string;
35
+ /** How the count reads — `alert` (default, red) or a plain fact. */
36
+ badgeTone?: NavBadgeTone;
37
+ /**
38
+ * The place exists but cannot work on THIS host: it stays visible, cannot be followed, and
39
+ * says why on hover — the owner's one-UI rule, never a hidden item. The reason, in a sentence.
40
+ */
41
+ unavailable?: string;
34
42
  }
35
43
 
36
44
  export interface TopBarProps {
37
45
  /** The app's mark. Its `label` is painted from `md` up and is the accessible name below it. */
38
46
  brand: { icon: ReactNode; label: string; href: string; linkProps?: TopBarLinkProps };
39
- /** The places. Icons only — see {@link TopBarItem.wordsBecause} for the one way out. */
47
+ /** The places — 2 to 5 of them (NAV-3). Six or more belong in `AppShell`'s sectioned `SideNav`. */
40
48
  items: readonly TopBarItem[];
41
- /** The trailing cluster: theme toggle, account menu. Never shrinks. */
49
+ /** The right zone: ⌘K, alerts, theme toggle, account. Locked — it never shrinks (NAV-1). */
42
50
  trailing?: ReactNode;
43
51
  /**
44
52
  * One control BEFORE the brand — a phone's way into the app's sidebar drawer (the owner's
45
53
  * music report, 2026-09-28: *"there is a large horizontal scrolling header row and no sidebar
46
- * popout"*). Top-left is where Gmail, Linear and Slack put it on a phone, so a reader finds it
47
- * without being told. Never shrinks. Render it only where there is a sidebar to open.
54
+ * popout"*). Top-left is where Gmail, Linear and Slack put it on a phone. Never shrinks.
48
55
  */
49
56
  leading?: ReactNode;
57
+ /**
58
+ * The center zone's content when it is NOT the places — a global search, a command trigger.
59
+ * Given together with `items`, the places render first and this follows them.
60
+ */
61
+ center?: ReactNode;
62
+ /**
63
+ * The header height (NAV-1): `roomy` 64px for media and consumer apps, `dense` 48px for a
64
+ * tool (station, desk, binary-server). Unset, it reads `--header-h`, which a `.cb-dense`
65
+ * scope already sets to 48px.
66
+ */
67
+ density?: 'roomy' | 'dense';
68
+ /**
69
+ * A UTILITY bar, not the app's router: icons only at every width, each with its tooltip.
70
+ * NAV-2 allows icons alone only in a utility cluster — a row of window controls, a
71
+ * viewer's tools — so this is the mode for those, never for an app's places.
72
+ */
73
+ iconOnly?: boolean;
74
+ /**
75
+ * Below 768px: `'icons'` (default) keeps the places as an icon strip in the header; `'none'`
76
+ * drops them, for a shell whose bottom `PlaceBar` already carries every place (NAV-14).
77
+ */
78
+ phonePlaces?: 'icons' | 'none';
79
+ /** SPA link component (e.g. react-router `Link`) — receives `to` and `href`. Default `<a>`. */
80
+ linkComponent?: ElementType;
50
81
  /** The nav landmark's name. Default `'Places'`. */
51
82
  navLabel?: string;
52
83
  className?: string;
53
84
  }
54
85
 
55
86
  /**
56
- * THE top bar: brand, places, trailing controls — one row that can never be wider than the
57
- * screen.
87
+ * THE top bar: three zones on one midline — brand, places, utilities — in a row that can
88
+ * never be wider than the screen.
58
89
  *
59
- * ── Why it exists (owner, 2026-09-25) ───────────────────────────────────────────────────
60
- * *"the music app header on iphone goes off the right side of the screen. It should use
61
- * icons instead of words and it should not be allowed to make the screen have a wide
62
- * scrollbar … cb should enforce this via a top bar component so each app cannot do it wrong
63
- * with separately made components."* Every app had hand-rolled its own `<header><nav>`, and
64
- * music's — "♪ Music · Library · Visualize · YouTube · Add music · Downloads" plus a theme
65
- * toggle and an avatar — was ~560px of words on a 390px phone, so the whole page scrolled
66
- * sideways.
67
- *
68
- * Two properties are built in rather than asked for:
69
- * · **Icons, not words.** An item cannot be declared without an icon, and its label is
70
- * the accessible name + hover hint, never painted — unless `wordsBecause` says why.
71
- * · **It cannot widen the page.** The row is `min-w-0 max-w-full`; the places take the
72
- * remaining width and, if a phone is narrower still, scroll INSIDE their own strip.
73
- * Brand and trailing controls never shrink, so the account menu is always reachable.
90
+ * ── Why it is shaped like this ─────────────────────────────────────────────────────────
91
+ * It was born (owner, 2026-09-25) from music's header running off a 390px phone, and the
92
+ * fix then was *"icons instead of words"*. The owner's 2026-10-01 review reversed the words
93
+ * half: thirteen naked icons is mystery-meat navigation (`docs/design/README.md`, "Settled
94
+ * conflicts" #1). So now (`docs/design/shell-and-navigation.md`):
95
+ * · **Destinations have words** (NAV-2) — icon plus label from 768px up. Icons alone only
96
+ * below 768px, or in an `iconOnly` utility bar.
97
+ * · **Three zones, one height** (NAV-1) — `--header-h` (64/48px), every item on one
98
+ * midline, a 1px bottom border and no shadow. Left and right are locked; when space
99
+ * runs out the center gives way, scrolling INSIDE its own strip, so the page never
100
+ * scrolls sideways and the account menu is always reachable.
101
+ * · **The active place is unmistakable** (NAV-6) — a 3px accent bar on the header-side
102
+ * edge, a tinted background and foreground (not muted) text. Never color alone.
74
103
  *
75
104
  * `guardrails no-raw-nav` is the other half: a hand-rolled `<nav>` in app source fails the
76
105
  * app's gate, so the next header is this one.
77
106
  */
78
- export function TopBar({ brand, items, trailing, leading, navLabel = 'Places', className }: TopBarProps) {
79
- const fade = useScrollEdgeFade();
107
+ export function TopBar({
108
+ brand,
109
+ items,
110
+ trailing,
111
+ leading,
112
+ center,
113
+ density,
114
+ iconOnly = false,
115
+ phonePlaces = 'icons',
116
+ linkComponent,
117
+ navLabel = 'Places',
118
+ className,
119
+ }: TopBarProps) {
120
+ if (!iconOnly && items.length > 5) {
121
+ devWarnOnce(
122
+ `TopBar:too-many:${brand.label}`,
123
+ `cursedbelt: <TopBar> "${brand.label}" puts ${items.length} places in the header. NAV-3 caps a top bar at 5 — ` +
124
+ 'pass the places to AppShell as a NavigationModel with sections, and it renders a SideNav.',
125
+ );
126
+ }
127
+ const hasPlaces = items.length > 0;
80
128
  return (
81
129
  <header
82
130
  data-cb-topbar=''
83
- className={cn('flex w-full min-w-0 max-w-full items-center gap-2 px-3 py-1.5 md:gap-3 md:px-6', className)}
131
+ data-cb-density={density}
132
+ className={cn(
133
+ 'flex w-full min-w-0 max-w-full shrink-0 items-center gap-2 border-b border-border px-3 md:gap-4 md:px-6',
134
+ density === 'dense'
135
+ ? 'h-[var(--header-h-dense)]'
136
+ : density === 'roomy'
137
+ ? 'h-[var(--header-h-roomy)]'
138
+ : 'h-[var(--header-h)]',
139
+ className,
140
+ )}
84
141
  >
85
- {leading ? <div className='flex shrink-0 items-center'>{leading}</div> : null}
86
- <a
87
- {...brand.linkProps}
88
- href={brand.href}
89
- aria-label={brand.label}
90
- data-cb-topbar-brand=''
91
- className={cn(
92
- 'flex shrink-0 items-center gap-2 rounded-md px-1.5 py-1 font-semibold text-foreground',
93
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
94
- TAP_TARGET_SQUARE,
95
- 'md:pointer-coarse:w-auto',
96
- )}
97
- >
98
- <span aria-hidden='true' className='grid size-6 place-items-center [&_svg]:size-5'>
99
- {brand.icon}
100
- </span>
101
- <span aria-hidden='true' className='hidden md:inline'>
102
- {brand.label}
103
- </span>
104
- </a>
105
-
106
- <nav aria-label={navLabel} data-cb-topbar-nav='' className='min-w-0 flex-1' style={fade.maskStyle}>
107
- {/* The places scroll INSIDE their own strip, so the page never does — with the
108
- belt's one edge-fade cue saying there is more, as TagStrip and DataTable do. */}
109
- <ScrollArea
110
- axis='x'
111
- space='none'
112
- safeArea={false}
113
- scrollRef={fade.ref}
114
- onScroll={fade.onScroll}
115
- className='w-full [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
142
+ {/* ── Left zone: locked ─────────────────────────────────────────────── */}
143
+ <div data-cb-topbar-zone='left' className='flex shrink-0 items-center gap-2'>
144
+ {leading ? <div className='flex shrink-0 items-center'>{leading}</div> : null}
145
+ <a
146
+ {...brand.linkProps}
147
+ href={brand.href}
148
+ aria-label={brand.label}
149
+ data-cb-topbar-brand=''
150
+ className={cn(
151
+ 'flex shrink-0 items-center gap-2 rounded-md px-1.5 py-1 font-semibold text-foreground',
152
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
153
+ TAP_TARGET_SQUARE,
154
+ 'md:pointer-coarse:w-auto',
155
+ )}
116
156
  >
117
- <div className='flex w-max items-center gap-1'>
118
- {items.map((item) => (
119
- <TopBarLink key={item.id} item={item} />
120
- ))}
121
- </div>
122
- </ScrollArea>
123
- </nav>
157
+ <span aria-hidden='true' className='grid size-6 place-items-center [&_svg]:size-5'>
158
+ {brand.icon}
159
+ </span>
160
+ <span aria-hidden='true' className='hidden md:inline'>
161
+ {brand.label}
162
+ </span>
163
+ </a>
164
+ </div>
124
165
 
125
- {trailing ? <div className='flex shrink-0 items-center gap-1'>{trailing}</div> : null}
166
+ {/* ── Center zone: fluid, gives way first ───────────────────────────── */}
167
+ <div data-cb-topbar-zone='center' className='flex h-full min-w-0 flex-1 items-center justify-center gap-3'>
168
+ {hasPlaces ? (
169
+ <TopBarPlaces
170
+ items={items}
171
+ iconOnly={iconOnly}
172
+ phonePlaces={phonePlaces}
173
+ linkComponent={linkComponent}
174
+ navLabel={navLabel}
175
+ />
176
+ ) : null}
177
+ {center ? <div className='flex min-w-0 flex-1 items-center justify-center'>{center}</div> : null}
178
+ </div>
179
+
180
+ {/* ── Right zone: locked, never shrinks ─────────────────────────────── */}
181
+ {trailing ? (
182
+ <div data-cb-topbar-zone='right' className='flex shrink-0 items-center gap-1'>
183
+ {trailing}
184
+ </div>
185
+ ) : null}
126
186
  </header>
127
187
  );
128
188
  }
129
189
 
130
- function TopBarLink({ item }: { item: TopBarItem }) {
131
- const words = Boolean(item.wordsBecause?.trim());
190
+ /** NAV-2's line: from here up a router's places show their words. */
191
+ const WIDE_QUERY = '(min-width: 768px)';
192
+
193
+ export interface TopBarPlacesProps
194
+ extends Pick<TopBarProps, 'items' | 'iconOnly' | 'phonePlaces' | 'linkComponent' | 'navLabel'> {}
195
+
196
+ /**
197
+ * The center zone's places on their own — `TopBar` draws them, and so does `AppShell`'s
198
+ * header for a 2–5-place app (NAV-3). One strip, so the two can never drift.
199
+ */
200
+ export function TopBarPlaces({
201
+ items,
202
+ iconOnly = false,
203
+ phonePlaces = 'icons',
204
+ linkComponent,
205
+ navLabel = 'Places',
206
+ }: TopBarPlacesProps) {
207
+ const fade = useScrollEdgeFade();
208
+ const wide = useMediaQuery(WIDE_QUERY);
132
209
  return (
133
- <Tooltip content={item.label} side='bottom'>
134
- <a
135
- {...item.linkProps}
136
- href={item.href}
137
- aria-label={item.label}
138
- aria-current={item.active ? 'page' : undefined}
139
- data-active={item.active ? '' : undefined}
140
- className={cn(
141
- 'relative flex h-9 min-w-9 shrink-0 items-center justify-center gap-2 rounded-md px-2 transition-colors',
142
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
143
- TAP_TARGET_SQUARE,
144
- words && 'sm:pointer-coarse:w-auto',
145
- item.active
146
- ? 'bg-muted text-foreground'
147
- : 'text-muted-foreground hover:bg-muted hover:text-foreground',
148
- )}
210
+ <nav
211
+ aria-label={navLabel}
212
+ data-cb-topbar-nav=''
213
+ // The design audit's NAV-2 check reads `data-cb-primary-nav`: a router must show its
214
+ // words at ≥768px. A utility bar is not a router, so it is marked as one instead.
215
+ data-cb-primary-nav={iconOnly ? undefined : ''}
216
+ data-cb-nav-utility={iconOnly ? '' : undefined}
217
+ className={cn('h-full min-w-0', phonePlaces === 'none' && !iconOnly && 'max-md:hidden')}
218
+ style={fade.maskStyle}
219
+ >
220
+ {/* The places scroll INSIDE their own strip, so the page never does — with the
221
+ belt's one edge-fade cue saying there is more, as TagStrip and DataTable do. */}
222
+ <ScrollArea
223
+ axis='x'
224
+ space='none'
225
+ safeArea={false}
226
+ scrollRef={fade.ref}
227
+ onScroll={fade.onScroll}
228
+ className='h-full w-full [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
149
229
  >
150
- <span aria-hidden='true' className='grid size-5 place-items-center [&_svg]:size-5'>
151
- {item.icon}
230
+ <div className='flex h-full w-max items-center gap-1'>
231
+ {items.map((item) => (
232
+ <TopBarLink
233
+ key={item.id}
234
+ item={item}
235
+ hint={iconOnly || !wide}
236
+ iconOnly={iconOnly}
237
+ linkComponent={linkComponent}
238
+ />
239
+ ))}
240
+ </div>
241
+ </ScrollArea>
242
+ </nav>
243
+ );
244
+ }
245
+
246
+ function TopBarLink({
247
+ item,
248
+ hint,
249
+ iconOnly,
250
+ linkComponent,
251
+ }: {
252
+ item: TopBarItem;
253
+ /** The glyph stands alone, so it carries a hover hint naming it. */
254
+ hint: boolean;
255
+ iconOnly: boolean;
256
+ linkComponent?: ElementType;
257
+ }) {
258
+ const Link: ElementType = item.unavailable ? 'span' : (linkComponent ?? 'a');
259
+ const showBadge = item.badge != null && item.badge !== 0;
260
+ const badgeText = typeof item.badge === 'number' && item.badge > 99 ? '99+' : item.badge;
261
+ const target = item.unavailable
262
+ ? // HTML has no disabled anchor: a span that still announces as the link it is.
263
+ { role: 'link', 'aria-disabled': 'true', tabIndex: 0 }
264
+ : { ...item.linkProps, ...(linkComponent ? { to: item.href } : {}), href: item.href };
265
+ const link = (
266
+ <Link
267
+ {...target}
268
+ // The count is part of the name, so a screen reader hears what the eye sees.
269
+ aria-label={showBadge ? `${item.label} · ${badgeText}` : item.label}
270
+ aria-current={item.active ? 'page' : undefined}
271
+ data-active={item.active ? '' : undefined}
272
+ data-cb-nav-item=''
273
+ className={cn(
274
+ 'relative flex h-9 min-w-9 shrink-0 items-center justify-center gap-2 rounded-md px-2 transition-colors',
275
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
276
+ TAP_TARGET_SQUARE,
277
+ !iconOnly && 'md:px-3 md:pointer-coarse:w-auto',
278
+ item.unavailable && 'opacity-55',
279
+ item.active
280
+ ? 'bg-accent/10 text-foreground'
281
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground',
282
+ )}
283
+ >
284
+ {/* NAV-6's leading edge for a horizontal bar is the one facing the page: the bottom. */}
285
+ {item.active ? (
286
+ <span
287
+ aria-hidden='true'
288
+ data-cb-active-indicator=''
289
+ className='absolute inset-x-2 bottom-0 h-[var(--indicator-w)] rounded-full bg-accent-solid'
290
+ />
291
+ ) : null}
292
+ <span aria-hidden='true' className='grid size-5 place-items-center [&_svg]:size-5'>
293
+ {item.icon}
294
+ </span>
295
+ {iconOnly ? null : (
296
+ <span aria-hidden='true' className='hidden whitespace-nowrap text-sm font-medium md:inline'>
297
+ {item.label}
152
298
  </span>
153
- {words ? (
154
- <span aria-hidden='true' className='hidden text-sm font-medium sm:inline'>
155
- {item.label}
156
- </span>
157
- ) : null}
158
- </a>
299
+ )}
300
+ {showBadge ? (
301
+ <span
302
+ aria-hidden='true'
303
+ data-cb-nav-badge=''
304
+ className={cn(
305
+ 'rounded-full px-1.5 py-0.5 text-xs font-bold leading-none',
306
+ NAV_BADGE_TONE[item.badgeTone ?? 'alert'],
307
+ // Where the glyph stands alone the count rides on its corner — a pill beside it
308
+ // would crowd the strip, and hiding it would hide the one figure the reader goes by.
309
+ iconOnly ? 'absolute -top-0.5 -right-0.5' : 'max-md:absolute max-md:-top-0.5 max-md:-right-0.5',
310
+ )}
311
+ >
312
+ {badgeText}
313
+ </span>
314
+ ) : null}
315
+ </Link>
316
+ );
317
+ if (item.unavailable) {
318
+ return (
319
+ <Tooltip content={`${item.label} — not available here. ${item.unavailable}`} side='bottom' multiline>
320
+ {link}
321
+ </Tooltip>
322
+ );
323
+ }
324
+ // The hover hint is only for a glyph standing alone: in a utility bar always, and below
325
+ // 768px in a router. Above it the word is painted, and a tooltip repeating it is noise.
326
+ return hint ? (
327
+ <Tooltip content={item.label} side='bottom'>
328
+ {link}
159
329
  </Tooltip>
330
+ ) : (
331
+ link
160
332
  );
161
333
  }
@@ -71,4 +71,11 @@ export {
71
71
  searchOptionId,
72
72
  } from './SearchResults.js';
73
73
  export { SideNav, type SideNavProps } from './SideNav.js';
74
- export { TopBar, type TopBarItem, type TopBarLinkProps, type TopBarProps } from './TopBar.js';
74
+ export {
75
+ TopBar,
76
+ type TopBarItem,
77
+ type TopBarLinkProps,
78
+ TopBarPlaces,
79
+ type TopBarPlacesProps,
80
+ type TopBarProps,
81
+ } from './TopBar.js';
@@ -67,10 +67,54 @@ describe('SideNav', () => {
67
67
  const { container } = render(
68
68
  <SideNav model={model} activeHref='/games/fractal' onNavigate={() => {}} />,
69
69
  );
70
- const rows = qa(container, '[data-cb-sidenav] a');
70
+ const rows = qa(container, '[data-cb-sidenav] a').filter((a) => !a.closest('[data-cb-sidenav-children]'));
71
71
  expect(rows.map((r) => text(r))).toEqual(['Today', 'Play', 'Tools', 'Admin']);
72
+ // /games/fractal lives within /games, and IS its Fractal item — the item is the page.
72
73
  const active = qa(container, '[data-cb-sidenav] a[aria-current="page"]');
73
- expect(active.map((r) => text(r))).toEqual(['Play']); // /games/fractal lives within /games
74
+ expect(active.map((r) => text(r))).toEqual(['Fractal']);
75
+ });
76
+
77
+ test('NAV-7: the active place shows its items beneath it, each label indented under the parent label', () => {
78
+ const { container } = render(<SideNav model={model} activeHref='/games' onNavigate={() => {}} />);
79
+ const children = qa(container, '[data-cb-sidenav-children] a');
80
+ expect(children.map(text)).toEqual(['Fractal', 'Comet']);
81
+ // The parent row is px-3 + a 20px icon + gap-3 = 44px to its label; the child pads 44px.
82
+ const parent = qa(container, '[data-cb-sidenav] a').find((a) => text(a) === 'Play') as HTMLElement;
83
+ expect(parent.className).toContain('px-3');
84
+ expect(parent.className).toContain('gap-3');
85
+ for (const child of children) expect(child.className).toContain('pl-11');
86
+ // Only the active place expands; the icon rail never does.
87
+ cleanup();
88
+ const rail = render(<SideNav model={model} activeHref='/games' collapsed onNavigate={() => {}} />);
89
+ expect(qa(rail.container, '[data-cb-sidenav-children]')).toHaveLength(0);
90
+ cleanup();
91
+ const off = render(<SideNav model={model} activeHref='/games' expandActive={false} onNavigate={() => {}} />);
92
+ expect(qa(off.container, '[data-cb-sidenav-children]')).toHaveLength(0);
93
+ });
94
+
95
+ test('NAV-13: the footer is 12px muted under a rule — and the 64px icon rail leaves it out', () => {
96
+ const { container } = render(<SideNav model={model} footer={<span>v1.2.3</span>} onNavigate={() => {}} />);
97
+ const footer = container.querySelector('[data-cb-sidenav-footer]') as HTMLElement;
98
+ expect(footer.textContent).toBe('v1.2.3');
99
+ expect(footer.className).toContain('text-xs');
100
+ expect(footer.className).toContain('text-muted-foreground');
101
+ expect(footer.className).toContain('border-t');
102
+ cleanup();
103
+ const rail = render(<SideNav model={model} collapsed footer={<span>v1.2.3</span>} onNavigate={() => {}} />);
104
+ expect(rail.container.querySelector('[data-cb-sidenav-footer]')).toBeNull();
105
+ });
106
+
107
+ test('NAV-6: the current place is marked by a bar, a tint and a foreground label — never color alone', () => {
108
+ const { container } = render(<SideNav model={model} activeHref='/tools' onNavigate={() => {}} />);
109
+ const links = qa(container, '[data-cb-sidenav] a');
110
+ const active = links.find((a) => text(a) === 'Tools') as HTMLElement;
111
+ expect(active.getAttribute('aria-current')).toBe('page');
112
+ expect(active.querySelector('[data-cb-active-indicator]')?.className).toContain('w-[var(--indicator-w)]');
113
+ expect(active.className).toContain('bg-accent/10');
114
+ expect(active.className).toContain('text-foreground');
115
+ const idle = links.find((a) => text(a) === 'Today') as HTMLElement;
116
+ expect(idle.querySelector('[data-cb-active-indicator]')).toBeNull();
117
+ expect(idle.className).toContain('text-muted-foreground');
74
118
  });
75
119
 
76
120
  test('a role gate hides the gated domain', () => {
@@ -7,8 +7,8 @@
7
7
  // nav rendered zero real anchors, so ctrl/cmd/middle-click "open in new tab"
8
8
  // and link-aware a11y/crawling tooling saw an empty nav).
9
9
 
10
- import type { CSSProperties, ElementType, ReactNode } from 'react';
11
- import { normalizeNavPath } from 'cursedbelt-core/navigation';
10
+ import type { CSSProperties, ElementType, MouseEvent, ReactNode } from 'react';
11
+ import { type NavDomain, normalizeNavPath } from 'cursedbelt-core/navigation';
12
12
  import { Tooltip } from '../components/Tooltip.js';
13
13
 
14
14
  /** Maps a model icon *name* to a node. Apps wire their icon set (e.g. lucide). */
@@ -165,6 +165,27 @@ export function isPathWithin(path: string, activeHref: string | undefined): bool
165
165
  return a === p || a.startsWith(`${p}/`);
166
166
  }
167
167
 
168
+ /** A place is active when the route is its landing or lives within one of its items. */
169
+ export function isDomainActive(domain: NavDomain, activeHref: string | undefined): boolean {
170
+ if (isPathWithin(domain.path, activeHref)) return true;
171
+ return domain.groups.some((g) => g.items.some((it) => isPathWithin(it.path, activeHref)));
172
+ }
173
+
174
+ /**
175
+ * The click a plain `<a href>` needs to route client-side through `onNavigate` — the same
176
+ * rule as {@link NavLink}: a plain left-click is intercepted, a modifier or middle click is
177
+ * left to the browser so "open in new tab" still works.
178
+ */
179
+ export function routeClick(path: string, onNavigate: ((path: string) => void) | undefined) {
180
+ return (event: MouseEvent<HTMLAnchorElement>) => {
181
+ if (!onNavigate) return;
182
+ if (event.defaultPrevented || event.button !== 0) return;
183
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
184
+ event.preventDefault();
185
+ onNavigate(path);
186
+ };
187
+ }
188
+
168
189
  /** Resolve a model icon name to a node via the app's resolver (or nothing). */
169
190
  export function renderIcon(
170
191
  resolveIcon: IconResolver | undefined,
@@ -201,7 +201,7 @@ export function NotificationFeed({
201
201
  return (
202
202
  <div className={cn('flex flex-col', className)}>
203
203
  {/* Header */}
204
- <div className='flex items-center justify-between border-b border-border px-4 py-2.5'>
204
+ <div className='flex items-center justify-between border-b border-border px-4 py-2'>
205
205
  <span className='text-sm font-semibold text-foreground'>
206
206
  Notifications
207
207
  {unreadCount > 0 && (
@@ -283,7 +283,7 @@ export function NotificationFeed({
283
283
 
284
284
  function GroupLabel({ label }: { label: string }) {
285
285
  return (
286
- <p className='cb-meta-label px-4 pb-1 pt-2.5'>{label}</p>
286
+ <p className='cb-meta-label px-4 pb-1 pt-3'>{label}</p>
287
287
  );
288
288
  }
289
289
 
@@ -67,7 +67,7 @@ export function CommandInput({ className, trailing, ...props }: CommandInputProp
67
67
  /** The scrolling results region (keyboard-safe via `ScrollArea`), capped at 60dvh. */
68
68
  export function CommandList({
69
69
  className,
70
- maxHeightClassName = 'max-h-[60dvh]',
70
+ maxHeightClassName = 'max-h-[var(--palette-max-h)]',
71
71
  ...props
72
72
  }: ComponentPropsWithoutRef<typeof CmdkList> & { maxHeightClassName?: string }) {
73
73
  return (
@@ -4,6 +4,7 @@ import type { GateFn, NavGate, NavigationModel } from 'cursedbelt-core/navigatio
4
4
  import { shortcutRegistry } from 'cursedbelt-core/palette/shortcut-registry';
5
5
  import type { SearchResultGroup } from '../nav/SearchResults.js';
6
6
  import { CommandPalette } from './CommandPalette.js';
7
+ import { PALETTE_RECENT_STORAGE_KEY } from './commandSources.js';
7
8
  import { useCommandPalette } from './useCommandPalette.js';
8
9
 
9
10
  afterEach(() => {
@@ -369,3 +370,33 @@ describe('registered shortcuts', () => {
369
370
  expect(onShortcut).toHaveBeenCalledTimes(1);
370
371
  });
371
372
  });
373
+
374
+ describe('contributed sources — recent items first (OVL-8)', () => {
375
+ test('an item run from the palette leads its group the next time it opens cold', () => {
376
+ localStorage.removeItem(PALETTE_RECENT_STORAGE_KEY);
377
+ const sources = [
378
+ {
379
+ group: 'Requests',
380
+ items: [
381
+ { id: 'a', label: 'Alpha request', onSelect: () => {} },
382
+ { id: 'b', label: 'Beta request', onSelect: () => {} },
383
+ { id: 'c', label: 'Gamma request', onSelect: () => {} },
384
+ ],
385
+ },
386
+ ];
387
+ const labels = () =>
388
+ Array.from(document.querySelectorAll('[cmdk-item]'))
389
+ .map((el) => el.textContent ?? '')
390
+ .filter((t) => t.endsWith('request'));
391
+ const view = (open: boolean) => (
392
+ <CommandPalette model={model} onNavigate={() => {}} shortcuts={[]} sources={sources} open={open} onOpenChange={() => {}} />
393
+ );
394
+ const { rerender } = render(view(true));
395
+ expect(labels()).toEqual(['Alpha request', 'Beta request', 'Gamma request']);
396
+ fireEvent.click(screen.getByText('Gamma request'));
397
+ rerender(view(false));
398
+ rerender(view(true));
399
+ expect(labels()).toEqual(['Gamma request', 'Alpha request', 'Beta request']);
400
+ localStorage.removeItem(PALETTE_RECENT_STORAGE_KEY);
401
+ });
402
+ });