studio-engine 0.61.1 → 0.61.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: '0082dfd62bcd08f1a3a540bc351c6171c27a563d85aa50d7492c1f0bb1f0271a'
4
- data.tar.gz: 920156a74de7a1bbc1953a29ef01cac6b79c7d5aaad81a9f223d64d72924702d
3
+ metadata.gz: e8e859fc4ea59dafad01b2aef524f8ff75bb2b5040bc05611ac17eec335c5c0a
4
+ data.tar.gz: ca4d9a0ed867017686c50d329190ef26d7488ea28349fa65996bd8b859ff4eee
5
5
  SHA512:
6
- metadata.gz: ff49493b81e76496c8977d2403d1b3855e8146db9f4c452a0f81707bf2aebca3c8607202d90e3c91c91c5e8e56b152b5113750b3c051dbf83c5e9340484406ea
7
- data.tar.gz: 525b285e971042834e9761b78cd359b7fc24a431bdf6e06ba6a1082fd80603ae1b7f7e1f8fbb68d69d22a174da53c429a6da505b4c534066df03b76bf7d3fbc3
6
+ metadata.gz: 805ad1e2f8d05504bb4ca7e782ab27fab96589dbe3acd5714502c24b0803de69631241a1a3374da1d7f92a762b1c21f6eb4122ba7c6cdafc3231844f2f03b479
7
+ data.tar.gz: 5cc030a1e38828fc0db6212f8686f6438253f00adf8a84dfa26faad576bf86fdda137b2d806e79d78a241b2b21ac706c39de6f99b694eedccfa034ec37669012
data/CHANGELOG.md CHANGED
@@ -233,6 +233,31 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
233
233
 
234
234
  ### Fixed
235
235
 
236
+ - **The age gate's back link now returns the date the person already entered.**
237
+ `blocks/_age_gate`'s "Update your Birthday" swapped back to `blocks/_birthday`
238
+ with an EMPTY props object, discarding the `dobYear`/`dobMonth`/`dobDay` parts
239
+ the birthday factory had just handed across the store — and the factory started
240
+ its three fields at `""` and never read them anyway. So the correction path came
241
+ back BLANK: a mistyped year cost all three picks, on a card whose own header
242
+ comment promised "a correction, not a restart".
243
+
244
+ Both sides of the seam moved. `back()` forwards the three date parts — and only
245
+ those three, deliberately: `minAge`, `state` and `message` describe the refusal,
246
+ and `validates` is load-bearing by its ABSENCE (the style guide specimen reads an
247
+ absent prop as validating, and a forwarded stale one would let the refusal pick
248
+ the next card's mode). `window.birthdayModal` gained an `init` that re-picks the
249
+ three selects from the props of the modal entry it mounted in, clamping a day
250
+ that the restored month cannot hold.
251
+
252
+ **The gate is not weakened.** Restoring the date restores the DATE and never the
253
+ verdict: the card comes back submittable and the app's endpoint re-decides on the
254
+ next submit exactly as it did on the first. `e2e/birthday_gate.spec.js` asserts a
255
+ restored under-age date is refused again.
256
+
257
+ **No consumer action.** A host that renders `blocks/_age_gate` and
258
+ `studio/_birthday_assets` gets this by upgrading; nothing in the call signature
259
+ changed and no host wiring moves.
260
+
236
261
  - **The Geo signpost now reaches every app whose admin chrome the engine owns.**
237
262
  The row added last release went into `components/_admin_dropdown` alone, on the
238
263
  premise that the shared dropdown reaches every app from one change. It does
@@ -29,6 +29,13 @@
29
29
  age-gate card instead. `_finish` is unchanged: the success path still flips
30
30
  session.ageVerified and dispatches `age-verified`, which is the hook consuming
31
31
  apps already listen on, so nothing downstream had to change.
32
+
33
+ AND THE HANDOFF RETURNS (2026-08-26). `_reject` sends dobYear/dobMonth/dobDay
34
+ across the store; the gate card's back() hands the same three parts back; and
35
+ `init` re-picks the three selects from them, so the correction the gate card
36
+ offers costs one scroll rather than all three fields. The date is restored,
37
+ never the VERDICT: the card comes back submittable and the app's endpoint
38
+ re-decides on the next submit exactly as it did on the first.
32
39
  %>
33
40
  <script>
34
41
  (function () {
@@ -89,6 +96,56 @@
89
96
  if (this.day && parseInt(this.day, 10) > this.dayOptions.length) this.day = "";
90
97
  this.error = "";
91
98
  },
99
+ // THE RETURN TRIP. _reject writes dobYear/dobMonth/dobDay onto the store
100
+ // on the way out to the age-gate card, and that card's back() hands the
101
+ // same three parts straight back — so a card mounted from a return trip
102
+ // finds the person's date on its own modal entry and re-picks the three
103
+ // selects. Without this the factory started at "" whatever it was handed,
104
+ // and a mistyped year cost all three fields (fixed 2026-08-26).
105
+ //
106
+ // Read off the STORE rather than taken as an opt: that is where this
107
+ // factory already meets the store (_reject writes to the same object
108
+ // through the same guard), the ERB locals in blocks/_birthday are
109
+ // server-side and cannot carry a client value anyway, and it means a
110
+ // consuming app that renders the card needs no change at all.
111
+ //
112
+ // STRINGS, because that is what the selects produce: the options render
113
+ // :value="m.n" — a number stringified by the DOM — and x-model writes
114
+ // that string back. Seeding a Number would leave the field holding a type
115
+ // the person's own pick never produces.
116
+ _returnedDob() {
117
+ var props;
118
+ try {
119
+ var entry = Alpine.store(this.store).current();
120
+ props = (entry && entry.props) || {};
121
+ } catch (e) {
122
+ return null;
123
+ }
124
+ var y = parseInt(props.dobYear, 10),
125
+ m = parseInt(props.dobMonth, 10),
126
+ d = parseInt(props.dobDay, 10);
127
+ if (!y || !m || !d) return null;
128
+ return { year: String(y), month: String(m), day: String(d) };
129
+ },
130
+ // Alpine calls init() on the data object at MOUNT, synchronously, inside
131
+ // the x-data directive and before it walks the three <select>s below
132
+ // (Alpine 3.16.1 as vendored: `a.init && T(t, a.init)`). So this is the
133
+ // fields' initial state rather than a later mutation of them, and x-model
134
+ // paints the selects from it. Measured in chromium: all three hold the
135
+ // restored values on the FIRST frame the card exists, six runs out of six
136
+ // — no deferral needed, and e2e/birthday_gate.spec.js is what watches it.
137
+ init() {
138
+ var dob = this._returnedDob();
139
+ if (!dob) return;
140
+ this.year = dob.year;
141
+ this.month = dob.month;
142
+ // Day LAST, and clamped: dayOptions is only the restored month's list
143
+ // once month and year are set, and an impossible pair from a hand-built
144
+ // props bag (Feb 30) must land empty rather than leave the select
145
+ // holding a value its own options do not contain — which would read as
146
+ // complete and submit a date nobody picked.
147
+ this.day = parseInt(dob.day, 10) <= this.dayOptions.length ? dob.day : "";
148
+ },
92
149
  // Success path: flip a session flag if present, close via the app's store,
93
150
  // and let the app resume its own flow (the entry gate listens for this).
94
151
  _finish() {
@@ -16,7 +16,14 @@
16
16
  the back link — the thing they might NEED. A wrong birthday is an ordinary
17
17
  typo (a mis-picked year is one scroll away), and a gate with no
18
18
  way back turns a typo into a locked door. swap()s back to the
19
- birthday card, so this is a correction, not a restart.
19
+ birthday card CARRYING THE DATE, so a mistyped year costs one
20
+ scroll and not all three fields. That is what makes this a
21
+ correction rather than a restart: back() forwards the dobYear /
22
+ dobMonth / dobDay parts _reject handed across the store on the
23
+ way in, and the birthday factory seeds its three selects from
24
+ them. Until 2026-08-26 it swapped with an EMPTY props object and
25
+ the card came back blank, which is the restart this paragraph
26
+ disclaimed.
20
27
 
21
28
  DELIBERATELY NOT DISMISSIBLE-BY-DEFAULT-ONLY: the host still decides via
22
29
  props.dismissible at open() time, exactly like every other card. What this
@@ -146,7 +153,26 @@
146
153
  // sibling cards that poll: modals/_web3_step_up, blocks/_cta_redirect,
147
154
  // blocks/_seeds_bar.
148
155
  destroy() { this.stop(); },
149
- back() { $store.<%= modal_store %>.swap('<%= j birthday_modal_id.to_s %>', {}); }
156
+ // THE DATE RIDES BACK. _reject put these three parts on the store on the
157
+ // way here so this card could say WHEN; handing them back is what makes
158
+ // the return trip a correction instead of a blank form. This swapped with
159
+ // an empty object until 2026-08-26, and a mistyped year cost all three
160
+ // picks.
161
+ //
162
+ // THE THREE PARTS ONLY, deliberately not the whole props bag. minAge,
163
+ // state and message describe the REFUSAL: message would land as an error
164
+ // line on a card nobody has submitted yet, and `validates` is load-bearing
165
+ // by its ABSENCE — the style guide specimen reads an absent prop as
166
+ // validating, so forwarding a stale one would let this card pick the next
167
+ // card's mode (style/modals/_birthday.html.erb).
168
+ back() {
169
+ var p = this.props;
170
+ $store.<%= modal_store %>.swap('<%= j birthday_modal_id.to_s %>', {
171
+ dobYear: p.dobYear || null,
172
+ dobMonth: p.dobMonth || null,
173
+ dobDay: p.dobDay || null
174
+ });
175
+ }
150
176
  }"
151
177
  <% if countdown %>x-init="start()"<% end %>
152
178
  class="relative">
@@ -211,7 +237,9 @@
211
237
 
212
238
  <% if birthday_modal_id.present? %>
213
239
  <%# The correction path. A mis-picked year is one scroll away from a right
214
- one, and without this the typo is indistinguishable from the verdict. %>
240
+ one, and without this the typo is indistinguishable from the verdict.
241
+ back() carries the date across, so what comes back is the same three
242
+ picks with the wrong one still selected. %>
215
243
  <button type="button" @click="back()"
216
244
  class="block mx-auto mt-3 text-sm text-secondary hover:text-heading transition underline-offset-2 hover:underline">
217
245
  <%= back_label %>
@@ -23,11 +23,17 @@
23
23
 
24
24
  Why absent is not "no bar": the only card that can REACH the age gate is the
25
25
  validating one — the no-bar card carries no min_age, so the factory's _reject
26
- never fires. So every arrival with no props is a return trip from the gate,
27
- and `back()` swaps with EMPTY props by design (blocks/_age_gate.html.erb).
28
- Reading undefined as falsy sent that return trip to the NO-BAR card, where
29
- resubmitting the SAME under-age date was accepted — the guide demonstrating an
30
- age gate being bypassed. Caught in review, 2026-08-25.
26
+ never fires. So every arrival with no `validates` is a return trip from the
27
+ gate, and `back()` never sends one: it forwards the dobYear/dobMonth/dobDay
28
+ parts and NOTHING ELSE, deliberately, so the refusal cannot pick this
29
+ specimen's mode (blocks/_age_gate.html.erb). Reading undefined as falsy sent
30
+ that return trip to the NO-BAR card, where resubmitting the SAME under-age
31
+ date was accepted — the guide demonstrating an age gate being bypassed.
32
+ Caught in review, 2026-08-25.
33
+
34
+ So the default is still load-bearing, and it is load-bearing in the same way
35
+ after the gate started carrying the date back (2026-08-26): the props bag on
36
+ a return trip is no longer empty, but `validates` is still absent from it.
31
37
 
32
38
  The engine primitive is right and is NOT changed: in a real host the mode
33
39
  comes from ERB locals, not from a prop, so this is a two-mode SPECIMEN's
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.61.1"
2
+ VERSION = "0.61.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.61.1
4
+ version: 0.61.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie