@brightspot/ui 5.0.2 → 5.0.3-css-bloat.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/components/button-group/ButtonGroup.d.ts +14 -4
  2. package/dist/components/button-group/ButtonGroup.d.ts.map +1 -1
  3. package/dist/components/button-group/ButtonGroup.js +14 -4
  4. package/dist/components/button-group/ButtonGroup.js.map +1 -1
  5. package/dist/components/circular-progress/CircularProgress.js +2 -2
  6. package/dist/components/dropdown/DropdownItem.d.ts.map +1 -1
  7. package/dist/components/dropdown/DropdownItem.js +3 -4
  8. package/dist/components/dropdown/DropdownItem.js.map +1 -1
  9. package/dist/components/icon/Icon.d.ts.map +1 -1
  10. package/dist/components/icon/Icon.js +7 -4
  11. package/dist/components/icon/Icon.js.map +1 -1
  12. package/dist/components/linear-progress/LinearProgress.js +1 -1
  13. package/dist/components/tabs/Tab.js +1 -1
  14. package/dist/components/tabs/Tab.js.map +1 -1
  15. package/dist/components/upload/UploadItem.js +1 -1
  16. package/dist/components/upload/UploadItem.js.map +1 -1
  17. package/dist/components/widget/Widget.css +1 -1
  18. package/dist/components/widget/Widget.js +1 -1
  19. package/dist/custom-elements.json +904 -892
  20. package/dist/storybook/assets/{ActionBar.stories-DR8qKNFC.js → ActionBar.stories-DpBBnIiE.js} +1 -1
  21. package/dist/storybook/assets/{ActionItem.stories-n0lxOjIK.js → ActionItem.stories-D-SD3oRh.js} +1 -1
  22. package/dist/storybook/assets/{Avatar.stories-DWNXboUH.js → Avatar.stories-BDy6f75b.js} +1 -1
  23. package/dist/storybook/assets/{AvatarGroup.stories-CJy1A8Mq.js → AvatarGroup.stories-D--M5Pyp.js} +5 -5
  24. package/dist/storybook/assets/{Badge.stories-BkrU56BA.js → Badge.stories-BOAvuQtE.js} +1 -1
  25. package/dist/storybook/assets/{Button-DqChP2U6.js → Button-DskBi-a0.js} +1 -1
  26. package/dist/storybook/assets/{Button.stories-C1SqU1BT.js → Button.stories--V5okepx.js} +1 -1
  27. package/dist/storybook/assets/{ButtonGroup.stories-CF1Mh2ug.js → ButtonGroup.stories-DsuXsM9v.js} +48 -48
  28. package/dist/storybook/assets/{Celebrate.stories-ClqAmUk2.js → Celebrate.stories-CgK_ukcB.js} +7 -7
  29. package/dist/storybook/assets/{Checkbox.stories-hmxev_eW.js → Checkbox.stories-CahQXA2h.js} +1 -1
  30. package/dist/storybook/assets/{CircularProgress.stories-cWJl_EBP.js → CircularProgress.stories-BcZsN6ZV.js} +1 -1
  31. package/dist/storybook/assets/{ClipboardMixin.stories-E6G4v8eu.js → ClipboardMixin.stories-iKXLs_Lx.js} +2 -2
  32. package/dist/storybook/assets/{Color-6BZIO3FS-mGoabL41.js → Color-6BZIO3FS-7ncXlNfS.js} +1 -1
  33. package/dist/storybook/assets/{Colors.stories-CjWwNT2v.js → Colors.stories-CMI52WH8.js} +1 -1
  34. package/dist/storybook/assets/{CombinedEffects.stories-DahZszMM.js → CombinedEffects.stories-pGE5o60x.js} +1 -1
  35. package/dist/storybook/assets/{ComponentStatesMixin-iXss03sN.js → ComponentStatesMixin-sjcdg7YH.js} +1 -1
  36. package/dist/storybook/assets/{ComponentStatesMixin.stories-C1b3zMgf.js → ComponentStatesMixin.stories-Dbxiy4qv.js} +3 -3
  37. package/dist/storybook/assets/{CopyToClipboard.stories-BEFwR9iw.js → CopyToClipboard.stories-BnYR5mPG.js} +1 -1
  38. package/dist/storybook/assets/{Debounce.stories-m7227tf1.js → Debounce.stories-nc7GvHdK.js} +3 -3
  39. package/dist/storybook/assets/{DocsRenderer-LL677BLK-ByZAmSPf.js → DocsRenderer-LL677BLK-K-gJNtRC.js} +3 -3
  40. package/dist/storybook/assets/{Dropdown.stories-CELZm3nV.js → Dropdown.stories-l-P53HBT.js} +3 -3
  41. package/dist/storybook/assets/{EmptyState.stories-DbIlZsvG.js → EmptyState.stories-Bsui95Si.js} +15 -15
  42. package/dist/storybook/assets/{Events.stories-E8SpdK9f.js → Events.stories-DcmUxFWp.js} +1 -1
  43. package/dist/storybook/assets/{Heading.stories-DIHkdyir.js → Heading.stories-lvQ63fpM.js} +1 -1
  44. package/dist/storybook/assets/{HueRipple.stories-TE5VhBNI.js → HueRipple.stories-BQ9jucCA.js} +1 -1
  45. package/dist/storybook/assets/{Icon.stories-V9A8zx3l.js → Icon.stories-DvL4qxSb.js} +7 -7
  46. package/dist/storybook/assets/{IconButton.stories-kyYFPqat.js → IconButton.stories-1W6ODsaX.js} +1 -1
  47. package/dist/storybook/assets/{LinearProgress.stories-DMQkVCfc.js → LinearProgress.stories-DUmR1GTa.js} +1 -1
  48. package/dist/storybook/assets/{Pagination.stories-B4Kikiss.js → Pagination.stories-Du0hsZpT.js} +3 -3
  49. package/dist/storybook/assets/{Popover.stories-DKYzeCVX.js → Popover.stories-DjJv1kBn.js} +18 -18
  50. package/dist/storybook/assets/{ReadyMixin-CfbnRH2m.js → ReadyMixin-BNk5yZSB.js} +1 -1
  51. package/dist/storybook/assets/{RovingTabindexMixin.stories-B1QUtSUN.js → RovingTabindexMixin.stories-DavyM2GK.js} +6 -6
  52. package/dist/storybook/assets/{Rtc.stories-DPk94cW8.js → Rtc.stories-CfLCEC6Z.js} +1 -1
  53. package/dist/storybook/assets/{ScrollShadow.stories-BzsVosZE.js → ScrollShadow.stories-w4P92oTn.js} +1 -1
  54. package/dist/storybook/assets/{Switch.stories-2ixJ66i0.js → Switch.stories-wLB1lmhw.js} +5 -5
  55. package/dist/storybook/assets/{Tab.stories-DjPAaRUe.js → Tab.stories-DBpVHDFv.js} +1 -1
  56. package/dist/storybook/assets/{Tabs.stories-DNGYpwdL.js → Tabs.stories-pOx3oPd9.js} +1 -1
  57. package/dist/storybook/assets/{Throttle.stories-DqiX39Q4.js → Throttle.stories-BbNaSo1Q.js} +5 -5
  58. package/dist/storybook/assets/{Tooltip.stories-RWvjZUq6.js → Tooltip.stories-CYopQvk8.js} +1 -1
  59. package/dist/storybook/assets/{Upload.stories-BPImkf-w.js → Upload.stories-D7YbbCD8.js} +1 -1
  60. package/dist/storybook/assets/{UploadItem.stories-sF91eklH.js → UploadItem.stories-BCd0JZXJ.js} +1 -1
  61. package/dist/storybook/assets/{Welcome.stories-C7T0lxjn.js → Welcome.stories-CmC1Tq-c.js} +1 -1
  62. package/dist/storybook/assets/{Widget.stories-hIm4Mgoy.js → Widget.stories-D6B4UeXS.js} +12 -12
  63. package/dist/storybook/assets/{WithTooltip-65CFNBJE-FBY88pP9.js → WithTooltip-65CFNBJE-DJFeVQni.js} +1 -1
  64. package/dist/storybook/assets/{blocks-DNAwNK2O.js → blocks-B3SR9dnr.js} +5 -5
  65. package/dist/storybook/assets/{formatter-EIJCOSYU--IWOLjx2.js → formatter-EIJCOSYU-ByY8vaiA.js} +1 -1
  66. package/dist/storybook/assets/if-defined-DzbkssR9.js +1 -0
  67. package/dist/storybook/assets/{iframe-apn1Lgr_.js → iframe-CMPv_g6-.js} +5 -5
  68. package/dist/storybook/assets/iframe-PeGbyIdX.css +1 -0
  69. package/dist/storybook/assets/{index-BSiX30xx.js → index-DJyZoz0Z.js} +1 -1
  70. package/dist/storybook/assets/{onFind-BrtZPfAg.js → onFind-B81tRmr7.js} +1 -1
  71. package/dist/storybook/assets/{onFind.stories-CdBIAJjT.js → onFind.stories-u0gQBbXV.js} +25 -25
  72. package/dist/storybook/assets/{onRemove.stories-BhaBwi44.js → onRemove.stories-Bo0PhHGT.js} +3 -3
  73. package/dist/storybook/assets/{onVisible.stories-BOsxDhRj.js → onVisible.stories-Db1s-swH.js} +1 -1
  74. package/dist/storybook/assets/{style-map-CAjfRbcg.js → style-map-968Ye5qo.js} +1 -1
  75. package/dist/storybook/assets/{syntaxhighlighter-ED5Y7EFY-5ISl-7xc.js → syntaxhighlighter-ED5Y7EFY-yvF0gUqy.js} +1 -1
  76. package/dist/storybook/iframe.html +2 -2
  77. package/dist/storybook/project.json +1 -1
  78. package/dist/tailwind-plugin-button-group.js +43 -44
  79. package/dist/tailwind-plugin-button-group.js.map +1 -1
  80. package/dist/tailwind-plugin-button-group.ts +43 -43
  81. package/dist/tailwind-plugin-button.js +22 -2
  82. package/dist/tailwind-plugin-button.js.map +1 -1
  83. package/dist/tailwind-plugin-button.ts +22 -2
  84. package/dist/tailwind-plugin-dropdown.js +6 -2
  85. package/dist/tailwind-plugin-dropdown.js.map +1 -1
  86. package/dist/tailwind-plugin-dropdown.ts +6 -2
  87. package/dist/tailwind-plugin-pagination.js +12 -8
  88. package/dist/tailwind-plugin-pagination.js.map +1 -1
  89. package/dist/tailwind-plugin-pagination.ts +12 -8
  90. package/dist/tailwind-plugin-upload.js +3 -2
  91. package/dist/tailwind-plugin-upload.js.map +1 -1
  92. package/dist/tailwind-plugin-upload.ts +3 -2
  93. package/dist/tailwind.config.d.ts +1 -4
  94. package/dist/tailwind.config.d.ts.map +1 -1
  95. package/dist/tailwind.config.js +74 -6
  96. package/dist/tailwind.config.js.map +1 -1
  97. package/dist/tailwind.config.ts +74 -6
  98. package/docs/adr/0001-monolith-css-delivery.md +20 -0
  99. package/docs/adr/0002-complete-component-preset.md +26 -0
  100. package/docs/adr/0003-plugin-selector-isolation.md +24 -0
  101. package/docs/adr/0004-dynamic-icon-names-route-through-runtime-injectors.md +50 -0
  102. package/docs/adr/0005-rtl-conditional-overlay.md +44 -0
  103. package/docs/adr/0006-editor-has-runtime-perf.md +33 -0
  104. package/docs/components/ButtonGroup.md +15 -12
  105. package/package.json +4 -3
  106. package/src/legacy/tool-ui/src/AIInline.css +1 -1
  107. package/src/legacy/tool-ui/src/Admin.css +2 -2
  108. package/src/legacy/tool-ui/src/Base.css +1 -1
  109. package/src/legacy/tool-ui/src/BulkUpload.css +1 -1
  110. package/src/legacy/tool-ui/src/CIGCluster.css +1 -1
  111. package/src/legacy/tool-ui/src/Card.css +1 -1
  112. package/src/legacy/tool-ui/src/CodeMirror.css +4 -4
  113. package/src/legacy/tool-ui/src/ColorInputSpectrum.css +2 -2
  114. package/src/legacy/tool-ui/src/ComboInput.css +3 -3
  115. package/src/legacy/tool-ui/src/Compat.css +1 -1
  116. package/src/legacy/tool-ui/src/ContentEdit.css +12 -12
  117. package/src/legacy/tool-ui/src/ContentEditDrawer.css +6 -6
  118. package/src/legacy/tool-ui/src/ContentInputGroup.css +5 -10
  119. package/src/legacy/tool-ui/src/ContentReporting.css +2 -2
  120. package/src/legacy/tool-ui/src/ContentSelector.css +5 -5
  121. package/src/legacy/tool-ui/src/ContentSummary.css +1 -3
  122. package/src/legacy/tool-ui/src/ContentTemplatesWidget.css +1 -1
  123. package/src/legacy/tool-ui/src/ContentTools.css +1 -1
  124. package/src/legacy/tool-ui/src/Conversation.css +2 -2
  125. package/src/legacy/tool-ui/src/Conversation.ts +10 -54
  126. package/src/legacy/tool-ui/src/Crosslinker.css +3 -3
  127. package/src/legacy/tool-ui/src/DashboardWidget.css +1 -1
  128. package/src/legacy/tool-ui/src/Dialog.css +4 -2
  129. package/src/legacy/tool-ui/src/Diff.css +1 -1
  130. package/src/legacy/tool-ui/src/Dropdown.css +2 -2
  131. package/src/legacy/tool-ui/src/Enhancement.css +1 -1
  132. package/src/legacy/tool-ui/src/FileInput.css +2 -2
  133. package/src/legacy/tool-ui/src/FormFilter.css +3 -3
  134. package/src/legacy/tool-ui/src/FullscreenView.css +4 -4
  135. package/src/legacy/tool-ui/src/Guide.css +6 -6
  136. package/src/legacy/tool-ui/src/Hierarchy.css +4 -4
  137. package/src/legacy/tool-ui/src/ImageEditor.css +13 -13
  138. package/src/legacy/tool-ui/src/LinkCarousel.css +1 -1
  139. package/src/legacy/tool-ui/src/LiveBlog.css +2 -2
  140. package/src/legacy/tool-ui/src/LocationMap.css +1 -1
  141. package/src/legacy/tool-ui/src/MailPublishing.css +1 -1
  142. package/src/legacy/tool-ui/src/MenuView.css +4 -4
  143. package/src/legacy/tool-ui/src/Message.css +1 -1
  144. package/src/legacy/tool-ui/src/Page.css +35 -16
  145. package/src/legacy/tool-ui/src/Popup.css +8 -3
  146. package/src/legacy/tool-ui/src/ProseMirror.css +1 -1
  147. package/src/legacy/tool-ui/src/ProseMirrorContainer.css +1 -1
  148. package/src/legacy/tool-ui/src/ProseMirrorEnhancementMenu.css +2 -2
  149. package/src/legacy/tool-ui/src/ProseMirrorFindReplace.css +2 -2
  150. package/src/legacy/tool-ui/src/QueryField.css +1 -1
  151. package/src/legacy/tool-ui/src/RepeatableContentInputGroup.css +1 -1
  152. package/src/legacy/tool-ui/src/RepeatableContentSelector.css +1 -1
  153. package/src/legacy/tool-ui/src/RichText.css +1 -1
  154. package/src/legacy/tool-ui/src/SearchFields.css +1 -1
  155. package/src/legacy/tool-ui/src/SearchResult.css +3 -3
  156. package/src/legacy/tool-ui/src/SearchWidget.css +1 -1
  157. package/src/legacy/tool-ui/src/SearchWidgetAdvanced.css +1 -1
  158. package/src/legacy/tool-ui/src/StyleEmbeddedContent.css +3 -3
  159. package/src/legacy/tool-ui/src/TabBar.css +1 -1
  160. package/src/legacy/tool-ui/src/Table.css +1 -1
  161. package/src/legacy/tool-ui/src/Taxonomy.css +4 -4
  162. package/src/legacy/tool-ui/src/ThemeBundleEditor.css +1 -1
  163. package/src/legacy/tool-ui/src/TimedContent.css +2 -2
  164. package/src/legacy/tool-ui/src/ViewWatchers.css +1 -1
  165. package/src/legacy/tool-ui/src/Widget.css +3 -2
  166. package/src/legacy/tool-ui/src/Workflow.css +3 -3
  167. package/src/legacy/tool-ui/src/main/webapp/dist/{7718.a937b0cafa8cf7c039d2.css → 2111.70d9e2ee26ead4bd147b.css} +1 -1
  168. package/src/legacy/tool-ui/src/main/webapp/dist/{6216.2d532054fcb8e634f96b.js → 6216.cf8adc1990ee1111f065.js} +1 -1
  169. package/src/legacy/tool-ui/src/main/webapp/dist/{2111.a937b0cafa8cf7c039d2.css → 7718.70d9e2ee26ead4bd147b.css} +1 -1
  170. package/src/legacy/tool-ui/src/main/webapp/dist/{RTEProseMirror.945bd28778b1a3e937c7.js → RTEProseMirror.e8521581e28e90ef6f30.js} +2 -2
  171. package/src/legacy/tool-ui/src/main/webapp/dist/{v4.dc7d7c1a18ddb6117061.js → v4.ab96a7dd75fa7af9c970.js} +3 -3
  172. package/src/legacy/tool-ui/src/main/webapp/dist/v5.844c5f9e05e351ee0579.css +5 -0
  173. package/src/legacy/tool-ui/src/main/webapp/dist/{v5.7eea426a9a47341f03f8.js → v5.e48c1d8f41b5e088bef0.js} +3 -3
  174. package/src/legacy/tool-ui/src/main/webapp/v4/rte/plugins/table_manager/views/ProseMirror-table.less +1 -28
  175. package/dist/storybook/assets/if-defined-Cxz6E-2i.js +0 -1
  176. package/dist/storybook/assets/iframe-D0roG0J-.css +0 -1
  177. package/src/legacy/tool-ui/src/main/webapp/dist/v5.951c664100c4fb201fd8.css +0 -5
  178. /package/src/legacy/tool-ui/src/main/webapp/dist/{RTEProseMirror.945bd28778b1a3e937c7.js.LICENSE.txt → RTEProseMirror.e8521581e28e90ef6f30.js.LICENSE.txt} +0 -0
  179. /package/src/legacy/tool-ui/src/main/webapp/dist/{v4.dc7d7c1a18ddb6117061.js.LICENSE.txt → v4.ab96a7dd75fa7af9c970.js.LICENSE.txt} +0 -0
  180. /package/src/legacy/tool-ui/src/main/webapp/dist/{v5.7eea426a9a47341f03f8.js.LICENSE.txt → v5.e48c1d8f41b5e088bef0.js.LICENSE.txt} +0 -0
