studio-engine 0.65.2 → 0.66.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7389678882ebbd2bba6a35738f330283a2e5a226e68009a76b83f0c14b0e1351
4
- data.tar.gz: de1b1a74888a3c2e47cc39da0377323c1b51c04d1aa718de9826fc338aff13a8
3
+ metadata.gz: d4fcdf18d2126610aee3af5f83e3cfd5c2c23253a5432ad1f54d5335ce1d3d03
4
+ data.tar.gz: 502c6ce16191beb678d1a23516862e596566394ea7b7a8756288cecd449c0551
5
5
  SHA512:
6
- metadata.gz: f2c671579063631d50ac0298f2812ccde9a32a4554cc85ecf2b23368a8f51d2027f4eb03ed748c87104b7b68c9150d4fa7accec288d6b1a545ddf6233c755847
7
- data.tar.gz: f2a53ef58b2bc5959d3a0c420f0cb8619b448caf496b9b437cd70e4f79ca5d8652e482338f2472ad3fd3f4b2609c0e5c47e8f0c7da3ca44d4d7b9e9828cb4f80
6
+ metadata.gz: c2bd8400088ba4b705391c85e271ff5fb00655c58a9f9e4e93671f4bada3da77b45f2d0926348541ee8c4a5fdc6e3e351d6e82ab2cf06b9f576257df07ffbcb4
7
+ data.tar.gz: 534da001522642f35e9aacd9d98a11c054dc491f6d97eddb4bf0f9571939da65d73869ff2e6daf2535698b4c55c8c3ed48c819981bcd9a15fe83278cd604a4a5
data/CHANGELOG.md CHANGED
@@ -6,6 +6,44 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
6
6
 
7
7
  ### Added
8
8
 
9
+ - **A green comment-leak scan used to mean "not looked at".**
10
+ `test/views/erb_comment_leak_test.rb` guards the ERB comment form in
11
+ `app/views/**/*.erb`. It never looked inside `<script>`, and that is where this
12
+ gem keeps its browser programs — `studio/modals/_host`,
13
+ `studio/solana/_phantom_deeplink`, `layouts/studio/_head` and the modal blocks
14
+ between them hold **1_239 JavaScript comments, 73_913 bytes of comment body**,
15
+ none of it read by any guard. A review of a diff made almost entirely of those
16
+ comments came back green, and the reviewer's note was the honest reading of it:
17
+ *"don't read that green as clearance."*
18
+
19
+ `test/views/script_comment_leak_test.rb` reads them. The rule is the one the ERB
20
+ guard applies — no ERB open or close sequence inside a comment body — and the
21
+ mechanism it defends against is worse than the ERB one. A tag quoted inside an
22
+ ERB comment is inert, because the comment swallows it. A tag quoted inside a
23
+ **JavaScript** comment is not inside anything ERB knows about, so ERB opens a tag
24
+ right there and **runs** it: a complete tag evaluates at render, and an
25
+ incomplete one swallows every byte up to the next close sequence in the file,
26
+ deleting working JavaScript with no syntax error to show for it. Wrapping the
27
+ line in an HTML comment does not help — ERB runs inside those too. Describing the
28
+ tag in words stays legal, exactly as it is for ERB comments.
29
+
30
+ **Finding a comment is the hard half, and the first cut of this got it wrong.**
31
+ Anchoring `<script>` blocks on raw source let an ERB comment's *prose mention* of
32
+ a script tag open the block: `studio/_board_assets.html.erb` reported 19_512
33
+ bytes of markup as JavaScript and never saw its real program on its own terms.
34
+ Blocks are now anchored on a copy with ERB tags and HTML comments blanked out,
35
+ length preserved so offsets and line numbers still land. Inside a block the
36
+ walker tracks the states that can hide a comment opener — a string
37
+ (`"https://…"` is not a comment), a template literal, a regex (`/\/\//`) — and a
38
+ literal or comment that never closes is REPORTED rather than absorbed, because a
39
+ walker that quietly lost its place is the same green as a clean file. The
40
+ assertions check the input as well as the verdict: how many blocks, comments and
41
+ bytes the scan actually reached, and that every block it opened matches a real
42
+ closing tag.
43
+
44
+ Zero findings across the engine at `origin/accepted`, so this lands green with no
45
+ allowlist.
46
+
9
47
  - **`data-pin` — the pinned stack publishes itself.** `--nav-h` / `--nav-bottom`
10
48
  answer for one element. Everything else that pins has been re-deriving the
11
49
  same geometry by hand: on `mcritchie-studio`'s `/deployments` the app strip
@@ -69,6 +107,54 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
69
107
 
70
108
  ### Changed
71
109
 
110
+ - **The base template is web2: `:wallet` has left the `auth_methods` default.**
111
+ `Studio.auth_methods` defaulted to `%i[magic_link google wallet]` while
112
+ `Studio.features` defaulted to `[]`. Those two defaults disagree, and the
113
+ disagreement was load-bearing: the auth modal renders its wallet button from
114
+ `auth_method?(:wallet) && feature?(:web3)`, so a stock app rendered **no**
115
+ wallet button — while `Studio.routes` draws the Solana trio from
116
+ `auth_method?(:wallet)` alone, so the same stock app published
117
+ `GET /auth/solana/nonce`, `POST /auth/solana/verify` and
118
+ `GET /auth/phantom/callback`. All three `skip_before_action
119
+ :require_authentication`, and `#verify` lands in `User.from_solana_wallet`,
120
+ which `validate_user_contract!` never requires a host to implement. A brand-new
121
+ newsletter app inherited three public endpoints into an unvalidated contract.
122
+
123
+ studio-engine + McRitchie Studio is the base template for **every** app, web2
124
+ and web3 alike; solana-studio + Turf Monster is the web3 bolt-on. Most apps are
125
+ web2, so the default now says so: `%i[magic_link google]`. `README.md` and
126
+ `docs/NEW_APP_SETUP.md` have always printed that exact line as the new-app
127
+ configuration — the code simply disagreed with the docs it shipped.
128
+
129
+ **No current consumer changes behaviour**, because all three declare
130
+ `auth_methods` explicitly: McRitchie Studio (`magic_link google wallet`),
131
+ Turf Monster (`magic_link google wallet`, and `draw_auth_routes = false`
132
+ besides, so the engine draws neither group for it), mcritchie-industries
133
+ (`magic_link`). A wallet app opts in to both knobs — `auth_methods` including
134
+ `:wallet` **and** `features` including `:web3`, the second being what makes the
135
+ button appear.
136
+
137
+ - **The auth route gate is pinned, and the gate choice is written down.**
138
+ Nothing tested which auth routes `Studio.routes` draws, on an engine whose
139
+ route changes break consumers at **boot** rather than at test time.
140
+ `test/integration/auth_route_gating_test.rb` draws the real host route table
141
+ under each consumer's configuration and reads the drawn routes back, so a
142
+ comment naming `auth/solana/nonce` cannot satisfy it.
143
+
144
+ The gate stays `draw_auth_routes && auth_method?(:wallet)` — **not** `&&
145
+ feature?(:web3)`, even though the modal uses both. `auth_methods` says which
146
+ CREDENTIALS an app accepts and these three paths are the credential exchange
147
+ itself; `features` gates product surfaces. `phantom_callback` is the mobile
148
+ deep-link RETURN url, so an app that declared `:wallet` and forgot `:web3`
149
+ would dead-end the handshake inside the user's wallet app with no server-side
150
+ trace — a worse failure than an endpoint that draws while the UI hides its
151
+ button. That decision is now a test with its rationale attached, so adding the
152
+ feature check flips a red assertion instead of silently unpublishing a live
153
+ consumer's routes. `Studio.draw_auth_routes`' comment likewise now records that
154
+ it is the OUTER switch, not the only gate: each group carries its own
155
+ `auth_methods` sub-gate, which is how a web2 app keeps magic-link while drawing
156
+ no `/auth/solana/*`.
157
+
72
158
  - **The navbar collapse primitive is broadcastable, rate-limited, and cheap per
73
159
  frame.** Three defects in the primitive as first landed, none of which a
74
160
  consuming app should inherit:
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.65.2"
2
+ VERSION = "0.66.0"
3
3
  end
data/lib/studio.rb CHANGED
@@ -119,7 +119,23 @@ module Studio
119
119
  # display order. Both McRitchie Studio + Turf Monster are passwordless; legacy
120
120
  # email+password is opt-in via :password (which also re-arms the User#authenticate
121
121
  # contract check — see validate_user_contract!).
122
- mattr_accessor :auth_methods, default: %i[magic_link google wallet]
122
+ #
123
+ # :wallet is deliberately NOT in the default. This engine plus McRitchie Studio
124
+ # is the BASE template for every app, web2 and web3 alike; solana-studio plus
125
+ # Turf Monster is the web3 bolt-on. Most apps are web2, so standing up Solana
126
+ # infrastructure to build a newsletter app is the wrong default.
127
+ #
128
+ # Membership here is also what DRAWS the Solana sign-in routes (see the wallet
129
+ # block in Studio.routes), so a default carrying :wallet published three public,
130
+ # unauthenticated /auth/solana/* endpoints on apps that ship no wallet at all —
131
+ # and #verify lands in User.from_solana_wallet, which validate_user_contract!
132
+ # does not require a host to implement. README.md and docs/NEW_APP_SETUP.md have
133
+ # always shown `%i[magic_link google]` as the new-app line; the code now agrees
134
+ # with the docs it ships. A wallet app opts in to BOTH knobs:
135
+ #
136
+ # config.auth_methods = %i[magic_link google wallet]
137
+ # config.features = %i[web3]
138
+ mattr_accessor :auth_methods, default: %i[magic_link google]
123
139
 
124
140
  # ---- Capability features --------------------------------------------------
125
141
  # Coarse app-level capability switches an app opts into. Distinct from
@@ -230,10 +246,18 @@ module Studio
230
246
  "boot until the line is gone."
231
247
  end
232
248
 
233
- # Whether Studio.routes draws the magic_link + solana wallet routes. An app that
234
- # already defines its own auth routes (e.g. turf-monster, which has battle-tested
235
- # magic_link/solana routes + extras) sets this false to avoid duplicate route
236
- # NAMES at boot, keeping its own routes intact. New consumers leave it true.
249
+ # Whether Studio.routes draws the magic_link + solana wallet routes AT ALL. An
250
+ # app that already defines its own auth routes (e.g. turf-monster, which has
251
+ # battle-tested magic_link/solana routes + extras) sets this false to avoid
252
+ # duplicate route NAMES at boot, keeping its own routes intact. New consumers
253
+ # leave it true.
254
+ #
255
+ # This is the OUTER switch, and it is all-or-nothing by design: false means the
256
+ # host draws every auth route itself. It is NOT the only gate. Each group also
257
+ # carries its own auth_methods sub-gate, so a true app still draws only the
258
+ # methods it declares — magic_link needs auth_method?(:magic_link), the Solana
259
+ # trio needs auth_method?(:wallet). A web2 app therefore keeps magic-link while
260
+ # drawing no /auth/solana/* routes, without touching this flag.
237
261
  mattr_accessor :draw_auth_routes, default: true
238
262
 
239
263
  # Whether Studio.routes draws the unified /l/<token> link routes
@@ -829,6 +853,21 @@ module Studio
829
853
  # mobile wallet path at all and copying 400 lines of SIWS protocol per
830
854
  # app is how the picker came to exist three times. Account-linking and
831
855
  # the OAuth popup DO stay app-side.
856
+ #
857
+ # The gate is auth_methods, NOT auth_methods && feature?(:web3), even though
858
+ # the auth modal computes its wallet button from both
859
+ # (app/views/style/modals/_auth.html.erb). Deliberate: auth_methods says
860
+ # which CREDENTIALS this app accepts, features gates PRODUCT SURFACES, and
861
+ # these three paths are the credential exchange itself. phantom_callback is
862
+ # the mobile deep-link RETURN url — a wallet app that declared :wallet but
863
+ # forgot :web3 would dead-end the handshake inside the user's wallet app
864
+ # with no server-side trace, which is a worse failure than an endpoint that
865
+ # draws while the UI hides its button. Keying on the app's own declaration
866
+ # also keeps the two knobs independent, so an app may offer wallet SIGN-IN
867
+ # without shipping on-chain product surfaces.
868
+ #
869
+ # What stops a plain web2 app from publishing these is that :wallet is no
870
+ # longer in the auth_methods default — see that accessor above.
832
871
  if Studio.draw_auth_routes && Studio.auth_method?(:wallet)
833
872
  get "auth/solana/nonce", to: "solana_sessions#nonce", as: :solana_nonce
834
873
  post "auth/solana/verify", to: "solana_sessions#verify", as: :solana_verify
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.65.2
4
+ version: 0.66.0
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-08-28 00:00:00.000000000 Z
11
+ date: 2026-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails