studio-engine 0.25.0 → 0.25.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: ecc09d648e749381eed240dbdf6290e6899bfff776a08a8cf76e8cf6fde5da35
4
- data.tar.gz: bfa162a9a1078281ce421fcc9dcb4b3741fc30ab8bc9147b8eedbd7cee9a825b
3
+ metadata.gz: d87b5711dd0833ea78e20b6f3174dd57e5cc3b0311e56f962e8954a408943963
4
+ data.tar.gz: 81708f5604bdebaa3f08504169e4bf1a55ba8ea9d8856f04594dbe49295d553c
5
5
  SHA512:
6
- metadata.gz: d1cc7ce640975c429a0f85ea08cdc66037506efa681995128ad4a3ab76bf0894d1e60bf4738c3d52bc85a2625ea6ee21b60a420b3eef98af56008f0de33f92d5
7
- data.tar.gz: fd4465c53f2591ff8031fcebee0ce81dfa7acc1113a934b98983a761ad54a2a9a6d4dff8c8b6b1c41b7c8947166f36d69dbf4da182c7e8713b4eb986c79ee6f3
6
+ metadata.gz: b3ffce4a4ba6b91444902123ee106f9f88fec2be0606d69f310ff464bfe336ca444de94f85c2c81873feb335080e2a9fc01390ff48df7f87977e645e0c3397ac
7
+ data.tar.gz: 6584381c67a206525171c7195ded0999f8ce20ad896196aa8259b116e450c7db2a23ed95193dcb8281e49f57f09c5010817ce67e127bcc3d10c517ca4a67ca16
data/CHANGELOG.md CHANGED
@@ -2,7 +2,38 @@
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.25.0 — 2026-07-28
5
+ ## 0.25.1 — 2026-07-28
6
+
7
+ **Fix the Profile Leveling save-close regression 0.25.0 introduced.** The 0.25.0
8
+ rebuild made `_finishSaved` ALWAYS advance to the engine's own updated view (the
9
+ seeds celebration or the plain confirmation), on the belief that Turf Monster's
10
+ live usage was "unaffected." It was not: TM's change-username modal
11
+ (`leveling: false`, `saved_event: "studio:username-saved"`) drives its OWN
12
+ post-save follow-on — its listener runs `completeQuest` and closes/swaps. With
13
+ the engine now also advancing to its own confirm view, the next quest step
14
+ ("Send Your First Message") rendered TWICE at once — once in the post-save modal
15
+ dialog, once in the inline quest card behind it — which TM's Playwright e2e
16
+ caught as a strict-mode double-render (`quest_ladder_web2`, `quest_ladder_web3`,
17
+ and the web3 step-dedup contract). In 0.24 a `leveling: false` modal rendered no
18
+ celebrate view and let the app close it, so the step rendered once.
19
+
20
+ ### Fixed
21
+
22
+ - **`studio/_leveling_activity_assets`** (factory) — `_finishSaved` now CLOSES on
23
+ save (fires the `saved_event`, then stops — the 0.24 contract) instead of
24
+ advancing to the engine's own confirm/celebrate WHEN the consuming app drives
25
+ its own follow-on. A new `appDrivenFollowOn` getter is that signal: a **non-demo**
26
+ caller that wired its OWN (non-default) `saved_event` owns the after-save UI, so
27
+ the engine yields (it never force-closes — the app's listener owns close-vs-swap,
28
+ which TM relies on: close on the contest page, swap to `quest-success` on
29
+ `/account`). **Demo** previews (`/admin/style`) and **standalone** callers on the
30
+ DEFAULT `saved_event` are unchanged — they still advance to the seeds celebration
31
+ (TM shape) or the plain confirmation (MS shape), and the "Great Username" /
32
+ "Subscribed!" specimen cards still open straight at their celebrate state via
33
+ `props.celebrate`. No consumer change is required; TM's existing wiring restores
34
+ its own contract.
35
+
36
+
6
37
 
7
38
  **Profile Leveling** — the `/admin/style` "Leveling activities" section is rebuilt
8
39
  into a single toggle-driven "Profile Leveling" flow, and the with-leveling /
@@ -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 || "studio:activity-saved",
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
- // Always advance to the updated state; the view gate (celebrate &&
153
- // leveling vs celebrate && !leveling) picks the seeds celebration (TM)
154
- // or the plain confirmation (MS). Seeds are set either way only the
155
- // leveling view reads them.
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;
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.25.0"
2
+ VERSION = "0.25.1"
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.25.0
4
+ version: 0.25.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie