studio-engine 0.56.4 → 0.57.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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -1
  3. data/README.md +24 -9
  4. data/app/assets/images/state-flags/ak.svg +22 -0
  5. data/app/assets/images/state-flags/al.svg +6 -0
  6. data/app/assets/images/state-flags/ar.svg +244 -0
  7. data/app/assets/images/state-flags/az.svg +8 -0
  8. data/app/assets/images/state-flags/ca.svg +1 -0
  9. data/app/assets/images/state-flags/co.svg +9 -0
  10. data/app/assets/images/state-flags/ct.svg +2472 -0
  11. data/app/assets/images/state-flags/dc.svg +18 -0
  12. data/app/assets/images/state-flags/de.svg +1549 -0
  13. data/app/assets/images/state-flags/fl.svg +6784 -0
  14. data/app/assets/images/state-flags/ga.svg +71 -0
  15. data/app/assets/images/state-flags/hi.svg +11 -0
  16. data/app/assets/images/state-flags/ia.svg +1 -0
  17. data/app/assets/images/state-flags/id.svg +105 -0
  18. data/app/assets/images/state-flags/il.svg +538 -0
  19. data/app/assets/images/state-flags/in.svg +57 -0
  20. data/app/assets/images/state-flags/ks.svg +2575 -0
  21. data/app/assets/images/state-flags/ky.svg +2918 -0
  22. data/app/assets/images/state-flags/la.svg +1904 -0
  23. data/app/assets/images/state-flags/ma.svg +142 -0
  24. data/app/assets/images/state-flags/md.svg +23 -0
  25. data/app/assets/images/state-flags/me.svg +3277 -0
  26. data/app/assets/images/state-flags/mi.svg +792 -0
  27. data/app/assets/images/state-flags/mn.svg +2486 -0
  28. data/app/assets/images/state-flags/mo.svg +2726 -0
  29. data/app/assets/images/state-flags/ms.svg +9 -0
  30. data/app/assets/images/state-flags/mt.svg +1288 -0
  31. data/app/assets/images/state-flags/nc.svg +268 -0
  32. data/app/assets/images/state-flags/nd.svg +2365 -0
  33. data/app/assets/images/state-flags/ne.svg +1960 -0
  34. data/app/assets/images/state-flags/nh.svg +2 -0
  35. data/app/assets/images/state-flags/nj.svg +2265 -0
  36. data/app/assets/images/state-flags/nm.svg +6 -0
  37. data/app/assets/images/state-flags/nv.svg +468 -0
  38. data/app/assets/images/state-flags/ny.svg +995 -0
  39. data/app/assets/images/state-flags/oh.svg +49 -0
  40. data/app/assets/images/state-flags/ok.svg +1036 -0
  41. data/app/assets/images/state-flags/or.svg +1 -0
  42. data/app/assets/images/state-flags/pa.svg +304 -0
  43. data/app/assets/images/state-flags/pr.svg +7 -0
  44. data/app/assets/images/state-flags/ri.svg +29 -0
  45. data/app/assets/images/state-flags/sc.svg +84 -0
  46. data/app/assets/images/state-flags/sd.svg +446 -0
  47. data/app/assets/images/state-flags/thumb/ak.png +0 -0
  48. data/app/assets/images/state-flags/thumb/al.png +0 -0
  49. data/app/assets/images/state-flags/thumb/ar.png +0 -0
  50. data/app/assets/images/state-flags/thumb/az.png +0 -0
  51. data/app/assets/images/state-flags/thumb/ca.png +0 -0
  52. data/app/assets/images/state-flags/thumb/co.png +0 -0
  53. data/app/assets/images/state-flags/thumb/ct.png +0 -0
  54. data/app/assets/images/state-flags/thumb/dc.png +0 -0
  55. data/app/assets/images/state-flags/thumb/de.png +0 -0
  56. data/app/assets/images/state-flags/thumb/fl.png +0 -0
  57. data/app/assets/images/state-flags/thumb/ga.png +0 -0
  58. data/app/assets/images/state-flags/thumb/hi.png +0 -0
  59. data/app/assets/images/state-flags/thumb/ia.png +0 -0
  60. data/app/assets/images/state-flags/thumb/id.png +0 -0
  61. data/app/assets/images/state-flags/thumb/il.png +0 -0
  62. data/app/assets/images/state-flags/thumb/in.png +0 -0
  63. data/app/assets/images/state-flags/thumb/ks.png +0 -0
  64. data/app/assets/images/state-flags/thumb/ky.png +0 -0
  65. data/app/assets/images/state-flags/thumb/la.png +0 -0
  66. data/app/assets/images/state-flags/thumb/ma.png +0 -0
  67. data/app/assets/images/state-flags/thumb/md.png +0 -0
  68. data/app/assets/images/state-flags/thumb/me.png +0 -0
  69. data/app/assets/images/state-flags/thumb/mi.png +0 -0
  70. data/app/assets/images/state-flags/thumb/mn.png +0 -0
  71. data/app/assets/images/state-flags/thumb/mo.png +0 -0
  72. data/app/assets/images/state-flags/thumb/ms.png +0 -0
  73. data/app/assets/images/state-flags/thumb/mt.png +0 -0
  74. data/app/assets/images/state-flags/thumb/nc.png +0 -0
  75. data/app/assets/images/state-flags/thumb/nd.png +0 -0
  76. data/app/assets/images/state-flags/thumb/ne.png +0 -0
  77. data/app/assets/images/state-flags/thumb/nh.png +0 -0
  78. data/app/assets/images/state-flags/thumb/nj.png +0 -0
  79. data/app/assets/images/state-flags/thumb/nm.png +0 -0
  80. data/app/assets/images/state-flags/thumb/nv.png +0 -0
  81. data/app/assets/images/state-flags/thumb/ny.png +0 -0
  82. data/app/assets/images/state-flags/thumb/oh.png +0 -0
  83. data/app/assets/images/state-flags/thumb/ok.png +0 -0
  84. data/app/assets/images/state-flags/thumb/or.png +0 -0
  85. data/app/assets/images/state-flags/thumb/pa.png +0 -0
  86. data/app/assets/images/state-flags/thumb/pr.png +0 -0
  87. data/app/assets/images/state-flags/thumb/ri.png +0 -0
  88. data/app/assets/images/state-flags/thumb/sc.png +0 -0
  89. data/app/assets/images/state-flags/thumb/sd.png +0 -0
  90. data/app/assets/images/state-flags/thumb/tn.png +0 -0
  91. data/app/assets/images/state-flags/thumb/tx.png +0 -0
  92. data/app/assets/images/state-flags/thumb/ut.png +0 -0
  93. data/app/assets/images/state-flags/thumb/va.png +0 -0
  94. data/app/assets/images/state-flags/thumb/vt.png +0 -0
  95. data/app/assets/images/state-flags/thumb/wa.png +0 -0
  96. data/app/assets/images/state-flags/thumb/wi.png +0 -0
  97. data/app/assets/images/state-flags/thumb/wv.png +0 -0
  98. data/app/assets/images/state-flags/thumb/wy.png +0 -0
  99. data/app/assets/images/state-flags/tn.svg +23 -0
  100. data/app/assets/images/state-flags/tx.svg +16 -0
  101. data/app/assets/images/state-flags/ut.svg +3225 -0
  102. data/app/assets/images/state-flags/va.svg +1 -0
  103. data/app/assets/images/state-flags/vt.svg +397 -0
  104. data/app/assets/images/state-flags/wa.svg +2620 -0
  105. data/app/assets/images/state-flags/wi.svg +3611 -0
  106. data/app/assets/images/state-flags/wv.svg +1 -0
  107. data/app/assets/images/state-flags/wy.svg +210 -0
  108. data/app/controllers/concerns/studio/geo_detection.rb +198 -0
  109. data/app/controllers/studio/geo_settings_controller.rb +155 -0
  110. data/app/helpers/studio/geo_helper.rb +73 -0
  111. data/app/models/studio/geo_setting.rb +137 -0
  112. data/app/views/components/_geo_badge.html.erb +63 -0
  113. data/app/views/studio/geo_settings/edit.html.erb +368 -0
  114. data/db/migrate/20260818120000_create_studio_geo_settings.rb +46 -0
  115. data/lib/studio/engine.rb +30 -0
  116. data/lib/studio/geo/countries.rb +269 -0
  117. data/lib/studio/geo/lookup.rb +114 -0
  118. data/lib/studio/geo.rb +244 -0
  119. data/lib/studio/version.rb +1 -1
  120. data/lib/studio.rb +89 -0
  121. data/studio-engine.gemspec +4 -0
  122. metadata +135 -1
data/lib/studio.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require "studio/version"
2
2
  require "studio/log_rotation"
3
3
  require "studio/ip_locations"
4
+ require "studio/geo"
5
+ require "studio/geo/lookup"
4
6
  require "studio/engine"
5
7
  require "studio/color_scale"
6
8
  require "studio/environment_banner"
@@ -244,6 +246,74 @@ module Studio
244
246
  # }
245
247
  mattr_accessor :onboarding_steps_resolver, default: ->(_user, _session) { [] }
246
248
 
249
+ # ---- Geo (Studio::Geo, Studio::GeoSetting, Studio::GeoDetection) ---------
250
+ #
251
+ # An app gets geo VALIDATION — where is this visitor, render their flag, is
252
+ # this location allowed — by including Studio::GeoDetection. It gets geo
253
+ # LOCKING by hanging `require_geo_allowed` on the surfaces it must not serve
254
+ # there. The engine deliberately does not decide which surfaces those are; see
255
+ # docs/GEO.md.
256
+
257
+ # This app's own country, ISO alpha-2. Two things read it: a bare subdivision
258
+ # code ("WA") is resolved as a subdivision OF this country, and the fail-closed
259
+ # rule fires only for visitors placed HERE with no detectable subdivision.
260
+ mattr_accessor :geo_home_country, default: "US"
261
+
262
+ # The policy a fresh app enforces before an operator has saved anything at
263
+ # /admin/geo. Countries are alpha-2 codes; subdivisions are region tokens
264
+ # ("US-WA"), though bare codes are accepted and normalized.
265
+ mattr_accessor :geo_default_banned_countries, default: []
266
+ mattr_accessor :geo_default_banned_subdivisions, default: []
267
+
268
+ # How to treat a HOME-COUNTRY visitor whose subdivision cannot be resolved (a
269
+ # VPN, a datacenter IP, a provider outage). true = blocked, because a blank
270
+ # could be any of the blocked regions masked by a failed lookup. An app whose
271
+ # geo rules are advisory rather than legal sets this false.
272
+ mattr_accessor :geo_fail_closed, default: true
273
+
274
+ # Freshness windows for the session-cached lookup. A RESOLVED region is trusted
275
+ # for a day; a BLANK one is retried within minutes, so a provider blip does not
276
+ # cache "nowhere" — and fail every gate closed — for 24 hours.
277
+ mattr_accessor :geo_ttl, default: 24.hours
278
+ mattr_accessor :geo_retry_ttl, default: 5.minutes
279
+
280
+ # Where a DEVELOPMENT session stands. Loopback never geocodes, so without this
281
+ # every geo-gated feature fails closed on a developer's own machine. Development
282
+ # only — test and production must report an unplaceable visitor honestly.
283
+ #
284
+ # config.geo_development_region = ENV.fetch("DEV_GEO_STATE", "US-CO")
285
+ mattr_accessor :geo_development_region, default: nil
286
+
287
+ # The region /admin/geo's "simulate" button puts the operator in, so a blocked
288
+ # experience can be walked without a VPN. Defaults to the first blocked
289
+ # subdivision the app actually has, which is the one worth testing.
290
+ mattr_accessor :geo_simulated_region, default: nil
291
+
292
+ # What a blocked visitor is told, and where an HTML request lands. The message
293
+ # takes (subdivision, country) — both may be nil on the fail-closed path, which
294
+ # is why the default omits an empty "( )" rather than printing one.
295
+ mattr_accessor :geo_blocked_message, default: lambda { |subdivision, _country|
296
+ place = subdivision.present? ? " (#{subdivision})" : ""
297
+ "This feature is not available in your area#{place}."
298
+ }
299
+ mattr_accessor :geo_blocked_redirect, default: nil
300
+
301
+ # Draw /admin/geo + /geo/check from Studio.routes. OFF by default, for the same
302
+ # hard reason as draw_admin_emails_routes: turf-monster already owns
303
+ # admin_geo_path, admin_geo_update_path, admin_geo_toggle_path and
304
+ # geo_check_path, and a duplicate route NAME raises while that app's routes.rb
305
+ # is loading — taking its entire route set down, not just this page.
306
+ mattr_accessor :draw_geo_routes, default: false
307
+
308
+ # Whether the engine configures Geocoder on boot (provider, HTTPS, timeout, and
309
+ # a Rails.cache-backed IP cache). An app that configures Geocoder itself sets
310
+ # this false; an app with no geocoder gem is unaffected either way.
311
+ mattr_accessor :configure_geocoder, default: true
312
+ mattr_accessor :geo_ip_provider, default: :ipinfo_io
313
+ mattr_accessor :geo_ip_api_key, default: nil
314
+ mattr_accessor :geo_lookup_timeout, default: 3
315
+ mattr_accessor :geo_cache_ttl, default: 24.hours
316
+
247
317
  # Session key recording "asked, and they said not now". Session-scoped
