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 +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +5 -0
- data/app/controllers/sessions_controller.rb +8 -2
- data/app/views/studio/surveys/_question.html.erb +1 -0
- data/app/views/studio/surveys/_script.html.erb +5 -2
- data/app/views/studio/surveys/_styles.html.erb +7 -1
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +13 -3
- 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: b177cff9fc53b20d566685aa744f1dc41f481017f3014c9a00ef71a43620365a
|
|
4
|
+
data.tar.gz: 100753f4d2f9dd2f936c8285a2b3aac844d2538099b066ef15013ca77f407392
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
141
|
-
|
|
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,
|
|
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; }
|
data/lib/studio/version.rb
CHANGED
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
|
|
150
|
-
# contract check
|
|
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
|
-
|
|
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"
|