cursedbelt 3.0.2 → 4.1.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 (252) hide show
  1. package/dist/react/components/ListToolbar.d.ts +94 -0
  2. package/dist/react/components/ListToolbar.d.ts.map +1 -0
  3. package/dist/react/components/ListToolbar.js +66 -0
  4. package/dist/react/components/ListToolbar.js.map +1 -0
  5. package/dist/react/file-tree/FileTree.d.ts +282 -3
  6. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  7. package/dist/react/file-tree/FileTree.js +799 -131
  8. package/dist/react/file-tree/FileTree.js.map +1 -1
  9. package/dist/react/file-tree/fileTree.css +151 -0
  10. package/dist/react/file-tree/fileTreeSelection.d.ts +187 -0
  11. package/dist/react/file-tree/fileTreeSelection.d.ts.map +1 -0
  12. package/dist/react/file-tree/fileTreeSelection.js +264 -0
  13. package/dist/react/file-tree/fileTreeSelection.js.map +1 -0
  14. package/dist/react/file-tree/index.d.ts +1 -0
  15. package/dist/react/file-tree/index.d.ts.map +1 -1
  16. package/dist/react/file-tree/index.js +8 -0
  17. package/dist/react/file-tree/index.js.map +1 -1
  18. package/dist/react/index.d.ts +1 -0
  19. package/dist/react/index.d.ts.map +1 -1
  20. package/dist/react/index.js +1 -0
  21. package/dist/react/index.js.map +1 -1
  22. package/dist/react/lib/form.d.ts +3 -0
  23. package/dist/react/lib/form.d.ts.map +1 -1
  24. package/dist/react/lib/form.js +43 -5
  25. package/dist/react/lib/form.js.map +1 -1
  26. package/dist/react/media/DenseGalleryShell.d.ts +19 -3
  27. package/dist/react/media/DenseGalleryShell.d.ts.map +1 -1
  28. package/dist/react/media/DenseGalleryShell.js +55 -15
  29. package/dist/react/media/DenseGalleryShell.js.map +1 -1
  30. package/dist/react/media/FocusViewer.d.ts.map +1 -1
  31. package/dist/react/media/FocusViewer.js +14 -14
  32. package/dist/react/media/FocusViewer.js.map +1 -1
  33. package/dist/react/media/FrameGrabber.d.ts +23 -0
  34. package/dist/react/media/FrameGrabber.d.ts.map +1 -1
  35. package/dist/react/media/FrameGrabber.js +67 -1
  36. package/dist/react/media/FrameGrabber.js.map +1 -1
  37. package/dist/react/media/MediaResize.d.ts +51 -19
  38. package/dist/react/media/MediaResize.d.ts.map +1 -1
  39. package/dist/react/media/MediaResize.js +20 -13
  40. package/dist/react/media/MediaResize.js.map +1 -1
  41. package/dist/react/media/VideoChapterEditor.d.ts +67 -0
  42. package/dist/react/media/VideoChapterEditor.d.ts.map +1 -0
  43. package/dist/react/media/VideoChapterEditor.js +148 -0
  44. package/dist/react/media/VideoChapterEditor.js.map +1 -0
  45. package/dist/react/media/VideoPlayer.d.ts +46 -4
  46. package/dist/react/media/VideoPlayer.d.ts.map +1 -1
  47. package/dist/react/media/VideoPlayer.js +124 -11
  48. package/dist/react/media/VideoPlayer.js.map +1 -1
  49. package/dist/react/media/VideoTurner.d.ts +15 -2
  50. package/dist/react/media/VideoTurner.d.ts.map +1 -1
  51. package/dist/react/media/VideoTurner.js +29 -2
  52. package/dist/react/media/VideoTurner.js.map +1 -1
  53. package/dist/react/media/denseGallery.css +104 -46
  54. package/dist/react/media/hlsSource.d.ts +57 -0
  55. package/dist/react/media/hlsSource.d.ts.map +1 -1
  56. package/dist/react/media/hlsSource.js +177 -1
  57. package/dist/react/media/hlsSource.js.map +1 -1
  58. package/dist/react/media/index.d.ts +2 -0
  59. package/dist/react/media/index.d.ts.map +1 -1
  60. package/dist/react/media/index.js +6 -0
  61. package/dist/react/media/index.js.map +1 -1
  62. package/dist/react/media/mediaResize.css +127 -54
  63. package/dist/react/media/pictureEditor.css +52 -6
  64. package/dist/react/media/videoChapterEditor.css +203 -0
  65. package/dist/react/media/videoChapters.d.ts +117 -0
  66. package/dist/react/media/videoChapters.d.ts.map +1 -0
  67. package/dist/react/media/videoChapters.js +95 -0
  68. package/dist/react/media/videoChapters.js.map +1 -0
  69. package/dist/react/media-gallery/GalleryTable.d.ts +52 -1
  70. package/dist/react/media-gallery/GalleryTable.d.ts.map +1 -1
  71. package/dist/react/media-gallery/GalleryTable.js +40 -9
  72. package/dist/react/media-gallery/GalleryTable.js.map +1 -1
  73. package/dist/react/media-gallery/MediaGallery.d.ts +107 -4
  74. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  75. package/dist/react/media-gallery/MediaGallery.js +484 -104
  76. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  77. package/dist/react/media-gallery/MediaMetaEditor.d.ts +48 -0
  78. package/dist/react/media-gallery/MediaMetaEditor.d.ts.map +1 -0
  79. package/dist/react/media-gallery/MediaMetaEditor.js +66 -0
  80. package/dist/react/media-gallery/MediaMetaEditor.js.map +1 -0
  81. package/dist/react/media-gallery/index.d.ts +2 -0
  82. package/dist/react/media-gallery/index.d.ts.map +1 -1
  83. package/dist/react/media-gallery/index.js +5 -0
  84. package/dist/react/media-gallery/index.js.map +1 -1
  85. package/dist/react/media-gallery/mediaGallery.css +180 -4
  86. package/dist/react/media-gallery/mediaMeta.d.ts +62 -0
  87. package/dist/react/media-gallery/mediaMeta.d.ts.map +1 -0
  88. package/dist/react/media-gallery/mediaMeta.js +56 -0
  89. package/dist/react/media-gallery/mediaMeta.js.map +1 -0
  90. package/dist/react/media-gallery/playbackPreferences.d.ts +77 -0
  91. package/dist/react/media-gallery/playbackPreferences.d.ts.map +1 -1
  92. package/dist/react/media-gallery/playbackPreferences.js +42 -0
  93. package/dist/react/media-gallery/playbackPreferences.js.map +1 -1
  94. package/dist/react/media-gallery/types.d.ts +34 -0
  95. package/dist/react/media-gallery/types.d.ts.map +1 -1
  96. package/dist/scripts/guardrailsEnforce.d.ts +29 -0
  97. package/dist/scripts/guardrailsEnforce.d.ts.map +1 -0
  98. package/dist/styles-areas/activity-bar.css +30 -0
  99. package/dist/styles-areas/analytics.css +672 -0
  100. package/dist/styles-areas/auth.css +154 -0
  101. package/dist/styles-areas/calendar.css +416 -0
  102. package/dist/styles-areas/chart-table.css +41 -0
  103. package/dist/styles-areas/charts.css +89 -0
  104. package/dist/styles-areas/chat.css +364 -0
  105. package/dist/styles-areas/clipboard.css +26 -0
  106. package/dist/styles-areas/code-editor.css +130 -0
  107. package/dist/styles-areas/companion-link.css +70 -0
  108. package/dist/styles-areas/core.css +2503 -0
  109. package/dist/styles-areas/dashboard-grid.css +84 -0
  110. package/dist/styles-areas/data-table.css +657 -0
  111. package/dist/styles-areas/deep-link.css +26 -0
  112. package/dist/styles-areas/diff-viewer.css +30 -0
  113. package/dist/styles-areas/disk-usage.css +262 -0
  114. package/dist/styles-areas/emoji.css +154 -0
  115. package/dist/styles-areas/fields.css +582 -0
  116. package/dist/styles-areas/file-tree.css +33 -0
  117. package/dist/styles-areas/filter-rail.css +235 -0
  118. package/dist/styles-areas/folder-tree.css +219 -0
  119. package/dist/styles-areas/keep-awake.css +26 -0
  120. package/dist/styles-areas/layout-engine.css +239 -0
  121. package/dist/styles-areas/markdown.css +87 -0
  122. package/dist/styles-areas/master-detail.css +239 -0
  123. package/dist/styles-areas/master-lock.css +26 -0
  124. package/dist/styles-areas/media-gallery.css +351 -0
  125. package/dist/styles-areas/media.css +658 -0
  126. package/dist/styles-areas/nav.css +488 -0
  127. package/dist/styles-areas/notifications.css +245 -0
  128. package/dist/styles-areas/overlays.css +133 -0
  129. package/dist/styles-areas/palette.css +140 -0
  130. package/dist/styles-areas/pdf-viewer.css +47 -0
  131. package/dist/styles-areas/problem-detail.css +97 -0
  132. package/dist/styles-areas/rich-text.css +167 -0
  133. package/dist/styles-areas/sharing.css +153 -0
  134. package/dist/styles-areas/spreadsheet.css +37 -0
  135. package/dist/styles-areas/stats.css +281 -0
  136. package/dist/styles-areas/test-report.css +133 -0
  137. package/dist/styles-areas/virtual.css +30 -0
  138. package/dist/styles-areas/wizard.css +381 -0
  139. package/dist/styles-areas/workbench.css +543 -0
  140. package/dist/styles-areas/workbook-viewer.css +44 -0
  141. package/dist/styles-static.css +34 -23
  142. package/dist/styles.css +34 -23
  143. package/package.json +130 -32
  144. package/scripts/checkDistExports.ts +67 -0
  145. package/scripts/cssRules.ts +125 -0
  146. package/scripts/fixtureAppCss.ts +344 -0
  147. package/scripts/generateAreaStyles.ts +253 -0
  148. package/scripts/guardrailsEnforce.spec.ts +62 -0
  149. package/scripts/guardrailsEnforce.ts +67 -3
  150. package/scripts/paths.ts +106 -0
  151. package/scripts/styleAreas.ts +585 -0
  152. package/scripts/verify.ts +5 -0
  153. package/src/barrelsReachNoOptionalPeer.spec.ts +117 -9
  154. package/src/docsMatchTheSplit.spec.ts +98 -0
  155. package/src/fixtureAppCss.spec.ts +177 -0
  156. package/src/namedSubpathsResolve.spec.ts +35 -0
  157. package/src/publishShape.spec.ts +67 -0
  158. package/src/react/components/ListToolbar.spec.tsx +172 -0
  159. package/src/react/components/ListToolbar.tsx +151 -0
  160. package/src/react/file-tree/FileTree.spec.tsx +1009 -0
  161. package/src/react/file-tree/FileTree.tsx +1459 -309
  162. package/src/react/file-tree/fileTree.css +151 -0
  163. package/src/react/file-tree/fileTreeSelection.spec.ts +327 -0
  164. package/src/react/file-tree/fileTreeSelection.ts +321 -0
  165. package/src/react/file-tree/index.ts +21 -0
  166. package/src/react/index.ts +1 -0
  167. package/src/react/lib/form.ts +51 -8
  168. package/src/react/markdown/MarkdownDoc.spec.tsx +1 -1
  169. package/src/react/media/DenseGalleryShell.tsx +108 -36
  170. package/src/react/media/FocusViewer.tsx +15 -8
  171. package/src/react/media/FrameGrabber.spec.tsx +175 -0
  172. package/src/react/media/FrameGrabber.tsx +67 -0
  173. package/src/react/media/MediaResize.spec.tsx +84 -14
  174. package/src/react/media/MediaResize.tsx +51 -19
  175. package/src/react/media/VideoChapterEditor.tsx +428 -0
  176. package/src/react/media/VideoPlayer.spec.tsx +111 -1
  177. package/src/react/media/VideoPlayer.tsx +158 -11
  178. package/src/react/media/VideoTurner.spec.tsx +67 -0
  179. package/src/react/media/VideoTurner.tsx +70 -15
  180. package/src/react/media/denseGallery.css +104 -46
  181. package/src/react/media/hlsSource.recovery.spec.ts +213 -0
  182. package/src/react/media/hlsSource.spec.ts +8 -0
  183. package/src/react/media/hlsSource.ts +189 -1
  184. package/src/react/media/index.ts +14 -0
  185. package/src/react/media/mediaResize.css +127 -54
  186. package/src/react/media/pictureEditor.css +52 -6
  187. package/src/react/media/videoChapterEditor.css +203 -0
  188. package/src/react/media/videoChapters.spec.ts +176 -0
  189. package/src/react/media/videoChapters.ts +181 -0
  190. package/src/react/media-gallery/GalleryTable.tsx +93 -3
  191. package/src/react/media-gallery/MediaGallery.spec.tsx +1156 -10
  192. package/src/react/media-gallery/MediaGallery.tsx +813 -95
  193. package/src/react/media-gallery/MediaMetaEditor.tsx +237 -0
  194. package/src/react/media-gallery/galleryTable.spec.ts +41 -1
  195. package/src/react/media-gallery/index.ts +7 -0
  196. package/src/react/media-gallery/mediaGallery.css +180 -4
  197. package/src/react/media-gallery/mediaMeta.spec.ts +95 -0
  198. package/src/react/media-gallery/mediaMeta.ts +101 -0
  199. package/src/react/media-gallery/playbackPreferences.spec.ts +172 -0
  200. package/src/react/media-gallery/playbackPreferences.ts +100 -0
  201. package/src/react/media-gallery/types.ts +34 -0
  202. package/src/shippedFilesAreTracked.spec.ts +69 -0
  203. package/src/styles-areas/activity-bar.css +30 -0
  204. package/src/styles-areas/analytics.css +672 -0
  205. package/src/styles-areas/auth.css +154 -0
  206. package/src/styles-areas/calendar.css +416 -0
  207. package/src/styles-areas/chart-table.css +41 -0
  208. package/src/styles-areas/charts.css +89 -0
  209. package/src/styles-areas/chat.css +364 -0
  210. package/src/styles-areas/clipboard.css +26 -0
  211. package/src/styles-areas/code-editor.css +130 -0
  212. package/src/styles-areas/companion-link.css +70 -0
  213. package/src/styles-areas/core.css +2503 -0
  214. package/src/styles-areas/dashboard-grid.css +84 -0
  215. package/src/styles-areas/data-table.css +657 -0
  216. package/src/styles-areas/deep-link.css +26 -0
  217. package/src/styles-areas/diff-viewer.css +30 -0
  218. package/src/styles-areas/disk-usage.css +262 -0
  219. package/src/styles-areas/emoji.css +154 -0
  220. package/src/styles-areas/fields.css +582 -0
  221. package/src/styles-areas/file-tree.css +33 -0
  222. package/src/styles-areas/filter-rail.css +235 -0
  223. package/src/styles-areas/folder-tree.css +219 -0
  224. package/src/styles-areas/keep-awake.css +26 -0
  225. package/src/styles-areas/layout-engine.css +239 -0
  226. package/src/styles-areas/markdown.css +87 -0
  227. package/src/styles-areas/master-detail.css +239 -0
  228. package/src/styles-areas/master-lock.css +26 -0
  229. package/src/styles-areas/media-gallery.css +351 -0
  230. package/src/styles-areas/media.css +658 -0
  231. package/src/styles-areas/nav.css +488 -0
  232. package/src/styles-areas/notifications.css +245 -0
  233. package/src/styles-areas/overlays.css +133 -0
  234. package/src/styles-areas/palette.css +140 -0
  235. package/src/styles-areas/pdf-viewer.css +47 -0
  236. package/src/styles-areas/problem-detail.css +97 -0
  237. package/src/styles-areas/rich-text.css +167 -0
  238. package/src/styles-areas/sharing.css +153 -0
  239. package/src/styles-areas/spreadsheet.css +37 -0
  240. package/src/styles-areas/stats.css +281 -0
  241. package/src/styles-areas/test-report.css +133 -0
  242. package/src/styles-areas/virtual.css +30 -0
  243. package/src/styles-areas/wizard.css +381 -0
  244. package/src/styles-areas/workbench.css +543 -0
  245. package/src/styles-areas/workbook-viewer.css +44 -0
  246. package/src/styles-static.css +34 -23
  247. package/src/styles.css +34 -23
  248. package/src/stylesAreas.spec.ts +247 -0
  249. package/src/stylesUtilitiesMatches.spec.ts +6 -77
  250. package/src/testFilesRunInParallel.spec.ts +79 -0
  251. package/src/typecheckCachesAreSeparate.spec.ts +128 -0
  252. package/src/verifyGraph.spec.ts +6 -0
package/dist/styles.css CHANGED
@@ -6,29 +6,32 @@
6
6
  * This `@source` makes Tailwind v4 scan cursedbelt's source files so the utility
7
7
  * classes used by its components are generated in the app's stylesheet.
8
8
  *
9
- * ── Two ways in, and which one an app wants ──────────────────────────────────
9
+ * ── Three ways in, and which one an app wants ────────────────────────────────
10
10
  * `@source "."` is the expensive half: it points Tailwind's JIT at cursedbelt's
11
11
  * ENTIRE source tree, so the app emits the union of the whole design system
12
12
  * whether it renders three components or eighty, and its own build walks 827
13
13
  * files it does not own. Everything else in this file is CSS the components
14
14
  * genuinely need and no app can do without.
15
15
  *
16
- * A CSS `@import` takes the whole file, so the split is two more subpaths:
16
+ * A CSS `@import` takes the whole file, so the split is more subpaths:
17
17
  *
18
- * cursedbelt/styles.css this file — the real CSS + the `@source` scan
19
- * cursedbelt/styles-static.css the same file MINUS the `@source` line
20
- * cursedbelt/styles-utilities.css the utilities that `@source` line would have
21
- * produced, precompiled at cursedbelt's build
18
+ * cursedbelt/styles.css this file — the real CSS + the `@source` scan
19
+ * cursedbelt/styles-static.css the same file MINUS the `@source` line
20
+ * cursedbelt/styles-utilities.css the utilities that `@source` line would have
21
+ * produced, precompiled at cursedbelt's build
22
+ * cursedbelt/styles-areas/<area>.css the same utilities, split by component family
22
23
  *
23
- * The second two are a PAIR and neither is usable alone — `styles-static.css` on
24
- * its own gets the tooltip pointer-events fix, the Progress keyframes, the
25
- * lightbox bridge, the ambient/glass layer and `tw-animate-css`, and emits none
26
- * of cursedbelt's utility classes, i.e. an unstyled app:
24
+ * `styles-static.css` is never usable alone — on its own it gets the tooltip
25
+ * pointer-events fix, the Progress keyframes, the lightbox bridge, the
26
+ * ambient/glass layer and `tw-animate-css`, and emits none of cursedbelt's
27
+ * utility classes, i.e. an unstyled app. It pairs with EITHER of the other two:
27
28
  *
28
- * @import "tailwindcss";
29
- * @import "cursedbelt/styles-static.css";
30
- * @import "cursedbelt/styles-utilities.css";
31
- * @import "cursedbelt/theme.css";
29
+ * @import "tailwindcss"; @import "tailwindcss";
30
+ * @import "cursedbelt/styles-static.css"; @import "cursedbelt/styles-static.css";
31
+ * @import "cursedbelt/styles-utilities.css"; @import "cursedbelt/styles-areas/core.css";
32
+ * @import "cursedbelt/theme.css"; @import "cursedbelt/styles-areas/data-table.css";
33
+ * @import "cursedbelt/theme.css";
34
+ * the whole design system what this app renders
32
35
  *
