studio-engine 0.25.1 → 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
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,6 +2,88 @@
|
|
|
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.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
|
+
|
|
5
87
|
## 0.25.1 — 2026-07-28
|
|
6
88
|
|
|
7
89
|
**Fix the Profile Leveling save-close regression 0.25.0 introduced.** The 0.25.0
|
|
@@ -110,6 +192,12 @@ follows the step machine), so this surfaced as a flash-of-all-glow on load.
|
|
|
110
192
|
unchanged. The `engine-motion.css` `.studio-team-glow` default is untouched
|
|
111
193
|
(the always-on Tricks demos depend on it).
|
|
112
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
|
+
|
|
113
201
|
## 0.24.0 — 2026-07-28
|
|
114
202
|
|
|
115
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; }
|
|
@@ -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
|