248
318
  # DELIBERATELY: skipping means not now, not never — the field stays blank, so a
249
319
  # later session may ask again. That is the whole reason this is not a column.
@@ -731,6 +801,25 @@ module Studio
731
801
  patch "admin/theme", to: "theme_settings#update", as: :admin_theme_update
732
802
  post "admin/theme/regenerate", to: "theme_settings#regenerate", as: :admin_theme_regenerate
733
803
  get "admin/schema", to: "schema#index", as: :admin_schema
804
+
805
+ # The shared geo manager (/admin/geo) + the public detection probe
806
+ # (/geo/check). OPT-IN — see Studio.draw_geo_routes: turf-monster owns all
807
+ # four of these helper names today, and drawing a name an app already has
808
+ # raises `Invalid route name, already in use` while that app's routes.rb
809
+ # loads, which kills every route in it. An app opts in from its initializer
810
+ # once its local copies are deleted:
811
+ #
812
+ # config.draw_geo_routes = true
813
+ #
814
+ # This gates only the PAGE and the probe. Detection, the badge, the policy
815
+ # and the gate are always available to an app that includes
816
+ # Studio::GeoDetection — an app is geo-aware whether or not it draws these.
817
+ if Studio.draw_geo_routes
818
+ get "geo/check", to: "studio/geo_settings#check", as: :geo_check
819
+ get "admin/geo", to: "studio/geo_settings#edit", as: :admin_geo
820
+ patch "admin/geo", to: "studio/geo_settings#update", as: :admin_geo_update
821
+ post "admin/geo/toggle", to: "studio/geo_settings#toggle_override", as: :admin_geo_toggle
822
+ end
734
823
  # The living style guide. Canonical at /admin/style (StyleController#index);
