@epilot/volt-ui-experimental-griffel 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/ADOPTING.md +416 -0
  2. package/README.md +32 -31
  3. package/dist/_virtual/jsx-dev-runtime.js +5 -0
  4. package/dist/_virtual/jsx-dev-runtime.js.map +1 -0
  5. package/dist/_virtual/react-jsx-dev-runtime.development.js +5 -0
  6. package/dist/_virtual/react-jsx-dev-runtime.development.js.map +1 -0
  7. package/dist/_virtual/react-jsx-dev-runtime.production.js +5 -0
  8. package/dist/_virtual/react-jsx-dev-runtime.production.js.map +1 -0
  9. package/dist/components/accordion/accordion.js +126 -66
  10. package/dist/components/accordion/accordion.js.map +1 -1
  11. package/dist/components/alert-dialog/alert-dialog.js +239 -137
  12. package/dist/components/alert-dialog/alert-dialog.js.map +1 -1
  13. package/dist/components/avatar/avatar-group.js +102 -47
  14. package/dist/components/avatar/avatar-group.js.map +1 -1
  15. package/dist/components/avatar/avatar.js +97 -73
  16. package/dist/components/avatar/avatar.js.map +1 -1
  17. package/dist/components/badge/badge.js +53 -37
  18. package/dist/components/badge/badge.js.map +1 -1
  19. package/dist/components/breadcrumb/breadcrumb.js +97 -49
  20. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  21. package/dist/components/button/button.js +88 -77
  22. package/dist/components/button/button.js.map +1 -1
  23. package/dist/components/button-group/button-group.js +55 -31
  24. package/dist/components/button-group/button-group.js.map +1 -1
  25. package/dist/components/callout/callout.js +288 -164
  26. package/dist/components/callout/callout.js.map +1 -1
  27. package/dist/components/card/card.js +137 -81
  28. package/dist/components/card/card.js.map +1 -1
  29. package/dist/components/checkbox/checkbox.js +100 -49
  30. package/dist/components/checkbox/checkbox.js.map +1 -1
  31. package/dist/components/collapsible-sidebar/collapsible-sidebar.js +155 -107
  32. package/dist/components/collapsible-sidebar/collapsible-sidebar.js.map +1 -1
  33. package/dist/components/command/command.js +297 -158
  34. package/dist/components/command/command.js.map +1 -1
  35. package/dist/components/data-table/data-table-body.js +114 -54
  36. package/dist/components/data-table/data-table-body.js.map +1 -1
  37. package/dist/components/data-table/data-table-column-header.js +117 -45
  38. package/dist/components/data-table/data-table-column-header.js.map +1 -1
  39. package/dist/components/data-table/data-table-column-visibility.js +98 -39
  40. package/dist/components/data-table/data-table-column-visibility.js.map +1 -1
  41. package/dist/components/data-table/data-table-content.js +90 -58
  42. package/dist/components/data-table/data-table-content.js.map +1 -1
  43. package/dist/components/data-table/data-table-error.js +83 -39
  44. package/dist/components/data-table/data-table-error.js.map +1 -1
  45. package/dist/components/data-table/data-table-header.js +88 -54
  46. package/dist/components/data-table/data-table-header.js.map +1 -1
  47. package/dist/components/data-table/data-table-loading.js +27 -15
  48. package/dist/components/data-table/data-table-loading.js.map +1 -1
  49. package/dist/components/data-table/data-table-pagination.js +27 -19
  50. package/dist/components/data-table/data-table-pagination.js.map +1 -1
  51. package/dist/components/data-table/data-table-row.js +87 -64
  52. package/dist/components/data-table/data-table-row.js.map +1 -1
  53. package/dist/components/data-table/data-table-toolbar.js +24 -16
  54. package/dist/components/data-table/data-table-toolbar.js.map +1 -1
  55. package/dist/components/data-table/data-table.js +182 -145
  56. package/dist/components/data-table/data-table.js.map +1 -1
  57. package/dist/components/date-range-picker/date-range-picker.js +342 -214
  58. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  59. package/dist/components/date-range-picker/range-calendar.js +132 -92
  60. package/dist/components/date-range-picker/range-calendar.js.map +1 -1
  61. package/dist/components/date-time-picker/calendar.js +501 -317
  62. package/dist/components/date-time-picker/calendar.js.map +1 -1
  63. package/dist/components/date-time-picker/date-time-picker-context.js +64 -60
  64. package/dist/components/date-time-picker/date-time-picker-context.js.map +1 -1
  65. package/dist/components/date-time-picker/date-time-picker-input.js +249 -149
  66. package/dist/components/date-time-picker/date-time-picker-input.js.map +1 -1
  67. package/dist/components/date-time-picker/date-time-picker-panel.js +273 -165
  68. package/dist/components/date-time-picker/date-time-picker-panel.js.map +1 -1
  69. package/dist/components/date-time-picker/date-time-picker-popover.js +22 -14
  70. package/dist/components/date-time-picker/date-time-picker-popover.js.map +1 -1
  71. package/dist/components/date-time-picker/date-time-picker.js +134 -90
  72. package/dist/components/date-time-picker/date-time-picker.js.map +1 -1
  73. package/dist/components/date-time-picker/time-picker.js +198 -132
  74. package/dist/components/date-time-picker/time-picker.js.map +1 -1
  75. package/dist/components/density-provider/density-provider.js +48 -0
  76. package/dist/components/density-provider/density-provider.js.map +1 -0
  77. package/dist/components/dialog/dialog.js +280 -150
  78. package/dist/components/dialog/dialog.js.map +1 -1
  79. package/dist/components/drawer/drawer.js +273 -163
  80. package/dist/components/drawer/drawer.js.map +1 -1
  81. package/dist/components/dropdown-menu/dropdown-menu.js +378 -195
  82. package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
  83. package/dist/components/field/field-combobox.js +447 -240
  84. package/dist/components/field/field-combobox.js.map +1 -1
  85. package/dist/components/field/field-select.js +23 -19
  86. package/dist/components/field/field-select.js.map +1 -1
  87. package/dist/components/field/field.js +318 -209
  88. package/dist/components/field/field.js.map +1 -1
  89. package/dist/components/label/label.js +27 -19
  90. package/dist/components/label/label.js.map +1 -1
  91. package/dist/components/list/list.js +623 -369
  92. package/dist/components/list/list.js.map +1 -1
  93. package/dist/components/option-card/option-card.js +323 -159
  94. package/dist/components/option-card/option-card.js.map +1 -1
  95. package/dist/components/pill/pill.js +82 -64
  96. package/dist/components/pill/pill.js.map +1 -1
  97. package/dist/components/popover/popover.js +101 -56
  98. package/dist/components/popover/popover.js.map +1 -1
  99. package/dist/components/radio/radio.js +94 -51
  100. package/dist/components/radio/radio.js.map +1 -1
  101. package/dist/components/scroll-overflow/scroll-overflow.js +350 -198
  102. package/dist/components/scroll-overflow/scroll-overflow.js.map +1 -1
  103. package/dist/components/segmented-control/segmented-control.js +141 -97
  104. package/dist/components/segmented-control/segmented-control.js.map +1 -1
  105. package/dist/components/select/select.js +452 -227
  106. package/dist/components/select/select.js.map +1 -1
  107. package/dist/components/separator/separator.js +24 -16
  108. package/dist/components/separator/separator.js.map +1 -1
  109. package/dist/components/skeleton/skeleton.js +83 -43
  110. package/dist/components/skeleton/skeleton.js.map +1 -1
  111. package/dist/components/spinner/spinner.js +44 -20
  112. package/dist/components/spinner/spinner.js.map +1 -1
  113. package/dist/components/switch/switch.js +163 -86
  114. package/dist/components/switch/switch.js.map +1 -1
  115. package/dist/components/table/table-pagination.js +465 -205
  116. package/dist/components/table/table-pagination.js.map +1 -1
  117. package/dist/components/table/table.js +212 -144
  118. package/dist/components/table/table.js.map +1 -1
  119. package/dist/components/tabs/tabs.js +94 -58
  120. package/dist/components/tabs/tabs.js.map +1 -1
  121. package/dist/components/text/text.js +40 -32
  122. package/dist/components/text/text.js.map +1 -1
  123. package/dist/components/toast/toast.js +187 -111
  124. package/dist/components/toast/toast.js.map +1 -1
  125. package/dist/components/toggle/toggle-group.js +40 -24
  126. package/dist/components/toggle/toggle-group.js.map +1 -1
  127. package/dist/components/toggle/toggle.js +48 -40
  128. package/dist/components/toggle/toggle.js.map +1 -1
  129. package/dist/components/tooltip/tooltip.js +101 -55
  130. package/dist/components/tooltip/tooltip.js.map +1 -1
  131. package/dist/index.d.ts +600 -427
  132. package/dist/index.js +215 -212
  133. package/dist/index.js.map +1 -1
  134. package/dist/lib/clear-button.js +27 -15
  135. package/dist/lib/clear-button.js.map +1 -1
  136. package/dist/lib/icons.js +18 -6
  137. package/dist/lib/icons.js.map +1 -1
  138. package/dist/packages/volt-runtime/dist/sheet.js +27 -10
  139. package/dist/packages/volt-runtime/dist/sheet.js.map +1 -1
  140. package/dist/packages/volt-runtime/dist/styles/recipe.js +76 -0
  141. package/dist/packages/volt-runtime/dist/styles/recipe.js.map +1 -0
  142. package/dist/styles/document-context.js +12 -0
  143. package/dist/styles/document-context.js.map +1 -0
  144. package/dist/styles/make-styles.js +18 -10
  145. package/dist/styles/make-styles.js.map +1 -1
  146. package/dist/tokens/core.generated.js +1238 -1166
  147. package/dist/tokens/core.generated.js.map +1 -1
  148. package/dist/tokens/index.d.ts +442 -415
  149. package/dist/tokens/index.js +1105 -1078
  150. package/dist/tokens/index.js.map +1 -1
  151. package/dist/tokens/install.js +9 -5
  152. package/dist/tokens/install.js.map +1 -1
  153. package/dist/tokens/use-palette-tokens.js +20 -0
  154. package/dist/tokens/use-palette-tokens.js.map +1 -0
  155. package/dist/tokens.json +83 -2
  156. package/dist/vendor/react/cjs/react-jsx-dev-runtime.development.js +236 -0
  157. package/dist/vendor/react/cjs/react-jsx-dev-runtime.development.js.map +1 -0
  158. package/dist/vendor/react/cjs/react-jsx-dev-runtime.production.js +21 -0
  159. package/dist/vendor/react/cjs/react-jsx-dev-runtime.production.js.map +1 -0
  160. package/dist/vendor/react/jsx-dev-runtime.js +9 -0
  161. package/dist/vendor/react/jsx-dev-runtime.js.map +1 -0
  162. package/package.json +13 -5
  163. package/scripts/migration-diagnostics-core.mjs +859 -0
  164. package/scripts/migration-diagnostics.mjs +353 -0
  165. package/scripts/migration-sweep.mjs +179 -0
  166. package/AGENTS.md +0 -153
  167. package/AUTHORING.md +0 -292
  168. package/dist/packages/volt-runtime/dist/styles/make-styles.js +0 -22
  169. package/dist/packages/volt-runtime/dist/styles/make-styles.js.map +0 -1