@@ -0,0 +1,50 @@
1
+ ---
2
+ status: accepted
3
+ ---
4
+
5
+ # Dynamic icon names route through runtime injectors, not per-name CSS
6
+
7
+ Every Brightspot path that renders a Lucide icon whose name is **chosen at runtime** (editor selections via `@ToolUi.IconName`, plugin code calling `createNavAreaHeader(..., "chart-pie")`, Material-to-Lucide compat lookups for `data-icon-button-name` elements, ProseMirror toolbar buttons, the `<btu-icon symbol="…">` web component itself) resolves the name in **JavaScript** and applies the SVG via **inline CSS custom properties** on the host element. None of these paths reads the Preset's per-name `.btu-icon-via-mask-NAME` rules. Therefore the Preset's **Safelist** must not force-emit those per-name rules — doing so ships ~1.18 MB of `v5.css` that the browser fetches, parses, and never consults.
8
+
9
+ ## Why
10
+
11
+ The icon Component Plugin (`tailwind-plugin-icon.ts`) generates 1,792 per-name rules — one per Lucide icon — each inlining the entire SVG as a URL-encoded data URL. Until this decision they were force-emitted to v5.css by two redundant safelist patterns (`/btu-icon-via-mask-.+/` and `/^btu-icon(-.*)?$/`). Measured: 2,638 emitted rules (postcss-rtl doubles the directional ones), 1,182,531 bytes, **24.9% of v5.css**.
12
+
13
+ Every actual rendering path bypasses these rules:
14
+
15
+ | Dynamic path | Resolution mechanism | CSS slot the SVG lands in |
16
+ | --- | --- | --- |
17
+ | `<btu-icon symbol="X">` (Nav-rail, content widgets) | `Icon.ts` calls `getIcon(name)`, inlines the SVG as Light-DOM markup with a `<mask>` `<defs>`, sets `style="--mask-url: url(#X)"` | `--mask-url` (highest-priority slot in the base rule's `var()` chain) |
18
+ | `<button data-icon-button-name="X">` (Right-rail / IconButton) | `v5.ts:applyIconProperties` (an `onFind`-registered host injector) maps Material → Lucide, calls `getIcon(name)`, sets `style="--compat-icon-via-mask: url(…)"` | `--compat-icon-via-mask` (third slot in the base rule's `var()` chain) |
19
+ | ProseMirror toolbar (RTE-blacklisted) | `resolveIconCompat.js` fires a `btu-resolve-icon` custom event that re-enters `applyIconProperties` from outside the RTE blacklist | same as above |
20
+ | `@apply btu-icon-via-mask-NAME` in developer CSS | Tailwind's `@apply` resolver copies the rule body from the plugin's internal registry into the consumer's selector at build time | the consumer's own rule (per-name source rule is not required in output) |
21
+ | `<i class="btu-icon-via-mask-NAME">` in scanned source | Tailwind's content scanner emits the per-name source rule | the per-name source rule itself (~6 names total today) |
22
+
23
+ The base rule the runtime injectors target is unchanged:
24
+
25
+ ```css
26
+ .btu-icon-via-mask {
27
+ mask: var(--mask-url, var(--icon-svg, var(--compat-icon-via-mask))) center / contain no-repeat;
28
+ …shared properties…
29
+ }
30
+ ```
31
+
32
+ The base rule survives any safelist edit that excludes only `btu-icon-via-mask-X` (X non-empty). The runtime injectors continue to work because they consume the **base** rule via inline custom properties — they never depended on the per-name rules.
33
+
34
+ The remaining ~1,786 names exist in v5.css purely as safelist artifacts. They are dead bytes.
35
+
36
+ ## Consequences
37
+
38
+ - The Preset's safelist explicitly excludes the per-name via-mask namespace. Concretely, no safelist pattern may match a class of the form `btu-icon-via-mask-X` for any X — enforced by a convention guard in `scripts/check-conventions.mjs` alongside the existing `no-foreign-btu-class` and `safelist-runtime-classes` rules.
39
+ - The Component Plugin remains unchanged. It continues to register all 1,792 per-name rules into Tailwind's internal registry so that **Cross-multiplication** for `@apply btu-icon-via-mask-NAME` in developer CSS keeps working (Tailwind copies the rule body into the consumer regardless of whether the source class survives output purging).
40
+ - The ~6 statically-discoverable raw-class names (`eye`, `eye-closed`, `file-text`, `sparkles`, `upload`, `zap` as of this writing) continue to emit per-name rules because Tailwind's content scanner finds them. New static-class names self-emit on first use; no maintenance step.
41
+ - A new raw-class name added by a downstream consumer in **non-scanned content** (e.g. a hand-built `<i class="btu-icon-via-mask-some-name">` injected at runtime by JS or by a template engine Tailwind doesn't scan) will **not** render its mask, because no rule will match. The supported alternatives are:
42
+ - Use `<btu-icon symbol="some-name">` (preferred — already runtime-resolved).
43
+ - Use the `data-icon` / `data-icon-name` / `data-icon-button-name` pattern (handled by `applyIconProperties`).
44
+ - Add the class to `additional-tailwind-classes.txt` so Tailwind scans it.
45
+ - A byte-budget regression test in `test/css-bloat/` asserts the aggregate size of per-name mask rules in v5.css stays under 20 KB — leaving headroom for the natural growth of statically-discoverable names while catching any reintroduction of force-emission.
46
+ - This ADR is the load-bearing premise for the Lever #1 safelist change in [`../../plans/lever-1-icon-emission-prd.md`](../../plans/lever-1-icon-emission-prd.md). It is reachable through any of the runtime-injector files (`src/legacy/tool-ui/src/v5.ts`, `src/legacy/tool-ui/src/main/webapp/v4/dom/resolveIconCompat.js`, `src/components/icon/Icon.ts`, `src/LucideDynamicLoader.ts`).
47
+
48
+ If a future requirement appears where dynamic icon names *must* render through the raw class form without scanning and without using `data-icon*` attributes, this ADR is the place to revisit. Two follow-up paths are sketched in the Lever #1 PRD's design discussion: (a) plugin refactor splitting shared behavior onto the base class so a library-side runtime scanner can complete the rule from inline style, and (b) per-element `<style>`-block injection. Neither is needed under the current consumer contract; both would be reopened only if the contract changed.
49
+
50
+ See also: [ADR 0001](./0001-monolith-css-delivery.md) (the Monolith is the single CSS delivery vehicle), [ADR 0002](./0002-complete-component-preset.md) (the Preset registers the complete component set; safelist patterns govern which variants force-emit), [ADR 0003](./0003-plugin-selector-isolation.md) (cross-referencing plugin selectors are forbidden).
@@ -0,0 +1,44 @@
1
+ ---
2
+ status: accepted
3
+ ---
4
+
5
+ # RTL ships as a conditional overlay, not doubled into the Monolith
6
+
7
+ Brightspot resolves text direction as a per-user setting server-side (`Localization.isCurrentUserRtl()`), emitted once into the `<html dir>` attribute. The Tool UI **Monolith** (`v5.css`), however, ships **both** directions to **every** user: `postcss-rtl` doubles each directional rule into `[dir="ltr"]` and `[dir="rtl"]` halves (plus bare-`[dir]` scoping), so ~25% of the post-Lever-#1 bundle is direction-prefixed and roughly half of that is dead weight on any given page. We therefore **split direction out of the Monolith**: all users load a direction-neutral base `v5.css`; RTL users *additionally* load a small `[dir="rtl"]`-only overlay `v5-rtl.css`, gated by the same `Localization.isCurrentUserRtl()` call that drives `<html dir>`. This **refines [ADR 0001](./0001-monolith-css-delivery.md)**: the Monolith stays the single styling vehicle, but materializes as a base + conditional overlay pair rather than one doubled bundle.
8
+
9
+ ## How the split is produced
10
+
11
+ The base and overlay are a **clean partition of the same source CSS**, produced by one paren-aware direction-filter run with opposite predicates:
12
+
13
+ | Bundle | Pipeline | Keeps |
14
+ | --- | --- | --- |
15
+ | `v5.css` (base) | postcss **without** postcss-rtlcss, then **strip-`[dir=rtl]`** | every rule except `[dir="rtl"]`-scoped parts; physical properties stay at their LTR-default values, unscoped |
16
+ | `v5-rtl.css` (overlay) | postcss **with** `postcss-rtlcss` `mode: 'override'`, then **keep-only-`[dir=rtl]`** (which also prunes the at-rule wrappers it empties) | only `[dir="rtl"]`-scoped rules — the mirrored overrides, the authored `[dir="rtl"]` rules, and Tailwind `rtl:`-variant output |
17
+
18
+ `base ⊎ overlay` reconstructs correct rendering for both users with **no gap and no overlap**: an LTR user gets the base alone; an RTL user gets the base (LTR-default physical props) plus the overlay's `[dir="rtl"]` overrides, which win on specificity regardless of load order.
19
+
20
+ The two filters are **one module, two predicates**, sharing a top-level comma splitter that respects `(`/`[` nesting — so `.a, [dir=rtl] .b { … }` is partitioned, not mangled.
21
+
22
+ Each stage runs as an **isolated postcss pass** (postcss-rtlcss → filter → cssnano), not one chained pipeline: postcss-rtlcss must fully finish adding `[dir=rtl]` overrides before keep-rtl runs, and keep-rtl must finish before cssnano — otherwise cssnano's empty-discard races keep-rtl's rule removal and the emptied `@media`/`@container` wrappers survive (measured: ~46 KB of husks, ~32% of the overlay). The filter therefore prunes empty at-rules in its own `OnceExit` rather than relying on cssnano.
23
+
24
+ ## Why postcss-rtlcss `override` + keep-rtl (verified empirically)
25
+
26
+ `postcss-rtlcss` v6 `mode: 'override'` was confirmed (2026-06-02, sandbox `/private/tmp/rtlcss-verify`) to emit **no** `[dir="ltr"]` halves and **no** bare-`[dir]` scaffolding — but it **keeps every unscoped original rule** alongside the `[dir="rtl"]` overrides. Override-alone is therefore not a lean overlay (it would carry a full copy of the base). The keep-rtl filter drops those originals, leaving the `[dir="rtl"]`-only overlay.
27
+
28
+ Override mode also emits the explicit cascade-resets the overlay needs (e.g. `[dir="rtl"] .x { border-left: none; border-right: … }`). This is what makes it beat the considered alternatives:
29
+
30
+ - **`mode: 'combined'`** — reproduces postcss-rtl's doubling (`[dir=ltr]` + `[dir=rtl]` halves). Rejected.
31
+ - **`mode: 'diff'`** — emits flipped declarations **unscoped** (relying on load order, not specificity) and **silently drops authored `[dir="rtl"]` rules**. Rejected.
32
+ - **"postcss-rtl extraction"** (filter postcss-rtl's doubled output down to its `[dir=rtl]` half) — the originally-feared fallback. Unnecessary, and *less correct*: postcss-rtl puts the cascade-resets in the `[dir=ltr]` half, so the extracted `[dir=rtl]` half would be missing them.
33
+ - **Parallel model** (RTL users load `v5-rtl.css` *instead of* `v5.css`) — two large bundles, lower edge-cache hit rate, no free graceful degradation. Rejected in favor of the overlay.
34
+
35
+ ## Consequences
36
+
37
+ - A **CI overlay-integrity assertion** guards the partition every build: the base must contain zero `[dir="rtl"]` rules; the overlay must be non-empty and contain only `[dir="rtl"]` rules (zero unscoped / `[dir="ltr"]` / bare-`[dir]`). This catches a strip-rtl leak into the base, a silently no-op'd postcss-rtlcss (empty overlay), and a keep-rtl leak of originals into the overlay — each diagnostically. Stronger than a bare "byte counts differ" check.
38
+ - **Graceful degradation is free — and better than worst-case.** The base is built on CSS **logical properties**, which respond to the server-set `<html dir="rtl">` natively (no `[dir=rtl]` rules needed). So if a deployment is missing `v5-rtl.css`, an RTL user still gets **mostly-correct RTL** — only the physical-property elements (the overlay's job) stay unflipped — never a blank page or a fully-LTR layout. No runtime fallback logic. (Validated live 2026-06-02: at `dir=rtl` the base alone mirrored the bulk of the Dashboard and Content Edit pages; the overlay then corrected 16 and 46 physical-property elements respectively, including reversing the RTE toolbar.)
39
+ - **Measured impact (2026-06-02, same shared compile, decimal MB):** LTR base 2.63 MB — **−12.8%** vs the 3.02 MB doubled build (which cross-checks against the shipped container artifact ≈ 3.01 MB); RTL base+overlay 2.73 MB — **−9.7%**; overlay **95 KB** (beats the ~130–140 KB projection after empty-wrapper pruning). The overlay's size is effectively a **proxy for physical-property debt** in the legacy CSS: it shrinks toward zero as source migrates from physical (`padding-left`) to logical (`padding-inline-start`) properties, at which point native `dir` handling would leave the overlay nearly empty.
40
+ - The direction-filter is the **deep module** of Lever #2, golden-tested in both directions independent of the build orchestration.
41
+ - **`v4.css` is out of scope** and keeps `postcss-rtl` until v4 is separately retired (it also has hand-authored `[dir='rtl']` Less rules that need their own audit).
42
+ - The change surface is entirely Host-CMS-consumer-side (`build-css.mjs` + a conditional second `<link>` in `DariHtmlToolPageUtils`); the **Preset** is unchanged, so other `@brightspot/ui` consumers are insulated.
43
+
44
+ See also: [ADR 0001](./0001-monolith-css-delivery.md) (Monolith as single delivery vehicle — refined here), and the full Lever #2 design this records the decision for: [`../../plans/lever-2-rtl-overlay-prd.md`](../../plans/lever-2-rtl-overlay-prd.md).
@@ -0,0 +1,33 @@
1
+ ---
2
+ status: accepted
3
+ ---
4
+
5
+ # Editor-scoped `:has()` is the v5 runtime-perf hazard; reduce it
6
+
7
+ The v5 CMS UI feels frozen during editing while v4 is smooth, on the same page/DOM/machine. The cause is **`:has()` selector invalidation**: v5's stylesheet authors ~782 distinct `:has()` selectors (993 occurrences) vs v4's 41, and the browser re-tests `:has()` on every DOM mutation and every change to a class/attribute used as a `:has()` argument — which the editor JS does constantly. Each such interaction costs **~250 ms in v5 vs ~0–2 ms in v4**; expanding a Subscriptions item makes it ~16× worse by bringing the `:has()`-dense `ContentForm` subtree live. **Decision: treat the editor-subtree `:has()` as a runtime-perf hazard and reduce the ~160 rules scoped to `.CIG`/`.RCIG`/`.EIG`/`ContentForm`** (rewrite to `:focus-within`, JS/server-toggled classes, `:where()`/sibling selectors), preserving the structural `display:none` hides. Full evidence and rewrite recipe: [`plans/v5-css-has-runtime-perf-investigation.md`](../../plans/v5-css-has-runtime-perf-investigation.md).
8
+
9
+ ## Considered options
10
+
11
+ All measured via live A/B (v5 `jpencola` vs v4 `joe`, same page), median forced-reflow cost per post-expand interaction:
12
+
13
+ - **Do nothing.** Stock v5 ≈ 507 ms/interaction (v4 ≈ 1 ms). **Rejected** — this is the reported "frozen" UX.
14
+ - **Rewrite only `:has(:focus)` / `:has(.is-*)` (the dynamic patterns, 149 rules).** 507 ms → ~375 ms (**1.4×**). **Rejected** — necessary mechanism but far short; cost is the aggregate `:has()` count in scope, not one category.
15
+ - **Remove all `:has()` (802 rules).** ~21 ms (**~25×**). **Rejected as the fix** — strips structural `display:none` hides and component styling system-wide; proves the lever but breaks layout/behavior.
16
+ - **Reduce the ~160 editor-scoped `:has()` (`.CIG`/`.RCIG`/`.EIG`/`ContentForm`).** ~22 ms (**~25×**, = full benefit) while leaving the other 768 untouched. **Chosen** — 16 % of the rules recover ~96 % of the speed, on a bounded surface (the editor CSS files).
17
+
18
+ ## Consequences
19
+
20
+ - The work is a **design-system `:has()` diet** in `src/legacy/tool-ui/src/` editor CSS (`ContentEdit.css`, `ContentInputGroup.css`, `ContentForm.css`, `RepeatableContentInputGroup/Selector.css`, `Widget.css`, …), not a one-line change. Behavior-sensitive: `:has(:focus)`→`:focus-within` is equivalent, but structural `:has(> child)` and `:has(.is-*)` rewrites must keep the same visual result (especially `display:none` hides) or layout regresses.
21
+ - Distinct from byte-bloat work (ADR 0002 and the bloat investigation): reducing `:has()` also trims ~185 KB, but the **runtime** win comes specifically from cutting the editor-subtree `:has()` count — size reduction alone (e.g. icon emission) would not fix the jank.
22
+ - `:has()` becomes a reviewable cost in the editor surface: new `:has()` in CIG/RCIG/ContentForm CSS should be justified or replaced with a class, since the cost scales with how many `:has()` cover an interacted subtree.
23
+ - The lever was first proven via live in-container CSS surgery (the proxy); the shipped fix is the source rewrite below, rebuilt through the real compilers and re-measured on the same page.
24
+
25
+ ## Outcome (implemented 2026-06-02, branch `fix/v5-css-bloat`)
26
+
27
+ Behavior-preserving rewrite of the 42 editor-scoped `:has()` source rules: `:has(:focus…)` → `:focus-within`; `:has(+ .CIG-row)` / error-border → `:not(:last-child)`; one inert `transition-delay` rule dropped; everything structural (`:has(> child)`, `:has(~ sibling)`, `:has(.is-*)`) → a runtime class set by a new self-contained module `src/legacy/tool-ui/src/editorHasCompat.ts` — an idempotent, debounced sweep (`querySelectorAll` + `classList.toggle`) driven by ready + the editor's `brightspot-content-state-change` event + a 120 ms `MutationObserver`, **not** Blink's per-mutation `:has()` re-test. (FormFilter marks `.is-filterResultAncestor` on result ancestors in JS, replacing `:has(.is-filterResult)`.)
28
+
29
+ - **Built-Monolith count (PostCSS walk):** total `:has(` **993 → 765**; editor-scoped (`.CIG/.RCIG/.EIG/ContentForm`) **223 → 3**. The 3 residuals are the self-contradictory `RCIG-list:empty:has(ol,ul)` ContentForm placeholders (`:empty` + `:has(children)` → never match), absent from normal content-edit surfaces.
30
+ - **Runtime verdict — human A/B, per the [microbenchmark pitfall](../../plans/v5-css-has-runtime-perf-investigation.md):** editor went from janky to "a lot better / acceptable." Synthetic forced-reflow probes do **not** reproduce the jank on Chrome 148 and must not be used to confirm it.
31
+ - **Confirmed by Chrome DevTools *Selector stats* under real editor churn (sorted by invalidation count):** **no rewritten editor `:has()` appears** — `has-formContent` et al. are plain classes now, so they don't invalidate. The only editor-scoped `:has()` in the trace are the 3 inert ContentForm `:empty:has` residuals, at ~748 invalidations but **~0.1 ms each** (`:empty` + `:has(children)` bails instantly — confirmed harmless). The top invalidators are all sub-1.1 ms: `:hover`/`:focus-within` recalc (the pattern this rewrite adopts) and tiny-scope out-of-scope page-chrome `:has()` (calendar / preview / page-header). The per-interaction `:has()` invalidation storm is gone; cost is now ordinary recalc.
32
+ - **Correctness:** for each runtime class, `querySelectorAll('.has-X')` equals the original `:has()` selector element-for-element — 11 classes confirmed EQUAL across Users / Blog Post / Dashboard, the `has-cig` setter and the live `MutationObserver` path confirmed by injection, no console errors. Found and fixed one gap during verification: `DashboardWidget`'s `form:has(.CIG)` → `form.has-cig` rewrite had no setter (added).
33
+ - **Follow-up (out of scope, file separately):** the single most expensive selector in the trace, `.is-imagePreview:not(:has(.ImageEditor-groups.is-focus-active))` (~46 ms, 0 invalidations), is a pre-existing ImageEditor `:has()` outside the `.CIG/.RCIG/.EIG/ContentForm` scope; 7 ImageEditor-related `:has()` remain as a candidate next pass.
@@ -65,21 +65,24 @@
65
65
 
66
66
  ### CSS Properties
67
67
 
68
- | Name | Default | Description |
69
- | ---------------------------------- | ------- | ----------------------------------------------------------------------- |
70
- | `--button-group-gap` | | Space between buttons when variant="spaced" (default: spacing.sm) |
71
- | `--button-group-border-radius` | | Corner radius of outer edges when variant="connected" (default: 0.5rem) |
72
- | `--button-group-border-color` | | Internal divider color between connected buttons (default: gray-300) |
73
- | `--button-group-selection-bg` | | Background of a toggled/selected child (default: primary-50) |
74
- | `--button-group-selection-color` | | Text/icon color of a toggled/selected child (default: primary-700) |
75
- | `--button-group-shimmer-highlight` | | Highlight color of the selection shimmer effect (default: primary-200) |
68
+ | Name | Default | Description |
69
+ | ---------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `--button-group-gap` | | Space between buttons when variant="spaced" (default: spacing.sm) |
71
+ | `--button-group-border-radius` | | Corner radius of outer edges when variant="connected" (default: 0.5rem) |
72
+ | `--button-group-border-color` | | Internal divider color between connected buttons (default: gray-300) |
73
+ | `--button-group-selection-bg` | | Background of a toggled/selected child (default: child's --button-tint-subtle) |
74
+ | `--button-group-selection-color` | | Text/icon color of a toggled/selected child (default: child's --button-tint-strong) |
75
+ | `--button-group-shimmer-highlight` | | Highlight color of the selection shimmer effect (default: child's --button-tint-highlight) Reads the following CSS custom properties from each child button (set by the button plugin per color variant — see tailwind-plugin-button.ts): |
76
+ | `--button-tint-subtle` | | Subtle tint of the child button's color family (read-only here) |
77
+ | `--button-tint-strong` | | Strong tint of the child button's color family (read-only here) |
78
+ | `--button-tint-highlight` | | Highlight tint of the child button's color family (read-only here) |
76
79
 
77
80
  ### Slots
78
81
 
79
- | Name | Description |
80
- | ----- | ---------------------------------------------------------------------------------------------- |
81
- | | One or more btu-button or btu-icon-button elements to display in the group |
82
- | `end` | Pushes the child to the end of the group via auto-margin (vertical: bottom, horizontal: right) |
82
+ | Name | Description |
83
+ | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
84
+ | | One or more \`\<button class="btu-button …">\` or \`\<btu-icon-button>\` elements to display in the group. The \`connected\` variant rounds the outer corners on the real \`\<button>\`, so anchors styled as \`.btu-button\` slotted directly will not get rounded corners. Use \`\<button>\` for grouped controls. |
85
+ | `end` | Pushes the child to the end of the group via auto-margin (vertical: bottom, horizontal: right) |
83
86
 
84
87
  <hr/>
85
88
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspot/ui",
3
- "version": "5.0.2",
3
+ "version": "5.0.3-css-bloat.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "description": "A UI library for building Brightspot CMS components.",
@@ -25,6 +25,7 @@
25
25
  "storybook": "yarn cem && storybook dev -p 6006",
26
26
  "build-storybook": "yarn cem && storybook build -o ./dist/storybook",
27
27
  "test:storybook": "vitest --project=storybook",
28
+ "test:css-bloat": "vitest run --project=css-bloat",
28
29
  "check:conventions": "yarn cem && node scripts/check-conventions-all.mjs",
29
30
  "check:conventions:fast": "node scripts/check-conventions.mjs",
30
31
  "prepare": "husky && (cd src/legacy/tool-ui && yarn install)",
@@ -40,7 +41,6 @@
40
41
  "@storybook/addon-themes": "10.2.12",
41
42
  "@storybook/addon-vitest": "10.2.12",
42
43
  "@storybook/web-components-vite": "10.2.12",
43
- "@tailwindcss/container-queries": "^0.1.1",
44
44
  "@types/node": "^24.10.0",
45
45
  "@types/postcss-import": "^14.0.3",
46
46
  "@types/uuid": "^8.3.0",
@@ -76,6 +76,7 @@
76
76
  "webpack-merge": "^5.8.0"
77
77
  },
78
78
  "dependencies": {
79
+ "@tailwindcss/container-queries": "^0.1.1",
79
80
  "broadcast-channel": "^4.13.0",
80
81
  "loglevel": "^1.8.0",
81
82
  "lucide-static": "0.508.0",
@@ -114,6 +115,6 @@
114
115
  "node scripts/generate-docs.mjs",
115
116
  "git add docs/components/"
116
117
  ],
117
- "*": "yarn format"
118
+ "*": "prettier --write --ignore-unknown"
118
119
  }
119
120
  }
@@ -78,7 +78,7 @@
78
78
 
79
79
  > button {
80
80
  all: unset;
81
- @apply box-border block w-full cursor-pointer text-start;
81
+ @apply box-border blokk w-full cursor-pointer text-start;
82
82
 
83
83
  &:disabled,
84
84
  &.is-disabled {
@@ -188,7 +188,7 @@
188
188
  }
189
189
 
190
190
  .Admin-nav {
191
- @apply overflow-auto border-r border-r-gray-200 [grid-area:nav];
191
+ @apply overflow-auto border-e border-e-gray-200 [grid-area:nav];
192
192
  }
193
193
 
194
194
  .Admin-main {
@@ -206,7 +206,7 @@
206
206
  @apply min-w-[--miller-columns-w] grow overflow-auto [grid-auto-rows:max-content];
207
207
 
208
208
  &:not(:first-child) {
209
- @apply border-l border-solid border-gray-100;
209
+ @apply border-s border-solid border-gray-100;
210
210
  }
211
211
 
212
212
  h1.Widget-title {
@@ -74,7 +74,7 @@ textarea,
74
74
  form {
75
75
  &.is-submitting {
76
76
  button.is-clicked {
77
- @apply btu-button-disabled before:btu-loader before:block before:size-[1.2rem];
77
+ @apply btu-button-disabled before:btu-loader before:blokk before:size-[1.2rem];
78
78
  }
79
79
  }
80
80
  }
@@ -32,7 +32,7 @@
32
32
  }
33
33
 
34
34
  .loaded & {
35
- @apply block;
35
+ @apply blokk;
36
36
  }
37
37
 
38
38
  .loading & {
@@ -1,5 +1,5 @@
1
1
  btu-cig-cluster {
2
- @apply block flex-grow basis-full contain-layout contain-style;
2
+ @apply blokk flex-grow basis-full contain-layout contain-style;
3
3
  }
4
4
 
5
5
  btu-cig-cluster:state(btu-error) {
@@ -11,7 +11,7 @@
11
11
  }
12
12
 
13
13
  .Card-media img {
14
- @apply block h-auto w-full;
14
+ @apply blokk h-auto w-full;
15
15
  }
16
16
 
17
17
  /* Supply a max size because in some instances the image is not constrained by a wrapping element. */
@@ -20,7 +20,7 @@
20
20
  }
21
21
 
22
22
  .CodeMirror-gutters {
23
- @apply border-r-gray-300 bg-gray-50;
23
+ @apply border-e-gray-300 bg-gray-50;
24
24
  }
25
25
 
26
26
  .CodeMirror-linenumber {
@@ -36,11 +36,11 @@
36
36
  }
37
37
 
38
38
  .CodeMirror-cursor {
39
- @apply border-l-[2px] border-l-primary-600;
39
+ @apply border-s-[2px] border-s-primary-600;
40
40
  }
41
41
 
42
42
  .CodeMirror div.CodeMirror-secondarycursor {
43
- @apply border-l-gray-200;
43
+ @apply border-s-gray-200;
44
44
  }
45
45
 
46
46
  .cm-fat-cursor .CodeMirror-cursor {
@@ -56,7 +56,7 @@
56
56
  }
57
57
 
58
58
  .CodeMirror-ruler {
59
- @apply border-l-gray-200;
59
+ @apply border-s-gray-200;
60
60
  }
61
61
 
62
62
  .cm-s-default .cm-header {
@@ -29,7 +29,7 @@
29
29
  }
30
30
 
31
31
  .sp-preview-inner {
32
- @apply absolute bottom-0 left-0 right-0 top-0 block h-5 w-5 rounded-full;
32
+ @apply absolute bottom-0 left-0 right-0 top-0 blokk h-5 w-5 rounded-full;
33
33
  }
34
34
 
35
35
  .sp-preview:not([data-placeholder^='#']) {
@@ -117,7 +117,7 @@
117
117
  }
118
118
 
119
119
  .sp-palette .sp-thumb-inner {
120
- @apply block h-full w-full overflow-hidden rounded-full;
120
+ @apply blokk h-full w-full overflow-hidden rounded-full;
121
121
  }
122
122
 
123
123
  .sp-button-container {
@@ -78,7 +78,7 @@
78
78
  :not([data-create], [data-sublist])
79
79
  ) {
80
80
  .ComboInput-check {
81
- @apply block scale-50 opacity-0 transition-[opacity_scale] duration-250 [transition-timing-function:theme(transitionTimingFunction[spring-stiff])] before:btu-icon before:btu-icon-check before:text-inherit;
81
+ @apply blokk scale-50 opacity-0 transition-[opacity_scale] duration-250 [transition-timing-function:theme(transitionTimingFunction[spring-stiff])] before:btu-icon before:btu-icon-check before:text-inherit;
82
82
  }
83
83
 
84
84
  &[aria-selected='true'] {
@@ -153,7 +153,7 @@ select {
153
153
  /***** "Create New" Options *****/
154
154
  /********************************/
155
155
  .ComboInput-option > a[data-create='true'] {
156
- @apply btu-heading-6 block px-4 py-2 no-underline before:btu-icon before:btu-icon-plus before:me-2;
156
+ @apply btu-heading-6 blokk px-4 py-2 no-underline before:btu-icon before:btu-icon-plus before:me-2;
157
157
  }
158
158
 
159
159
  .ComboInput-option[data-create='true'],
@@ -195,7 +195,7 @@ select {
195
195
 
196
196
  .ComboInput-option-favorite:not(.is-unstarred)
197
197
  .ComboInput-option-favorite-icon {
198
- @apply block size-4 bg-primary-800 [clip-path:theme(shape.starFilled)];
198
+ @apply blokk size-4 bg-primary-800 [clip-path:theme(shape.starFilled)];
199
199
  }
200
200
 
201
201
  /*****************************/
@@ -197,7 +197,7 @@
197
197
  @apply pointer-events-none;
198
198
 
199
199
  &:not(.Link) {
200
- @apply btu-button-disabled before:btu-loader before:block before:size-[1.2rem];
200
+ @apply btu-button-disabled before:btu-loader before:blokk before:size-[1.2rem];
201
201
  }
202
202
  }
203
203
  }
@@ -90,7 +90,7 @@
90
90
  }
91
91
 
92
92
  .ContentEdit-top + .Message {
93
- @apply rounded-none border-b border-l-0 border-r-0 border-t-0 [grid-area:status] max-md:p-4 md:[grid-area:banner];
93
+ @apply rounded-none border-b border-s-0 border-e-0 border-t-0 [grid-area:status] max-md:p-4 md:[grid-area:banner];
94
94
  }
95
95
 
96
96
  .ContentEdit-top + .Message > .WorkInProgressRestoredMessage-actions a {
@@ -181,7 +181,7 @@
181
181
  }
182
182
 
183
183
  .contentForm-main {
184
- @apply overflow-auto [grid-area:content] [scrollbar-gutter:stable] max-md:h-full md:overflow-y-auto md:overflow-x-hidden md:overscroll-none md:border-r md:[grid-area:left];
184
+ @apply overflow-auto [grid-area:content] [scrollbar-gutter:stable] max-md:h-full md:overflow-y-auto md:overflow-x-hidden md:overscroll-none md:border-e md:[grid-area:left];
185
185
  }
186
186
 
187
187
  .ContentEdit-main {
@@ -212,7 +212,7 @@
212
212
  }
213
213
 
214
214
  .ContentEdit-preview {
215
- @apply visible relative bg-gray-100 transition-all @container/preview [transform:scaleX(.000001)] max-md:absolute max-md:bottom-[52px] max-md:z-[21] max-md:h-[calc(100dvh_-_52px)] max-md:w-full max-md:duration-700 md:origin-right md:border-r md:border-gray-200 md:[grid-area:preview];
215
+ @apply visible relative bg-gray-100 transition-all @container/preview [transform:scaleX(.000001)] max-md:absolute max-md:bottom-[52px] max-md:z-[21] max-md:h-[calc(100dvh_-_52px)] max-md:w-full max-md:duration-700 md:origin-right md:border-e md:border-gray-200 md:[grid-area:preview];
216
216
 
217
217
  iframe {
218
218
  @apply absolute left-0 top-0 h-[var(--PreviewFrame-height,_calc(100%_/_var(--PreviewFrame-scale)))] w-[var(--ContentEdit-previewFrameWidth,_1920px)] origin-top-left overflow-clip;
@@ -252,7 +252,7 @@
252
252
  }
253
253
 
254
254
  & .PreviewFrame-typeDisplay {
255
- @apply relative block size-full overflow-y-auto overflow-x-hidden rounded-none rounded-t-2xl border border-b-0 border-gray-200;
255
+ @apply relative blokk size-full overflow-y-auto overflow-x-hidden rounded-none rounded-t-2xl border border-b-0 border-gray-200;
256
256
  }
257
257
 
258
258
  & .PreviewFrame-typeDisplay > iframe[name*='PreviewFrame'] {
@@ -357,13 +357,13 @@
357
357
  .PreviewFrame-typeControls [name='_previewPath'] + .ComboInput
358
358
  )
359
359
  .PreviewFrame-moreActions {
360
- @apply @preview-lg/preview:block;
360
+ @apply @preview-lg/preview:blokk;
361
361
  }
362
362
 
363
363
  &
364
364
  .PreviewFrame.HasPreviewSelect:has(.PreviewFrame-previewToEditToggle)
365
365
  .PreviewFrame-moreActions {
366
- @apply !block;
366
+ @apply !blokk;
367
367
  }
368
368
 
369
369
  & .PreviewFrame-typeDisplay {
@@ -409,7 +409,7 @@
409
409
  }
410
410
 
411
411
  .ContentEdit-toolbar {
412
- @apply z-30 w-full bg-white px-4 py-2 [grid-area:bottom] max-md:flex max-md:flex-nowrap max-md:justify-evenly max-md:overflow-hidden max-md:overflow-x-auto max-md:border-t max-md:border-t-gray-200 md:z-10 md:block md:bg-gray-50 md:p-4 md:[grid-area:toolbar] md:[width:var(--ContentEdit-toolbarWidth)] md:[&_>_li]:mb-2;
412
+ @apply z-30 w-full bg-white px-4 py-2 [grid-area:bottom] max-md:flex max-md:flex-nowrap max-md:justify-evenly max-md:overflow-hidden max-md:overflow-x-auto max-md:border-t max-md:border-t-gray-200 md:z-10 md:blokk md:bg-gray-50 md:p-4 md:[grid-area:toolbar] md:[width:var(--ContentEdit-toolbarWidth)] md:[&_>_li]:mb-2;
413
413
 
414
414
  button {
415
415
  @apply btu-button btu-button-text-hidden btu-button-fill-none btu-button-gray;
@@ -458,7 +458,7 @@
458
458
  }
459
459
 
460
460
  .ContentEdit-right {
461
- @apply hidden overflow-y-auto overflow-x-hidden bg-gray-50 [grid-area:content] max-md:absolute max-md:bottom-[52px] max-md:z-10 max-md:h-[calc(100dvh-52px)] max-md:w-full md:relative md:bottom-auto md:left-auto md:right-auto md:top-auto md:w-auto md:border-r md:border-solid md:[grid-area:right];
461
+ @apply hidden overflow-y-auto overflow-x-hidden bg-gray-50 [grid-area:content] max-md:absolute max-md:bottom-[52px] max-md:z-10 max-md:h-[calc(100dvh-52px)] max-md:w-full md:relative md:bottom-auto md:left-auto md:right-auto md:top-auto md:w-auto md:border-e md:border-solid md:[grid-area:right];
462
462
 
463
463
  &:has(.ContentEditDrawer-widget) {
464
464
  @apply top-auto;
@@ -590,7 +590,7 @@
590
590
  }
591
591
 
592
592
  .ContentEdit-right.is-visible {
593
- @apply block;
593
+ @apply blokk;
594
594
  }
595
595
 
596
596
  .ContentEdit-overlayList,
@@ -617,12 +617,12 @@
617
617
 
618
618
  /* Content type */
619
619
  .ContentEdit-contentType--v5 {
620
- @apply btu-badge-gray btu-badge-sm -order-2 block flex-1 truncate md:me-2 md:@6xl/page-header:inline;
620
+ @apply btu-badge-gray btu-badge-sm -order-2 blokk flex-1 truncate md:me-2 md:@6xl/page-header:inline;
621
621
  }
622
622
 
623
623
  /* Content status */
624
624
  .ContentEdit-status--v5 {
625
- @apply -order-2 block min-w-[unset] flex-1 truncate before:me-1.5 before:inline-block md:me-2;
625
+ @apply -order-2 blokk min-w-[unset] flex-1 truncate before:me-1.5 before:inline-block md:me-2;
626
626
  }
627
627
  }
628
628
 
@@ -747,7 +747,7 @@
747
747
  }
748
748
 
749
749
  .ContentEdit-overlay {
750
- @apply btu-badge-gray btu-badge-sm relative block flex-1 truncate !pe-7 after:btu-icon after:btu-icon-chevron-down after:absolute after:right-2 after:top-[5px] after:btu-icon-xs;
750
+ @apply btu-badge-gray btu-badge-sm relative blokk flex-1 truncate !pe-7 after:btu-icon after:btu-icon-chevron-down after:absolute after:right-2 after:top-[5px] after:btu-icon-xs;
751
751
  }
752
752
  }
753
753
 
@@ -9,7 +9,7 @@
9
9
  @apply fixed scale-x-0 [content-visibility:hidden] md:[--nav-width:var(--pageNav-w)];
10
10
 
11
11
  &.is-open {
12
- @apply bottom-[calc(var(--toolbar-h-mobile)_+_var(--offset))] left-[calc(var(--nav-width)_+_var(--offset))] z-20 origin-right scale-x-100 overflow-hidden border-r bg-gray-50 p-0 shadow-lg transition-transform duration-300 ease-out [content-visibility:auto] max-md:right-[--offset] md:bottom-[--offset] md:left-[calc(var(--nav-width)_+_var(--panel-width)_+_var(--offset))] md:right-[calc(var(--right-toolbar-w)_+_var(--offset))];
12
+ @apply bottom-[calc(var(--toolbar-h-mobile)_+_var(--offset))] left-[calc(var(--nav-width)_+_var(--offset))] z-20 origin-right scale-x-100 overflow-hidden border-e bg-gray-50 p-0 shadow-lg transition-transform duration-300 ease-out [content-visibility:auto] max-md:right-[--offset] md:bottom-[--offset] md:left-[calc(var(--nav-width)_+_var(--panel-width)_+_var(--offset))] md:right-[calc(var(--right-toolbar-w)_+_var(--offset))];
13
13
  }
14
14
  }
15
15
 
@@ -65,11 +65,11 @@
65
65
  /* Display spinner when filters are updated and frame is loading */
66
66
  .ContentEditDrawer-widget.is-loading {
67
67
  &::before {
68
- @apply absolute inset-0 z-[2] block bg-gray-50 content-[''];
68
+ @apply absolute inset-0 z-[2] blokk bg-gray-50 content-[''];
69
69
  }
70
70
 
71
71
  &::after {
72
- @apply btu-loader visible absolute left-1/2 top-1/2 z-[2] block size-[--loader-size] -translate-x-1/2 -translate-y-1/2;
72
+ @apply btu-loader visible absolute left-1/2 top-1/2 z-[2] blokk size-[--loader-size] -translate-x-1/2 -translate-y-1/2;
73
73
  }
74
74
  }
75
75
 
@@ -86,11 +86,11 @@
86
86
  }
87
87
 
88
88
  .ContentEditDrawer-widget-carousel-list-item-moreInfoButton.ContentSummary-info {
89
- @apply absolute bottom-[30px] right-3 m-0 hidden touch-device:block;
89
+ @apply absolute bottom-[30px] right-3 m-0 hidden touch-device:blokk;
90
90
  }
91
91
 
92
92
  .ContentEditDrawer-widget-carousel-list-item-selectButton {
93
- @apply absolute right-0 top-0 m-1 box-content rounded-lg border-[6px] border-white bg-gray-300 before:absolute before:inset-[1px] before:block before:rounded-[1px] before:bg-white hover:border-gray-100 hover:bg-primary-800 hover:before:bg-primary-50;
93
+ @apply absolute right-0 top-0 m-1 box-content rounded-lg border-[6px] border-white bg-gray-300 before:absolute before:inset-[1px] before:blokk before:rounded-[1px] before:bg-white hover:border-gray-100 hover:bg-primary-800 hover:before:bg-primary-50;
94
94
 
95
95
  &:checked {
96
96
  @apply border-primary-50 bg-primary-800 before:bg-primary-800 hover:border-primary-800 hover:bg-primary-800;
@@ -106,7 +106,7 @@
106
106
  }
107
107
 
108
108
  .ContentSummary-info {
109
- @apply block;
109
+ @apply blokk;
110
110
  }
111
111
  }
112
112
 
@@ -534,14 +534,10 @@
534
534
  }
535
535
  }
536
536
 
537
- /* Single and Repeatable Content selector inputs in readonly mode.
538
- Locking pointer events is handled element-by-element in the block above
539
- (input, .ContentSelector-search, .ComboInput, etc.) — matching V4's
540
- surgical approach — so the edit pencil stays clickable to view the
541
- referenced object. Only border and text color belong here. */
537
+ /* Single and Repeatable Content selector inputs in readonly mode */
542
538
  .CIG-small:has(> .ContentSelector-select),
543
539
  .RCS-list > li {
544
- @apply border-gray-300 text-gray-400;
540
+ @apply pointer-events-none border-gray-300 text-gray-400;
545
541
  }
546
542
  }
547
543
 
@@ -631,7 +627,7 @@
631
627
  }
632
628
 
633
629
  .CIG-small > .DateStringCalendarEmbedded .DateStringCalendarEmbedded-widget {
634
- @apply block border-none;
630
+ @apply blokk border-none;
635
631
 
636
632
  .DateStringCalendarTime,
637
633
  /* Unset the horizontal borders which appear in these pseudo-elements */
@@ -674,9 +670,8 @@
674
670
  :is(.CIG-row.is-readOnly, .CIG.is-readOnly) .CIG-small:focus-within {
675
671
  @apply rounded-lg outline-dashed outline-[4px] outline-gray-300;
676
672
 
677
- /* Remove default focus styles, except on elements that stay interactive in
678
- read-only mode (e.g. the edit pencil, which opens the referenced object). */
679
- :focus:not(.ImageEditor-editToggle, .filePreview > a, .ContentSelector-edit),
673
+ /* Remove default focus styles */
674
+ :focus:not(.ImageEditor-editToggle, .filePreview > a),
680
675
  .ProseMirrorContainer {
681
676
  @apply !border-transparent !ring-0;
682
677
  }
@@ -42,11 +42,11 @@
42
42
  }
43
43
 
44
44
  .Widget-reports th.downloadData {
45
- @apply w-[68px] border-l border-gray-200 text-[0];
45
+ @apply w-[68px] border-s border-gray-200 text-[0];
46
46
  }
47
47
 
48
48
  .Widget-reports td.downloadLink {
49
- @apply border-l border-gray-200 px-4;
49
+ @apply border-s border-gray-200 px-4;
50
50
  }
51
51
 
52
52
  .Widget-reports td.downloadLink a {
@@ -66,7 +66,7 @@ input[data-read-only='true'] ~ .ContentSelector-edit {
66
66
 
67
67
  .ContentSelector-select + .ComboInput,
68
68
  .RCS-item > .ComboInput {
69
- @apply box-border max-h-[36px] !w-full min-w-0 rounded-r-none pe-2 touch-device:pe-3;
69
+ @apply box-border max-h-[36px] !w-full min-w-0 rounded-e-none pe-2 touch-device:pe-3;
70
70
 
71
71
  &:not([aria-expanded='true'], :focus-visible) {
72
72
  @apply border-transparent more-contrast:!border-transparent;
@@ -85,7 +85,7 @@ input[data-read-only='true'] ~ .ContentSelector-edit {
85
85
  /* Image preview not available - text only */
86
86
  input[data-previewable='true']:not([data-preview]) + .ContentSelector-search,
87
87
  input[data-previewable='true'][data-preview=''] + .ContentSelector-search {
88
- @apply relative m-0 inline-block w-full truncate rounded-r-none border-solid py-2 pe-10 ps-4 text-sm font-normal text-gray-700 no-underline before:btu-button before:btu-button-text-hidden before:btu-button-fill-none before:btu-button-gray before:btu-button-xs before:btu-icon touch-device:before:btu-button-sm touch-device:before:btu-icon before:absolute before:right-2 before:top-1 touch-device:before:top-0;
88
+ @apply relative m-0 inline-block w-full truncate rounded-e-none border-solid py-2 pe-10 ps-4 text-sm font-normal text-gray-700 no-underline before:btu-button before:btu-button-text-hidden before:btu-button-fill-none before:btu-button-gray before:btu-button-xs before:btu-icon touch-device:before:btu-button-sm touch-device:before:btu-icon before:absolute before:right-2 before:top-1 touch-device:before:top-0;
89
89
 
90
90
  &.is-removing {
91
91
  @apply text-error-400 line-through;
@@ -151,7 +151,7 @@ input[data-previewable='true'][data-preview=''] + .ContentSelector-search {
151
151
  @apply flex-wrap;
152
152
 
153
153
  .ContentSelector-search {
154
- @apply m-0 inline-block w-[calc(100%-6.75rem)] truncate rounded-r-none border-solid py-2 pe-9 ps-4 text-sm font-medium text-gray-700 before:!absolute before:right-1 before:top-1;
154
+ @apply m-0 inline-block w-[calc(100%-6.75rem)] truncate rounded-e-none border-solid py-2 pe-9 ps-4 text-sm font-medium text-gray-700 before:!absolute before:right-1 before:top-1;
155
155
  }
156
156
 
157
157
  .ContentSelector-clear {
@@ -166,7 +166,7 @@ input[data-previewable='true'][data-preview=''] + .ContentSelector-search {
166
166
  }
167
167
 
168
168
  .ImageEditor-focusToggle {
169
- @apply after:border-b-0 after:border-l-0;
169
+ @apply after:border-b-0 after:border-s-0;
170
170
  }
171
171
 
172
172
  .ImageEditor-open {
@@ -174,7 +174,7 @@ input[data-previewable='true'][data-preview=''] + .ContentSelector-search {
174
174
  }
175
175
 
176
176
  .ImageEditor-editToggle {
177
- @apply after:border-b-0 after:border-r-0;
177
+ @apply after:border-b-0 after:border-e-0;
178
178
  }
179
179
 
180
180
  .ComboInput {