735
824
  # /admin/design_system redirects here but KEEPS its admin_design_system_path
736
825
  # helper so a shipped host sidebar link on the old helper still resolves.
@@ -37,4 +37,8 @@ Gem::Specification.new do |spec|
37
37
  # 500'd a host app's task board — declaring it here makes that impossible to repeat.
38
38
  spec.add_dependency "redis", ">= 4.0.1"
39
39
  spec.add_dependency "turbo-rails", ">= 1.0"
40
+ # IP -> location for Studio::Geo. A dependency rather than a host concern
41
+ # because the point of the geo primitive is that EVERY app has the capacity:
42
+ # an app that has to add a gem before it can place a visitor does not.
43
+ spec.add_dependency "geocoder", ">= 1.8", "< 2.0"
40
44
  end
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.56.4
4
+ version: 0.57.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
@@ -154,6 +154,26 @@ dependencies:
154
154
  - - ">="
155
155
  - !ruby/object:Gem::Version
156
156
  version: '1.0'
157
+ - !ruby/object:Gem::Dependency
158
+ name: geocoder
159
+ requirement: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '1.8'
164
+ - - "<"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.0'
167
+ type: :runtime
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '1.8'
174
+ - - "<"
175
+ - !ruby/object:Gem::Version
176
+ version: '2.0'
157
177
  description: Studio Engine is a non-isolated Rails engine that ships an opinionated
158
178
  authentication + SSO contract, a polymorphic ErrorLog model, a Sluggable concern,
159
179
  a 7-role dynamic theme system with CSS-custom-property generation, and an S3-backed
@@ -177,6 +197,110 @@ files:
177
197
  - app/assets/images/emails/newsletter-subscribed-background.gif
178
198
  - app/assets/images/resend-favicon.png
179
199
  - app/assets/images/ses-favicon.png
200
+ - app/assets/images/state-flags/ak.svg
201
+ - app/assets/images/state-flags/al.svg
202
+ - app/assets/images/state-flags/ar.svg
203
+ - app/assets/images/state-flags/az.svg
204
+ - app/assets/images/state-flags/ca.svg
205
+ - app/assets/images/state-flags/co.svg
206
+ - app/assets/images/state-flags/ct.svg
207
+ - app/assets/images/state-flags/dc.svg
208
+ - app/assets/images/state-flags/de.svg
209
+ - app/assets/images/state-flags/fl.svg
210
+ - app/assets/images/state-flags/ga.svg
211
+ - app/assets/images/state-flags/hi.svg
212
+ - app/assets/images/state-flags/ia.svg
213
+ - app/assets/images/state-flags/id.svg
214
+ - app/assets/images/state-flags/il.svg
215
+ - app/assets/images/state-flags/in.svg
216
+ - app/assets/images/state-flags/ks.svg
217
+ - app/assets/images/state-flags/ky.svg
218
+ - app/assets/images/state-flags/la.svg
219
+ - app/assets/images/state-flags/ma.svg
220
+ - app/assets/images/state-flags/md.svg
221
+ - app/assets/images/state-flags/me.svg
222
+ - app/assets/images/state-flags/mi.svg
223
+ - app/assets/images/state-flags/mn.svg
224
+ - app/assets/images/state-flags/mo.svg
225
+ - app/assets/images/state-flags/ms.svg
226
+ - app/assets/images/state-flags/mt.svg
227
+ - app/assets/images/state-flags/nc.svg
228
+ - app/assets/images/state-flags/nd.svg
229
+ - app/assets/images/state-flags/ne.svg
230
+ - app/assets/images/state-flags/nh.svg
231
+ - app/assets/images/state-flags/nj.svg
232
+ - app/assets/images/state-flags/nm.svg
233
+ - app/assets/images/state-flags/nv.svg
234
+ - app/assets/images/state-flags/ny.svg
235
+ - app/assets/images/state-flags/oh.svg
236
+ - app/assets/images/state-flags/ok.svg
237
+ - app/assets/images/state-flags/or.svg
238
+ - app/assets/images/state-flags/pa.svg
239
+ - app/assets/images/state-flags/pr.svg
240
+ - app/assets/images/state-flags/ri.svg
241
+ - app/assets/images/state-flags/sc.svg
242
+ - app/assets/images/state-flags/sd.svg
243
+ - app/assets/images/state-flags/thumb/ak.png
244
+ - app/assets/images/state-flags/thumb/al.png
245
+ - app/assets/images/state-flags/thumb/ar.png
246
+ - app/assets/images/state-flags/thumb/az.png
247
+ - app/assets/images/state-flags/thumb/ca.png
248
+ - app/assets/images/state-flags/thumb/co.png
249
+ - app/assets/images/state-flags/thumb/ct.png
250
+ - app/assets/images/state-flags/thumb/dc.png
251
+ - app/assets/images/state-flags/thumb/de.png
252
+ - app/assets/images/state-flags/thumb/fl.png
253
+ - app/assets/images/state-flags/thumb/ga.png
254
+ - app/assets/images/state-flags/thumb/hi.png
255
+ - app/assets/images/state-flags/thumb/ia.png
256
+ - app/assets/images/state-flags/thumb/id.png
257
+ - app/assets/images/state-flags/thumb/il.png
258
+ - app/assets/images/state-flags/thumb/in.png
259
+ - app/assets/images/state-flags/thumb/ks.png
260
+ - app/assets/images/state-flags/thumb/ky.png
261
+ - app/assets/images/state-flags/thumb/la.png
262
+ - app/assets/images/state-flags/thumb/ma.png
263
+ - app/assets/images/state-flags/thumb/md.png
264
+ - app/assets/images/state-flags/thumb/me.png
265
+ - app/assets/images/state-flags/thumb/mi.png
266
+ - app/assets/images/state-flags/thumb/mn.png
267
+ - app/assets/images/state-flags/thumb/mo.png
268
+ - app/assets/images/state-flags/thumb/ms.png
269
+ - app/assets/images/state-flags/thumb/mt.png
270
+ - app/assets/images/state-flags/thumb/nc.png
271
+ - app/assets/images/state-flags/thumb/nd.png
272
+ - app/assets/images/state-flags/thumb/ne.png
273
+ - app/assets/images/state-flags/thumb/nh.png
274
+ - app/assets/images/state-flags/thumb/nj.png
275
+ - app/assets/images/state-flags/thumb/nm.png
276
+ - app/assets/images/state-flags/thumb/nv.png
277
+ - app/assets/images/state-flags/thumb/ny.png
278
+ - app/assets/images/state-flags/thumb/oh.png
279
+ - app/assets/images/state-flags/thumb/ok.png
280
+ - app/assets/images/state-flags/thumb/or.png
281
+ - app/assets/images/state-flags/thumb/pa.png
282
+ - app/assets/images/state-flags/thumb/pr.png
283
+ - app/assets/images/state-flags/thumb/ri.png
284
+ - app/assets/images/state-flags/thumb/sc.png
285
+ - app/assets/images/state-flags/thumb/sd.png
286
+ - app/assets/images/state-flags/thumb/tn.png
287
+ - app/assets/images/state-flags/thumb/tx.png
288
+ - app/assets/images/state-flags/thumb/ut.png
289
+ - app/assets/images/state-flags/thumb/va.png
290
+ - app/assets/images/state-flags/thumb/vt.png
291
+ - app/assets/images/state-flags/thumb/wa.png
292
+ - app/assets/images/state-flags/thumb/wi.png
293
+ - app/assets/images/state-flags/thumb/wv.png
294
+ - app/assets/images/state-flags/thumb/wy.png
295
+ - app/assets/images/state-flags/tn.svg
296
+ - app/assets/images/state-flags/tx.svg
297
+ - app/assets/images/state-flags/ut.svg
298
+ - app/assets/images/state-flags/va.svg
299
+ - app/assets/images/state-flags/vt.svg
300
+ - app/assets/images/state-flags/wa.svg
301
+ - app/assets/images/state-flags/wi.svg
302
+ - app/assets/images/state-flags/wv.svg
303
+ - app/assets/images/state-flags/wy.svg
180
304
  - app/assets/javascripts/studio/alpine.js
181
305
  - app/assets/javascripts/studio/canvas_confetti.js
182
306
  - app/assets/javascripts/studio/sortable.js
@@ -189,6 +313,7 @@ files:
189
313
  - app/controllers/concerns/studio/admin_models.rb
190
314
  - app/controllers/concerns/studio/board/reorderable.rb
191
315
  - app/controllers/concerns/studio/error_handling.rb
316
+ - app/controllers/concerns/studio/geo_detection.rb
192
317
  - app/controllers/concerns/studio/impersonation.rb
193
318
  - app/controllers/concerns/studio/link_consumption.rb
194
319
  - app/controllers/concerns/studio/magic_link_issuing.rb
@@ -202,6 +327,7 @@ files:
202
327
  - app/controllers/solana_sessions_controller.rb
203
328
  - app/controllers/studio/email_images_controller.rb
204
329
  - app/controllers/studio/emails_controller.rb
330
+ - app/controllers/studio/geo_settings_controller.rb
205
331
  - app/controllers/studio/links_controller.rb
206
332
  - app/controllers/studio/local_emails_controller.rb
207
333
  - app/controllers/studio/local_reviews_controller.rb
@@ -213,6 +339,7 @@ files:
213
339
  - app/helpers/studio/admin_models_table_helper.rb
214
340
  - app/helpers/studio/at_time_helper.rb
