studio-engine 0.13.1 → 0.15.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: 1509550e891472f578f1472b33782de7104a592e8d325ad11aa520c0b600171a
4
- data.tar.gz: cac1e68d9950257d017a2f3c93d81733ac96e9ba27e9cf8b53cdfe06c9709552
3
+ metadata.gz: 4c7828a97a832ad01592ab1875eef12fc5365d1ad98e2e7c19da71b8e5a47cab
4
+ data.tar.gz: 8a8c6726ea00cad17bd17426184ca546c5ad3fc08c7d9d497448b76b0a5626d2
5
5
  SHA512:
6
- metadata.gz: 2880909b924c60c406aae3990240c47ef0d36cba7b4c34fb7b150dcbc1cd93a35d81d6bbb1cb4b65d22b832f7e83635ac68b0b26ded20bd099cf434e5773a066
7
- data.tar.gz: f87db04adcf27abd17485c52bbb1f5767269f50ed19e9ac66a808d9d95e0271adc1109eb64539173e81a603a3cae915d7b380282d2ab2fad3cf14bef0936933e
6
+ metadata.gz: df1f556225655c111cdb3499c0809f4495dc941421c2606a0c2c85f21f28b412f7a53a8c99fef6712e191cb3f4d8b892872fc799d3dadc3d0b666a42408b9c89
7
+ data.tar.gz: 1ac4533e4061b6da06319854f478d7adc5eaf01e38f04a0885cda122ea712c1f9211e4010582a6e2bfa62cbad2637e82518f28ef7ceac3d742a7cf5f9641d665
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) — `MAJOR.MINOR.PATCH`. Consumer Rails apps install the released RubyGems package with `gem "studio-engine", "~> 0.6"`; bumping the gem version and updating consumer lockfiles is a release.
4
4
 
5
+ ## 0.14.0 — 2026-07-22
6
+
7
+ ### Added
8
+
9
+ - **Passwordless engine login** — the stock `sessions/new` sign-in view now
10
+ renders its method set from `auth_methods`, so a host app configured for
11
+ passwordless sign-in gets the correct engine-shipped view with no local
12
+ override. Fixes a 500 in the passwordless path where the view assumed a
13
+ password field.
14
+
5
15
  ## 0.13.1 — 2026-07-19
6
16
 
7
17
  ### Changed
@@ -62,6 +62,33 @@
62
62
 
63
63
  /* -- Button system (standardized across all Studio apps) ------------------- */
64
64
 
65
+ /*
66
+ Overridable variants — token contract.
67
+
68
+ Most filled variants sit on a theme role and darken on hover. Two of them
69
+ (btn-secondary, btn-neutral) are TOKEN-DRIVEN so a consuming app can retune
70
+ them WITHOUT redeclaring the @utility — a local `@utility` redeclaration is
71
+ ADDITIVE in Tailwind v4 (both definitions emit and the engine's rules leak in),
72
+ so forking is not a clean override. Set these on :root in the consumer's CSS:
73
+
74
+ --btn-secondary-bg base fill (default: var(--color-success))
75
+ --btn-secondary-bg-hover hover fill (default: same as base)
76
+ --btn-secondary-hover-filter hover filter (default: brightness(0.9))
77
+ --btn-neutral-bg base fill (default: var(--color-surface-alt))
78
+ --btn-neutral-bg-hover hover fill (default: var(--color-surface))
79
+ --btn-neutral-hover-filter hover filter (default: none)
80
+
81
+ With no tokens set, the compiled result is visually identical to the previous
82
+ hard-coded rules. Example — turf-monster's violet secondary + brightness neutral:
83
+
84
+ :root {
85
+ --btn-secondary-bg: #8e82fe; --btn-secondary-bg-hover: #7b6ef6;
86
+ --btn-secondary-hover-filter: none;
87
+ --btn-neutral-bg-hover: var(--color-surface-alt);
88
+ --btn-neutral-hover-filter: brightness(1.3);
89
+ }
90
+ */
91
+
65
92
  @utility btn {
66
93
  @apply inline-flex items-center justify-center font-bold rounded-lg transition px-6 py-2 text-sm disabled:opacity-50 disabled:cursor-not-allowed;
67
94
  &:focus-visible {
@@ -78,14 +105,17 @@
78
105
  }
79
106
  }
80
107
 
