@ai-matrx/design-system 0.9.0 → 0.10.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,232 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.1 — 2026-09-07
4
+
5
+ **THE CENTRED DIALOG FLEW IN FROM OFF THE SCREEN.** 0.10.0 shipped a live
6
+ regression: `Dialog`, `AlertDialog` and `CommandDialog` entered from beyond the
7
+ top-left corner of the viewport and SNAPPED into place at the end of their
8
+ 200ms animation. Independent review caught it on the live surface, measured it,
9
+ and proved the cause rather than inferring it.
10
+
11
+ **The cause is that one Tailwind class means two different things.**
12
+
13
+ | | `translate-x-[-50%] translate-y-[-50%]` compiles to |
14
+ |---|---|
15
+ | Tailwind **v3** | `transform: translate(-50%, -50%)` — REPLACED by an animation of `transform` |
16
+ | Tailwind **v4** | `translate: -50% -50%` — an independent property that **composes with** `transform` |
17
+
18
+ 0.10.0's keyframes restated the centring inside `transform`
19
+ (`translate(-50%,-48%)` -> `translate(-50%,-50%)`), which is correct under v3
20
+ and doubles the centring under v4. Measured on the live element, viewport
21
+ 2560x1289, `getComputedStyle(el).translate === "-50% -50%"` while
22
+ `transform === "none"` at rest:
23
+
24
+ | | x | y |
25
+ |---|---|---|
26
+ | resting (correct) | 128 | 32 |
27
+ | `currentTime = 0` | **-966** | **-525** |
28
+ | `currentTime = 100` | **-1015** | **-571** |
29
+ | `currentTime = 199`, `el.style.translate = "none"` | 137 | 41 |
30
+
31
+ That last row is the proof: removing the `translate` property mid-animation
32
+ restores the resting position exactly, because the element was wearing the
33
+ centring twice.
34
+
35
+ **The fix is not a different spelling of the keyframes** — there isn't one that
36
+ works in both hosts. Restate the offset and v4 applies it twice; omit it and v3
37
+ drops it for the animation's duration and snaps at the end. So the offset stops
38
+ being a Tailwind utility at all. This package now owns it, in the `translate`
39
+ property, in its own stylesheet:
40
+
41
+ - `.matrx-dialog-centered` (`translate: -50% -50%`) carries the centring, and
42
+ the keyframes carry ONLY the delta — `opacity`, plus a 2% rise scaled up from
43
+ 95% in `transform`, ending on `transform: none`. Resting geometry is
44
+ byte-identical to 0.10.0's; `transform` and `translate` never restate each
45
+ other again, in any host.
46
+ - **Census — the same hazard, one more surface.** `Select` in `popper` position
47
+ held its 4px gap from the trigger with `data-[side=bottom]:translate-y-1` and
48
+ three siblings, while `matrx-popper-*` keyframes animate `transform`. Same
49
+ defect, 4px instead of half a viewport, failing the other way round (a v3
50
+ host ate the gap for the animation and snapped it back). It is now
51
+ `.matrx-popper-offset`, sized by the new `--matrx-popper-offset` token.
52
+ - `Sheet`, the mobile bottom sheet and every other popper were **not** affected
53
+ and are unchanged — their translate utilities are zero-valued or absent.
54
+
55
+ **The guard, proven failing-then-passing.** jsdom cannot measure a composite
56
+ transform, so the hazard is asserted as a RULE over the sources in
57
+ `motion.test.tsx`: *no component may pair a NON-ZERO Tailwind translate utility
58
+ with a package motion class*, plus the centred keyframes may not mention `-50%`
59
+ and must end on `transform: none`, plus each of the three surfaces must render
60
+ the package's centring class. Restoring 0.10.0's exact shape (the utility in
61
+ `DIALOG_DESKTOP_CLASSES` and the centring back in the keyframes) turns 4 of
62
+ these red; the fix turns them green. `motion.test.tsx`'s old assertion that
63
+ `translate-x-[-50%]` survives `animated={false}` PINNED the defect — it now
64
+ asserts the centring class survives and the utility is gone. The C26 canaries
65
+ grew too: both new rules are required in `styles.test.ts` and in the packed
66
+ tarball.
67
+
68
+ ### Consumer action (C28)
69
+
70
+ **None.** No API changed, no class a consumer writes changed, and resting
71
+ geometry is identical. Take the patch.
72
+
73
+ Named, because they are the ones that regressed: **aidream/dashboard**,
74
+ **aidream/workflow-studio** and **matrx-extend** loaded no animation rules at
75
+ all before 0.10.0, so their dialogs had always appeared instantly and correctly
76
+ centred — 0.10.0 is what gave them a dialog that flies in from off-screen.
77
+ matrx-frontend was NOT visibly changed by either release: its pre-0.10.0
78
+ `tw-animate-css` spelling had the identical stacking flaw. All four are correct
79
+ on 0.10.1.
80
+
81
+ ## 0.10.0 — 2026-09-07
82
+
83
+ **THE MOTION SWEEP. Ten more primitives were animating with a host plugin this
84
+ package never declared and cannot declare — and three of the four consumers
85
+ never loaded it.** 0.8.0 and 0.9.0 closed the coin flip for `Accordion` and
86
+ `Collapsible` one primitive at a time. This release swept the rest of the
87
+ package for the same shape and found it everywhere at once.
88
+
89
+ Every floating surface here — `Dialog`, `AlertDialog`, `Sheet`,
90
+ `CommandDialog`, `Popover`, `DropdownMenu`, `ContextMenu`, `HoverCard`,
91
+ `Select`, `Tooltip` — carried a correctly spelled
92
+ `data-[state=open]:animate-in data-[state=closed]:animate-out
93
+ data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95
94
+ data-[side=bottom]:slide-in-from-top-2 …`. None of those is a Tailwind
95
+ utility. They come from the `tailwindcss-animate` plugin (or its v4 successor
96
+ `tw-animate-css`), which a host loads from its own CSS entry — something a
97
+ node_modules package can neither ship nor require.
98
+
99
+ Measured on each consumer's BUILT stylesheet before any of this landed
100
+ (`grep -c 'animate-in\|slide-in-from'`):
101
+
102
+ | Consumer | plugin | rules in built CSS |
103
+ |---|---|---|
104
+ | matrx-frontend | `@import "tw-animate-css"` in `globals.css` | present — animated |
105
+ | aidream/dashboard | none | **0** |
106
+ | aidream/workflow-studio | none | **0** |
107
+ | matrx-extend | `tailwindcss-animate` in `package.json`, **never loaded** (a Tailwind v3 plugin; extend is on v4 with no `@plugin` directive) | **0** |
108
+
109
+ So every dialog, dropdown, popover, tooltip, select and sheet in the two
110
+ aidream admin apps and the Chrome extension appeared instantly, with no
111
+ animation and no error, for as long as those components existed. The one host
112
+ that worked, worked by accident of its own CSS entry. That is the doctrine
113
+ sentence from `FEATURE.md` proven at scale: a primitive that leaves motion to
114
+ the host has not shipped motion, it has shipped a coin flip.
115
+
116
+ **The package now owns all of it.** `styles.css` gains a documented motion
117
+ layer: package-declared `@keyframes matrx-*`, applied through `matrx-`-prefixed
118
+ classes the components put on the DOM themselves, unlayered like the accordion
119
+ and collapsible rules so every consumer gets identical motion even where the
120
+ host plugin is still loaded.
121
+
122
+ | Motion | Class | Primitives |
123
+ |---|---|---|
124
+ | scrim fade | `matrx-motion-overlay` | Dialog, AlertDialog, Sheet, CommandDialog |
125
+ | centred card | `matrx-motion-dialog` | Dialog (desktop), AlertDialog, CommandDialog (desktop) |
126
+ | bottom sheet | `matrx-motion-bottom-sheet` | Dialog / CommandDialog below the mobile breakpoint |
127
+ | edge sheet | `matrx-motion-sheet-{top,bottom,left,right,center}` | Sheet, per `side` |
128
+ | popper | `matrx-motion-popper` | Popover, DropdownMenu, ContextMenu, HoverCard, Select, Tooltip |
129
+ | pulse | `matrx-pulse` | Skeleton |
130
+ | spin | `matrx-spin` | every inline busy indicator in the package |
131
+
132
+ Three things changed for the better on the way through:
133
+
134
+ - **The popper grows out of its trigger, not out of itself.** The rule reads
135
+ the anchor-facing origin Radix publishes — as a fallback CHAIN over
136
+ `--radix-popper-transform-origin` and each primitive's own alias
137
+ (`--radix-select-content-transform-origin`,
138
+ `--radix-tooltip-content-transform-origin`, …), because reusing one
139
+ primitive's measured variable on another is precisely the silent death
140
+ census row 19f recorded. The `zoom-in-95` original always scaled from the
141
+ element's centre.
142
+ - **Timing is a knob.** Every duration and easing resolves from a
143
+ `--matrx-motion-*` token in `tokens.css` (fast 150ms, base 200ms, slow
144
+ 300ms, sheet 500ms, plus the two easings and the popper travel distance). An
145
+ org that wants calmer motion sets seven values instead of overriding twenty
146
+ rules. `prefers-reduced-motion` is NOT one of them — it is honoured in the
147
+ stylesheet regardless.
148
+ - **`animate-pulse` / `animate-spin` came home too.** Those two ARE core
149
+ Tailwind, so they survived in a Tailwind host — but a Skeleton that does not
150
+ pulse is an empty grey box and a spinner that does not spin says the work
151
+ stopped. Both are four lines; the package owns them. Under reduced motion
152
+ they SLOW rather than stop, because stopping them removes information rather
153
+ than removing motion.
154
+
155
+ **Where the line is, stated in the sheet:** this layer owns keyframe
156
+ animation — motion that either happens or silently does not. It deliberately
157
+ does not own Tailwind's `transition-*` / `duration-*` / `ease-*` utilities the
158
+ primitives also carry (a hover colour, the progress bar's width, a close
159
+ button's opacity). Those are core Tailwind, present in every Tailwind host,
160
+ and their absence degrades to an instant-but-correct change rather than to a
161
+ surface that lies about its state. Re-implementing them here would mean
162
+ shipping a copy of Tailwind.
163
+
164
+ **Opt-out:** `animated={false}` on `DialogContent`, `AlertDialogContent`,
165
+ `SheetContent`, `PopoverContent`, `HoverCardContent`, `TooltipContent`,
166
+ `SelectContent` and `Skeleton` returns the surface to instant. It is not the
167
+ reduced-motion switch; the stylesheet already handles that.
168
+
169
+ Guards, all proven failing-then-passing by re-introducing a
170
+ `data-[state=open]:animate-in` on `PopoverContent`:
171
+
172
+ - `motion.ts` — ONE exported constant per motion shape, so the eleventh
173
+ primitive cannot invent an eleventh spelling. Exported publicly
174
+ (`ALL_MOTION_CLASSES`, `FORBIDDEN_HOST_MOTION_UTILITIES`) so a consumer can
175
+ audit its own CSS against the same list.
176
+ - `motion.test.tsx` — one case per primitive, asserting BOTH halves: the
177
+ rendered element carries the class, AND `styles.css` defines a rule for it
178
+ whose keyframe that same sheet declares. A class-name assertion alone would
179
+ have passed throughout the defect. Plus: every Sheet side wears its own edge
180
+ class and no other; the mobile bottom sheet does not wear the card's
181
+ keyframe; every opt-out actually opts out and leaves geometry alone.
182
+ - `styles.test.ts` — no component may reference a host-plugin utility again;
183
+ every keyframe referenced is declared; every keyframe is `matrx-` prefixed;
184
+ every overlay duration comes from a token; every motion class appears in a
185
+ `prefers-reduced-motion` block; the popper origin chain is complete; and the
186
+ class list is exhaustive in BOTH directions.
187
+ - The packed-tarball canary asserts thirteen rules, six keyframes, both origin
188
+ variables, the timing tokens, and — the assertion that would have caught
189
+ this whole release from outside — that **every keyframe the packed sheet
190
+ references is declared in that same packed sheet**, read from the artifact a
191
+ consumer installs with no host in the room.
192
+
193
+ ### Consumer action (C28)
194
+
195
+ - 🔶 **This is a VISIBLE change in three of the four consumers, on purpose.**
196
+ `aidream/dashboard`, `aidream/workflow-studio` and `matrx-extend` get open /
197
+ close animation on every dialog, dropdown, popover, tooltip, select and
198
+ sheet **for the first time**. They need no code change and no CSS: they
199
+ already import `@ai-matrx/design-system/styles.css`, so bumping the version
200
+ is the whole adoption. If a specific surface must not move, pass
201
+ `animated={false}` — do not re-add a host plugin.
202
+ - **matrx-frontend: `tw-animate-css` is no longer required by anything this
203
+ package renders.** Every `animate-in` / `slide-in-from-*` this package
204
+ emitted is gone. Audit your own components for remaining uses before
205
+ deleting the `@import "tw-animate-css"` from `app/globals.css` and the
206
+ dependency from `package.json`; the package no longer needs either.
207
+ - **matrx-extend: delete `tailwindcss-animate` from `package.json`.** It has
208
+ never been loaded — it is a Tailwind v3 plugin in a v4 app with no `@plugin`
209
+ directive, and the built stylesheet proves it contributed zero rules. It is
210
+ dead weight that also makes the app look like it has motion support.
211
+ - **Retiming is a token change now.** Do not override the motion rules; set
212
+ `--matrx-motion-duration-*` / `--matrx-motion-ease-*` /
213
+ `--matrx-motion-popper-travel`.
214
+ - New type exports: `PopoverContentProps`, `SelectContentProps`,
215
+ `SkeletonProps`. New value exports: `ALL_MOTION_CLASSES`,
216
+ `FORBIDDEN_HOST_MOTION_UTILITIES`, `MOTION_OVERLAY`, `MOTION_DIALOG`,
217
+ `MOTION_BOTTOM_SHEET`, `MOTION_POPPER`, `MOTION_SHEET`, `MOTION_PULSE`,
218
+ `MOTION_SPIN`.
219
+ - **Not fixed here, and named so it is not lost:** `@ai-matrx/kit`'s
220
+ `src/confirm/alert-dialog.tsx` carries the identical defect (`animate-in`,
221
+ `zoom-in-95`, `slide-in-from-top-[48%]`) and ships NO stylesheet at all, so
222
+ its confirm dialog is unanimated in the same three consumers. It cannot
223
+ import these rules: `@ai-matrx/design-system` depends on `@ai-matrx/kit`,
224
+ so the fix is a package-graph decision (move the confirm dialog, or invert
225
+ the dependency), not a class rename. `@ai-matrx/tap-target` has one
226
+ `animate-spin` in `src/tap-buttons.tsx` — core Tailwind, so it survives in
227
+ every current consumer, but it is the same class of host reliance and rides
228
+ the next tap-target release.
229
+
3
230
  ## 0.9.0 — 2026-09-07
4
231
 
5
232
  **`Collapsible` gets the open/close transition the package never shipped
@@ -229,9 +456,19 @@ Install `@ai-matrx/design-system@0.7.0` (and `@ai-matrx/tap-target@0.2.0`), then
229
456
  `alert-dialog.tsx`, `alert.tsx`, `slider.tsx`, `resizable.tsx`,
230
457
  `matrx/resizable.tsx`, `radio-group.tsx`, `hover-card.tsx`,
231
458
  `toggle-group.tsx`, `toggle.tsx`, `radix-dialog-modal-context.tsx`.
232
- - No other consumer repo carries a fork of these — verified by census on
459
+ - ~~No other consumer repo carries a fork of these — verified by census on
233
460
  2026-09-07 across matrx-extend, matrx-games, aidream `apps/dashboard` and
234
- `apps/workflow-studio`.
461
+ `apps/workflow-studio`.~~ 🚨 **WRONG, corrected 2026-09-07.** That census
462
+ swept FOUR repos and there are FIVE: **matrx-local** (`desktop/`, the
463
+ Tauri app) carried its own `tooltip.tsx`, `slider.tsx`, `popover.tsx` and
464
+ `select.tsx` the whole time — plus ten more from the 0.5.0 list below,
465
+ whose Consumer action omitted it for the same reason. The sentence was
466
+ repeated verbatim into census row 19, and every later wave that read it
467
+ kept skipping the repo. matrx-local was adopted on 0.10.0 the same day
468
+ this was found: all seventeen forks swapped, fourteen files deleted, 131
469
+ files repointed, 56 twin-register rows added. **The lesson is the
470
+ sentence, not the repo — a "no other consumer" claim must name the repos
471
+ it swept, and the list must be checked against the actual consumer set.**
235
472
  3. **`matrx/resizable.tsx` callers keep their `size` prop** — it is the same
236
473
  eight-step scale, now on the one component. Callers of the plain
237
474
  `resizable.tsx` get the identical 2px default (`size="sm"`).
@@ -418,6 +655,11 @@ copy of something this version now ships:
418
655
  Bind `Card size="lg"` to keep this app's density (Table defaults to md as of 0.5.1).
419
656
  - **aidream/apps/workflow-studio** — `src/components/ui/{dialog,dropdown-menu,scroll-area,switch,tabs,textarea}.tsx`.
420
657
  Its ScrollArea viewport hack becomes `viewportClassName="[&>div]:!block [&>div]:min-w-0"`.
658
+ - **matrx-local** — `desktop/src/components/ui/{avatar,card,checkbox,dialog,progress,scroll-area,switch,tabs,textarea}.tsx`.
659
+ **Added 2026-09-07**: this repo was missing from the list above (see the
660
+ correction under 0.7.0 Consumer action 2). Adopted on 0.10.0 — `card` and
661
+ `dialog` keep one-prop binding shims for this app's `lg` density and its
662
+ glass surface; everything else re-points straight at the package.
421
663
 
422
664
  Hosts that do not already import the stylesheets must, or the new structural
423
665
  rules do nothing:
package/README.md CHANGED
@@ -12,7 +12,21 @@ The small, semantic UI foundation shared by AI Matrx React applications — prim
12
12
 
13
13
  Where hosts had forked a primitive over DENSITY, density is a prop, not a fork: `Card size="sm|md|lg"`, `Table size="sm|md"`, `Accordion size="sm|md"`, `Switch`/`Avatar`/`Checkbox`/`Slider`/`RadioGroup`/`Toggle` `size`, `ResizableHandle size="xs".."4xl"`. It is declared once on the root and every item reads it from context, so a card cannot mix paddings and a radio group cannot mix control sizes.
14
14
 
15
- **Motion ships with the component.** `Accordion` and `Collapsible` animate open and closed out of the box (200ms, `prefers-reduced-motion` honoured) from rules in `styles.css` — you never define keyframes or add an `animate-*` utility for them. `CollapsibleContent` also clips its body while it moves; pass `animated={false}` for the rare panel with a child that must overflow its bounds.
15
+ **Motion ships with the component ALL of it.** Every animated surface here
16
+ (`Dialog`, `AlertDialog`, `Sheet`, `CommandDialog`, `Popover`, `DropdownMenu`,
17
+ `ContextMenu`, `HoverCard`, `Select`, `Tooltip`, `Accordion`, `Collapsible`,
18
+ `Skeleton`) animates out of the box from keyframes in `styles.css`. **You never
19
+ define keyframes, add an `animate-*` utility, or install `tailwindcss-animate`
20
+ / `tw-animate-css` for anything this package renders** — that plugin is loaded
21
+ from a host's CSS entry, which is why three of the four apps that already used
22
+ this package had zero animation on every overlay and no error to show for it
23
+ (0.10.0). `prefers-reduced-motion` is honoured in the sheet.
24
+
25
+ Retiming is a token change, not an override — set `--matrx-motion-duration-fast`
26
+ / `-base` / `-slow` / `-sheet`, `--matrx-motion-ease-out` / `-in`, or
27
+ `--matrx-motion-popper-travel`. For one surface that must appear instantly,
28
+ pass `animated={false}`; `CollapsibleContent`'s opt-out additionally stops it
29
+ clipping its body, for the rare panel with a child that must overflow.
16
30
 
17
31
  **Mobile is the Drawer's job.** `Drawer` owns the iOS rules every host was re-deriving — `dvh` never `vh`, `pb-safe`/`pt-safe` safe-area padding, a 44px grab-handle target, `DrawerBody` as the `flex-1 min-h-0` scroll shard — and `direction` makes a top/side drawer a prop rather than a fork. Every touch control (`Slider`, `RadioGroup`, `ResizableHandle`) carries an invisible 44px target on coarse pointers, with desktop density untouched.
18
32