studio-engine 0.65.1 → 0.65.3
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 +4 -4
- data/CHANGELOG.md +92 -0
- data/app/assets/tailwind/studio_engine/engine.css +43 -4
- data/app/views/layouts/studio/_flash.html.erb +13 -3
- data/lib/studio/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ed062df7a9840a9633eb1c4995bc878bee401f98d85086e7943c5ec9f6b55d4
|
|
4
|
+
data.tar.gz: df0c1c7dc4e13185b3c8e3fbe7cb9243f4b310398843c43913ef0a1aa7b03762
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca97a89b5c1452bad931ec0a332e9fff228ea43b7ebe2249516b3f60b9bf79f03fcb3171a66c5b8c7692e25389ac15d24a8c70b0176a82921fee80b2dd80a06e
|
|
7
|
+
data.tar.gz: 97061141650afc6472e15bcaf40dc96c5175897a25f6d4aea5cfb318dd1d92100db6cb1aa52c3feb4a018fe93bb924485fdee7fe97d5989a7779dfb0959b03c0
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,44 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
8
|
|
|
9
|
+
- **A green comment-leak scan used to mean "not looked at".**
|
|
10
|
+
`test/views/erb_comment_leak_test.rb` guards the ERB comment form in
|
|
11
|
+
`app/views/**/*.erb`. It never looked inside `<script>`, and that is where this
|
|
12
|
+
gem keeps its browser programs — `studio/modals/_host`,
|
|
13
|
+
`studio/solana/_phantom_deeplink`, `layouts/studio/_head` and the modal blocks
|
|
14
|
+
between them hold **1_239 JavaScript comments, 73_913 bytes of comment body**,
|
|
15
|
+
none of it read by any guard. A review of a diff made almost entirely of those
|
|
16
|
+
comments came back green, and the reviewer's note was the honest reading of it:
|
|
17
|
+
*"don't read that green as clearance."*
|
|
18
|
+
|
|
19
|
+
`test/views/script_comment_leak_test.rb` reads them. The rule is the one the ERB
|
|
20
|
+
guard applies — no ERB open or close sequence inside a comment body — and the
|
|
21
|
+
mechanism it defends against is worse than the ERB one. A tag quoted inside an
|
|
22
|
+
ERB comment is inert, because the comment swallows it. A tag quoted inside a
|
|
23
|
+
**JavaScript** comment is not inside anything ERB knows about, so ERB opens a tag
|
|
24
|
+
right there and **runs** it: a complete tag evaluates at render, and an
|
|
25
|
+
incomplete one swallows every byte up to the next close sequence in the file,
|
|
26
|
+
deleting working JavaScript with no syntax error to show for it. Wrapping the
|
|
27
|
+
line in an HTML comment does not help — ERB runs inside those too. Describing the
|
|
28
|
+
tag in words stays legal, exactly as it is for ERB comments.
|
|
29
|
+
|
|
30
|
+
**Finding a comment is the hard half, and the first cut of this got it wrong.**
|
|
31
|
+
Anchoring `<script>` blocks on raw source let an ERB comment's *prose mention* of
|
|
32
|
+
a script tag open the block: `studio/_board_assets.html.erb` reported 19_512
|
|
33
|
+
bytes of markup as JavaScript and never saw its real program on its own terms.
|
|
34
|
+
Blocks are now anchored on a copy with ERB tags and HTML comments blanked out,
|
|
35
|
+
length preserved so offsets and line numbers still land. Inside a block the
|
|
36
|
+
walker tracks the states that can hide a comment opener — a string
|
|
37
|
+
(`"https://…"` is not a comment), a template literal, a regex (`/\/\//`) — and a
|
|
38
|
+
literal or comment that never closes is REPORTED rather than absorbed, because a
|
|
39
|
+
walker that quietly lost its place is the same green as a clean file. The
|
|
40
|
+
assertions check the input as well as the verdict: how many blocks, comments and
|
|
41
|
+
bytes the scan actually reached, and that every block it opened matches a real
|
|
42
|
+
closing tag.
|
|
43
|
+
|
|
44
|
+
Zero findings across the engine at `origin/accepted`, so this lands green with no
|
|
45
|
+
allowlist.
|
|
46
|
+
|
|
9
47
|
- **`data-pin` — the pinned stack publishes itself.** `--nav-h` / `--nav-bottom`
|
|
10
48
|
answer for one element. Everything else that pins has been re-deriving the
|
|
11
49
|
same geometry by hand: on `mcritchie-studio`'s `/deployments` the app strip
|
|
@@ -119,6 +157,60 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
119
157
|
guard on the read-before-write order.
|
|
120
158
|
|
|
121
159
|
|
|
160
|
+
### Fixed
|
|
161
|
+
|
|
162
|
+
- **A toast with buttons could not be dismissed while a modal was open.** The
|
|
163
|
+
layer scale put `--z-banner` (500) above `--z-toast` (400), and `body.modal-open`
|
|
164
|
+
lifts the environment bar stack to that tier at `position: sticky; top: 0`.
|
|
165
|
+
`#toast-container` is fixed at top 0 with `1rem` of padding, so the two land on
|
|
166
|
+
the same pixels: measured in a browser, the bars own y0-47 while the toast's
|
|
167
|
+
Dismiss button runs y28-40 — an 8-14px overlap at every viewport tested.
|
|
168
|
+
`elementFromPoint` at that button returned the **banner**, and a real click left
|
|
169
|
+
the toast on the page. Because the toast manager gives any toast carrying
|
|
170
|
+
buttons `duration: 0`, that X was the toast's **only** exit: it was stuck for
|
|
171
|
+
the rest of the session.
|
|
172
|
+
|
|
173
|
+
**The tiers are reordered** — `--z-banner` 400, `--z-toast-blur` 499, `--z-toast`
|
|
174
|
+
500 — and that is a semantic decision rather than a nudge around the geometry: a
|
|
175
|
+
toast is transient and demands interaction, a banner is persistent chrome that
|
|
176
|
+
will still be there afterwards. `--z-banner` still clears `--z-modal` (200), so
|
|
177
|
+
the property the lift exists for — DEV MODE and the email chip stay lit and
|
|
178
|
+
clickable over a modal — is unchanged and now has its own spec.
|
|
179
|
+
|
|
180
|
+
**One measured cost.** A banner button's tooltip is a descendant of the bar
|
|
181
|
+
stack, and the modal-open lift makes that stack a stacking context, so the
|
|
182
|
+
tooltip composites at `--z-banner` rather than at its own `--z-tooltip` (600).
|
|
183
|
+
At 1440x900 the tooltip and the toast card never meet; at 390x844 they overlap
|
|
184
|
+
by 260x45px, so a banner tooltip opened with both a modal and a toast up is
|
|
185
|
+
drawn under the toast. Hover/focus-only, purely visual, and recorded beside the
|
|
186
|
+
scale.
|
|
187
|
+
|
|
188
|
+
**Consumers that redefine these tokens in their own `:root` after the engine
|
|
189
|
+
import still win**, as they always have, so an app carrying a local copy of the
|
|
190
|
+
scale keeps the old order (and the bug) until it drops the copy.
|
|
191
|
+
|
|
192
|
+
Two literals moved with the tiers: `#toast-container` and `.toast-page-blur` in
|
|
193
|
+
`layouts/studio/_flash` carry `var(--studio-toast-z, var(--z-toast, …))`
|
|
194
|
+
fallbacks, and a fallback that disagreed with the scale would hand the bug to
|
|
195
|
+
any app rendering the partial without the engine sheet.
|
|
196
|
+
|
|
197
|
+
**The existing test could not have caught this and still cannot on its own.**
|
|
198
|
+
`layer_scale_contract_test` asserted `--z-toast > --z-modal`, which was true the
|
|
199
|
+
entire time the toast was unusable — both tiers cleared the modal, which says
|
|
200
|
+
nothing about which of *them* wins. It now names the banner, and pins the halo
|
|
201
|
+
directly beneath its own toast so no tier can settle between them. The property
|
|
202
|
+
is also asserted where the defect lives, in `e2e/toast_over_banner.spec.js`: a
|
|
203
|
+
hit test and a real mouse click at the Dismiss button, at desktop and phone
|
|
204
|
+
widths, at scroll-top and at an offset. And once more on the artifact a
|
|
205
|
+
consumer is actually served — `test/integration/layer_scale_build_test.rb`
|
|
206
|
+
runs the real Tailwind binary over the engine's entry point and reads the
|
|
207
|
+
tiers out of the COMPILED bundle, where `@import` resolution, layer ordering
|
|
208
|
+
and a shadowing `:root` are all in play and a source read sees none of them. One trap is recorded there — at 390px
|
|
209
|
+
the point under that button is the banner's Email link, so on the broken build a
|
|
210
|
+
click *navigated* and the toast count on the new page was zero; asserting the
|
|
211
|
+
count alone passes over the bug.
|
|
212
|
+
|
|
213
|
+
|
|
122
214
|
### Breaking
|
|
123
215
|
|
|
124
216
|
- **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-
|
|
67
|
-
--z-toast:
|
|
68
|
-
--z-
|
|
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,
|
|
152
|
+
z-index: var(--z-banner, 400);
|
|
114
153
|
}
|
|
115
154
|
|
|
116
155
|
/* -- Surfaces -------------------------------------------------------------- */
|
|
@@ -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
|
-
|
|
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,
|
|
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
|
}
|
data/lib/studio/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.65.
|
|
4
|
+
version: 0.65.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|