studio-engine 0.81.1 → 0.82.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 +151 -0
- data/README.md +2 -0
- data/app/controllers/concerns/studio/link_preview_bots.rb +110 -0
- data/app/controllers/studio/public_users_controller.rb +32 -0
- data/app/controllers/studio/site_identities_controller.rb +119 -0
- data/app/helpers/studio/link_preview_helper.rb +161 -0
- data/app/helpers/studio/public_user_helper.rb +61 -0
- data/app/models/studio/site_identity.rb +263 -0
- data/app/views/components/_avatar.html.erb +5 -3
- data/app/views/layouts/studio/_head.html.erb +6 -0
- data/app/views/layouts/studio/_link_preview_tags.html.erb +37 -0
- data/app/views/studio/public_users/not_found.html.erb +15 -0
- data/app/views/studio/public_users/show.html.erb +28 -0
- data/app/views/studio/site_identities/edit.html.erb +233 -0
- data/db/migrate/20260930120000_create_studio_site_identities.rb +30 -0
- data/lib/generators/studio/site_identity/site_identity_generator.rb +139 -0
- data/lib/studio/link_preview.rb +335 -0
- data/lib/studio/public_user.rb +65 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +151 -0
- metadata +16 -2
data/lib/studio.rb
CHANGED
|
@@ -3,6 +3,7 @@ require "studio/log_rotation"
|
|
|
3
3
|
require "studio/ip_locations"
|
|
4
4
|
require "studio/geo"
|
|
5
5
|
require "studio/geo/lookup"
|
|
6
|
+
require "studio/link_preview"
|
|
6
7
|
require "studio/engine"
|
|
7
8
|
require "studio/color_scale"
|
|
8
9
|
require "studio/environment_banner"
|
|
@@ -19,6 +20,7 @@ require "studio/profile_image"
|
|
|
19
20
|
require "studio/oauth_identity"
|
|
20
21
|
require "studio/newsletter"
|
|
21
22
|
require "studio/username_generator"
|
|
23
|
+
require "studio/public_user"
|
|
22
24
|
require "studio/name_parts"
|
|
23
25
|
require "studio/s3"
|
|
24
26
|
require "studio/image_cache"
|
|
@@ -471,6 +473,136 @@ module Studio
|
|
|
471
473
|
# is loading — taking its entire route set down, not just this page.
|
|
472
474
|
mattr_accessor :draw_geo_routes, default: false
|
|
473
475
|
|
|
476
|
+
# ---- Link preview (Studio::LinkPreview, SiteIdentity, LinkPreviewHelper)
|
|
477
|
+
#
|
|
478
|
+
# What an unfurl shows when someone pastes a link to this app: the SITE
|
|
479
|
+
# IDENTITY (Studio::SiteIdentity — image, title, description) the operator sets
|
|
480
|
+
# at /admin/link_preview, which any page may override (`link_preview image:
|
|
481
|
+
# ..., title: ...`). See docs/LINK_PREVIEW.md.
|
|
482
|
+
|
|
483
|
+
# Whether layouts/studio/_head emits the og:/twitter: tags.
|
|
484
|
+
#
|
|
485
|
+
# :auto (default) emit once this app has INSTALLED the engine's
|
|
486
|
+
# studio_site_identities table — installing the
|
|
487
|
+
# migration is the adoption act — and no template under
|
|
488
|
+
# app/views writes its own og:title/og:image. An app that
|
|
489
|
+
# emits its own tags (turf-monster, cyvasse) gets no second
|
|
490
|
+
# set, even after `install:migrations` brings the table in.
|
|
491
|
+
# true always emit (the static fallback and the site name still
|
|
492
|
+
# answer with no table).
|
|
493
|
+
# false never emit from the head; the app renders
|
|
494
|
+
# `studio_link_preview_tags` itself, or owns its tags.
|
|
495
|
+
#
|
|
496
|
+
# An app whose own tags live somewhere the scan cannot see (a helper that
|
|
497
|
+
# builds them in Ruby) sets this false until its adoption deletes them.
|
|
498
|
+
mattr_accessor :link_preview_tags, default: :auto
|
|
499
|
+
|
|
500
|
+
# The Active Storage service the DEFAULT image is attached to. nil = the app's
|
|
501
|
+
# default service. Unfurlers cache the og:image URL and re-fetch it days later,
|
|
502
|
+
# so the tag must be a PERMANENT URL: a service whose `public?` is true answers
|
|
503
|
+
# its own public URL; any other service is served through Rails' storage PROXY
|
|
504
|
+
# route (a permanent URL on this app's own domain, streaming from the private
|
|
505
|
+
# bucket). An app with a public-read service names it here, e.g. turf-monster:
|
|
506
|
+
#
|
|
507
|
+
# config.link_preview_image_service = OgImageAttachable::PUBLIC_OG_SERVICE
|
|
508
|
+
#
|
|
509
|
+
# Read once, when Studio::SiteIdentity loads (has_one_attached's service
|
|
510
|
+
# is a literal fixed at class load), so set it in the initializer.
|
|
511
|
+
mattr_accessor :link_preview_image_service, default: nil
|
|
512
|
+
|
|
513
|
+
# The last image rung, when neither the page nor the operator supplied one. A
|
|
514
|
+
# root-relative path is used only when the file exists under public/, so an app
|
|
515
|
+
# without it emits no og:image rather than a broken one. An absolute URL is
|
|
516
|
+
# trusted as given.
|
|
517
|
+
mattr_accessor :link_preview_fallback_image, default: "/og.png"
|
|
518
|
+
|
|
519
|
+
# The app's CANONICAL base URL for previews, e.g. "https://cyvasse.xyz". When
|
|
520
|
+
# set, og:url is this base plus the page's path (no query string), and every
|
|
521
|
+
# relative preview image is made absolute on it — so a share through a
|
|
522
|
+
# herokuapp.com host or a ?utm_ link does not split one page into many URLs in
|
|
523
|
+
# an unfurler's share counts. nil (the default) uses the request's base URL,
|
|
524
|
+
# and og:url is the URL as requested. Set it in production only, or a local
|
|
525
|
+
# page's card points at production:
|
|
526
|
+
#
|
|
527
|
+
# config.link_preview_base_url = "https://cyvasse.xyz" if Rails.env.production?
|
|
528
|
+
mattr_accessor :link_preview_base_url, default: nil
|
|
529
|
+
|
|
530
|
+
# THE SITE IDENTITY'S DRAFTED DEFAULTS — this app's title and description as
|
|
531
|
+
# written in code, under whatever the operator saves at /admin/link_preview.
|
|
532
|
+
# The standing convention is that an agent drafts these when it sets an app up
|
|
533
|
+
# (`bin/rails g studio:site_identity --title "..." --description "..."` writes
|
|
534
|
+
# them here) and Alex edits them on the page. nil title means Studio.app_name;
|
|
535
|
+
# nil description means none. Read the resolved answer through
|
|
536
|
+
# Studio.site_identity, never these directly.
|
|
537
|
+
mattr_accessor :site_title, default: nil
|
|
538
|
+
mattr_accessor :site_description, default: nil
|
|
539
|
+
|
|
540
|
+
# Draw /admin/link_preview from Studio.routes. ON by default: no consumer owns
|
|
541
|
+
# these paths or helper names (admin_link_preview, admin_link_preview_image),
|
|
542
|
+
# checked 2026-09-30 across mcritchie-studio, turf-monster, cyvasse and
|
|
543
|
+
# mcritchie-industries. The page explains itself when the table is missing.
|
|
544
|
+
mattr_accessor :draw_link_preview_routes, default: true
|
|
545
|
+
|
|
546
|
+
# Draw the public user page, GET /u/:username (Studio::PublicUsersController,
|
|
547
|
+
# route helper studio_public_user_path; link to it with the view helpers
|
|
548
|
+
# studio_user_profile_path(user) / link_to_user_profile(user)). See
|
|
549
|
+
# docs/PUBLIC_USER_PAGE.md.
|
|
550
|
+
#
|
|
551
|
+
# OFF by default, like every route surface a consumer might already own. No
|
|
552
|
+
# consumer owns /u or the helper name today (checked 2026-09-30 across
|
|
553
|
+
# mcritchie-studio, mcritchie-industries, cyvasse and turf-monster), but the
|
|
554
|
+
# page also needs a `username` column, which the two hub apps do not have yet
|
|
555
|
+
# (their slug is keyed on the email, so it can never stand in). Each app's
|
|
556
|
+
# adoption turns it on:
|
|
557
|
+
#
|
|
558
|
+
# config.draw_public_user_routes = true
|
|
559
|
+
mattr_accessor :draw_public_user_routes, default: false
|
|
560
|
+
|
|
561
|
+
# THE APP'S IDENTITY COPY, resolved: { title:, description:, image_url: }.
|
|
562
|
+
# The operator's saved value (Studio::SiteIdentity, edited at
|
|
563
|
+
# /admin/link_preview) wins, then the drafted Studio.site_title /
|
|
564
|
+
# site_description, then Studio.app_name for the title. image_url is the
|
|
565
|
+
# uploaded image, else the static fallback, else nil — absolute when
|
|
566
|
+
# `base_url` is given (pass request.base_url) or the image lives on a public
|
|
567
|
+
# service. Reuse it anywhere the app needs to say what it is: a meta
|
|
568
|
+
# description, share text, an email footer. Views have `studio_site_identity`.
|
|
569
|
+
# Never raises; an app without the table answers from the drafted defaults.
|
|
570
|
+
def self.site_identity(base_url: nil)
|
|
571
|
+
Studio::SiteIdentity.resolved(base_url: base_url)
|
|
572
|
+
rescue StandardError
|
|
573
|
+
{ title: site_title.presence || app_name.to_s, description: site_description.presence, image_url: nil }
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# :auto emits when the table is installed AND no view of this app writes its
|
|
577
|
+
# own og tags (Studio.link_preview_own_tags_file); true/false are taken as
|
|
578
|
+
# given.
|
|
579
|
+
def self.link_preview_tags?
|
|
580
|
+
case link_preview_tags
|
|
581
|
+
when :auto, "auto", nil then Studio::SiteIdentity.table_ready? && link_preview_own_tags_file.nil?
|
|
582
|
+
else !!link_preview_tags
|
|
583
|
+
end
|
|
584
|
+
rescue StandardError
|
|
585
|
+
false
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# The host view that writes its own og tags, or nil — scanned once per process
|
|
589
|
+
# (app/views is fixed at deploy) and logged once when it keeps :auto off.
|
|
590
|
+
def self.link_preview_own_tags_file
|
|
591
|
+
return @link_preview_own_tags_file if defined?(@link_preview_own_tags_file)
|
|
592
|
+
|
|
593
|
+
views = defined?(Rails.root) && Rails.root ? Rails.root.join("app/views") : nil
|
|
594
|
+
found = Studio::LinkPreview.own_tag_file(views)
|
|
595
|
+
if found && defined?(Rails.logger) && Rails.logger
|
|
596
|
+
Rails.logger.info("[studio.link_preview] #{found} writes its own og tags, so the engine's head tags " \
|
|
597
|
+
"stay off under link_preview_tags = :auto. Set it to true once they are removed.")
|
|
598
|
+
end
|
|
599
|
+
@link_preview_own_tags_file = found
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def self.reset_link_preview_own_tags!
|
|
603
|
+
remove_instance_variable(:@link_preview_own_tags_file) if defined?(@link_preview_own_tags_file)
|
|
604
|
+
end
|
|
605
|
+
|
|
474
606
|
# Whether the engine configures Geocoder on boot (provider, HTTPS, timeout, and
|
|
475
607
|
# a Rails.cache-backed IP cache). An app that configures Geocoder itself sets
|
|
476
608
|
# this false; an app with no geocoder gem is unaffected either way.
|
|
@@ -1110,6 +1242,25 @@ module Studio
|
|
|
1110
1242
|
patch "admin/geo", to: "studio/geo_settings#update", as: :admin_geo_update
|
|
1111
1243
|
post "admin/geo/toggle", to: "studio/geo_settings#toggle_override", as: :admin_geo_toggle
|
|
1112
1244
|
end
|
|
1245
|
+
|
|
1246
|
+
# The link-preview default (/admin/link_preview): the image, title and
|
|
1247
|
+
# description every page unfurls with unless it overrides them. ON by
|
|
1248
|
+
# default (Studio.draw_link_preview_routes) because no consumer owns these
|
|
1249
|
+
# names; an app can still switch it off from its initializer.
|
|
1250
|
+
if Studio.draw_link_preview_routes
|
|
1251
|
+
get "admin/link_preview", to: "studio/site_identities#edit", as: :admin_link_preview
|
|
1252
|
+
patch "admin/link_preview", to: "studio/site_identities#update"
|
|
1253
|
+
delete "admin/link_preview/image", to: "studio/site_identities#destroy_image", as: :admin_link_preview_image
|
|
1254
|
+
end
|
|
1255
|
+
# The public user page (/u/:username): avatar and username only, and the
|
|
1256
|
+
# user's avatar as its link-preview image. OPT-IN — see
|
|
1257
|
+
# Studio.draw_public_user_routes. `format: false` because usernames may
|
|
1258
|
+
# carry a dot, which would otherwise be read as a format extension.
|
|
1259
|
+
if Studio.draw_public_user_routes
|
|
1260
|
+
get "u/:username", to: "studio/public_users#show", as: :studio_public_user,
|
|
1261
|
+
format: false, constraints: { username: %r{[^/]+} }
|
|
1262
|
+
end
|
|
1263
|
+
|
|
1113
1264
|
# The living style guide. Canonical at /admin/style (StyleController#index);
|
|
1114
1265
|
# /admin/design_system redirects here but KEEPS its admin_design_system_path
|
|
1115
1266
|
# helper so a shipped host sidebar link on the old helper still resolves.
|
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.
|
|
4
|
+
version: 0.82.1
|
|
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-
|
|
11
|
+
date: 2026-10-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -323,6 +323,7 @@ files:
|
|
|
323
323
|
- app/controllers/concerns/studio/geo_detection.rb
|
|
324
324
|
- app/controllers/concerns/studio/impersonation.rb
|
|
325
325
|
- app/controllers/concerns/studio/link_consumption.rb
|
|
326
|
+
- app/controllers/concerns/studio/link_preview_bots.rb
|
|
326
327
|
- app/controllers/concerns/studio/magic_link_issuing.rb
|
|
327
328
|
- app/controllers/concerns/studio/session_drift.rb
|
|
328
329
|
- app/controllers/error_logs_controller.rb
|
|
@@ -343,13 +344,17 @@ files:
|
|
|
343
344
|
- app/controllers/studio/models_controller.rb
|
|
344
345
|
- app/controllers/studio/onboarding_controller.rb
|
|
345
346
|
- app/controllers/studio/profiles_controller.rb
|
|
347
|
+
- app/controllers/studio/public_users_controller.rb
|
|
346
348
|
- app/controllers/studio/session_states_controller.rb
|
|
349
|
+
- app/controllers/studio/site_identities_controller.rb
|
|
347
350
|
- app/controllers/style_controller.rb
|
|
348
351
|
- app/controllers/theme_settings_controller.rb
|
|
349
352
|
- app/helpers/studio/admin_models_table_helper.rb
|
|
350
353
|
- app/helpers/studio/at_time_helper.rb
|
|
351
354
|
- app/helpers/studio/fizz_helper.rb
|
|
352
355
|
- app/helpers/studio/geo_helper.rb
|
|
356
|
+
- app/helpers/studio/link_preview_helper.rb
|
|
357
|
+
- app/helpers/studio/public_user_helper.rb
|
|
353
358
|
- app/helpers/studio_email_delivery_helper.rb
|
|
354
359
|
- app/helpers/studio_navbar_helper.rb
|
|
355
360
|
- app/helpers/studio_sidebar_helper.rb
|
|
@@ -376,6 +381,7 @@ files:
|
|
|
376
381
|
- app/models/studio/knowledge_expectation.rb
|
|
377
382
|
- app/models/studio/link.rb
|
|
378
383
|
- app/models/studio/model_page.rb
|
|
384
|
+
- app/models/studio/site_identity.rb
|
|
379
385
|
- app/models/theme_setting.rb
|
|
380
386
|
- app/services/google_oauth_validator.rb
|
|
381
387
|
- app/services/studio/banner.rb
|
|
@@ -408,6 +414,7 @@ files:
|
|
|
408
414
|
- app/views/layouts/branded_mailer.html.erb
|
|
409
415
|
- app/views/layouts/studio/_flash.html.erb
|
|
410
416
|
- app/views/layouts/studio/_head.html.erb
|
|
417
|
+
- app/views/layouts/studio/_link_preview_tags.html.erb
|
|
411
418
|
- app/views/layouts/studio/_smooth_load.html.erb
|
|
412
419
|
- app/views/navbar/show.html.erb
|
|
413
420
|
- app/views/registrations/new.html.erb
|
|
@@ -520,6 +527,9 @@ files:
|
|
|
520
527
|
- app/views/studio/profiles/_save_controls.html.erb
|
|
521
528
|
- app/views/studio/profiles/edit.html.erb
|
|
522
529
|
- app/views/studio/profiles/show.html.erb
|
|
530
|
+
- app/views/studio/public_users/not_found.html.erb
|
|
531
|
+
- app/views/studio/public_users/show.html.erb
|
|
532
|
+
- app/views/studio/site_identities/edit.html.erb
|
|
523
533
|
- app/views/style/_host.html.erb
|
|
524
534
|
- app/views/style/_modal_specimen.html.erb
|
|
525
535
|
- app/views/style/_modals.html.erb
|
|
@@ -559,6 +569,8 @@ files:
|
|
|
559
569
|
- db/migrate/20260901000001_create_studio_knowledge_docs.rb
|
|
560
570
|
- db/migrate/20260902000001_create_studio_knowledge_expectations.rb
|
|
561
571
|
- db/migrate/20260902000002_add_expectation_to_studio_knowledge_docs.rb
|
|
572
|
+
- db/migrate/20260930120000_create_studio_site_identities.rb
|
|
573
|
+
- lib/generators/studio/site_identity/site_identity_generator.rb
|
|
562
574
|
- lib/studio-engine.rb
|
|
563
575
|
- lib/studio.rb
|
|
564
576
|
- lib/studio/cable.rb
|
|
@@ -574,6 +586,7 @@ files:
|
|
|
574
586
|
- lib/studio/ip_locations.rb
|
|
575
587
|
- lib/studio/js_identifier.rb
|
|
576
588
|
- lib/studio/js_literal.rb
|
|
589
|
+
- lib/studio/link_preview.rb
|
|
577
590
|
- lib/studio/link_resolution.rb
|
|
578
591
|
- lib/studio/link_token.rb
|
|
579
592
|
- lib/studio/log_rotation.rb
|
|
@@ -586,6 +599,7 @@ files:
|
|
|
586
599
|
- lib/studio/partial_block.rb
|
|
587
600
|
- lib/studio/profile_image.rb
|
|
588
601
|
- lib/studio/profile_sections.rb
|
|
602
|
+
- lib/studio/public_user.rb
|
|
589
603
|
- lib/studio/redis.rb
|
|
590
604
|
- lib/studio/s3.rb
|
|
591
605
|
- lib/studio/session_fingerprint.rb
|