package/AUTHORING.md DELETED
@@ -1,292 +0,0 @@
1
- # Authoring volt-ui-v2 — what went wrong, and why the rules exist
2
-
3
- `AGENTS.md` is the brief: what to do, and it is normative. This is the reasoning: what broke, how it was found,
4
- and what it cost. Every item here is a defect that actually shipped into the branch, and most of them
5
- passed source review, a green unit suite and a source-level audit before a rendering comparison caught
6
- them.
7
-
8
- Read this before changing a component or adding one. The single most useful thing in it is the pattern
9
- at the end: **the same class of mistake recurred four times, and it was never caught by reading.**
10
-
11
- ---
12
-
13
- ## 1. The engine — four Griffel constraints, all of which fail silently
14
-
15
- ### 1.1 `&` must be the first token of a selector
16
-
17
- Writing `"[data-pressed] &"` reads as "style me when an ancestor is pressed". Griffel does not
18
- substitute `&` in that position; it prepends its own class to the whole selector and emits:
19
-
20
- ```css
21
- .f1y9ypwo[data-pressed] .f1y9ypwo {
22
- opacity: 1;
23
- }
24
- ```
25
-
26
- That needs one element to carry the class AND be pressed AND contain a second element with the same
27
- class. It never matches. No warning, no error, no failing test.
28
-
29
- **Cost: fifteen dead declarations across six components**, every one a feature that silently did not
30
- work — all four of Toast's type colours (every toast looked identical), OptionCard's six
31
- selected-state glyphs, Label's disabled dimming, Select's chevron rotation.
32
-
33
- Only OptionCard's was caught by the visual gate, because it was the only one with a specimen that
34
- exercised the state. `src/ancestor-selector.test.ts` now forbids the form, and reads source for
35
- exactly that reason.
36
-
37
- **Instead:** declare the rule on the slot of the element that carries the state, and reach the
38
- descendant by `SLOT_ATTR`:
39
-
40
- ```ts
41
- [`&[data-pressed] [${SLOT_ATTR}="option-card-radio-dot"]`]: { opacity: 1 }
42
- ```
43
-
44
- If the ancestor belongs to another component, the rule belongs in that component — Label's disabled
45
- rules live in `field.tsx`. **And there is a real capability loss here:** v1 expresses these with
46
- Tailwind's `group-data-[…]:` and `peer-disabled:`, which work only because Tailwind emits _global_
47
- utility classes that can be rooted anywhere. Scoped atomic CSS cannot. A Label used outside a Field,
48
- next to a disabled input, will not dim.
49
-
50
- ### 1.2 Declaration order inside a slot encodes precedence
51
-
52
- Griffel emits atoms in source order, so a later state selector beats an earlier one. **v1's precedence
53
- does not come from order** — it comes from Tailwind's utility layer, where `aria-invalid:` beats
54
- `disabled:` no matter where either sits in the class string.
55
-
56
- Transcribing v1's class order therefore inverts the result. Checkbox and Radio both put
57
- `&[aria-invalid='true']` before `&[data-disabled]`, so a disabled invalid control lost its error
58
- border to the gray one: 17% and 44% pixel differences on a 16×16 box.
59
-
60
- **Put the state that must win last.**
61
-
62
- ### 1.3 Dedup only happens when the expanded property keys match
63
-
64
- OptionCard's card is a `ToggleGroupItem`. Toggle declares `shorthands.paddingInline`; the card
65
- declared `paddingLeft`/`paddingRight`. Different keys, so both survived and the cascade kept Toggle's
66
- value — the padding stayed 4px-per-side wrong _after the value itself had been corrected_, which is a
67
- confusing thing to debug.
68
-
69
- Match the neighbour's property shape, not just its value.
70
-
71
- ### 1.4 A typo'd slot name used to compile
72
-
73
- `makeVoltStyles` was generic over the key union:
74
-
75
- ```
76
- makeVoltStyles<Slots extends string>(styles: Record<Slots, GriffelStyle>): () => Record<Slots, string>
77
- ```
78
-
79
- Inferring a type parameter from `Record<Slots, …>` against an object literal **widens `Slots` to
80
- `string`**, so the result was `Record<string, string>` and every slot name typechecked — including
81
- ones that did not exist.
82
-
83
- OptionCard read `styles.card` and `styles[layout]` with neither declared. The component rendered with
84
- **no card styling at all** — v1's card properties had been copied onto the `group` slot and applied to
85
- the wrong element. `tsc` clean, 322 tests green.
86
-
87
- Now generic over the object, so `keyof S` is the real union. It found exactly three errors, all in
88
- OptionCard: the hole was library-wide, the exploitation was not.
89
-
90
- ---
91
-
92
- ## 2. Shipping no reset — v2 inherits nothing v1 got for free
93
-
94
- v1's components were never written against a reset either. They inherited whatever the _element Radix
95
- happened to render_ got from the browser's UA stylesheet. Base UI renders different elements, so v2
96
- inherits nothing.
97
-
98
- **Checkbox rendered 2px wide.** Base UI's root is a `<span>`: `display: inline`, where width and
99
- height **do not apply at all**. v1's Radix root is a `<button>`, which the UA sheet gives
100
- `inline-block` and `border-box`. v1's own class list declares neither.
101
-
102
- Two further subtleties from the same fix:
103
-
104
- - `inline-block` specifically, not `inline-flex`. The box is 16×16 either way, but an inline-flex box
105
- takes its baseline from its first flex item and sat the line box 2px taller.
106
- - Declare `boxSizing` where the element type changed, but **not library-wide**. Where v1 also renders
107
- a plain div, both arms are already `content-box` and agree — adding `border-box` there would
108
- _create_ a difference. 26 slots across 17 components set a size and a border; only the ones the
109
- visual gate flagged were actually wrong.
110
-
111
- **The reset does exist in production, but do not rely on it.** `epilot360-root-config` imports
112
- `tailwindcss/preflight.css` globally at the single-spa root. So `box-sizing: border-box` is there for
113
- an MFE inside the portal — and absent for one bundled standalone. Explicit declarations are no-ops in
114
- the first case and load-bearing in the second.
115
-
116
- ---
117
-
118
- ## 3. The primitive layer is Radix — and the Base UI detour is why
119
-
120
- **Historical. v2 uses Radix, at v1's exact pins.** Nothing in this section is current guidance; it is
121
- here because the detour cost real time and the reasoning generalises. `src/lib/auto-portal.tsx`, the
122
- `InsideMenuGroupContext` wrapper and `collectItemLabels` are all deleted — if you find a reference to
123
- them, it is stale.
124
-
125
- v2 was first built on Base UI and rolled back. The measured case against it:
126
-
127
- - **2.1x the size** for the same primitive — tooltip 33.9k vs 16.8k gzip.
128
- - Its shipped types **forced every consumer onto `@types/react` 19**: 312 errors under 18, none of them
129
- in our own logic. It was the sole source of that requirement.
130
- - It needed wrappers Radix does not — `<Dialog.Portal>` is mandatory, `DropdownMenuLabel` must sit
131
- inside a `Menu.Group` — so **every overlay in the package rendered nothing**: nine specimens, blank
132
- pages, while 321 tests and four isolation gates stayed green. The package's own tests used the
133
- explicit composition Base UI documents, which is exactly why they never hit it.
134
-
135
- Two lessons that outlive the decision:
136
-
137
- - **Silent acceptance of an unsupported option.** `toast("…", { duration: Infinity })` was accepted,
138
- did nothing, and reported nothing; all four toast specimens rendered a bare viewport.
139
- - **Strictness is not uniformly one way.** v1 **throws** on `OptionCardGroup` without a `type` prop
140
- where v2 tolerates it silently — so "v2 is more permissive" is not a safe generalisation, and neither
141
- is its opposite.
142
-
143
- Radix state attributes are what the code uses now: `data-state="checked" | "on" | "open" | "closed"`,
144
- and enter/exit transitions key on `data-state` rather than on separate start/end hooks.
145
-
146
- ---
147
-
148
- ## 4. Rule 0 means v1's OUTPUT, not v1's source
149
-
150
- This is the subtlest thing in the package, and it cuts both ways.
151
-
152
- **Four v1 production bugs were found here, all one family — the prefixer rewrites definitions and
153
- misses references:**
154
-
155
- 1. `--color-level-2`/`-3` point at `--volt-level-2`/`-3`, which nothing defines.
156
- 2. `.volt-text-xs` resolves `line-height: var(--volt-leading, var(--text-xs--line-height))` — the
157
- definition ships prefixed, the reference does not, and the unprefixed name is defined nowhere. So
158
- **every text utility in v1 computes an invalid line-height and inherits instead.**
159
- 3. Label's `leading-none` never reaches the DOM at all: v1's `cn()` runs tailwind-merge, which treats
160
- the later `text-sm` as owning line-height and drops it. Combined with (2), production v1 renders
161
- labels at preflight's 1.5 rather than at 1.
162
- 4. ScrollOverflow's viewport declares `[&>div]:!flex` and `[&>div]:!flex-row`, and neither survives
163
- the build — so v1's _horizontal_ scroller stacks its children vertically.
164
-
165
- **The rule: reproduce the harmless ones, refuse the harmful ones.**
166
-
167
- - ScrollOverflow does **not** reproduce bug (4). Matching it would ship a horizontal scroll container
168
- that cannot scroll horizontally. That field requires a written reason and prints on every run.
169
- - Nor does anything reproduce bugs (2) and (3), and that is the one **standing** exception, decided
170
- in DECISIONS.md D9. Every slot that sets `fontSize: tokens.text*` also sets the matching
171
- `lineHeight: tokens.leading*`, Label included — it follows the `text-sm` its class list actually
172
- ships, not the `leading-none` tailwind-merge ate. Bug (2) is not cosmetic: it kills the leading at
173
- every one of the seven steps, so reproducing it means shipping a type scale with no vertical
174
- rhythm. It is also too wide for an `acceptedDifference` — every specimen containing text would need
175
- one — so the VR harness corrects it on the v1 arm instead, once and by name
176
- (`V1_DECLARED_LEADING_SHIM` in `vr/run.mjs`, pinned and dated by
177
- `vr/v1-leading-references.test.ts`).
178
-
179
- Read the exception narrowly. It is about leading and nothing else; everywhere else, output still
180
- wins.
181
-
182
- The same principle settles smaller cases without any bug involved: v1 doesn't style disabled tabs at
183
- all, so v2's invented `opacity: 0.5` was removed. v1's toast declares `shadow-md` and renders a fully
184
- transparent one, so v2 has no shadow. v1's OptionCard has a bare Tailwind `border` with no colour
185
- utility, so its border is `currentColor` — a near-black `rgba(2, 0, 8, 0.88)` that looks like an
186
- accident and is nevertheless what ships.
187
-
188
- **When in doubt, measure the rendered result on both arms. Do not read the class list and infer.**
189
-
190
- ---
191
-
192
- ## 5. Retiring a dependency inherits its defaults
193
-
194
- Toast's width (356px), corner offset (24px), zero-width border and absent shadow were all **sonner**
195
- defaults, not choices in v1's source. The port replaced sonner's behaviour and kept Tailwind-flavoured
196
- values — 24rem, 1rem, a real 1px border, a real shadow — so every toast was 28px too wide, 8px too
197
- close to the corner, and carried an edge and a shadow v1 does not render.
198
-
199
- Nothing but a pixel comparison would have said so. The same exposure applies to `cmdk` in Command,
200
- which has no v1 export to compare against at all.
201
-
202
- ---
203
-
204
- ## 6. The token layer
205
-
206
- - **There was no 0.75rem spacing step, and that is why `spacingElement3` now exists.** The scale ran
207
- 0.25 / 0.5 / 0.5 / 1 / 1.5 / 2 rem while v1 uses Tailwind's `*-3` freely, so authors reached for the
208
- nearest-_sounding_ token (`spacingGroup1`, which is **0.5rem**) and silently lost 4px per side.
209
- **That exact mistake was found four times** — Toast, Command, Field, SegmentedControl — which made
210
- it a scale problem, not four authoring slips. The step was added; use `tokens.spacingElement3`.
211
- This paragraph used to end "use the literal `0.75rem`", which was correct only before the token
212
- landed. Sizes are still literals: there is no dimension token family, so `width: "0.75rem"` on an
213
- svg is not `spacingElement3` — spacing and size are different axes that share a number.
214
- - **Line-height tokens are v1's own declared values, and they are applied.** This paragraph used to
215
- read "deliberately unused", on the reasoning that applying them diverges from what v1 _renders_
216
- (§4 bug 2). That was reversed in DECISIONS.md D9: the divergence is from v1's bug, not from v1, and
217
- the harness now measures against v1-as-declared. Pair the leading with the size in the same slot,
218
- `fontSize` first. Leave alone the slots that already declare a line-height — those are v1
219
- `leading-*` utilities that survived tailwind-merge, so they are v1's output as well as its intent.
220
- - **The namespace segment is derived from the token values.** `--volt2-<hash>-accent-9` changes
221
- whenever any token changes; that is what lets two builds coexist on `:root`. **Never hardcode it.**
222
- One test did and broke the moment line-height tokens were added.
223
- - The accessor is 942 **named exports**, not an object literal, because a bundler cannot drop unused
224
- properties of a literal. Worth 6.5 kB gzip to every partial consumer. Authoring is unchanged:
225
- `import * as tokens` and keep saying `tokens.accentSolid`.
226
-
227
- ---
228
-
229
- ## 7. Verifying — where the checks themselves were wrong
230
-
231
- A gate that cannot fail is worse than no gate, because it launders an unverified claim. Every one of
232
- these was a real false pass:
233
-
234
- - **Run gates AFTER wiring exports.** `gates/isolation-fixture.tsx` imports the package namespace, so
235
- it only inspects what `src/index.ts` exports. Running it on an unwired component is a meaningless
236
- pass — that is how Select's `data-slot` violation shipped.
237
- - **The visual gate reads `dist`.** The first negative control perturbed a padding by 2px and still
238
- reported a clean pass, because the edit was in `src`. Staleness is now a hard error.
239
- - **A blank render is not a match.** Nine overlays rendered nothing while the gate reported
240
- "116/124 match", because a blank capture against a mostly-white v1 is a _small_ diff. Uniform
241
- captures and thrown pages are now hard failures.
242
- - **A parse error makes everything pass.** Typechecking a real consumer against v2 reported 0 errors,
243
- and a positive control importing a nonexistent name also reported 0 — because the consumer's
244
- TypeScript 4.9 could not parse Base UI's `.d.ts`, so the module resolved to an error type. Under TS
245
- 5.6 the same code reports 148.
246
- - **A state nobody renders is a state nobody has checked.** That is the whole reason §1.1 needed a
247
- source scan on top of the visual gate.
248
- - **Identical results after a change that should have moved something is a signal.** A patch adding
249
- preflight to the harness silently missed its target line; the numbers came back byte-identical and
250
- only a direct `box-sizing` measurement caught it.
251
- - **A test that passes in the file and fails alone has not passed.** Four overlay dismissal tests were
252
- green under React 19 and red under React 18, and the component behaved _identically_ in both. Run
253
- the suspect test with `-t` before you believe either colour: all four also failed under React 19 in
254
- isolation. Three defects were stacked, each masking the next — see §7.1.
255
-
256
- ### 7.1 The three that hid behind each other
257
-
258
- Worth reading as a set, because no single one of them was visible while the others stood:
259
-
260
- 1. **Testing Library's auto-cleanup never ran.** It self-registers only when the framework's
261
- `afterEach` is a global, and neither vitest config sets `globals: true`. Test files had compensated
262
- with `document.body.innerHTML = ""`, which detaches nodes without unmounting roots. Radix delivers
263
- Escape only to the topmost `DismissableLayer`, so layers left behind by earlier tests swallowed the
264
- key. The tell was `data-scroll-locked="3"` on a page containing one dialog.
265
- Now registered explicitly in `src/setup.testing-library.ts`, in both configs.
266
- 2. **The removal assertion could not pass on its own merits.** Radix's `Presence` holds a closed node
267
- until `animationend`, and jsdom runs no animations. `fireEvent.animationEnd` does not fix it:
268
- jsdom implements no `AnimationEvent`, so Testing Library falls back to a plain `Event` and drops
269
- `animationName` — precisely the field `Presence` gates on. Construct the event by hand
270
- (`src/overlay-test-helpers.ts`). Asserting `data-state="closed"` instead would be vacuous: that
271
- attribute is set before `Presence` decides anything.
272
- 3. **Tooltip's Escape raced its own listener.** Radix attaches it in an effect, and the `waitFor`
273
- resolved on DOM presence — under React 18, before that effect ran, so the keydown landed with
274
- nothing listening. `await act(async () => {})` first. React 19 flushed early enough to hide it.
275
-
276
- The general shape: **a version-dependent test result is a claim about the test, until you have shown
277
- the component behaves differently.** Here it never did.
278
-
279
- ---
280
-
281
- ## 8. The pattern worth internalising
282
-
283
- Four separate times, the same 4px spacing error. Fifteen dead selectors of one shape. Every overlay
284
- non-functional. A component with no styling at all. None of it was found by reading code — not by
285
- source review, not by unit tests, not by three source-level audit passes that each found _other_ real
286
- defects.
287
-
288
- All of it was found by rendering both versions and comparing pixels.
289
-
290
- So: **when you change a component, run `bun run gate:vr`.** When you add one, add specimens for its
291
- states, not just its default. And when a check comes back clean on something that has been wrong
292
- before, find out whether the check can fail at all.
@@ -1,22 +0,0 @@
1
- import { getVoltRenderer as r } from "../renderer/index.js";
2
- import { makeStyles as n } from "../vendor/@griffel/core/src/makeStyles.js";
3
- import { makeResetStyles as o } from "../vendor/@griffel/core/src/makeResetStyles.js";
4
- function a(t) {
5
- const e = n(
6
- t
7
- );
8
- return function() {
9
- return e({ dir: "ltr", renderer: r() });
10
- };
11
- }
12
- function l(t) {
13
- const e = o(t);
14
- return function() {
15
- return e({ dir: "ltr", renderer: r() });
16
- };
17
- }
18
- export {
19
- l as makeVoltResetStyles,
20
- a as makeVoltStyles
21
- };
22
- //# sourceMappingURL=make-styles.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"make-styles.js","sources":["../../../../../../volt-runtime/dist/styles/make-styles.js"],"sourcesContent":["import { getVoltRenderer as r } from \"../renderer/index.js\";\nimport { makeStyles as n } from \"../vendor/@griffel/core/src/makeStyles.js\";\nimport { makeResetStyles as l } from \"../vendor/@griffel/core/src/makeResetStyles.js\";\nfunction i(e) {\n const t = n(\n e\n );\n return function() {\n return t({ dir: \"ltr\", renderer: r() });\n };\n}\nfunction c(e) {\n const t = l(e);\n return function() {\n return t({ dir: \"ltr\", renderer: r() });\n };\n}\nexport {\n c as makeVoltResetStyles,\n i as makeVoltStyles\n};\n//# sourceMappingURL=make-styles.js.map\n"],"names":["i","e","t","n","r","c","l"],"mappings":";;;AAGA,SAASA,EAAEC,GAAG;AACZ,QAAMC,IAAIC;AAAAA,IACRF;AAAA,EACJ;AACE,SAAO,WAAW;AAChB,WAAOC,EAAE,EAAE,KAAK,OAAO,UAAUE,EAAC,GAAI;AAAA,EACxC;AACF;AACA,SAASC,EAAEJ,GAAG;AACZ,QAAMC,IAAII,EAAEL,CAAC;AACb,SAAO,WAAW;AAChB,WAAOC,EAAE,EAAE,KAAK,OAAO,UAAUE,EAAC,GAAI;AAAA,EACxC;AACF;"}