studio-engine 0.91.0 → 0.91.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: 0cf7b3c1fa31a2eb5f9746838c8c4976d026920c986504c674f760c57c73a33d
4
- data.tar.gz: c39625c99d384e68eedfee1a31dde4816e6f3aa2f004f6ab2f88c4e10d447b00
3
+ metadata.gz: b177cff9fc53b20d566685aa744f1dc41f481017f3014c9a00ef71a43620365a
4
+ data.tar.gz: 100753f4d2f9dd2f936c8285a2b3aac844d2538099b066ef15013ca77f407392
5
5
  SHA512:
6
- metadata.gz: bfeb555efeae9e981014cf5afb73c8e1eb7b01c90d92dc0c1ba6838d5c1acc950ddc908846be93d15d23ac07a9f5acf277c103c3d95b4611e85b758de5c940f0
7
- data.tar.gz: 728932ef4510055ab9b8cd3d6f472abe228ee632febfe007251beb242aec114a1e11c77381a63e44167fb7817c79d8d6716d24687e4261b4c660968e23ffef8c
6
+ metadata.gz: 76433dc917873ff167918968f9ec85d1d6765606e58f3c6502183cab9fca55b3b19a4bc74652f93c74222c3799e4cedbc411a7ce79c6d4d8ff207524982dca20
7
+ data.tar.gz: 3c69e3e3ed94f63952f9cbf2c2e75446b3d95a7d63c51e5b7331dce801bc5ae5329761b456f6cd968e276d6874daf453dcce551c20b87960c49a604eda81d80e
data/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.91.1 — 2026-10-06
8
+
9
+ ### Fixed
10
+
11
+ - **`POST /login` is drawn only for apps that declare `:password`.** A passwordless
12
+ app answered it 500 for a member's address and 422 for a stranger's, so the
13
+ answer revealed who is a member; it now answers 404 for every address. `GET
14
+ /login` and `login_path` are unchanged. An app that still wants a `POST /login`
15
+ of its own draws it before `Studio.routes(self)`.
16
+
17
+ - **SSO sign-up works on passwordless apps.** "Continue as …" (`POST
18
+ /sso_continue`) created a first-time visitor with `password:
19
+ SecureRandom.hex(16)`, which raised `UnknownAttributeError` on any User without
20
+ `has_secure_password`; the engine logged it and bounced to the login page. The
21
+ engine now sets a random password only when the User answers `password=`.
22
+ - **The survey keeps its h1 for a screen reader on every step.** From question 2
23
+ on, the stepper set `hidden` on the survey head, which took the page's only
24
+ h1 out of the accessibility tree. The head now steps into a 1px visual clip
25
+ instead, holding only the title.
26
+
27
+ ### Changed
28
+
29
+ - **The survey partials declare strict locals.** `studio/surveys/_question`
30
+ takes `question:`, `index:`, `total:`, `response:` and an optional `error:`;
31
+ `_script` and `_styles` take none. A render with an unknown or missing local
32
+ now raises instead of painting a blank. The survey accent's fallback reads
33
+ `Studio::ThemeResolver::DEFAULT_PRIMARY` instead of a copied hex.
34
+
7
35
  ## 0.91.0 — 2026-10-06
8
36
 
9
37
  ### Added
data/README.md CHANGED
@@ -82,6 +82,11 @@ sign-in-link form when `:magic_link` is enabled, and the Google button only with
82
82
  and using the link creates the account. So `registration_params`' `:name` shows a
83
83
  Name field only on a password app.
84
84
 
85
+ `POST /login`, the password exchange, is drawn only when `auth_methods` includes
86
+ `:password`. A passwordless app answers it with 404 for every address, so it
87
+ cannot tell a member from a stranger. `GET /login` and `login_path` are drawn for
88
+ every app.
89
+
85
90
  ### Local log rotation (automatic — nothing to configure)
86
91
 
87
92
  The engine caps the host app's **development** log at 16 MB and its **test** log
@@ -55,9 +55,15 @@ class SessionsController < ApplicationController
55
55
  email: session[:sso_email],
56
56
  name: session[:sso_name],
57
57
  provider: session[:sso_provider],
58
- uid: session[:sso_uid],
59
- password: SecureRandom.hex(16)
58
+ uid: session[:sso_uid]
60
59
  )
60
+ # Only a host that takes a password gets one: has_secure_password defines
61
+ # `password=` and requires a value on create, so those apps need a random
62
+ # placeholder. A passwordless User has no such writer, and passing
63
+ # `password:` to User.new raised UnknownAttributeError, which sso_continue
64
+ # rescued into a redirect: SSO sign-up failed silently on every passwordless
65
+ # app (/tasks/engine-sso-creates-passwordless-users).
66
+ user.password = SecureRandom.hex(16) if user.respond_to?(:password=)
61
67
  Studio.configure_sso_user.call(user)
62
68
  rescue_and_log(target: user) do
63
69
  user.save!
@@ -1,3 +1,4 @@
1
+ <%# locals: (question:, index:, total:, response:, error: nil) %>
1
2
  <%# One survey question as a fieldset (studio/surveys/show). It is a complete
2
3
  form control without JavaScript: native radios, checkboxes and text fields
3
4
  named answers[key], with `required` so the no-JS form validates in the
@@ -1,3 +1,4 @@
1
+ <%# locals: () %>
1
2
  <%# The survey stepper: progressive enhancement over studio/surveys/show.
2
3
  Plain JavaScript (no Alpine dependency), idempotent per root so a Turbo
3
4
  render that re-runs it never double-binds. Bound roots are tracked in a
@@ -137,8 +138,10 @@
137
138
  back.hidden = first;
138
139
  next.hidden = last;
139
140
  submit.hidden = !last;
140
- // The title and intro lead question 1 only; later screens are the question alone.
141
- if (head) head.hidden = !first;
141
+ // The title and intro lead question 1 only; later screens show the
142
+ // question alone. The head steps into a visual clip rather than
143
+ // `hidden`, so the page keeps its h1 for a screen reader on every step.
144
+ if (head) head.classList.toggle("studio-survey__head--later", !first);
142
145
  // The number-key shortcut only means something on a choice or scale.
143
146
  var step = steps[index];
144
147
  hint.hidden = !(step.dataset.key && (isChoice(step) || isMulti(step)));
@@ -1,3 +1,4 @@
1
+ <%# locals: () %>
1
2
  <%# Survey styles, scoped under .studio-survey. Plain CSS on the theme's CSS
2
3
  custom properties (--color-cta, --color-surface, ...) rather than Tailwind
3
4
  utilities, so the page looks the same in every host whatever its Tailwind
@@ -6,7 +7,7 @@
6
7
  <style>
7
8
  /* overflow-x: clip, not hidden: the slide-in must never widen the page, and
8
9
  clip makes no scroll container, so the sticky nav still sticks. */
9
- .studio-survey { --sv-accent: var(--color-cta, #8E82FE); display: flex; justify-content: center; padding: 1.25rem 1rem 2rem; color: var(--color-text-body); overflow-x: clip; }
10
+ .studio-survey { --sv-accent: var(--color-cta, <%= Studio::ThemeResolver::DEFAULT_PRIMARY %>); display: flex; justify-content: center; padding: 1.25rem 1rem 2rem; color: var(--color-text-body); overflow-x: clip; }
10
11
  .studio-survey [hidden] { display: none !important; }
11
12
  .studio-survey__shell { width: 100%; max-width: 36rem; }
12
13
  .studio-survey__sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@@ -25,6 +26,11 @@
25
26
  .studio-survey.is-enhanced .studio-survey__head { margin-bottom: 1.25rem; }
26
27
  .studio-survey.is-enhanced .studio-survey__title { margin: 0 0 .375rem; font-size: 1rem; line-height: 1.4; font-weight: 700; color: var(--color-text-secondary); }
27
28
  .studio-survey.is-enhanced .studio-survey__head--echo .studio-survey__title { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
29
+ /* After question 1 the head steps aside visually, but its h1 stays the page's
30
+ heading for a screen reader: the head goes into a 1px clip and only the
31
+ title remains inside it. */
32
+ .studio-survey.is-enhanced .studio-survey__head--later { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
33
+ .studio-survey.is-enhanced .studio-survey__head--later > :not(.studio-survey__title) { display: none; }
28
34
  .studio-survey.is-enhanced .studio-survey__intro-copy { margin: 0; font-size: .9375rem; line-height: 1.5; color: var(--color-text-secondary); }
29
35
  .studio-survey.is-enhanced .studio-survey__meta { display: none; }
30
36
  .studio-survey__question { display: block; width: 100%; padding: 0; margin: 0 0 .5rem; font-size: 1.375rem; line-height: 1.3; font-weight: 700; color: var(--color-text); outline: none; }
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.91.0"
2
+ VERSION = "0.91.1"
3
3
  end
data/lib/studio.rb CHANGED
@@ -146,8 +146,9 @@ module Studio
146
146
  # Which sign-in methods this app offers. The shared login/signup views render
147
147
  # a button/field per enabled method (gate with Studio.auth_method?). Order is
148
148
  # display order. Both McRitchie Studio + Turf Monster are passwordless; legacy
149
- # email+password is opt-in via :password (which also re-arms the User#authenticate
150
- # contract check — see validate_user_contract!).
149
+ # email+password is opt-in via :password, which also draws POST /login (see
150
+ # Studio.routes) and re-arms the User#authenticate contract check (see
151
+ # validate_user_contract!).
151
152
  #
152
153
  # :wallet is deliberately NOT in the default. This engine plus McRitchie Studio
153
154
  # is the BASE template for every app, web2 and web3 alike; solana-studio plus
@@ -1336,7 +1337,16 @@ module Studio
1336
1337
  def self.routes(router)
1337
1338
  router.instance_exec do
1338
1339
  get "login", to: "sessions#new"
1339
- post "login", to: "sessions#create"
1340
+
1341
+ # Email + password sign-in, drawn only for an app that declares :password.
1342
+ # SessionsController#create calls user.authenticate, which a passwordless
1343
+ # User does not define: on a passwordless app this POST answered 500 for a
1344
+ # member's address and 422 for a stranger's, which told anyone who asked
1345
+ # who the members are. Undrawn, it answers 404 for every address.
1346
+ # GET /login stays drawn for every app: it is the sign-in page, and
1347
+ # login_path names it.
1348
+ post "login", to: "sessions#create" if Studio.auth_method?(:password)
1349
+
1340
1350
  post "sso_continue", to: "sessions#sso_continue"
1341
1351
  get "sso_login", to: "sessions#sso_login"
1342
1352
  get "logout", to: "sessions#destroy"
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.91.0
4
+ version: 0.91.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie