studio-engine 0.65.0 → 0.65.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8914b74430f105820bbc5a41f44d6c573ff88492bcd882534f769fa2c2a11608
4
- data.tar.gz: f3d1ffbec8dcb09ef354d21ab3786d2d5b9b0590a665ac95a949e7282668a66b
3
+ metadata.gz: 7389678882ebbd2bba6a35738f330283a2e5a226e68009a76b83f0c14b0e1351
4
+ data.tar.gz: de1b1a74888a3c2e47cc39da0377323c1b51c04d1aa718de9826fc338aff13a8
5
5
  SHA512:
6
- metadata.gz: d52952922f19f864f97fd09297d8711c246ba54415bd40d60682569cbdfe0d51ccf7fb7673e7d921a361ab3657b35871f4999aad0baa80374dd3098cd3c267b2
7
- data.tar.gz: 82afd75d6a4e57bfbe3708b59c1c1e7eec188cf2bcfd5921e3eeb50e006090d1f0839e5fc280be8ee60c8d6dc999bb07bb148e7e2220b7d061559048e14b6225
6
+ metadata.gz: f2c671579063631d50ac0298f2812ccde9a32a4554cc85ecf2b23368a8f51d2027f4eb03ed748c87104b7b68c9150d4fa7accec288d6b1a545ddf6233c755847
7
+ data.tar.gz: f2a53ef58b2bc5959d3a0c420f0cb8619b448caf496b9b437cd70e4f79ca5d8652e482338f2472ad3fd3f4b2609c0e5c47e8f0c7da3ca44d4d7b9e9828cb4f80
data/CHANGELOG.md CHANGED
@@ -119,6 +119,60 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
119
119
  guard on the read-before-write order.
120
120
 
121
121
 
122
+ ### Fixed
123
+
124
+ - **A toast with buttons could not be dismissed while a modal was open.** The
125
+ layer scale put `--z-banner` (500) above `--z-toast` (400), and `body.modal-open`
126
+ lifts the environment bar stack to that tier at `position: sticky; top: 0`.
127
+ `#toast-container` is fixed at top 0 with `1rem` of padding, so the two land on
128
+ the same pixels: measured in a browser, the bars own y0-47 while the toast's
129
+ Dismiss button runs y28-40 — an 8-14px overlap at every viewport tested.
130
+ `elementFromPoint` at that button returned the **banner**, and a real click left
131
+ the toast on the page. Because the toast manager gives any toast carrying
132
+ buttons `duration: 0`, that X was the toast's **only** exit: it was stuck for
133
+ the rest of the session.
134
+
135
+ **The tiers are reordered** — `--z-banner` 400, `--z-toast-blur` 499, `--z-toast`
136
+ 500 — and that is a semantic decision rather than a nudge around the geometry: a
137
+ toast is transient and demands interaction, a banner is persistent chrome that
138
+ will still be there afterwards. `--z-banner` still clears `--z-modal` (200), so
139
+ the property the lift exists for — DEV MODE and the email chip stay lit and
140
+ clickable over a modal — is unchanged and now has its own spec.
141
+
142
+ **One measured cost.** A banner button's tooltip is a descendant of the bar
143
+ stack, and the modal-open lift makes that stack a stacking context, so the
144
+ tooltip composites at `--z-banner` rather than at its own `--z-tooltip` (600).
145
+ At 1440x900 the tooltip and the toast card never meet; at 390x844 they overlap
146
+ by 260x45px, so a banner tooltip opened with both a modal and a toast up is
147
+ drawn under the toast. Hover/focus-only, purely visual, and recorded beside the
148
+ scale.
149
+
150
+ **Consumers that redefine these tokens in their own `:root` after the engine
151
+ import still win**, as they always have, so an app carrying a local copy of the
152
+ scale keeps the old order (and the bug) until it drops the copy.
153
+
154
+ Two literals moved with the tiers: `#toast-container` and `.toast-page-blur` in
155
+ `layouts/studio/_flash` carry `var(--studio-toast-z, var(--z-toast, …))`
156
+ fallbacks, and a fallback that disagreed with the scale would hand the bug to
157
+ any app rendering the partial without the engine sheet.
158
+
159
+ **The existing test could not have caught this and still cannot on its own.**
160
+ `layer_scale_contract_test` asserted `--z-toast > --z-modal`, which was true the
161
+ entire time the toast was unusable — both tiers cleared the modal, which says
162
+ nothing about which of *them* wins. It now names the banner, and pins the halo
163
+ directly beneath its own toast so no tier can settle between them. The property
164
+ is also asserted where the defect lives, in `e2e/toast_over_banner.spec.js`: a
165
+ hit test and a real mouse click at the Dismiss button, at desktop and phone
166
+ widths, at scroll-top and at an offset. And once more on the artifact a
167
+ consumer is actually served — `test/integration/layer_scale_build_test.rb`
168
+ runs the real Tailwind binary over the engine's entry point and reads the
169
+ tiers out of the COMPILED bundle, where `@import` resolution, layer ordering
170
+ and a shadowing `:root` are all in play and a source read sees none of them. One trap is recorded there — at 390px
171
+ the point under that button is the banner's Email link, so on the broken build a
172
+ click *navigated* and the toast count on the new page was zero; asserting the
173
+ count alone passes over the bug.
174
+
175
+
122
176
  ### Breaking
123
177
 
124
178
  - **The age-gate DOB modal is renamed, and the refusal moved to its own card.**
@@ -63,12 +63,51 @@
63
63
  --z-modal: 200; /* modal backdrop + card — THE app blocker */
64
64
  --z-lightbox: 210; /* a lightbox opened from inside a modal */
65
65
  --z-alert: 300; /* transient overlays that must not be missed */
66
- --z-toast-blur: 399; /* the frosted halo behind the toasts */
67
- --z-toast: 400; /* toasts */
68
- --z-banner: 500; /* environment / DEV MODE reachable mid-modal */
66
+ --z-banner: 400; /* environment / DEV MODE reachable mid-modal */
67
+ --z-toast-blur: 499; /* the frosted halo behind the toasts */
68
+ --z-toast: 500; /* toasts ABOVE the banner; see below */
69
69
  --z-tooltip: 600; /* tooltips on the banner, so they clear it */
70
70
  }
71
71
 
72
+ /* WHY THE TOAST OUTRANKS THE BANNER, and why it did not used to.
73
+
74
+ The first cut of this scale put --z-banner (500) over --z-toast (400) on the
75
+ reasoning that the environment bars must stay reachable mid-modal. They must
76
+ — but so must a toast, and the two claims collided the moment BOTH were true
77
+ at once, because they occupy the same corner of the viewport.
78
+
79
+ THE DEFECT, MEASURED on mcritchie-studio at 1440x900 and 390x844, dark and
80
+ light, scrollY 0 and 900. #toast-container is fixed at top 0 with 1rem of
81
+ padding, so a toast card sits at roughly y16-92. With a modal open, the rule
82
+ below lifts the bar stack to sticky top 0 at --z-banner, so it owns y0-47 —
83
+ overlapping the toast's top edge, which is where the Dismiss button lives.
84
+ `document.elementFromPoint` at that button returned the BANNER div, and a
85
+ real click left the toast count at 1. With no modal open it returned the
86
+ button's own SVG and the count dropped to 0.
87
+
88
+ That is not a cosmetic overlap. A toast carrying action buttons is given
89
+ `duration: 0` by the toast manager (layouts/studio/_flash), so it never
90
+ auto-dismisses — the Dismiss button is its ONLY exit, and under a lifted
91
+ banner the toast is stuck on the page for the rest of the session.
92
+
93
+ SO THE ORDER IS A SEMANTIC DECISION, not a workaround for the geometry: a
94
+ TOAST IS TRANSIENT AND DEMANDS INTERACTION; a BANNER IS PERSISTENT CHROME
95
+ that will still be there afterwards. The thing asking to be dealt with wins.
96
+ Two alternatives were considered and rejected — lowering the modal-open lift
97
+ below the toast (narrower blast radius, but leaves the scale saying something
98
+ nobody believes) and offsetting #toast-container while modal-open (treats the
99
+ symptom; the toast goes back under the banner the day either one grows).
100
+
101
+ WHAT THIS COSTS, MEASURED RATHER THAN ASSUMED. --z-tooltip (600) still clears
102
+ both tiers, but a banner button's tooltip is a DESCENDANT of the bar stack —
103
+ and while a modal is open the rule below gives that stack a z-index, which
104
+ makes it a stacking context and clamps every descendant inside it. So the
105
+ tooltip now composites at --z-banner rather than at 600. At 1440x900 the two
106
+ never meet (tooltip x1083-1343, toast card x496-944); at 390x844 they overlap
107
+ by 260x45px, so a banner tooltip opened with a modal AND a toast up is drawn
108
+ under the toast card. It is hover/focus-only, purely visual (the tooltip is
109
+ pointer-events:none), and it is the accepted cost of this ordering. */
110
+
72
111
  /* The environment / impersonation bars stay in NORMAL FLOW at rest
73
112
  (banners/_stack records why: pinning them in the RESTING state needs one
74
113
  pinned sibling to measure the other, and the measured version visibly
@@ -110,7 +149,7 @@ body.modal-open .studio-bar-stack,
110
149
  body.modal-open .studio-app-banner {
111
150
  position: sticky;
112
151
  top: 0;
113
- z-index: var(--z-banner, 500);
152
+ z-index: var(--z-banner, 400);
114
153
  }
115
154
 
116
155
  /* -- Surfaces -------------------------------------------------------------- */
@@ -395,6 +434,28 @@ body.modal-open .studio-app-banner {
395
434
  x-data="navCollapse()" on it, and override only the endpoints that differ
396
435
  from the defaults below.
397
436
 
437
+ ADOPTING IT IN A FORK COSTS MORE THAN THE OPT-IN, corrected 2026-08-28 after
438
+ checking against a real forked header. Two attributes is what it takes to opt
439
+ IN; it is not the job. mcritchie-studio's inlined header needs, at minimum:
440
+
441
+ REMOVE the bindings that fight the primitive —
442
+ :class="scrolled ? 'py-2' : 'py-6'" on the row
443
+ x-bind:class="scrolled ? 'w-5 h-5' : …" on the logo
444
+ x-bind:class="scrolled ? 'text-base' : …" on the h1, which is a DISCRETE
445
+ swap and therefore the exact 1px reverse lurch this primitive exists to
446
+ remove — a fork that keeps it reproduces the bug it is adopting to fix
447
+ transition-all duration-300 in three places
448
+ ADD the hooks the band table actually keys on —
449
+ nav-row, nav-logo, nav-title, nav-logo-link
450
+ (nav-logo-link is MOBILE-ONLY: omit it and a desktop check still passes,
451
+ which is how an adoption ships half-done)
452
+ WRAP the h1's bare text node in a span so there is an element to size.
453
+
454
+ That is roughly fourteen edits across five elements. The SIZING LAYER does
455
+ reach every app — that is the win this block delivers and it is unaffected.
456
+ Only the cost sentence was wrong, and an understated cost is how an adoption
457
+ gets started and abandoned half-done.
458
+
398
459
  LAYERED ON PURPOSE. These are DEFAULTS, so a consuming app has to be able to
399
460
  beat them, and unlayered CSS beats every layered rule regardless of
400
461
  specificity — shipping them bare would make the engine's numbers
@@ -74,8 +74,17 @@
74
74
  rendering THIS partial got it. Three of six apps fork the navbar, so each
75
75
  was left to hand-write its own — the way four independent copies happened
76
76
  in the first place. It ships from engine.css now (every consuming app
77
- imports it), so a forking app adopts the collapse with `nav-shell` +
78
- x-data="navCollapse()" and overrides only the endpoints that differ. */
77
+ imports it), so a forking app OPTS IN with `nav-shell` +
78
+ x-data="navCollapse()" and overrides only the endpoints that differ.
79
+
80
+ THE OPT-IN IS NOT THE WHOLE JOB — this is the partial a forker actually
81
+ reads, so the caveat belongs here too rather than only beside the band
82
+ table. A hand-written header must also REMOVE the bindings that fight the
83
+ primitive (a :class size swap on the row, x-bind:class on the logo AND on
84
+ the title, transition-all alongside) and ADD the hooks the table keys on
85
+ (nav-row, nav-logo, nav-title, and the mobile-only nav-logo-link). The
86
+ title swap matters most: it is DISCRETE, which is the 1px reverse lurch
87
+ the primitive exists to delete. Full accounting in engine.css. */
79
88
  </style>