215
341
  - app/helpers/studio/fizz_helper.rb
342
+ - app/helpers/studio/geo_helper.rb
216
343
  - app/helpers/studio_email_delivery_helper.rb
217
344
  - app/helpers/studio_sidebar_helper.rb
218
345
  - app/helpers/studio_theme_helper.rb
@@ -233,6 +360,7 @@ files:
233
360
  - app/models/studio/email_delivery.rb
234
361
  - app/models/studio/email_setting.rb
235
362
  - app/models/studio/enumeral.rb
363
+ - app/models/studio/geo_setting.rb
236
364
  - app/models/studio/link.rb
237
365
  - app/models/studio/model_page.rb
238
366
  - app/models/theme_setting.rb
@@ -249,6 +377,7 @@ files:
249
377
  - app/views/components/_copy_button.html.erb
250
378
  - app/views/components/_emoji_swap.html.erb
251
379
  - app/views/components/_empty_state.html.erb
380
+ - app/views/components/_geo_badge.html.erb
252
381
  - app/views/components/_google_logo.html.erb
253
382
  - app/views/components/_input.html.erb
254
383
  - app/views/components/_json_debug.html.erb
@@ -304,6 +433,7 @@ files:
304
433
  - app/views/studio/emails/orphan.html.erb
305
434
  - app/views/studio/emails/show.html.erb
306
435
  - app/views/studio/fields/_date_of_birth.html.erb
436
+ - app/views/studio/geo_settings/edit.html.erb
307
437
  - app/views/studio/links/confirm.html.erb
308
438
  - app/views/studio/local_emails/index.html.erb
309
439
  - app/views/studio/mailers/_layered_banner.html.erb
@@ -388,6 +518,7 @@ files:
388
518
  - db/migrate/20260812220000_add_subject_to_studio_email_settings.rb
389
519
  - db/migrate/20260813010000_add_body_cta_footer_to_studio_email_settings.rb
390
520
  - db/migrate/20260813220000_add_standard_user_profile_columns.rb
521
+ - db/migrate/20260818120000_create_studio_geo_settings.rb
391
522
  - lib/studio-engine.rb
392
523
  - lib/studio.rb
393
524
  - lib/studio/cable.rb
@@ -396,6 +527,9 @@ files:
396
527
  - lib/studio/email_smoke.rb
397
528
  - lib/studio/engine.rb
398
529
  - lib/studio/environment_banner.rb
530
+ - lib/studio/geo.rb
531
+ - lib/studio/geo/countries.rb
532
+ - lib/studio/geo/lookup.rb
399
533
  - lib/studio/image_cache.rb
400
534
  - lib/studio/ip_locations.rb
401
535
  - lib/studio/link_resolution.rb