studio-engine 0.141.3 → 0.142.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 +22 -1
- data/README.md +103 -1
- data/app/assets/tailwind/studio_engine/engine.css +90 -18
- data/app/components/previews/studio/button_component_preview/disabled.html.erb +12 -0
- data/app/components/previews/studio/button_component_preview/icon_and_link.html.erb +7 -0
- data/app/components/previews/studio/button_component_preview/loading.html.erb +12 -0
- data/app/components/previews/studio/button_component_preview/sizes.html.erb +9 -0
- data/app/components/previews/studio/button_component_preview/tones.html.erb +5 -0
- data/app/components/previews/studio/button_component_preview.rb +34 -0
- data/app/components/previews/studio/empty_state_component_preview/with_icon_and_action.html.erb +5 -0
- data/app/components/previews/studio/empty_state_component_preview.rb +29 -0
- data/app/components/previews/studio/table_component_preview/rows.html.erb +14 -0
- data/app/components/previews/studio/table_component_preview.rb +33 -0
- data/app/components/studio/button_component.html.erb +1 -0
- data/app/components/studio/button_component.rb +151 -0
- data/app/components/studio/empty_state_component.html.erb +12 -0
- data/app/components/studio/empty_state_component.rb +64 -0
- data/app/components/studio/table_component.html.erb +32 -0
- data/app/components/studio/table_component.rb +152 -0
- data/app/controllers/studio/modals_controller.rb +48 -0
- data/app/javascript/studio/carousel.js +52 -0
- data/app/javascript/studio/controllers/carousel_controller.js +7 -13
- data/app/javascript/studio/in_page_links.js +85 -11
- data/app/javascript/studio/modal_host.js +219 -0
- data/app/models/error_log.rb +5 -5
- data/app/views/components/_empty_state.html.erb +2 -6
- data/app/views/studio/modals/_host.html.erb +34 -2
- data/app/views/studio/modals/remote.html.erb +5 -0
- data/app/views/style/_components.html.erb +5 -2
- data/app/views/style/_glass.html.erb +9 -1
- data/app/views/style/_primitives.html.erb +106 -0
- data/config/studio_tricks.yml +6 -2
- data/db/migrate/20261109180000_add_engine_not_null_checks.rb +148 -0
- data/lib/studio/modals.rb +101 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +11 -0
- metadata +22 -1
data/config/studio_tricks.yml
CHANGED
|
@@ -211,19 +211,23 @@ glass:
|
|
|
211
211
|
name: Glass
|
|
212
212
|
group: Glass
|
|
213
213
|
token: "config.surface_style = :glass"
|
|
214
|
-
note: "Frosts the cards and lights the controls
|
|
214
|
+
note: "Frosts the cards and lights the controls; the standard for a new app."
|
|
215
215
|
resolve: [config:surface_style, css:surface-glass-blur]
|
|
216
216
|
variants:
|
|
217
217
|
lit:
|
|
218
218
|
token: ".btn, .badge, .input-field under surface_style = :glass"
|
|
219
219
|
note: "Buttons, badges and inputs lit from above, in every state."
|
|
220
220
|
resolve: [config:surface_style]
|
|
221
|
+
tile:
|
|
222
|
+
token: ".lit-tile, .lit-tile-soft under surface_style = :glass"
|
|
223
|
+
note: "An app's own call-to-action tile, lit like a button."
|
|
224
|
+
resolve: [css:lit-tile, css:lit-tile-soft]
|
|
221
225
|
|
|
222
226
|
glass_sidebar:
|
|
223
227
|
name: Glass Sidebar
|
|
224
228
|
group: Glass
|
|
225
229
|
token: "config.sidebar_style = :glass"
|
|
226
|
-
note: "A frosted link sidebar
|
|
230
|
+
note: "A frosted link sidebar in the app's colours; the standard for a new app."
|
|
227
231
|
resolve: [config:sidebar_style]
|
|
228
232
|
|
|
229
233
|
icon:
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# The second half of the engine's slug standard and of the role standard: the
|
|
2
|
+
# database refuses a NULL `slug` on error_logs, theme_settings,
|
|
3
|
+
# studio_geo_settings and studio_site_identities, and a NULL `role` on the
|
|
4
|
+
# host's users (database audit 2026-11-07; the steps
|
|
5
|
+
# RequireSlugsOnEngineTables and AddUsersRoleKnownCheck deferred to a later
|
|
6
|
+
# release, engine-not-null-next-release).
|
|
7
|
+
#
|
|
8
|
+
# THE FORM: a validated CHECK named `<table>_<column>_present`, holding
|
|
9
|
+
# `<column> IS NOT NULL`, not a column-level NOT NULL.
|
|
10
|
+
#
|
|
11
|
+
# * It is the same rule to every write: Postgres refuses the NULL insert or
|
|
12
|
+
# update either way.
|
|
13
|
+
# * It is the one form the release's schema-dump guard
|
|
14
|
+
# (Release::EngineMigrationInstall.schema_dump_safe?) accepts. The sweep
|
|
15
|
+
# migrates each host's throwaway database and diffs db/schema.rb; the guard
|
|
16
|
+
# passes a diff that only ADDS lines. `change_column_null` rewrites the
|
|
17
|
+
# existing `t.string "slug"` line to `t.string "slug", null: false`, a
|
|
18
|
+
# removed line, and the guard refuses it by design. A CHECK adds one
|
|
19
|
+
# `t.check_constraint` line and removes nothing.
|
|
20
|
+
# * Its dump is stable. Postgres deparses `slug IS NOT NULL` to
|
|
21
|
+
# `(slug IS NOT NULL)` and deparses that to itself again, so a host's dump
|
|
22
|
+
# after this migrate equals its dump after a schema load, byte for byte
|
|
23
|
+
# (unlike an `IN (...)` list, see AddUsersRoleKnownCheck#expression).
|
|
24
|
+
# * On Postgres 12 and later, a validated `CHECK (x IS NOT NULL)` lets a
|
|
25
|
+
# later `SET NOT NULL` skip its table scan, if that form is ever wanted.
|
|
26
|
+
#
|
|
27
|
+
# ROLLBACK SAFETY. A column that is NOT NULL breaks every insert that leaves it
|
|
28
|
+
# NULL, and Heroku runs this migration while the previous release's dynos still
|
|
29
|
+
# serve, and a `heroku rollback` keeps the constraint. So this migration ships
|
|
30
|
+
# only once every host already runs code that never writes a NULL here:
|
|
31
|
+
#
|
|
32
|
+
# * error_logs: ErrorLog includes Sluggable from 0.138.0 (first shipped in
|
|
33
|
+
# 0.141.0), which writes a placeholder slug before the insert. Up to 0.137.0,
|
|
34
|
+
# ErrorLog.capture! inserted a NULL slug and filled it after.
|
|
35
|
+
# * theme_settings, studio_geo_settings, studio_site_identities: Sluggable
|
|
36
|
+
# since 0.129.0.
|
|
37
|
+
# * users.role: the column's default is "viewer" on every host, and no engine
|
|
38
|
+
# or host code writes an explicit NULL role.
|
|
39
|
+
#
|
|
40
|
+
# The error_logs line is the floor: a host must have run 0.141.0 or later in
|
|
41
|
+
# production for one release before it installs this migration. A host that
|
|
42
|
+
# jumps from 0.137.0 or older straight to this version in one release breaks
|
|
43
|
+
# its own error writes while the old dynos drain, and again on a rollback.
|
|
44
|
+
#
|
|
45
|
+
# Each step, per table, and a table or column the host does not have is skipped:
|
|
46
|
+
#
|
|
47
|
+
# 1. A column that is already NOT NULL (the hub's and Turf's users.role) needs
|
|
48
|
+
# no CHECK: skipped.
|
|
49
|
+
# 2. A CHECK of this name already there: skipped, so a second run is a no-op.
|
|
50
|
+
# 3. Backfill. A NULL or blank slug gets the one its model would write (the
|
|
51
|
+
# same rule as RequireSlugsOnEngineTables, copied, since a migration must
|
|
52
|
+
# not depend on another migration's class); a NULL role becomes "viewer".
|
|
53
|
+
# 4. Add the CHECK, validated.
|
|
54
|
+
#
|
|
55
|
+
# Production on 2026-11-08 held no NULL slug and no NULL role. On Postgres each
|
|
56
|
+
# ALTER waits at most LOCK_TIMEOUT for its lock; a migrate that cannot get it
|
|
57
|
+
# fails whole, changes nothing, and runs again on the next release.
|
|
58
|
+
class AddEngineNotNullChecks < ActiveRecord::Migration[7.2]
|
|
59
|
+
LOCK_TIMEOUT = "10s"
|
|
60
|
+
|
|
61
|
+
# table => the slug prefix its model's name_slug writes before the app name.
|
|
62
|
+
# error_logs has no app name: its slug is the id.
|
|
63
|
+
NAMED = {
|
|
64
|
+
"theme_settings" => "theme",
|
|
65
|
+
"studio_geo_settings" => "geo",
|
|
66
|
+
"studio_site_identities" => "site-identity"
|
|
67
|
+
}.freeze
|
|
68
|
+
SLUG_TABLES = ([ "error_logs" ] + NAMED.keys).freeze
|
|
69
|
+
|
|
70
|
+
# [table, column] pairs, in the order `up` runs them.
|
|
71
|
+
COLUMNS = (SLUG_TABLES.map { |table| [ table, "slug" ] } + [ %w[users role] ]).freeze
|
|
72
|
+
|
|
73
|
+
def self.constraint_name(table, column) = "#{table}_#{column}_present"
|
|
74
|
+
|
|
75
|
+
def up
|
|
76
|
+
set_lock_timeout
|
|
77
|
+
COLUMNS.each do |table, column|
|
|
78
|
+
next unless nullable_column?(table, column)
|
|
79
|
+
|
|
80
|
+
name = self.class.constraint_name(table, column)
|
|
81
|
+
next if check_constraint_exists?(table, name: name)
|
|
82
|
+
|
|
83
|
+
column == "role" ? backfill_role : backfill_slug(table)
|
|
84
|
+
add_check_constraint table, "#{column} IS NOT NULL", name: name
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Removes only the CHECKs `up` adds. Backfilled values stay: they are valid
|
|
89
|
+
# either way. Run it before a rollback to a release older than 0.141.0 (see
|
|
90
|
+
# the changelog): that code inserts a NULL error_logs slug.
|
|
91
|
+
def down
|
|
92
|
+
COLUMNS.each do |table, column|
|
|
93
|
+
next unless table_exists?(table)
|
|
94
|
+
|
|
95
|
+
remove_check_constraint table, name: self.class.constraint_name(table, column), if_exists: true
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def postgres? = connection.adapter_name.downcase.include?("postgres")
|
|
102
|
+
|
|
103
|
+
def set_lock_timeout
|
|
104
|
+
return unless postgres?
|
|
105
|
+
|
|
106
|
+
execute "SET LOCAL lock_timeout = '#{LOCK_TIMEOUT}'"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def nullable_column?(table, column)
|
|
110
|
+
return false unless table_exists?(table)
|
|
111
|
+
|
|
112
|
+
found = columns(table).find { |c| c.name == column }
|
|
113
|
+
found.present? && found.null
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def quoted(table) = connection.quote_table_name(table)
|
|
117
|
+
|
|
118
|
+
def backfill_role
|
|
119
|
+
execute "UPDATE users SET role = 'viewer' WHERE role IS NULL"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def backfill_slug(table)
|
|
123
|
+
rows = connection.select_rows(<<~SQL.squish)
|
|
124
|
+
SELECT id#{', app_name' if NAMED.key?(table)} FROM #{quoted(table)}
|
|
125
|
+
WHERE slug IS NULL OR slug = '' ORDER BY id
|
|
126
|
+
SQL
|
|
127
|
+
rows.each do |id, app_name|
|
|
128
|
+
derived = derived_slug(table, id, app_name)
|
|
129
|
+
derived = "#{derived}-#{id}" if taken?(table, derived, id)
|
|
130
|
+
connection.update(<<~SQL.squish, "#{self.class.name} backfill")
|
|
131
|
+
UPDATE #{quoted(table)} SET slug = #{connection.quote(derived)} WHERE id = #{Integer(id)}
|
|
132
|
+
SQL
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def derived_slug(table, id, app_name)
|
|
137
|
+
return "error-log-#{id}" unless NAMED.key?(table)
|
|
138
|
+
|
|
139
|
+
name = ActiveSupport::Inflector.parameterize(app_name.to_s)
|
|
140
|
+
name.empty? ? "#{NAMED.fetch(table)}-#{id}" : "#{NAMED.fetch(table)}-#{name}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def taken?(table, slug, id)
|
|
144
|
+
connection.select_value(<<~SQL.squish).present?
|
|
145
|
+
SELECT 1 FROM #{quoted(table)} WHERE slug = #{connection.quote(slug)} AND id <> #{Integer(id)} LIMIT 1
|
|
146
|
+
SQL
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Studio
|
|
4
|
+
# The on-demand modal registry: modals the shared host (studio/modals/_host)
|
|
5
|
+
# fetches the first time they open, instead of the layout rendering every
|
|
6
|
+
# template into every page.
|
|
7
|
+
#
|
|
8
|
+
# # config/initializers/studio.rb
|
|
9
|
+
# Studio.modals.register :contest_rules, partial: "modals/contest_rules"
|
|
10
|
+
# Studio.modals.register :wallet_setup, partial: "modals/wallet_setup",
|
|
11
|
+
# auth: :user, locals: :wallet_setup_modal_locals
|
|
12
|
+
# Studio.modals.register :auth, partial: "modals/auth", prefetch: true
|
|
13
|
+
#
|
|
14
|
+
# A registered id is served by GET /_studio/modals/:id
|
|
15
|
+
# (Studio::ModalsController) and opened with the same
|
|
16
|
+
# $store.modals.open(id, props) call as before. Render it ONLY through the
|
|
17
|
+
# registry: an id that the layout still renders AND the registry names would
|
|
18
|
+
# mount twice. Every id the registry does not name behaves as it always did.
|
|
19
|
+
#
|
|
20
|
+
# Options:
|
|
21
|
+
# partial: the content partial, rendered exactly as the layout rendered it
|
|
22
|
+
# (one root element; see the README's "Modal host" rules).
|
|
23
|
+
# auth: :any (default) or :user. A :user modal answers 401 to a
|
|
24
|
+
# signed-out request, and is never prefetched for one.
|
|
25
|
+
# prefetch: true fetches the modal when the browser idles after load, for a
|
|
26
|
+
# modal that should open with no wait (the sign-in card).
|
|
27
|
+
# locals: the partial's locals. A Hash, a Symbol naming a helper the view
|
|
28
|
+
# answers (Turf's existing *_modal_locals helpers), or a callable
|
|
29
|
+
# taking the view. Resolved per request, inside the view.
|
|
30
|
+
module Modals
|
|
31
|
+
AUTH_LEVELS = %i[any user].freeze
|
|
32
|
+
|
|
33
|
+
# The id lands inside a single-quoted JavaScript string in the host's x-if,
|
|
34
|
+
# and in a URL path segment, so it is held to a shape that needs no escaping
|
|
35
|
+
# in either place.
|
|
36
|
+
ID_FORMAT = /\A[a-z0-9][a-z0-9_-]*\z/
|
|
37
|
+
|
|
38
|
+
Entry = Struct.new(:id, :partial, :auth, :prefetch, :locals, keyword_init: true) do
|
|
39
|
+
def user_only? = auth == :user
|
|
40
|
+
|
|
41
|
+
# The locals for one render, resolved in that render's view.
|
|
42
|
+
def locals_for(view)
|
|
43
|
+
resolved =
|
|
44
|
+
case locals
|
|
45
|
+
when nil then {}
|
|
46
|
+
when Hash then locals
|
|
47
|
+
when Symbol, String then view.public_send(locals)
|
|
48
|
+
else locals.arity.zero? ? view.instance_exec(&locals) : locals.call(view)
|
|
49
|
+
end
|
|
50
|
+
raise ArgumentError, "Studio.modals #{id}: locals must resolve to a Hash, got #{resolved.class}" unless resolved.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
resolved
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class Registry
|
|
57
|
+
def initialize
|
|
58
|
+
@entries = {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def register(id, partial:, auth: :any, prefetch: false, locals: nil)
|
|
62
|
+
key = id.to_s
|
|
63
|
+
raise ArgumentError, "Studio.modals: id #{id.inspect} must match #{ID_FORMAT.inspect}" unless key.match?(ID_FORMAT)
|
|
64
|
+
raise ArgumentError, "Studio.modals #{key}: partial is required" if partial.to_s.strip.empty?
|
|
65
|
+
raise ArgumentError, "Studio.modals #{key}: auth must be one of #{AUTH_LEVELS.inspect}" unless AUTH_LEVELS.include?(auth)
|
|
66
|
+
unless locals.nil? || locals.is_a?(Hash) || locals.is_a?(Symbol) || locals.is_a?(String) || locals.respond_to?(:call)
|
|
67
|
+
raise ArgumentError, "Studio.modals #{key}: locals must be a Hash, a helper name or a callable"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
@entries[key] = Entry.new(id: key, partial: partial.to_s, auth: auth, prefetch: prefetch == true, locals: locals)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def [](id) = @entries[id.to_s]
|
|
74
|
+
def registered?(id) = @entries.key?(id.to_s)
|
|
75
|
+
def ids = @entries.keys
|
|
76
|
+
def entries = @entries.values
|
|
77
|
+
def any? = @entries.any?
|
|
78
|
+
def empty? = @entries.empty?
|
|
79
|
+
|
|
80
|
+
# Removes one id, or every id. For tests and for a host that registers
|
|
81
|
+
# conditionally on reload.
|
|
82
|
+
def unregister(id) = @entries.delete(id.to_s)
|
|
83
|
+
def clear! = @entries.clear
|
|
84
|
+
|
|
85
|
+
# What the host template advertises to studio/modal_host. Every id, so a
|
|
86
|
+
# signed-out open of a :user modal still reaches the endpoint and shows
|
|
87
|
+
# the error card; prefetch only what this viewer may load.
|
|
88
|
+
def client_config(url_template:, logged_in:)
|
|
89
|
+
{
|
|
90
|
+
url: url_template,
|
|
91
|
+
ids: ids,
|
|
92
|
+
prefetch: entries.select { |e| e.prefetch && (!e.user_only? || logged_in) }.map(&:id)
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.modals
|
|
99
|
+
@modals ||= Modals::Registry.new
|
|
100
|
+
end
|
|
101
|
+
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
|
@@ -33,6 +33,7 @@ require "studio/js_literal"
|
|
|
33
33
|
require "studio/js_identifier"
|
|
34
34
|
require "studio/hold_button"
|
|
35
35
|
require "studio/partial_block"
|
|
36
|
+
require "studio/modals"
|
|
36
37
|
require "studio/sidebar_sections"
|
|
37
38
|
require "studio/icons"
|
|
38
39
|
require "studio/site_footer"
|
|
@@ -306,6 +307,7 @@ module Studio
|
|
|
306
307
|
# today. :glass floats an inset, translucent panel over the page, with line icons
|
|
307
308
|
# (a link's icon:), a marked current page and no separate blurred edge. It is opt-in,
|
|
308
309
|
# so an app moves only when it asks. engine.css, THE GLASS SIDEBAR, holds the look.
|
|
310
|
+
# :glass is the standard for a NEW app (docs/NEW_APP_SETUP.md); the default stays put.
|
|
309
311
|
mattr_reader :sidebar_style, default: :default
|
|
310
312
|
|
|
311
313
|
def self.sidebar_style=(value)
|
|
@@ -316,6 +318,7 @@ module Studio
|
|
|
316
318
|
# How cards look. :glass makes them translucent over a soft ambient page, once the host
|
|
317
319
|
# layout says so on its root: <html data-surface-style="<%= Studio.surface_style %>">.
|
|
318
320
|
# :default is what every app has today. engine.css, THE GLASS SURFACES, holds the look.
|
|
321
|
+
# :glass is the standard for a NEW app (docs/NEW_APP_SETUP.md); the default stays put.
|
|
319
322
|
SURFACE_STYLES = %i[default glass].freeze
|
|
320
323
|
mattr_reader :surface_style, default: :default
|
|
321
324
|
|
|
@@ -2192,6 +2195,14 @@ module Studio
|
|
|
2192
2195
|
get "_studio/health_report", to: "studio/health_reports#show", as: :studio_health_report,
|
|
2193
2196
|
defaults: { format: :json }
|
|
2194
2197
|
|
|
2198
|
+
# The on-demand modal fragments (Studio::ModalsController, Studio.modals).
|
|
2199
|
+
# Drawn for every app: the controller is the gate, answering 404 for any
|
|
2200
|
+
# id the registry does not name, so an app that registers nothing serves
|
|
2201
|
+
# nothing. HTML only; studio/modal_host is its one caller.
|
|
2202
|
+
get "_studio/modals/:id", to: "studio/modals#show", as: :studio_modal,
|
|
2203
|
+
constraints: { id: /[a-z0-9][a-z0-9_-]*/ },
|
|
2204
|
+
defaults: { format: :html }
|
|
2205
|
+
|
|
2195
2206
|
# Developer-desk tools. Drawn outside production, and each controller
|
|
2196
2207
|
# re-checks Studio.local_tool_enabled? per request (loopback only) — the
|
|
2197
2208
|
# route being absent is the outer gate, not the only one.
|
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.
|
|
4
|
+
version: 0.142.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
@@ -439,8 +439,24 @@ files:
|
|
|
439
439
|
- app/components/previews/studio/badge_component_preview.rb
|
|
440
440
|
- app/components/previews/studio/badge_component_preview/schemes.html.erb
|
|
441
441
|
- app/components/previews/studio/badge_component_preview/tones.html.erb
|
|
442
|
+
- app/components/previews/studio/button_component_preview.rb
|
|
443
|
+
- app/components/previews/studio/button_component_preview/disabled.html.erb
|
|
444
|
+
- app/components/previews/studio/button_component_preview/icon_and_link.html.erb
|
|
445
|
+
- app/components/previews/studio/button_component_preview/loading.html.erb
|
|
446
|
+
- app/components/previews/studio/button_component_preview/sizes.html.erb
|
|
447
|
+
- app/components/previews/studio/button_component_preview/tones.html.erb
|
|
448
|
+
- app/components/previews/studio/empty_state_component_preview.rb
|
|
449
|
+
- app/components/previews/studio/empty_state_component_preview/with_icon_and_action.html.erb
|
|
450
|
+
- app/components/previews/studio/table_component_preview.rb
|
|
451
|
+
- app/components/previews/studio/table_component_preview/rows.html.erb
|
|
442
452
|
- app/components/studio/badge_component.html.erb
|
|
443
453
|
- app/components/studio/badge_component.rb
|
|
454
|
+
- app/components/studio/button_component.html.erb
|
|
455
|
+
- app/components/studio/button_component.rb
|
|
456
|
+
- app/components/studio/empty_state_component.html.erb
|
|
457
|
+
- app/components/studio/empty_state_component.rb
|
|
458
|
+
- app/components/studio/table_component.html.erb
|
|
459
|
+
- app/components/studio/table_component.rb
|
|
444
460
|
- app/controllers/concerns/solana/session_auth.rb
|
|
445
461
|
- app/controllers/concerns/studio/admin_models.rb
|
|
446
462
|
- app/controllers/concerns/studio/board/reorderable.rb
|
|
@@ -474,6 +490,7 @@ files:
|
|
|
474
490
|
- app/controllers/studio/local_emails_controller.rb
|
|
475
491
|
- app/controllers/studio/local_reviews_controller.rb
|
|
476
492
|
- app/controllers/studio/marketing_kit_controller.rb
|
|
493
|
+
- app/controllers/studio/modals_controller.rb
|
|
477
494
|
- app/controllers/studio/models_controller.rb
|
|
478
495
|
- app/controllers/studio/not_found_controller.rb
|
|
479
496
|
- app/controllers/studio/onboarding_controller.rb
|
|
@@ -779,6 +796,7 @@ files:
|
|
|
779
796
|
- app/views/studio/modals/blocks/_success_card.html.erb
|
|
780
797
|
- app/views/studio/modals/blocks/_wallet_brand_sprite.html.erb
|
|
781
798
|
- app/views/studio/modals/onboarding/_first_name.html.erb
|
|
799
|
+
- app/views/studio/modals/remote.html.erb
|
|
782
800
|
- app/views/studio/modals/shared/_age_attestation.html.erb
|
|
783
801
|
- app/views/studio/modals/shared/_email_field.html.erb
|
|
784
802
|
- app/views/studio/modals/templates/_action.html.erb
|
|
@@ -838,6 +856,7 @@ files:
|
|
|
838
856
|
- app/views/style/_modal_specimen.html.erb
|
|
839
857
|
- app/views/style/_modals.html.erb
|
|
840
858
|
- app/views/style/_pages.html.erb
|
|
859
|
+
- app/views/style/_primitives.html.erb
|
|
841
860
|
- app/views/style/_scrims.html.erb
|
|
842
861
|
- app/views/style/_site_footer.html.erb
|
|
843
862
|
- app/views/style/_specimen.html.erb
|
|
@@ -892,6 +911,7 @@ files:
|
|
|
892
911
|
- db/migrate/20261108160000_require_slugs_on_engine_tables.rb
|
|
893
912
|
- db/migrate/20261108160001_add_engine_state_checks.rb
|
|
894
913
|
- db/migrate/20261108170000_add_users_role_known_check.rb
|
|
914
|
+
- db/migrate/20261109180000_add_engine_not_null_checks.rb
|
|
895
915
|
- docs/BOOKING.md
|
|
896
916
|
- docs/SITE_FOOTER.md
|
|
897
917
|
- lib/active_storage/service/studio_trash_s3_service.rb
|
|
@@ -965,6 +985,7 @@ files:
|
|
|
965
985
|
- lib/studio/mail_sender.rb
|
|
966
986
|
- lib/studio/mail_transport.rb
|
|
967
987
|
- lib/studio/marketing_kit.rb
|
|
988
|
+
- lib/studio/modals.rb
|
|
968
989
|
- lib/studio/name_parts.rb
|
|
969
990
|
- lib/studio/navbar_account_sidebar.rb
|
|
970
991
|
- lib/studio/navbar_brand.rb
|