80
89
  <div class="nav-row flex items-center">
81
90
  <%# Left side: logo + nav %>
@@ -17,8 +17,18 @@
17
17
  bare 60/55 that sat below every app navbar. A toast is the one thing that
18
18
  must surface over an open modal, so --z-toast is above --z-modal and an app
19
19
  that never overrides gets that for free. Apps that DO override should set
20
- the tier (--z-toast) instead, and drop the per-component name. */
21
- #toast-container { z-index: var(--studio-toast-z, var(--z-toast, 400)); }
20
+ the tier (--z-toast) instead, and drop the per-component name.
21
+
22
+ ABOVE THE BANNER TOO, and the fallbacks below have to say so. A toast and
23
+ the environment bars share the top of the viewport, and with a modal open
24
+ engine.css lifts those bars to --z-banner at top 0 — which used to sit at
25
+ 500, over the toast's 400, so the toast's Dismiss button was covered and a
26
+ click landed on the banner. A toast carrying buttons is given duration 0 by
27
+ the manager below, so that button is its only exit: covered, the toast was
28
+ stuck forever. The tiers were reordered (engine.css carries the argument);
29
+ these literals are what an app gets when the engine sheet is absent, so
30
+ they must carry the SAME order or that app keeps the bug. */
31
+ #toast-container { z-index: var(--studio-toast-z, var(--z-toast, 500)); }
22
32
  .toast-shadow-all {
23
33
  box-shadow: 0 0 30px rgba(0,0,0,0.4), 0 0 10px rgba(0,0,0,0.2);
24
34
  }
@@ -65,7 +75,7 @@
65
75
  mask-image: radial-gradient(ellipse 70% 80% at 50% 0%, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.3) 40%, rgba(0,0,0,0) 100%);
66
76
  -webkit-mask-image: radial-gradient(ellipse 70% 80% at 50% 0%, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.3) 40%, rgba(0,0,0,0) 100%);
67
77
  pointer-events: none;
68
- z-index: var(--studio-toast-blur-z, var(--z-toast-blur, 399));
78
+ z-index: var(--studio-toast-blur-z, var(--z-toast-blur, 499));
69
79
  transition: opacity 0.5s ease;
70
80
  background: rgba(255, 255, 255, 0.15);
71
81
  }
@@ -541,9 +541,10 @@
541
541
  // KEEP FRAMES COMING WHILE CATCHING UP. Nothing else will schedule
542
542
  // one: the finger is off, so no more scroll events arrive, and
543
543
  // without this the collapse freezes wherever the clamp left it. It
544
- // converges LINEARLY and lands exactly — about 4 frames (~67ms) from
545
- // a hard flick rather than crawling asymptotically the way the
546
- // 300ms ease this replaced did.
544
+ // converges LINEARLY and lands exactly — about 8 frames (~133ms) at
545
+ // the mobile --nav-ramp of 120, and about 10 (~167ms) at the default
546
+ // 144, from a hard flick — rather than crawling asymptotically the
547
+ // way the 300ms ease this replaced did.
547
548
  if (!snap && p !== target) {
548
549
  queued = true;
549
550
  requestAnimationFrame(apply);
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.65.0"
2
+ VERSION = "0.65.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.65.0
4
+ version: 0.65.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie