@flikk/ui 1.0.0-beta.31 → 1.0.0-beta.33

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 (460) hide show
  1. package/CHANGELOG.md +497 -22
  2. package/dist/components/ai/AgentRequest/AgentRequest.animations.d.ts +25 -0
  3. package/dist/components/ai/AgentRequest/AgentRequest.animations.js +81 -0
  4. package/dist/components/ai/AgentRequest/AgentRequest.d.ts +3 -0
  5. package/dist/components/ai/AgentRequest/AgentRequest.js +435 -0
  6. package/dist/components/ai/AgentRequest/AgentRequest.theme.d.ts +2 -0
  7. package/dist/components/ai/AgentRequest/AgentRequest.theme.js +106 -0
  8. package/dist/components/ai/AgentRequest/AgentRequest.types.d.ts +241 -0
  9. package/dist/components/ai/AgentRequest/index.d.ts +3 -0
  10. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +1 -1
  11. package/dist/components/ai/PromptInput/PromptInput.js +2 -2
  12. package/dist/components/ai/PromptInput/PromptInput.theme.js +2 -2
  13. package/dist/components/ai/PromptSuggestion/PromptSuggestion.theme.js +2 -2
  14. package/dist/components/ai/Reasoning/Reasoning.js +27 -3
  15. package/dist/components/ai/Reasoning/Reasoning.theme.d.ts +6 -0
  16. package/dist/components/ai/Reasoning/Reasoning.theme.js +36 -0
  17. package/dist/components/ai/Reasoning/Reasoning.types.d.ts +58 -5
  18. package/dist/components/ai/Reasoning/ReasoningTrace.d.ts +11 -0
  19. package/dist/components/ai/Reasoning/ReasoningTrace.js +84 -0
  20. package/dist/components/ai/Reasoning/index.d.ts +2 -1
  21. package/dist/components/ai/StreamingResponse/AnimatedText.d.ts +7 -0
  22. package/dist/components/ai/StreamingResponse/AnimatedText.js +8 -7
  23. package/dist/components/ai/StreamingResponse/MarkdownRenderer.d.ts +5 -0
  24. package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +4 -4
  25. package/dist/components/ai/StreamingResponse/StreamingCursor.d.ts +2 -2
  26. package/dist/components/ai/StreamingResponse/StreamingCursor.js +10 -7
  27. package/dist/components/ai/StreamingResponse/StreamingResponse.animations.d.ts +10 -0
  28. package/dist/components/ai/StreamingResponse/StreamingResponse.animations.js +59 -33
  29. package/dist/components/ai/StreamingResponse/StreamingResponse.js +13 -7
  30. package/dist/components/ai/StreamingResponse/StreamingResponse.types.d.ts +33 -2
  31. package/dist/components/ai/StreamingResponse/StreamingSurface.d.ts +13 -0
  32. package/dist/components/ai/StreamingResponse/StreamingSurface.js +38 -0
  33. package/dist/components/ai/StreamingResponse/WordAnimationContext.d.ts +5 -0
  34. package/dist/components/ai/StreamingResponse/WordAnimationContext.js +5 -4
  35. package/dist/components/ai/StreamingResponse/index.d.ts +4 -2
  36. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
  37. package/dist/components/ai/index.d.ts +3 -2
  38. package/dist/components/ai/index.js +4 -0
  39. package/dist/components/canvas/BaseNode.js +1 -1
  40. package/dist/components/canvas/CanvasToolbar.js +0 -1
  41. package/dist/components/canvas/Edge.js +2 -2
  42. package/dist/components/canvas/NodeControls.js +0 -1
  43. package/dist/components/canvas/NodeRenderer.js +2 -2
  44. package/dist/components/charts/ActivityRings/ActivityRings.js +2 -2
  45. package/dist/components/charts/ActivityRings/ActivityRings.theme.js +1 -1
  46. package/dist/components/charts/AreaChart/AreaChart.js +3 -3
  47. package/dist/components/charts/AreaChart/AreaChart.theme.js +3 -3
  48. package/dist/components/charts/BarChart/BarChart.js +4 -4
  49. package/dist/components/charts/ComboChart/BarRenderer.js +2 -2
  50. package/dist/components/charts/ComboChart/BarRenderer.types.d.ts +1 -1
  51. package/dist/components/charts/ComboChart/ComboChartCanvas.js +1 -1
  52. package/dist/components/charts/ComboChart/LineRenderer.js +1 -1
  53. package/dist/components/charts/ComboChart/LineRenderer.types.d.ts +1 -1
  54. package/dist/components/charts/DonutChart/DonutChart.theme.js +4 -4
  55. package/dist/components/charts/DonutChart/MultiSegmentDonutChart.theme.js +1 -1
  56. package/dist/components/charts/Heatmap/Heatmap.js +49 -42
  57. package/dist/components/charts/Heatmap/Heatmap.theme.d.ts +6 -2
  58. package/dist/components/charts/Heatmap/Heatmap.theme.js +20 -8
  59. package/dist/components/charts/Heatmap/Heatmap.types.d.ts +49 -82
  60. package/dist/components/charts/Heatmap/HeatmapCell.js +30 -81
  61. package/dist/components/charts/Heatmap/HeatmapLegend.d.ts +7 -6
  62. package/dist/components/charts/Heatmap/HeatmapLegend.js +6 -6
  63. package/dist/components/charts/Heatmap/index.d.ts +1 -1
  64. package/dist/components/charts/Heatmap/utils/heatmapUtils.d.ts +7 -12
  65. package/dist/components/charts/Heatmap/utils/heatmapUtils.js +16 -61
  66. package/dist/components/charts/Heatmap/utils/heatmapUtils.test.d.ts +1 -0
  67. package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.js +1 -1
  68. package/dist/components/charts/LineChart/LineChart.js +2 -2
  69. package/dist/components/charts/LollipopChart/LollipopChart.js +1 -1
  70. package/dist/components/charts/RadarChart/RadarChart.js +1 -1
  71. package/dist/components/charts/StackedBarChart/StackedBarChart.js +3 -3
  72. package/dist/components/charts/hooks/useChartAccessibility.js +9 -6
  73. package/dist/components/charts/index.d.ts +4 -8
  74. package/dist/components/charts/index.js +2 -4
  75. package/dist/components/charts/shared/ChartText/ChartText.theme.js +1 -1
  76. package/dist/components/charts/shared/index.d.ts +1 -1
  77. package/dist/components/charts/theme/chart.theme.js +3 -3
  78. package/dist/components/charts/utils/animation-utils.d.ts +12 -7
  79. package/dist/components/charts/utils/animation-utils.js +19 -19
  80. package/dist/components/core/Accordion/Accordion.theme.js +1 -1
  81. package/dist/components/core/Alert/Alert.animations.d.ts +11 -4
  82. package/dist/components/core/Alert/Alert.animations.js +11 -4
  83. package/dist/components/core/Alert/Alert.js +16 -2
  84. package/dist/components/core/Alert/Alert.theme.js +1 -1
  85. package/dist/components/core/Alert/Alert.types.d.ts +4 -2
  86. package/dist/components/core/AlertDialog/AlertDialog.js +2 -2
  87. package/dist/components/core/Avatar/Avatar.theme.js +1 -1
  88. package/dist/components/core/Badge/Badge.animations.d.ts +0 -5
  89. package/dist/components/core/Badge/Badge.animations.js +5 -84
  90. package/dist/components/core/Badge/Badge.d.ts +7 -4
  91. package/dist/components/core/Badge/Badge.js +27 -64
  92. package/dist/components/core/Badge/Badge.theme.js +8 -12
  93. package/dist/components/core/Badge/Badge.types.d.ts +18 -34
  94. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +4 -4
  95. package/dist/components/core/Button/Button.js +7 -43
  96. package/dist/components/core/Button/Button.theme.js +5 -5
  97. package/dist/components/core/Button/Button.types.d.ts +8 -12
  98. package/dist/components/core/Button/index.d.ts +0 -2
  99. package/dist/components/core/Calendar/Calendar.theme.js +1 -1
  100. package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +2 -2
  101. package/dist/components/core/Card/Card.js +7 -2
  102. package/dist/components/core/Card/Card.theme.js +29 -1
  103. package/dist/components/core/Card/Card.types.d.ts +20 -0
  104. package/dist/components/core/Card/CardBody.js +9 -1
  105. package/dist/components/core/Card/CardContext.d.ts +13 -0
  106. package/dist/components/core/Card/CardContext.js +14 -0
  107. package/dist/components/core/Card/CardHeader.js +4 -1
  108. package/dist/components/core/ContextMenu/ContextMenu.theme.js +4 -3
  109. package/dist/components/core/ContextMenu/ContextMenu.types.d.ts +8 -1
  110. package/dist/components/core/ContextMenu/ContextMenuSub.d.ts +11 -1
  111. package/dist/components/core/ContextMenu/ContextMenuSub.js +35 -22
  112. package/dist/components/core/ContextMenu/ContextMenuSubBody.js +14 -3
  113. package/dist/components/core/ContextMenu/ContextMenuSubTrigger.js +21 -8
  114. package/dist/components/core/DragDrop/DragDrop.theme.js +2 -2
  115. package/dist/components/core/Drawer/Drawer.theme.js +1 -1
  116. package/dist/components/core/Drawer/DrawerHeader.js +0 -1
  117. package/dist/components/core/Dropdown/DropdownMenu.js +22 -7
  118. package/dist/components/core/Link/Link.js +1 -1
  119. package/dist/components/core/Masonry/Masonry.theme.js +1 -1
  120. package/dist/components/core/MenuItem/MenuItem.js +2 -1
  121. package/dist/components/core/MenuItem/MenuItem.theme.js +13 -1
  122. package/dist/components/core/Message/Message.js +2 -0
  123. package/dist/components/core/Message/Message.theme.js +3 -3
  124. package/dist/components/core/Modal/Modal.animations.d.ts +1 -1
  125. package/dist/components/core/Modal/Modal.animations.js +50 -11
  126. package/dist/components/core/Modal/Modal.js +5 -1
  127. package/dist/components/core/Modal/Modal.theme.js +1 -1
  128. package/dist/components/core/Modal/ModalHeader.js +0 -1
  129. package/dist/components/core/ModalStack/ModalStackModal.js +3 -1
  130. package/dist/components/core/NavItem/NavItem.js +3 -2
  131. package/dist/components/core/NavItem/NavItem.theme.js +1 -1
  132. package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +4 -2
  133. package/dist/components/core/PageHeading/PageHeading.theme.js +1 -1
  134. package/dist/components/core/Pagination/Pagination.theme.js +2 -2
  135. package/dist/components/core/Pill/Pill.js +34 -18
  136. package/dist/components/core/Pill/Pill.theme.js +4 -2
  137. package/dist/components/core/Pill/Pill.types.d.ts +14 -1
  138. package/dist/components/core/Popover/PopoverBody.js +16 -5
  139. package/dist/components/core/Progress/Progress.d.ts +0 -25
  140. package/dist/components/core/Progress/Progress.js +79 -11
  141. package/dist/components/core/Progress/Progress.theme.js +17 -0
  142. package/dist/components/core/Progress/Progress.types.d.ts +47 -1
  143. package/dist/components/core/Rating/Rating.theme.js +1 -1
  144. package/dist/components/core/Rating/Rating.types.d.ts +3 -2
  145. package/dist/components/core/ScrollArea/ScrollArea.js +1 -1
  146. package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
  147. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  148. package/dist/components/core/Segmented/SegmentedContext.js +1 -0
  149. package/dist/components/core/Sidebar/Sidebar.js +5 -2
  150. package/dist/components/core/Sidebar/Sidebar.theme.js +2 -2
  151. package/dist/components/core/Sidebar/SidebarContent.js +1 -1
  152. package/dist/components/core/Sidebar/SidebarHeader.js +1 -1
  153. package/dist/components/core/Sidebar/SidebarSearch.js +2 -2
  154. package/dist/components/core/Sidebar/SidebarToggle.js +1 -1
  155. package/dist/components/core/Sidebar/SidebarUserProfile.js +1 -1
  156. package/dist/components/core/SlidingNumber/SlidingNumber.d.ts +8 -0
  157. package/dist/components/core/SlidingNumber/SlidingNumber.js +12 -3
  158. package/dist/components/core/Sortable/Sortable.theme.js +1 -1
  159. package/dist/components/core/Tabs/Tabs.theme.js +1 -1
  160. package/dist/components/core/Tag/Tag.theme.js +1 -1
  161. package/dist/components/core/Toast/ToastProvider.js +1 -1
  162. package/dist/components/core/Tree/Tree.theme.js +1 -1
  163. package/dist/components/core/index.d.ts +0 -2
  164. package/dist/components/core/index.js +0 -4
  165. package/dist/components/core/shared/ChartTooltip/ChartTooltip.animations.d.ts +4 -0
  166. package/dist/components/core/shared/ChartTooltip/ChartTooltip.animations.js +20 -0
  167. package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.js +1 -1
  168. package/dist/components/core/shared/Ripple.d.ts +16 -0
  169. package/dist/components/core/shared/Ripple.js +141 -0
  170. package/dist/components/data-display/GanttChart/GanttChart.theme.js +6 -6
  171. package/dist/components/data-display/GanttChart/GanttTaskBar.js +1 -1
  172. package/dist/components/data-display/GanttChart/GanttTimelineHeader.js +1 -1
  173. package/dist/components/data-display/GanttChart/GanttToolbar.js +0 -1
  174. package/dist/components/data-display/KPI/KPI.js +11 -20
  175. package/dist/components/data-display/KPI/KPI.theme.js +8 -4
  176. package/dist/components/data-display/Metric/Metric.theme.js +4 -4
  177. package/dist/components/data-display/Table/Table.theme.js +2 -2
  178. package/dist/components/data-display/Table/TableColumnManager.js +1 -2
  179. package/dist/components/data-display/Table/TableContext.js +1 -0
  180. package/dist/components/data-display/Table/TableFilter.js +9 -2
  181. package/dist/components/data-display/Table/TableSelectionHeader.js +1 -1
  182. package/dist/components/data-display/Timeline/Timeline.theme.js +1 -1
  183. package/dist/components/effects/CustomCursor/CustomCursor.d.ts +1 -1
  184. package/dist/components/effects/CustomCursor/CustomCursor.js +1 -1
  185. package/dist/components/effects/CustomCursor/CustomCursor.theme.js +4 -4
  186. package/dist/components/effects/Neumorphic/InsetCircleButton.js +1 -1
  187. package/dist/components/effects/Overlay/Overlay.js +7 -3
  188. package/dist/components/effects/index.d.ts +0 -32
  189. package/dist/components/effects/index.js +0 -16
  190. package/dist/components/effects/shared/ShaderCanvas/ShaderCanvas.d.ts +0 -2
  191. package/dist/components/effects/shared/ShaderCanvas/ShaderCanvas.js +1 -93
  192. package/dist/components/effects/shared/ShaderCanvas/ShaderCanvas.types.d.ts +0 -10
  193. package/dist/components/effects/shared/ShaderCanvas/index.d.ts +1 -1
  194. package/dist/components/effects/shared/index.d.ts +1 -3
  195. package/dist/components/effects/shared/shaderNoise.glsl.d.ts +1 -1
  196. package/dist/components/effects/shared/shaderNoise.glsl.js +2 -11
  197. package/dist/components/forms/Checkbox/Checkbox.theme.js +1 -1
  198. package/dist/components/forms/ColorPicker/ColorPicker.js +45 -3
  199. package/dist/components/forms/ColorPicker/ColorPickerBody.js +8 -4
  200. package/dist/components/forms/ColorPicker/ColorPickerHeader.js +1 -1
  201. package/dist/components/forms/ColorPicker/ColorPickerImage.js +1 -1
  202. package/dist/components/forms/Combobox/Combobox.js +10 -4
  203. package/dist/components/forms/Combobox/Combobox.theme.js +9 -0
  204. package/dist/components/forms/Combobox/Combobox.types.d.ts +13 -0
  205. package/dist/components/forms/DatePicker/DatePicker.js +19 -12
  206. package/dist/components/forms/DatePicker/DatePicker.theme.js +5 -0
  207. package/dist/components/forms/DatePicker/DatePicker.types.d.ts +8 -0
  208. package/dist/components/forms/DatePicker/DatePickerContext.d.ts +0 -1
  209. package/dist/components/forms/DatePicker/DatePickerTrigger.js +5 -3
  210. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +6 -1
  211. package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +8 -0
  212. package/dist/components/forms/DateRangePicker/DateRangePickerContext.js +1 -0
  213. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +5 -3
  214. package/dist/components/forms/FileUpload/FileUpload.theme.js +2 -2
  215. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +1 -1
  216. package/dist/components/forms/Input/Input.js +17 -12
  217. package/dist/components/forms/Input/Input.theme.js +10 -0
  218. package/dist/components/forms/Input/Input.types.d.ts +14 -0
  219. package/dist/components/forms/InputAddress/InputAddress.js +5 -5
  220. package/dist/components/forms/InputAddress/InputAddress.types.d.ts +9 -2
  221. package/dist/components/forms/InputCounter/InputCounter.js +194 -17
  222. package/dist/components/forms/InputCounter/InputCounter.theme.d.ts +12 -0
  223. package/dist/components/forms/InputCounter/InputCounter.theme.js +92 -2
  224. package/dist/components/forms/InputCounter/InputCounter.types.d.ts +47 -4
  225. package/dist/components/forms/InputCreditCard/InputCreditCard.js +4 -4
  226. package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +7 -0
  227. package/dist/components/forms/Mention/Mention.js +13 -11
  228. package/dist/components/forms/Radio/Radio.theme.js +1 -1
  229. package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +12 -16
  230. package/dist/components/forms/RichTextEditor/RichTextEditor.js +174 -1680
  231. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +12 -56
  232. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +60 -118
  233. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +62 -126
  234. package/dist/components/forms/RichTextEditor/RichTextLinkField.d.ts +18 -0
  235. package/dist/components/forms/RichTextEditor/RichTextLinkField.js +46 -0
  236. package/dist/components/forms/RichTextEditor/RichTextToolbar.d.ts +15 -0
  237. package/dist/components/forms/RichTextEditor/RichTextToolbar.js +93 -0
  238. package/dist/components/forms/RichTextEditor/editorCommands.d.ts +50 -0
  239. package/dist/components/forms/RichTextEditor/editorCommands.js +58 -0
  240. package/dist/components/forms/RichTextEditor/editorState.d.ts +19 -0
  241. package/dist/components/forms/RichTextEditor/editorState.js +103 -0
  242. package/dist/components/forms/RichTextEditor/index.d.ts +5 -3
  243. package/dist/components/forms/RichTextEditor/sanitizeHtml.d.ts +38 -0
  244. package/dist/components/forms/RichTextEditor/sanitizeHtml.js +179 -0
  245. package/dist/components/forms/RichTextEditor/sanitizeHtml.test.d.ts +1 -0
  246. package/dist/components/forms/Select/Select.js +19 -12
  247. package/dist/components/forms/Select/Select.theme.js +9 -1
  248. package/dist/components/forms/Select/Select.types.d.ts +86 -8
  249. package/dist/components/forms/Select/useSelectState.d.ts +9 -3
  250. package/dist/components/forms/Select/useSelectState.js +4 -1
  251. package/dist/components/forms/SelectableCard/SelectableCard.js +2 -1
  252. package/dist/components/forms/SelectableCard/SelectableCard.theme.js +1 -1
  253. package/dist/components/forms/Switch/Switch.theme.js +2 -2
  254. package/dist/components/forms/Textarea/Textarea.js +14 -25
  255. package/dist/components/forms/TimePicker/TimePicker.theme.js +10 -1
  256. package/dist/components/forms/TimePicker/TimePicker.types.d.ts +11 -0
  257. package/dist/components/forms/TimePicker/TimePickerTrigger.js +7 -5
  258. package/dist/components/forms/forms.theme.d.ts +57 -0
  259. package/dist/components/forms/forms.theme.js +102 -13
  260. package/dist/components/forms/index.js +1 -0
  261. package/dist/components/generative/FormShell.d.ts +22 -0
  262. package/dist/components/generative/FormShell.js +102 -0
  263. package/dist/components/generative/GenerativeView.d.ts +52 -1
  264. package/dist/components/generative/GenerativeView.js +203 -11
  265. package/dist/components/generative/actions.d.ts +5 -0
  266. package/dist/components/generative/ai-sdk/index.d.ts +1 -0
  267. package/dist/components/generative/ai-sdk/index.js +1 -0
  268. package/dist/components/generative/ai-sdk/useGenerativeChat.d.ts +52 -0
  269. package/dist/components/generative/ai-sdk/useGenerativeChat.js +73 -0
  270. package/dist/components/generative/ai-sdk/useGenerativeChat.test.d.ts +1 -0
  271. package/dist/components/generative/fieldBinding.d.ts +31 -0
  272. package/dist/components/generative/fieldBinding.js +33 -0
  273. package/dist/components/generative/fieldBinding.test.d.ts +1 -0
  274. package/dist/components/generative/formScope.d.ts +71 -0
  275. package/dist/components/generative/formScope.js +135 -0
  276. package/dist/components/generative/index.d.ts +4 -0
  277. package/dist/components/generative/index.js +4 -0
  278. package/dist/components/generative/registry.d.ts +2 -0
  279. package/dist/components/generative/registry.js +11 -10
  280. package/dist/components/generative/schema.generated.js +199 -293
  281. package/dist/components/generative/toToolResult.d.ts +21 -0
  282. package/dist/components/generative/toToolResult.js +15 -0
  283. package/dist/components/generative/toToolResult.test.d.ts +1 -0
  284. package/dist/components/generative/validate.d.ts +10 -1
  285. package/dist/components/generative/validate.js +126 -11
  286. package/dist/components/layout/Grid/Grid.js +1 -0
  287. package/dist/components/layout/Section/Section.theme.js +1 -1
  288. package/dist/components/layout/Stack/Stack.js +1 -0
  289. package/dist/generative.schema.json +199 -293
  290. package/dist/index.js +1 -4
  291. package/dist/registry.json +2765 -2450
  292. package/dist/shadcn-compat.css +5 -5
  293. package/dist/styles/theme.test.d.ts +1 -0
  294. package/dist/styles.css +1 -1
  295. package/dist/test/render.d.ts +17 -0
  296. package/dist/themes/precision.css +232 -0
  297. package/dist/tools.json +200 -295
  298. package/dist/utils/composeEventHandlers.d.ts +19 -0
  299. package/dist/utils/composeEventHandlers.js +26 -0
  300. package/dist/utils/composeEventHandlers.test.d.ts +1 -0
  301. package/dist/utils/index.d.ts +1 -1
  302. package/package.json +31 -1
  303. package/src/global.scss +21 -28
  304. package/src/styles/theme.css +157 -50
  305. package/src/styles/theme.test.ts +119 -0
  306. package/tailwind.preset.cjs +30 -20
  307. package/dist/components/charts/FunnelChart/FunnelChart.animations.d.ts +0 -35
  308. package/dist/components/charts/FunnelChart/FunnelChart.animations.js +0 -59
  309. package/dist/components/charts/FunnelChart/FunnelChart.d.ts +0 -8
  310. package/dist/components/charts/FunnelChart/FunnelChart.js +0 -410
  311. package/dist/components/charts/FunnelChart/FunnelChart.theme.d.ts +0 -2
  312. package/dist/components/charts/FunnelChart/FunnelChart.theme.js +0 -12
  313. package/dist/components/charts/FunnelChart/FunnelChart.types.d.ts +0 -105
  314. package/dist/components/charts/FunnelChart/index.d.ts +0 -4
  315. package/dist/components/charts/ScatterPlot/ScatterPlot.d.ts +0 -9
  316. package/dist/components/charts/ScatterPlot/ScatterPlot.js +0 -208
  317. package/dist/components/charts/ScatterPlot/ScatterPlot.theme.d.ts +0 -18
  318. package/dist/components/charts/ScatterPlot/ScatterPlot.theme.js +0 -42
  319. package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +0 -60
  320. package/dist/components/charts/ScatterPlot/ScatterPlot.types.js +0 -19
  321. package/dist/components/charts/ScatterPlot/index.d.ts +0 -3
  322. package/dist/components/core/Button/Button.ripple.d.ts +0 -71
  323. package/dist/components/core/Button/Button.ripple.js +0 -236
  324. package/dist/components/core/Calendar/CalendarEvent/CalendarEvent.d.ts +0 -4
  325. package/dist/components/core/Calendar/CalendarEvent/CalendarEvent.theme.d.ts +0 -12
  326. package/dist/components/core/Calendar/CalendarEvent/CalendarEvent.types.d.ts +0 -23
  327. package/dist/components/core/Calendar/CalendarEvent/index.d.ts +0 -2
  328. package/dist/components/core/CardStack/CardStack.animations.d.ts +0 -47
  329. package/dist/components/core/CardStack/CardStack.d.ts +0 -3
  330. package/dist/components/core/CardStack/CardStack.js +0 -187
  331. package/dist/components/core/CardStack/CardStack.theme.d.ts +0 -6
  332. package/dist/components/core/CardStack/CardStack.theme.js +0 -33
  333. package/dist/components/core/CardStack/CardStack.types.d.ts +0 -139
  334. package/dist/components/core/CardStack/index.d.ts +0 -7
  335. package/dist/components/core/Carousel/Carousel.d.ts +0 -3
  336. package/dist/components/core/Carousel/Carousel.js +0 -83
  337. package/dist/components/core/Carousel/Carousel.theme.d.ts +0 -10
  338. package/dist/components/core/Carousel/Carousel.theme.js +0 -20
  339. package/dist/components/core/Carousel/Carousel.types.d.ts +0 -113
  340. package/dist/components/core/Carousel/CarouselBody.d.ts +0 -4
  341. package/dist/components/core/Carousel/CarouselBody.js +0 -59
  342. package/dist/components/core/Carousel/CarouselContext.d.ts +0 -3
  343. package/dist/components/core/Carousel/CarouselContext.js +0 -13
  344. package/dist/components/core/Carousel/CarouselItem.d.ts +0 -4
  345. package/dist/components/core/Carousel/CarouselItem.js +0 -11
  346. package/dist/components/core/Carousel/CarouselNext.d.ts +0 -4
  347. package/dist/components/core/Carousel/CarouselNext.js +0 -16
  348. package/dist/components/core/Carousel/CarouselPrevious.d.ts +0 -4
  349. package/dist/components/core/Carousel/CarouselPrevious.js +0 -16
  350. package/dist/components/core/Carousel/index.d.ts +0 -3
  351. package/dist/components/effects/ColorPanels/ColorPanels.d.ts +0 -8
  352. package/dist/components/effects/ColorPanels/ColorPanels.js +0 -66
  353. package/dist/components/effects/ColorPanels/ColorPanels.shaders.d.ts +0 -12
  354. package/dist/components/effects/ColorPanels/ColorPanels.shaders.js +0 -218
  355. package/dist/components/effects/ColorPanels/ColorPanels.types.d.ts +0 -29
  356. package/dist/components/effects/ColorPanels/ColorPanels.types.js +0 -3
  357. package/dist/components/effects/ColorPanels/index.d.ts +0 -2
  358. package/dist/components/effects/FlutedGlass/FlutedGlass.d.ts +0 -9
  359. package/dist/components/effects/FlutedGlass/FlutedGlass.js +0 -80
  360. package/dist/components/effects/FlutedGlass/FlutedGlass.shaders.d.ts +0 -12
  361. package/dist/components/effects/FlutedGlass/FlutedGlass.shaders.js +0 -335
  362. package/dist/components/effects/FlutedGlass/FlutedGlass.types.d.ts +0 -61
  363. package/dist/components/effects/FlutedGlass/FlutedGlass.types.js +0 -16
  364. package/dist/components/effects/FlutedGlass/index.d.ts +0 -3
  365. package/dist/components/effects/GlassEffect/GlassEffect.d.ts +0 -15
  366. package/dist/components/effects/GlassEffect/GlassEffect.js +0 -38
  367. package/dist/components/effects/GlassEffect/GlassEffect.types.d.ts +0 -19
  368. package/dist/components/effects/GlassEffect/index.d.ts +0 -2
  369. package/dist/components/effects/GlassSurface/GlassSurface.d.ts +0 -8
  370. package/dist/components/effects/GlassSurface/GlassSurface.js +0 -250
  371. package/dist/components/effects/GlassSurface/GlassSurface.types.d.ts +0 -66
  372. package/dist/components/effects/GlassSurface/index.d.ts +0 -2
  373. package/dist/components/effects/GodRays/GodRays.d.ts +0 -8
  374. package/dist/components/effects/GodRays/GodRays.js +0 -68
  375. package/dist/components/effects/GodRays/GodRays.shaders.d.ts +0 -10
  376. package/dist/components/effects/GodRays/GodRays.shaders.js +0 -128
  377. package/dist/components/effects/GodRays/GodRays.types.d.ts +0 -25
  378. package/dist/components/effects/GodRays/GodRays.types.js +0 -3
  379. package/dist/components/effects/GodRays/index.d.ts +0 -2
  380. package/dist/components/effects/GridPattern/GridPattern.d.ts +0 -18
  381. package/dist/components/effects/GridPattern/GridPattern.js +0 -65
  382. package/dist/components/effects/GridPattern/GridPattern.types.d.ts +0 -47
  383. package/dist/components/effects/GridPattern/index.d.ts +0 -2
  384. package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.d.ts +0 -10
  385. package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.js +0 -78
  386. package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.shaders.d.ts +0 -22
  387. package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.shaders.js +0 -239
  388. package/dist/components/effects/HalftoneCmyk/HalftoneCmyk.types.d.ts +0 -52
  389. package/dist/components/effects/HalftoneCmyk/index.d.ts +0 -2
  390. package/dist/components/effects/HalftoneDots/HalftoneDots.d.ts +0 -9
  391. package/dist/components/effects/HalftoneDots/HalftoneDots.js +0 -68
  392. package/dist/components/effects/HalftoneDots/HalftoneDots.shaders.d.ts +0 -13
  393. package/dist/components/effects/HalftoneDots/HalftoneDots.shaders.js +0 -296
  394. package/dist/components/effects/HalftoneDots/HalftoneDots.types.d.ts +0 -43
  395. package/dist/components/effects/HalftoneDots/HalftoneDots.types.js +0 -12
  396. package/dist/components/effects/HalftoneDots/index.d.ts +0 -3
  397. package/dist/components/effects/ImageDithering/ImageDithering.d.ts +0 -9
  398. package/dist/components/effects/ImageDithering/ImageDithering.js +0 -68
  399. package/dist/components/effects/ImageDithering/ImageDithering.shaders.d.ts +0 -12
  400. package/dist/components/effects/ImageDithering/ImageDithering.shaders.js +0 -117
  401. package/dist/components/effects/ImageDithering/ImageDithering.types.d.ts +0 -30
  402. package/dist/components/effects/ImageDithering/ImageDithering.types.js +0 -8
  403. package/dist/components/effects/ImageDithering/index.d.ts +0 -3
  404. package/dist/components/effects/MagneticElement/MagneticElement.d.ts +0 -30
  405. package/dist/components/effects/MagneticElement/MagneticElement.js +0 -111
  406. package/dist/components/effects/MagneticElement/MagneticElement.types.d.ts +0 -44
  407. package/dist/components/effects/MagneticElement/index.d.ts +0 -2
  408. package/dist/components/effects/Metaballs/Metaballs.d.ts +0 -8
  409. package/dist/components/effects/Metaballs/Metaballs.js +0 -59
  410. package/dist/components/effects/Metaballs/Metaballs.shaders.d.ts +0 -10
  411. package/dist/components/effects/Metaballs/Metaballs.shaders.js +0 -102
  412. package/dist/components/effects/Metaballs/Metaballs.types.d.ts +0 -16
  413. package/dist/components/effects/Metaballs/Metaballs.types.js +0 -3
  414. package/dist/components/effects/Metaballs/index.d.ts +0 -2
  415. package/dist/components/effects/PaperTexture/PaperTexture.d.ts +0 -9
  416. package/dist/components/effects/PaperTexture/PaperTexture.js +0 -61
  417. package/dist/components/effects/PaperTexture/PaperTexture.shaders.d.ts +0 -22
  418. package/dist/components/effects/PaperTexture/PaperTexture.shaders.js +0 -145
  419. package/dist/components/effects/PaperTexture/PaperTexture.types.d.ts +0 -19
  420. package/dist/components/effects/PaperTexture/index.d.ts +0 -2
  421. package/dist/components/effects/PulsingBorder/PulsingBorder.d.ts +0 -27
  422. package/dist/components/effects/PulsingBorder/PulsingBorder.js +0 -174
  423. package/dist/components/effects/PulsingBorder/PulsingBorder.shaders.d.ts +0 -14
  424. package/dist/components/effects/PulsingBorder/PulsingBorder.shaders.js +0 -258
  425. package/dist/components/effects/PulsingBorder/PulsingBorder.types.d.ts +0 -75
  426. package/dist/components/effects/PulsingBorder/PulsingBorder.types.js +0 -3
  427. package/dist/components/effects/PulsingBorder/index.d.ts +0 -2
  428. package/dist/components/effects/SmokeRing/SmokeRing.d.ts +0 -8
  429. package/dist/components/effects/SmokeRing/SmokeRing.js +0 -62
  430. package/dist/components/effects/SmokeRing/SmokeRing.shaders.d.ts +0 -14
  431. package/dist/components/effects/SmokeRing/SmokeRing.shaders.js +0 -130
  432. package/dist/components/effects/SmokeRing/SmokeRing.types.d.ts +0 -22
  433. package/dist/components/effects/SmokeRing/SmokeRing.types.js +0 -3
  434. package/dist/components/effects/SmokeRing/index.d.ts +0 -2
  435. package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.d.ts +0 -9
  436. package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.js +0 -59
  437. package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.shaders.d.ts +0 -14
  438. package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.shaders.js +0 -129
  439. package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.types.d.ts +0 -23
  440. package/dist/components/effects/StaticGradientMesh/StaticGradientMesh.types.js +0 -3
  441. package/dist/components/effects/StaticGradientMesh/index.d.ts +0 -2
  442. package/dist/components/effects/Water/Water.d.ts +0 -9
  443. package/dist/components/effects/Water/Water.js +0 -65
  444. package/dist/components/effects/Water/Water.shaders.d.ts +0 -11
  445. package/dist/components/effects/Water/Water.shaders.js +0 -123
  446. package/dist/components/effects/Water/Water.types.d.ts +0 -25
  447. package/dist/components/effects/Water/index.d.ts +0 -2
  448. package/dist/components/effects/shared/bayerMatrices.glsl.d.ts +0 -10
  449. package/dist/components/effects/shared/bayerMatrices.glsl.js +0 -44
  450. package/dist/components/effects/shared/imageUV.glsl.d.ts +0 -12
  451. package/dist/components/effects/shared/imageUV.glsl.js +0 -26
  452. package/dist/components/forms/RichTextEditor/RichTextEditor.animations.d.ts +0 -7
  453. package/dist/components/forms/RichTextEditor/RichTextEditor.animations.js +0 -32
  454. package/dist/utils/calendarUtils.d.ts +0 -60
  455. /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.d.ts +0 -0
  456. /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.theme.d.ts +0 -0
  457. /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.theme.js +0 -0
  458. /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltip.types.d.ts +0 -0
  459. /package/dist/components/{charts → core}/shared/ChartTooltip/ChartTooltipContent.d.ts +0 -0
  460. /package/dist/components/{charts → core}/shared/ChartTooltip/index.d.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -1,20 +1,495 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0-beta.33] - 2026-08-20
4
+
5
+ ### Breaking Changes
6
+
7
+ - 07713f5: **Fixed `ColorPicker` emitting `onChange` on mount, and echoing values the parent pushed in.**
8
+
9
+ `prevOutputRef` started as `undefined`, so the very first pass of the notify effect always "differed" and fired `onChange` — before any interaction. Worse, ColorPicker re-derives its output through hex → HSL (integer rounding) → hex, so the emitted string often isn't the one that was passed in (`#3B82F6` round-trips to `#3C83F6`, casing normalizes). A consumer cannot tell that apart from a real user edit:
10
+
11
+ - Any app tracking "has the user modified this?" recorded a **phantom override** — merely rendering the picker (e.g. selecting a layer in an inspector) wrote a colour identical to the one already in force.
12
+ - A parent that stores what it receives and feeds it back could ping-pong into `Maximum update depth exceeded`, since each render handed back a value that re-entered as a new `value` prop.
13
+
14
+ Two changes:
15
+
16
+ 1. **The first effect pass seeds the baseline and emits nothing.** Mounting is not a user edit.
17
+ 2. **A value arriving via the controlled `value` prop is recorded as already in force**, using a new pure `formatColorAs(color, format)` that mirrors the display pipeline. The parent's own value is therefore never echoed back.
18
+
19
+ Real user changes (preset click, hex entry, slider drag, format switch) emit exactly as before — now exactly once.
20
+
21
+ `InputCounter` was audited as the only other component emitting from an effect. Its emission is deliberate and stays: it fires only when the incoming `value` is outside `min`/`max`, which is a genuine correction the parent must know about — suppressing it would leave the parent disagreeing with what's displayed.
22
+
23
+ CLAUDE.md §9 now states the rule: a controlled component never emits a change for a value the user didn't produce.
24
+
25
+ - 07713f5: **Fixed `ColorPicker` panel overflowing the viewport instead of clamping to it.**
26
+
27
+ `ColorPicker.Body` positions through `useSelectPortal`, which measures the panel via the `contentRef` it **returns** and shifts the panel left when its right edge would cross the viewport edge. Body declared its _own_ `contentRef` for click-outside and attached that one to the panel, so the hook's ref stayed `null` for the panel's whole lifetime.
28
+
29
+ With nothing to measure, `measureDropdownWidth` fell through to its last-resort fallback — the **trigger's** width. Collision detection then fitted a ~32px box, concluded there was plenty of room, and left the ~320px panel anchored at `bottom-start` running off the right edge. The same null ref also disabled the vertical clamp and left the panel's `ResizeObserver` observing nothing.
30
+
31
+ Body now takes `contentRef` from the hook and attaches it to the panel, which is what every other `useSelectPortal` consumer already does. Click-outside uses the same ref — one element, one ref, no merging needed.
32
+
33
+ Audited the other nine consumers (Select, Combobox, Dropdown, Tooltip, Popover, TimePicker, InputTag, DatePicker, DateRangePicker): all wire the hook's ref correctly. ColorPicker was the only one.
34
+
35
+ - 07713f5: **Fixed a family of components that looked controlled but weren't.** Two distinct root causes, both of which made a component silently disagree with the props it was given.
36
+
37
+ ### Controlled props are now authoritative (`Pill`, `DatePicker`)
38
+
39
+ Both mirrored their controlled prop into `useState` and re-synced from a `useEffect` keyed on that prop — the pattern §9 forbids ("derive per render, never store in state"). The effect only fires when the prop _changes_, so a parent that **rejects or normalizes** a change left the component holding a value the parent never accepted:
40
+
41
+ - **`Pill`** — in a single-choice group, re-clicking the option already in force calls `onSelect(false)`, the parent keeps `selected={true}`, the prop is unchanged, the effect never runs, and the chip reports `aria-pressed="false"` while still being the selected option. A control lying about the state it controls. Consumers were working around this by remounting the pill with a `key` to force the truth back — that hack can now be deleted.
42
+ - **`DatePicker`** — same desync, plus its sync effect was unconditional, so on mount it ran with `value === undefined` and wiped the `useState(value || defaultValue)` seed. **`defaultValue` — a documented public prop — never survived the first paint.** It works now.
43
+
44
+ Both components now derive `isControlled` per render and write internal state only when uncontrolled.
45
+
46
+ **`Pill` gains `defaultSelected`** to complete the conventional pair, so an uncontrolled row of filter chips can start with some applied. Passing both `selected` and `defaultSelected` dev-warns; `selected` wins.
47
+
48
+ ⚠️ **Behavior change:** `<Pill selected={true}>` passed once as a _one-time initial value_ no longer toggles — that now means "controlled, permanently selected", which is what the prop always claimed. Use `defaultSelected` for the initial-value case.
49
+
50
+ `DatePicker`'s internal context no longer exposes `setSelectedValue` (it was unused and could not be made controlled-safe), and its single-mode dedupe now compares dates **by time rather than identity** — every calendar click builds a new `Date`, so the old `newDate !== selectedValue` guard was always true and never fired.
51
+
52
+ ### Handlers are composed, not clobbered (`RichTextEditor`, `Mention`)
53
+
54
+ `{...rest}` spread before an element's own attributes means every later attribute silently wins. The prop stays in the public type, so TypeScript accepts it and it does nothing at runtime — invisible on both sides. `RichTextEditor` dropped `onInput`, `onPaste`, `onDrop`, `onKeyUp`, `onMouseUp` and `onBlur`; `Mention` dropped seven of its nine.
55
+
56
+ Spreading last isn't the fix either: a consumer's `onKeyUp` would then _replace_ `RichTextEditor`'s selection tracking and silently disable the toolbar's active state. Internal handlers are load-bearing machinery, not defaults. Both components now compose via a new internal `composeEventHandlers(ours, theirs)`:
57
+
58
+ - **Ours runs first**, so consumer code observes settled internal state.
59
+ - **Both always run.** A `preventDefault()` in either does not skip the other — a consumer that cares reads `event.defaultPrevented`. Conditionally dropping the second handler would reintroduce the same invisible-drop bug in a new costume.
60
+
61
+ `Pill`'s `onKeyDown` had the _mirrored_ version of this bug — `{...restProps}` spreads last there, so a consumer's handler replaced Pill's own and silently killed Enter/Space selection. Now composed.
62
+
63
+ `ScrollArea` and `Signature` were audited and are correct as-is (ScrollArea already calls through to `props.onWheel`; Signature's spread targets the wrapper while the drawing handlers sit on the canvas, so consumer handlers still fire via bubbling). Both gained tests pinning that contract.
64
+
65
+ CLAUDE.md §9 documents the merge rules for `className`, `style`, `id` and handlers.
66
+
67
+ - f4ac111: **Icons inside form controls are now themeable and size-aware — `--form-icon-size-sm/md/lg`.**
68
+
69
+ A field had its box tokenised (`--form-min-h-*`, `--form-px/py-*`, `--form-radius`) and its type tokenised (`--form-text-size-*`), but every icon inside it was a hardcoded `size-4` / `size-5` / `h-4 w-4` at the call site. Icon size was the one axis of a control's geometry a theme could not reach: scale a preset up and the chevron, clock and search glyph stayed exactly where they were.
70
+
71
+ `--form-icon-size-sm/md/lg` (12 / 14 / 16px) closes that, consumed as `size-[var(--form-icon-size-…)]` through `formsBaseTheme.iconStyles.sizes`. The ramp is 12/14/16, matching the control type ramp. (It shipped as the gentler of the two, against a type ramp of 14/14/16, on the argument that an icon carries more optical mass per px than a text glyph; the `sm` type rung dropped to 12px in the same release and the two converged.)
72
+
73
+ **Slot geometry ramps with it**, derived from the control's own padding rather than flat literals:
74
+
75
+ | | leading slot | trailing slot |
76
+ | ----------- | -------------------------------- | ----------------------------- |
77
+ | inset | `--form-px-*` exactly (12/14/16) | `--form-px-* − 4px` (8/10/12) |
78
+ | text runway | `+ 20/22/24` (32/36/40) | `+ 16/18/20` (28/32/36) |
79
+
80
+ The two differ on purpose: a leading icon stands where the text would have started, so matching `--form-px-*` keeps a field's left edge constant with or without one, while a trailing affordance is chrome — a chevron is mostly whitespace and reads further from the edge than a glyph at the same metric distance. New keys on `formsBaseTheme.iconStyles`: `leftSizes`, `rightSizes`, `paddingLeftSizes`, `paddingRightSizes`, `sizes`. The size-blind `left` / `right` / `padding` remain as the fallback.
81
+
82
+ **Adopted by** `Input` (built-in type glyphs, number-stepper glyphs, both slots), `Select`, `Combobox`, `DatePicker`, `DateRangePicker`, `TimePicker`. Each component's theme gained optional per-size keys (`iconStartStyles`, `iconEndStyles`, `iconSizes`, `iconPaddings` / `iconEndPaddings`) and falls back to its flat keys, so an existing theme override keeps applying.
83
+
84
+ ⚠️ **Visible change.** `Input`'s built-in lock and search glyphs were `size-5` (20px) at every size and are now 14px at `md`; the picker/clock/calendar carets were `h-4 w-4` and now ramp. **Consumer-supplied slot icons are untouched** — passing `className` on a slot icon replaces the auto size (§7), so only our own built-ins moved.
85
+
86
+ **Not adopted by** listbox check marks or panel chrome (ColorPicker internals, FileUpload dropzone): a dropdown row's type doesn't track its trigger's size, so its icons shouldn't either.
87
+
88
+ ### Breaking: `--button-icon-size-*` → `--button-icon-square-*`
89
+
90
+ The icon-only Button's **box** (aliased to `--form-min-h-*`) was called `--button-icon-size-*`. Shipping a glyph token called `--form-icon-size-*` alongside it would have put two tokens named "icon size" meaning different things into the system, so the box token was renamed to say what it is. Update any theme overriding it.
91
+
92
+ The Figma exporter's variable NAME is deliberately unchanged (`size/button/icon-size-*`): it matches existing kit variables by name, so emitting a new name would create a second variable and strand every component bound to the first. Renaming it in the kit is a deliberate pass, not a side effect. `--form-icon-size-*` exports as `size/form/icon-*`.
93
+
94
+ - 967f18b: **`InputCounter` gains `size` and manual entry; `InputAddress` and `InputCreditCard` gain `size`.**
95
+
96
+ ### `size` — the counter can finally line up with the fields around it
97
+
98
+ `InputCounter` was the only stepper-style control with no `size` prop, and its geometry was hardcoded three ways: an implicit height from `p-1`, `<Button size="sm">` on both steppers, and a `text-sm` number. Nothing about it responded to a size, so a counter sitting under a `Select` in an inspector column was visibly taller and chunkier than the fields above it, with no way to fix it from the outside.
99
+
100
+ It now takes `size?: 'sm' | 'md' | 'lg'` (default `'md'`), drawn from the shared `formsBaseTheme.sizes` scale — the same `--form-min-h-*` / `--form-text-size-*` tokens `Input`, `Select` and `Combobox` use, which is what keeps a row of fields on one baseline. A `buttonSizes` map in the theme picks the stepper Button size per counter size, so it is tunable in one place.
101
+
102
+ ⚠️ **Visual change at the default.** Today's hardcoded look is roughly `md` height with `sm` steppers and `sm` text; at `size="md"` the number and steppers now grow to match a real `md` field. That is the point of the change, but existing counters will render slightly larger.
103
+
104
+ `InputAddress` and `InputCreditCard` take the same `size`, forwarded to every field they render (a per-field `*InputProps.size` still wins). `InputDate` already inherited and forwarded it via `InputProps`.
105
+
106
+ ### The steppers are now derived from the counter, and the edge is the shared one
107
+
108
+ Two geometry bugs fell out of the size work, both visible at `sm`:
109
+
110
+ - **The steppers were mapped onto `Button`'s own size scale**, which has only three rungs — so `sm` and `md` both got the same 32px square, and at `sm` that square was **taller than the counter's content box**: the ghost hover fill punched straight through the rounded border, and its 6px radius didn't match the container's 8px. They are now squares derived from the counter's own height (`--form-min-h-*` minus the container's 4px inset → 24 / 28 / 32) with a concentric radius (`--form-radius` − 4px), so they scale with the control instead of landing on whichever `Button` size is closest. Stepper icons track the type ramp too (16 / 16 / 20px). A `buttonProps.className` still wins.
111
+ - **The container drew a real `border`.** That is a §9 violation on its own — the edge of every form control is the shared inset `box-shadow` — but it was also doing structural damage: a real border consumes 2px of the fixed `--form-min-h-*` height, which is what left the `sm` steppers with only 30px to fit into. It now composes `formsBaseTheme.inputGroupBorderStyle`, so the counter reads identically to the `Select` beside it, picks up the hover edge, gains a **focus ring it never had** (it was the only tabbable form control without one), and takes the shared `states.invalid` instead of a bespoke `ring-1`.
112
+
113
+ `formsBaseTheme` gains `inputGroupBorderStyle` — the field edge alone, without `inputGroupBaseStyle`'s `flex`, fill and `transition-all`. The counter's root is a motion element carrying `layout`, and `transition-all` there covers `transform`, re-interpolating motion's per-frame writes every frame (§12); it lists `transition-[box-shadow,background-color]` instead. `inputGroupBaseStyle` is now literally that constant plus the three extras, so there is still exactly one definition of the edge.
114
+
115
+ The number also drops to normal weight, matching the text in the fields it sits beside, and disabled counters now take the shared `states.disabled` treatment instead of looking identical to an enabled one.
116
+
117
+ **The odometer no longer rolls for values that never travelled.** The sliding animation is feedback for _stepping_ — it shows the value moving by a step. A typed value didn't move through the digits in between, so rolling `4 → 9` past 5, 6, 7 and 8 read as the control second-guessing the entry. Manual entry now lands in place, and so does any return from an edit session: the display is mounted fresh when the field unmounts, and a motion element without `initial` animates on mount, so even **Escape** used to roll the untouched number up from zero. Stepping still animates.
118
+
119
+ ### Manual entry
120
+
121
+ Clicking the number — or pressing Enter while the counter has focus, so it is reachable without a mouse — swaps `SlidingNumber` for a real text field, focused with its contents selected. Typing is free-form: no clamping and no `onChange` while the user is mid-edit, so typing `5` on the way to `50` under `min={10}` is no longer eaten. The value is parsed, rounded to the counter's decimal places, clamped to `min`/`max` and emitted on **Enter or blur**; **Escape** reverts and returns focus to the counter. Empty or unparseable input reverts. Per §9, a typed value equal to the current one emits nothing.
122
+
123
+ **Clicking the number does not resize the control.** The field used to size itself with the `size` attribute — the browser's average character width — which never matched `SlidingNumber`'s fixed per-character em box, so the counter grew 1–3px on every click, animated into a visible wobble by the root's `layout`. Both states now lay into one slot whose width is reserved from the character count in the same `em` metric `SlidingNumber` uses (exported as `SLIDING_NUMBER_CHAR_WIDTH_EM`, internal). A bounded counter reserves room for its widest legal value up front, so stepping 9 → 10 doesn't resize it either.
124
+
125
+ **An out-of-range draft is flagged while you type.** Typing stays free-form and clamping still happens on commit — clamping mid-edit would eat the `5` on the way to `50` under `min={10}` — but a draft that parses outside `min`/`max` now paints the shared invalid edge and sets `aria-invalid` on the field, so `34` under `max={10}` reads as rejected _before_ you blur rather than silently becoming `10`. Unparseable input isn't flagged; it simply reverts.
126
+
127
+ The field is mounted only while editing, so there is exactly one tab stop at any moment and no `tabIndex` juggling. The root keeps `role="spinbutton"` — a spinbutton containing a textbox is the canonical composite pattern — and its arrow/PageUp/PageDown handling is suppressed while editing so those keys move the caret instead of stepping the value.
128
+
129
+ Opt out with `editable={false}`.
130
+
131
+ The field is a bare transparent `<input>` inside the counter's existing surface rather than a nested `<Input>`, which would have meant suppressing that component's border, background, height and padding. CLAUDE.md §2 records this as the one sanctioned exception to the reuse rule, with the test for when it applies.
132
+
133
+ ### Removed: `InputCounter`'s `formatNumber`
134
+
135
+ **Breaking.** The prop was destructured and never used — it never reached the rendered `SlidingNumber`, so passing it did nothing. It has been removed rather than wired up, because its documented default (`(n) => n.toString()`) would render `1` where the display shows `1.00`, making "fixing" it a behavior change rather than a fix.
136
+
137
+ The counter displays a number; render units (`%`, `px`, `$`) as adjacent text, and use `decimalPlaces` (auto-detected from `step`) for precision. It was already excluded from the generative schema, so the curated surface is unchanged.
138
+
139
+ - 07713f5: **Fixed `RichTextEditor` silently discarding a consumer's `style` prop, and `RichTextEditor`/`Mention` discarding a consumer's `id`.**
140
+
141
+ Both components spread `{...rest}` onto their contentEditable surface _before_ setting their own attributes on the same element, so any prop the component also sets was overwritten by the later JSX attribute. `style` and `id` are both part of the public typed surface — the props extend `React.HTMLAttributes<HTMLDivElement>` — so TypeScript accepted them and they silently did nothing. The reported symptom was a consumer routing typography overrides through the forwarded ref because `style` never landed.
142
+
143
+ `style` was the outlier: `Mention` already destructured and merged it (`{ minHeight, ...styleProp }`), and every other component in the library that sets `style` alongside a spread merges the incoming value (Skeleton, AspectRatio, Masonry, AppShell's sidebars, the effects family). `RichTextEditor` is now consistent with them:
144
+
145
+ ```tsx
146
+ style={{ minHeight, maxHeight, ...styleProp }}
147
+ ```
148
+
149
+ A passed `style` therefore wins over the `minHeight`/`maxHeight` props for the keys it sets, and leaves the rest in place.
150
+
151
+ `id` was missing from both. Every other form control resolves `id || generatedId` (Input, Textarea, Select, Switch, Slider, DatePicker, …); these two ignored the prop and kept their `useId()` value, which also left `FormLabel`'s `htmlFor` pointing at an id the consumer could not predict. Both now resolve `idProp ?? autoId`, and every derived id (`-description`, `-listbox`) hangs off the resolved value, so a consumer-supplied id makes the whole ARIA wiring addressable.
152
+
153
+ `className` was already merged correctly on both components and is unchanged.
154
+
155
+ - f4ac111: **`Select`'s chevron scales with the control.**
156
+
157
+ The trailing chevron was hardcoded three ways at every size — a `size-4` glyph, a `pr-3` slot, and a `pr-10` text runway — while the field's own horizontal padding ramps 12 / 14 / 16 (`--form-px-*`). At `sm` that left a 16px icon out-weighing the 14px text beside it, and at every size the icon's distance from the edge was unrelated to the control it sat in.
158
+
159
+ All three now derive from the size:
160
+
161
+ | | sm | md | lg |
162
+ | --------------------- | -------------------------- | ------------------------- | ------------------------- |
163
+ | glyph | 12px | 14px | 16px |
164
+ | inset from right edge | `--form-px-sm − 4px` (8) | `--form-px-md − 4px` (10) | `--form-px-lg − 4px` (12) |
165
+ | text runway | `--form-px-sm + 16px` (28) | `+ 18px` (32) | `+ 20px` (36) |
166
+
167
+ The slot insets one step tighter than the text on purpose: a chevron is mostly whitespace, so at the same metric distance it reads as further from the edge than a glyph does. Both are expressed against `--form-px-*` rather than literals, so retuning a theme's control padding moves the icon with it.
168
+
169
+ The values live in `formsBaseTheme.iconStyles` as `rightSizes` / `paddingRightSizes` / `sizes`, alongside the size-blind `right` / `padding` they replace. **`Select` is the only adopter so far** — `Input`, `Combobox`, `DatePicker`, `DateRangePicker` and `TimePicker` still render trailing icons through the flat keys and are unchanged.
170
+
171
+ `SelectThemeOverrides` gains `iconEndStyles`, `iconEndSizes` and `iconEndPaddings` (all `Partial<Record<SelectSize, string>>`). `iconEndStyle` and `iconPadding.right` still work and are used as the fallback when a size has no entry, so an existing theme override keeps applying.
172
+
173
+ - 8819689: **The `sm` control rung is now a 28px box with 12px type.**
174
+
175
+ `--form-text-size-sm` moves from `--text-sm` (14px) to `--text-xs` (12px) with its line-height partner, and `--form-min-h-sm` from 32px to 28px. Everything that aliases them follows: `--button-text-size-sm`, `--segmented-text-size-sm`, `--button-min-h-sm`, `--segmented-min-h-sm`, `--otp-slot-size-sm`, `--button-icon-square-sm`. So `sm` finally reads as a genuinely denser rung rather than an `md` in a slightly shorter box.
176
+
177
+ `--form-py-sm` drops to 6px alongside. A 28px box around a 12px/16px line has exactly 12px of padding to spend; at 8px the padding claimed 16px of a 12px content box — invisible wherever a fixed `h-*` wins, but wrong for anything sized by padding.
178
+
179
+ This retires the "deliberately two rungs across three sizes" note in `docs/THEMING.md`, which argued that 12px on a 32px-tall action was a legibility liability and that the repeat shouldn't be "fixed" into 12/14/16 without a design pass. That pass is this change: shadcn ships `text-xs` on its `sm` button at `h-8`, and the objection was really about 12px in a control too tall for it — so the box came down to 28px in the same move. The type ramp is now 12/14/16 and matches the control icon ramp exactly.
180
+
181
+ ### `InputCounter` at `sm` and WCAG 2.2 target size
182
+
183
+ A 28px box cannot hold a 24px stepper at any sensible inset, so the `sm` steppers (20×20) are undersized for **SC 2.5.8 Target Size (Minimum), Level AA** and clear it through the spacing exception instead: a 24px circle centred on each target must not intersect the next one's. The neighbouring target is the click-to-edit value, so `sm` widens its gap to 8px, putting those centres **25.2px** apart. `md` (26px) and `lg` (30px) pass on size outright.
184
+
185
+ Two details are load-bearing there, both commented in the source:
186
+
187
+ - The value slot now reserves a **two-character minimum**. A single-character slot pulls the centres back to 21.6px and fails.
188
+ - `sm` uses a 4px inset (20×20 square) where `md`/`lg` use 5px. The uniform 5px inset it briefly had produced a 22×22 square at 32px whose centres sat 22.2px apart — which also failed, on the previous box height.
189
+
190
+ ### Fixed
191
+
192
+ - 91f4939: **Fix: `Textarea` with `unstyled` silently ignored `onValueChange`, making a controlled textarea read-only.**
193
+
194
+ The `unstyled` early return destructured `onValueChange` out of props and then never wired it — only the styled path below it did. So `value` + `onValueChange` + `unstyled` rendered a textarea React considered controlled with no `onChange`: it logged the controlled-without-onChange warning and then discarded every keystroke. Nothing threw, and the field simply refused to accept input. `onChange` worked on both paths, which is why `PromptInput` (the only in-library consumer of `unstyled`) never hit it.
195
+
196
+ Both paths now share one `handleChange`, wired above the branch so a future path cannot drop it again.
197
+
198
+ Two related defects in the same branch, found while fixing it:
199
+
200
+ - **`{...props}` was spread AFTER the internal `onFocus`/`onBlur`**, so a consumer's handlers silently replaced the component's own — §9's clobber rule, in the one place it still applied.
201
+ - **Those internal handlers only fed an `isFocused` state that nothing read.** Rather than compose dead code, the state and both wrappers are removed; consumer `onFocus`/`onBlur` now pass straight through. No visual behaviour changes — the focus ring is CSS `focus-within`, never that state.
202
+
203
+ Regression tests cover all three, and were confirmed to fail against the pre-fix component.
204
+
205
+ ## [1.0.0-beta.32] - 2026-08-14
206
+
207
+ ### Breaking Changes
208
+
209
+ - f60562e: **Badge is now a pure, non-interactive status label.**
210
+
211
+ Two changes, one idea: a badge labels something, it doesn't do something.
212
+
213
+ **1. `variant` now defaults to `"soft"` (was `"filled"`).**
214
+
215
+ A tinted chip is the idiom for a status label; a full-strength fill overstates a label sitting beside real controls. Our own usage already said so — across the library and its stories, `soft` was passed 47 times, `outline` 9, and `filled` 4, so the default was the least-used variant. This matches Radix Themes (`soft`), Chakra (`subtle`), and Polaris. `Button` keeps its `filled` default — an action _should_ be the loudest thing on the row.
216
+
217
+ ```diff
218
+ -<Badge color="primary">Beta</Badge> {/* was a solid chip */}
219
+ +<Badge color="primary" variant="filled">Beta</Badge>
220
+ ```
221
+
222
+ Badges that already pass `variant`, or that override the fill via `className`, are unaffected.
223
+
224
+ **2. Removed `dismissible`, `onDismiss`, and `disabled`; `onClick` is no longer a supported prop.**
225
+
226
+ These duplicated `Tag`, and the click path was broken: it rendered a bare `<span onClick>` with no `role` and no `tabIndex`, so keyboard users could never reach it. Rather than build a `role`/`tabIndex`/Space-key path for a feature with no usages, the surface is gone. No major library ships a dismissible Badge — every one routes removable to a separate component (Polaris/Fluent/Ant/Chakra `Tag`, MUI `Chip`, Mantine `Pill`).
227
+
228
+ Migrate by picking the component that matches the interaction:
229
+
230
+ ```diff
231
+ -<Badge dismissible onDismiss={remove}>React</Badge>
232
+ +<Tag removable onRemove={remove}>React</Tag>
233
+ ```
234
+
235
+ - removable chip → **`Tag`** (also has `disabled`)
236
+ - selectable filter chip → **`Pill`** (`selected` / `onSelect`)
237
+ - anything that performs an action → **`Button`**
238
+
239
+ `onClick` still reaches the DOM through the spread `...rest` props, but it is no longer part of Badge's API — it gets no `role`, no keyboard handling, and no `disabled` gating. Don't rely on it.
240
+
241
+ Theme authors: `BadgeTheme.dismissSizes` and `BadgeTheme.states` are removed, as are the `dismissVariants` / `dismissButtonVariants` / `animationTiming` animation exports.
242
+
243
+ - 75605a2: **Fixed `ContextMenu.SubTrigger` rounding all four corners at `--radius-base`.**
244
+
245
+ Menu rows express their corner radius with `first:rounded-t-[var(--radius-base)]` / `last:rounded-b-[var(--radius-base)]` over a flat `rounded-[4px]`, so only the top and bottom rows of a menu pick up the larger radius. `ContextMenu.Sub` wrapped its children in a `<div>`, and because `ContextMenu.SubBody` portals to `document.body` that wrapper's only DOM child was the sub-trigger — making it both `:first-child` and `:last-child` of its own subtree. Both variants fired, and a mid-list sub-trigger rendered as a fully rounded pill next to flat neighbours.
246
+
247
+ `ContextMenu.Sub` now renders no element of its own, so the sub-trigger is a real sibling of the surrounding `ContextMenu.Item` rows and the `first:`/`last:` rules resolve against the menu. As a side effect the generic `<div>` that sat inside `role="menu"` is gone.
248
+
249
+ Hover behaviour is unchanged but is now coordinated through the submenu context instead of the wrapper's mouse handlers: entering the trigger or the submenu body cancels the pending close, leaving either one schedules it, so the pointer still crosses the gap between them without the submenu closing.
250
+
251
+ `ContextMenu.Sub` no longer accepts `className` — there is no element left to apply it to. Style the trigger via `ContextMenu.SubTrigger`'s `className` and the panel via `ContextMenu.SubBody`'s.
252
+
253
+ **Menu rows now round concentrically with their panel.** The first and last row took `--radius-base` (8px at the default) inside a panel with a 12px radius, a 1px border and 4px of padding — so the row's curve bulged past the panel's. Two new tokens, mirroring `--segmented-padding` / `--segmented-inner-radius`:
254
+
255
+ ```
256
+ --dropdown-padding: 0.25rem;
257
+ --dropdown-inner-radius: calc(var(--dropdown-radius) - var(--dropdown-padding) - 1px);
258
+ ```
259
+
260
+ `--dropdown-padding` drives the panel's own padding (Dropdown's menu, ContextMenu's body and submenu body, previously a literal `p-1`) as well as the inner radius, so a themed `--dropdown-radius` keeps the two concentric. Interior rows are unchanged at a flat 4px.
261
+
262
+ **The form listbox panel (Select, Combobox, InputTag, Mention) gets the same treatment — and stops using a control token for its radius.** It painted `rounded-[var(--form-radius)]`, and `--form-radius` is `var(--radius-base)`: the _uncapped_ control radius on a padded content container, which is the bypass `--surface-radius-cap-*` exists to prevent (on `ember`, `--radius-base: 24px`, it curved a 4px-padded panel by 24px). Three more tokens:
263
+
264
+ ```
265
+ --form-dropdown-radius: var(--dropdown-radius);
266
+ --form-dropdown-padding: var(--dropdown-padding);
267
+ --form-dropdown-inner-radius: calc(var(--form-dropdown-radius) - var(--form-dropdown-padding) - 1px);
268
+ ```
269
+
270
+ Aliases rather than duplicated formulas: the form listbox is the same kind of anchored menu panel (Mention renders `MenuItem` rows in it), and `precision.css` overrides `--dropdown-radius` alone — two parallel formulas would have left form listboxes at a different radius than the menus beside them. Override `--form-dropdown-radius` to split them deliberately.
271
+
272
+ Visible changes at the default theme: the listbox panel goes 8px → 12px; option rows go a flat 8px → 4px interior with 7px on the corners that meet the panel. Mention's rows were the worst case (7px rows in an 8px panel) and are now concentric.
273
+
274
+ - f92b8ba: **One press ripple, shared across every interactive component.**
275
+
276
+ `Button` shipped six ripple effects (`default`, `aurora`, `particle`, `glass`, `neon`, `echo`) behind a `rippleEffect` prop. Five were unused everywhere in the library, and the whole system was Button-only: the exported hook did the bookkeeping while every visible part — the circles, their positioning, their color — lived inline in `Button.tsx`, so nothing else could reuse it.
277
+
278
+ There is now a single ripple, and it drops onto any interactive component.
279
+
280
+ **Breaking — removed from `Button`:**
281
+
282
+ - Props `rippleEffect`, `rippleScale`, `rippleClassName`. `enableRipple` stays.
283
+ - Exports `useRipple`, `getRippleColorClass`, `getRippleAnimation`, and the types `RippleEffectType`, `RippleConfig`, `Ripple`. The ripple is now an internal shared primitive with no public API.
284
+
285
+ ```tsx
286
+ // before
287
+ <Button rippleEffect="neon" rippleScale={12} rippleClassName="bg-white/40">Save</Button>
288
+
289
+ // now — the ripple is simply correct by default
290
+ <Button>Save</Button>
291
+ <Button enableRipple={false}>No ripple</Button>
292
+ ```
293
+
294
+ **New behaviour:**
295
+
296
+ - **It now appears on `NavItem`, `MenuItem`, `Pill` (when selectable), and `SelectableCard`,** not just `Button`.
297
+ - **It fits its host.** The radius is measured from the press point to the farthest corner, so it covers a large `SelectableCard` as correctly as a small button, and the duration scales with that radius. The old fixed 200px circle was tuned for buttons alone.
298
+ - **It inherits `currentColor`** instead of branching on variant/color, so it stays legible on every variant, surface, theme, and color scheme. One visible change: an `outline` + `primary` button's ripple is now neutral rather than brand-tinted, because `outline` sets its text to `--color-text-primary`.
299
+ - **It spawns on press rather than release**, which reads as noticeably more responsive.
300
+ - **It is pointer-only.** Keyboard activation gets the focus ring instead — previously an Enter press spawned a ripple from the top-left corner, because a synthetic click reports `clientX/clientY` of 0.
301
+ - **It only attaches to genuinely interactive hosts** — a `button`, a link, a button-ish role, or a real tab stop. A bare `<div onClick>` gets nothing, so `Card` and `HeroCard` are deliberately excluded rather than advertising a press affordance keyboard users cannot reach.
302
+
303
+ **Fixed along the way:** concurrent ripples could collide on duplicate React keys when two presses landed in the same millisecond (ids came from `Date.now()`), and the cleanup path no longer needs timer bookkeeping at all.
304
+
305
+ - 512965d: **Removed `CardStack` and `Carousel`.**
306
+
307
+ Both were carrying documented defects rather than earning their keep. `Carousel` was one of the three components explicitly carved out of the Storybook overhaul as "very buggy — needs a full re-engineering of its drag/swipe interaction logic", and it was the last remaining docs-site stub with no working preview. `CardStack` shipped a types↔impl mismatch: `CardStack.types.ts` declared a compound `CardStack.Card` plus `swipeable`, `loop`, `stackDepth`, `offsetStep`, `currentIndex`, `defaultIndex` and `onStackEmpty`, none of which the implementation ever read, and its declared defaults disagreed with the real ones (`rotationRange` 3 vs 5, `scaleStep` 0.05 vs 0.06). Rather than rebuild two components with no meaningful usage, they're gone.
308
+
309
+ ```diff
310
+ -import { CardStack, Carousel } from "@flikk/ui";
311
+ ```
312
+
313
+ What's removed:
314
+
315
+ - `CardStack` (`CardStackProps`, `CardStackTheme`, `cardStackTheme`, `cardStackAnimations`) and `Carousel` — including `Carousel.Body`, `Carousel.Item`, `Carousel.Previous`, `Carousel.Next` and their types — from `@flikk/ui` and `@flikk/ui/core`. Core drops from 51 to 49 components; the library total from 131 to 129.
316
+ - Their MCP `registry.json` entries and their pages, examples and nav slugs on the docs site.
317
+
318
+ Neither was in the generative curated set, so `schema.generated.ts`, `generative.schema.json` and `tools.json` are unaffected.
319
+
320
+ No replacement is shipped. For a swipeable stack or a slide-through gallery, hold the component out of the set until a version worth shipping exists.
321
+
322
+ - 1d57803: **Removed `FunnelChart` and `ScatterPlot`.**
323
+
324
+ Both were below the bar the rest of the chart set holds to, and neither had earned its keep: `FunnelChart` still carried inline animation literals outside `charts/utils/animation-utils.ts` (a documented, deferred audit finding), and `ScatterPlot` had accumulated a bespoke prop surface — `pointSize`, its own axis/grid/dot `className` escape hatches, its own oversized X-axis margin — that no other chart shares. Rather than rebuild two components with no meaningful usage, they're gone.
325
+
326
+ ```diff
327
+ -import { FunnelChart, ScatterPlot } from "@flikk/ui/charts";
328
+ ```
329
+
330
+ What's removed:
331
+
332
+ - The `FunnelChart` and `ScatterPlot` components and their types (`FunnelChartProps`, `FunnelChartDataPoint`, `ScatterPlotProps`, `ScatterPlotDataPoint`) from `@flikk/ui/charts`.
333
+ - The `SCATTER_POINT_STAGGER` / `SCATTER_POINT_DURATION` constants from the chart animation module.
334
+ - Both entries from the generative curated set, which drops from 67 to **65 components** — `schema.generated.ts`, `dist/generative.schema.json`, and `dist/tools.json` are regenerated, and the `funnelStages` / `scatterPoints` data shapes are gone. A saved `UISpec` naming either component now fails validation.
335
+ - Their MCP `registry.json` entries and their pages on the docs site.
336
+
337
+ No replacement is shipped. For a conversion funnel, a `HorizontalBarChart` reads the same story; for correlation, there is no drop-in — hold the chart out of the set until a version worth shipping exists.
338
+
339
+ - ad116b4: **Removed the `Water` effect.**
340
+
341
+ `Water` was the last of the six image-filter shader effects — the other five (`ImageDithering`, `PaperTexture`, `FlutedGlass`, `HalftoneDots`, `HalftoneCmyk`) went in the 38 → 23 narrowing of the effects surface. One image filter isn't a category, and it was the only effect in the set that rendered nothing without a consumer-supplied `image`; every other effect is a drop-in background. Rather than keep the orphan — and the image-texture machinery underneath it — alive for a single component, it's gone.
342
+
343
+ ```diff
344
+ -import { Water } from "@flikk/ui/effects";
345
+ ```
346
+
347
+ What's removed:
348
+
349
+ - The `Water` component and its `WaterProps` type from `@flikk/ui/effects`.
350
+ - The image-texture path in the internal shader runtime it was the sole consumer of: `ShaderCanvas`'s `texturesRef` / `onTextureLoad` props, the `ShaderTextureSource` type, and the `getImageCoverUV` GLSL cover-fit helper. `ShaderCanvas` and the GLSL helpers are internal — never exported from `@flikk/ui/effects` — so this is not a public API change, and the remaining uniform-only shader effects are untouched.
351
+ - Its MCP `registry.json` entry and its page on the docs site.
352
+
353
+ No replacement is shipped. For a decorative animated image treatment, layer an existing background effect over the image, or copy the shader out of git history — nothing else in the library reads a texture.
354
+
355
+ - 19d6dfe: **`RichTextEditor` rebuilt as a semantic authoring editor.**
356
+
357
+ The previous implementation was a 2,687-line single file that shipped a typography playground — font family, 13 sizes, 9 weights, letter spacing, line height and 244 colours — but had no headings, lists, links, blockquotes or code. It is now a semantic editor across seven focused modules, with **zero new dependencies**.
358
+
359
+ **Security fixes (both were exploitable):**
360
+
361
+ - **Pasted and dropped HTML was never sanitized.** There was no `onPaste` or `onDrop` handler at all, so foreign markup entered the document via the browser's default handler. Both are now intercepted, sanitized, and re-inserted.
362
+ - **Sanitization was skipped entirely when `onChange` was absent.** `handleContentChange` returned early on `if (!editorRef.current || !onChange)`, so an uncontrolled editor never cleaned anything. Sanitization no longer depends on `onChange`.
363
+
364
+ The built-in sanitizer was a regex that stripped `<script>` and `on*=` and nothing else — it permitted `javascript:` hrefs, `data:` URLs, SVG/MathML payloads and `<style>`. It is now a deny-by-default DOM allowlist that parses into an inert document, validates URL schemes, and runs to a fixed point (markup that will not stabilize under re-parsing is reduced to plain text rather than trusted).
365
+
366
+ **Stored HTML is now portable.** Formatting was serialized as Tailwind utility classes (`font-bold`, `text-rose-500`, `leading-relaxed`) applied at runtime via `classList.add`, so content only rendered correctly inside a Tailwind build that had scanned a 242-entry keepalive sentinel. Output is now plain semantic HTML — `<h2>`, `<strong>`, `<ul>`, `<a href>` — with no classes or inline styles. **Content authored with a previous version will lose its typography formatting.**
367
+
368
+ **Accessibility.** The five hand-rolled `role="listbox"` dropdowns had no keyboard support whatsoever — no arrow keys, no Home/End, no Escape, no active-descendant, no focus management. They are replaced by one `Select` (block style), which already implements all of it.
369
+
370
+ **Breaking changes:**
371
+
372
+ - `className` now targets the **editable surface**, not the outer wrapper — matching `Input`, `Textarea` and `Select` per CLAUDE.md §9. Use `wrapperClassName` for the outer element.
373
+ - `wrapperClassName` and `editorClassName` were previously declared but never applied — they fell through to the DOM as unknown attributes. `wrapperClassName` now works; `editorClassName` is removed (it is `className`). New: `containerClassName`, `toolbarClassName`.
374
+ - Removed props: `fontFamilies`, `loadFontsOnDemand`, `elevation`, `lift`, and `ToolbarConfig.position` (the last three were accepted but ignored).
375
+ - Removed exports: `DEFAULT_FONT_FAMILY_OPTIONS`, `loadGoogleFont`, `colorOptions`, and the types `ColorOption`, `FontFamilyOption`, `RichTextFontSizeKey`, `RichTextFontWeightKey`, `RichTextLineHeightKey`, `RichTextLetterSpacingKey`.
376
+ - `ToolbarTool` is a new semantic set: `blockStyle`, `bold`, `italic`, `underline`, `strikethrough`, `link`, `bulletList`, `orderedList`, `blockquote`, `code`, `clearFormat`, `undo`, `redo`, `separator`. The typography tools no longer exist.
377
+ - New exports: `sanitizeHtml`, `RichTextToolbar`, `DEFAULT_TOOLBAR_TOOLS`, and the types `BlockStyle`, `FormattingState`.
378
+
379
+ **Also fixed:** undo/redo now uses the browser's native stack instead of snapshotting `innerHTML` into a custom history, which discarded live Ranges and moved the caret on every undo. Sanitized HTML is no longer written back into the DOM mid-typing for the same reason.
380
+
381
+ `document.execCommand` is retained for mutation — every alternative is an editor framework, and this library takes no such dependency — but it is confined to a single `editorCommands.ts` module, and its one practical weakness (inconsistent markup across browsers) is neutralized by the sanitizer's normalizer, which rewrites `<b>`→`<strong>`, `<i>`→`<em>` and `<div>`→`<p>` on every read. State detection no longer uses `queryCommandState`, which is unreliable for block-level state; it walks the selection's ancestors instead.
382
+
383
+ - **`Heatmap` props flattened onto the chart-family contract** — `Heatmap` was the only chart that did not extend `StandardChartDisplayProps`; it had re-invented four standard props and wrapped them in nested config objects no other chart uses. It now takes flat props like every other chart: `axis={{ showXAxis, showYAxis }}` → `showXAxis`/`showYAxis`, `axis={{ xAxisFormatter, yAxisFormatter }}` → `xAxisFormatter`/`yAxisFormatter`, `layout={{ gap }}` → `cellGap`, `colorScale={{ minOpacity }}` → `minOpacity`, `tooltip={{ enabled, formatter }}` → `showTooltip`/`tooltipFormatter`, `legend={{ enabled: false }}` → `legend="none"`, `legend={{ labels }}` → `legendLabels`, and `valueRange={[min, max]}` → `minValue`/`maxValue`. `legend` now takes the family's `'top' | 'bottom' | 'none'` union (defaulting to `'bottom'`, since a heatmap's colour ramp is its value key) instead of a config object with an incompatible type. Removed with no replacement: `variant="bordered"` (it was `border + rounded + p-4` — use `className`), `axis={{ xAxisRotation, yAxisRotation }}`, `colorScale={{ steps, colors }}` and `tooltip={{ delay }}` (all three were declared but never read), the barrel export `generateSampleData`, and the local `SemanticColor` type export (import it from the shared `SemanticColor` instead, which also adds `mono`). `enableKeyboardNavigation` is gone from the type: it was inherited, silently ignored, and — because `BaseChartProps` does not extend `HTMLAttributes` — leaked onto the root `<div>` as an unknown DOM attribute, along with `width`, `height`, `title` and `description`. `height` now sizes the chart, and `title`/`description` feed the grid's `aria-label`/`aria-describedby`. `HeatmapLegend` also drops `minValue`/`maxValue`: the ramp renders its two end captions and never the numeric bounds, so both props had always been accepted and ignored — the same trap the flat-API pass removed from `HeatmapProps`. Showing the numbers beside the ramp would be a deliberate feature with its own rendering, not two dormant props.
384
+ - **15 effects removed from `@flikk/ui/effects`** — `ColorPanels`, `FlutedGlass`, `GlassEffect`, `GlassSurface`, `GodRays`, `GridPattern`, `HalftoneCmyk`, `HalftoneDots`, `ImageDithering`, `MagneticElement`, `Metaballs`, `PaperTexture`, `PulsingBorder`, `SmokeRing` and `StaticGradientMesh` are deleted, along with their prop types (`GlassEffectProps`, `BlendMode`, `GridPatternProps`, `GridPatternFadeDirection`, `GlassSurfaceProps`, `GlassChannel`, `GlassMixBlendMode`, and the rest). Part of a deliberate narrowing of the effects surface: the category had grown to 38 components, most of them single-use shader ports. The 23 that remain (`AIOrb`, `Animated`, `Aurora`, `CustomCursor`, `Dithering`, `DotPattern`, `GemSmoke`, `GradientMesh`, `GrainGradient`, `InteractiveCharacters`, `LiquidMetal`, `MeshGradient`, `NoiseOverlay`, `Overlay`, `Particles`, `Preloader`, `ProgressiveBlur`, `ScrollReveal`, `Shimmer`, `StaticRadialGradient`, `StripePattern`, `TiltCard`, `Water`) are unchanged. There is no migration path — these have no replacement; copy the source out of git history if you depend on one. Note `StripePattern` was kept precisely because `ImagePlaceholder` renders it and exposes it through its `pattern` prop.
385
+ - **4 blocks removed** — the story-only reference layouts `ActivityTimelineLayout`, `ErrorLayout`, `InboxLayout` and `MailComposerLayout` are deleted. Blocks were never exported components, so no import breaks; they are gone from Storybook and from the `/blocks` page on the docs site, and `list_blocks` / `get_block` in the MCP server no longer return them.
386
+
387
+ ### Added
388
+
389
+ - 378b7f5: **`AgentRequest` (`@flikk/ui/ai`) — the human-in-the-loop primitive: the questions an agent asks before it acts.**
390
+
391
+ A serializable `questions` array drives three kinds — `approval` (a permission gate), `choice` (pick one, or several with `multiple`) and `input` (free text) — modelled as a **discriminated union** rather than one widened interface, so `options` is required exactly where it means something and `multiple: true` genuinely changes the answer shape: `AgentRequestAnswer["value"]` is `string[]` for a multi-choice question and `string` everywhere else (narrow with `Array.isArray`). Same §9 rule `Select`'s `multiple` follows.
392
+
393
+ It belongs in the **composer slot**, not the transcript — it replaces the input, the user answers, and it unmounts, so a consumer renders `{request ? <AgentRequest … /> : <PromptInput … />}`. Deliberately a sibling of `PromptInput` rather than a mode on it: not every consumer uses `PromptInput`, and an answer state machine has no business inside a text input. Pairs with `AgentStatus` by name — _status_ is the agent telling you something, _request_ is the agent asking you something.
394
+
395
+ **One question is on screen at a time**, and the panel resizes to fit it. The question is the heading — there is no title row, and `title` is the region's accessible name only — with a compact `‹ 2 of 3 ›` nav at the bottom left, sharing the footer line with the question's own actions. The actions row is right-anchored so the primary action is the rightmost thing in the panel (`Skip · Always allow · Deny · Approve`).
396
+
397
+ - **The cursor is independent of the answers, which are keyed by `id`.** The panel opens on the first _unanswered_ question, so a `defaultValue` carrying two of three answers opens on the third. Forward is enabled only once the displayed question is answered, so an open question can never be paged past. Answering an earlier question **replaces that answer in place** and lands back on the first question still open, rather than walking forward through answers already given.
398
+ - **Revisiting is editable.** Arrow back and the recorded answer is pre-selected — radio checked, boxes ticked, text refilled — with an answered chip naming the outcome, which is the only way an `approval` can show what was chosen, since a button cannot hold a selected state. A free-text answer returns to the row it was typed in rather than to an invisible selection.
399
+ - **A user can always get out of a question.** `allowOther` adds a plain `Input` under the options for an answer that isn't on the list; typing turns the primary action into `Send`, and on a multi-choice question `Confirm` folds the typed answer in with the ticked options rather than replacing them, counting toward `minSelected`/`maxSelected` like any other selection. `optional` adds `Skip`, recorded as the first-class `deferred` outcome — and it is available on **every** kind including `approval`, which has no free-text row to fall back on.
400
+ - **Options are the real `Radio` and `Checkbox`**, in a connected list, rather than hand-drawn marks: they own the control geometry, the checked and focus visuals, and the input semantics. Single-choice sits in a `role="radiogroup"` sharing one `name`, so arrow-key navigation works. Multi-choice never auto-advances — the user isn't finished until they say so — and gates its confirm on `minSelected`, disabling further boxes at `maxSelected`.
401
+ - **`maxHeight` is opt-in and unset by default** — the panel grows to fit. Set it for a question long enough to bury the conversation behind the composer (many options, a large `payload`) and the _answering controls_ scroll with fading edges, while the question heading above and the actions row below stay put.
402
+ - **Focus is recovered, not stolen.** Answering unmounts the control that had focus, which would otherwise drop the keyboard back to the top of the page; the panel takes it instead. The arrows keep their own focus when they are what moved the cursor, so a second press still lands on them.
403
+ - The panel is a `Card` pinned to the overlay rung, so it takes its surface, elevation shadow and the `--field-fill` its controls need from the relative-elevation engine instead of hand-painting them.
404
+
405
+ Controlled (`value`/`onValueChange`) and uncontrolled (`defaultValue`) per §9, plus `onAnswer` (once per question) and `onComplete` (when the last one lands). Every built-in string is overridable per §6 — `approveLabel`, `denyLabel`, `alwaysAllowLabel`, `submitLabel`, `confirmLabel`, `skipLabel`, `otherPlaceholder`, `otherSubmitLabel`, `previousLabel`, `nextLabel`, and `progressLabel(current, total)` for the footer count, whose rendered width is measured so a longer label can't collide with the actions beside it. Motion honours `useReducedMotion` throughout.
406
+
407
+ `payload` — the thing being approved, a command or a diff — is `ReactNode` and therefore outside the generative-safe schema; everything else on a question is serializable, so a model can emit a whole sequence as JSON.
408
+
409
+ - a2b7011: **`Select` now accepts `aria-label`.** A `Select` with no visible `label` — a
410
+ toolbar control, a compact filter — had no way to get an accessible name.
411
+ `aria-labelledby` was supported, but it needs an element to point at, and the
412
+ whole point of these cases is that there isn't one.
413
+
414
+ The trap was that it _looked_ supported. TypeScript does not check hyphenated
415
+ JSX attributes against a component's props type, so `<Select aria-label="Block
416
+ style" />` compiled cleanly, and `Select` — which forwards named ARIA props
417
+ rather than spreading rest — dropped it on the floor. The control shipped
418
+ nameless, and nothing anywhere reported a problem.
419
+
420
+ `aria-labelledby` still wins when both are present, per ARIA, so a labelled
421
+ `Select` is unchanged and never emits a second, losing name source.
422
+
423
+ - **Two new radius tokens: `--agent-request-radius` and `--agent-request-well-radius`** — the panel takes `min(calc(var(--radius-base) * 1.5), var(--surface-radius-cap-sm))`, the same compact tier as Dropdown/Popover/Toast, rather than reusing `--card-radius`; Card sits in the `lg` tier because it has 24px of padding to absorb the curve, while this panel has ~14px. The payload well nested inside it takes the tighter `min(var(--radius-base), var(--surface-radius-cap-xs))`, mirroring `--tooltip-radius`, since it has only ~10px of padding. **No change under the default theme** (panel 12px, well 8px either way) — this only bites themes with a large `--radius-base`, where the panel previously rendered a 32px curve at `ember`'s 24px base and crowded its own header row.
424
+ - **`Progress` — segmented (LED-style) rendering via `segments`** — Passing `segments={24}` renders the bar as a row of discrete cells, the first N lit, instead of one continuous fill: a segmented bar gauge / LED meter. Omitting the prop leaves today's continuous bar completely unchanged. Accompanying props: **`max`** (default `100`) measures `value` against an arbitrary range rather than a percentage, so a readout can express `<Progress value={214} max={300} segments={24} />`, and **`segmentFade`** (default `false`) ramps the opacity of the last three lit cells so the meter fades out at its leading edge. Indeterminate (`value` undefined) works in the segmented form too, as a chase animation travelling along the cells; it falls back to a static dim row under `prefers-reduced-motion` or `animated={false}`. `className` still targets the fill colour and now lands on the lit cells; the new theme keys are `segmentTrackStyle`, `segmentStyle`, `segmentFillStyle`, `segmentEmptyStyle`, `segmentSizes` and `segmentChaseStyle`. Each cell carries a `data-progress-segment` attribute with `data-state="filled" | "empty"` for consumer CSS.
425
+ - `max` is honoured by the continuous bar as well, and `aria-valuemax` now reflects it (previously hardcoded to `100`). With the default `max={100}` every ARIA attribute is byte-identical to before.
426
+ - **Generative UI — interaction round trip.** `Form` container in the generative schema: children with a `name` prop register as fields and submitting delivers every value in one `onAction` call (`ActionContext.values`). A child `Button` that declares no `actions` of its own becomes the form's submit button; otherwise `Form` renders one from `submitLabel`. `required: true` on any field blocks submit while it is empty and renders a `role="alert"` summary listing every blocked field (heading overridable per spec via `errorSummaryTitle`) — a form-level summary rather than per-field focus, because seven of the thirteen curated field components expose no focusable element for the engine to reach and two render no error text at all. New `toToolResult()` helper shapes a dispatch into a provider-neutral payload for a model. New optional subpath `@flikk/ui/generative/ai-sdk` exporting `useGenerativeChat()` (optional peers `ai`, `@ai-sdk/react`).
427
+ - `Rating` now declares a `change` action binding in the generative registry.
428
+
429
+ ### Changed
430
+
431
+ - **`KPI`'s mini bar chart is now a `Progress` under the hood** — The ~40 lines of cell markup inlined in `KPI.tsx` were the only implementation of this visual in the library; it moved into `Progress` and KPI renders `<Progress segments segmentFade>` instead. **Visually identical** — the geometry, colours, entrance stagger and leading-edge fade all carry over, and `KPI`'s `barCount` / `color` / `size` / `theme.barEmptyColor` / `theme.barFilledColors` props are unchanged. One behavioural difference: the meter now exposes **`role="progressbar"` with `aria-valuenow`/`aria-valuemax`** instead of `role="img"`. Its accessible name is unchanged (`"Progress: N%"`), but a test or query selecting it by `role="img"` needs updating to `role="progressbar"`.
432
+ - `ActionContext` gained an optional `values` field. Additive — no existing handler breaks.
433
+ - **`validate()` now checks every node's `id`.** An `id` is not only a React key — the engine uses a `Form`'s id as an object key for that form's values — so it now passes the same safety grammar as a field `name` (no `Object.prototype` member, no assignment-target hijack, `/^[A-Za-z_][A-Za-z0-9_-]*$/`). An unsafe id is dropped with a new `invalid-id` warning issue and the node still renders; only streaming-patch stability is affected.
434
+ - **Generative schema — `name` prop description unified across field components.** `Select`, `Slider`, `Switch`, and `Combobox` each declared their own ad hoc `name` doc comment (e.g. "Name for the select component"); all four now carry the same description as every other field component ("Field name. Required for this control's value to be included when a parent Form is submitted. Must match /^[A-Za-z\_][A-Za-z0-9_-]\*$/."), overriding what would otherwise auto-extract from each component's own `.types.ts`. (`InputTag` is unaffected — its props extend `React.HTMLAttributes`, which carries no `name` at all, so its entry was a clean addition, not an override.)
435
+
436
+ ### Fixed
437
+
438
+ - 2f17e9f: **`Alert`: a custom `icon` now inherits the default icon's size and semantic color.**
439
+
440
+ `<Alert icon={…} />` rendered the passed element with no styling at all, while the built-in per-color icon got `w-6 h-6` plus the intent color. Two things fell out of that: Phosphor icons default to `1em`, so a custom icon rendered at the alert root's `text-sm` (14px) inside a 24px box — visibly smaller and misaligned next to every stock alert — and it painted in the ambient text color, so `color="danger"` with a custom icon produced a neutral-colored icon on a danger alert.
441
+
442
+ The custom element now receives the same `w-6 h-6` + semantic color, applied as defaults so a `className` on the element still wins:
443
+
444
+ ```tsx
445
+ // now 24px, in --color-danger, matching every built-in alert icon
446
+ <Alert color="danger" icon={<Fire />} title="Server on fire" />
447
+
448
+ // className still overrides both
449
+ <Alert color="danger" icon={<Fire className="size-5 text-purple-500" />} />
450
+ ```
451
+
452
+ This is a visual change for existing custom-icon alerts: they get larger and pick up the intent color. If you were relying on the old ambient-color, `1em` rendering, pass the size and color explicitly on the element.
453
+
454
+ `color` is unaffected and still drives the default icon glyph and the alert's `role="alert"` / `role="status"` announcement semantics.
455
+
456
+ - 512965d: **Removed `CalendarEvent`.**
457
+
458
+ The event-block component that sat under `Calendar/` was never wired up: it was not exported from `Calendar/index.ts`, so it never reached `@flikk/ui` or `@flikk/ui/core`, and `Calendar` itself never rendered it. It was reachable only through the deep `@flikk/ui/components/core/Calendar/CalendarEvent` path, and it shipped with known defects — `event.color` was ignored (the theme hardcoded `--color-primary`) and its dark-mode fill used an invalid arbitrary value (`dark:bg-[var(--color-primary-900)/80]`, with the alpha inside the brackets) so it never applied.
459
+
460
+ ```diff
461
+ -import { CalendarEvent } from "@flikk/ui/components/core/Calendar/CalendarEvent";
462
+ ```
463
+
464
+ What's removed: the `CalendarEvent` component, its `CalendarEventProps` / `CalendarEventVariant` types, and `calendarEventTheme`. `Calendar` and `CalendarMini` are unchanged.
465
+
466
+ `src/utils/calendarUtils.ts` goes with it — the event-layout helpers (`calculateEventPosition`, `calculateEventHeight`, `groupOverlappingEvents`, `resolveOverlappingEvents`, `generateTimeGrid`, `DEFAULT_GRID_CONFIGS`, the `CalendarEvent` / `GridConfig` / `EventPosition` / `EventLayout` / `TimeSlot` types, and friends) existed solely to position `CalendarEvent` blocks and had no other caller. They were never part of the public surface — `@flikk/ui` exports `cn` and `debounce` from utils and nothing else — so no published import breaks.
467
+
468
+ No replacement is shipped — a scheduling/day-grid view is a larger component than a single event block, and it should be designed as one.
469
+
470
+ - ab75f9e: **RSC fix — five modules shipped without their `"use client"` directive.** `Grid`, `Stack`, `SegmentedContext`, `TableContext` and `DateRangePickerContext` use client-only React APIs (`useMemo`, `createContext`) but reached `dist` unmarked, so importing them from a Next.js App Router **server component** threw at runtime.
471
+
472
+ The cause was in the build-time audit rather than the components: `scripts/audit-use-client.mjs` matched only `name(`, so any call carrying an explicit generic type argument was invisible to it — `useMemo<GridTheme>(…)` and `createContext<TableCtx>(…)` put `<` where the pattern expected `(`. Both patterns now accept `(?:<|\s*\()`, with `<` required to follow the identifier immediately so `useCount < 5` stays a comparison.
473
+
474
+ Found by a new packaged-output smoke test that resolves every `exports` subpath through Node's own resolver and checks the directive survived the bundle — where `tsc` has already stripped the generics, so the plain form reappears and the source-side regex and the build output disagreed.
475
+
3
476
  All notable changes to Flikkui will be documented in this file.
4
477
 
5
478
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
479
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
480
 
8
- ## [Unreleased]
481
+ - **`Heatmap` — hydration, ARIA and animation fixes** — Cell entrance delays came from `Math.random()`, so the server and client rendered different inline styles and no snapshot was reproducible; the entrance is now a deterministic `(row + col)` stagger driven by `animation-utils.ts` constants, and the `@keyframes flicker` block is gone. The two axis-description ids were hardcoded string literals, so any page with more than one heatmap emitted duplicate ids (an `aria-describedby` pointing at an ambiguous target) — they come from `useId()` now. Touch detection read `window` in the render body and now goes through `useMediaQuery('(pointer: coarse)')`. Cells carried `transition-all` while `motion` animated their hover/tap scale, re-interpolating the transform every frame — the transition lists `background-color` only, at `--motion-duration-fast` rather than `slowest`. Hardcoded Tailwind greys (`border-gray-200`, `dark:border-gray-700`, `text-gray-700`) are CSS variables. The default tooltip finally honours `config[key].label`, matching every other chart.
482
+ - **Chart description ids are stable across hydration** — `useChartAccessibility` minted its `aria-describedby` id with `Math.random()`, so the server and client disagreed on every chart using it (AreaChart, BarChart, ComboChart, HorizontalBarChart, LineChart, LollipopChart, StackedBarChart). It uses `useId()` now.
483
+ - **`KPI` bars with `color="mono"` were painted with a text token** — `kpiTheme.barFilledColors.mono` was `bg-[var(--color-text-secondary)]`, one of the sites CLAUDE.md §8 flags under "never paint a fill with a `--color-text-*` token". It is now `bg-[var(--color-mono)]`, the gray role. Note this token is opaque (`var(--color-mono-700)`), so unlike the alpha-derived `--color-text-muted`/`-placeholder` the bars were **not** rendering translucent — the bug was latent (a fill depending on a token defined for glyphs) rather than visible. **Minor visual change:** mono bars now sit one step heavier — `mono-900` in light mode (was `mono-700`) and `mono-200` in dark (was ~`oklch(80%)`). A fixed ramp rung can't replace it, since the fill has to flip between modes and only `--color-mono` does.
9
484
 
10
485
  ## [1.0.0-beta.31] - 2026-07-31
11
486
 
12
487
  ### Breaking Changes
13
488
 
14
- - **Icon library swapped: `@heroicons/react` → `@phosphor-icons/react`** — The required icon peer dependency is now Phosphor (`>=2.1.0`); Heroicons is no longer used anywhere in the library. Migration for consumers: `yarn remove @heroicons/react && yarn add @phosphor-icons/react`. **No component API changes** — every icon slot stays `ReactNode`, so icons you pass in (from any library) keep working; only the *built-in* glyphs (chevrons, dismiss ✕, check marks, status icons, etc.) changed shape from Heroicons to the visually close Phosphor equivalents at `regular` weight (`bold` where the old icons used `strokeWidth≥2`, replacing the old `24/solid` uses — one consistent weight voice). Rationale: the Figma design kit standardised on the Phosphor library, and this aligns code with design 1:1. Bundle note: Phosphor modules carry all six weights (~3–6× per icon, ~10–15 KB gzipped total for the ~25 built-in chrome icons); the generative icon vocabulary (`DEFAULT_ICONS`) keeps its library-agnostic names, so generative specs and `generative.schema.json` are unchanged.
489
+ - **Icon library swapped: `@heroicons/react` → `@phosphor-icons/react`** — The required icon peer dependency is now Phosphor (`>=2.1.0`); Heroicons is no longer used anywhere in the library. Migration for consumers: `yarn remove @heroicons/react && yarn add @phosphor-icons/react`. **No component API changes** — every icon slot stays `ReactNode`, so icons you pass in (from any library) keep working; only the _built-in_ glyphs (chevrons, dismiss ✕, check marks, status icons, etc.) changed shape from Heroicons to the visually close Phosphor equivalents at `regular` weight (`bold` where the old icons used `strokeWidth≥2`, replacing the old `24/solid` uses — one consistent weight voice). Rationale: the Figma design kit standardised on the Phosphor library, and this aligns code with design 1:1. Bundle note: Phosphor modules carry all six weights (~3–6× per icon, ~10–15 KB gzipped total for the ~25 built-in chrome icons); the generative icon vocabulary (`DEFAULT_ICONS`) keeps its library-agnostic names, so generative specs and `generative.schema.json` are unchanged.
15
490
 
