@crewlethq/ui 0.2.0 → 0.3.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 (497) hide show
  1. package/README.md +1714 -14
  2. package/dist/Accordion/Accordion.d.ts +54 -0
  3. package/dist/Accordion/index.d.ts +4 -0
  4. package/dist/Accordion/index.js +6 -0
  5. package/dist/Accordion-ZI2WLD5C.css +87 -0
  6. package/dist/AddPill/AddPill.d.ts +114 -0
  7. package/dist/AddPill/index.d.ts +3 -0
  8. package/dist/AddPill/index.js +3 -0
  9. package/dist/AddPill-DFWXYLIQ.css +434 -0
  10. package/dist/Announcer/Announcer.d.ts +15 -0
  11. package/dist/Announcer/index.d.ts +2 -0
  12. package/dist/Announcer/index.js +3 -0
  13. package/dist/AppShell/AppShell.d.ts +193 -0
  14. package/dist/AppShell/index.d.ts +3 -0
  15. package/dist/AppShell/index.js +6 -0
  16. package/dist/AppShell-NVE6LHNI.css +395 -0
  17. package/dist/Avatar/Avatar.d.ts +104 -0
  18. package/dist/Avatar/index.d.ts +3 -0
  19. package/dist/Avatar/index.js +2 -0
  20. package/dist/Avatar-P2KRQM2B.css +156 -0
  21. package/dist/BrandLockup/BrandLockup.d.ts +41 -0
  22. package/dist/BrandLockup/index.d.ts +3 -0
  23. package/dist/BrandLockup/index.js +2 -0
  24. package/dist/BrandLockup-6ISDEHPJ.css +108 -0
  25. package/dist/Button/Button.d.ts +88 -0
  26. package/dist/Button/index.d.ts +3 -0
  27. package/dist/Button/index.js +3 -0
  28. package/dist/Button-D4J6B4MA.css +302 -0
  29. package/dist/CalendarGrid/CalendarGrid.d.ts +87 -0
  30. package/dist/CalendarGrid/index.d.ts +3 -0
  31. package/dist/CalendarGrid/index.js +2 -0
  32. package/dist/CalendarGrid-V6TQ5QKS.css +128 -0
  33. package/dist/Callout/Callout.d.ts +47 -0
  34. package/dist/Callout/index.d.ts +3 -0
  35. package/dist/Callout/index.js +2 -0
  36. package/dist/Callout-S7QDWHDL.css +111 -0
  37. package/dist/Canvas/Canvas.d.ts +234 -0
  38. package/dist/Canvas/geometry.d.ts +249 -0
  39. package/dist/Canvas/index.d.ts +5 -0
  40. package/dist/Canvas/index.js +8 -0
  41. package/dist/Canvas/layerNode.d.ts +20 -0
  42. package/dist/Canvas-Q6JETCWR.css +315 -0
  43. package/dist/Card/Card.d.ts +142 -0
  44. package/dist/Card/headerSlot.d.ts +65 -0
  45. package/dist/Card/index.d.ts +5 -0
  46. package/dist/Card/index.js +6 -0
  47. package/dist/Card-AHX3Q2JD.css +428 -0
  48. package/dist/Charts/ActivityStrip.d.ts +45 -0
  49. package/dist/Charts/BarList.d.ts +51 -0
  50. package/dist/Charts/Legend.d.ts +32 -0
  51. package/dist/Charts/StackedBar.d.ts +38 -0
  52. package/dist/Charts/TimeSeries.d.ts +92 -0
  53. package/dist/Charts/dataColor.d.ts +20 -0
  54. package/dist/Charts/index.d.ts +12 -0
  55. package/dist/Charts/index.js +3 -0
  56. package/dist/Charts-D7G2BSXH.css +277 -0
  57. package/dist/Checkbox/Checkbox.d.ts +45 -0
  58. package/dist/Checkbox/index.d.ts +3 -0
  59. package/dist/Checkbox/index.js +2 -0
  60. package/dist/Checkbox-VGW26YLQ.css +111 -0
  61. package/dist/CodeBlock/CodeBlock.d.ts +60 -0
  62. package/dist/CodeBlock/index.d.ts +3 -0
  63. package/dist/CodeBlock/index.js +7 -0
  64. package/dist/CodeBlock-U2W6NNVC.css +153 -0
  65. package/dist/Combobox/Combobox.d.ts +89 -0
  66. package/dist/Combobox/index.d.ts +3 -0
  67. package/dist/Combobox/index.js +8 -0
  68. package/dist/Combobox-4FUVYIE7.css +80 -0
  69. package/dist/CommandPalette/CommandPalette.d.ts +77 -0
  70. package/dist/CommandPalette/index.d.ts +3 -0
  71. package/dist/CommandPalette/index.js +11 -0
  72. package/dist/CommandPalette-CSJDUCQA.css +115 -0
  73. package/dist/Container/Container.d.ts +17 -0
  74. package/dist/Container/index.d.ts +3 -0
  75. package/dist/Container/index.js +2 -0
  76. package/dist/Container-3TSKMGNI.css +52 -0
  77. package/dist/Copyable/CopyButton.d.ts +54 -0
  78. package/dist/Copyable/Copyable.d.ts +34 -0
  79. package/dist/Copyable/index.d.ts +5 -0
  80. package/dist/Copyable/index.js +6 -0
  81. package/dist/Copyable-5PNRFOLS.css +125 -0
  82. package/dist/Count/Count.d.ts +24 -0
  83. package/dist/Count/index.d.ts +3 -0
  84. package/dist/Count/index.js +3 -0
  85. package/dist/Count-ZVCJ6VPD.css +20 -0
  86. package/dist/DataTable/CopyableCell.d.ts +12 -0
  87. package/dist/DataTable/DataTable.d.ts +615 -0
  88. package/dist/DataTable/RowActionsMenu.d.ts +7 -0
  89. package/dist/DataTable/index.d.ts +7 -0
  90. package/dist/DataTable/index.js +14 -0
  91. package/dist/DataTable-ITNACQ2X.css +2020 -0
  92. package/dist/DataView/DataView.d.ts +109 -0
  93. package/dist/DataView/DataViewToolbar.d.ts +87 -0
  94. package/dist/DataView/FilterAxisBar.d.ts +103 -0
  95. package/dist/DataView/columns.d.ts +35 -0
  96. package/dist/DataView/filters.d.ts +184 -0
  97. package/dist/DataView/index.d.ts +11 -0
  98. package/dist/DataView/index.js +25 -0
  99. package/dist/DataView-256YF32J.css +441 -0
  100. package/dist/DateTimePicker/DateTimePicker.d.ts +91 -0
  101. package/dist/DateTimePicker/index.d.ts +3 -0
  102. package/dist/DateTimePicker/index.js +9 -0
  103. package/dist/DateTimePicker-22VNXRWC.css +199 -0
  104. package/dist/DescriptionList/DescriptionList.d.ts +36 -0
  105. package/dist/DescriptionList/index.d.ts +3 -0
  106. package/dist/DescriptionList/index.js +2 -0
  107. package/dist/DescriptionList-YYY3V3O6.css +59 -0
  108. package/dist/DiffList/DiffList.d.ts +47 -0
  109. package/dist/DiffList/index.d.ts +3 -0
  110. package/dist/DiffList/index.js +3 -0
  111. package/dist/DiffList-3EX64AAE.css +94 -0
  112. package/dist/Disclosure/Disclosure.d.ts +56 -0
  113. package/dist/Disclosure/anatomy.d.ts +43 -0
  114. package/dist/Disclosure/index.d.ts +5 -0
  115. package/dist/Disclosure/index.js +6 -0
  116. package/dist/Disclosure-VKBJJIZ7.css +176 -0
  117. package/dist/EmptyState/EmptyState.d.ts +39 -0
  118. package/dist/EmptyState/index.d.ts +3 -0
  119. package/dist/EmptyState/index.js +3 -0
  120. package/dist/EmptyState-C6HRBGT7.css +73 -0
  121. package/dist/EmptyValue/EmptyValue.d.ts +31 -0
  122. package/dist/EmptyValue/index.d.ts +3 -0
  123. package/dist/EmptyValue/index.js +3 -0
  124. package/dist/EmptyValue-4B6ODXHT.css +12 -0
  125. package/dist/EntityChip/EntityChip.d.ts +40 -0
  126. package/dist/EntityChip/index.d.ts +3 -0
  127. package/dist/EntityChip/index.js +3 -0
  128. package/dist/EntityChip-LUCPZBBG.css +64 -0
  129. package/dist/ErrorBoundary/ErrorBoundary.d.ts +58 -0
  130. package/dist/ErrorBoundary/index.d.ts +3 -0
  131. package/dist/ErrorBoundary/index.js +4 -0
  132. package/dist/ErrorBoundary-ME36CQTA.css +59 -0
  133. package/dist/Eyebrow/Eyebrow.d.ts +18 -0
  134. package/dist/Eyebrow/index.d.ts +3 -0
  135. package/dist/Eyebrow/index.js +2 -0
  136. package/dist/Eyebrow-5NREGHQH.css +46 -0
  137. package/dist/FilterChip/FilterChip.d.ts +68 -0
  138. package/dist/FilterChip/index.d.ts +3 -0
  139. package/dist/FilterChip/index.js +3 -0
  140. package/dist/FilterChip-GTGLLOXE.css +107 -0
  141. package/dist/Fullscreen/index.d.ts +2 -0
  142. package/dist/Fullscreen/index.js +1 -0
  143. package/dist/Fullscreen/useFullscreen.d.ts +32 -0
  144. package/dist/IconButton/IconButton.d.ts +45 -0
  145. package/dist/IconButton/index.d.ts +3 -0
  146. package/dist/IconButton/index.js +3 -0
  147. package/dist/IconButton-VQGNMJDI.css +158 -0
  148. package/dist/ImageUpload/ImageUpload.d.ts +37 -0
  149. package/dist/ImageUpload/index.d.ts +3 -0
  150. package/dist/ImageUpload/index.js +3 -0
  151. package/dist/ImageUpload-HKFIZR6D.css +193 -0
  152. package/dist/InlineCode/InlineCode.d.ts +37 -0
  153. package/dist/InlineCode/index.d.ts +3 -0
  154. package/dist/InlineCode/index.js +2 -0
  155. package/dist/InlineCode-6WSR5COL.css +65 -0
  156. package/dist/Input/FormField.d.ts +61 -0
  157. package/dist/Input/FormRow.d.ts +6 -0
  158. package/dist/Input/FormSection.d.ts +28 -0
  159. package/dist/Input/Input.d.ts +98 -0
  160. package/dist/Input/Label.d.ts +31 -0
  161. package/dist/Input/ReadOnlyField.d.ts +30 -0
  162. package/dist/Input/Textarea.d.ts +16 -0
  163. package/dist/Input/index.d.ts +15 -0
  164. package/dist/Input/index.js +5 -0
  165. package/dist/Input-2PU5CPKK.css +616 -0
  166. package/dist/Kbd/Kbd.d.ts +40 -0
  167. package/dist/Kbd/index.d.ts +3 -0
  168. package/dist/Kbd/index.js +3 -0
  169. package/dist/Kbd-W66MXQI5.css +61 -0
  170. package/dist/Layer/LayerHost.d.ts +23 -0
  171. package/dist/Layer/index.d.ts +7 -0
  172. package/dist/Layer/index.js +3 -0
  173. package/dist/Layer/place.d.ts +63 -0
  174. package/dist/Layer/stack.d.ts +159 -0
  175. package/dist/Layer-NAFSGCFA.css +23 -0
  176. package/dist/Layout/Layout.d.ts +104 -0
  177. package/dist/Layout/index.d.ts +3 -0
  178. package/dist/Layout/index.js +2 -0
  179. package/dist/Layout-2ZCV3IGF.css +171 -0
  180. package/dist/Link/Link.d.ts +45 -0
  181. package/dist/Link/index.d.ts +3 -0
  182. package/dist/Link/index.js +3 -0
  183. package/dist/Link-6BEFNMN2.css +80 -0
  184. package/dist/List/List.d.ts +73 -0
  185. package/dist/List/index.d.ts +3 -0
  186. package/dist/List/index.js +3 -0
  187. package/dist/List-XTHESXLR.css +161 -0
  188. package/dist/ListInput/ListInput.d.ts +68 -0
  189. package/dist/ListInput/index.d.ts +3 -0
  190. package/dist/ListInput/index.js +10 -0
  191. package/dist/ListInput-O7LJKOIG.css +147 -0
  192. package/dist/Listbox/index.d.ts +5 -0
  193. package/dist/Listbox/index.js +2 -0
  194. package/dist/Listbox/useListbox.d.ts +97 -0
  195. package/dist/Listbox/useOptionKeys.d.ts +64 -0
  196. package/dist/Listbox-X2DOTSQA.css +110 -0
  197. package/dist/Menu/Menu.d.ts +118 -0
  198. package/dist/Menu/index.d.ts +3 -0
  199. package/dist/Menu/index.js +7 -0
  200. package/dist/Menu-6DE7I66I.css +222 -0
  201. package/dist/Meter/Meter.d.ts +49 -0
  202. package/dist/Meter/index.d.ts +3 -0
  203. package/dist/Meter/index.js +3 -0
  204. package/dist/Meter-SKOOXQBC.css +87 -0
  205. package/dist/Modal/ConfirmModal.d.ts +74 -0
  206. package/dist/Modal/Modal.d.ts +154 -0
  207. package/dist/Modal/index.d.ts +5 -0
  208. package/dist/Modal/index.js +8 -0
  209. package/dist/Modal-XYQJ4BL6.css +484 -0
  210. package/dist/NewItemsNotice/NewItemsNotice.d.ts +28 -0
  211. package/dist/NewItemsNotice/index.d.ts +3 -0
  212. package/dist/NewItemsNotice/index.js +4 -0
  213. package/dist/NewItemsNotice-T4DSSPVV.css +60 -0
  214. package/dist/OrgLabel/OrgLabel.d.ts +117 -0
  215. package/dist/OrgLabel/index.d.ts +3 -0
  216. package/dist/OrgLabel/index.js +2 -0
  217. package/dist/OrgLabel-D7U6QJZD.css +293 -0
  218. package/dist/OrgNode/OrgNode.d.ts +103 -0
  219. package/dist/OrgNode/index.d.ts +3 -0
  220. package/dist/OrgNode/index.js +3 -0
  221. package/dist/OrgNode-BT7T2Q2X.css +214 -0
  222. package/dist/OrgTable/OrgTable.d.ts +113 -0
  223. package/dist/OrgTable/index.d.ts +3 -0
  224. package/dist/OrgTable/index.js +11 -0
  225. package/dist/OrgTable-VWMFC3TB.css +360 -0
  226. package/dist/PageHeader/PageHeader.d.ts +35 -0
  227. package/dist/PageHeader/index.d.ts +3 -0
  228. package/dist/PageHeader/index.js +3 -0
  229. package/dist/PageHeader-6EYWQXRE.css +88 -0
  230. package/dist/Popover/Popover.d.ts +91 -0
  231. package/dist/Popover/index.d.ts +3 -0
  232. package/dist/Popover/index.js +4 -0
  233. package/dist/Popover-WJGCF3ZQ.css +185 -0
  234. package/dist/PricingCard/PricingCard.d.ts +56 -0
  235. package/dist/PricingCard/index.d.ts +3 -0
  236. package/dist/PricingCard/index.js +4 -0
  237. package/dist/PricingCard-JFI7RYXP.css +278 -0
  238. package/dist/Prose/Prose.d.ts +33 -0
  239. package/dist/Prose/index.d.ts +3 -0
  240. package/dist/Prose/index.js +2 -0
  241. package/dist/Prose-BTXNFT2Q.css +99 -0
  242. package/dist/RelativeTime/RelativeTime.d.ts +31 -0
  243. package/dist/RelativeTime/clock.d.ts +9 -0
  244. package/dist/RelativeTime/format.d.ts +38 -0
  245. package/dist/RelativeTime/index.d.ts +6 -0
  246. package/dist/RelativeTime/index.js +4 -0
  247. package/dist/RelativeTime-EBAQPGO2.css +10 -0
  248. package/dist/SearchTrigger/SearchTrigger.d.ts +29 -0
  249. package/dist/SearchTrigger/index.d.ts +3 -0
  250. package/dist/SearchTrigger/index.js +2 -0
  251. package/dist/SearchTrigger-QXOYAUUY.css +79 -0
  252. package/dist/Section/Section.d.ts +41 -0
  253. package/dist/Section/index.d.ts +3 -0
  254. package/dist/Section/index.js +3 -0
  255. package/dist/Section-QRUPRIQ2.css +127 -0
  256. package/dist/SegmentedControl/SegmentedControl.d.ts +68 -0
  257. package/dist/SegmentedControl/index.d.ts +4 -0
  258. package/dist/SegmentedControl/index.js +5 -0
  259. package/dist/SegmentedControl-X3BI3XU4.css +68 -0
  260. package/dist/Select/Select.d.ts +138 -0
  261. package/dist/Select/index.d.ts +3 -0
  262. package/dist/Select/index.js +5 -0
  263. package/dist/Select-2X77FSWR.css +350 -0
  264. package/dist/Shortcut/index.d.ts +2 -0
  265. package/dist/Shortcut/index.js +4 -0
  266. package/dist/Shortcut/useShortcut.d.ts +68 -0
  267. package/dist/SidebarNav/SidebarNav.d.ts +117 -0
  268. package/dist/SidebarNav/index.d.ts +3 -0
  269. package/dist/SidebarNav/index.js +3 -0
  270. package/dist/SidebarNav-QGSFKZYA.css +319 -0
  271. package/dist/Skeleton/Skeleton.d.ts +50 -0
  272. package/dist/Skeleton/index.d.ts +3 -0
  273. package/dist/Skeleton/index.js +3 -0
  274. package/dist/Skeleton-V735HRGM.css +421 -0
  275. package/dist/StatCard/StatCard.d.ts +56 -0
  276. package/dist/StatCard/index.d.ts +3 -0
  277. package/dist/StatCard/index.js +3 -0
  278. package/dist/StatCard-KYPKZUWX.css +111 -0
  279. package/dist/StatGroup/StatGroup.d.ts +20 -0
  280. package/dist/StatGroup/index.d.ts +3 -0
  281. package/dist/StatGroup/index.js +2 -0
  282. package/dist/StatGroup-TGB6AEKM.css +80 -0
  283. package/dist/StatusDot/StatusDot.d.ts +25 -0
  284. package/dist/StatusDot/index.d.ts +3 -0
  285. package/dist/StatusDot/index.js +2 -0
  286. package/dist/StatusDot-HA2L2FSX.css +84 -0
  287. package/dist/Switch/Switch.d.ts +38 -0
  288. package/dist/Switch/index.d.ts +3 -0
  289. package/dist/Switch/index.js +2 -0
  290. package/dist/Switch-U4OWK4CL.css +169 -0
  291. package/dist/Table/Table.d.ts +51 -0
  292. package/dist/Table/index.d.ts +3 -0
  293. package/dist/Table/index.js +3 -0
  294. package/dist/Table-MDOLVUAW.css +159 -0
  295. package/dist/Tabs/Tabs.d.ts +97 -0
  296. package/dist/Tabs/index.d.ts +3 -0
  297. package/dist/Tabs/index.js +4 -0
  298. package/dist/Tabs/roving.d.ts +36 -0
  299. package/dist/Tabs-VLICJFVB.css +275 -0
  300. package/dist/Tag/Tag.d.ts +107 -0
  301. package/dist/Tag/index.d.ts +3 -0
  302. package/dist/Tag/index.js +3 -0
  303. package/dist/Tag-VNZEYLB6.css +406 -0
  304. package/dist/TagGroup/TagGroup.d.ts +41 -0
  305. package/dist/TagGroup/index.d.ts +3 -0
  306. package/dist/TagGroup/index.js +4 -0
  307. package/dist/TagGroup-APC2EFH4.css +75 -0
  308. package/dist/TagsInput/TagsInput.d.ts +112 -0
  309. package/dist/TagsInput/index.d.ts +3 -0
  310. package/dist/TagsInput/index.js +10 -0
  311. package/dist/TagsInput-W4P36PRJ.css +168 -0
  312. package/dist/Text/Text.d.ts +67 -0
  313. package/dist/Text/index.d.ts +3 -0
  314. package/dist/Text/index.js +2 -0
  315. package/dist/Text-XIPYYZX2.css +154 -0
  316. package/dist/ThemeSwitcher/ThemeSwitcher.d.ts +47 -0
  317. package/dist/ThemeSwitcher/index.d.ts +4 -0
  318. package/dist/ThemeSwitcher/index.js +6 -0
  319. package/dist/ThemeSwitcher/preferences.d.ts +51 -0
  320. package/dist/TimeWindowPicker/TimeWindowPicker.d.ts +231 -0
  321. package/dist/TimeWindowPicker/index.d.ts +3 -0
  322. package/dist/TimeWindowPicker/index.js +13 -0
  323. package/dist/TimeWindowPicker/mask.d.ts +82 -0
  324. package/dist/TimeWindowPicker-7I7KWCMU.css +403 -0
  325. package/dist/Timeline/Timeline.d.ts +51 -0
  326. package/dist/Timeline/index.d.ts +3 -0
  327. package/dist/Timeline/index.js +3 -0
  328. package/dist/Timeline-27SYKOFX.css +144 -0
  329. package/dist/Toaster/Toaster.d.ts +108 -0
  330. package/dist/Toaster/index.d.ts +3 -0
  331. package/dist/Toaster/index.js +5 -0
  332. package/dist/Toaster-NVDCEIP5.css +306 -0
  333. package/dist/Toolbar/Toolbar.d.ts +62 -0
  334. package/dist/Toolbar/index.d.ts +3 -0
  335. package/dist/Toolbar/index.js +4 -0
  336. package/dist/Toolbar-IP6LXN45.css +48 -0
  337. package/dist/Tooltip/Tooltip.d.ts +51 -0
  338. package/dist/Tooltip/index.d.ts +3 -0
  339. package/dist/Tooltip/index.js +4 -0
  340. package/dist/Tooltip-LKQF7CE4.css +71 -0
  341. package/dist/Tree/index.d.ts +8 -0
  342. package/dist/Tree/index.js +1 -0
  343. package/dist/Tree/layout.d.ts +160 -0
  344. package/dist/Tree/model.d.ts +110 -0
  345. package/dist/Tree/useMeasuredSizes.d.ts +61 -0
  346. package/dist/Tree/useTreeState.d.ts +89 -0
  347. package/dist/TreeCanvas/TreeCanvas.d.ts +384 -0
  348. package/dist/TreeCanvas/index.d.ts +3 -0
  349. package/dist/TreeCanvas/index.js +10 -0
  350. package/dist/TreeCanvas/motion.d.ts +59 -0
  351. package/dist/TreeCanvas-WBNQPHC7.css +1098 -0
  352. package/dist/TreeGrid/TreeGrid.d.ts +142 -0
  353. package/dist/TreeGrid/index.d.ts +3 -0
  354. package/dist/TreeGrid/index.js +8 -0
  355. package/dist/TreeGrid-NSW7VMOX.css +132 -0
  356. package/dist/VisuallyHidden/VisuallyHidden.d.ts +23 -0
  357. package/dist/VisuallyHidden/index.d.ts +3 -0
  358. package/dist/VisuallyHidden/index.js +2 -0
  359. package/dist/VisuallyHidden-L552H7OA.css +21 -0
  360. package/dist/chunk-22O7LAW2.js +41 -0
  361. package/dist/chunk-23DHIMJA.js +38 -0
  362. package/dist/chunk-2EOKFKHX.js +263 -0
  363. package/dist/chunk-2PRH365R.js +74 -0
  364. package/dist/chunk-2ZQMA2WB.js +57 -0
  365. package/dist/chunk-3CZ7XAVY.js +139 -0
  366. package/dist/chunk-3EYF6HKB.js +135 -0
  367. package/dist/chunk-3ZP55QZE.js +40 -0
  368. package/dist/chunk-53DVJCVW.js +14 -0
  369. package/dist/chunk-5C5YMZQU.js +51 -0
  370. package/dist/chunk-5EKIWVOR.js +132 -0
  371. package/dist/chunk-5IMACPJS.js +495 -0
  372. package/dist/chunk-5KPQ6D7N.js +54 -0
  373. package/dist/chunk-5SDBMKUO.js +297 -0
  374. package/dist/chunk-5WS6L6FA.js +114 -0
  375. package/dist/chunk-7EM5T4ME.js +39 -0
  376. package/dist/chunk-7FZ53WDJ.js +163 -0
  377. package/dist/chunk-7HPRF5XN.js +525 -0
  378. package/dist/chunk-7RPGY3JE.js +43 -0
  379. package/dist/chunk-A2UXBCLO.js +48 -0
  380. package/dist/chunk-ARMRX2YV.js +96 -0
  381. package/dist/chunk-AV2DXAEL.js +27 -0
  382. package/dist/chunk-BHHIIK3M.js +17 -0
  383. package/dist/chunk-BZFWDIV6.js +9 -0
  384. package/dist/chunk-BZYDZBAN.js +123 -0
  385. package/dist/chunk-CMEE37QJ.js +628 -0
  386. package/dist/chunk-D3OADIM6.js +112 -0
  387. package/dist/chunk-DD5BBGBE.js +162 -0
  388. package/dist/chunk-EHMQQHYR.js +1652 -0
  389. package/dist/chunk-EN6K2IFA.js +256 -0
  390. package/dist/chunk-EPOOQZYA.js +118 -0
  391. package/dist/chunk-ESCO4XYW.js +144 -0
  392. package/dist/chunk-EUCC37WY.js +14 -0
  393. package/dist/chunk-EV6IMYJ6.js +62 -0
  394. package/dist/chunk-F27EPQ7D.js +68 -0
  395. package/dist/chunk-F4PJZ4ED.js +245 -0
  396. package/dist/chunk-F6OE5FH3.js +49 -0
  397. package/dist/chunk-FBZAKNSX.js +263 -0
  398. package/dist/chunk-FEFTGVMF.js +46 -0
  399. package/dist/chunk-G2QFRTJ2.js +823 -0
  400. package/dist/chunk-GHYY2WPF.js +907 -0
  401. package/dist/chunk-GTOUGQQK.js +147 -0
  402. package/dist/chunk-GUIF6Q6N.js +143 -0
  403. package/dist/chunk-GXQ3B46I.js +216 -0
  404. package/dist/chunk-H4FBTAYS.js +130 -0
  405. package/dist/chunk-HOXGBLEY.js +37 -0
  406. package/dist/chunk-HQNHV7D6.js +54 -0
  407. package/dist/chunk-HTDFCRJZ.js +92 -0
  408. package/dist/chunk-HZ5XZNPS.js +98 -0
  409. package/dist/chunk-IAJLHVUA.js +28 -0
  410. package/dist/chunk-IJ7QXNIJ.js +1 -0
  411. package/dist/chunk-ITHXJHBB.js +280 -0
  412. package/dist/chunk-JCSA3XVH.js +32 -0
  413. package/dist/chunk-KLJCCMF3.js +97 -0
  414. package/dist/chunk-LGCPQIOZ.js +49 -0
  415. package/dist/chunk-LWKJQTOM.js +77 -0
  416. package/dist/chunk-MNAXYY3R.js +215 -0
  417. package/dist/chunk-NN62JQLR.js +29 -0
  418. package/dist/chunk-NPZY6YYL.js +251 -0
  419. package/dist/chunk-NTRZGNEX.js +529 -0
  420. package/dist/chunk-O6232G37.js +273 -0
  421. package/dist/chunk-O67VF5MS.js +58 -0
  422. package/dist/chunk-ODEQTPFM.js +6 -0
  423. package/dist/chunk-PNFASD6F.js +98 -0
  424. package/dist/chunk-PQADV6P6.js +19 -0
  425. package/dist/chunk-PZSWF74H.js +15 -0
  426. package/dist/chunk-Q4ZPPART.js +26 -0
  427. package/dist/chunk-QNCVHRZL.js +17 -0
  428. package/dist/chunk-QVE4GJAT.js +15 -0
  429. package/dist/chunk-QYOFACSP.js +36 -0
  430. package/dist/chunk-RA2ULG6H.js +29 -0
  431. package/dist/chunk-RDGD7Z7I.js +350 -0
  432. package/dist/chunk-RK4XPHVG.js +16 -0
  433. package/dist/chunk-RSAQOKH3.js +38 -0
  434. package/dist/chunk-S2XHHEW4.js +84 -0
  435. package/dist/chunk-S4GZWSKR.js +137 -0
  436. package/dist/chunk-SFZOKW2G.js +52 -0
  437. package/dist/chunk-T7FKOJKW.js +221 -0
  438. package/dist/chunk-UG46QBLT.js +325 -0
  439. package/dist/chunk-UOGSZKLT.js +134 -0
  440. package/dist/chunk-V2XMNLMS.js +52 -0
  441. package/dist/chunk-V663QU4R.js +51 -0
  442. package/dist/chunk-W4ENKCXJ.js +40 -0
  443. package/dist/chunk-W5J7R5EB.js +107 -0
  444. package/dist/chunk-WNI2WSJF.js +27 -0
  445. package/dist/chunk-XDTYBF2F.js +36 -0
  446. package/dist/chunk-XN4JBAN3.js +32 -0
  447. package/dist/chunk-XV6WRMBC.js +211 -0
  448. package/dist/chunk-YLYOWZ4V.js +33 -0
  449. package/dist/chunk-YMWONFSI.js +170 -0
  450. package/dist/chunk-ZCR6YGQ4.js +1 -0
  451. package/dist/generated/index.d.ts +83 -0
  452. package/dist/generated/index.js +91 -0
  453. package/dist/index.d.ts +1 -1217
  454. package/dist/index.js +91 -4518
  455. package/dist/styles.css +14841 -4274
  456. package/dist/utils/cx.d.ts +10 -0
  457. package/dist/utils/headingLevel.d.ts +16 -0
  458. package/dist/utils/index.d.ts +6 -0
  459. package/dist/utils/index.js +4 -0
  460. package/dist/utils/tone.d.ts +25 -0
  461. package/dist/utils/useClipboard.d.ts +30 -0
  462. package/package.json +18 -10
  463. package/scripts/bundle-css.mjs +25 -0
  464. package/scripts/check-css-literals.mjs +166 -0
  465. package/scripts/check-css-variables.mjs +184 -0
  466. package/scripts/check-css.mjs +27 -0
  467. package/scripts/css-source.mjs +67 -0
  468. package/scripts/write-barrel.mjs +60 -0
  469. package/dist/Accordion-NIMJQIOI.css +0 -88
  470. package/dist/AppShell-ZW3NSFBP.css +0 -51
  471. package/dist/Avatar-V6NG6Q45.css +0 -67
  472. package/dist/Button-XGCTXXYP.css +0 -174
  473. package/dist/Callout-LEGDIMAM.css +0 -52
  474. package/dist/Card-BOZGBML7.css +0 -86
  475. package/dist/Checkbox-OCUICIIZ.css +0 -58
  476. package/dist/CodeBlock-IEMBNIQC.css +0 -90
  477. package/dist/Container-LTKBA2FW.css +0 -21
  478. package/dist/Copyable-IB57OTHZ.css +0 -123
  479. package/dist/DataTable-3GALZUW2.css +0 -1593
  480. package/dist/DateTimePicker-NMSSGVPK.css +0 -312
  481. package/dist/Eyebrow-AVBORAS2.css +0 -30
  482. package/dist/IconButton-GB4PJDG2.css +0 -82
  483. package/dist/ImageUpload-3HQDF2KC.css +0 -158
  484. package/dist/Input-ZHUTN7CU.css +0 -210
  485. package/dist/Kbd-Q77CDZY7.css +0 -23
  486. package/dist/Modal-FJVG66S4.css +0 -138
  487. package/dist/Popover-FUFULUE2.css +0 -123
  488. package/dist/PricingCard-NKYV3JHA.css +0 -238
  489. package/dist/Section-665ZIPG6.css +0 -20
  490. package/dist/Select-GPRF7PRB.css +0 -294
  491. package/dist/Skeleton-3WEFLRDN.css +0 -378
  492. package/dist/StatCard-RTJIVTRB.css +0 -50
  493. package/dist/Table-AR3TRY2E.css +0 -89
  494. package/dist/Tabs-XZ4JIH3E.css +0 -153
  495. package/dist/Tag-R5QDJLYJ.css +0 -163
  496. package/dist/TimeWindowPicker-L3G5Q7UT.css +0 -629
  497. package/dist/Toaster-2O65VOR4.css +0 -264
package/README.md CHANGED
@@ -5,16 +5,17 @@ Crewlet shared React components. Built on top of `@crewlethq/tokens` (CSS variab
5
5
  ## Use it
6
6
 
7
7
  ```tsx
8
- import '@crewlethq/tokens/css'; // load CSS variables once at app entry
9
- import '@crewlethq/tokens/css/fonts'; // self-hosted Inter and JetBrains Mono
10
- import '@crewlethq/tokens/css/material-symbols'; // icon font the components render glyphs with
11
- import '@crewlethq/ui/styles.css'; // component styles (or rely on per-component CSS imports)
8
+ import '@crewlethq/tokens/css'; // the variables, once at the application entry
9
+ import '@crewlethq/tokens/css/themes'; // light, dark and follow-the-system
10
+ import '@crewlethq/tokens/css/density'; // compact and comfortable, optional
11
+ import '@crewlethq/tokens/css/fonts'; // self-hosted Inter and JetBrains Mono
12
+ import '@crewlethq/ui/styles.css'; // every component stylesheet, in one file
12
13
  import { Button, Card } from '@crewlethq/ui';
13
- import { Icon } from '@crewlethq/icons';
14
+ import { Book2Glyph } from '@crewlethq/icons/glyphs';
14
15
 
15
16
  <Card>
16
17
  <Card.Body>
17
- <Button variant="primary" leadingIcon={<Icon name="AgentReading" />}>
18
+ <Button variant="primary" leadingIcon={<Book2Glyph />}>
18
19
  Open documentation
19
20
  </Button>
20
21
  <Button variant="outline">Cancel</Button>
@@ -22,26 +23,55 @@ import { Icon } from '@crewlethq/icons';
22
23
  </Card>
23
24
  ```
24
25
 
25
- The components render their glyphs as ligatures of the Material Symbols Outlined icon font inside `<span class="material-symbols-outlined">`. `@crewlethq/tokens/css/material-symbols` loads that font from Google Fonts (Apache License 2.0) and is the only import above that contacts a third-party host. Skip it when the app already loads the font under the same class name; see the `@crewlethq/tokens` README for details.
26
+ **Nothing here contacts a third-party host.** A glyph is an SVG that ships in
27
+ `@crewlethq/icons`, never a ligature of a font fetched from somewhere else: a
28
+ ligature renders the WORD until the font arrives, renders it for good on a
29
+ closed network, and is read aloud as that word by a screen reader.
30
+
31
+ **One component, one stylesheet.** `@crewlethq/ui/styles.css` is every
32
+ component's CSS in one file, for a consumer that wants it that way. Import the
33
+ folder instead and you carry that component's stylesheet and nothing else:
34
+
35
+ ```tsx
36
+ import { Button } from '@crewlethq/ui/Button'; // 8 KB of CSS, not 182 KB
37
+ ```
26
38
 
27
39
  ## What is in here
28
40
 
29
41
  | Area | Components |
30
42
  | ---- | ---------- |
31
- | Actions | `Button` (variants `primary`, `secondary`, `outline`, `tertiary`, `accent`, `danger`; sizes `small`, `medium`, `large`; shapes `square`, `pill`), `IconButton`, `Copyable`, `Kbd` |
43
+ | Actions | `Button` and `ButtonLink` (variants `primary`, `secondary`, `outline`, `tertiary`, `accent`, `danger`; sizes `small`, `medium`, `large`; shapes `square`, `pill`), `IconButton`, `Menu`, `Link`, `Copyable`, `CopyButton`, `Kbd` |
44
+ | Content and text | `Text` (the eight registers), `Prose`, `InlineCode`, `List` and `ListItem`, `DescriptionList`, `Timeline`, `RelativeTime` |
32
45
  | Forms | `Input`, `Textarea`, `Label`, `FormField`, `FormRow`, `Checkbox`, `Select`, `Tag`, `TagsInput`, `DateTimePicker`, `TimeWindowPicker`, `ImageUpload` |
33
46
  | Data display | `DataTable`, `CopyableCell`, `Table`, `StatCard`, `PricingCard`, `Avatar`, `CodeBlock`, `Skeleton`, `Eyebrow` |
34
- | Feedback and overlays | `Callout`, `Toaster`, `Modal`, `ConfirmModal`, `Popover` |
35
- | Layout | `AppShell`, `Container`, `Section`, `Card`, `Tabs`, `Accordion` |
47
+ | List screens | `DataView`, and its parts on their own: `DataViewToolbar`, `FilterAxisBar`, `FilterAxisChip`, plus the filter model (`filterDefsFromColumns`, `applyColumnFilters`, `filterPredicate`, `blankFilterValue`) |
48
+ | Feedback and overlays | `Callout`, `Toaster`, `Modal` (with `variant="sheet"`), `ConfirmModal`, `CommandPalette`, `Tooltip`, `Popover`, `Announcer` |
49
+ | Marks | `Count`, `StatusDot`, `EmptyValue`, `VisuallyHidden` |
50
+ | Hooks and seams | `useModalLayer`, `usePopupLayer`, `LayerHost`, `useListbox`, `useOptionKeys`, `useClipboard`, `useAnnouncer`, `useNow`, `formatRelative`, `HeadingLevelProvider`, `cx` |
51
+ | Layout | `Container`, `Section`, `Card`, `Tabs`, `Accordion`, `Disclosure` |
52
+ | Application shell | `AppShell` with `AppShell.Rail`, `AppShell.Topbar` and `useAppShell`, `SidebarNav` and `NavItem`, `BrandLockup`, `SearchTrigger`, `PageHeader`, `Toolbar`, `Stack`, `Inline`, `Spacer`, `AutoGrid`, `ErrorBoundary`, `useShortcut`, `useFullscreen` |
36
53
 
37
54
  Storybook stories live under `apps/storybook/src/stories`.
38
55
 
39
- Add new components under `src/<Name>/` with a `Component.tsx`, optional `Component.css`, and an `index.ts`. Re-export from `src/index.ts`.
56
+ Add new components under `src/<Name>/` with a `Component.tsx`, an optional
57
+ `Component.css`, an `index.ts` and a colocated `Component.test.tsx`. The root
58
+ barrel is GENERATED from the folder list by `scripts/write-barrel.mjs`, so
59
+ there is no shared index to edit and no conflict in it when several components
60
+ land at once.
40
61
 
41
62
  ## Theming hooks
42
63
 
43
- - **Light and dark palettes.** Import `@crewlethq/tokens/css/themes` and toggle `theme-light` or `theme-dark` on `<body>`. Every component reads the canonical `--color-*` variables, so no provider is needed.
44
- - **Accent colour.** The selected, active and focus states read `--color-brand-accent` and its companions from `@crewlethq/tokens`: `--color-brand-accent-rgb` (the same colour as a comma-separated `r, g, b` triple, for translucent tints), `--color-brand-accent-hover`, `--color-brand-accent-active`, `--color-brand-accent-soft` and `--color-brand-accent-soft-strong`. Rebind them together to retint the components. `Select` menus, `Popover` panels and the `DateTimePicker` and `TimeWindowPicker` popovers are portaled to `<body>`, so declare the override on `<body>` (or `<html>`) for those panels to follow it.
64
+ - **Light and dark palettes.** Import `@crewlethq/tokens/css/themes` and set
65
+ `data-theme="light"` or `data-theme="dark"` on `<html>`. With the attribute
66
+ absent the document follows the system. Every component reads the canonical
67
+ `--color-*` variables, so no provider is needed.
68
+ - **Density.** Import `@crewlethq/tokens/css/density` and set
69
+ `data-density="compact"` or `"comfortable"` on `<html>`. Every spacing and
70
+ size token scales with it, and the small control and row steps floor at 24px
71
+ so no density takes a pointer target under the size a finger can hit.
72
+ - **Accent colour.** The selected, active and focus states read `--color-brand-accent` and its companions from `@crewlethq/tokens`: `--color-brand-accent-rgb` (the same colour as a comma-separated `r, g, b` triple, for translucent tints), `--color-brand-accent-hover`, `--color-brand-accent-active`, `--color-brand-accent-soft` and `--color-brand-accent-soft-strong`. Rebind them together to retint the components. Every overlay portals into the nearest `LayerHost`, or into `<body>` when
73
+ there is none, so declare the override on `<html>` for those panels to follow
74
+ it.
45
75
 
46
76
  ```css
47
77
  /* Retint every accent state to the brand slate. */
@@ -57,7 +87,1677 @@ body {
57
87
 
58
88
  - **Component variables.** A few components expose their own `--crewlet-*` variables for values that are not tokens, for example `--crewlet-data-table-active-sort-color`, `--crewlet-data-table-row-hover-bg` and `--crewlet-data-table-archived-tint-rgb` on the compact `DataTable`, and `--crewlet-avatar-tint-fg` for the initials on a tinted `Avatar`. Override them from a rule that targets the component's root.
59
89
 
60
- The component stylesheets read only variables that `@crewlethq/tokens` emits or that the components declare themselves; `npm run lint` in this package enforces it.
90
+ The component stylesheets read only variables that `@crewlethq/tokens` emits or
91
+ that the components declare themselves, and the same check refuses a glyph
92
+ drawn as a ligature, anything loaded from a host, a theme painted on a body
93
+ class, the decoration step used as text, and a focus rule that turns the
94
+ outline off without putting one back. `npm run lint` in this package runs it,
95
+ and `npx crewlet-css-check <folder>` runs it and the literal check over an
96
+ application's own stylesheets.
97
+
98
+ ## Overlays
99
+
100
+ Every overlay in this package is on ONE layer stack, so a press of Escape
101
+ closes exactly one surface, a press outside dismisses the surface it belongs
102
+ to, and a modal traps Tab. A surface's z-index is its DEPTH in the stack inside
103
+ the token layer band, never a counter, so the tenth dialog of a session still
104
+ paints below a toast.
105
+
106
+ An application does not have to do anything to get this. It matters in two
107
+ places:
108
+
109
+ - A container that can go FULLSCREEN wraps its content in a `LayerHost`. A
110
+ fullscreen element renders only its own subtree, so a dialog portalled to
111
+ `document.body` while a canvas is fullscreen is not painted at all.
112
+ - A surface that pans or zooms dispatches `LAYER_REPOSITION_EVENT` on its host
113
+ when the content beneath it moves, and every panel anchored inside follows.
114
+
115
+ ## Breaking changes in 0.3.0
116
+
117
+ Every one of these is a change a consumer can see. None of them needs an edit
118
+ unless it is listed under "What to change".
119
+
120
+ **The overlays.** Modal, Popover, Select and DateTimePicker each used to add
121
+ their own Escape listener to the document, so one press closed all of them, and
122
+ a backdrop press closed two layers at once. They are on one stack now: Escape
123
+ reaches the TOPMOST surface and a backdrop press dismisses the surface it
124
+ belongs to. An application that relied on one press closing a dialog and the
125
+ menu inside it together now needs two. A panel portals into the nearest
126
+ `LayerHost` rather than always into `document.body`.
127
+
128
+ **The controls wear the product's own register.** Every form control, button
129
+ and tab in the package is drawn the way the engine dashboard draws it, which
130
+ is the look the product ships. What follows is that change, component by
131
+ component. None of it needs an edit; all of it renders differently.
132
+
133
+ **`Button`'s hierarchy is redrawn.** `primary` is the ACCENT FILL rather than
134
+ the monochrome `--color-brand-primary`, because the one action on a screen has
135
+ to read as the answer the screen is asking for, and a white primary beside a
136
+ light-grey secondary was one pair of greys on the dark palette. `accent` is
137
+ the same recipe under the name a call site may already spell, so the two are
138
+ one rule and cannot drift into two blues. `danger` is no longer a red slab: it
139
+ is a secondary that has gone red, stating itself in the critical INK and
140
+ taking the hue as a tint and a boundary on hover, because it sits beside
141
+ Cancel and it is the button nobody should press by reflex. Labels are medium
142
+ rather than semibold and carry no negative tracking, and the padding ramp is
143
+ one scale step per size (small 8px, medium 12px, large 16px). What to change:
144
+ a call site that reached for `variant="danger"` to draw a filled red
145
+ confirmation now gets the quiet one; nothing else.
146
+
147
+ **`IconButton` has a `secondary` variant**, which is the bordered square a
148
+ secondary Button with no room for a label would be: a month's back and
149
+ forward, a step in a toolbar. Its corner is `--radius-md` like every other
150
+ button, rather than `--radius-sm`, and its glyph follows the label size a
151
+ Button of that step would draw (12px at `sm`, 14px at `md`) instead of running
152
+ one step heavier.
153
+
154
+ **The form row is one line, and its label is a micro-label.** `Label`,
155
+ `FormField`'s legend and `ReadOnlyField`'s label are 11px, medium, opened up
156
+ and uppercased on the tertiary step: a form is read by its values, and a label
157
+ set in the value's own size competes with it. A field carries its inset as
158
+ side padding only, so a medium one is exactly `--size-control-md` tall and
159
+ lines up with the button and the select beside it; it used to draw 39px
160
+ against a 32px button. Values are 13px rather than 14px, a `FormField`'s own
161
+ parts sit one scale step apart rather than two, and the affix a url field
162
+ wears (`InputAffix`) is the page's face rather than the code face and is
163
+ joined to the value it prefixes: it takes the inset a value takes and the
164
+ control after it takes none, so `https://` and the host read as one string
165
+ rather than as two fields. What to change: a screen that wrote its labels in
166
+ Title Case now reads them in caps; write them as sentences.
167
+
168
+ **A focused field shows ONE ring, and it is an outline.** `Input`, `Textarea`,
169
+ `Select` and `TagsInput` each drew two: the boundary took `--color-focus` and
170
+ `--shadow-focus` laid a second focus-coloured band outside it with a
171
+ background-coloured gap between the two, so a focused field read as an accent
172
+ hairline, a gap and an accent band. What is left is one 2px outline in
173
+ `--color-focus`, drawn INSIDE the box on `--size-focus-ring-inset-offset`, so
174
+ it covers the boundary rather than standing off it: a scroller cannot clip it,
175
+ and the field no longer grows a 4px halo when a caret lands in it. The
176
+ boundary keeps its own `--color-border-control` under it, a refused field
177
+ rings in `--color-feedback-danger` so a focus ring cannot paint out the one
178
+ mark saying the value cannot be saved, and the field's ring is keyed on the
179
+ field's own control rather than on `:focus-within`, so tabbing to a search
180
+ box's clear control rings the button and not the whole field around it.
181
+ `Combobox` and `ListInput` follow, being built from these. What to change:
182
+ nothing in a call site; a stylesheet of your own that keyed on
183
+ `.crewlet-input:focus-within` or on the focus shadow needs rewriting.
184
+
185
+ **Every dropdown is one panel.** `Select`'s list, `Combobox`'s completions and
186
+ `TagsInput`'s offers take the product's panel surface, hairline, corner and
187
+ medium shadow rather than the elevated surface and the large one, and each
188
+ caps at `12rem` rather than 240 or 320px. In `Select` and `TagsInput`, WHAT IS
189
+ CHOSEN and WHERE THE READER IS are drawn apart: the chosen row keeps its tick
190
+ in the accent ink and the row under the arrows or the pointer takes the hover
191
+ tint, which used to be the same accent tint on both. A row a caller draws
192
+ itself with `renderOption` carries the choice in its weight.
193
+
194
+ **`Select` takes a `width`.** `full` is the default and is what every call
195
+ site has today. `auto` is the toolbar picker: it sizes to its answer between
196
+ 132px and 220px, which is what a filter bar is, rather than one question per
197
+ line. Its trigger is the field's own chrome at 13px, and its chevron is the
198
+ decoration step, as every glyph that repeats what the control already says.
199
+
200
+ **`Tabs` and `SegmentedControl` are redrawn.** The underline row scrolls
201
+ sideways rather than pushing the screen it is on, with its scrollbar hidden
202
+ and its focus ring drawn INSIDE the tab, because an outset one is clipped by
203
+ the scroller the row has become. The row brings its current section into its
204
+ own visible range when it mounts and when the section changes, moving nothing
205
+ but itself, and the accent bar is placed from a content offset so it stays
206
+ under its tab however far the row is scrolled. Its labels are 13px on the
207
+ tertiary step,
208
+ carry only the side pad that ring needs, and stand one scale step apart. The
209
+ pill row is a WELL WITH CHIPS IN IT: the inset surface, no boundary of its
210
+ own, a 2px inset, `--radius-md` on the container and `--radius-chip` on a chip,
211
+ 12px labels, and the chip that is on lifted off the well with the panel
212
+ surface. A chip clears the 24px target floor in BOTH directions, where its
213
+ width used to be a pad around whatever it held and a one-letter mark drew a
214
+ target 19px wide, and a chip wider than its content by that floor centres what
215
+ it draws. The `sm` chip is the quiet step a rail's foot wears: 4px of side
216
+ padding and a glyph one type step under its label's, so a theme row and a
217
+ density row beside each other draw one cell each and read as one row of
218
+ controls. A hover brightens the label in both chromes rather than filling the
219
+ box behind it. `SegmentedControl`'s card rows take the framed-choice boundary
220
+ (no surface of their own, `--radius-lg`), which is the frame a `Checkbox`
221
+ draws. `ThemeSwitcher` follows, being a `SegmentedControl`.
222
+
223
+ **A focused `Switch` is ringed where a reader can see it.** The ring used to be
224
+ drawn on the native checkbox the track carries, which is the element that takes
225
+ focus and is also the element drawn at `opacity: 0` so the pointer hits a real
226
+ input. Opacity applies to the whole of an element's rendering, its outline
227
+ included, so a switch a keyboard reader tabbed onto showed no indicator at all.
228
+ It is drawn on the track now, keyed on the control's own `:focus-visible`, in
229
+ the same 2px outline every other control in the package wears. What to change:
230
+ nothing, unless a stylesheet of your own keyed on
231
+ `.crewlet-switch__control:focus-visible`.
232
+
233
+ **A framed `Checkbox` or `Switch` is a boundary, not a panel.** Neither draws
234
+ a surface of its own any more, both take `--radius-lg`, and a `tone="danger"`
235
+ framed row is no longer tinted red: what it deletes is said in the words and
236
+ in the colour of the tick, and a red panel puts the alarm on the frame. Their
237
+ descriptions are 12px, and both rows state the label's own 14px on the root
238
+ rather than inheriting an application's body size, because the box and the
239
+ track are centred on the label's first line in `em`. A screen that set a font
240
+ size on a `Checkbox` or `Switch` from outside now has to set it on the label
241
+ it means.
242
+
243
+ **The pickers take the control chrome.** `DateTimePicker` and
244
+ `TimeWindowPicker` are one line tall with a side inset, their glyphs are the
245
+ decoration step, their small boxes take `--radius-md` like every other field,
246
+ and a `TimeWindowPicker` preset is drawn as the small button it is.
247
+ `CalendarGrid`'s day cells take `--radius-xs`, the corner every small row in
248
+ the package wears.
249
+
250
+ **`TimeWindowPicker` is a rail of NAMED windows beside the calendar.** The two
251
+ tabs are gone. The relative pane used to be a grid of bare numbers under unit
252
+ headings, so "Last 7 days" was a 7 in a row called Days and reaching it meant
253
+ switching tab first; the windows a reader actually asks for (Last 15 minutes,
254
+ Last hour, Last 4 hours, Today, Yesterday, Last 24 hours, Last 7 days, Last 30
255
+ days) are now a radio group down the side of the panel, with the absolute range
256
+ beside them rather than behind them. The two sides SAY THE SAME THING: take a
257
+ named window and the calendar shades it and the boxes fill with the moments it
258
+ resolves to (a rolling window's end is drawn as the now it reaches); edit a box
259
+ or take a day and the window becomes that range and the rail lets go. The panel
260
+ is sized by its content rather than pinned to 560px, so a calendar is not
261
+ squeezed at comfortable density, and it scrolls only where one column will not
262
+ fit. What to change: `presets` is a flat `TimeWindowPreset[]` of
263
+ `{ label, duration | span }` rather than groups of `{ label, unit, values }`;
264
+ every individual label prop (`absoluteLabel`, `cancelLabel`, `presetLabel`, and
265
+ the rest) moves into one `labels` bag (`Partial<TimeWindowLabels>`), as
266
+ `DataView` already takes its own; and a screen with a default window should pass
267
+ `defaultValue`, which is what the new Reset goes back to and without which no
268
+ Reset is drawn.
269
+
270
+ **A time window can be a CALENDAR SPAN, not only a duration.** `TimeWindowValue`
271
+ grows `span` (`today`, `yesterday`), which starts at midnight in the value's own
272
+ time zone rather than a fixed number of hours ago, and it is its own field
273
+ because a field called `duration` holding `today` is a field that lies. A value
274
+ carrying both keeps the span. `resolveTimeWindow` takes an optional `now`, so a
275
+ caller can resolve two windows against one moment, and `describeTimeWindow` is
276
+ new: it answers what a window is CALLED, what it resolves to, and both as one
277
+ sentence, which is the one place the trigger's text, the trigger's name and the
278
+ panel's live line all read from.
279
+
280
+ **A picker trigger is named by the value it holds.** `DateTimePicker` and
281
+ `TimeWindowPicker` each carried `aria-label={ariaLabel}` over their own text,
282
+ and an `aria-label` REPLACES the content of the element it is on: a button
283
+ drawn as "Last 7 days" announced itself as "Time window", so the one thing the
284
+ control exists to say was the one thing a screen reader could not hear from it.
285
+ The name is the label and the value now ("Time window: Last 7 days", "Starts
286
+ at: Mar 10, 2026, 09:30"), which is also what Label in Name asks for, and how it
287
+ is composed is a prop. `DateTimePicker` grows a Now (or Today, where there is no
288
+ time to set), clamped to the bounds rather than refused, and moves its own label
289
+ props into one `labels` bag; `placeholder` and `ariaLabel` stay where they are,
290
+ so `DataView`'s moment editor needs no edit. What to change: a test asserting
291
+ the old bare name, and a screen that drew a Now button of its own beside the
292
+ picker.
293
+
294
+ **`CalendarGrid` draws a range as one band.** Its ends carry `is-range-start`
295
+ and `is-range-end`, asked of `inRange` about each chosen day's neighbours
296
+ rather than passed in as two more props, and the band closes the 1px gap
297
+ between cells with an outline in its own colour. An outline rather than a
298
+ box-shadow because today's ring is a box-shadow, and a day that was both today
299
+ and inside the range used to lose the ring saying which day it was.
300
+
301
+ **`ListInput`'s rows are tighter.** A value and its Move and Remove controls
302
+ sit one scale step apart rather than two, the controls carry no gap of their
303
+ own because an icon button's square already separates them, and they sit
304
+ level with the top of the control rather than nudged down.
305
+
306
+ **The overlays wear the engine dashboard's look.** Every surface that floats
307
+ over a page repaints. `Modal`, `ConfirmModal` and anything built on them sit on
308
+ `--color-surface-veil`, the page's own background at 0.72 rather than a black
309
+ scrim, so the page behind fades to context instead of being darkened out of the
310
+ way; the panel is `--color-surface-subtle`; a dialog is TOP placed by default,
311
+ `--spacing-10` down from the top of the window; the head is centred and its
312
+ title is the body size at semibold; a sheet's head is tighter than a dialog's;
313
+ and the body's prose is the page's own ink. `Menu` and `Popover` take the same
314
+ panel step, the menu at `--radius-md` and sized by its own longest row rather
315
+ than floored at 200px, the popover floored at 260px, both entering with a short
316
+ rise from under the trigger. A menu row is one line at `--size-row-sm` with
317
+ `--spacing-2` of inline padding and `--radius-xs`; a disabled row is the
318
+ tertiary ink rather than half-strength; a row that carries a description is the
319
+ only one drawn taller. `Listbox`, and with it `Select`, `Combobox`,
320
+ `MultiPicker` and `CommandPalette`, moves to a taller row (`--spacing-2` by
321
+ `--spacing-3` at `--radius-md`) whose resting ink is the secondary step.
322
+ `Tooltip` takes the panel step and `--radius-md`. What to change: a dialog that
323
+ relied on being vertically centred passes `placement="center"`; a caller that
324
+ sized a `Menu` or `Popover` through a wrapper of its own should drop it; a
325
+ caller measuring a fixed number of visible listbox rows should re-measure. To
326
+ get the palette's keyboard band, pass `CommandPalette`'s `footer` a line of
327
+ `Kbd` hints; it is drawn at the micro register rather than a modal footer's.
328
+
329
+ **What the overlay restyle settles on top of that.** A dialog CLIPS to its own
330
+ corners, so a body with `flush` no longer paints a square edge over a round
331
+ one; a sheet has no corners and no clip. A `Menu` panel is as wide as its
332
+ longest row up to 320px and the label that does not fit truncates, where the
333
+ pair of widths it shipped with let the panel leave the window instead. A
334
+ `Popover` row wraps: it is a panel of a declared width, and a line that cannot
335
+ break has nowhere to go in one. The row a menu or a panel hands focus to is
336
+ TINTED, not only ringed, so a menu opened with the pointer shows where the keys
337
+ will land; the ring is still `:focus-visible` only. A row that is both
338
+ destructive and disabled is drawn in the tertiary ink, because the danger ink
339
+ on a row nobody can press is the loudest invitation in the palette. And
340
+ `--color-surface-veil` is each root's OWN `--color-surface-background` at 0.72,
341
+ the marketing root included, which is a rule the palette suite now measures.
342
+
343
+ **`Popover`.** Its default `role` is `dialog` rather than `menu`, because a
344
+ filter form announced as a menu of commands told a screen reader that every
345
+ field in it was a menu item. `onOpenChange` takes a second argument saying why,
346
+ and is never called on mount; the effect it replaced reported one on mount and
347
+ on every render after it.
348
+
349
+ **`Button` and `IconButton`.** Sizes follow `--size-control-*`, so a small
350
+ button is 28px and a large one 44px. `IconButton`'s `sm` step is 24px rather
351
+ than 22px, which is the smallest target WCAG 2.2 accepts.
352
+
353
+ **Glyphs are nodes, not names.** `TabsItem.icon`, `DataTable`'s `icon` and
354
+ `actionIcon`, `DataTableRowAction.icon` and the Toaster's `icon` each take a
355
+ glyph component now, and the `material-symbols-outlined` class has left
356
+ component markup.
357
+
358
+ **Every dropdown is the design system's own.** `Select` draws its listbox in
359
+ every mode it is used in, which is what 0.2.0 did and what the product's own
360
+ look asks for: the list takes the theme, the density, the tokens and the layer
361
+ stack, rather than the operating system's palette in the middle of a dark
362
+ dialog. What a reader used to get free from the platform it now earns back for
363
+ itself, and those rules are shared with every other list in the package
364
+ (`useOptionKeys`): type-ahead, Home and End, disabled answers stepped over, the
365
+ highlight announced through `aria-activedescendant`, Tab closing the list and
366
+ carrying on to the next control, Escape stopping at the list, and a stored
367
+ value the options no longer offer kept rather than swapped. A real `<select>`
368
+ is available as `mode="native"` for a surface that wants the phone's own
369
+ full-screen picker, and a call site that asks for it should say why. The two
370
+ inside `TimeWindowPicker`, its timezone and its custom unit, had no such reason
371
+ and are drawn here now.
372
+
373
+ **An empty answer the options offer is a choice.** A `Select` reads the empty
374
+ string as "nothing chosen" and shows its placeholder, unless the list itself
375
+ offers an option whose value is empty, which every filter row does ("Any
376
+ role"). On those lists the placeholder was drawn OVER the answer already
377
+ selected. What to change: a caller that offered `{ value: '', label: 'Any' }`
378
+ AND a `placeholder` now sees the option's own label, which is what it meant.
379
+
380
+ **A search field's clear control is a real button.** `<Input type="search">`
381
+ used to leave it to `::-webkit-search-cancel-button`, which one browser family
382
+ draws, nobody else does, and no keyboard anywhere can reach. Pass `onClear` and
383
+ the field draws the package's own: named, in the tab order, and it hands focus
384
+ back to the box it emptied. Without `onClear` a search field now has no clear
385
+ control at all, which is the honest version of what most readers already had.
386
+
387
+ **A table that does not page is not offered a page size, and draws no
388
+ chevrons.** `DataTable`'s settings frame always drew an items-per-page row and
389
+ its toolbar always drew Previous, a page number and Next; with
390
+ `paginated={false}` the caller slices its own rows, so nothing read that number
391
+ and neither chevron could ever be pressed. Both are drawn for a table that
392
+ reads them: `paginated`, or a caller that says it reads them itself
393
+ (`onItemsPerPageChange` for the size, `onPageChange` with `pageCount` for the
394
+ chevrons). A table that passes none of those keeps the rest of the frame, the
395
+ column order and visibility and the wrap-lines toggle, unchanged.
396
+
397
+ **The page, its size and the column order are controlled pairs.** `page` /
398
+ `onPageChange`, `itemsPerPage` / `onItemsPerPageChange` and `columnOrder` /
399
+ `onColumnOrderChange`, each the shape `visibleColumns` / `onVisibleColumnsChange`
400
+ already had: the table owns the choice when the pair is absent, and the caller
401
+ owns it when it is present. Which is the split the table now states outright.
402
+ What is on screen, the page, the size, the columns shown, their order and the
403
+ sort, is what a reader would send somebody, so every one of them can be lifted
404
+ into a screen's own history (a URL). What is a fact about this browser, the
405
+ wrapping and the column widths, stays in `localStorage` under `storageKey`. A
406
+ controlled choice is neither read from storage nor written to it, so the two
407
+ can never disagree on a reload.
408
+
409
+ **`All` means every row, not the row count when it was pressed.** The chip
410
+ wrote `data.length`, so a reader who chose All at twelve rows was on "twelve
411
+ per page" and the table began paging silently at the thirteenth; on an empty
412
+ table it wrote 0, and a page of no rows hid every row that arrived afterwards,
413
+ for the life of that `storageKey`. It is the sentinel `ALL_ITEMS` (`'all'`)
414
+ now, in the prop, in the callback and in storage, and the chip's accessible
415
+ name carries the count ("All 1,284 rows") because drawing every row of a long
416
+ list is a decision with a size on it.
417
+
418
+ **A column can be one a reader may not hide.** `DataTableColumn.hideable:
419
+ false` marks the column that says which row this is, and the last visible
420
+ column cannot be unticked either, in the settings frame or in the Columns
421
+ panel. Both drew a tick that unticked, and a table with no columns is a spacer
422
+ with a footer.
423
+
424
+ **The column list reorders from a control with a name.** The drag handle was an
425
+ `aria-hidden` glyph on a row draggable along its whole width. It is a button
426
+ now: named "Reorder <column>", in the tab order, and the drag starts from IT
427
+ rather than from anywhere in the row. Its arrow keys move the column, as the
428
+ Move up and Move down buttons beside it do; those two are `aria-disabled` with
429
+ a reason at the ends of the list rather than `disabled`, because disabling the
430
+ button a press is on drops focus to the document body. Each move is announced.
431
+
432
+ **A resize handle is a separator, not a button.** `role="separator"` with
433
+ `aria-orientation`, `aria-valuenow` and `aria-valuemin`, which is the shape a
434
+ reader's software already knows how to step. A column header is named by its
435
+ label alone now (`aria-labelledby`), because a cell named from its contents
436
+ reads every control that has drifted into it: with the handle a real control,
437
+ each header announced itself as "Seat Resize Seat".
438
+
439
+ **Reset to Default restores the order a table opens in.** It cleared the sort
440
+ outright, while the header's own reset put `defaultSort` back: one button, one
441
+ name, two meanings of "default". A table that opens ordered by spend, or by
442
+ when a turn started, answered Reset to Default with the order its rows happened
443
+ to arrive in. A caller holding the sort is told `defaultSort` now rather than
444
+ `null`, so a screen keeping the sort in a URL stops carrying a non-default
445
+ after a reset. `DataView` passes its own `defaultSort` down for the same
446
+ reason. What to change: a table whose default really is no order at all passes
447
+ no `defaultSort`, which is still what it gets.
448
+
449
+ **The settings cog no longer depends on a row-actions column.** In
450
+ `variant="default"` the cog lived inside the actions header, so a table with no
451
+ `rowActions` and no `onRowAction` had no way into its own settings at all. The
452
+ trailing chrome column is drawn for the cog on its own, which is one more
453
+ column in the row of a default-variant table that has none today.
454
+
455
+ **The Columns panel is gone from the table's chrome.** `DataTable`'s toolbar
456
+ carried a Columns popover, drawn for a caller that passed the
457
+ `visibleColumns` / `onVisibleColumnsChange` pair, and the settings frame owns
458
+ that job: a reader looking for which columns are shown goes to the gear, and
459
+ two controls for one answer is two places for the rules about it to disagree
460
+ (they already had, over the column a table may not hide). The frame is the way
461
+ in for every table now, the toolbar gets the width back, and a table with no
462
+ chevrons and no gear draws no toolbar row at all rather than one holding the
463
+ panel alone. `labels.columnsMenu` and `labels.columnsMenuTitle` go with it,
464
+ as do `.crewlet-data-table__columns-panel`, `__columns-fieldset` and
465
+ `__columns-legend`. The controlled pair is untouched and still drives the
466
+ table: what it changes is where the answer is KEPT, never how it is reached.
467
+ What to change: a caller that passed the pair alongside `showSettings={false}`
468
+ has taken the reader's only way in with it, so it draws a control of its own
469
+ or drops `showSettings={false}`.
470
+
471
+ **Every table opens the same settings frame.** `settingsVariant` is gone, and
472
+ so is the `compact` shape it chose: a table with a title, a description or an
473
+ icon used to get a frame holding the page size and the wrapping and nothing
474
+ else, so the only way to its columns was the Columns panel in the toolbar. One
475
+ frame now, at the wide step, holding the items-per-page chips, Wrap lines, and
476
+ the column list with its handles, its ticks and its Move up and Move down. The
477
+ two shapes differed in nothing a caller could have wanted besides that list: a
478
+ dialog carrying a drag-reorder list wants the width whatever the table above it
479
+ looks like. `labels.pageSize` goes with it, since the section is named
480
+ `labels.itemsPerPage` on every table. What to change: drop `settingsVariant`,
481
+ and where a card-style table was chosen for the shorter frame, expect the full
482
+ one.
483
+
484
+ **The count line under a table is gone, and so is `TableFooter`.** Every list
485
+ closed on a row saying "Showing 12 of 300", or "12 rows" where nothing was
486
+ hidden. What it said is said better where a reader already looks: how many of
487
+ a thing there are is a fact about the LIST rather than about the table drawing
488
+ it, so it belongs in the screen's own header beside the name of the thing
489
+ counted (`PageHeader`'s `badges`, or `Card.Header`'s `count` over a panel
490
+ table); which filters narrowed it is on the chips a step above the rows; and
491
+ which rows of how many are on this page is what the pager announces, the
492
+ control that moved the reader there. A second number under the table was a row
493
+ of chrome on every panel and a figure free to disagree with the one above it.
494
+ The component is deleted rather than left undrawn: nothing in the package
495
+ renders it, and the only shape a caller had left was hand-drawing it beside a
496
+ `DataTable`, which is the thing being removed. `DataView`'s `totalCount`,
497
+ `pagination` and `labels.footer` go with it, each having had the footer as its
498
+ only reader. What to change: put the count in the screen's header, and reach
499
+ for `DataTable`'s own `hasMore` / `onLoadMore` for a Load more control, which
500
+ draws under the rows inside the table.
501
+
502
+ **A table that pages can be paged, whichever variant it is.** The pager was
503
+ the compact variant's alone and `paginated` defaults to true in both, so a
504
+ `variant="default"` table sliced its rows into pages of ten and drew nothing to
505
+ turn them with: on twenty-five rows, fifteen of them were reachable only by
506
+ opening the settings frame and choosing All. Which control a reader is offered
507
+ is a question about whether the table pages, not about how it looks, so the
508
+ chrome row is drawn on either variant wherever there are pages to turn. The
509
+ variant still decides where the COG goes, which is the trailing chrome column's
510
+ header on a default table and that row on a compact one, so neither draws it
511
+ twice. `--crewlet-data-table-chrome-pad-x` moves onto the root both variants
512
+ carry: unset on the default one, every padding written through it was an
513
+ invalid declaration a browser drops whole, and the bar would have arrived with
514
+ no inset at all. What to change: a default-variant table that should not page
515
+ passes `paginated={false}`, which is what it always meant.
516
+
517
+ **A table inside a card draws its controls on the card's header.** A panel
518
+ whose header says what the rows are and how many there are, with a bar under it
519
+ holding a pager and a cog and nothing else, is two rows of chrome over one
520
+ table. Where a compact `DataTable` fills a `Card` that has a `Card.Header`, the
521
+ pager and the settings cog are now drawn at the end of that header row and no
522
+ toolbar is drawn for them; the header keeps its icon, its title and its count
523
+ on the left, and the card's own `actions` keep the end of the row with the
524
+ table's controls closing onto them. Nothing is passed between the two: the card
525
+ publishes the row and the table draws into it, so a screen writes a card, a
526
+ header and a table exactly as before. The row is taken by ONE table, so a card
527
+ holding two of them puts the first on the header and leaves the second with its
528
+ own bar; a table that titles itself keeps its controls in its own header; and a
529
+ table outside a card, or in a card with no header, is unchanged. The seam is
530
+ `useCardHeaderSlot`, exported for any component that draws controls of its own.
531
+ `.crewlet-data-table__toolbar-end` is renamed `.crewlet-data-table__chrome`,
532
+ since the row is no longer always in a toolbar, and every rule dressing the
533
+ pager and the cog is now written through it rather than through the table's
534
+ root, which is no longer an ancestor once a card's header holds them. What to
535
+ change: nothing, unless a stylesheet targeted the old class name.
536
+
537
+ **A table keeps itself inside the container it is drawn in.** Its columns are
538
+ fitted to that container by a pass that measures the scroller, and three things
539
+ were stopping it. The widths the pass computed were saved under the same entry
540
+ as the widths an operator drags to, and the existence of that entry turned the
541
+ pass off for the life of the table, so it found its own arithmetic in the store
542
+ on the next mount and stood down: a window narrowed after the first paint left
543
+ every column at the width a wider one had justified. Which columns are on was
544
+ watched by nothing, so turning a hidden one back on added its width to a row
545
+ already exactly as wide as its box. And what asked for a fit was a flag the
546
+ pass could only clear once it had something to measure, so a table mounted in a
547
+ panel that is not on screen never fitted at all once it opened. Measured on the
548
+ engine dashboard before the change: twelve of twelve tables scrolling sideways
549
+ by 300 to 500px, seven of them with no rows in them. Only the columns a reader
550
+ resized are persisted now, under `<storageKey>_resizedColumns`, and the fit
551
+ treats those exactly as it treats a `width` a caller locked;
552
+ `<storageKey>_columnWidths` is retired and deleted on mount, because nothing in
553
+ it could be told from the pass's own arithmetic. The shrink also takes its room
554
+ from every column still above the floor rather than from whichever is last in
555
+ the row, and a row a single pixel over its container is corrected rather than
556
+ tolerated. What to change: nothing, unless a screen relied on a table holding
557
+ widths measured against some other container. A reader who had dragged a column
558
+ wide sees it back at the table's own width once.
559
+
560
+ **A table with no rows is laid out by its content.** A column width is a claim
561
+ about content: this column needs this much room for what is in it. A table
562
+ drawing one full-width message instead of rows, because it has nothing to show
563
+ or an error to show in its place, has no such content, and under the fixed
564
+ layout that claim is an instruction the browser carries out whatever it costs,
565
+ so the row came out wider than the box it sits in with a reader scrolling
566
+ sideways past columns holding nothing. The widths stay stated, so the header
567
+ keeps the shape its rows will land in; the table carries
568
+ `crewlet-data-table__table--rowless` until they arrive, and that class puts it
569
+ on the automatic layout, which reads a stated width as a preference and shrinks
570
+ the row to its container rather than past it. The loading skeleton keeps the
571
+ fixed layout, because its bars stand in for the rows about to replace them.
572
+ What to change: nothing.
573
+
574
+ **A frozen table scrolls rather than clip.** A rail carrying a `sticky: 'right'`
575
+ column locked its scroller to `overflow-x: hidden`, on the reasoning that the
576
+ fit pass always redistributes the regular columns to fit. The fit has a floor,
577
+ `minColumnWidth`, so a container narrower than those floors plus the pinned pane
578
+ is one the columns cannot be fitted to, and what the lock did there was clip
579
+ them: measured in a 460px box, 164px of the row was cut off, reachable by no
580
+ wheel, trackpad or bar a reader has. The row scrolls in that state and the pane
581
+ stays pinned to the scroller's right edge at its own width. The gutter the fit
582
+ pass reserves beside that pane is read off the cell that draws it rather than
583
+ assumed, so it is right at every density: it was a flat 24px, and the
584
+ comfortable density draws 27, which put the row three pixels past its container
585
+ and is a scrollbar now that the rail is not locked shut. What to change:
586
+ nothing.
587
+
588
+ **`TagsInput` has its own folder**, so `@crewlethq/ui/TagsInput` carries its
589
+ stylesheet. Importing `styles.css` whole, or the root barrel, changes nothing.
590
+
591
+ **The focus ring is an OUTLINE.** Nineteen component rules drew it as `outline:
592
+ none` plus a box-shadow, and forced-colors mode drops every box-shadow and
593
+ keeps outlines, so that ring disappeared entirely for the readers who most need
594
+ one. An application stylesheet that restyled a uilet component's focus ring by
595
+ overriding its `box-shadow` no longer reaches it and overrides `outline`
596
+ instead.
597
+
598
+ **`Tabs` renders a `div role="tablist"`, not a `nav`.** A tab strip is not a
599
+ navigation landmark, and a screen reader listing a page's landmarks was offered
600
+ every strip on it. A stylesheet or a query that selected the `nav` element
601
+ selects `.crewlet-tabs` instead; a class selector is unaffected.
602
+
603
+ **`Tabs` is one tab stop, and selection does not follow focus.** Every tab
604
+ used to be a tab stop and no arrow key did anything. The arrows, Home and End
605
+ move along the row now and Enter or Space selects, because a tab is a section
606
+ that pushes a history entry. Tabs carry `aria-selected` and no longer carry
607
+ `aria-current`: on a tab that told a screen reader the tab was the page the
608
+ reader was on. In `renderItem` (router) mode the root is a `nav` and nothing
609
+ carries a tab role, which is what a row of links has always been.
610
+
611
+ **`Checkbox`'s accessible name is now the label alone.** The label element
612
+ wrapped the description too, so the name was the whole paragraph and the
613
+ consequence of ticking the box was buried inside the name of the box. The
614
+ description is pointed at with `aria-describedby`, merged with any the caller
615
+ passes. The root is the `label` element rather than a div beside one, so the
616
+ whole row is one target.
617
+
618
+ **`FormField` renders the help line BESIDE an error, not instead of it**, and
619
+ joins both into `aria-describedby` in reading order. A form that relied on the
620
+ help line disappearing when a value was refused now shows both.
621
+
622
+ **`Select`'s keyboard changed.** Its list is a popup on the layer stack, so
623
+ Escape closes the list and stops there, and a press on a dialog's veil closes
624
+ the list and leaves the dialog. The arrows, Home and End step over disabled
625
+ options, type-ahead reaches a row, and `aria-selected` marks the CHOSEN option
626
+ rather than the highlighted one, which `aria-activedescendant` points at. The
627
+ document `mousedown` listener and the swallowed presses inside the panel are
628
+ gone with it.
629
+
630
+ **`TimeWindowPicker` validates by round trip.** 31 February, 31 April and
631
+ 24:60:60 were accepted and rolled forward by `Date`; they are refused. The
632
+ year is no longer capped at 2030. Its masked boxes hold only what was typed,
633
+ with the mask letters drawn as an aria-hidden overlay rather than being the
634
+ input's own value, and every key that is not a printable non-digit reaches the
635
+ browser again.
636
+
637
+ **`DateTimePicker`'s month is a real grid.** It was 42 buttons in a row under
638
+ a `grid` role with no rows in it, each its own tab stop. It is one tab stop
639
+ with the arrow, Home, End and Page keys, and each day is named by its full
640
+ date. A stylesheet that targeted the old `.crewlet-datetime__cell` or
641
+ `.crewlet-datetime__grid` classes targets `.crewlet-calendar__day` and
642
+ `.crewlet-calendar` instead.
643
+
644
+ **`TagsInput` draws its chips above the field**, not inline in the box, and
645
+ its markup is `.crewlet-tags-input__chips` over `.crewlet-tags-input__field`.
646
+ A value shared a box with the text somebody was typing, so a long one pushed
647
+ the caret off the end and the Move controls an ordered chain needs had nowhere
648
+ to go.
649
+
650
+ **`Modal` closes on the backdrop's CLICK**, not its `mousedown`. The overlay
651
+ used to be gone before a tap's compatibility mouse events were hit-tested, so
652
+ the click a finger ended with landed on whatever the dialog had been covering.
653
+ A test that fired `mouseDown` on the overlay to close a dialog fires `click`.
654
+
655
+ **Headings follow where a component sits.** `Accordion.Item`'s trigger and
656
+ `Card.Title` used to render `h3` whatever was above them. Both now take the
657
+ level the surrounding surface declares, which is `h2` at the top of a page and
658
+ one deeper inside a `Card` or a titled `Section`. A hard-coded `h3` in an
659
+ outline is a level missing, which is what a screen reader navigating by
660
+ heading meets. Pass `headingLevel` on an Accordion, or `as` on a Card title, to
661
+ pin one.
662
+
663
+ **`Copyable` is ONE tab stop.** Its value was a `role="button"` span with its
664
+ own `tabindex` beside the button that did the same thing, so every identifier
665
+ in a table was two stops on the way to the next row. The value is plain text
666
+ now, and the icon button is the only control; a click on the value no longer
667
+ copies. It also REPORTS A REFUSAL rather than showing the tick over an empty
668
+ clipboard, which is what it did on any origin the Clipboard API is not
669
+ available at.
670
+
671
+ **`Card`'s section padding is applied.** `padding` on `Card.Header`,
672
+ `Card.Body` and `Card.Footer` was declared and never read, so it reached the
673
+ DOM as a `padding="md"` attribute and styled nothing. It is a class now, and
674
+ `Card.Body` and `Card.Footer` take the SAME five steps as the card itself, so
675
+ one word names one inset wherever it is said. `Card.Header` takes none at all:
676
+ its rhythm is the header row's, and `divided` is the control over it. A
677
+ selector matching `[padding]` matches nothing. An interactive card also lifts
678
+ by its shadow rather than by a one pixel `translateY`, so a grid of them no
679
+ longer shifts under the pointer.
680
+
681
+ **`CodeBlock`'s lines carry a real newline**, so `textContent` and a selection
682
+ keep the structure they used to lose, and with line numbers off the code is one
683
+ text node. Its header control is `CopyButton`, which is a labelled button
684
+ ("Copy") rather than the icon-only one named "Copy code to clipboard".
685
+
686
+ **`Accordion`'s trigger hover is neutral**, not the accent, and the trigger
687
+ carries `crewlet-disclosure__trigger` alongside `crewlet-accordion__trigger`,
688
+ because `Accordion.Item` and `Disclosure` are now one implementation.
689
+
690
+ **`Container`'s narrow gutter switches at 480px**, the small breakpoint token,
691
+ rather than at 640px, which was a step nothing else in the system uses.
692
+
693
+ **`Kbd` is set in the mono face, and `subtle` no longer fades it.** A keycap is
694
+ a literal, and a proportional face draws `l`, `I` and `1` as three widths of
695
+ one mark. `subtle` used to be `opacity: 0.7`, which quiets the printed key as
696
+ well as the frame and measured 3.43:1 on the panel; it gives up its raised edge
697
+ and its fill instead and takes the tertiary ink, which is measured at 4.5:1 on
698
+ every surface. A hint that names the key a reader is told to press is the last
699
+ thing on a row that may be unreadable.
700
+
701
+ **One spelling for a status.** `Tag`'s `warn` variant is `warning`, and the
702
+ Toaster's `warn` and `error` are `warning` and `danger`. The package shipped
703
+ `warn` in two components and `warning` in two others, and `danger` beside
704
+ `error`, so the same state had two names depending on which component drew it.
705
+ A tag's tones are also the phase vocabulary now (`phase-onboarding`,
706
+ `phase-execute`, `phase-review`), and every tone takes its fill, ink and line
707
+ from the token layer rather than from the five dark-theme literals it used to
708
+ spell, which measured between 1.14:1 and 1.58:1 on a light page.
709
+
710
+ **Nothing animates on arrival.** A `Tag` no longer fades in on mount: tags
711
+ arrive in their hundreds on a data push, and a list that animates every arrival
712
+ is one nobody can read while it is arriving. `animateIn` opts back in, guarded
713
+ by reduced motion.
714
+
715
+ **`Toaster`.** Its live regions are mounted from the start rather than arriving
716
+ with the first toast, split into a polite one and an assertive one, and no
717
+ toast carries a region of its own. Dismissal is a timer that pauses on hover
718
+ and on focus and keeps what is left of it, where it used to be the drain's
719
+ `animationend`: under reduced motion, and under any application rule that
720
+ collapses animations, a toast never went away at all. A toast with no
721
+ `duration` now takes its variant's default (4s for `success` and `info`, sticky
722
+ for `warning` and `danger`) rather than being sticky, and the stack portals
723
+ into the nearest `LayerHost`. `ToastProvider` and `useToast` are new.
724
+
725
+ **`Callout` sets no role.** Every callout was a `role="status"`, so a screen
726
+ reader arriving at a screen read out each static banner in turn and one that
727
+ had been up for an hour was announced as news. Announcing is opt-in through
728
+ `live` or an explicit `role`. It also always draws a glyph, which cannot be
729
+ turned off: under deuteranopia the warning and danger hues move towards one
730
+ another and a callout has no label of its own to fall back on.
731
+
732
+ **`Avatar` is neutral.** The initials fallback no longer takes the brand fill,
733
+ and the seeded tints need `tone="seeded"` beside `colorSeed`. A hash of a name
734
+ carries no information: rename the seat and its colour changes. Its default
735
+ size is the `md` step (32px) rather than 64px, and its initials split on
736
+ hyphen, underscore and dot as well as whitespace, so `backend-engineer` is BE.
737
+
738
+ **`StatCard` drops the `brand` tone**, and the tone it is given now paints the
739
+ VALUE rather than the icon, which is what its props always said. A `loading`
740
+ tile draws a placeholder line and carries `aria-busy` instead of a literal
741
+ `--`, which a screen reader reads as "dash dash".
742
+
743
+ **`Skeleton` drops `glowColor` and `animationDuration`**, and the
744
+ `SkeletonGlow` type with them. Each of the five glows was a raw `rgba` outside
745
+ the palette, and a blue placeholder and a red one say nothing different about
746
+ content that has not arrived.
747
+
748
+ **`Modal` names itself.** `labelledBy` is gone: the title is linked by an id the
749
+ component mints, so a dialog is named whether or not the caller remembered to
750
+ name it, and a surface that draws no title takes `ariaLabel` instead. None of
751
+ conlet's sixteen dialogs passed `labelledBy`, and every one of them was
752
+ announced as "dialog" and nothing more. `describedBy` still overrides, and
753
+ defaults to the subtitle.
754
+
755
+ **`Modal`'s title is not a heading element.** It was an `h2`; it is drawn at the
756
+ heading register on a `p` now, and a surface PROVIDES heading level 2 to
757
+ everything inside it, so a section in a dialog is an `h2` however deeply the
758
+ component that opened the dialog was nested. The dialog role already announces
759
+ the title, and a reader moving by heading met it twice. A stylesheet on
760
+ `.crewlet-modal__title` is unaffected; a query for an `h2` inside a dialog is
761
+ not.
762
+
763
+ **`Modal`'s footer holds two slots.** `footerStart` is drawn at the inline start
764
+ and `footer` at the inline end, each in a wrapper of its own, so a stylesheet
765
+ selecting `.crewlet-modal__footer > button` selects nothing now and wants
766
+ `.crewlet-modal__footer-end > button`.
767
+
768
+ **`ConfirmModal` mid-request refuses every way out.** `submitting` used to stop
769
+ the veil alone, so Escape and the close control still abandoned a request whose
770
+ outcome nobody had seen; it maps to `dismissable` now, which is all three. The
771
+ confirm button reports `aria-busy` instead of having its label swapped for
772
+ "Working", the prompt is a form whose confirm is the submit button (so Enter
773
+ from a field answers it), a `destructive` prompt is an `alertdialog`, and a
774
+ rejected `onConfirm` leaves the prompt open rather than becoming an unhandled
775
+ rejection. `confirmDisabled` and `submitting` no longer set the native
776
+ `disabled` attribute on the prompt's buttons either: each is soft-disabled with
777
+ `aria-disabled` and a reason, so the button keeps its focus and its pointer
778
+ events and can say why it refuses, which a natively disabled control cannot.
779
+
780
+ **`DataTable` sorts and marks rows differently.** The first press on a header
781
+ sorts ascending unless the column says otherwise with `firstDirection`, the
782
+ comparator is index-stable, and an absent value (null, undefined or NaN) sorts
783
+ last in BOTH directions, because a seat with no meter has not spent the least,
784
+ it has not been measured. A row's expanded state moved from the `tr` to a
785
+ button inside it, so `aria-expanded` is on the control rather than on the row;
786
+ a press that starts on a link, button or field inside a row no longer also
787
+ fires `onRowClick`; and the page resets on a filter or a sort rather than on
788
+ the row count. The props are generic in the row type (`DataTableProps<TRow>`),
789
+ which only a TypeScript consumer sees.
790
+
791
+ **A DataTable row action says `danger`, not `variant`.** `DataTableRowAction`
792
+ takes `danger: true` in place of `variant: 'danger'`, and `variant: 'primary'`
793
+ is gone: the kebab is the package `Menu` now rather than a second menu built
794
+ inside a Popover, and a menu has one destructive step and no headline one. Its
795
+ markup and its `crewlet-data-table__row-actions-*` classes went with it.
796
+
797
+ **A DataTable's archived row is neutral, and names its own property.** The
798
+ row used to be tinted and railed in a red the stylesheet mixed itself, which
799
+ says "this failed" about a row whose only fact is that nobody is using it any
800
+ more; a state that is not a problem takes no status hue. The rail is
801
+ `--crewlet-data-table-archived-rail`, one colour rather than the RGB triple
802
+ `--crewlet-data-table-archived-tint-rgb` carried, and that property is gone,
803
+ so an application that set it now has a neutral rail and no error to say so.
804
+
805
+ **`Table` renders a table.** It was a grid of `div`s and `span`s, which a
806
+ screen reader reads as a pile of text with no columns and no header
807
+ association. It emits `table`, `thead`, `th scope="col"`, `tbody` and `td`
808
+ now, with the same look and the same class names, so a stylesheet that
809
+ selected `.crewlet-table__row` as a `div` finds a `tr`.
810
+
811
+ **`AppShell` owns the layout.** It used to be passive: the slots positioned
812
+ themselves with `position: fixed` and the shell only reserved a matching margin
813
+ and padding on the content column, so every consumer repeated the rail's width
814
+ and the bar's height as literals in its own stylesheet. It is a grid now, sized
815
+ from `--size-shell-rail` and `--size-shell-topbar`, at `100dvh` with `overflow:
816
+ hidden` on the root, with real landmarks (an `aside` rail, a `header` outside
817
+ `main`, a focusable `main`), a skip link, and a narrow layout in which the rail
818
+ becomes a modal drawer on the layer stack. `sidebarWidth` and `topbarHeight` are
819
+ gone with the margins they set. The slot NAMES do not change: `sidebar` is still
820
+ `sidebar` and `topbar` is still `topbar`, and `AppShell.Rail` and
821
+ `AppShell.Topbar` are new sub-components to render into them.
822
+
823
+ **The rail's rows are chrome, and the row the reader is on takes the accent.**
824
+ `SidebarNav` drew the current row as a neutral lift with a one-pixel accent bar
825
+ outside its left edge, and its rows at the body step in the regular weight on a
826
+ 12px corner. A rail is read at a glance rather than read through: a row is the
827
+ compact step in the medium weight on the chrome's own 8px corner now, and the
828
+ row the reader is on takes the accent's soft tint AND the accent ink, for its
829
+ label and for its glyph together. Position and hue, so it is found by a reader
830
+ who sees the hue and by one who does not. The glyph keeps no ramp of its own
831
+ either: it is the decoration step at rest and the row's own colour on a hovered
832
+ row and on the current one. Every pair is measured on the rail's own composites
833
+ by @crewlethq/tokens' palette suite. What does not change is where the state
834
+ comes from: `aria-current="page"` and nothing beside it.
835
+
836
+ **A rail row reads at full ink, and the rows in a group are flush.** A row at
837
+ rest takes `--color-text-primary` rather than the secondary step: a destination
838
+ is not quiet text, and fifteen rows in the quiet step are a column of grey a
839
+ reader has to hunt through. Contrast goes up on every ground a row can have,
840
+ and what carries hover is now the tint and the glyph alone, because there is no
841
+ ink step above the resting one. `NavGroup` also stopped putting a step between
842
+ the rows it holds: the group's label has its own bottom inset and the rail's
843
+ own gap separates one run from the next, so a step per row was 4px fifteen
844
+ times over, which stood the engine's rail 823px in a 755px scroller and left
845
+ its last two destinations reachable only by scrolling. A group whose label is
846
+ empty (`""`, `null` or `false`) draws no heading box at all and adds no level
847
+ for a screen reader to walk, where an empty `<div>` used to carry the label's
848
+ own padding and put sixteen blank pixels at the top of the rail.
849
+
850
+ **A nav badge has a tone, and the slot is what paints it.** `NavItem` takes
851
+ `badgeTone="attention"` for the one badge in the chrome allowed a status hue: a
852
+ count of what is waiting on a person, drawn as the warning ink on the warning
853
+ tint in a pill. The slot draws every badge now, so whatever is passed in
854
+ contributes its text and the name a screen reader reads after the row's own,
855
+ and a `<Count>` in a rail row is a figure rather than a second pill inside the
856
+ first. A caller that relied on a `<Count>` keeping its own inset pill in a nav
857
+ row sees a bare figure instead, which is what a rail badge was always meant to
858
+ be.
859
+
860
+ **The rail's head draws no divider, and its foot has a row shape.** `AppShell`
861
+ put a bottom border under `AppShell.Rail`'s header, at the same height as the
862
+ top bar's own, so the two read as one rule running the width of the window with
863
+ the brand and the screen title above it like a second bar. The rail is one
864
+ column: the bar's line belongs to the bar and the rail's right edge separates
865
+ the two. `AppShell.RailRow` is new, for the foot: a row on the rail's own two
866
+ insets (the gutter and the row pad), so a status line's mark lands on the line
867
+ every nav glyph above it sits on, with the 24px target floor that
868
+ `--size-row-sm` carries. It is a control only when it takes an `onClick`, and
869
+ `label` is refused by the type without one, because `aria-label` on an element
870
+ with no role is a name nobody is ever told.
871
+
872
+ **And the foot claims whatever else lands in it.** An application with a status
873
+ control of its own reaches for that one whatever this package offers, so a
874
+ `Button` that is a direct child of `AppShell.Rail`'s `footer` is drawn as a row
875
+ in the rail: the gutter and the row pad, the quiet ink, the regular weight,
876
+ left aligned, filling the rail, its label truncating, and its focus ring inside
877
+ its own box because the shell clips at exactly the viewport's height. A button
878
+ dropped there used to be centred in a 263px rail on a toolbar's inset, in a
879
+ toolbar's ink. Only a DIRECT child is claimed, so the theme and density
880
+ switchers a foot also holds, which sit in a row of their own, keep their own
881
+ register. The row itself is a spacing step above and below its line (34px at
882
+ the normal density) rather than a control height, with `--size-row-sm` under it
883
+ as the 24px target floor, and its transparent boundary spends one pixel of that
884
+ step rather than adding one, so a bordered row and a borderless one in one foot
885
+ stand the same height.
886
+
887
+ **The brand lockup is one block in the rail's own register.** `BrandLockup`
888
+ put a 4px gap between the product name and the context line under it, which
889
+ read as a name with a caption rather than as one lockup; the leading is the
890
+ space between them now, and it is the document's own rather than a tight step,
891
+ because the eight pixels it spends are eight the rail's head already has and
892
+ the two lines otherwise sat closer together than any other pair of lines in the
893
+ product. The context line drops from the wider uppercase step to the wide one,
894
+ which is the step the rail's group labels take: the two uppercase runs in the
895
+ rail sit twenty pixels apart, and at the wider step the company's name was
896
+ visibly broader than the sections under it. It also drops to the regular
897
+ weight, which is the one thing left separating it from those group labels: at
898
+ the same size, tracking, case and weight the company's name read as a third
899
+ section heading. The mark stays 24px, because that box is also the link's
900
+ target height and 22px would take the one control that always means "go home"
901
+ under the 24px floor.
902
+
903
+ **A keycap and a monogram are set on the document's own line.** `Kbd` took a
904
+ tighter step than the text it is drawn inside, so a cap stood a pixel under its
905
+ line and every row holding one sat a pixel off the rows that did not; a cap is
906
+ 21.5px tall now rather than 20.4px. `Avatar`'s initials took a leading of 1 and
907
+ take the document's too, which draws identically (a fixed square centres
908
+ whatever line it holds) and is one number fewer in a component that sets four.
909
+
910
+ **`Input`'s widest step is 520px**, up from 480px. The steps are named for what
911
+ they hold and the widest one is the field a reader types a SENTENCE into rather
912
+ than a name, which is what the engine's knowledge search asks for: its own
913
+ placeholder runs past sixty characters and was cut mid-word at the step below.
914
+ Nothing spells `width="lg"` in the console today, so nothing there moves.
915
+
916
+ **The engine's look: the marks, the status surfaces and the feedback.** Every
917
+ one of these draws what the engine dashboard draws, which is the look the
918
+ product is standardising on. Nothing about any component's API, keyboard model,
919
+ ARIA or focus ring changes.
920
+
921
+ - **`Tag`** is the engine's badge: an 11px label in the medium weight, a 4px
922
+ corner, one pixel of padding above and below and eight on each side, on the
923
+ tone's soft tint under its ink. An unsized tag renders at `size="sm"`, which
924
+ is that 20px badge, where it used to render at 28px; `size="md"` keeps 28px
925
+ and moves to a 12px label. A tag that ACTS is 24px, because the engine's own
926
+ 20px press is a target under the WCAG 2.2 floor and that is the one place its
927
+ look and the rule disagree. A phase variant renders UPPERCASE in the
928
+ micro-label register. A neutral `outline` tag takes the tertiary ink. A
929
+ `dot` inside a pill is drawn in the PILL'S OWN INK rather than in the
930
+ tone's fill step: the fill is measured as a mark against a page surface,
931
+ and on the tone's own soft tint the accent fell to 2.65:1 and the danger
932
+ red to 2.96:1 in the dark palette.
933
+ - **`FilterChip`** is neutral until it is on: the tertiary ink at the regular
934
+ weight, 8px of side padding rather than 12px. Its count inherits the chip's
935
+ ink rather than drawing a step quieter.
936
+ - **`Avatar`** is a ROUNDED SQUARE unless `shape="circle"` is passed, and the
937
+ square's corner moves with the box (4px at 20, 8px at 26 and 32, 12px at 40).
938
+ A NUMERIC size follows the same ladder rather than taking one fixed step,
939
+ so `size={24}` draws an 8px corner where it used to draw 12px, and any box
940
+ of 36px or more keeps the 12px it had. A dashed human badge takes the
941
+ default hairline. `ImageUpload` follows both: its trigger and its overlay
942
+ round with the badge inside them rather than at a step of their own.
943
+ - **`EntityChip`** takes the accent ink on hover as well as underlining.
944
+ - **`Callout`** draws no coloured boundary: the tint IS the boundary. Its
945
+ padding is 8px and 12px rather than 12px and 16px, and its corner 8px.
946
+ - **`Toaster`** lands on the panel surface with an 8px corner and the md
947
+ shadow, 16px from the viewport edge, 420px wide. A danger toast paints its
948
+ title, message and close control in the danger ink.
949
+ - **`Skeleton`**'s line, avatar, circle and button sweep a gradient rather than
950
+ fading a flat fill, at 1.5s; its card, list-item and info-item frames no
951
+ longer pulse.
952
+ - **`EmptyState`** sets its title in the secondary ink and its description in
953
+ the tertiary.
954
+ - **`Meter`**'s track is 4px at both sizes, and its legend label is tertiary.
955
+ - **`StatCard`** renders its LABEL ABOVE ITS VALUE, which is the order a board
956
+ is read and heard in, with its sub line in the tertiary ink. A standalone
957
+ tile and a `StatGroup` both cast the panel's xs shadow, and a `StatGroup`
958
+ reflows to two columns at 900px rather than 768px.
959
+ - **`PricingCard.Tag` and `PricingCard.PriceNote` render a `Tag`**, so they are
960
+ no longer uppercase, their corner is 4px rather than a pill, and the top tag
961
+ takes the accent's soft tint under its ink rather than the solid accent fill
962
+ under white. The card casts the panel's xs shadow.
963
+ - **`Count`, `StatusDot`, `TagGroup` and `NewItemsNotice`** keep their APIs and
964
+ their shapes. `StatusDot`'s pulse completes in 1.8s rather than 3.6s, and
965
+ `NewItemsNotice` drops to the regular weight with the accent FILL on its
966
+ hover boundary.
967
+
968
+ Three engine values are deliberately not adopted, each because it puts a colour
969
+ under the floor it has to clear: the neutral `StatusDot` and the neutral
970
+ `Meter` fill keep `--color-text-tertiary` rather than the decoration step,
971
+ which measures 2.33:1 on a light page and 2.25:1 on a dark one as a mark; a
972
+ `FilterChip`'s count keeps a measured text step for the same reason; and a
973
+ pressed `Tag` keeps its boundary rather than repainting its ground, which the
974
+ engine draws as its own ink on its own ink, at 1:1.
975
+
976
+ **Every table is drawn in one register.** The two `DataTable` variants,
977
+ `Table` and `TreeGrid` each spelled their own version of a column head; they
978
+ take one now, which is the micro-label step the rest of the system uses: 11px,
979
+ medium, uppercase, on the wide tracking, in the tertiary ink, with one rule
980
+ under it and a hairline between one column and the next. A cell is the compact
981
+ step on the table's own inset pair, in a row at least as tall as
982
+ `--size-row-md`, so a table tightens with the density setting. What this costs
983
+ a consumer: a header that was body-sized words is now a micro-label row.
984
+
985
+ **The header row is a band only where the rows are bounded.**
986
+ `maxBodyHeight` is new on `DataTable`, passed through by `DataView`, and unset
987
+ by default. A number of pixels or any CSS length caps the rows, so they scroll,
988
+ and that is also what pins the column names and paints the ground they need to
989
+ be read over the rows passing behind them. ONE prop, because either half alone
990
+ does nothing: the rows already sit in a scroller of their own (a table has to
991
+ be able to overflow sideways, and a box that scrolls in one axis scrolls in
992
+ both), so a pinned header pins against THAT box, and unbounded it is as tall as
993
+ its rows and never moves. Unset, the header row stands on whatever the table
994
+ stands on, which is what a table the PAGE scrolls wants. The comfortable
995
+ density still paints its header either way: at that density an unfilled header
996
+ row reads as one more row.
997
+
998
+ **Every row lights up under the pointer, and a row that acts points.** On a
999
+ table twelve columns wide the tint is how a reader holds their eye on one row
1000
+ while they cross it, so every row takes it; what says a row can be PRESSED is
1001
+ the cursor, and only a row that is `--clickable` or carries a row link takes
1002
+ one. `--crewlet-data-table-row-hover-bg` is the OVERLAY step rather than an
1003
+ opaque surface, because a table sits on the page, on a card and inside a dialog
1004
+ and only an overlay composites onto all three; the rows themselves paint no
1005
+ background at all. A hovered row carries that tint all the way to its pinned
1006
+ right pane, whether or not the row is a control: the table the pane was drawn
1007
+ for holds its action as a link inside the pane and has no clickable row at all.
1008
+
1009
+ **A chevron column is drawn at the chevron's size, and pressed at the target
1010
+ floor.** The expand-all control in the header and the expand control on each
1011
+ row are the drawing and a step of halo, not a 28px control box, because those
1012
+ two cells are what set the height of the column-name row and of every row in
1013
+ the table: painted at the control step they cost six pixels of the first and
1014
+ ten of each of the rest. The pressable area is a centred pseudo element at
1015
+ `--size-control-sm`, which floors at 24px however the density is set and
1016
+ reaches into the cell's own padding rather than making the cell taller. Their
1017
+ focus ring is the one ring in this component drawn OUTSIDE its control, since
1018
+ an inset one would be a square on the glyph.
1019
+
1020
+ **A selected row is the tint, and a row's state rail is an ink step.** The
1021
+ selection ring came off: it is the mark a selected CARD takes, and on a run of
1022
+ hairline rows it boxed one row out of the list. The tone rails
1023
+ (`--tone-danger`, `--tone-warning`, `--tone-info`) move from the fill step to
1024
+ the matching `-ink` step, because a fill is measured to 3:1 against the OPAQUE
1025
+ surfaces and a rail is drawn on the row: on the accent tint of a selected row
1026
+ the danger fill measured 2.86:1 in dark on a card, so the one row that was both
1027
+ the reader's and broken had a mark nobody could see. The other two tones cleared
1028
+ the floor there and move with it anyway, because which tone a row carries is not
1029
+ a reason for its rail to be drawn to a different floor. @crewlethq/tokens'
1030
+ palette suite measures all three on every ground a row can have now; the
1031
+ tightest is 5.05:1.
1032
+
1033
+ **The table says what it only drew.** The pagination cluster is a named
1034
+ `group`, so the name it has always carried is attached to a role and read: on
1035
+ a plain div `aria-label` names nothing. A group and not a landmark, because a
1036
+ landmark named "Pagination" has to be unique on the page and a table cannot
1037
+ know how many tables it is on screen with. The page number announces which rows the page is as
1038
+ well as which page it is (`labels.pageRange`), because a page number says where
1039
+ a reader is in the chrome and nothing about what they are paging through. One
1040
+ live region sits beside the table, mounted with it and empty until it has
1041
+ something to say, and reports both the loading state (`labels.loading`, beside
1042
+ the table's own `aria-busy`) and the empty row's own rendered text, so a filter
1043
+ that empties a list is heard and not merely seen. The TEXT rather than the node:
1044
+ `emptyMessage` is usually a whole `EmptyState` and sometimes holds a control, and
1045
+ a second copy of that inside the region would be a tab stop nobody can see. It is beside the table rather than around
1046
+ the sentence because a region that arrives together with its first content
1047
+ announces nothing: a reader's software registers the region, then reports what
1048
+ changes inside it, and the empty row appears at exactly the moment it would
1049
+ have had to speak.
1050
+
1051
+ **A pinned column keeps an opaque base.** A sticky-right cell mirrors the row's
1052
+ tint as a background LAYER over its own opaque colour rather than as the
1053
+ background itself, so the rows scrolling behind it still do not show through.
1054
+ Its default ground is the elevated step: the pane is the one part of a row that
1055
+ is not the row, and it reads as the pane it is by standing one step off the
1056
+ ground the rows are on. A table on another ground rebinds
1057
+ `--crewlet-data-table-sticky-bg`.
1058
+
1059
+ **`Table` frames itself and can be bounded.** The boundary and the radius move
1060
+ from the scroller to the table, so the frame clips a header band that runs to
1061
+ the edge and an action row that is fenced from the rows above it rather than
1062
+ floating under the frame. The plain `Table` draws no hover tint, for the reason
1063
+ above: none of its rows is a control. `maxHeight` is new, and it is what makes
1064
+ the sticky header mean something: a bounded table becomes its own vertical
1065
+ scroller and keeps its column names on screen.
1066
+
1067
+ **`PageHeader` is the operator's head, not the marketing one.** The title drops
1068
+ from the 32px display step to 20px and the description from 16px body to the
1069
+ 13px compact step in the tertiary ink, with one spacing step between them
1070
+ instead of two. On a screen whose job is to show a table, the old head took a
1071
+ third of the first fold before a reader reached a row.
1072
+
1073
+ **`DataView` pages when a screen asks it to.** The composite passed
1074
+ `paginated={false}` as a literal, so no list screen could page whatever it
1075
+ passed, and the settings frame's page-size row was gated off with it. It is a
1076
+ prop that still DEFAULTS to false (a list screen scrolls unless it asks not
1077
+ to), and `page`, `itemsPerPage`, `pageCount`, their callbacks,
1078
+ `defaultItemsPerPage`, `itemsPerPageOptions`, `columnOrder`,
1079
+ `onColumnOrderChange` and `resizable` reach the table too.
1080
+
1081
+ **A list screen is one object.** `DataView`'s toolbar renders in the table's
1082
+ own toolbar row, through the table's `renderToolbar` slot, so the screen's
1083
+ search, Filter and Sort share a row with the table's pagination and settings
1084
+ cog rather than drawing a second bar above it. The chips go in
1085
+ `renderFilterBar`, a new `DataTable` slot for the band between the toolbar and
1086
+ the rows, drawn only when something is in it: a screen with no filters on has
1087
+ no empty strip. The chip row keeps its own fences when it is used on its own,
1088
+ which is what the exported `FilterAxisBar` is for. A framed list clips with
1089
+ `overflow: clip` rather than `overflow: hidden`, because `hidden` makes the
1090
+ panel the nearest scrollport and a sticky child of a box that never scrolls
1091
+ never moves, which is what `stickyToolbar` needs; it now pins the whole band,
1092
+ the table's half of the row included.
1093
+
1094
+ **`DataView`'s panel holds the table and nothing else.** The count that used
1095
+ to close it is gone from every list, so `.crewlet-data-view__table` has one
1096
+ child and the rows are the last thing inside the frame. The last row keeps its
1097
+ hairline cancelled, because what closes the list now is the frame's own bottom
1098
+ edge one pixel below it. The chip fence is a solid hairline rather than a
1099
+ dashed one, because a dashed edge means something else in this system: it is
1100
+ the mark a card wears when it stands for somebody outside it. The axis name on
1101
+ a chip and the chevron beside it drop their opacity, which had taken the chip's
1102
+ own measured ink down to 3.97:1 and 3.00:1 respectively.
1103
+
1104
+ **The canvas stands on the page's ground, and `TreeCanvas` draws the card.**
1105
+ `Canvas` paints `--color-surface-background` rather than the panel surface, so
1106
+ the cards on it read as objects on a field rather than as one sheet; a card
1107
+ that painted the panel colour is now visible against it, and one that painted
1108
+ `--color-surface-elevated` should move to the panel step.
1109
+ `.crewlet-tree-canvas__card` carries the frame (surface, boundary, 12px radius,
1110
+ `--shadow-xs`), the inset and radius of every `role="treeitem"` inside it, the
1111
+ focus ring those items never had, and the accent ring on the selected one, so a
1112
+ consumer that drew all of that itself now draws it twice. `cardOutline` is the
1113
+ new prop for the dashed edge of a card that stands for somebody outside the
1114
+ system. The connectors take `--color-border-control` rather than
1115
+ `--color-border-hover`: they are the only thing on the chart that says who
1116
+ reports to whom, and at 1.48:1 in light they were a line a reader had to hunt
1117
+ for.
1118
+
1119
+ **`TreeGrid` stands on the panel surface** rather than the elevated step, which
1120
+ is the colour a hovered row takes elsewhere, so an outline beside a table read
1121
+ as a table with every row hovered at once.
1122
+
1123
+ **`Charts` gains `TimeSeries` and `Sparkline`,** the two shapes the kit had no
1124
+ way to draw: a quantity over time whose x domain is the WINDOW rather than the
1125
+ data, and a shape beside a number that is hidden from assistive technology
1126
+ because the number already says everything it could. The legend swatch is a
1127
+ rounded square rather than a circle (at the smallest radius step an 8px swatch
1128
+ is a `StatusDot`), and a bar list's sub-label, tail and empty line move from
1129
+ the 11px micro-label step to the 12px caption step, which is what they are.
1130
+
1131
+ **`ErrorBoundary` is a panel.** It takes the panel's body inset and its rim
1132
+ light, its title drops from the 18px heading step to the panel-title step, and
1133
+ its description to the compact step.
1134
+
1135
+ **The rule under a table's last row belongs to whatever ends the rows.** A
1136
+ table standing on the page draws it, because nothing else closes the list and
1137
+ without it the rows stop in mid-air. Inside a `DataView` panel the frame takes
1138
+ it away, since the count row's own border sits one hairline lower and the two
1139
+ met as a doubled rule; the suppression is written where the frame is, which is
1140
+ `DataView`'s sheet, so a consumer framing a table itself keeps the same
1141
+ freedom. Both horizontal scrollers also contain their overscroll now, so a
1142
+ trackpad swipe that runs out of table no longer carries on into the browser's
1143
+ back navigation.
1144
+
1145
+ **A `TreeCanvas` node is a stack the component draws.** The card's frame moved
1146
+ into the component and the rhythm inside a node did not, so `role="treeitem"`
1147
+ now carries the flex column, the one-step gap and the default cursor along
1148
+ with the inset and the radius it already had. A consumer drawing those round
1149
+ its own name and meta line should drop them.
1150
+
1151
+ **A `TreeCanvas` card is one column, and the pointer's controls are drawn over
1152
+ its end.** The card was two columns and the second held the buttons, so every
1153
+ name in a chart was measured against the width three controls left over and the
1154
+ longer half were truncated at rest, for controls nobody was pointing at. The
1155
+ strip is quiet until the card is reached (by hover, by focus, on the selected
1156
+ node, and while a menu opened from it is up), so a name has the whole card.
1157
+ `ctx.actions(id)` is the whole of that strip now and replaces spreading
1158
+ `ctx.press(id)` on one: it carries the class the reveal is keyed on, the
1159
+ removal from the accessibility tree and the same press. It has to sit
1160
+ immediately after the node's own treeitem. `ctx.press` stays, for a
1161
+ pointer-only region that is NOT that strip. `renderUnder` is new: what hangs on
1162
+ the branch below a card, normally the control that adds a child, in a band the
1163
+ card's measured height already includes.
1164
+
1165
+ **`TreeCanvas` connectors turn near the child rather than running flat across
1166
+ the gap.** They were a square step, whose corners sit on the horizontal run a
1167
+ parent's children all share; they are a rounded step, so the corner belongs to
1168
+ the child it reaches. A consumer that measured the path data will see `V`, `Q`
1169
+ and `H` where it saw `V` and `H`.
1170
+
1171
+ **`TreeCanvas` draws an org chart node as well as a card, and `layoutConnectors`
1172
+ returns objects.** `appearance="node"` is the console org chart's own drawing: a
1173
+ node one rank tall and as wide as its own name between a fixed icon zone and a
1174
+ fixed actions column, the actions as a column down its right edge, and the
1175
+ control that adds a child as a disc on the branch below. `connector="curve"` is
1176
+ that chart's branch, one cubic from a parent's bottom into a child's top.
1177
+ `cardTone` tints a node with one of six hues, which reaches its fill, its edge,
1178
+ a halo outside it, its name's ink and the branch arriving at it, from one
1179
+ answer. `OrgNodeLabel` and `OrgNodeLead` are what such a node normally holds, the
1180
+ first of them `OrgLabel` in its node layout, which is the same component a
1181
+ table row's name cell is.
1182
+ Nothing changes for a chart that asks for none of it, except that
1183
+ `layoutConnectors` now returns `{ id, parent, d }` rather than a bare path
1184
+ string, so a connector can be asked about the node it reaches.
1185
+
1186
+ **An org chart is drawn on RANKS, and the space around it is part of the
1187
+ layout.** `appearance="node"` lays every card of a depth on one band, as tall
1188
+ as the tallest card in it and each card centred in it, with the next band a gap
1189
+ below: a rank of an organization is a row a reader scans across, and two units
1190
+ of one company sit on one line whether or not one of them carries a lead along
1191
+ its bottom edge. It is what the console chart does, and the spacing is that
1192
+ chart's own, as the nearest steps on this scale: `--spacing-9` between
1193
+ neighbours on a rank, `--spacing-11` between ranks, and `--spacing-8` around
1194
+ the whole chart, measured from probes so density scales them. A chart of panels
1195
+ is unchanged, and either answer can be asked for outright with the new `ranks`
1196
+ prop. `layoutForest` takes `ranks` and `margin` alongside its gaps, and its
1197
+ refusal now names the three rather than "gaps".
1198
+
1199
+ **A chart arrives rank by rank, and a relayout moves the cards rather than
1200
+ replacing them.** Cards are revealed 60ms apart, down the chart and then across
1201
+ each row, and each branch waits for the card it arrives at; a relayout tweens
1202
+ every card and every connector from where it was to where it is, over 350ms. A
1203
+ card waiting its turn carries `data-enter="waiting"` and is drawn at nothing,
1204
+ still measured and still in the layout. Neither runs under
1205
+ `prefers-reduced-motion: reduce`, where the chart is drawn whole and every
1206
+ relayout is a jump. A consumer that read a card's `transform` immediately after
1207
+ a change will now read where the card WAS, and should read it a frame later or
1208
+ ask the layout.
1209
+
1210
+ **The control that adds a child is a pill that splits, and it is drawn on a
1211
+ resting chart.** `AddPill` is the console chart's own gesture: one quiet mark on
1212
+ the branch, a spacing step across against the 24px pointer target around it,
1213
+ which opens into a section per kind when it is pointed at, with a spring out of
1214
+ a fraction of its width and a recoil back. In `appearance="node"` the strip
1215
+ under a card is no longer hidden until the card is reached; what is in it and is
1216
+ not the pill still is. A chart that hung a `Menu` under its cards keeps working
1217
+ and keeps its old resting state; one that wants the console's gesture returns an
1218
+ `AddPill` from `renderUnder`.
1219
+
1220
+ **`OrgTable` draws an organization as an indented table of rows,** which is the
1221
+ console's org table and the other half of the pair `TreeCanvas` opens: the
1222
+ chart shows the shape and the table answers questions about it, in an order a
1223
+ sort would destroy. It composes `TreeGrid`, so the rows, the keys, the cells,
1224
+ the add row and the layer its menus open in are unchanged; what it adds is the
1225
+ drawing that table already had and no consumer could reach. The tree's wires
1226
+ run down one gutter, and the element that draws a row's branch IS its indent,
1227
+ so the two can never disagree. `OrgTableName` is the two-line name group, and it
1228
+ is the SAME COMPONENT a chart node carries (`OrgLabel`) in its row layout,
1229
+ ranged left rather than centred, because a column of names is read down its
1230
+ leading edge. `OrgTableActions` is the strip
1231
+ of row controls, quiet until the row is reached by the pointer, by focus or by
1232
+ being the selected row, and simply drawn where a pointer cannot hover.
1233
+ `OrgTableAdd` is the SLOT the add pill opens in, and the pill in it is
1234
+ `AddPill` itself, so a row's plus and a branch's are one gesture. `tone` marks a row with one of the six
1235
+ node hues, reaching the branch arriving at it and the mark and name in it, and
1236
+ never the caption: the accent clears 4.2:1 on the table's own ground, which is
1237
+ over the floor for a drawing and under it for a word. Expand all and Collapse
1238
+ all are a tab on the table's own top edge, since they act on that table rather
1239
+ than on the screen around it.
1240
+
1241
+ **The org table's add is the chart's own pill, and a row stands at the
1242
+ console's own step.** `OrgTableAdd` held a second implementation of the split
1243
+ pill, which had no split, no divider between its kinds and a boundary of its
1244
+ own; it holds `AddPill` now and passes that component's props straight through,
1245
+ so one keyframe pair, one divider and one colour reach both surfaces. The plus
1246
+ is a sibling of `OrgTableActions` rather than a control inside it, so it is
1247
+ drawn on every row that can take a child instead of waiting for the pointer,
1248
+ and the row reserves the width the pill takes OPEN (`--crewlet-org-table-add`,
1249
+ one pointer target per kind), so the split no longer grows back over the cell
1250
+ beside it. The indent is `--spacing-8` a level where it was `--spacing-5`, a
1251
+ row in the tree stands at `--size-row-lg` where it stood at the grid's own step,
1252
+ the name group fills its cell so a trailing slot lines up down the column, and
1253
+ the Expand all and Collapse all pair no longer wears the same plus as the add
1254
+ on the rows beneath it. In `TreeGrid`, opening a row TURNS its chevron rather
1255
+ than swapping one drawing for another.
1256
+
1257
+ **`Canvas` says its zoom, between the two steppers.** Pressing the percentage
1258
+ puts a field in its place; Enter applies, Escape and blur abandon. Two labels
1259
+ join `CanvasLabels` for it, `zoom` and `zoomLevel`, so a canvas in another
1260
+ language sets them. Nothing needs an edit: a canvas drawing its own zoom
1261
+ readout beside the controls now draws two.
1262
+
1263
+ **`AddPill` is GREEN, and it is one control for two surfaces.** The mark, the
1264
+ pill's edge, the dividers and a hovered choice read the node ramp's green
1265
+ (`--color-node-green` and its derived steps) rather than the brand accent: the
1266
+ org chart this is drawn from paints this control, and nothing else on the
1267
+ chart, in one hue, which is what lets "the green plus" name a control. The
1268
+ quiet at rest is on the PLUS rather than on the whole button, because the disc
1269
+ behind it is a mask for the branch and a mask at half opacity is not one; the
1270
+ resting plus measures 4.01:1 and 3.79:1 on the dark grounds and 3.50:1 and
1271
+ 3.31:1 on the light, where it measured 2.96 and 2.47 before. It also takes
1272
+ `size` (`sm`, the branch's disc; `md`, a row's control step) and `layout`
1273
+ (`split` over the mark; `inline` beside it), so a table row draws the same
1274
+ gesture as the chart, and a section may carry a `disabledReason`. Nothing
1275
+ needs an edit: the defaults are what the chart drew before.
1276
+
1277
+ **`AddPill` wears the console chart's own chrome.** The disc and the pill carry
1278
+ the NODE's ground (the page's own, with the card's step painted over it, so the
1279
+ pair is opaque wherever the chart is panned to) rather than the page's alone,
1280
+ which read as a hole punched in the chart; the disc's hairline is drawn at rest
1281
+ and fades over 150ms, as that chart's does; the two dividers are inset to the
1282
+ pill's own sixth, take no press, and are ELEMENTS rather than a border on a
1283
+ section, because a border cannot be shorter than its box and the box here is a
1284
+ pointer target half as tall again as the pill. A resting choice is at the
1285
+ plus's own step, which measures 3.80:1 and 3.99 on the dark grounds and 3.31
1286
+ and 3.51 on the light; that chart draws its choices at 0.6, which measures 2.51
1287
+ on the light node ground. What to change: nothing, unless a stylesheet reached
1288
+ the disc as `.crewlet-add-pill__mark::before` or the divider as a section's
1289
+ `border-left` (see the table below).
1290
+
1291
+ **`OrgNodeDisclosure` is new, and a chart that collapses draws its expander in
1292
+ it.** The branch under a node is the ADD's, and only the add's, which is what
1293
+ that chart draws: a second control beside it is a pair fighting over one place,
1294
+ and a pill splitting open covers whatever was next to it. In the `node`
1295
+ appearance this straddles the card's leading boundary and is out of the flow,
1296
+ so a node that can be expanded is the same width, the same height and in the
1297
+ same place as one that cannot, and it is quiet until the node is reached, by
1298
+ focus as well as by a pointer. **Render it as a SIBLING of the element you
1299
+ spread `ctx.item` on**, exactly where `ctx.actions` goes: a tree's items hold
1300
+ nothing focusable, so it cannot be a slot on `OrgNodeLabel`, which renders
1301
+ inside that element. It hides itself, as the actions strip does, and costs a
1302
+ reader nothing, because expansion is on the treeitem as `aria-expanded` and the
1303
+ Right and Left arrows. Spread `ctx.press(id)` on it, exactly as the strip under
1304
+ a card takes it: a press has to leave focus on the NODE, and the component owns
1305
+ what cannot be forgotten while the caller passes the one thing only the chart
1306
+ knows. What to change: a chart drawing an expander in
1307
+ `renderUnder`, or as a third cell of the actions column (16px on one rank,
1308
+ under the target floor), draws it here instead.
1309
+
1310
+ **`OrgNodeLead` draws the pill and the clear the way that chart does.** The
1311
+ pill is bounded at the hover step rather than the separator's, since it is a
1312
+ boundary around a value and every other line inside a node is a separator, and
1313
+ its words carry medium weight; the clear is a small disc with a ground a step
1314
+ past the card's and a boundary of its own, with a glyph a third of it, inside
1315
+ the pointer target that is pressed. Both the disc and the pill's own ends are
1316
+ ELEMENTS, because what a node is painted with is measured and no cascade
1317
+ reports a pseudo element's paint. What to change: nothing, unless a stylesheet
1318
+ reached inside the clear.
1319
+
1320
+ **A `TreeCanvas` node is painted over the page's ground, toned or not.** The
1321
+ branches are drawn under the cards and the neutral surface step is translucent
1322
+ where no theme is loaded, so a node with no tone had the rank above running
1323
+ through its own face and its name; a toned card already went down this way.
1324
+ What to change: nothing.
1325
+
1326
+ **`OrgNodeLabel`'s caption is the neutral tertiary ink on every node.** It used
1327
+ to take the node's own hue on a toned card, where it measures 4.2:1: over the
1328
+ floor for a drawing and under it for a WORD, and the same word was drawn in two
1329
+ colours across a chart and the table beside it. What to change: a chart that
1330
+ wants its caption tinted draws its own; the hue still reaches the card, its
1331
+ edge, its halo, its branch and its name.
1332
+
1333
+ **`OrgNodeLabel`'s trailing slot is a fixed width, and only drawn when asked
1334
+ for.** It was a 4px floor, so a live badge arriving made the node 35px wider
1335
+ and relaid the chart, which is what both this component and the chart above it
1336
+ promise never happens. It holds one control step of room whatever it is
1337
+ given, with its own separation either side, and a node that passes no
1338
+ `trailing` is drawn with no slot at all. What to change: a
1339
+ chart whose marks are wider than that step either shortens them or draws them
1340
+ on the caption line, where the wiring marks already ride.
1341
+
1342
+ **`OrgNodeLabel` and `OrgTableName` are one component now, `OrgLabel`.** They
1343
+ said the same four things about the same organization, in two components under
1344
+ two class prefixes: measured declaration by declaration the two stylesheets
1345
+ agreed on 38 of the 62 they set across the six elements they both draw, and
1346
+ seventeen of the 24 that differed are one difference restated, that a chart
1347
+ node is a box as wide as its own name and a table row is a line in a column.
1348
+ That is `layout="node" | "row"` now, and every other rule is written once. The
1349
+ other seven were the same rule written two ways, which is where the two defects
1350
+ below lived. Both names stay, as the layout filled in, so no
1351
+ call site changes.
1352
+
1353
+ Two defects go with the split. The chart repainted a mark that publishes its
1354
+ own ink and the TABLE did not, so a brand mark on a toned row was the one thing
1355
+ on it still drawn in somebody else's colour; the table held every child of its
1356
+ caption to a fixed size and the CHART held only a bare glyph, so a mark that
1357
+ arrived inside a span naming it shrank with the strip around it. Each surface
1358
+ had one of the two; both now hold for both.
1359
+
1360
+ What to change: a stylesheet reaching either component's parts by class. The
1361
+ shared elements are `.crewlet-org-label__icon`, `__text`, `__name`,
1362
+ `__caption`, `__kind` and `__trailing` for both surfaces, and a row's wrapper,
1363
+ which was `.crewlet-org-table__node`, is `.crewlet-org-label--row` and still
1364
+ carries `data-tone`. Nothing a caller passes moved, and `OrgNodeDisclosure`,
1365
+ `OrgNodeLead` and the rest of the table are untouched.
1366
+
1367
+ **`OrgNodeLabel` takes `iconSize`.** `lg` fills three quarters of the icon
1368
+ zone, which is the proportion the console chart gives the mark that stands for
1369
+ the thing a chart is ABOUT; `md`, the default, is unchanged.
1370
+
1371
+ **`OrgNodeLead` wraps its `clear` control**, so the chart can reveal it with
1372
+ the node's other controls and grow its target past the pill's own edge. What to
1373
+ change: nothing, unless a stylesheet targeted the control as a direct child of
1374
+ the pill.
1375
+
1376
+ **`TreeCanvas`'s add hangs BELOW the node it belongs to.** In its `node`
1377
+ appearance the strip under a card is out of the flow and drawn on the branch
1378
+ the children come off, centred on the node's own axis in a three-track grid,
1379
+ which is where the console chart puts it; it was inside the card, 13px above
1380
+ the branch's own start, and 14px right of the axis whenever an expander shared
1381
+ the strip. The room it needs moved into the rank gap with it, so the distance a
1382
+ reader sees between two ranks is the same number it was. The hairlines inside a
1383
+ node are inset at both ends, as that chart's are. What to change: nothing; a
1384
+ caller that positioned something in that strip itself should check it.
1385
+
1386
+ **`TreeCanvas` puts the canvas controls in the chart's own corner.** The
1387
+ `node` appearance defaults them to the top right, which is where that chart's
1388
+ toolbar is. `controlsExtra`, `controlsBelow` and `controlsPlacement` pass
1389
+ through to `Canvas`; `dimmed` pushes the chart back behind a surface opened
1390
+ about one of its nodes, and the ref is a `TreeCanvasHandle`, which adds
1391
+ `focusRegion` and `restoreView` to what a tree view answers.
1392
+
1393
+ **`TreeCanvas` adds a node IN the chart, not in a dialog over it.**
1394
+ `composing` names a GHOST: `{ id, parent, label, render, onCancel }`, a card
1395
+ the layout knows about and the data does not. The chart grafts it into the CARD
1396
+ forest after `parent`'s own children, opens `parent` if it is closed, makes
1397
+ room for it in the rank the new node will land in, draws the branch into it
1398
+ dashed, eases onto it with the room a form needs
1399
+ (`CANVAS_COMPOSE_CONTEXT`, two thirds of the pane, where a node opened about
1400
+ gets a third) and puts focus on the first control inside it; Escape calls
1401
+ `onCancel`, and when it goes the view the reader left is given back and focus
1402
+ returns to `parent`. Escape cancels from anywhere in the chart, not only from inside the form: a
1403
+ node being composed is a MODE, and a reader leaves a mode wherever they are
1404
+ standing. Its id must NOT be an id of `nodes`: that is what keeps
1405
+ every real node's level, position in set, set size and key the same while a
1406
+ node is being composed, and keeps the arrows, type ahead and the selection off
1407
+ something that is not there yet. It is drawn OUTSIDE the tree element, because
1408
+ a form is not a `treeitem`, and it is the one card in the chart that holds a
1409
+ tab stop. Width is `--crewlet-tree-canvas-compose-width` (20rem). What to
1410
+ change: nothing; a chart that asked for a child's name in a modal over itself
1411
+ can hand that form to `composing` instead.
1412
+
1413
+ **`CanvasHandle.focusRegion` takes a second argument, and `rememberView` is
1414
+ new beside it.** `focusRegion(target, context)`, where `context` is how many
1415
+ times the region's own width the visible width comes to: `CANVAS_FOCUS_CONTEXT`
1416
+ (3) when it is left out, which is what every existing caller gets.
1417
+ **The canvas has TWO eases, and `data-animate` names which is running.** A
1418
+ fit, a zoom step and a reveal are CORRECTIONS to the view the reader already
1419
+ has, and keep the default step; easing onto a region and giving the view back
1420
+ are the chart GOING somewhere, and take two `moderate` steps with the in-out
1421
+ curve, which is the console chart's own 400ms viewBox ease. At the correction's
1422
+ step that movement read as a jump, 2.7 times faster than the chart it is drawn
1423
+ to match, and what it is for is saying WHERE the place is. Reduced motion
1424
+ cancels both. What to change: a stylesheet selecting
1425
+ `.crewlet-canvas[data-animate='true']` now wants `'near'`, or `'travel'`.
1426
+
1427
+ A third argument, `ceiling`, caps how far in the ease may zoom, for a region
1428
+ with a size of its own to respect: a chart with few nodes has room to zoom far
1429
+ past a form's designed size and took it. `rememberView()` marks the view
1430
+ `restoreView` gives back without moving anything, for a region that does not
1431
+ exist yet: a card being added changes the content before there is a rectangle
1432
+ to ease onto, and the canvas answers content that changed shape by keeping what
1433
+ it holds reachable, so by the time `focusRegion` can be called the view is no
1434
+ longer the reader's. What to change: nothing.
1435
+
1436
+ **`Canvas`'s zoom keys answer anywhere inside it, and its limits are the
1437
+ content's.** `+`, `-` and `0` used to reach only the viewport ELEMENT, which is
1438
+ never what holds focus in a chart whose tab stop is an item: a reader who had
1439
+ navigated the chart pressed `+` and nothing happened. The arrows are still the
1440
+ viewport's own, and a text field inside keeps every key. The floor is now the
1441
+ FIT rather than a quarter, because there is nothing past the whole chart to
1442
+ see, and the ceiling is the largest item filling the viewport
1443
+ (`largestItemWidth`) rather than double size. `zoomAt`, `pinchView` and
1444
+ `clampZoom` take the limits as a last argument, defaulting to the fixed pair.
1445
+ What to change: a canvas that relied on zooming out past its own fit.
1446
+
1447
+ **`Canvas`'s controls are a GROUP, and the bar inside it is
1448
+ `.crewlet-canvas__bar`.** The group is a column in one corner holding the zoom
1449
+ bar, anything the caller adds to the end of it (`controlsExtra`) and any bars
1450
+ it stacks under it (`controlsBelow`). The zoom field is one control step wide
1451
+ rather than one step at least, so pressing the readout no longer moves the bar
1452
+ 111px. What to change: a stylesheet that targeted `.crewlet-canvas__controls`
1453
+ for the bar's own ground or padding now wants `.crewlet-canvas__bar`.
1454
+
1455
+ **`Modal`'s side sheet is committed from its head.** The sheet variant takes a
1456
+ `headerActions` slot beside its title, its head stands at the shell's own
1457
+ top-bar height (`--size-shell-topbar`) with 16px over 24px insets rather than
1458
+ 12 over 16, its title is drawn at `--font-size-md`, and its body is inset 24px
1459
+ with an 8px stack gap. It also arrives properly: the frame travels its own
1460
+ width in from off screen with no opacity ramp, where it used to move 16px and
1461
+ fade, and its separation is cast on the INLINE axis through a new
1462
+ `--shadow-sheet` token rather than borrowing `--shadow-xl`, whose two vertical
1463
+ offsets both fall off the screen on a frame that runs the full height of the
1464
+ window. The veil under a sheet now fades over the same 300ms the sheet travels
1465
+ rather than 150ms. A dialog is unchanged in every one of those. What to
1466
+ change: nothing, unless a sheet was relying on its head being 49px tall.
1467
+
1468
+ **`ConfirmModal` is drawn as a prompt.** A confirmation is one question and one
1469
+ answer, and it used to carry a head band with a glyph and a close control that
1470
+ did exactly what the Cancel two inches below it did. It is 420px, centred, with
1471
+ no head band, no glyph and no close control: the title is the body's own first
1472
+ line and the actions hang off its bottom edge. It still names the frame, and a
1473
+ destructive one is still an `alertdialog`. `Modal` takes the same
1474
+ `shape="prompt"` directly, and `shape="framed"` is the way back for a
1475
+ confirmation that grew a form inside it. What to change: a confirmation that
1476
+ passed `size` to widen itself needs `shape="framed"` as well, since the prompt
1477
+ has a width of its own.
1478
+
1479
+ **The veil's blur is drawn again.** `Modal.css` declared `backdrop-filter` and
1480
+ then `-webkit-backdrop-filter` by hand, and a minifier answers a rule holding
1481
+ both by keeping the prefixed declaration and dropping the standard one: the
1482
+ shipped rule carried the prefixed spelling alone, which Chrome does not
1483
+ support, so the 3px softening behind every dialog and every sheet in the
1484
+ product computed to `none`. The hand-written prefix is gone; bundlers emit
1485
+ their own. Nothing needs an edit.
1486
+
1487
+ **`ListInput` is one card, and the box is above it.** The list was a stack of
1488
+ separate boxes, each a two-row textarea with Move up, Move down and Remove
1489
+ beside it: three items measured 256px where the console's own list draws 111,
1490
+ and the second row of every textarea was empty. It is a bordered card whose
1491
+ rows are divided by a rule, each row one growing line with its position drawn
1492
+ in the accent, and the new-item box sits above the list rather than below it,
1493
+ which is the order the two read in. The add control is an accent square beside
1494
+ the legend, where the console puts it and where the eye is when the list is
1495
+ empty. Move up and Move down are still there and still named, revealed by the
1496
+ pointer and by the focus rather than drawn on every row, and still on Alt+Up
1497
+ and Alt+Down from the item itself. What to change: `rows` is gone, since an
1498
+ item is one line that grows; `addLabel` is a function of the item's name
1499
+ (`addLabel={(name) => "Add " + name}`) rather than a fixed word, because
1500
+ the control is now an icon and its name is all a screen reader has.
1501
+
1502
+ **A tag's remove control reaches the pill's own outer edge.** It is a flex
1503
+ child stretched to the container's CONTENT box, which is the pill's 1px
1504
+ boundary shorter on each side, so it drew 22px inside the 24px a
1505
+ `--crewlet-tag-acts` pill is floored at: under the target floor the class
1506
+ beside it exists to hold. Nothing needs an edit, unless a stylesheet gave the
1507
+ control its own block margins.
1508
+
1509
+ **A `Modal` that submits is no longer a `<form>` with a dialog role on it.**
1510
+ A `<form>` accepts only `search`, `none` and `presentation` as an explicit
1511
+ role, so the frame this package drew for every surface that submits (the node
1512
+ editor, every confirmation) was a conformance failure axe reports on each one.
1513
+ The form is INSIDE the frame now, drawing no box of its own, so Enter from any
1514
+ control still submits and the head, the body and the footer are still the
1515
+ frame's own children. What to change: a stylesheet or a query that reached the
1516
+ frame as a `form` element; the frame is the element carrying `role="dialog"`,
1517
+ whichever way it submits.
1518
+
1519
+ **A row's control strip keeps its room whether or not it draws it.** The
1520
+ actions cell is end-aligned and the add pill sits before the strip, so a row
1521
+ drawing fewer controls dragged the plus along with it: measured on the engine's
1522
+ builder, the company's row draws no trash and no menu, its strip stood 28px
1523
+ against every other row's 92, and the plus a reader scans down the table for
1524
+ was 64px to the right of the one on the row beneath it. The strip is floored at
1525
+ `--crewlet-org-table-strip` (three control steps and their two gaps) and
1526
+ end-aligned inside it. Nothing needs an edit, unless a table draws more than
1527
+ three controls in a row's strip, which sets that variable.
1528
+
1529
+ ### What to change
1530
+
1531
+ | Was | Is |
1532
+ |---|---|
1533
+ | `<IconButton aria-label="Add" />` | `<IconButton label="Add" />` (required) |
1534
+ | `icon="cached"` on a Tabs item, a DataTable column or a row action | `icon={<CachedGlyph />}` |
1535
+ | `<Popover>` holding a form or a panel | the same, plus `label`, which the type now requires for a dialog |
1536
+ | `<Popover>` whose children carry `menuitem` or `option` roles | `role="menu"` or `role="listbox"`, where `label` stays optional |
1537
+ | a stylesheet targeting `.material-symbols-outlined` inside a uilet component | `.crewlet-glyph` |
1538
+ | a stylesheet overriding a component's focus `box-shadow` | override `outline` |
1539
+ | a click handler relying on `Copyable`'s value span | press the copy control, which is the only one |
1540
+ | `<Card.Body padding="tight">` for the vertical-only step | `padding="sm"`; `tight` is the tile's 12px on every edge, as on the card |
1541
+ | a `<Card.Footer>` relied on for 8px over 16px from `padding="md"` | it is the default, and the default is `sm`; `md` is 16px on every edge |
1542
+ | `<ListInput rows={3}>` | nothing: an item is one line that grows with its text |
1543
+ | `<ListInput addLabel="Add a goal">` | `addLabel={(name) => "Add a " + name}` |
1544
+ | `<ConfirmModal size="md">` widened by hand | `<ConfirmModal size="md" shape="framed">`; the prompt shape has its own 420px |
1545
+ | a stylesheet overriding `.crewlet-modal__header--sheet` padding | the sheet's head is 64px with 16px over 24px insets; override `--size-shell-topbar` or the rule itself |
1546
+ | a `<Card.Footer padding="tight">` relied on for 4px over 16px | `padding="sm"`, or `none` and a rhythm of its own |
1547
+ | `padding` on a `Card.Header` | drop it, and reach for `divided` |
1548
+ | an `Accordion.Item` heading assumed to be `h3` | `headingLevel={3}`, or the level where it sits |
1549
+ | a stylesheet styling `.crewlet-canvas__controls` as the bar | `.crewlet-canvas__bar` |
1550
+ | a stylesheet or a test reading `.crewlet-canvas[data-animate='true']` | `'near'` for a fit, a zoom or a reveal; `'travel'` for a region ease |
1551
+ | `<OrgNodeLabel>` relied on for a trailing slot with no `trailing` passed | pass `trailing`, even empty, where a push can arrive |
1552
+ | a stylesheet reaching a chart node's name group as `.crewlet-org-node__icon`, `__text`, `__name`, `__caption`, `__kind` or `__trailing` | the same part under `.crewlet-org-label__`, which both surfaces now draw |
1553
+ | a stylesheet reaching a table row's name group as `.crewlet-org-table__icon`, `__text`, `__name`, `__caption`, `__kind` or `__trailing` | the same part under `.crewlet-org-label__` |
1554
+ | a stylesheet or a query reaching a row's name cell as `.crewlet-org-table__node` | `.crewlet-org-label--row`, which still carries `data-tone` |
1555
+ | a stylesheet reaching the add pill's disc as `.crewlet-add-pill__mark::before` | `.crewlet-add-pill__disc`, which is an element |
1556
+ | a stylesheet reaching a pill divider as `.crewlet-add-pill__section` `border-left` | `.crewlet-add-pill__divider`, which is an element between two sections |
1557
+ | a chart drawing its expander in `renderUnder` or in the actions column | `<OrgNodeDisclosure>`, beside the treeitem, on the card's leading edge |
1558
+ | `ref={...}` on `TreeCanvas` typed as `TreeViewHandle` | `TreeCanvasHandle` |
1559
+ | a query or a stylesheet reaching a submitting `Modal`'s frame as a `form` | the frame is the element with `role="dialog"`; the form is inside it |
1560
+
1561
+ | `<Tag variant="warn">` | `<Tag variant="warning">` |
1562
+ | `{ variant: 'warn' }` or `{ variant: 'error' }` on a toast | `'warning'` or `'danger'` |
1563
+ | a success toast with no `duration`, kept until dismissed | `duration={0}` |
1564
+ | a `<Callout>` that had to be announced | add `live="polite"`, or `role="alert"` |
1565
+ | `<Avatar colorSeed={id} />` | `<Avatar tone="seeded" colorSeed={id} />` |
1566
+ | `<Avatar />` at the old default size | `<Avatar size={64} />` |
1567
+ | `<Avatar />` where a round badge is wanted | `<Avatar shape="circle" />` |
1568
+ | `<ImageUpload />` where a round badge is wanted | `<ImageUpload shape="circle" />` |
1569
+ | `<Tag />` where the 28px pill is wanted | `<Tag size="md" />` |
1570
+ | a stylesheet that restyled `.crewlet-pricing-card__tag` | restyle `.crewlet-tag`, or pass a `variant` |
1571
+ | a layout that assumed a StatCard's value came first | the label comes first; read `.crewlet-statcard__value` by class |
1572
+ | `<StatCard tone="brand" />` | drop the tone, or pick the outcome it reports |
1573
+ | `<Skeleton variant="box" glowColor="green" />` | `<Skeleton variant="box" />` |
1574
+
1575
+ | `<Tabs>` whose sections have a panel | the same, plus `panelId`, with `<TabPanel id value>` around the section |
1576
+ | a `<Segmented>`-style row hand-built from buttons | `<SegmentedControl label semantics="radio" \| "tabs">` |
1577
+ | `<Checkbox onChange={(event) => …}>` | the same, or `onCheckedChange={(checked) => …}` |
1578
+ | a binary setting drawn as an On and Off `<Select>` | `<Switch label checked onCheckedChange>` |
1579
+ | an application that boots its theme from an inline script | `applyStoredPreferences({ themeKey, densityKey })` from the module bundle |
1580
+ | a component that announces its own changes in a live region of its own | mount one `<Announcer>` in the shell; `TagsInput` and `ListInput` speak through it |
1581
+
1582
+ | `<Modal labelledBy="heading-id">` | drop it, or `ariaLabel` for a surface with no title |
1583
+ | a stylesheet or query for `.crewlet-modal__footer > button` | `.crewlet-modal__footer-end > button` |
1584
+ | a query for the `h2` inside a dialog | `.crewlet-modal__title`, or the dialog's accessible name |
1585
+ | `<ConfirmModal submitting>` relied on for a closeable dialog | it is `dismissable={false}` now: close it from the caller |
1586
+ | `aria-label`, `aria-labelledby` or `aria-describedby` on a `Modal` | `ariaLabel` and `describedBy`, which the component actually reads |
1587
+ | a check that a `ConfirmModal` button is `disabled` | it is `aria-disabled` with a reason, and it keeps focus |
1588
+
1589
+ | `{ label: 'Delete', variant: 'danger' }` in `rowActions` | `{ label: 'Delete', danger: true }` |
1590
+ | `{ label: 'Publish', variant: 'primary' }` in `rowActions` | `{ label: 'Publish' }` (a menu has no headline step) |
1591
+ | a stylesheet targeting `.crewlet-data-table__row-actions-item` | `.crewlet-menu__item` |
1592
+ | `Record<string, DataTableColumn>` in TypeScript | `Record<string, DataTableColumn<MyRow>>` |
1593
+ | a `div` selector under `.crewlet-table` | the `tr` or `td` it is now |
1594
+ | `--crewlet-data-table-archived-tint-rgb: 196, 96, 96` | `--crewlet-data-table-archived-rail: <a colour>`, or nothing for the neutral rail |
1595
+ | a `paginated={false}` table relied on for its Previous / Next chevrons | `paginated`, or `onPageChange` with `pageCount` |
1596
+ | a `paginated={false}` table relied on for the page-size chips | `onItemsPerPageChange`, which is what says the number is read |
1597
+ | `itemsPerPage` read back as `data.length` after pressing All | `ALL_ITEMS` (`'all'`), in the prop, the callback and storage |
1598
+ | a settings frame dragged from anywhere in a column row | drag from the handle, which is a named button now |
1599
+ | a query for the drag handle as `.crewlet-data-table__column-drag-handle` glyph | it is an `IconButton` carrying that class |
1600
+ | a check that Move up is `disabled` at the top of the list | it is `aria-disabled` with a reason, and it keeps focus |
1601
+ | a query for the resize handle as a `button` | `role="separator"`, named the same |
1602
+ | a `variant="default"` table with no row actions, assumed to have no trailing column | it has one, holding the settings cog |
1603
+ | a settings frame expected to open onto a 350px column list | the list is as tall as its rows, capped at 40vh and scrolled |
1604
+ | `settingsVariant="compact"` or `"rich"`, on `DataTable` or `DataView` | drop it: every table opens one frame, and it holds the column list |
1605
+ | a `labels.pageSize` override | `labels.itemsPerPage`, which names the section on every table |
1606
+ | a titled table's settings frame expected at the `sm` dialog step | it is `lg`, because it carries the column list |
1607
+ | the toolbar's Columns button, reached by name or by click | open the settings frame and use its column list |
1608
+ | a `labels.columnsMenu` or `labels.columnsMenuTitle` override | drop it: the frame's section is `labels.columnsSection` |
1609
+ | a stylesheet for `.crewlet-data-table__columns-panel` or `__columns-fieldset` | drop it: neither is emitted |
1610
+ | a stylesheet targeting `.crewlet-data-table__toolbar-end` | `.crewlet-data-table__chrome`, which a card's header may hold |
1611
+ | the controlled pair passed with `showSettings={false}` | keep the cog, or draw your own control: the frame was the way in |
1612
+ | a `sticky: 'right'` table relied on never showing a scrollbar | it scrolls where the container is narrower than its column floors |
1613
+ | an empty table relied on for the fixed layout its rows are drawn under | it carries `crewlet-data-table__table--rowless` and is laid out by content |
1614
+ | a table relied on for widths measured against another container | it fits the container it is in, and a drag is what is remembered |
1615
+ | a query for `<storageKey>_columnWidths` in storage | `<storageKey>_resizedColumns`, holding only what a reader dragged |
1616
+ | `import { TableFooter }` or `TABLE_FOOTER_LABELS` | drop it: no table draws a count line |
1617
+ | `<DataView totalCount={n}>` | drop it, and say the number in the screen's header |
1618
+ | `<DataView pagination={<LoadOlder />}>` | `hasMore` with `onLoadMore`, which the table draws under its rows |
1619
+ | a `labels.footer` override on `DataView` | drop it: there is no sentence left to translate |
1620
+
1621
+ | `<Count>` in a `NavItem` badge, relied on for its own pill | it draws as a figure; pass `badgeTone="attention"` for a pill |
1622
+ | a stylesheet overriding `.crewlet-nav-item__row` to tint the current row | drop it: the row takes `--color-brand-accent-soft` and `--color-brand-accent-ink` |
1623
+ | a stylesheet reading `.crewlet-nav-item__row[aria-current='page']::before` | the bar is gone; the row's own tint is the mark |
1624
+
1625
+ | a `<Button block>` dropped into `AppShell.Rail`'s `footer` | nothing: the foot draws a direct-child button as a row on the rail's own insets. `<AppShell.RailRow>` is still the one to reach for, because it also decides whether the row is a control |
1626
+ | a stylesheet putting a dropped-in foot button back on a toolbar's inset | drop it: the foot's own rule is what the rail draws |
1627
+ | a stylesheet putting the rail's head divider back | nothing: the bar's line is the only one at that height |
1628
+
1629
+ | a stylesheet restyling a table's `th` | it is a sticky micro-label band on `--color-surface-subtle` with no column dividers |
1630
+ | a table row relied on for a hover tint | it lights up only with `onRowClick` or a row link |
1631
+ | `--crewlet-data-table-row-hover-bg` set to an opaque surface | an overlay step, or accept `--color-surface-hover` |
1632
+ | `--crewlet-data-table-sticky-bg` left unset on a table not on the panel surface | set it: the default is `--color-surface-subtle` now |
1633
+ | a selected row expected to carry `--shadow-selection` | the accent tint is the whole mark |
1634
+ | `.crewlet-table__container` styled as the table's frame | the frame is `.crewlet-table`; the container is the scroller |
1635
+ | a `Table` with many rows and a wrapper that scrolled it | `<Table maxHeight="24rem">`, which also makes the header stick |
1636
+ | a page relying on `PageHeader`'s 32px title | it is 20px over a 13px sentence; use `Text` for a display heading |
1637
+ | a query, a stylesheet or a test for `.crewlet-table-footer` | nothing emits it: no table draws a count line |
1638
+ | a `TreeCanvas` card drawing its own surface, border, radius or shadow | drop all four: `.crewlet-tree-canvas__card` draws them |
1639
+ | a `TreeCanvas` card drawing its own dashed edge for a human seat | `cardOutline={(id) => …}` |
1640
+ | a `TreeCanvas` treeitem drawing its own padding, focus ring or selection ring | drop all three |
1641
+ | a card placed on a `Canvas` painted `--color-surface-elevated` | `--color-surface-subtle`: the canvas is the page's ground now |
1642
+ | a stylesheet expecting the expanded detail row on `--color-surface-subtle` | it is `--color-surface-inset` |
1643
+ | a pagination or settings button hover expecting `--color-surface-elevated` | it is the `--color-surface-hover` overlay |
1644
+ | a stylesheet cancelling the last row's `border-bottom` | the table does it |
1645
+ | a `TreeCanvas` treeitem drawing its own column layout, gap or cursor | drop all three |
1646
+ | a `TreeCanvas` actions strip spreading `ctx.press(id)` with its own class and `aria-hidden` | `{...ctx.actions(id)}`, immediately after the node's treeitem |
1647
+ | a `TreeCanvas` card drawing its own two-column grid for that strip | drop it: the card is one column and the strip is drawn over its end |
1648
+ | a `TreeCanvas` consumer drawing an add control beside a card's name | `renderUnder`, which hangs it on the branch below the card |
1649
+ | a `TreeCanvas` consumer hanging a `Menu` under a card to add a child | `AddPill`, whose sections are the same list |
1650
+ | a `TreeCanvas` consumer opening a modal to name a child | `composing`, which draws that form in the chart where the child will go |
1651
+ | a chart of org chart nodes expecting each card under its own parent | `ranks="per-parent"`, or read a rank as a row |
1652
+ | `layoutForest` called with `{ gapX, gapY }` and expected to start at the origin | the same, plus `margin` where the frame clips |
1653
+ | a test reading a card's `transform` in the same tick as the change | read it after a frame, or from `layoutForest` |
1654
+ | `layoutConnectors(layout)` read as `string[]` | `{ id, parent, d }[]`; map to `.d` for the path, and `.id` is the node it arrives at |
1655
+ | a chart drawing its own compact org-chart node, its lead strip or its hue | `appearance="node"`, `cardTone`, `OrgNodeLabel` and `OrgNodeLead` |
1656
+
1657
+ | `<AppShell sidebarWidth={64}>` | `style={{ '--crewlet-app-shell-rail': '64px' }}` on the shell, or set `--size-shell-rail` |
1658
+ | `<AppShell topbarHeight={72}>` | `style={{ '--crewlet-app-shell-topbar': '72px' }}` on the shell |
1659
+ | a sidebar or header stylesheet with `position: fixed`, a width and a top offset | drop all three: the shell's grid places both slots |
1660
+ | a page that scrolled as a whole | it scrolls in `main`; pass `mainId` or `mainRef` to reach that scroller |
1661
+ | a `<Section>` relying on the 80px marketing band it defaulted to | `spacing="md"`; the default is `compact`, the console band |
1662
+ | a `<BarList emptyLabel>` relied on to carry an inset of its own | it starts where the bars start; the panel holding it carries the inset |
1663
+ | a `<Card>` with a header whose other children were relied on to sit flush | pass `padding="none"`; a card draws its own content in a body now |
1664
+ | a `<Card.Title>` relied on to wrap onto a second line | it is one line; the subtitle beside it gives up its tail first |
1665
+ | a `<Card padding="lg">` built from slots, relied on to pad the card itself | it pads what the card holds beside its slots; the slots still reach its edges |
1666
+ | an `<AutoGrid>` relied on to stretch a row that is short of cards | the tracks are kept; a lone card is drawn at a full card's width |
1667
+ | a `<Card variant="subtle">` relied on for the 16px inset | pass `padding="md"` |
1668
+ | a `<Card variant="subtle">` expected flush with the page | it is the panel's ground with no lift, at a 12px inset; `variant="outlined"` is the transparent one |
1669
+ | a `<Section>` description measured at 13px | it is `--font-size-xs` |
1670
+ | a `<PageHeader>`, `<Card.Title>`, `<Section>` or `<EmptyState>` title measured at 1.2 leading | it is `--font-line-height-normal` |
1671
+ | a `<Section>` description measured at 13px | it is `--font-size-xs` |
1672
+ | a `<PageHeader>`, `<Card.Title>`, `<Section>` or `<EmptyState>` title measured at 1.2 leading | it is `--font-line-height-normal` |
1673
+ | a stylesheet matching the pill chip's 6px corner | it is `--radius-chip`, one hairline inside the well |
1674
+ | a pill `<Tabs>` or `<SegmentedControl>` chip measured at a control step | it is 26px, and 24px at `size="sm"` |
1675
+ | a pill or underline tab narrower than 24px, which a one-letter label or a bare glyph drew | every tab carries `min-width: var(--size-target-min)` |
1676
+ | a pill chip's content aligned to the start | it is centred; the segmented CARD row is the one that starts at its own inset |
1677
+ | a `size="sm"` pill chip measured at 8px of side padding with a 14px glyph | 4px and 12px, so a rail's foot reads quieter |
1678
+
1679
+ **Card is the engine dashboard's panel (C1).** A card built from
1680
+ `Card.Header`, `Card.Body` or `Card.Footer` is FLUSH: it carries no padding of
1681
+ its own, the slots carry theirs at the panel's inset, and the card clips them
1682
+ to its radius so the header's rule and a table's first row reach the edges. A
1683
+ card given raw children is still padded, at `--spacing-4` rather than the
1684
+ `--spacing-5`/`--spacing-6` block it was. `Card.Header` draws its rule unless
1685
+ `divided={false}`, renders its subtitle beside the title rather than under it,
1686
+ and no longer wraps the title in `.crewlet-card__header-text`. Its row is ONE
1687
+ LINE: the title never wraps, and the subtitle absorbs the shrinking, so a head
1688
+ one pixel too narrow gives up the subtitle's tail rather than the title's. Two grounds are
1689
+ new, `variant="inset"` and `variant="quiet"`.
1690
+
1691
+ A card's OWN content, where it holds any beside those slots, is drawn in a body
1692
+ of its own at the inset `padding` names, so a sentence written straight under a
1693
+ `Card.Header` starts on the same line as the title rather than against the
1694
+ border. `padding` on a flush card never goes back onto the card itself: the
1695
+ slots have to reach its edges, and `padding="none"` leaves that content exactly
1696
+ as it was written, for a table.
1697
+
1698
+ Every padding step moved with it, so a card that names one renders differently
1699
+ too: `sm` is 8px over 16px (was 12 over 16), `md` is 16px (was 20 over 24) and
1700
+ `lg` is 20px over 24px (was 24 over 32). The section steps are those same five
1701
+ now, rather than a scale of three on which `tight` meant a vertical-only step
1702
+ and `md` meant one inset on a body and another on a footer. And a `default` card that is NOT built
1703
+ from slots now LIFTS, at `--shadow-xs` with the dark theme's hairline along its
1704
+ top edge, which is what the dashboard's own panel carries; a flush one stays
1705
+ flat, because its rule and its slots are what give it depth. Slots reached
1706
+ through a fragment count as slots, so the flush recipe survives the ordinary
1707
+ `{ready ? <><Card.Header /><Card.Body /></> : <Skeleton />}`.
1708
+
1709
+ **The type registers move onto the dashboard's values (C1).** `Eyebrow` is THE
1710
+ micro-label now, the same register `Text variant="label"` and a table's column
1711
+ head take: 11px, medium, the wide track, uppercase, tertiary, in the document's
1712
+ own face. It was monospace, one size up, semibold and on the wider track, and
1713
+ `variant="accent"` spent the accent FILL on a word, which clears 3:1 as a mark
1714
+ and not the 4.5:1 a word owes; it paints `--color-brand-accent-ink` now.
1715
+ `Text variant="heading"` and a `Section` title track tighter, a `Section`
1716
+ description is 13px with a measure rather than 12px truncated, and `Container`'s
1717
+ gutter is 20px, 12px under 480px.
1718
+
1719
+ **Rows, expanders and records (C1).** `List` renders `variant="divided"` unless
1720
+ told otherwise: it is the list every screen draws, and `plain` is one word away.
1721
+ A `Disclosure` head is 12px with a 4px gap, its `size="compact"` no longer
1722
+ restates that register, and `variant="aside"` rules its panel in
1723
+ `--color-border-hover`. An `Accordion` trigger is semibold on the tight track
1724
+ and its compact trigger lands on the disclosure head's own register. A
1725
+ `Timeline` step takes `--radius-md`. `DescriptionList` was already the
1726
+ dashboard's own key/value grid and is unchanged.
1727
+
1728
+ **Code, chips and keycaps (C1).** `CodeBlock` WRAPS unless `wrap={false}`, and
1729
+ its block is 12px on `--radius-md` in `--color-text-secondary` at `--spacing-3`,
1730
+ with a header that is a rule rather than a second ground. `InlineCode`
1731
+ `tone="inherit"` keeps the inset chip and takes only the message's ink (the
1732
+ composite is measured by `@crewlethq/tokens`, tightest pair 4.61:1), and
1733
+ `variant="reference"` is `--color-text-secondary`. `Kbd` is
1734
+ `--color-text-tertiary` on `--color-surface-inset` at regular weight.
1735
+ `Copyable`'s value is `--color-text-secondary`, its chip takes the chip's own
1736
+ padding and its block the `--color-surface-subtle` surface.
1737
+
1738
+ | was | is now |
1739
+ | --- | ------ |
1740
+ | `<Card padding="none">` with slots, plus inline padding on each slot | drop both: a card built from slots is flush and its slots are inset |
1741
+ | `<Card padding="sm">` or `padding="lg"` laid out against the old numbers | `sm` is 8px over 16px and `lg` is 20px over 24px |
1742
+ | a `default` card a stylesheet assumed was flat | it carries `--shadow-xs` and the hairline unless it is built from slots |
1743
+ | a `Card.Title` holding a phrase with inline markup in it | the title is a flex row at `--spacing-2`, so give it one run of text and put a mark beside it |
1744
+ | `<Card.Header divided>` | drop it, the rule is the default; `divided={false}` for a card whose body is one paragraph |
1745
+ | a stylesheet or query for `.crewlet-card__header-text` | the title and the subtitle are siblings in `.crewlet-card__header` |
1746
+ | a stylesheet for `.crewlet-card__header--divided` | `.crewlet-card__header--plain`, on the opposite condition |
1747
+ | a plain `<List>` relied on for an undivided stack | `<List variant="plain">` |
1748
+ | `<CodeBlock wrap>` | drop it, wrapping is the default |
1749
+ | `<CodeBlock>` relied on for a sideways scroll | `<CodeBlock wrap={false}>` |
1750
+ | a stylesheet for `.crewlet-codeblock--wrap` | `.crewlet-codeblock--nowrap`, on the opposite condition |
1751
+ | an `Eyebrow` relied on for a monospace kicker | `<Text variant="label" mono>` |
1752
+ | a stylesheet keyed on `.crewlet-input:focus-within` or on a field's focus `box-shadow` | `.crewlet-input:has(.crewlet-input__control:focus-visible)`, and the ring is an `outline` |
1753
+ | a stylesheet keyed on `.crewlet-select__trigger:focus-visible` for the select's ring | `.crewlet-select:focus-within`, which is the frame a reader reads as the field |
1754
+ | a stylesheet keyed on `.crewlet-switch__control:focus-visible` | `.crewlet-switch__track:has(.crewlet-switch__control:focus-visible)` |
1755
+
1756
+ | `<OrgTableAdd options={[{ label, icon, disabledReason, onSelect }]} />` | `<OrgTableAdd sections={[{ label, icon, disabled, onSelect }]} />`, which is `AddPill`'s own list |
1757
+ | an `<OrgTableAdd>` rendered inside `<OrgTableActions>` | a sibling before it, so the plus is drawn at rest |
1758
+ | a stylesheet targeting `.crewlet-org-table__pill` or `.crewlet-org-table__pill-btn` | `.crewlet-add-pill__sections` and `.crewlet-add-pill__section` |
1759
+ | a table whose add offers a number of kinds other than three | set `--crewlet-org-table-add-kinds` on `.crewlet-org-table` |
1760
+ | a layout measured against the org table's 20px indent or 36px row | 40px a level and 48px a row |
61
1761
 
62
1762
  ## Why CSS, not CSS-in-JS
63
1763