studio-engine 0.61.1 → 0.62.0
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 +25 -0
- data/app/views/studio/_birthday_assets.html.erb +57 -0
- data/app/views/studio/modals/blocks/_age_gate.html.erb +31 -3
- data/app/views/studio/modals/blocks/_entry_confirmed.html.erb +32 -2
- data/app/views/studio/modals/blocks/_success_card.html.erb +19 -4
- data/app/views/studio/modals/blocks/_wallet_brand_sprite.html.erb +20 -3
- data/app/views/style/modals/_birthday.html.erb +11 -5
- data/lib/studio/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3af6e5f7754c621df6bba0ef0972d018d48f0a08ede302530b5d5922e43328cf
|
|
4
|
+
data.tar.gz: e54f8c9cfbbd02cf9473e7e68ac6150a3a7c8da4ab861070da662184e1ee082d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08ae8d20d1a6e862e6985a2589221a87207d0ed562d0a6b696563b34d301cc77e6eef0bbc828074b09f104550041cbab78e529d8e553253b4b0de7dc5cb38432'
|
|
7
|
+
data.tar.gz: 1b785c9cab657acc47e1fd8d56109d91727086bf8b2aeb98bf0ebc1938bf550c3f6233fd4daa885daeab35a4b2a77b04b8f842cb14c10b7fec7f6cdcaab745b9
|
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
|
|
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
|
-
|
|
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,6 +23,23 @@
|
|
|
23
23
|
|
|
24
24
|
Locals (all optional; sensible props.* / static defaults):
|
|
25
25
|
modal_store — Alpine store name backing props/close. Default "modals".
|
|
26
|
+
props_expr — Alpine expression yielding the PROPS OBJECT. Default reads
|
|
27
|
+
$store.<modal_store>.current().props, which is the shape
|
|
28
|
+
the engine's own host uses. A consumer whose store is a
|
|
29
|
+
DIRECT-FIELD object with no .current() — turf-monster's
|
|
30
|
+
$store.solanaModal is one — passes that store itself here.
|
|
31
|
+
Without it every props.* default below resolves to {} and
|
|
32
|
+
the card renders complete with none of its numbers.
|
|
33
|
+
above_seeds — partial path rendered INSIDE the card, above the seeds bar
|
|
34
|
+
(e.g. a contest kickoff countdown). Named locals rather
|
|
35
|
+
than a second block: _success_card's yield is already
|
|
36
|
+
spent on the leveling enrichment, and blocks/_card_header
|
|
37
|
+
documents the Rails hazard where a partial's block_given?
|
|
38
|
+
reads TRUE off a layout yield with no block passed.
|
|
39
|
+
above_seeds_locals — locals for it. Default {}.
|
|
40
|
+
slot_position — passed through to _success_card. Default :above_cta here,
|
|
41
|
+
because a celebration's button is where the card ENDS:
|
|
42
|
+
anything under it is furniture behind the exit.
|
|
26
43
|
title_key — Alpine expr for the headline. Default "props.title || 'Good Luck'".
|
|
27
44
|
subtitle — static sub-headline. Default "Entry Confirmed".
|
|
28
45
|
subtitle_key — Alpine expr for the sub-headline (priority over subtitle).
|
|
@@ -37,6 +54,11 @@
|
|
|
37
54
|
%>
|
|
38
55
|
<%
|
|
39
56
|
modal_store = local_assigns.fetch(:modal_store, "modals")
|
|
57
|
+
props_expr = local_assigns.fetch(:props_expr,
|
|
58
|
+
"(function () { var c = $store.#{modal_store}.current(); return (c && c.props) || {}; })()")
|
|
59
|
+
above_seeds = local_assigns[:above_seeds]
|
|
60
|
+
above_seeds_locals = local_assigns.fetch(:above_seeds_locals, {})
|
|
61
|
+
slot_position = local_assigns.fetch(:slot_position, :above_cta)
|
|
40
62
|
title_key = local_assigns.fetch(:title_key, "props.title || 'Good Luck'")
|
|
41
63
|
subtitle = local_assigns.fetch(:subtitle, "Entry Confirmed")
|
|
42
64
|
subtitle_key = local_assigns[:subtitle_key]
|
|
@@ -59,7 +81,8 @@
|
|
|
59
81
|
cta_href_key: lobby_url_key,
|
|
60
82
|
cta_drain: true,
|
|
61
83
|
auto_redirect_url_key: lobby_url_key,
|
|
62
|
-
confetti: true
|
|
84
|
+
confetti: true,
|
|
85
|
+
slot_position: slot_position
|
|
63
86
|
}
|
|
64
87
|
if subtitle_key
|
|
65
88
|
sc_locals[:message_key] = subtitle_key
|
|
@@ -70,8 +93,15 @@
|
|
|
70
93
|
<%# props getter + clusterParam live on the root scope so _success_card's nested
|
|
71
94
|
Alpine expressions (props.txSignature, props.lobbyUrl, clusterParam) resolve up
|
|
72
95
|
the scope chain. clusterParam feeds _success_card's branded tx-link URL. %>
|
|
73
|
-
<div x-data="{ get props() {
|
|
96
|
+
<div x-data="{ get props() { return <%= props_expr %>; }, clusterParam: '<%= cluster_param %>' }">
|
|
74
97
|
<%= render "studio/modals/blocks/success_card", sc_locals do %>
|
|
98
|
+
<%# The app's own enrichment, above the seeds bar. turf puts its kickoff
|
|
99
|
+
countdown here — "when does it start" reads before the celebratory seeds
|
|
100
|
+
animation, not after it. %>
|
|
101
|
+
<% if above_seeds.present? %>
|
|
102
|
+
<%= render above_seeds, **above_seeds_locals %>
|
|
103
|
+
<% end %>
|
|
104
|
+
|
|
75
105
|
<% if Studio.feature?(:leveling) %>
|
|
76
106
|
<%# Leveling enrichment — mounts only when this entry awarded seeds. %>
|
|
77
107
|
<template x-if="(<%= seeds_earned_key %>) > 0">
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
on the left and a launch arrow on the
|
|
36
36
|
right. The "canonical" tx link.
|
|
37
37
|
|
|
38
|
+
slot_position: :below_cta (default) or :above_cta — where the block
|
|
39
|
+
renders. See the note on the slot itself.
|
|
38
40
|
cta_label: primary CTA button text.
|
|
39
41
|
cta_href_key: Alpine expression for the button's href.
|
|
40
42
|
Mutually exclusive with cta_event.
|
|
@@ -70,6 +72,7 @@
|
|
|
70
72
|
fire_confetti = !!local_assigns[:confetti]
|
|
71
73
|
large_title = !!local_assigns[:large_title]
|
|
72
74
|
use_drain = has_redirect && !!local_assigns[:cta_drain]
|
|
75
|
+
slot_position = local_assigns.fetch(:slot_position, :below_cta).to_sym
|
|
73
76
|
|
|
74
77
|
data_attr = "{
|
|
75
78
|
_remaining: #{redirect_secs},
|
|
@@ -182,6 +185,18 @@
|
|
|
182
185
|
</p>
|
|
183
186
|
<% end %>
|
|
184
187
|
|
|
188
|
+
<%# The block, when the caller wants it ABOVE the button.
|
|
189
|
+
WHY THE POSITION IS A CHOICE. Below the CTA is right for an afterthought —
|
|
190
|
+
a badge, a stat that ticks. It is wrong for anything the person is meant to
|
|
191
|
+
READ before deciding, because the button is where a celebration card ends:
|
|
192
|
+
put a countdown under it and it is furniture behind the exit. turf's entry
|
|
193
|
+
card has always run its kickoff timer and seeds bar above the button for
|
|
194
|
+
that reason, and this is what lets the engine's card do the same instead of
|
|
195
|
+
the app forking one to keep the order. %>
|
|
196
|
+
<% if block_given? && slot_position == :above_cta %>
|
|
197
|
+
<div class="mb-4"><%= yield %></div>
|
|
198
|
+
<% end %>
|
|
199
|
+
|
|
185
200
|
<%# Primary CTA — three flavors:
|
|
186
201
|
1. cta_drain + auto_redirect: countdown drains over the button
|
|
187
202
|
2. cta_href_key: anchor
|
|
@@ -219,10 +234,10 @@
|
|
|
219
234
|
</button>
|
|
220
235
|
<% end %>
|
|
221
236
|
|
|
222
|
-
<%#
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
<% if block_given? %>
|
|
237
|
+
<%# ...or below it, which stays the DEFAULT: every existing caller renders here
|
|
238
|
+
and none of them asked to move. A partial can only yield once, so exactly
|
|
239
|
+
one of these two branches runs. %>
|
|
240
|
+
<% if block_given? && slot_position != :above_cta %>
|
|
226
241
|
<div class="mt-3"><%= yield %></div>
|
|
227
242
|
<% end %>
|
|
228
243
|
</div>
|
|
@@ -15,14 +15,31 @@
|
|
|
15
15
|
the name and returns it only for a known brand, else null → letter fallback).
|
|
16
16
|
|
|
17
17
|
The sprite is visually hidden (zero-box, absolute) and aria-hidden; only the
|
|
18
|
-
<use> references paint. Each <symbol> carries the OFFICIAL
|
|
18
|
+
<use> references paint. Each BRAND <symbol> carries the OFFICIAL asset embedded
|
|
19
19
|
inline as a base64 PNG data-URI <image> — byte-identical to the real logos each
|
|
20
20
|
wallet ships (matching Turf Monster's public/wallet-*.png), so no hand-drawn
|
|
21
|
-
approximation drifts from the brand.
|
|
22
|
-
|
|
21
|
+
approximation drifts from the brand.
|
|
22
|
+
|
|
23
|
+
se-wallet-default is the ONE exception, and deliberately so. It stands for "a
|
|
24
|
+
wallet, brand unknown", which has no official artwork to be faithful to — so it
|
|
25
|
+
is drawn in PATHS rather than embedded as a raster. That buys two things a PNG
|
|
26
|
+
could not: it scales to any size without a second asset, and it paints in
|
|
27
|
+
currentColor, so a caller can tint it to whatever it is standing in for. Use it
|
|
28
|
+
whenever a brand is absent or unrecognised, instead of hiding the avatar and
|
|
29
|
+
giving the surface two different shapes.
|
|
23
30
|
%>
|
|
24
31
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"
|
|
25
32
|
style="position:absolute;width:0;height:0;overflow:hidden" data-wallet-brand-sprite>
|
|
33
|
+
<%# Default — a wallet, brand unknown. Paths, not a raster: it scales and it
|
|
34
|
+
takes currentColor. The clasp is an evenodd hole rather than a second fill,
|
|
35
|
+
so the whole mark is one colour and cannot half-tint. %>
|
|
36
|
+
<symbol id="se-wallet-default" viewBox="0 0 24 24">
|
|
37
|
+
<path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"
|
|
38
|
+
d="M6.5 4H17a1 1 0 1 1 0 2H6.5a.5.5 0 0 0 0 1H18a2 2 0 0 1 2 2v8a2 2 0
|
|
39
|
+
0 1-2 2H6.5A2.5 2.5 0 0 1 4 16.5v-10A2.5 2.5 0 0 1 6.5 4Zm9.5
|
|
40
|
+
8.2a1.3 1.3 0 1 0 0 2.6 1.3 1.3 0 0 0 0-2.6Z"/>
|
|
41
|
+
</symbol>
|
|
42
|
+
|
|
26
43
|
<%# Phantom — official brand mark (light-purple tile, cream ghost). %>
|
|
27
44
|
<symbol id="se-wallet-phantom" viewBox="0 0 460 460">
|
|
28
45
|
<image width="460" height="460" href="data:image/png;base64,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"/>
|
|
@@ -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
|
|
27
|
-
and `back()`
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
data/lib/studio/version.rb
CHANGED