81
- /* NOTE: btn-secondary and btn-success compile to IDENTICAL declarations (both
82
- sit on var(--color-success)) an intentional hub-canonical alias, kept as
83
- two names so existing markup in every consumer keeps working. */
108
+ /* btn-secondary DEFAULTS to the success role with no tokens set it compiles
109
+ like btn-success, preserving the hub-canonical alias every consumer relies on.
110
+ It is token-overridable (see the contract above) so an app whose secondary
111
+ action diverges from success (turf-monster's violet) retunes it via
112
+ --btn-secondary-* instead of forking the @utility. */
84
113
  @utility btn-secondary {
85
114
  @apply text-white;
86
- background-color: var(--color-success);
115
+ background-color: var(--btn-secondary-bg, var(--color-success));
87
116
  &:hover {
88
- filter: brightness(0.9);
117
+ background-color: var(--btn-secondary-bg-hover, var(--btn-secondary-bg, var(--color-success)));
118
+ filter: var(--btn-secondary-hover-filter, brightness(0.9));
89
119
  }
90
120
  }
91
121
 
@@ -130,10 +160,16 @@
130
160
  }
131
161
  }
132
162
 
163
+ /* btn-neutral DEFAULTS to the surface-alt base / surface hover pair, token-
164
+ overridable the same way (--btn-neutral-*): turf-monster keeps its
165
+ brightness(1.3) hover by setting --btn-neutral-hover-filter and pinning
166
+ --btn-neutral-bg-hover back to the base so the fill does not change. */
133
167
  @utility btn-neutral {
134
- @apply bg-surface-alt text-heading border border-strong;
168
+ @apply text-heading border border-strong;
169
+ background-color: var(--btn-neutral-bg, var(--color-surface-alt));
135
170
  &:hover {
136
- @apply bg-surface;
171
+ background-color: var(--btn-neutral-bg-hover, var(--color-surface));
172
+ filter: var(--btn-neutral-hover-filter, none);
137
173
  }
138
174
  }
139
175
 
@@ -35,36 +35,60 @@
35
35
  <%= render "sessions/sso_continue" if defined?(sso_user_available?) && sso_user_available? %>
36
36
  <% end %>
37
37
 
38
- <%= form_tag login_path, method: :post do %>
39
- <div class="mb-5">
40
- <label for="email" class="block text-sm text-secondary mb-2 font-medium">Email</label>
41
- <input type="email" name="email" id="email" value="<%= params[:email] %>" <%= 'autofocus' unless has_sso %>
42
- x-ref="emailField"
43
- class="input-field"
44
- placeholder="you@example.com">
45
- </div>
38
+ <%# The primary email form renders from the app's configured auth_methods: a PASSWORD
39
+ form only when passwords are available (Studio.password_login_available? — enabled AND
40
+ the User supports `authenticate`), otherwise the passwordless MAGIC-LINK request form.
41
+ The old hardcoded password field 500'd on every passwordless app's only sign-in path. %>
42
+ <% if Studio.password_login_available? %>
43
+ <%= form_tag login_path, method: :post do %>
44
+ <div class="mb-5">
45
+ <label for="email" class="block text-sm text-secondary mb-2 font-medium">Email</label>
46
+ <input type="email" name="email" id="email" value="<%= params[:email] %>" <%= 'autofocus' unless has_sso %>
47
+ x-ref="emailField"
48
+ class="input-field"
49
+ placeholder="you@example.com">
50
+ </div>
46
51
 
47
- <div class="mb-6">
48
- <label for="password" class="block text-sm text-secondary mb-2 font-medium">Password</label>
49
- <input type="password" name="password" id="password"
50
- class="input-field"
51
- placeholder="Your password">
52
- </div>
52
+ <div class="mb-6">
53
+ <label for="password" class="block text-sm text-secondary mb-2 font-medium">Password</label>
54
+ <input type="password" name="password" id="password"
55
+ class="input-field"
56
+ placeholder="Your password">
57
+ </div>
53
58
 
54
- <button type="submit" class="btn btn-primary btn-lg w-full">
55
- Log In
56
- </button>
59
+ <button type="submit" class="btn btn-primary btn-lg w-full">
60
+ Log In
61
+ </button>
62
+ <% end %>
63
+ <% elsif Studio.auth_method?(:magic_link) %>
64
+ <%= form_tag magic_link_request_path, method: :post do %>
65
+ <div class="mb-5">
66
+ <label for="email" class="block text-sm text-secondary mb-2 font-medium">Email</label>
67
+ <input type="email" name="email" id="email" value="<%= params[:email] %>" <%= 'autofocus' unless has_sso %>
68
+ x-ref="emailField"
69
+ class="input-field"
70
+ placeholder="you@example.com">
71
+ </div>
72
+
73
+ <button type="submit" class="btn btn-primary btn-lg w-full">
74
+ Send sign-in link
75
+ </button>
76
+ <% end %>
57
77
  <% end %>
58
78
 
59
- <div class="relative my-6">
60
- <div class="absolute inset-0 flex items-center"><div class="w-full border-t border-subtle"></div></div>
61
- <div class="relative flex justify-center text-sm"><span class="bg-surface px-3 text-secondary">or</span></div>
62
- </div>
79
+ <% if Studio.auth_method?(:google) %>
80
+ <% if Studio.password_login_available? || Studio.auth_method?(:magic_link) %>
81
+ <div class="relative my-6">
82
+ <div class="absolute inset-0 flex items-center"><div class="w-full border-t border-subtle"></div></div>
83
+ <div class="relative flex justify-center text-sm"><span class="bg-surface px-3 text-secondary">or</span></div>
84
+ </div>
85
+ <% end %>
63
86
 
64
- <%= button_to "/auth/google_oauth2", method: :post, data: { turbo: false },
65
- class: "btn btn-google btn-lg w-full gap-3" do %>
66
- <%= render "components/google_logo" %>
67
- Sign in with Google
87
+ <%= button_to "/auth/google_oauth2", method: :post, data: { turbo: false },
88
+ class: "btn btn-google btn-lg w-full gap-3" do %>
89
+ <%= render "components/google_logo" %>
90
+ Sign in with Google
91
+ <% end %>
68
92
  <% end %>
69
93
 
70
94
  <p class="text-center text-secondary text-sm mt-6">
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.13.1"
2
+ VERSION = "0.15.0"
3
3
  end
data/lib/studio.rb CHANGED
@@ -152,6 +152,28 @@ module Studio
152
152
  auth_methods.include?(method.to_sym)
153
153
  end
154
154
 
155
+ # True when the engine login should render a PASSWORD field/form: passwords are enabled
156
+ # (`:password` in auth_methods) AND the host User model actually supports them (responds to
157
+ # `authenticate` — i.e. `has_secure_password`). Both are required, and the second is the
158
+ # belt-and-suspenders: without it, a passwordless app (User with no `authenticate`) rendered
159
+ # the hardcoded password field and 500'd on submit via `user.authenticate` — the whole fleet
160
+ # having moved off passwords, this made the engine default wrong for every consumer. The User
161
+ # check is defensive of a mis-set auth_methods; the contract validation normally guarantees it
162
+ # (validate_user_contract! requires PASSWORD_USER_INSTANCE_METHODS iff auth_method?(:password)).
163
+ def self.password_login_available?
164
+ auth_method?(:password) && user_supports_password?
165
+ end
166
+
167
+ # Does the host User model respond to `authenticate` (has_secure_password)? Safe when no User
168
+ # is defined (an engine-only boot / a test with no host model) — answers false rather than raise.
169
+ def self.user_supports_password?
170
+ return false unless defined?(::User) && ::User.respond_to?(:instance_methods)
171
+
172
+ PASSWORD_USER_INSTANCE_METHODS.all? { |m| ::User.instance_methods.include?(m) }
173
+ rescue StandardError
174
+ false
175
+ end
176
+
155
177
  # True when the emailed/inbox magic-link URL is the short /l/<token> — i.e.
156
178
  # magic links are Studio::Link rows AND this app draws the /l routes. False =
157
179
  # the legacy /magic_link/<token> path: the :signed store, OR an app on the
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-07-20 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -298,7 +297,6 @@ metadata:
298
297
  source_code_uri: https://github.com/amcritchie/studio-engine/tree/main
299
298
  bug_tracker_uri: https://github.com/amcritchie/studio-engine/issues
300
299
  changelog_uri: https://github.com/amcritchie/studio-engine/blob/main/CHANGELOG.md
301
- post_install_message:
302
300
  rdoc_options: []
303
301
  require_paths:
304
302
  - lib
@@ -313,8 +311,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
313
311
  - !ruby/object:Gem::Version
314
312
  version: '0'
315
313
  requirements: []
316
- rubygems_version: 3.5.22
317
- signing_key:
314
+ rubygems_version: 4.0.9
318
315
  specification_version: 4
319
316
  summary: Shared Rails engine providing auth, SSO, error logging, theming, and S3-backed
320
317
  image caching