16
491
  - **Color role `neutral` renamed to `mono`** — The gray/low-emphasis color role is now `mono` everywhere: the prop value (`color="mono"` on Button, Badge, Alert, Toast, KPI, Metric, Link, Spinner, CustomCursor), the `SemanticColor` / `ButtonColor` unions, and the CSS variables (`--color-mono-50…950`, `--color-mono`, `--color-mono-contrast`). The old name collided with Tailwind's own `neutral` color palette — mapping `neutral` in `tailwind.preset.cjs` shadowed Tailwind's real neutral so consumers couldn't reach it, and it blocked the goal of every role being usable as a shorthand (`bg-mono`, `bg-primary`, …) without stealing a Tailwind palette name. The preset now maps `mono` and no longer maps `neutral`, so `bg-neutral-*` / `bg-slate-*` / `bg-zinc-*` all resolve to Tailwind's real palettes again. There is **no `neutral` alias** at the prop level. Migration: replace `color="neutral"` → `color="mono"` and `var(--color-neutral-*)` → `var(--color-mono-*)`. Note: `--color-neutral-*` still resolves (to Tailwind's own neutral, a near-identical gray in the default theme) but no longer tracks the themed role — use `--color-mono-*` for the role.
17
- - **Charts — ~25 dead props removed from the public API** — `BarChart`, `StackedBarChart` and `AreaChart` each declared `extends StandardVisualProps` with no `Omit`. That interface is the *union* of every chart's visual props, so each chart inherited the others': they type-checked, appeared in the generative schema (an LLM could emit them), and were never read. `BaseChartProps` doesn't extend `HTMLAttributes` and the spread targets are plain `<div>`s, so they couldn't even be forwarded — `<BarChart curved />` reached a div as an unknown attribute. Each chart's props are now narrowed with `Omit` to what it implements, matching what `LineChart`/`ComboChart`/`HorizontalBarChart` already did. Removed:
492
+ - **Charts — ~25 dead props removed from the public API** — `BarChart`, `StackedBarChart` and `AreaChart` each declared `extends StandardVisualProps` with no `Omit`. That interface is the _union_ of every chart's visual props, so each chart inherited the others': they type-checked, appeared in the generative schema (an LLM could emit them), and were never read. `BaseChartProps` doesn't extend `HTMLAttributes` and the spread targets are plain `<div>`s, so they couldn't even be forwarded — `<BarChart curved />` reached a div as an unknown attribute. Each chart's props are now narrowed with `Omit` to what it implements, matching what `LineChart`/`ComboChart`/`HorizontalBarChart` already did. Removed:
18
493
  - **`BarChart`** — `orientation`, `curved`, `curveType`, `showDots`, `fillOpacity`, `showStroke`, `stacked`, `dotRadius`, `cellRadius`, `strokeWidth`. For horizontal bars use the new `HorizontalBarChart` (`orientation` was a second, non-functional way to ask for the same thing).
19
494
  - **`StackedBarChart`** — the same ten, plus `gap` (segment separation is a fixed hairline, not a tunable) and `radius` (its `@deprecated` → `barRadius` mapping was never implemented, so unlike `BarChart`/`AreaChart`/`LineChart` it never worked at all).
20
495
  - **`AreaChart`** — `barRadius`, `cellRadius`, `gap`.
@@ -22,20 +497,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
497
  - **`ScatterPlot`** — `variant`. Scatter points are flat single-colour marks; there is no gradient/minimal treatment to select.
23
498
  - **`cellRadius` is gone library-wide** — it was declared "for heatmap cells" and `Heatmap` never referenced it.
24
499
  - **Axis family** — `color` and `fontSize` are removed from `BaseAxisProps` and `AxisTickProps` (so from `XAxis`, `YAxis`, `ValueXAxis`, `CategoryYAxis`), and `color` from `HorizontalGrid`. Axis lines and labels are themed via CSS variables (`stroke-[var(--color-border)]`); `color` was accepted, defaulted to `"currentColor"`, and ignored, and `fontSize` had no implementation anywhere. Restyle an axis with `className`, or change the token.
25
- - **Charts — `CHART_DEFAULTS` and the exported `*_DEFAULTS` objects lost their dead keys** — each `*_DEFAULTS` spread `...CHART_DEFAULTS`, so every chart published a default for every other chart's props. They now list only what their component reads. `CHART_DEFAULTS` also drops `cellRadius`, `orientation`, and `radius` (nothing consulted the last one — the deprecated `radius` *prop* still works on `BarChart`/`AreaChart`/`LineChart`, falling back to `barRadius`/`dotRadius`). These objects are publicly exported, so reads like `BAR_CHART_DEFAULTS.curved` no longer compile.
26
- - **`AvatarGroup` — `spacing` prop removed** — The group now renders one fixed overlap (the former `"normal"`, `-ml-3`). The three-value scale was a styling axis with an inverted, confusing vocabulary — `"tight"` meant *more* overlap (`-ml-5`) and `"loose"` meant *less* (`-ml-2`), the reverse of how the words read — and it duplicated what `className` already does better. The `AvatarGroupSpacing` type is no longer exported. Migration: drop the prop; to restore a custom overlap, pass it through `className` (e.g. `className="[&>*+*]:-ml-5"`). Internal call sites that used `"loose"`/`"tight"` (`CalendarEvent`, Popover stories) now use the single default, so their avatars sit fractionally differently.
500
+ - **Charts — `CHART_DEFAULTS` and the exported `*_DEFAULTS` objects lost their dead keys** — each `*_DEFAULTS` spread `...CHART_DEFAULTS`, so every chart published a default for every other chart's props. They now list only what their component reads. `CHART_DEFAULTS` also drops `cellRadius`, `orientation`, and `radius` (nothing consulted the last one — the deprecated `radius` _prop_ still works on `BarChart`/`AreaChart`/`LineChart`, falling back to `barRadius`/`dotRadius`). These objects are publicly exported, so reads like `BAR_CHART_DEFAULTS.curved` no longer compile.
501
+ - **`AvatarGroup` — `spacing` prop removed** — The group now renders one fixed overlap (the former `"normal"`, `-ml-3`). The three-value scale was a styling axis with an inverted, confusing vocabulary — `"tight"` meant _more_ overlap (`-ml-5`) and `"loose"` meant _less_ (`-ml-2`), the reverse of how the words read — and it duplicated what `className` already does better. The `AvatarGroupSpacing` type is no longer exported. Migration: drop the prop; to restore a custom overlap, pass it through `className` (e.g. `className="[&>*+*]:-ml-5"`). Internal call sites that used `"loose"`/`"tight"` (`CalendarEvent`, Popover stories) now use the single default, so their avatars sit fractionally differently.
27
502
  - **`Segmented` — default width is now fit-content** — The control previously stretched to fill its parent by default (its `flex` root is block-level, so it spanned full width regardless of the `block` prop, which was effectively a no-op). It now shrinks to fit its segments by default, and `block` genuinely opts into full width — matching the documented `block` contract and the iOS/shadcn segmented-control convention. Migration: add `block` to any `Segmented` that should span its container.
28
503
  - **Control heights and button padding retuned — every form control and Button changes size** — No API change, but the default values move, so consumer layouts shift. Heights (`--form-min-h-sm/md/lg`) go from **32/40/48px → 32/36/40px**, an even 4px step where the old ramp jumped 8px; button horizontal padding (`--button-px-sm/md/lg`) goes from **12/12/20px → 10/12/14px**, a consistent +2 ramp (`sm` and `md` previously collided at 12px and `lg` was an outlier). This lands the ramp on the same scale Untitled UI and shadcn use, with our `lg` (40px) at the industry-standard default height. Blast radius is wider than the token names suggest: `--button-min-h-*` and `--button-icon-size-*` both alias `--form-min-h-*`, so this resizes every form control, every Button, and every icon-only Button. Icon-only `sm` stays 32×32, clear of WCAG 2.2 SC 2.5.8's 24×24 floor. `--form-px-*` is deliberately untouched (12/14/16) — controls get shorter but keep their horizontal padding. To keep the old sizing, override the three height tokens in your own CSS.
29
504
  - **`--radius-base` default is now 8px** (was 12px) — Every derived radius in the library scales from this one token, so the default theme is uniformly less round. Set `--radius-base: 12px` to restore the previous look.
30
- - **Default chart series colors are no longer the semantic status palette** — Multi-series charts previously drew from `primary`/`warning`/`success`/`danger` at two lightness steps, which meant a categorical series could read as a *status* (a red series looking like an error). They now resolve from the new dedicated `--color-chart-1…5` palette. An explicit `config[key].color` is unaffected and still wins, including the bare-token shorthand (`"success"` → `var(--color-success)`). To restore the old look, set the colors explicitly in `config`, or override `--color-chart-1…5`.
31
- - **`--hero-card-radius` retired** — `HeroCard` had its own radius token at 3× base (vs `Card`'s 1.5×) on the belief that it needed a rounder look. It is a *presentation* of Card, not a distinct container, so it now shares `--card-radius`. Per-instance overrides still work via `className`. Migration: if you set `--hero-card-radius`, move that value onto the HeroCard's `className` or override `--card-radius`.
505
+ - **Default chart series colors are no longer the semantic status palette** — Multi-series charts previously drew from `primary`/`warning`/`success`/`danger` at two lightness steps, which meant a categorical series could read as a _status_ (a red series looking like an error). They now resolve from the new dedicated `--color-chart-1…5` palette. An explicit `config[key].color` is unaffected and still wins, including the bare-token shorthand (`"success"` → `var(--color-success)`). To restore the old look, set the colors explicitly in `config`, or override `--color-chart-1…5`.
506
+ - **`--hero-card-radius` retired** — `HeroCard` had its own radius token at 3× base (vs `Card`'s 1.5×) on the belief that it needed a rounder look. It is a _presentation_ of Card, not a distinct container, so it now shares `--card-radius`. Per-instance overrides still work via `className`. Migration: if you set `--hero-card-radius`, move that value onto the HeroCard's `className` or override `--card-radius`.
32
507
 
33
508
  ### Added
34
509
 
35
510
  - **AI — `AgentStatus`, `Reasoning` and `SourceCitation`** (`@flikk/ui/ai`) — `AgentStatus` is the single agent-activity-row primitive and owns the reasoning disclosure through a `mode` prop, so one component covers the three ways a chat surfaces the same row: a live status line (`Thinking… 4s`), a collapsible "show your work", and each step inside it. `mode` and the lifecycle `status` stay **orthogonal** — a reasoning block is itself active or settled. The reasoning body is not always a step list: `steps={[{label,status,duration}]}` renders an `<ol>` of nested rows, while `children` render **raw**, so prose and markdown stay valid HTML. `Reasoning` is a thin preset over `mode="reasoning"` (`Reasoning.Step` aliases a status row). `SourceCitation` renders inline citation chips with preview popovers. Both share one `StatusIndicator` glyph, whose active spinner is a CSS `animate-spin` comet — motion only does enter/exit, since a single-value `rotate` target flattens to a static transform. New public hook **`useElapsedTime`** powers `startedAt`: the duration ticks on its own while active, takes one exact final reading on settle, then freezes, and the clock is only read inside an effect so there is no `Date.now()` in render to hydrate-mismatch.
36
511
  - **`HorizontalBarChart`** (`@flikk/ui/charts`) — Categories run down the left, values extend right. It shares `BarChart`'s data model and none of its axis wiring (the value/category axes are swapped), so it ships as its own component rather than an `orientation` flag. Use it when category names are long or numerous, or when the chart ranks items — a column chart cannot fit "United Kingdom" under its axis. Three new shared axis primitives came with it, because the axis layer had no horizontal support at all: `ValueXAxis` (a value axis rendered horizontally, Heckbert nice ticks), `CategoryYAxis` (a category axis rendered vertically), and `VerticalGrid`. 🔴 Note the axis family splits by what an axis **measures**, not which way it points — `XAxis` is a category axis despite being horizontal.
37
512
  - **`LollipopChart`** (`@flikk/ui/charts`) — Stem-and-dot marks with a staggered entrance, an always-on stem gradient + soft glow, hover/keyboard-focus/pinned-selection states (controlled and uncontrolled `selected`/`defaultSelected`), and a dot↔pill morph that animates a single `rx`-interpolating `motion.rect`. Uses the shared `XAxis` and `X_AXIS_MARGIN` so its baseline aligns with every other chart.
38
- - **`Button` — `iconStart` / `iconEnd`** — Button had no icon prop at all: `children` covers icons via the flex gap, which holds for hand-written code, but **a generative UI spec is JSON** — it can *name* an icon, never pass a React element — so an LLM literally could not put an icon on a button. This is the "one contract, two front doors" pattern §2 already established for collection `items` arrays and Card's flat `title`/`subtitle` slot-props. Button deliberately takes the **icon slots only** and *not* `contentStart`/`contentEnd`: an icon can be named, an arbitrary `ReactNode` cannot, so content slots would buy the generative path nothing. Composite stays the idiom and every existing call site is untouched — pass neither slot and `children` renders exactly as before. `theme.iconSizes` uses `[&>svg]:size-*` so a bare `<SearchIcon />` with no `className` still lands at the right size per button size (14/16/20 for sm/md/lg); a caller's own `className` still wins. The icon wrappers are `aria-hidden`, so the accessible name stays the label alone.
513
+ - **`Button` — `iconStart` / `iconEnd`** — Button had no icon prop at all: `children` covers icons via the flex gap, which holds for hand-written code, but **a generative UI spec is JSON** — it can _name_ an icon, never pass a React element — so an LLM literally could not put an icon on a button. This is the "one contract, two front doors" pattern §2 already established for collection `items` arrays and Card's flat `title`/`subtitle` slot-props. Button deliberately takes the **icon slots only** and _not_ `contentStart`/`contentEnd`: an icon can be named, an arbitrary `ReactNode` cannot, so content slots would buy the generative path nothing. Composite stays the idiom and every existing call site is untouched — pass neither slot and `children` renders exactly as before. `theme.iconSizes` uses `[&>svg]:size-*` so a bare `<SearchIcon />` with no `className` still lands at the right size per button size (14/16/20 for sm/md/lg); a caller's own `className` still wins. The icon wrappers are `aria-hidden`, so the accessible name stays the label alone.
39
514
  - **`Tooltip` — controlled mode** — New `isOpen` / `defaultIsOpen` / `onOpenChange` props, plus a `recalcTrigger` escape hatch for triggers that move without firing a scroll or resize event (a dragged `Slider` thumb, for instance). `aria-describedby` is now merged with any existing value instead of clobbering it.
40
515
  - **`Modal` — `darkMode` prop** — Mirrors `Button`'s: applies the `dark` class to the modal container so a modal can render dark while the page is light.
41
516
  - **`InputAddress` — `showCountrySelector` / `showPostalCode`** — Lets either field be hidden for address formats that don't use them. Because the fields render as one merged perimeter, the corner radii are position-aware: the last visible field carries the bottom corners, and a lone bottom field spans the full row instead of splitting into two columns.
@@ -44,17 +519,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
44
519
  - **`CommandPalette` — `onCommandSelect(id)`** — Fires alongside each command's own `onSelect`, so a single top-level callback can observe selection (this is what lets the generative registry bind commands through the standard action mechanism instead of synthesizing per-item callbacks).
45
520
  - **`Checkbox` / `Radio` — selection ripple** — A shared ripple animation on check, with reduced-motion coverage.
46
521
  - **Preset theme "Ember"** — A third shipped theme: orange primary, zinc neutrals, Plus Jakarta Sans, pill geometry and a 24px base radius. Registered in the Storybook theme switcher alongside Iris and Jade.
47
- - **Four new themeable token systems** — (1) **Per-container padding**: `--card-px/py`, `--modal-px/py`, `--drawer-px/py`, `--metric-content-p-sm/md/lg`, so a theme can make the brand-visible containers roomier or tighter without touching every padded element in the library. (2) **Three-tier radius ceilings**: `--surface-radius-cap-xs/sm/lg` wrap every content-bearing container's derived radius in a `min()`, so a theme chasing pill *controls* at a large `--radius-base` can't round a padded container past what its own padding can absorb (a curve-vs-padding collision that visually eats into header text). Controls stay uncapped — going fully pill there is correct. (3) **Per-role font sizes**: `--card-title-size`, `--modal-title-size`, `--drawer-title-size`, `--page-heading-title-size`, `--section-title-size`, `--metric-value-size-*`, `--kpi-value-size-*` — `--font-scale` can only resize the *whole* UI, so these let a theme resize a single role. Each defaults to a `--text-*` rung, so `--font-scale` still flows through. (4) **`--color-chart-1…5`**: a categorical chart palette decoupled from the semantic status colors. Sixteen components whose radius/padding/title-size had drifted into a bare uncapped `calc()` were wired onto these systems.
522
+ - **Four new themeable token systems** — (1) **Per-container padding**: `--card-px/py`, `--modal-px/py`, `--drawer-px/py`, `--metric-content-p-sm/md/lg`, so a theme can make the brand-visible containers roomier or tighter without touching every padded element in the library. (2) **Three-tier radius ceilings**: `--surface-radius-cap-xs/sm/lg` wrap every content-bearing container's derived radius in a `min()`, so a theme chasing pill _controls_ at a large `--radius-base` can't round a padded container past what its own padding can absorb (a curve-vs-padding collision that visually eats into header text). Controls stay uncapped — going fully pill there is correct. (3) **Per-role font sizes**: `--card-title-size`, `--modal-title-size`, `--drawer-title-size`, `--page-heading-title-size`, `--section-title-size`, `--metric-value-size-*`, `--kpi-value-size-*` — `--font-scale` can only resize the _whole_ UI, so these let a theme resize a single role. Each defaults to a `--text-*` rung, so `--font-scale` still flows through. (4) **`--color-chart-1…5`**: a categorical chart palette decoupled from the semantic status colors. Sixteen components whose radius/padding/title-size had drifted into a bare uncapped `calc()` were wired onto these systems.
48
523
  - **Generative UI — curated set expanded from 32 to 66 components** — Forms were entirely uncurated before this (Input, Select, Combobox, Textarea, Checkbox, Switch, Slider, InputOTP, TimePicker, InputDate, InputCounter, InputTag), joined by 16 more Core/Data Display entries (Spinner, Skeleton, Link, Kbd, DotSeparator, Icon, Pagination, Message, MenuItem, Calendar, GanttChart, AspectRatio, ButtonGroup, Masonry, ScrollArea, HeroCard), `Tree` and `CommandPalette`, and the five remaining charts the manifest had deferred as "bespoke prop systems" (RadarChart, ScatterPlot, FunnelChart, ActivityRings, Heatmap). Notable: `Calendar`'s schema carries ISO strings with a `render:` wrapper coercing string→Date inbound and Date→ISO outbound; `CommandPalette` forces `isOpen: true` (it renders through the shared `Overlay`, so a spec node is always a full-screen modal) and supports only the flat `CommandItem[]` shape.
49
524
  - **`flikkui init` CLI** (separate `flikkui` package, `npx flikkui init`) — Implements the setup command the installation docs have long referenced. Detects framework, package manager and Tailwind; installs `@flikk/ui@beta` plus any missing required peers; injects the `styles.css` (and optional preset theme) import; and wires the Tailwind v4 dark variant. Zero runtime dependencies. Every project ends in exactly one explicit outcome — auto-wired (Next App/Pages Router, Vite), a named-framework manual snippet (CRA, Remix/RR7, Gatsby, Astro), a generic snippet, or a refusal with a stated reason (no `package.json`, React Native/Expo, React < 18). Theme imports are capability-checked against the installed package version. 128 unit tests + fixtures. Tracked in `packages/cli/CHANGELOG.md`.
50
- - **Figma design-kit generator (`yarn build:figma-tokens`)** — Design-ops tooling, deliberately **not** wired into `yarn build`. Parses `src/styles/theme.css` and emits `dist/figma-variables.json`: variables split across `Flikk/Primitives` (single mode) and `Flikk/Semantic` (Light/Dark), plus shadow effect styles derived from the `--elevation-surface-*` ladder. Mode-dependence is **derived, not curated** — a token is Semantic iff `.dark` overrides it *or it transitively references something that is*. Unknown value forms are a **hard failure (exit 1), never a guess**: the evaluator understands exactly four computed families, so adding a new one must break this build. See `docs/figma-component.md`.
525
+ - **Figma design-kit generator (`yarn build:figma-tokens`)** — Design-ops tooling, deliberately **not** wired into `yarn build`. Parses `src/styles/theme.css` and emits `dist/figma-variables.json`: variables split across `Flikk/Primitives` (single mode) and `Flikk/Semantic` (Light/Dark), plus shadow effect styles derived from the `--elevation-surface-*` ladder. Mode-dependence is **derived, not curated** — a token is Semantic iff `.dark` overrides it _or it transitively references something that is_. Unknown value forms are a **hard failure (exit 1), never a guess**: the evaluator understands exactly four computed families, so adding a new one must break this build. See `docs/figma-component.md`.
51
526
 
52
527
  - **`Avatar` / `AvatarGroup` — `size` now accepts an exact pixel number** — `size={30}` sizes the circle to exactly 30px alongside the existing `"xs" | "sm" | "md" | "lg" | "xl"` rungs (`AvatarSize = AvatarSizeToken | number`; the new `AvatarSizeToken` type is exported). The type already admitted `number` but no implementation backed it: the theme's `Record<AvatarSize, string>` lookup returned `undefined` for a number, so `size={30}` type-checked and rendered an avatar with **no size class at all**. This is a fix as much as a feature. Note that `className="size-[30px]"` is not an equivalent workaround — the shipped `styles.css` only contains classes found in this library's own source and `tailwindcss` is an optional peer, so an arbitrary size utility resolves to nothing for consumers who don't run Tailwind themselves. Geometry is exact; decorations (glyph, status dot, verified badge, text gap) snap to the nearest rung (30 → `sm`). Token sizes render byte-for-byte identical to before. Implementation note: the circle now always carries the class `size-[var(--avatar-size)]` with the value supplied inline via the `--avatar-size` custom property, so anything targeting the old per-token `size-[var(--avatar-size-md)]` class in CSS will no longer match.
53
528
  - **Preset themes (`flikkui/themes/*`) + first theme "Iris"** — The library now ships swappable preset theme files: plain CSS-variable override files imported after `styles.css` (`import 'flikkui/themes/iris.css'`) — no config, no JS, no rebuild. The default look is unchanged and needs no import ("Neutral" is baked into `styles.css`). **Iris** restyles the whole library: Tailwind indigo primary ramp, slate-tinted neutrals and borders, Bricolage Grotesque type, and a rounder 16px base radius — with its own dark-mode values (theme files carry explicit `.dark` overrides inside `@layer theme` so dark mode composes correctly). Storybook gets a toolbar theme switcher (paintbrush icon) that live-swaps themes, including combined with dark mode. Custom themes are the same file format — see the Preset themes section in `docs/INSTALLATION.md`. A second preset, **Jade**, reshapes geometry and type as well as color: Tailwind emerald primary, warm stone neutrals (hueless in dark), Poppins on a 14px text scale, and pill-leaning controls (`--radius-base: 20px`, taller form/button heights with wider padding).
54
- - **Chip-tier padding tokens — themeable Badge / Pill / Tag / Kbd spacing** — The four small "chip" controls now read their padding from CSS variables instead of hardcoded Tailwind literals, so a theme can grow (or tighten) them alongside its buttons and inputs — the same reasoning as the per-component container padding and type-role tokens (a global knob can't make *only* badges roomier). New tokens: `--badge-px-sm/md`, `--badge-py-sm/md`, `--badge-pill-px-sm/md`; `--pill-px-sm/md/lg`, `--pill-py-sm/md/lg`; `--tag-px-sm/md`, `--tag-pl-md`, `--tag-py-sm/md`; `--kbd-px-sm/md`. Defaults are byte-for-byte identical to the previous literals, so the default theme is unchanged. Radius needs no new token — `--badge-radius` now derives from `--radius-base` (`calc(var(--radius-base) * 0.75)`, was a hardcoded `6px` that silently ignored the base, so a large `--radius-base` now rounds badges too), Kbd already derived, and Pill/Tag are intrinsic `rounded-full`. The `jade` and `ember` presets set these so their badges/pills/tags match their spacious geometry. Chip **gap** and Kbd **height** stay literal for now.
529
+ - **Chip-tier padding tokens — themeable Badge / Pill / Tag / Kbd spacing** — The four small "chip" controls now read their padding from CSS variables instead of hardcoded Tailwind literals, so a theme can grow (or tighten) them alongside its buttons and inputs — the same reasoning as the per-component container padding and type-role tokens (a global knob can't make _only_ badges roomier). New tokens: `--badge-px-sm/md`, `--badge-py-sm/md`, `--badge-pill-px-sm/md`; `--pill-px-sm/md/lg`, `--pill-py-sm/md/lg`; `--tag-px-sm/md`, `--tag-pl-md`, `--tag-py-sm/md`; `--kbd-px-sm/md`. Defaults are byte-for-byte identical to the previous literals, so the default theme is unchanged. Radius needs no new token — `--badge-radius` now derives from `--radius-base` (`calc(var(--radius-base) * 0.75)`, was a hardcoded `6px` that silently ignored the base, so a large `--radius-base` now rounds badges too), Kbd already derived, and Pill/Tag are intrinsic `rounded-full`. The `jade` and `ember` presets set these so their badges/pills/tags match their spacious geometry. Chip **gap** and Kbd **height** stay literal for now.
55
530
  - **`--font-scale` — one-knob type scale** — The base theme now defines the whole Tailwind `--text-xs…9xl` ramp as `calc(<tailwind default> * var(--font-scale))`. At the default `1` every utility is byte-for-byte Tailwind (`text-sm` = 14px); a theme or consumer sets a single factor (Jade: `0.875` → 14px-based text) and the entire ramp plus line-heights follow. Each step is `round()`ed to a whole pixel so scaled sizes stay clean (13px, not 13.125px). Text-only — spacing and px geometry tokens are unaffected.
56
- - **Control type-size tokens — themeable Button / Input / Segmented label size** — The control tier had every *box* axis tokenised (`--form-min-h-*`, `--form-px/py-*`, `--form-radius`) while its *type* stayed a hardcoded `text-sm` / `text-base` in each `.theme.ts`, so a theme could retune a control's height and padding but never its label. New tokens: `--form-text-size-sm/md/lg` with `--button-text-size-*` and `--segmented-text-size-*` aliasing it (mirroring how `--button-min-h-*` already aliases `--form-min-h-*`), each paired with a `--*-line-height-*` token. Consumed by Button, Segmented, and — via `formsBaseTheme.sizes.*.text` — Input, Select, Textarea, Combobox, DatePicker, DateRangePicker, TimePicker and RichTextEditor. Defaults are byte-for-byte identical to the literals they replaced (sm/md 14px/20px, lg 16px/24px) and each points at a `--text-*` rung, so `--font-scale` still flows through and the default theme is visually unchanged apart from the Segmented fix below. Note the ramp is deliberately **two rungs across three sizes** — sm and md share 14px and only lg steps to 16px; the small sizes are separated by box, not type.
57
- - **`InputOTP` — `size` prop (`sm | md | lg | xl`)** — `InputOTP` was the only text-entry form control without one, so an OTP could not be lined up with an adjacent `sm` or `lg` `Input`. `sm`/`md`/`lg` resolve to the same heights as the equivalent `Input` size; **`xl` is a new OTP-only rung** (48px in the default theme) for standalone verification screens where the code is the focus of the page. Defaults to `lg`. New tokens: `--otp-slot-size-sm/md/lg/xl`, `--otp-text-size-*` and `--otp-line-height-*`. `sm`/`md`/`lg` alias `--form-min-h-*` and `xl` derives from `--form-min-h-lg` (`calc(… * 1.2)`), so all four track a theme that retunes the form ramp — a hardcoded `xl` would have rendered *smaller than `lg`* under the `jade` (36/44/52) and `ember` (40/48/56) presets. The digit type ramp (16/16/18/20px) deliberately runs one rung hotter than form body text: an OTP numeral is a display glyph, not label text. The digit and the caret now scale with the slot; both were previously fixed at 18px and 20px regardless of size.
531
+ - **Control type-size tokens — themeable Button / Input / Segmented label size** — The control tier had every _box_ axis tokenised (`--form-min-h-*`, `--form-px/py-*`, `--form-radius`) while its _type_ stayed a hardcoded `text-sm` / `text-base` in each `.theme.ts`, so a theme could retune a control's height and padding but never its label. New tokens: `--form-text-size-sm/md/lg` with `--button-text-size-*` and `--segmented-text-size-*` aliasing it (mirroring how `--button-min-h-*` already aliases `--form-min-h-*`), each paired with a `--*-line-height-*` token. Consumed by Button, Segmented, and — via `formsBaseTheme.sizes.*.text` — Input, Select, Textarea, Combobox, DatePicker, DateRangePicker, TimePicker and RichTextEditor. Defaults are byte-for-byte identical to the literals they replaced (sm/md 14px/20px, lg 16px/24px) and each points at a `--text-*` rung, so `--font-scale` still flows through and the default theme is visually unchanged apart from the Segmented fix below. Note the ramp is deliberately **two rungs across three sizes** — sm and md share 14px and only lg steps to 16px; the small sizes are separated by box, not type.
532
+ - **`InputOTP` — `size` prop (`sm | md | lg | xl`)** — `InputOTP` was the only text-entry form control without one, so an OTP could not be lined up with an adjacent `sm` or `lg` `Input`. `sm`/`md`/`lg` resolve to the same heights as the equivalent `Input` size; **`xl` is a new OTP-only rung** (48px in the default theme) for standalone verification screens where the code is the focus of the page. Defaults to `lg`. New tokens: `--otp-slot-size-sm/md/lg/xl`, `--otp-text-size-*` and `--otp-line-height-*`. `sm`/`md`/`lg` alias `--form-min-h-*` and `xl` derives from `--form-min-h-lg` (`calc(… * 1.2)`), so all four track a theme that retunes the form ramp — a hardcoded `xl` would have rendered _smaller than `lg`_ under the `jade` (36/44/52) and `ember` (40/48/56) presets. The digit type ramp (16/16/18/20px) deliberately runs one rung hotter than form body text: an OTP numeral is a display glyph, not label text. The digit and the caret now scale with the slot; both were previously fixed at 18px and 20px regardless of size.
58
533
  - **`DotPattern` — `fadeDirection="center"`** — A new inverse-radial fade: dots stay solid toward the edges and fade out in the center (the opposite of `"radial"`), producing a vignette that keeps the content area behind the pattern clear. Tunable via `fadeSize` like the other directions. `DotPatternFadeDirection` is now `"radial" | "center" | "top" | "bottom" | "left" | "right"`.
59
534
 
60
535
  ### Changed
@@ -77,12 +552,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
552
 
78
553
  ### Fixed
79
554
 
80
- - **`PromptInput` — the invalid focus ring now uses the shared `--color-danger` token** — Its invalid state drew the `ring-4` glow from `--color-danger-500` in light mode, while every other form control (via `formsBaseTheme.states.invalid`) uses `--color-danger` (the 600 rung). Dark mode already agreed on both sides (`--color-danger-500` at 20%), so the two disagreed *only* in light — and the inset danger border on the very same line already used `--color-danger`, making the rule internally inconsistent as well. The light ring is now marginally deeper red and consistent with Input/Select/Textarea/OTP. No API change; the `dark:` value is untouched. Regression test added. This also lets the component share the Figma kit's single `Rings/Invalid` effect style instead of needing a one-off.
555
+ - **`PromptInput` — the invalid focus ring now uses the shared `--color-danger` token** — Its invalid state drew the `ring-4` glow from `--color-danger-500` in light mode, while every other form control (via `formsBaseTheme.states.invalid`) uses `--color-danger` (the 600 rung). Dark mode already agreed on both sides (`--color-danger-500` at 20%), so the two disagreed _only_ in light — and the inset danger border on the very same line already used `--color-danger`, making the rule internally inconsistent as well. The light ring is now marginally deeper red and consistent with Input/Select/Textarea/OTP. No API change; the `dark:` value is untouched. Regression test added. This also lets the component share the Figma kit's single `Rings/Invalid` effect style instead of needing a one-off.
81
556
  - **`Segmented` — `size="lg"` now steps its label up to 16px** — Segmented's box tokens are straight aliases of the shared form ramp (`--segmented-min-h-*` → `--form-min-h-*`), but its type was pinned to `text-sm` at **all three** sizes, so `lg` grew its height and padding while the label stayed at 14px — out of step with Button and Input, which both step 14px → 16px at `lg`. `lg` labels now render at 16px/24px like every other large control. **The control's height is unchanged** (still 40px; the taller line-height fits inside the existing `min-h`), so this is a type fix, not a layout shift. `sm` and `md` are untouched. The Figma design kit's `Segmented item` `lg` variants were re-pushed to match. Regression test added.
82
557
  - **Dead props — five public props were accepted and typed but never read** — each silently did nothing when passed. All five are now implemented, with regression tests:
83
558
  - **`ScatterPlot` — `legend`** — inherited from `StandardChartDisplayProps` but never destructured, so the legend was hardcoded to always-render-at-bottom and every value (`"top"`, `"bottom"`, `"none"`) did nothing. It now honours all three, resolved through the same shared `resolveLegendPosition` every other chart uses, satisfying the documented contract that ScatterPlot renders the shared `ChartLegend` from `legend`. **Behavior change:** an omitted `legend` now resolves to `"bottom"` for multi-category data and `"none"` for single-category — so a single-category scatter no longer shows a one-item legend by default. Pass `legend="bottom"` to keep it.
84
559
  - **`Modal` — `darkMode`** — documented as applying the `dark` class to the container, but never destructured, so it fell through `...props` into `Overlay` (which doesn't declare it either) and dead-ended. It now applies `dark` to the modal container, matching the existing `Input`/`BarChart`/`LollipopChart` implementations of the same prop.
85
- - **`Sidebar.Toggle` — `expandIcon` / `collapseIcon`** — destructured but unused; the toggle always rendered its hardcoded chevron. Both are now wired, falling back to the default chevron when omitted. Semantics are **action-based**, matching the button's own aria-label: `expandIcon` shows while the sidebar is *collapsed* (clicking expands), `collapseIcon` while *expanded*. A custom icon renders as-is — the default chevron's 180° flip is only applied to the chevron itself. The JSDoc, which described the opposite (state-based) meaning, was corrected.
560
+ - **`Sidebar.Toggle` — `expandIcon` / `collapseIcon`** — destructured but unused; the toggle always rendered its hardcoded chevron. Both are now wired, falling back to the default chevron when omitted. Semantics are **action-based**, matching the button's own aria-label: `expandIcon` shows while the sidebar is _collapsed_ (clicking expands), `collapseIcon` while _expanded_. A custom icon renders as-is — the default chevron's 180° flip is only applied to the chevron itself. The JSDoc, which described the opposite (state-based) meaning, was corrected.
86
561
  - **`InputAddress` — `showCountrySelector` / `showPostalCode`** — typed but never read (the component has no rest spread, so they dead-ended entirely). Both now hide their field. Because the fields render as one merged perimeter, the radii are position-aware: the last visible field carries the bottom corners, and a lone bottom field spans the full row.
87
562
  - **`ScatterPlot` — `gridClassName`** — destructured but unused, while `svgClassName`/`dotClassName`/`axisClassName` all worked. It now styles the grid lines (plumbed through the shared `YAxis`, which draws ScatterPlot's grid), matching `AreaChart`'s existing `gridClassName`.
88
563
  - **`tailwind.preset.cjs` — `bg-primary` now matches component colors** — Each semantic color's `DEFAULT` was pinned to a fixed rung (`primary-600`, `success-500`, …), so consumer shorthands like `bg-primary` resolved to a different color than the library's own components (which paint the bare `--color-primary` base — `-900` in the monochrome base theme, `-600` in a colored theme). DEFAULTs now point at the bare `var(--color-*)` base, so `bg-primary` equals what components render and follows the active theme and dark mode. Also pruned deprecated `background-quaternary/quinary` maps, added the surface ladder + `border-secondary`/`border-hover`, and corrected the `--form-radius`/`--button-radius` token names. The `@flikk/ui/theme.css` v4 entry's broken `dist` relative import and a stale `--form-rounded` token in `shadcn-compat.css` were fixed too.
@@ -90,11 +565,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
90
565
  - **`Badge` — `iconStart` no longer hugs the edge** — the icon wrapper applied a `-ml-1` optical tuck unconditionally, which cancelled the regular badge's 4px padding and left the icon ~1px from the border. The tuck is now pill-only (pills have the extra `pillSizes` padding to absorb it), mirroring the avatar slot's existing pill/non-pill conditional. Badge width with text-only content is unchanged.
91
566
  - **`PromptInput` — model-menu icon stacked above the label** — The model selector passed its icon inside `Dropdown.Item` children, where MenuItem's block-level `truncate` label span (plus Tailwind preflight's `svg { display: block }`) broke it onto its own line. The icon now goes through the proper `startContent` slot and renders inline with the label.
92
567
  - **`Dropdown` — menu gets a `max-w-[20rem]` cap** — A long item label stretched the menu unboundedly (MenuItem's `truncate` could never engage without a width bound). Long labels now ellipsize at 320px.
93
- - **`Dropdown.Trigger` — no longer overrides a `Button` trigger's styling** — The trigger injected a translucent `surface-hover` tint (on hover, and while the menu is open) onto whatever it wrapped. For a *filled* `Button` — which hovers via a `brightness` filter, not a `hover:bg-*` — that tint had nothing to override and instead replaced the solid fill, so a filled trigger (e.g. the chevron half of a filled split button) went see-through/dark on hover and mismatched its sibling. The tint is now skipped for `Button` children (Buttons are self-styled and bring their own hover/active/variant states); plain/custom trigger elements still get it. Fixes the `ButtonGroup` "Split Button" filled example.
568
+ - **`Dropdown.Trigger` — no longer overrides a `Button` trigger's styling** — The trigger injected a translucent `surface-hover` tint (on hover, and while the menu is open) onto whatever it wrapped. For a _filled_ `Button` — which hovers via a `brightness` filter, not a `hover:bg-*` — that tint had nothing to override and instead replaced the solid fill, so a filled trigger (e.g. the chevron half of a filled split button) went see-through/dark on hover and mismatched its sibling. The tint is now skipped for `Button` children (Buttons are self-styled and bring their own hover/active/variant states); plain/custom trigger elements still get it. Fixes the `ButtonGroup` "Split Button" filled example.
94
569
  - **`Calendar` — consistent selected & hover styling across single/range modes** — Range start/end days had their own fill/text rules and no hover override, so hovering an endpoint fell through to the generic neutral day-hover (a muddy grey with poor contrast) and looked different from a single-selected day. Endpoints now route through `data-selected`, inheriting the exact same fill, text, top-border, and hover as a single-selected cell (one source of truth for "a selected day"). In-range (between) days no longer carry a bespoke hover either — they use the same base day-hover as normal days, so a hovered cell looks identical whether or not it sits inside a range.
95
570
  - **`Table` — jerk at the end of row collapse** — Expanding rows use a height-animating container inside a full-width colspan `<td>`. That cell was picking up ~20px of vertical padding from the table's `[&_td]:py-*` descendant rule (which out-specifies the cell's own `p-0`), so the cell held a ~21px residual height after the animator had already collapsed to 0 — then snapped to 0 when the row unmounted, jerking the rows below. The expanded cell now forces `!p-0` so it tracks the animator down to ~0 (mirrors Accordion, where the animating container carries no padding and the inner content owns it). Padding for expanded content is owned by `expandedRowRender`, as before.
96
571
  - **`Segmented` — dark-mode background no longer collides with consumer utilities** — The theme hardcoded raw Tailwind classes (`bg-neutral-100 dark:bg-neutral-900` on the track, `dark:bg-neutral-800` on the sliding indicator). These emit global class names that a consumer's own `bg-neutral-*` utility could override by source order, permanently pinning the control's background to one tone regardless of the `.dark` class. The track now paints from `--color-surface-sunken` and the indicator from `--color-surface` (theme-aware CSS variables, no `dark:` variants), so it follows the theme correctly and can't collide. Visually unchanged. (PER-292)
97
- - **`Avatar` — status dot stranded outside the circle** — The dot was corner-anchored (`left-0 bottom-0`), but an avatar is a *circle*: its bounding-box corner sits ~41% further from the centre than the edge does, so the dot floated off the arc. It now centres on the circle's 45° point via one proportional rule (`left-[14.6%] bottom-[14.6%]` plus half-translates), which holds at every size including the new arbitrary numeric ones. Three further fixes fell out of the same pass: **(1)** the ring was a flat 2px at every rung, making it proportionally 3.3× heavier on `xs` than `xl` — dot and ring now ramp together, holding the badge's total footprint near 30% of the avatar across the ladder (was 42%→25%); **(2)** the `offline` dot moved off `--color-text-muted` — the text tokens are `color-mix` alpha derivations, so as a *fill* the dot went translucent and the photo showed through; it now uses `--color-mono-500`, the only rung clearing WCAG 1.4.11's 3:1 against its ring in **both** modes; **(3)** `isolate` added to the circle, since `relative` with `z-index: auto` creates no stacking context, so the contents' `z-10` escaped and painted over the sibling status dot.
572
+ - **`Avatar` — status dot stranded outside the circle** — The dot was corner-anchored (`left-0 bottom-0`), but an avatar is a _circle_: its bounding-box corner sits ~41% further from the centre than the edge does, so the dot floated off the arc. It now centres on the circle's 45° point via one proportional rule (`left-[14.6%] bottom-[14.6%]` plus half-translates), which holds at every size including the new arbitrary numeric ones. Three further fixes fell out of the same pass: **(1)** the ring was a flat 2px at every rung, making it proportionally 3.3× heavier on `xs` than `xl` — dot and ring now ramp together, holding the badge's total footprint near 30% of the avatar across the ladder (was 42%→25%); **(2)** the `offline` dot moved off `--color-text-muted` — the text tokens are `color-mix` alpha derivations, so as a _fill_ the dot went translucent and the photo showed through; it now uses `--color-mono-500`, the only rung clearing WCAG 1.4.11's 3:1 against its ring in **both** modes; **(3)** `isolate` added to the circle, since `relative` with `z-index: auto` creates no stacking context, so the contents' `z-10` escaped and painted over the sibling status dot.
98
573
  - **`Select` — `searchPlaceholder` was ignored** — The prop was declared and documented, but the search input hardcoded `"Search..."`, trapping a user-facing string (every built-in visible string must be overridable). It's now threaded through the Select context to the input. The default was inconsistent too — `SelectProps` documented `"Search..."` while the implementation defaulted to `"Search options..."`; both are `"Search..."` now. This is also what `InputAddress`'s new `showCountrySelector` depended on, since it was already passing `searchPlaceholder` to the country Select.
99
574
  - **`Message` — system bubble radius bypassed the surface cap** — It derived from a bare `calc()` on `--radius-base`. It's a tiny, tight-padding chip, so it now takes `--surface-radius-cap-xs`; without the cap, a theme chasing pill controls at a large base rounded the corners past what the bubble's own padding could absorb.
100
575
  - **`Metric` — `label` is now optional** — It was required with no reason to be; a metric can legitimately render a value alone. The generative schema is regenerated to match.
@@ -109,7 +584,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
109
584
  - **`glass` variant removed** — `Alert`, `Toast`, and `Metric` no longer ship a `glass` variant. The `AlertVariant` / `ToastVariant` types (and the `variant` prop on `Alert`, `Toast`, `useToast` options, and `ToastData`) are removed entirely — `Alert` and `Toast` are now single-look components. `MetricVariant` drops `glass` (now `'filled' | 'ghost'`), and `MetricTheme`/`MetricThemeOverrides` drop `iconGlassShadows`. Migration: remove any `variant="glass"` / `variant="default"` props from these components; for a glass look compose `GlassEffect`/`GlassSurface` manually.
110
585
  - **`PageLayout` removed** — The `PageLayout` compound component (`PageLayout.Sidebar` / `.Header` / `.Content`) is removed from the library, barrel exports, docs, and the MCP registry. It was superseded by `AppShell` (beta.27), which covers the same full-viewport layout with region sub-components. Migration: replace `PageLayout` with `AppShell` (`Sidebar` → `AppShell.Sidebar`, `Header` → `AppShell.Header`, `Content` → `AppShell.Main`).
111
586
  - **`Button` — `link` variant removed** — `ButtonVariant` is now `'filled' | 'outline' | 'soft' | 'ghost'`. Use the `Link` component for text-action affordances. `Link` is now **polymorphic by behavior**: pass `href` to render an `<a>`, omit it (with `onClick`) to render a real `<button>` styled as a link, and it gains `color` (shared `SemanticColor`), `type`, `target`, and `rel`. `LinkProps` now extends `HTMLAttributes<HTMLElement>` (was `AnchorHTMLAttributes<HTMLAnchorElement>`).
112
- - **`Button` — `error` state removed** — `ButtonState` is now `'default' | 'disabled' | 'loading'`. Form errors belong on form controls via `state="invalid"` + `errorMessage`, not on the action button. The loading state was also reworked: loading now resolves *before* disabled so a loading button keeps its color instead of desaturating; the spinner uses `border-current` so it inherits the button's text color (the `-contrast` token on filled variants — no hardcoded white, so it stays visible under brand overrides and dark mode); the button emits `aria-busy` while loading and uses `cursor-wait`.
587
+ - **`Button` — `error` state removed** — `ButtonState` is now `'default' | 'disabled' | 'loading'`. Form errors belong on form controls via `state="invalid"` + `errorMessage`, not on the action button. The loading state was also reworked: loading now resolves _before_ disabled so a loading button keeps its color instead of desaturating; the spinner uses `border-current` so it inherits the button's text color (the `-contrast` token on filled variants — no hardcoded white, so it stays visible under brand overrides and dark mode); the button emits `aria-busy` while loading and uses `cursor-wait`.
113
588
  - **`Button` — `color` narrowed to `'neutral' | 'primary' | 'danger'`** — `success`/`warning` removed from `ButtonColor` (they remain on the shared `SemanticColor` set for Badge/Alert/Toast/KPI/etc., which are status indicators, not actions). Migration: replace `color="success"`/`color="warning"` on `Button` with `color="primary"` (or `"danger"` if the action itself is destructive/negative). The generative schema was regenerated accordingly, so `Button` no longer offers `success`/`warning` there either.
114
589
  - **`InputAddress` / `InputCreditCard` — group-led invalid state** — Composite inputs now take a consolidated `state="invalid"` + `errorMessage` on the group (`aria-invalid`/`aria-errormessage` on the `<fieldset>`), painted as one red perimeter overlay around the stitched fields instead of reddening each field; the message renders once with `role="alert"` and self-clears on the next edit. Per-field `helperText` was stripped. `InputAddress` gains top-level `state`/`errorMessage`, and its `countryInputProps` changed type from `Omit<InputProps, 'ref'>` to `Partial<SelectProps<string>>` (the country field is a `Select` — pass `searchable`, `placement`, etc.). `InputCreditCard`'s `error` prop is deprecated in favor of `errorMessage` (kept as an alias for now). Interior 1px seams between fields no longer double up, and a focused field lifts its own ring above its neighbors.
115
590
  - **`StackedBarChart` — default `variant` is now `"gradient"`** — matches `BarChart`/`AreaChart` (premium per-segment gradient + lift shadow + hover glow). Pass `variant="default"` for the previous flat fills.
@@ -121,7 +596,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
121
596
 
122
597
  ### Added
123
598
 
124
- - **Relative-elevation surface engine** — New `SurfaceProvider`, `useSurfaceLevel` / `useSurfaceElevation`, and `surfaceClasses` exported from the theme entry: surfaces read the rung they sit on and paint `substrate + offset` (clamped 1–8), so overlays lift *relative to their host* at runtime — anchored overlays (Dropdown, Popover, Select, Tooltip) lift `+2` over wherever they open; grounded overlays (Modal, Drawer, CommandPalette, Toast) paint a fixed rung. Override is first-class: `elevation` prop (re-bases children too) › `ground`/`lift={false}` › auto lift; `className` always wins for the element's own paint. See `docs/SURFACE_ELEVATION.md`.
599
+ - **Relative-elevation surface engine** — New `SurfaceProvider`, `useSurfaceLevel` / `useSurfaceElevation`, and `surfaceClasses` exported from the theme entry: surfaces read the rung they sit on and paint `substrate + offset` (clamped 1–8), so overlays lift _relative to their host_ at runtime — anchored overlays (Dropdown, Popover, Select, Tooltip) lift `+2` over wherever they open; grounded overlays (Modal, Drawer, CommandPalette, Toast) paint a fixed rung. Override is first-class: `elevation` prop (re-bases children too) › `ground`/`lift={false}` › auto lift; `className` always wins for the element's own paint. See `docs/SURFACE_ELEVATION.md`.
125
600
  - **Effects — 18 in-house WebGL2 shader effects** — Ported from paper-design/shaders (Apache 2.0) and built in-house so every color prop stays CSS-variable/theme-aware: 10 generative backgrounds (`Dithering`, `GradientMesh`, `StaticGradientMesh`, `StaticRadialGradient`, `Metaballs`, `ColorPanels`, `SmokeRing`, `GodRays`, `GrainGradient`, `PulsingBorder`), 2 dual-mode shape effects (`LiquidMetal`, `GemSmoke`), and 6 image filters (`Water`, `ImageDithering`, `PaperTexture`, `FlutedGlass`, `HalftoneDots`, `HalftoneCmyk`). All exported from `@flikk/ui/effects`. Built on shared shader infrastructure (generalized from `AIOrb.Aura`): tab-visibility RAF pause, image-texture loading, and a `resolveShaderColor` that normalizes any CSS color syntax — including `oklch()` and `var(--…)` theme tokens.
126
601
  - **`MeshGradient`** — New effect in `@flikk/ui/effects`: a deterministic seed → marble-gradient tile (faithful SVG port of boring-avatars' marble — base rect + two Gaussian-blurred signature paths, transformed per seed; blur is in viewBox units so it's resolution-independent). Props: `seed`, `colors` (any CSS colors incl. theme vars), `blendMode`, `animated`, `noise` (grain overlay, default on). Also exports `MESH_PALETTES` / `MESH_PALETTE_BY_NAME` / `pickPalette` (curated palettes, `MeshPaletteName`) and `getMeshTextTone(seed)` — the readable text tone for a seed's gradient.
127
602
  - **`Shimmer`** — New effect in `@flikk/ui/effects`: CSS-only shimmer with two variants — `text` (the "AI thinking" text-clip highlight sweep) and `surface` (a highlight band sweeping across a loading placeholder box). Polymorphic `as` prop, `active` toggle (freeze without unmounting), `duration`/band-width controls; respects `prefers-reduced-motion`; tokens themeable with a dark-mode override.
@@ -146,7 +621,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
146
621
  - **Theme polish pass** — Toast, Pill, Badge, Radio, and related themes migrated to the shared surface/interaction tokens (`--color-surface*`, `--color-surface-hover`, `--color-border-hover`, `shadow-real-*`) and off bespoke per-step neutrals and the deprecated `--color-background-secondary` / `secondary` colors. Badge `soft`/`filled` now keep a transparent 1px border (consistent height); semantic fills drive from the mode-aware base var so auto-derived `-contrast` text stays readable. `FunnelChart` reworked visually (gradient fills, soft `Badge` callouts). Chart animation timings consolidated into `charts/utils/animation-utils.ts`.
147
622
  - **Outline borders — full-color, library-wide** — `outline` variant borders are now color-matched per semantic color: `neutral` → `--color-border-secondary` (hueless, full-weight — no longer the faint `--color-border` hairline); `primary`/`success`/`warning`/`danger` → `border-[var(--color-{color}-600)]` (+ `-400` in dark), matching the text. Applies to `Button` (neutral only changed) and `Badge` (all five colors). Visual diff for outline Badges/Buttons.
148
623
  - **Forms — invalid-state visual unified** — Invalid now paints a danger border + faint danger ring with **no surface fill** (previously a danger-50 background tint) — one treatment across single fields, OTP slots, and the new composite-group perimeter overlay.
149
- - **`PulsingBorder` — self-configuring fit (visual behavior change)** — With no sizing props, the corner radius now auto-matches the host element's computed CSS `border-radius` (previously a fixed `roundness` of `0.4`), and the border line now sits fully *outside* the host (inner edge touching) so it never overlaps card content. The wrapper auto-expands past the host by exactly the room the soft band/glow needs, so the halo always fades out instead of cropping at the host edge. Existing usages will render slightly differently by default; pass explicit `roundness` and `margin` to approximate the old inside-the-box look.
624
+ - **`PulsingBorder` — self-configuring fit (visual behavior change)** — With no sizing props, the corner radius now auto-matches the host element's computed CSS `border-radius` (previously a fixed `roundness` of `0.4`), and the border line now sits fully _outside_ the host (inner edge touching) so it never overlaps card content. The wrapper auto-expands past the host by exactly the room the soft band/glow needs, so the halo always fades out instead of cropping at the host edge. Existing usages will render slightly differently by default; pass explicit `roundness` and `margin` to approximate the old inside-the-box look.
150
625
  - **Circular dependencies eliminated library-wide** — All 34 module cycles across 10 compound-component families (Accordion, Modal, Drawer, Animated, Table, Timeline, GanttChart, DatePicker, DateRangePicker, TimePicker) are gone: contexts extracted to dedicated `[Name]Context.ts` modules with the root files re-exporting the moved names, so public import paths are unchanged. Consumers' bundlers no longer emit circular-dependency warnings for `@flikk/ui`.
151
626
  - **`NodeCanvas` — render-performance contract documented** — The split-context memoization (per-frame state vs. stable API contexts, memoized node/edge renderers: a graph update re-renders only the changed node; pan/zoom re-renders none) is now documented in `docs/NODE_CANVAS.md` — keep your `nodeTypes` map referentially stable or the memoization is defeated. The `NodeCanvasEditorLayout` reference story was reworked to demonstrate it.
152
627
  - **`Segmented` — `data-state` attribute** — Items now emit `data-state="active|inactive"` alongside the existing `data-selected`, matching the library-wide `data-[state=*]` styling convention. `data-selected` is kept for one deprecation cycle. (#54)
@@ -161,7 +636,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
161
636
  - **`Combobox` — opening no longer filters by the selected label** — With a value selected, opening the listbox previously narrowed it to the single already-selected match (the selected label doubles as the input text). The list now stays full on open and only filters once the user actually types, matching `Select` and the WAI-ARIA editable-combobox pattern.
162
637
  - **`Checkbox` / `Radio` — rest props reach the `<input>`** — `aria-label` and other rest props were spread onto the wrapper `<div>`, leaving the actual control unlabeled (this surfaced in `Table`'s row-select checkboxes). Rest props now land on the `<input>`; `className` stays on the wrapper. (#51)
163
638
  - **`Toast` — per-toast live regions** — Only the container had `aria-live`; each toast now carries `role="alert"` (danger/warning) or `role="status"` (info/success) so screen readers announce exactly once, with per-toast urgency. (#51)
164
- - **`Toast` — timer resume** — Hovering or expanding a toast now resumes the *remaining* auto-dismiss time instead of restarting the full countdown on every hover/expand toggle.
639
+ - **`Toast` — timer resume** — Hovering or expanding a toast now resumes the _remaining_ auto-dismiss time instead of restarting the full countdown on every hover/expand toggle.
165
640
  - **A11y — labels on roleless elements** — `Avatar` now actually renders its computed `role` (fallback-initials avatars and consumer `role="img"` are honored); `SlidingNumber` gets `role="img"`; `Message`'s status indicator moves its label to an sr-only text node with the glyph `aria-hidden`. (#51)
166
641
  - **`InputTag` — paste and reopen bugs** — Pasting comma-separated values committed only the last tag (stale-closure loop); all values now commit in one update, respecting `maxTags` and dedup. The suggestion dropdown also no longer reopens immediately after a click selection. (#52)
167
642
  - **`DateRangePicker` — controlled mode drift** — Internal state no longer updates when a `value` prop is provided; `onChange` always fires. (#52)
@@ -224,7 +699,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
224
699
 
225
700
  ### Changed
226
701
 
227
- - **Theme — elevation polish pass** — Re-tuned the surface/elevation ladder to read *lighter in both modes* (monotonic + symmetric: higher = lighter), with real dark-mode-aware `shadow-real-*` / `shadow-inner-real-*` shadows (`--elevation-*` tokens; `.dark` swaps to a stronger depth-pool + a 1px white top rim). Refined the `AppShell` color model — only `root` paints the floor; chrome inherits; side-by-side panels are opt-in; horizontal sticky bars use a translucent frosted-floor tint. (PER-163)
702
+ - **Theme — elevation polish pass** — Re-tuned the surface/elevation ladder to read _lighter in both modes_ (monotonic + symmetric: higher = lighter), with real dark-mode-aware `shadow-real-*` / `shadow-inner-real-*` shadows (`--elevation-*` tokens; `.dark` swaps to a stronger depth-pool + a 1px white top rim). Refined the `AppShell` color model — only `root` paints the floor; chrome inherits; side-by-side panels are opt-in; horizontal sticky bars use a translucent frosted-floor tint. (PER-163)
228
703
  - **`ProgressiveBlur` — perf + a11y** — Dropped redundant per-layer `z-index` (the `backdrop-filter` already establishes a stacking context), `aria-hidden` on decorative instances, memoized layer style objects, clamped/guarded the `layers` prop, deduped position arrays, and dev-warns on `ref` + multi-position. Demo wrappers constrained so the blur no longer smears past the container edge. (PER-164)
229
704
  - **Assets** — `public/bg` backdrops converted from JPG to WebP for smaller payloads. (PER-162)
230
705