33
36
  * 🔴 Do NOT hand-list cursedbelt's component files to get the utilities back.
34
37
  * Tailwind v4 resolves every `@source` to a DIRECTORY and scans it recursively —
@@ -38,22 +41,30 @@
38
41
  * into this package's source tree is also a `file:` dependency wearing a
39
42
  * stylesheet's clothes, and it buys nothing: it is the thing the previous
40
43
  * generation's 29-line-per-app arrangement is remembered for, and those 29 lines
41
- * were never file-level scoping either.
44
+ * were never file-level scoping either. Three apps hand-maintained such a list
45
+ * and one of them ran to 173 lines; all three were deleted, because every one of
46
+ * them was a directory list wearing a costume.
42
47
  *
43
- * The pair is not SMALLER than this file — measured, both routes emit the same
44
- * rules within 54 bytes. What it buys is a build that scans nothing of another
45
- * package's source tree, and a `styles-static.css` that finally has a correct
46
- * use. The measurements, and what would actually shrink a consumer, are in the
47
- * header of `scripts/generateUtilityStyles.ts`.
48
+ * `styles-utilities.css` is not SMALLER than this file — measured, both routes
49
+ * emit the same rules within 54 bytes; the union is the union however it is
50
+ * assembled. What it buys is a build that scans nothing of another package's
51
+ * source tree. 🔴 What makes a consumer smaller is the AREAS, and only the
52
+ * package could build them: Tailwind cannot aim a `@source` at anything finer
53
+ * than a directory, but `@tailwindcss/oxide` can be handed a FILE LIST, which is
54
+ * what `scripts/generateAreaStyles.ts` does. Measured on the fixture consumer
55
+ * `demo/fixtures/lean-app`, the area route emits 30% less CSS than the union, and
56
+ * `src/fixtureAppCss.spec.ts` is the ratchet that stops it climbing back. The
57
+ * area map, and a sentence on what each area is for, is `scripts/styleAreas.ts`.
48
58
  *
49
59
  * 🔴 This header is in BOTH of the first two files, because one is generated from
50
60
  * the other and a test holds them byte-identical apart from the `@source` line.
51
61
  * Always edit `src/styles.css`; never `src/styles-static.css`, which
52
62
  * `scripts/generateStaticStyles.ts` rewrites on every `bun run build` and
53
63
  * `src/stylesStaticMatches.spec.ts` fails on when it is stale. Editing this file
54
- * invalidates `src/styles-utilities.css` too — it is compiled against the static
55
- * variant, `scripts/generateUtilityStyles.ts` rewrites it in the same build step,
56
- * and `src/stylesUtilitiesMatches.spec.ts` is what fails when it is stale.
64
+ * invalidates `src/styles-utilities.css` and every `src/styles-areas/*.css` too —
65
+ * all of them are compiled against the static variant, `bun run styles` rewrites
66
+ * them in the same build step, and `src/stylesUtilitiesMatches.spec.ts` /
67
+ * `src/stylesAreas.spec.ts` are what fail when they are stale.
57
68
  */
58
69
 
59
70
  /* Animation utilities (animate-in/out, fade/zoom/slide-in-from-*) consumed by the
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "cursedbelt",
3
- "version": "3.0.2",
3
+ "version": "4.1.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
- "description": "The React design system of the cursedbelt split \u2014 components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
6
+ "description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
7
7
  "cursed": {
8
8
  "schemaVersion": 1,
9
9
  "app": {
@@ -20,30 +20,35 @@
20
20
  }
21
21
  },
22
22
  "scripts": {
23
- "typecheck": "bun run typecheck:root && bun run typecheck:specs",
23
+ "typecheck": "bun run typecheck:root && bun run typecheck:specs && bun run typecheck:scripts",
24
24
  "typecheck:root": "tsc --noEmit",
25
25
  "typecheck:specs": "tsc --noEmit --project tsconfig.specs.json",
26
+ "typecheck:scripts": "tsc --noEmit --project tsconfig.scripts.json",
26
27
  "demo:check": "tsc --noEmit --project demo/tsconfig.json",
27
28
  "demo:paths": "bun run demo/generatePaths.ts --write",
28
29
  "guardrails:report": "bun run scripts/guardrailsEnforce.ts no-raw-action-button,no-raw-overflow,no-raw-inputs,no-hardcoded-colors,no-raw-modal-footer,no-unguarded-editing-modal,control-height-tokens,no-raw-zindex,no-raw-data-table,no-tailwind-palette,no-raw-title-attr,micro-typography-floor,choice-needs-empty-state,no-select-as-action-menu,no-native-dialog,no-fast-tooltip --scope cursedbelt src/react src/styles.css",
29
30
  "format": "biome format --write .",
30
- "styles": "bun run styles:static && bun run styles:utilities",
31
+ "styles": "bun run styles:static && bun run styles:utilities && bun run styles:areas",
31
32
  "styles:static": "bun run scripts/generateStaticStyles.ts",
32
33
  "styles:utilities": "bun run scripts/generateUtilityStyles.ts",
33
- "test": "NODE_ENV=development bun test src",
34
+ "test": "NODE_ENV=development bun test src --parallel=6",
34
35
  "demo": "vite --config demo/vite.config.ts",
35
36
  "demo:build": "vite build --config demo/vite.config.ts",
36
37
  "demo:serve": "bun run demo:build && vite preview --config demo/vite.config.ts",
37
- "build": "rm -rf dist.next dist.prev && bun run scripts/generateStaticStyles.ts && bun run scripts/generateUtilityStyles.ts && tsc --project tsconfig.build.json --outDir dist.next && CURSEDBELT_DIST_DIR=dist.next bun run scripts/copyDistAssets.ts && { [ -d dist ] && mv dist dist.prev || true; } && mv dist.next dist && rm -rf dist.prev",
38
+ "build": "rm -rf dist.next dist.prev && bun run scripts/generateStaticStyles.ts && bun run scripts/generateUtilityStyles.ts && bun run scripts/generateAreaStyles.ts && tsc --project tsconfig.build.json --outDir dist.next && tsc --project tsconfig.scripts.build.json --outDir dist.next/scripts && CURSEDBELT_DIST_DIR=dist.next bun run scripts/copyDistAssets.ts && CURSEDBELT_DIST_DIR=dist.next bun run scripts/checkDistExports.ts &&{ [ -d dist ] && mv dist dist.prev || true; } && mv dist.next dist && rm -rf dist.prev",
38
39
  "e2e": "playwright test --config e2e/playwright.config.ts",
39
40
  "e2e:filter-rail": "playwright test --config e2e/playwright.config.ts filter-rail.spec.ts",
40
41
  "e2e:table-layout": "playwright test --config e2e/playwright.config.ts table-layout.spec.ts",
41
42
  "e2e:stage-layout": "playwright test --config e2e/playwright.config.ts stage-layout.spec.ts",
43
+ "e2e:dialog-reach": "playwright test --config e2e/playwright.config.ts dialog-reach.spec.ts",
44
+ "e2e:video-turn": "playwright test --config e2e/playwright.config.ts video-turn.spec.ts",
45
+ "e2e:resize-grip": "playwright test --config e2e/playwright.config.ts resize-grip-rail.spec.ts",
42
46
  "e2e:touch": "playwright test --config e2e/playwright.config.ts --project=touch",
43
47
  "done:serial": "bun run typecheck && bun run lint && bun run test && bun run build",
44
- "paths": "bun ../../tools/check-paths.ts .",
48
+ "paths": "bun run scripts/paths.ts",
45
49
  "verify": "bun run scripts/verify.ts",
46
- "prepublishOnly": "bun run verify"
50
+ "prepublishOnly": "bun run verify",
51
+ "styles:areas": "bun run scripts/generateAreaStyles.ts"
47
52
  },
48
53
  "exports": {
49
54
  ".": {
@@ -53,7 +58,7 @@
53
58
  "import": "./dist/index.js"
54
59
  },
55
60
  "./guardrails": {
56
- "types": "./scripts/guardrailsEnforce.ts",
61
+ "types": "./dist/scripts/guardrailsEnforce.d.ts",
57
62
  "bun": "./scripts/guardrailsEnforce.ts",
58
63
  "source": "./scripts/guardrailsEnforce.ts",
59
64
  "default": "./scripts/guardrailsEnforce.ts"
@@ -286,6 +291,24 @@
286
291
  "source": "./src/react/components/SelectField.tsx",
287
292
  "import": "./dist/react/components/SelectField.js"
288
293
  },
294
+ "./react/list-selection": {
295
+ "types": "./dist/react/hooks/useListSelection.d.ts",
296
+ "bun": "./src/react/hooks/useListSelection.ts",
297
+ "source": "./src/react/hooks/useListSelection.ts",
298
+ "import": "./dist/react/hooks/useListSelection.js"
299
+ },
300
+ "./react/table-sort": {
301
+ "types": "./dist/react/hooks/useTableSort.d.ts",
302
+ "bun": "./src/react/hooks/useTableSort.ts",
303
+ "source": "./src/react/hooks/useTableSort.ts",
304
+ "import": "./dist/react/hooks/useTableSort.js"
305
+ },
306
+ "./react/multi-select-field": {
307
+ "types": "./dist/react/components/MultiSelectField.d.ts",
308
+ "bun": "./src/react/components/MultiSelectField.tsx",
309
+ "source": "./src/react/components/MultiSelectField.tsx",
310
+ "import": "./dist/react/components/MultiSelectField.js"
311
+ },
289
312
  "./react/sharing": {
290
313
  "types": "./dist/react/sharing/index.d.ts",
291
314
  "bun": "./src/react/sharing/index.ts",
@@ -317,6 +340,49 @@
317
340
  "import": "./dist/react/workbook-viewer/index.js"
318
341
  },
319
342
  "./styles-static.css": "./src/styles-static.css",
343
+ "./styles-areas/activity-bar.css": "./src/styles-areas/activity-bar.css",
344
+ "./styles-areas/analytics.css": "./src/styles-areas/analytics.css",
345
+ "./styles-areas/auth.css": "./src/styles-areas/auth.css",
346
+ "./styles-areas/calendar.css": "./src/styles-areas/calendar.css",
347
+ "./styles-areas/chart-table.css": "./src/styles-areas/chart-table.css",
348
+ "./styles-areas/charts.css": "./src/styles-areas/charts.css",
349
+ "./styles-areas/chat.css": "./src/styles-areas/chat.css",
350
+ "./styles-areas/clipboard.css": "./src/styles-areas/clipboard.css",
351
+ "./styles-areas/code-editor.css": "./src/styles-areas/code-editor.css",
352
+ "./styles-areas/companion-link.css": "./src/styles-areas/companion-link.css",
353
+ "./styles-areas/core.css": "./src/styles-areas/core.css",
354
+ "./styles-areas/dashboard-grid.css": "./src/styles-areas/dashboard-grid.css",
355
+ "./styles-areas/data-table.css": "./src/styles-areas/data-table.css",
356
+ "./styles-areas/deep-link.css": "./src/styles-areas/deep-link.css",
357
+ "./styles-areas/diff-viewer.css": "./src/styles-areas/diff-viewer.css",
358
+ "./styles-areas/disk-usage.css": "./src/styles-areas/disk-usage.css",
359
+ "./styles-areas/emoji.css": "./src/styles-areas/emoji.css",
360
+ "./styles-areas/fields.css": "./src/styles-areas/fields.css",
361
+ "./styles-areas/file-tree.css": "./src/styles-areas/file-tree.css",
362
+ "./styles-areas/filter-rail.css": "./src/styles-areas/filter-rail.css",
363
+ "./styles-areas/folder-tree.css": "./src/styles-areas/folder-tree.css",
364
+ "./styles-areas/keep-awake.css": "./src/styles-areas/keep-awake.css",
365
+ "./styles-areas/layout-engine.css": "./src/styles-areas/layout-engine.css",
366
+ "./styles-areas/markdown.css": "./src/styles-areas/markdown.css",
367
+ "./styles-areas/master-detail.css": "./src/styles-areas/master-detail.css",
368
+ "./styles-areas/master-lock.css": "./src/styles-areas/master-lock.css",
369
+ "./styles-areas/media.css": "./src/styles-areas/media.css",
370
+ "./styles-areas/media-gallery.css": "./src/styles-areas/media-gallery.css",
371
+ "./styles-areas/nav.css": "./src/styles-areas/nav.css",
372
+ "./styles-areas/notifications.css": "./src/styles-areas/notifications.css",
373
+ "./styles-areas/overlays.css": "./src/styles-areas/overlays.css",
374
+ "./styles-areas/palette.css": "./src/styles-areas/palette.css",
375
+ "./styles-areas/pdf-viewer.css": "./src/styles-areas/pdf-viewer.css",
376
+ "./styles-areas/problem-detail.css": "./src/styles-areas/problem-detail.css",
377
+ "./styles-areas/rich-text.css": "./src/styles-areas/rich-text.css",
378
+ "./styles-areas/sharing.css": "./src/styles-areas/sharing.css",
379
+ "./styles-areas/spreadsheet.css": "./src/styles-areas/spreadsheet.css",
380
+ "./styles-areas/stats.css": "./src/styles-areas/stats.css",
381
+ "./styles-areas/test-report.css": "./src/styles-areas/test-report.css",
382
+ "./styles-areas/virtual.css": "./src/styles-areas/virtual.css",
383
+ "./styles-areas/wizard.css": "./src/styles-areas/wizard.css",
384
+ "./styles-areas/workbench.css": "./src/styles-areas/workbench.css",
385
+ "./styles-areas/workbook-viewer.css": "./src/styles-areas/workbook-viewer.css",
320
386
  "./styles-utilities.css": "./src/styles-utilities.css",
321
387
  "./styles.css": "./src/styles.css",
322
388
  "./testing": {
@@ -350,10 +416,14 @@
350
416
  "@codemirror/legacy-modes": ">=6",
351
417
  "@codemirror/state": ">=6",
352
418
  "@codemirror/view": ">=6",
353
- "@lezer/highlight": ">=1",
419
+ "@emoji-mart/data": ">=1",
420
+ "@emoji-mart/react": ">=1",
354
421
  "@ffmpeg/ffmpeg": ">=0.12",
355
422
  "@ffmpeg/util": ">=0.12",
423
+ "@git-diff-view/react": ">=0.1.6",
356
424
  "@glideapps/glide-data-grid": ">=6",
425
+ "@hookform/resolvers": ">=5",
426
+ "@lezer/highlight": ">=1",
357
427
  "@tanstack/react-query": ">=5",
358
428
  "@testing-library/react": ">=16",
359
429
  "@tiptap/core": ">=3",
@@ -366,17 +436,21 @@
366
436
  "@tiptap/react": ">=3",
367
437
  "@tiptap/starter-kit": ">=3",
368
438
  "@vidstack/react": ">=1",
439
+ "diff": ">=9",
440
+ "emoji-mart": ">=5",
369
441
  "hls.js": ">=1",
370
442
  "lowlight": ">=3",
443
+ "marked": ">=14",
444
+ "pdfjs-dist": ">=4",
371
445
  "react": "^19",
372
446
  "react-dom": "^19",
447
+ "react-grid-layout": "2.2.3",
373
448
  "react-photo-album": ">=3",
374
449
  "recharts": ">=3",
375
450
  "tiptap-markdown": ">=0.9",
451
+ "xlsx": ">=0.20",
376
452
  "yet-another-react-lightbox": ">=3",
377
- "zod": "^4.4.0",
378
- "pdfjs-dist": ">=4",
379
- "xlsx": ">=0.20"
453
+ "zod": "^4.4.0"
380
454
  },
381
455
  "peerDependenciesMeta": {
382
456
  "@codemirror/commands": {
@@ -406,7 +480,10 @@
406
480
  "@codemirror/view": {
407
481
  "optional": true
408
482
  },
409
- "@lezer/highlight": {
483
+ "@emoji-mart/data": {
484
+ "optional": true
485
+ },
486
+ "@emoji-mart/react": {
410
487
  "optional": true
411
488
  },
412
489
  "@ffmpeg/ffmpeg": {
@@ -415,9 +492,18 @@
415
492
  "@ffmpeg/util": {
416
493
  "optional": true
417
494
  },
495
+ "@git-diff-view/react": {
496
+ "optional": true
497
+ },
418
498
  "@glideapps/glide-data-grid": {
419
499
  "optional": true
420
500
  },
501
+ "@hookform/resolvers": {
502
+ "optional": true
503
+ },
504
+ "@lezer/highlight": {
505
+ "optional": true
506
+ },
421
507
  "@testing-library/react": {
422
508
  "optional": true
423
509
  },
@@ -451,29 +537,41 @@
451
537
  "@vidstack/react": {
452
538
  "optional": true
453
539
  },
540
+ "diff": {
541
+ "optional": true
542
+ },
543
+ "emoji-mart": {
544
+ "optional": true
545
+ },
454
546
  "hls.js": {
455
547
  "optional": true
456
548
  },
457
549
  "lowlight": {
458
550
  "optional": true
459
551
  },
460
- "react-photo-album": {
552
+ "marked": {
461
553
  "optional": true
462
554
  },
463
- "recharts": {
555
+ "pdfjs-dist": {
464
556
  "optional": true
465
557
  },
466
- "tiptap-markdown": {
558
+ "react-grid-layout": {
467
559
  "optional": true
468
560
  },
469
- "yet-another-react-lightbox": {
561
+ "react-photo-album": {
470
562
  "optional": true
471
563
  },
472
- "pdfjs-dist": {
564
+ "recharts": {
565
+ "optional": true
566
+ },
567
+ "tiptap-markdown": {
473
568
  "optional": true
474
569
  },
475
570
  "xlsx": {
476
571
  "optional": true
572
+ },
573
+ "yet-another-react-lightbox": {
574
+ "optional": true
477
575
  }
478
576
  },
479
577
  "devDependencies": {
@@ -490,12 +588,16 @@
490
588
  "@codemirror/search": "^6.7.1",
491
589
  "@codemirror/state": "^6.7.0",
492
590
  "@codemirror/view": "^6.43.4",
591
+ "@emoji-mart/data": "^1.2.1",
592
+ "@emoji-mart/react": "^1.1.1",
493
593
  "@ffmpeg/ffmpeg": "^0.12.15",
494
594
  "@ffmpeg/util": "^0.12.2",
595
+ "@git-diff-view/react": "^0.1.6",
495
596
  "@glideapps/glide-data-grid": "^6.0.3",
496
597
  "@happy-dom/global-registrator": "^20.10.6",
598
+ "@hookform/resolvers": "^5.4.0",
497
599
  "@lezer/highlight": "^1.2.3",
498
- "@playwright/test": "^1.61.0",
600
+ "@playwright/test": "1.62.1",
499
601
  "@tailwindcss/node": "^4.3.1",
500
602
  "@tailwindcss/oxide": "^4.3.1",
501
603
  "@tailwindcss/vite": "^4.3.1",
@@ -520,12 +622,17 @@
520
622
  "@types/react-dom": "^19.2.3",
521
623
  "@vidstack/react": "1.15.6",
522
624
  "@vitejs/plugin-react": "^5.2.0",
625
+ "cursedbelt-server": "^1.0.1",
626
+ "diff": "^9.0.0",
627
+ "emoji-mart": "^5.6.0",
523
628
  "hls.js": "^1.5.0",
524
629
  "hono": "4.12.28",
525
630
  "lowlight": "^3.3.0",
631
+ "marked": "^14.0.0",
526
632
  "pdfjs-dist": "^4.10.38",
527
633
  "react": "19.2.7",
528
634
  "react-dom": "19.2.7",
635
+ "react-grid-layout": "2.2.3",
529
636
  "react-photo-album": "^3.6.0",
530
637
  "recharts": "^3.8.1",
531
638
  "tailwindcss": "^4.3.1",
@@ -534,8 +641,7 @@
534
641
  "vite": "^7.3.6",
535
642
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
536
643
  "yet-another-react-lightbox": "^3.32.0",
537
- "zod": "4.4.3",
538
- "cursedbelt-server": "^1.0.1"
644
+ "zod": "4.4.3"
539
645
  },
540
646
  "dependencies": {
541
647
  "@atlaskit/pragmatic-drag-and-drop": "^2.0.1",
@@ -543,11 +649,7 @@
543
649
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.0.0",
544
650
  "@base-ui/react": "^1.6.0",
545
651
  "@daypicker/react": "^10.0.1",
546
- "@emoji-mart/data": "^1.2.1",
547
- "@emoji-mart/react": "^1.1.1",
548
652
  "@floating-ui/react": "^0.27.19",
549
- "@git-diff-view/react": "^0.1.6",
550
- "@hookform/resolvers": "^5.4.0",
551
653
  "@radix-ui/react-accordion": "^1.2.14",
552
654
  "@radix-ui/react-checkbox": "^1.3.5",
553
655
  "@radix-ui/react-collapsible": "^1.1.14",
@@ -568,19 +670,15 @@
568
670
  "class-variance-authority": "^0.7.1",
569
671
  "clsx": "^2.1.1",
570
672
  "cmdk": "^1.1.1",
673
+ "cursedbelt-core": "^1.1.0",
571
674
  "cwip": "^3.0.2",
572
- "diff": "^9.0.0",
573
- "emoji-mart": "^5.6.0",
574
675
  "lucide-react": "^1.21.0",
575
- "marked": "^14.0.0",
576
- "react-grid-layout": "2.2.3",
577
676
  "react-hook-form": "^7.80.0",
578
677
  "react-resizable-panels": "^4.12.0",
579
678
  "sonner": "^2.0.7",
580
679
  "tailwind-merge": "^3.6.0",
581
680
  "tw-animate-css": "^1.4.0",
582
- "zustand": "^5.0.14",
583
- "cursedbelt-core": "^1.1.0"
681
+ "zustand": "^5.0.14"
584
682
  },
585
683
  "publishConfig": {
586
684
  "access": "public"
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Every `exports` target that points into `dist/` must EXIST in the build that just ran.
4
+ *
5
+ * ── Why this is a build step and not a spec ────────────────────────────────────────
6
+ * A missing target does not throw. Node and tsc walk the conditions in order and take the
7
+ * first one that matches an ACTIVE condition — but a `types` (or `import`) target whose
8
+ * file is absent makes the resolver fall through to the next condition, which for this
9
+ * package is `bun`/`source`/`default`: the raw `.ts`. So an export that stops being built
10
+ * silently reverts to the exact defect task 172 fixed — a consumer's `tsc` compiling our
11
+ * source again — and every test in this repo stays green while it happens.
12
+ *
13
+ * It has to run inside `build`, against `CURSEDBELT_DIST_DIR`, because `dist/` is the one
14
+ * thing no spec here may read: `build` runs BESIDE `test` and `e2e` in the verify graph
15
+ * (`src/verifyGraph.spec.ts` asserts no spec opens `dist/`), so a spec looking at it would
16
+ * be reading a directory another stage is in the middle of swapping.
17
+ *
18
+ * Declarations are checked the same as runtime files on purpose — a `.d.ts` that was not
19
+ * emitted is not a smaller problem than a `.js` that was not emitted; it is a quieter one.
20
+ */
21
+ import path from "node:path";
22
+
23
+ const root = path.resolve(import.meta.dir, "..");
24
+ const outDir = process.env.CURSEDBELT_DIST_DIR || "dist";
25
+
26
+ const pkg = (await Bun.file(path.join(root, "package.json")).json()) as {
27
+ exports: Record<string, string | Record<string, string>>;
28
+ };
29
+
30
+ /** `[subpath, condition, target]` for every export target inside `dist/`. */
31
+ const distTargets: [string, string, string][] = [];
32
+ for (const [subpath, entry] of Object.entries(pkg.exports)) {
33
+ const conditions = typeof entry === "string" ? { default: entry } : entry;
34
+ for (const [condition, target] of Object.entries(conditions)) {
35
+ if (typeof target === "string" && target.startsWith("./dist/")) {
36
+ distTargets.push([subpath, condition, target]);
37
+ }
38
+ }
39
+ }
40
+
41
+ // A manifest this script cannot read is a manifest it cannot check; passing silently on
42
+ // zero targets would make the whole step decorative.
43
+ if (distTargets.length === 0) {
44
+ console.error("🔴 no exports target resolves into dist/ — checkDistExports is checking nothing.");
45
+ process.exit(1);
46
+ }
47
+
48
+ const missing: string[] = [];
49
+ for (const [subpath, condition, target] of distTargets) {
50
+ // `./dist/react/index.js` → `<outDir>/react/index.js`, so a staged build (dist.next)
51
+ // is checked where it actually is rather than where it will be after the swap.
52
+ const staged = path.join(root, outDir, target.slice("./dist/".length));
53
+ if (!(await Bun.file(staged).exists())) {
54
+ missing.push(`${subpath} [${condition}] → ${target} (not emitted at ${outDir}/${target.slice(7)})`);
55
+ }
56
+ }
57
+
58
+ if (missing.length > 0) {
59
+ console.error(
60
+ `\n🔴 the build did not emit ${missing.length} file(s) package.json exports promise:\n ${missing.join("\n ")}\n\n` +
61
+ "A consumer does not get an error for these — the resolver falls through to the next\n" +
62
+ "condition, which is our raw `.ts`, and the consumer's tsc compiles our source instead.\n",
63
+ );
64
+ process.exit(1);
65
+ }
66
+
67
+ console.log(`✅ all ${distTargets.length} dist export target(s) present in ${outDir}`);
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Compare two compiled stylesheets by the RULES they emit, not by their bytes.
3
+ *
4
+ * Every split this package has made to its CSS — `styles-static.css`,
5
+ * `styles-utilities.css`, and now `styles-areas/*.css` — can fail in exactly one
6
+ * way that matters: the new route emits FEWER rules than the old one, so some
7
+ * component renders unstyled in the apps that adopted it. That failure is
8
+ * invisible to `tsc` (it is CSS), to Biome (nothing is wrong with the file), and
9
+ * to a byte count read in the only direction anybody reads it, because a smaller
10
+ * stylesheet is what the whole exercise was asked for.
11
+ *
12
+ * So the specs compare rule sets. These helpers were written inside
13
+ * `src/stylesUtilitiesMatches.spec.ts` and live here now because
14
+ * `src/stylesAreas.spec.ts` needs the same comparison against the same two
15
+ * normalisations — a second copy of a CSS flattener is a second copy that can
16
+ * drift from the assertions relying on it.
17
+ */
18
+
19
+ /**
20
+ * Flatten a stylesheet into `at-rule ‖ … ‖ selector` → the set of declarations in
21
+ * that block, merging blocks that repeat (both routes emit `@keyframes spin`
22
+ * twice; a consumer's browser does not care, and neither should a comparison).
23
+ *
24
+ * A hand-rolled walk rather than a CSS parser because the only input is Tailwind's
25
+ * own output: well-formed, and the one construct that could confuse brace counting
26
+ * — a `{` inside a string or a comment — is skipped explicitly.
27
+ */
28
+ export const flatten = (css: string): Map<string, Set<string>> => {
29
+ const out = new Map<string, Set<string>>();
30
+ const stack: string[] = [];
31
+ const open: string[][] = [[]];
32
+ let buf = '';
33
+ const push = (raw: string): void => {
34
+ const decl = raw.trim();
35
+ if (decl) open[open.length - 1]?.push(decl);
36
+ };
37
+ for (let i = 0; i < css.length; i++) {
38
+ const ch = css[i];
39
+ if (ch === '/' && css[i + 1] === '*') {
40
+ i = css.indexOf('*/', i) + 1;
41
+ continue;
42
+ }
43
+ if (ch === '"' || ch === "'") {
44
+ const end = css.indexOf(ch, i + 1);
45
+ buf += css.slice(i, end + 1);
46
+ i = end;
47
+ continue;
48
+ }
49
+ if (ch === '{') {
50
+ stack.push(buf.trim().replace(/\s+/g, ' '));
51
+ buf = '';
52
+ open.push([]);
53
+ continue;
54
+ }
55
+ if (ch === '}') {
56
+ push(buf);
57
+ buf = '';
58
+ const key = stack.join(' ‖ ');
59
+ const set = out.get(key) ?? new Set<string>();
60
+ for (const decl of open.pop() ?? []) set.add(decl);
61
+ out.set(key, set);
62
+ stack.pop();
63
+ continue;
64
+ }
65
+ // At depth 0 a `;` ends a statement at-rule (`@layer a, b;`, `@import …`),
66
+ // which carries no declarations and must not stick to the next selector.
67
+ if (ch === ';') {
68
+ if (stack.length) push(buf);
69
+ buf = '';
70
+ continue;
71
+ }
72
+ buf += ch;
73
+ }
74
+ return out;
75
+ };
76
+
77
+ /** `var(--x, <anything>)` → `var(--x)`, so a fallback reads as the same declaration. */
78
+ export const dropFallbacks = (decl: string): string => decl.replace(/var\((\s*--[\w-]+)[^)]*\)/g, 'var($1)');
79
+
80
+ export const CUSTOM_PROPERTY = /^(--[\w-]+)\s*:/;
81
+
82
+ /** Every custom property a stylesheet actually reads through `var()`. */
83
+ export const referenced = (css: string): Set<string> => {
84
+ const out = new Set<string>();
85
+ for (const m of css.matchAll(/var\(\s*(--[\w-]+)/g)) out.add(m[1] as string);
86
+ return out;
87
+ };
88
+
89
+ /** Every custom property a stylesheet declares, including `@property` registrations. */
90
+ export const declared = (css: string): Set<string> => {
91
+ const out = new Set<string>();
92
+ for (const m of css.matchAll(/(?:^|[;{\s])(--[\w-]+)\s*:/g)) out.add(m[1] as string);
93
+ for (const m of css.matchAll(/@property\s+(--[\w-]+)/g)) out.add(m[1] as string);
94
+ return out;
95
+ };
96
+
97
+ /**
98
+ * Rules and declarations `before` emits that `after` does not — the whole point.
99
+ *
100
+ * `ignoreProperty` is how a caller excuses the `@theme inline` alias declarations
101
+ * that one route writes into `:root` and nothing ever reads; everything else is a
102
+ * real difference.
103
+ */
104
+ export const lostRules = (
105
+ before: Map<string, Set<string>>,
106
+ after: Map<string, Set<string>>,
107
+ ignoreProperty: (prop: string) => boolean = () => false,
108
+ ): { rules: string[]; declarations: string[] } => {
109
+ const rules: string[] = [];
110
+ const declarations: string[] = [];
111
+ for (const [key, decls] of before) {
112
+ const mine = after.get(key);
113
+ if (!mine) {
114
+ rules.push(key);
115
+ continue;
116
+ }
117
+ const theirs = new Set([...mine].map(dropFallbacks));
118
+ for (const decl of decls) {
119
+ const prop = CUSTOM_PROPERTY.exec(decl)?.[1];
120
+ if (prop && ignoreProperty(prop)) continue;
121
+ if (!theirs.has(dropFallbacks(decl))) declarations.push(`${key} → ${decl}`);
122
+ }
123
+ }
124
+ return { rules, declarations };
125
+ };