studio-engine 0.85.0 → 0.87.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.
data/lib/studio.rb CHANGED
@@ -1,3 +1,16 @@
1
+ # What this file and lib/studio/* call at REQUIRE time, required here rather
2
+ # than left to the host. `15.minutes` and `24.hours` below are evaluated the
3
+ # moment `require "studio"` runs, which is Bundler.require in the host's
4
+ # config/application.rb, BEFORE Rails' :load_active_support initializer pulls in
5
+ # any core extension. A host on `rails/all` happens to have them already (the
6
+ # ActiveRecord and ActiveJob railties load them); a host with no ActiveRecord
7
+ # does not, and its boot died on `Integer#minutes` until it required
8
+ # active_support/core_ext/integer/time itself. A gem requires what it uses.
9
+ # test/integration/footer_only_consumer_test.rb boots that host.
10
+ require "active_support"
11
+ require "active_support/core_ext/module/attribute_accessors"
12
+ require "active_support/core_ext/numeric/time"
13
+ require "active_support/core_ext/integer/time"
1
14
  require "studio/version"
2
15
  require "studio/log_rotation"
3
16
  require "studio/ip_locations"
@@ -569,10 +582,16 @@ module Studio
569
582
  # primitives: Google Calendar's appointment page inline, in a popup, and on a
570
583
  # page. See docs/SITE_FOOTER.md and docs/BOOKING.md.
571
584
 
572
- # The footer's facts: a Hash, or a callable receiving the view. nil (the
573
- # default) means this app has no footer, and `studio_site_footer` renders
574
- # nothing, so a layout can call it before the app declares one. Every key is
575
- # optional; shape and rules: lib/studio/site_footer.rb.
585
+ # The footer's facts. THE FOOTER IS ON BY DEFAULT: left unset (nil), the
586
+ # layout line `studio_site_footer` renders the DEFAULT footer, built from what
587
+ # the engine already knows (the site name, the footer or navbar logo, and a
588
+ # legal line linking the host's `privacy` and `terms` routes when it has
589
+ # them). No address, no map, no email, no link columns.
590
+ #
591
+ # nil (unset) the default footer
592
+ # false NO footer: the one opt-out
593
+ # a Hash, or a callable receiving the view: the app's own facts. Every key
594
+ # is optional; shape and rules: lib/studio/site_footer.rb.
576
595
  #
577
596
  # config.site_footer = ->(view) {
578
597
  # { tagline: "Everything, by example",
@@ -588,6 +607,35 @@ module Studio
588
607
  @@site_footer = declared
589
608
  end
590
609
 
610
+ # True unless the app turned the footer off (`config.site_footer = false`).
611
+ def self.site_footer_enabled?
612
+ site_footer != false
613
+ end
614
+
615
+ # True when the app wrote its own facts, as against taking the default footer
616
+ # (unset) or none (false).
617
+ def self.site_footer_declared?
618
+ !site_footer.nil? && site_footer != false
619
+ end
620
+
621
+ # THE LOCATION, AS ONE SETTING: the address the footer prints in its Location
622
+ # band, and maps when it carries lat: and lng:. nil (the default) is no
623
+ # location. It is how an app on the default footer adds one without writing
624
+ # the facts callable:
625
+ #
626
+ # config.site_footer_address = { street: "123 Example St", city_line: "Washington, DC 20024",
627
+ # lat: 38.8894, lng: -77.0352 }
628
+ #
629
+ # The keys are the facts' `address:` keys (docs/SITE_FOOTER.md). An app's own
630
+ # facts WIN: when they carry an address this is not read, and `address: false`
631
+ # there shows none.
632
+ mattr_reader :site_footer_address, default: nil
633
+
634
+ def self.site_footer_address=(declared)
635
+ SiteFooter.validate_address!(declared)
636
+ @@site_footer_address = declared || nil
637
+ end
638
+
591
639
  # Controllers whose pages keep the footer for a SIGNED-IN viewer, by
592
640
  # controller name ("landing") or path ("admin/reports"). A visitor gets the
593
641
  # footer on every page; a signed-in viewer gets it only here, because every
@@ -952,6 +1000,25 @@ module Studio
952
1000
  # that intentionally break the contract).
953
1001
  mattr_accessor :validate_user_contract, default: true
954
1002
 
1003
+ # Does the host load ActiveRecord at all? True for every app that requires
1004
+ # `rails/all` or `active_record/railtie` (all the engine's database-backed
1005
+ # consumers); false for a FOOTER-ONLY consumer, an app with no database that
1006
+ # takes the engine for its site footer and nothing else (docs/SITE_FOOTER.md,
1007
+ # "An app with no database").
1008
+ #
1009
+ # The RAILTIE, not ActiveRecord::Base: the railtie is what an app chooses in
1010
+ # config/application.rb, it is defined at require time (before any engine
1011
+ # initializer runs), and asking for it autoloads nothing.
1012
+ #
1013
+ # Three things key off it, each only when it reads false (Studio::Engine):
1014
+ # the user-contract check is skipped, the engine's ActiveRecord-dependent
1015
+ # app/ roots are kept out of eager loading, and
1016
+ # `studio_engine:install:migrations` is a no-op that exits 0. An app WITH
1017
+ # ActiveRecord sees none of them change.
1018
+ def self.active_record?
1019
+ defined?(::ActiveRecord::Railtie) ? true : false
1020
+ end
1021
+
955
1022
  # Only methods that consumers must explicitly define are checked here.
956
1023
  # Column accessors (#email, #name, #role) are NOT validated because
957
1024
  # ActiveRecord defines them lazily — they don't appear on `.instance_methods`
@@ -1175,14 +1242,25 @@ module Studio
1175
1242
  end
1176
1243
 
1177
1244
  # The site footer's facts resolved for a view (lib/studio/site_footer.rb), or
1178
- # nil when the app declared none. The name defaults to the site identity's
1179
- # title and the logo to the navbar logo, so an app that has set those up says
1180
- # them once.
1245
+ # nil when the app turned the footer off (or its own callable answered nil).
1246
+ # Unset, the facts are the default footer's. The name defaults to the site
1247
+ # identity's title and the logo to the navbar logo, so an app that has set
1248
+ # those up says them once; Studio.site_footer_address is the address of any
1249
+ # facts that carry none.
1181
1250
  def self.site_footer_for(view)
1182
- return nil if site_footer.nil?
1251
+ return nil unless site_footer_enabled?
1252
+
1253
+ declared = site_footer.nil? ? SiteFooter.default_facts(view, routes: host_route_set) : site_footer
1254
+ SiteFooter.resolve(declared, view, name: site_identity[:title], logo: logo_for("Footer Logo"),
1255
+ booking_path: booking_path_for(view), address: site_footer_address)
1256
+ end
1257
+
1258
+ # The host's route set, for the default footer's legal links. nil outside a
1259
+ # booted Rails app.
1260
+ def self.host_route_set
1261
+ return nil unless defined?(::Rails) && ::Rails.respond_to?(:application) && ::Rails.application
1183
1262
 
1184
- SiteFooter.resolve(site_footer, view, name: site_identity[:title], logo: logo_for("Footer Logo"),
1185
- booking_path: booking_path_for(view))
1263
+ ::Rails.application.routes
1186
1264
  end
1187
1265
 
1188
1266
  # Navbar links resolved for a view context (lib/studio/navbar_links.rb).
@@ -22,7 +22,14 @@ Gem::Specification.new do |spec|
22
22
  # read by bin/e2e-executed-set-check and test/lib/e2e_lane_contract_test.rb. It is
23
23
  # the only thing under config/, and it has no meaning in a consuming app, so it is
24
24
  # excluded rather than shipped. (e2e/, bin/ and test/ were never in this list.)
25
- spec.files = Dir["lib/**/*", "app/**/*", "config/**/*", "db/**/*", "tailwind/**/*", "Gemfile", "studio-engine.gemspec", "README.md", "CHANGELOG.md", "LICENSE"] - ["config/e2e_lane.yml"]
25
+ #
26
+ # docs/SITE_FOOTER.md and docs/BOOKING.md ship because consumers cite them from
27
+ # their own initializers ("docs/SITE_FOOTER.md is the contract"), and a
28
+ # citation should resolve in the installed gem, not only on GitHub. The rest of
29
+ # docs/ is the engine's own operating material. Guarded by
30
+ # test/lib/gemspec_ships_footer_docs_test.rb.
31
+ spec.files = Dir["lib/**/*", "app/**/*", "config/**/*", "db/**/*", "tailwind/**/*", "Gemfile", "studio-engine.gemspec", "README.md", "CHANGELOG.md", "LICENSE",
32
+ "docs/SITE_FOOTER.md", "docs/BOOKING.md"] - ["config/e2e_lane.yml"]
26
33
  spec.require_paths = ["lib"]
27
34
 
28
35
  spec.add_dependency "rails", ">= 7.2", "< 8.2"
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.85.0
4
+ version: 0.87.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-10-03 00:00:00.000000000 Z
11
+ date: 2026-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -541,6 +541,7 @@ files:
541
541
  - app/views/studio/site_footer/_footer.html.erb
542
542
  - app/views/studio/site_footer/_link.html.erb
543
543
  - app/views/studio/site_footer/_map.html.erb
544
+ - app/views/studio/site_footer/_map_assets.html.erb
544
545
  - app/views/studio/site_footer/_social_icon.html.erb
545
546
  - app/views/studio/site_identities/edit.html.erb
546
547
  - app/views/style/_host.html.erb
@@ -584,6 +585,8 @@ files:
584
585
  - db/migrate/20260902000001_create_studio_knowledge_expectations.rb
585
586
  - db/migrate/20260902000002_add_expectation_to_studio_knowledge_docs.rb
586
587
  - db/migrate/20260930120000_create_studio_site_identities.rb
588
+ - docs/BOOKING.md
589
+ - docs/SITE_FOOTER.md
587
590
  - lib/active_storage/service/studio_trash_s3_service.rb
588
591
  - lib/generators/studio/site_identity/site_identity_generator.rb
589
592
  - lib/studio-engine.rb