studio-engine 0.25.0 → 0.26.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +120 -1
- data/app/assets/tailwind/studio_engine/engine.css +18 -2
- data/app/views/layouts/_navbar.html.erb +9 -1
- data/app/views/studio/_leveling_activity_assets.html.erb +38 -5
- data/app/views/style/_modal_specimen.html.erb +8 -1
- data/app/views/style/_modals.html.erb +17 -2
- 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: 8d1e665b17a360de65ba411842a7c8368e8077160cf34e4a218ebbe1857d0988
|
|
4
|
+
data.tar.gz: 1b9ebc66583eeacb8c65d97f4ec1eb90b0451829cf7ed263d0a5cd8895b96b88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61d7d9f100144b0683bfa27a12a86ad4b0cac8cc3280c067c9f016a99edf98b0c28f73cf38e2f212d26183336f519d03f633970b15381ebcf165ba9ee143997b
|
|
7
|
+
data.tar.gz: 56b58a13c533dd060fd5d8313987735123e6ed1bd1f148d15ef51ea7306815f365dabf900770f0b8de84fc81b6588bf80e61545ba5a8440b49243a3db7883f52
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) — `MAJOR.MINOR.PATCH`. Consumer Rails apps install the released RubyGems package with `gem "studio-engine", "~> 0.6"`; bumping the gem version and updating consumer lockfiles is a release.
|
|
4
4
|
|
|
5
|
-
## 0.
|
|
5
|
+
## 0.26.1 — 2026-07-28
|
|
6
|
+
|
|
7
|
+
**Fix the `/admin/style` Design System page breaking when reached via Turbo Drive
|
|
8
|
+
navigation (the admin sidebar "Design System" link).** Reached by a direct page
|
|
9
|
+
load the page was fine; reached by an in-app Turbo visit its modals would not open
|
|
10
|
+
on a card click and EVERY specimen glow ring lit at once. Both symptoms were one
|
|
11
|
+
cause: the page-scoped `dsModals` (and `dsSolanaModal`) Alpine store was registered
|
|
12
|
+
ONLY inside a `document.addEventListener('alpine:init', …)` handler in the page
|
|
13
|
+
body. Alpine loads via a deferred CDN `<script>` in the engine head and fires
|
|
14
|
+
`alpine:init` exactly once, on the first full-document load; Turbo Drive advance
|
|
15
|
+
visits swap `<body>` without reloading that head script, so `alpine:init` never
|
|
16
|
+
fires again — and because the registration lives only in the `/admin/style` body,
|
|
17
|
+
it was absent during that one `alpine:init`. The specimen cards' `x-data` still
|
|
18
|
+
re-initialize on the Turbo body swap (Alpine's MutationObserver), so their
|
|
19
|
+
`@click="$store.dsModals.open(…)"`, `:style` glow bindings, and the host's
|
|
20
|
+
`<template x-if="$store.dsModals.current()…">` all evaluated against an undefined
|
|
21
|
+
store and threw `Cannot read properties of undefined (reading 'current')` — no
|
|
22
|
+
modal opened, and the throwing `:style` wiped each card's static inline
|
|
23
|
+
`--studio-team-glow-opacity: 0`, so the CSS default of `1` relit all rings.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **`style/_modals`** — the `dsModals` + `dsSolanaModal` registration is now a
|
|
28
|
+
named `registerDsModals()` invoked through the same dual-guard the engine
|
|
29
|
+
already uses for `studio/modals/_image_upload`'s `cropPhotoModal`:
|
|
30
|
+
`if (window.Alpine) registerDsModals(); else document.addEventListener('alpine:init', registerDsModals);`.
|
|
31
|
+
On a Turbo visit Alpine has already started, so the store registers immediately
|
|
32
|
+
(the body script re-runs on every Turbo render) and `$store.dsModals` is defined
|
|
33
|
+
before the cards' bindings evaluate; on a first load Alpine has not booted yet,
|
|
34
|
+
so it still defers to `alpine:init`. The `if (Alpine.store('dsModals')) return;`
|
|
35
|
+
idempotency guard is unchanged, so a later `alpine:init` is a no-op. No consumer
|
|
36
|
+
change is required — `/admin/style` is an engine page.
|
|
37
|
+
- **Docs** — `style/_modal_specimen`'s fail-closed comment now records that the
|
|
38
|
+
static inline `--studio-team-glow-opacity: 0` guards the ring only while
|
|
39
|
+
`$store.dsModals` is DEFINED (a throwing `:style` wipes the inline value) — the
|
|
40
|
+
refinement of 0.24.1's "regardless of Alpine timing" claim that this bug exposed.
|
|
41
|
+
|
|
42
|
+
### Swept
|
|
43
|
+
|
|
44
|
+
- Audited every engine `Alpine.store(...)` / `Alpine.data(...)` registration for
|
|
45
|
+
the same Turbo-visit fragility. `dsModals`/`dsSolanaModal` (`style/_modals`) was
|
|
46
|
+
the only page-scoped registration missing the guard. `studio/modals/_image_upload`
|
|
47
|
+
(`cropPhotoModal`) already carries it; the shared modal host (`studio/modals/_host`
|
|
48
|
+
→ `modals`) and the theme/devMode stores (`layouts/studio/_head`) are app-wide
|
|
49
|
+
bootstrap rendered on the first full load, so their single `alpine:init`
|
|
50
|
+
registration persists across Turbo visits (verified: `$store.modals` stays
|
|
51
|
+
defined after a Turbo nav).
|
|
52
|
+
|
|
53
|
+
## 0.26.0 — 2026-07-28
|
|
54
|
+
|
|
55
|
+
**The engine navbar pins itself under smooth-load, and the smooth-load CSS is
|
|
56
|
+
fully engine-owned.** Since 0.24 the `vt-pinned-header` pin was opt-in but the
|
|
57
|
+
engine's own `layouts/_navbar` never carried it, so a host that wanted the
|
|
58
|
+
pinned-header transition had to SHADOW the whole partial just to add one class
|
|
59
|
+
(acquisition-studio did exactly that — the known override-drift trap). The
|
|
60
|
+
navbar now self-pins when `Studio.smooth_load` is on, and the engine also ships
|
|
61
|
+
the `studio-header` cross-fade suppression that mcritchie-studio and
|
|
62
|
+
turf-monster had been carrying app-side as a bridge. **Consumer cleanup this
|
|
63
|
+
version unlocks:** acquisition-studio deletes its entire
|
|
64
|
+
`app/views/layouts/_navbar.html.erb` override; mcritchie-studio and
|
|
65
|
+
turf-monster delete their app-side `::view-transition-old(studio-header)` /
|
|
66
|
+
`::view-transition-new(studio-header) { animation: none; }` bridge blocks.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **`layouts/_navbar`** — the sticky header adds `vt-pinned-header` itself when
|
|
71
|
+
`Studio.smooth_load` is on, non-preview branch ONLY (preview renders can
|
|
72
|
+
repeat per page, and a duplicate `view-transition-name` silently disables
|
|
73
|
+
every transition). With the flag off nothing extra renders, so a plain gem
|
|
74
|
+
bump changes no opted-out app.
|
|
75
|
+
- **`engine.css`** — new `::view-transition-old(studio-header)` /
|
|
76
|
+
`::view-transition-new(studio-header) { animation: none; }` rule beside the
|
|
77
|
+
smooth-load block: it suppresses the UA-default ~250ms plus-lighter
|
|
78
|
+
cross-fade between the header's two snapshots, which double-drew the wordmark
|
|
79
|
+
and buttons on any navigation from a scrolled page (collapsed header →
|
|
80
|
+
expanded at top). Lifts the identical bridge rule the hub and turf-monster
|
|
81
|
+
carried app-side.
|
|
82
|
+
- **`engine.css`** — `.turbo-progress-bar` background becomes
|
|
83
|
+
`var(--color-cta, #0076ff)`: the fallback (Turbo's own default blue) keeps
|
|
84
|
+
the bar visible in a layout that never renders the runtime theme block
|
|
85
|
+
(`studio_theme_css_tag`), where `--color-cta` is unset.
|
|
86
|
+
|
|
87
|
+
## 0.25.1 — 2026-07-28
|
|
88
|
+
|
|
89
|
+
**Fix the Profile Leveling save-close regression 0.25.0 introduced.** The 0.25.0
|
|
90
|
+
rebuild made `_finishSaved` ALWAYS advance to the engine's own updated view (the
|
|
91
|
+
seeds celebration or the plain confirmation), on the belief that Turf Monster's
|
|
92
|
+
live usage was "unaffected." It was not: TM's change-username modal
|
|
93
|
+
(`leveling: false`, `saved_event: "studio:username-saved"`) drives its OWN
|
|
94
|
+
post-save follow-on — its listener runs `completeQuest` and closes/swaps. With
|
|
95
|
+
the engine now also advancing to its own confirm view, the next quest step
|
|
96
|
+
("Send Your First Message") rendered TWICE at once — once in the post-save modal
|
|
97
|
+
dialog, once in the inline quest card behind it — which TM's Playwright e2e
|
|
98
|
+
caught as a strict-mode double-render (`quest_ladder_web2`, `quest_ladder_web3`,
|
|
99
|
+
and the web3 step-dedup contract). In 0.24 a `leveling: false` modal rendered no
|
|
100
|
+
celebrate view and let the app close it, so the step rendered once.
|
|
101
|
+
|
|
102
|
+
### Fixed
|
|
103
|
+
|
|
104
|
+
- **`studio/_leveling_activity_assets`** (factory) — `_finishSaved` now CLOSES on
|
|
105
|
+
save (fires the `saved_event`, then stops — the 0.24 contract) instead of
|
|
106
|
+
advancing to the engine's own confirm/celebrate WHEN the consuming app drives
|
|
107
|
+
its own follow-on. A new `appDrivenFollowOn` getter is that signal: a **non-demo**
|
|
108
|
+
caller that wired its OWN (non-default) `saved_event` owns the after-save UI, so
|
|
109
|
+
the engine yields (it never force-closes — the app's listener owns close-vs-swap,
|
|
110
|
+
which TM relies on: close on the contest page, swap to `quest-success` on
|
|
111
|
+
`/account`). **Demo** previews (`/admin/style`) and **standalone** callers on the
|
|
112
|
+
DEFAULT `saved_event` are unchanged — they still advance to the seeds celebration
|
|
113
|
+
(TM shape) or the plain confirmation (MS shape), and the "Great Username" /
|
|
114
|
+
"Subscribed!" specimen cards still open straight at their celebrate state via
|
|
115
|
+
`props.celebrate`. No consumer change is required; TM's existing wiring restores
|
|
116
|
+
its own contract.
|
|
117
|
+
|
|
118
|
+
|
|
6
119
|
|
|
7
120
|
**Profile Leveling** — the `/admin/style` "Leveling activities" section is rebuilt
|
|
8
121
|
into a single toggle-driven "Profile Leveling" flow, and the with-leveling /
|
|
@@ -79,6 +192,12 @@ follows the step machine), so this surfaced as a flash-of-all-glow on load.
|
|
|
79
192
|
unchanged. The `engine-motion.css` `.studio-team-glow` default is untouched
|
|
80
193
|
(the always-on Tricks demos depend on it).
|
|
81
194
|
|
|
195
|
+
> **Refined in 0.26.1:** "regardless of Alpine timing" holds only while
|
|
196
|
+
> `$store.dsModals` is defined. On a Turbo Drive visit the store went
|
|
197
|
+
> unregistered, the reactive `:style` threw, and Alpine wiped this inline
|
|
198
|
+
> default — relighting every ring. 0.26.1 dual-guards the store registration so
|
|
199
|
+
> it survives Turbo visits, restoring the fail-closed guarantee.
|
|
200
|
+
|
|
82
201
|
## 0.24.0 — 2026-07-28
|
|
83
202
|
|
|
84
203
|
The **smooth-load convention**, opt-in per app. Pages materialize behind the
|
|
@@ -221,12 +221,28 @@
|
|
|
221
221
|
view-transition-name: studio-header;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
/* Suppress the UA default on the pinned header's own group: without this the
|
|
225
|
+
browser runs its ~250ms plus-lighter cross-fade between the header's old and
|
|
226
|
+
new snapshots, and on any navigation from a scrolled page (collapsed header
|
|
227
|
+
→ expanded at top) the wordmark and buttons draw TWICE, offset by the height
|
|
228
|
+
delta, until the blend settles. animation: none shows the incoming header
|
|
229
|
+
immediately while the group box still morphs its height. Engine-owned as of
|
|
230
|
+
0.26.0 — this lifts the identical bridge rule mcritchie-studio and
|
|
231
|
+
turf-monster carried app-side; consumers delete their copies when adopting
|
|
232
|
+
this version. */
|
|
233
|
+
::view-transition-old(studio-header),
|
|
234
|
+
::view-transition-new(studio-header) {
|
|
235
|
+
animation: none;
|
|
236
|
+
}
|
|
237
|
+
|
|
224
238
|
/* Turbo's progress bar is the slow-load feedback under no-preview (the old
|
|
225
239
|
page holds until the fresh response). Theme it so every app's bar matches
|
|
226
|
-
its brand instead of Turbo's default.
|
|
240
|
+
its brand instead of Turbo's default. The fallback (#0076ff, Turbo's own
|
|
241
|
+
default blue) keeps the bar visible in a layout that never renders the
|
|
242
|
+
runtime theme block (studio_theme_css_tag), where --color-cta is unset. */
|
|
227
243
|
.turbo-progress-bar {
|
|
228
244
|
height: 3px;
|
|
229
|
-
background: var(--color-cta);
|
|
245
|
+
background: var(--color-cta, #0076ff);
|
|
230
246
|
}
|
|
231
247
|
|
|
232
248
|
/* Reduced motion: keep the single-render swap, drop the choreography. Also
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
%>
|
|
9
9
|
<% show_user = local_assigns.fetch(:show_logged_in, logged_in?) %>
|
|
10
10
|
<% is_preview = local_assigns.fetch(:preview, false) %>
|
|
11
|
+
<%# Smooth-load self-pin (engine 0.26): when the app opts into Studio.smooth_load,
|
|
12
|
+
the sticky header names itself as the shared view-transition group
|
|
13
|
+
(vt-pinned-header) so page content transitions beneath a navbar that stays
|
|
14
|
+
put — hosts no longer shadow this partial just to add the class. Non-preview
|
|
15
|
+
branch ONLY: preview renders can repeat per page, and a duplicate
|
|
16
|
+
view-transition-name silently disables every transition. With the flag off,
|
|
17
|
+
nothing extra renders. %>
|
|
18
|
+
<% pin_header = Studio.smooth_load && !is_preview %>
|
|
11
19
|
<% balance_html = local_assigns.fetch(:balance_html, nil) %>
|
|
12
20
|
<% extra_icons_html = local_assigns.fetch(:extra_icons_html, nil) %>
|
|
13
21
|
<% show_logout_link = local_assigns.fetch(:show_logout_link, false) %>
|
|
@@ -19,7 +27,7 @@
|
|
|
19
27
|
%>
|
|
20
28
|
|
|
21
29
|
<header x-data="{ scrolled: false }" <%= '@scroll.window="scrolled = scrolled ? (window.scrollY > 5) : (window.scrollY > 60)"'.html_safe unless is_preview %>
|
|
22
|
-
class="<%= is_preview ? 'bg-page' : 'sticky top-0 z-50 bg-page transition-shadow duration-300
|
|
30
|
+
class="<%= is_preview ? 'bg-page' : "#{'vt-pinned-header ' if pin_header}sticky top-0 z-50 bg-page transition-shadow duration-300" %>"
|
|
23
31
|
:class="scrolled && 'shadow-lg border-b border-subtle is-scrolled'">
|
|
24
32
|
<style>
|
|
25
33
|
.user-nav-col { width: 14rem; }
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
if none); it owns EVERY app-side detail of that step.
|
|
38
38
|
savedEvent DOM event dispatched on success, carrying the app payload, so
|
|
39
39
|
the app can run its own follow-on. Default "studio:activity-saved".
|
|
40
|
+
A non-demo caller that supplies its OWN (non-default) saved_event
|
|
41
|
+
is declaring it drives that follow-on: the engine then CLOSES on
|
|
42
|
+
save (fires the event, stops) rather than rendering its own
|
|
43
|
+
confirm/celebrate, so the app's follow-on never renders twice.
|
|
40
44
|
store Alpine modal store name backing close(). Default "modals".
|
|
41
45
|
leveling the render-time DEFAULT for the seeds celebration. The live
|
|
42
46
|
value is read at RUNTIME from the modal store's current
|
|
@@ -56,6 +60,11 @@
|
|
|
56
60
|
<script>
|
|
57
61
|
(function () {
|
|
58
62
|
if (window.levelingActionModal) return;
|
|
63
|
+
// The default saved event. A non-demo caller that wires its OWN (non-default)
|
|
64
|
+
// saved_event is declaring it drives the post-save follow-on itself — see the
|
|
65
|
+
// appDrivenFollowOn getter, which then makes the engine close on save instead of
|
|
66
|
+
// rendering its own confirm/celebrate.
|
|
67
|
+
var DEFAULT_SAVED_EVENT = "studio:activity-saved";
|
|
59
68
|
window.levelingActionModal = function (opts) {
|
|
60
69
|
opts = opts || {};
|
|
61
70
|
return {
|
|
@@ -66,7 +75,7 @@
|
|
|
66
75
|
submitUrl: opts.submitUrl || "",
|
|
67
76
|
finalizeUrl: opts.finalizeUrl || "",
|
|
68
77
|
finalizeHook: opts.finalizeHook || "",
|
|
69
|
-
savedEvent: opts.savedEvent ||
|
|
78
|
+
savedEvent: opts.savedEvent || DEFAULT_SAVED_EVENT,
|
|
70
79
|
store: opts.store || "modals",
|
|
71
80
|
// leveling is a RUNTIME getter (below) reading props.leveling off the modal
|
|
72
81
|
// store, so ONE modal id flips TM <-> MS shape live as a section toggle
|
|
@@ -129,6 +138,21 @@
|
|
|
129
138
|
return v !== (this.original || "").trim();
|
|
130
139
|
},
|
|
131
140
|
|
|
141
|
+
// appDrivenFollowOn — a real (non-demo) consumer that wired its OWN saved_event
|
|
142
|
+
// owns the after-save UI: its listener runs the follow-on (e.g. Turf Monster's
|
|
143
|
+
// completeQuest / quest-success on 'studio:username-saved'), including whether
|
|
144
|
+
// to close, swap, or advance. For those the engine must NOT advance to its own
|
|
145
|
+
// confirm/celebrate on save — that double-renders the app's follow-on step
|
|
146
|
+
// (once in this dialog, once in the app's own inline card: the strict-mode
|
|
147
|
+
// double-render TM's e2e caught). The engine yields: it fires the saved event,
|
|
148
|
+
// then STOPS (the 0.24 contract), letting the app drive. Demo previews (self-
|
|
149
|
+
// contained) and standalone callers on the DEFAULT event keep the engine's own
|
|
150
|
+
// confirm/celebrate.
|
|
151
|
+
get appDrivenFollowOn() {
|
|
152
|
+
if (this.demo) return false;
|
|
153
|
+
return this.savedEvent !== DEFAULT_SAVED_EVENT;
|
|
154
|
+
},
|
|
155
|
+
|
|
132
156
|
_dispatch: function (payload) {
|
|
133
157
|
// In demo mode (the /admin/style preview) do NOT fire the app-facing
|
|
134
158
|
// saved event: the host app may listen for it and open its OWN follow-on
|
|
@@ -149,10 +173,19 @@
|
|
|
149
173
|
this.progressLabel = "";
|
|
150
174
|
if (this.hasInput) this.original = (this.value || "").trim();
|
|
151
175
|
this._dispatch(payload);
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
176
|
+
// App-driven follow-on: the consumer wired its own saved_event and owns
|
|
177
|
+
// what comes next (its listener advances/closes/swaps). Dispatch fired
|
|
178
|
+
// above; STOP here — the 0.24 contract — instead of advancing to the
|
|
179
|
+
// engine's own confirm/celebrate, which would render the app's follow-on
|
|
180
|
+
// step twice. The engine never force-closes here: the app's listener owns
|
|
181
|
+
// close-vs-swap (TM closes on the contest page, swaps to quest-success on
|
|
182
|
+
// /account), so a force-close would clobber that swap.
|
|
183
|
+
if (this.appDrivenFollowOn) return;
|
|
184
|
+
// Otherwise (a demo preview, or a standalone caller on the DEFAULT event
|
|
185
|
+
// with no app follow-on): advance to the updated state; the view gate
|
|
186
|
+
// (celebrate && leveling vs celebrate && !leveling) picks the seeds
|
|
187
|
+
// celebration (TM shape) or the plain confirmation (MS shape). Seeds are
|
|
188
|
+
// set either way — only the leveling view reads them.
|
|
156
189
|
this.seedsEarned = parseInt(payload.seeds_earned, 10) || 0;
|
|
157
190
|
this.seedsTotal = parseInt(payload.seeds_total, 10) || 0;
|
|
158
191
|
this.celebrate = true;
|
|
@@ -59,7 +59,14 @@
|
|
|
59
59
|
default (--studio-team-glow-opacity of 1) so the ring is OFF at first
|
|
60
60
|
paint, BEFORE Alpine hydrates the reactive :style below. Without it every
|
|
61
61
|
glow card shows its ring until Alpine boots (the fail-open flash). The
|
|
62
|
-
:style then drives 0 to 0.95 with the 0.4s cross-fade. Keep BOTH.
|
|
62
|
+
:style then drives 0 to 0.95 with the 0.4s cross-fade. Keep BOTH.
|
|
63
|
+
CAVEAT: this fail-closed default holds only while $store.dsModals is
|
|
64
|
+
DEFINED. glow_when dereferences the store; if the store is missing the
|
|
65
|
+
reactive :style THROWS, and Alpine's style bind wipes this inline value
|
|
66
|
+
on the way, so the CSS default of 1 relights every ring. That is exactly
|
|
67
|
+
the Turbo-nav store gap fixed in 0.26.1 (style/_modals dual-guards the
|
|
68
|
+
dsModals registration so it survives Turbo visits, not just first load).
|
|
69
|
+
So the guarantee is: static inline 0 PLUS a registered dsModals. %>
|
|
63
70
|
<% if glow_when %>style="--studio-team-glow-opacity: 0" :style="{ '--studio-team-glow-opacity': (<%= glow_when %>) ? '0.95' : '0' }"<% end %>>
|
|
64
71
|
<article
|
|
65
72
|
class="card overflow-hidden flex flex-col h-full<%= " opacity-95" if disabled %><%= " cursor-pointer" if clickable %>"
|
|
@@ -107,7 +107,10 @@
|
|
|
107
107
|
isAvailable: function () { return false; }
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
// Register the page-scoped dsModals + dsSolanaModal stores. Named (not an
|
|
111
|
+
// inline alpine:init callback) so the dual-guard at the end can invoke it
|
|
112
|
+
// directly on a Turbo visit, when alpine:init will not fire again.
|
|
113
|
+
function registerDsModals() {
|
|
111
114
|
if (Alpine.store('dsModals')) return;
|
|
112
115
|
|
|
113
116
|
// Enter/exit animation registry — keys map to a CSS class (engine-
|
|
@@ -244,7 +247,19 @@
|
|
|
244
247
|
get ctaLabel() { return this._read('ctaLabel', ''); },
|
|
245
248
|
get ctaHref() { return this._read('ctaHref', ''); }
|
|
246
249
|
});
|
|
247
|
-
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Alpine's deferred CDN <script> fires alpine:init exactly ONCE, on the
|
|
253
|
+
// first full-document load. Turbo Drive advance visits swap the document
|
|
254
|
+
// body without reloading that head script, so alpine:init never fires
|
|
255
|
+
// again — and
|
|
256
|
+
// because this dsModals registration lives ONLY in the /admin/style body,
|
|
257
|
+
// it was absent during that one alpine:init. Registering here on the
|
|
258
|
+
// Turbo-visit path (Alpine already started) keeps $store.dsModals defined,
|
|
259
|
+
// so the specimen cards' @click / :style / <template x-if> don't throw.
|
|
260
|
+
// Mirrors studio/modals/_image_upload's cropPhotoModal guard.
|
|
261
|
+
if (window.Alpine) { registerDsModals(); }
|
|
262
|
+
else { document.addEventListener('alpine:init', registerDsModals); }
|
|
248
263
|
})();
|
|
249
264
|
</script>
|
|
250
265
|
|
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.
|
|
4
|
+
version: 0.26.1
|
|
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-07-
|
|
11
|
+
date: 2026-07-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|