studio-engine 0.129.0 → 0.131.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +192 -0
  3. data/README.md +48 -6
  4. data/app/assets/tailwind/studio_engine/engine-motion.css +322 -56
  5. data/app/assets/tailwind/studio_engine/engine.css +737 -19
  6. data/app/assets/tailwind/studio_engine/theme.css +4 -0
  7. data/app/controllers/concerns/studio/error_handling.rb +53 -15
  8. data/app/controllers/concerns/studio/magic_link_issuing.rb +4 -0
  9. data/app/controllers/magic_links_controller.rb +25 -2
  10. data/app/controllers/registrations_controller.rb +58 -22
  11. data/app/controllers/style_controller.rb +6 -0
  12. data/app/helpers/studio/agent_chat_helper.rb +14 -1
  13. data/app/helpers/studio/crumbs_helper.rb +34 -0
  14. data/app/helpers/studio/fizz_helper.rb +5 -0
  15. data/app/helpers/studio/loader_helper.rb +79 -2
  16. data/app/javascript/studio/agent_chat.js +294 -6
  17. data/app/javascript/studio/carousel.js +99 -0
  18. data/app/javascript/studio/controllers/carousel_controller.js +138 -0
  19. data/app/javascript/studio/controllers/infinite_scroll_controller.js +17 -0
  20. data/app/javascript/studio/controllers/pull_to_refresh_controller.js +18 -0
  21. data/app/javascript/studio/controllers/style_section_nav_controller.js +4 -4
  22. data/app/javascript/studio/controllers/talk_to_agent_controller.js +20 -0
  23. data/app/javascript/studio/infinite_scroll.js +142 -0
  24. data/app/javascript/studio/loader.js +6 -1
  25. data/app/javascript/studio/page_transitions.js +65 -8
  26. data/app/javascript/studio/pinned_stack.js +112 -4
  27. data/app/javascript/studio/pull_to_refresh.js +147 -0
  28. data/app/javascript/studio/stimulus.js +12 -1
  29. data/app/javascript/studio/talk_to_agent.js +116 -0
  30. data/app/views/components/_geo_signpost.html.erb +2 -2
  31. data/app/views/components/_link_sidebar.html.erb +2 -2
  32. data/app/views/components/_link_sidebar_trigger.html.erb +1 -1
  33. data/app/views/components/_sidebar_panel.html.erb +1 -1
  34. data/app/views/error_logs/index.html.erb +2 -8
  35. data/app/views/studio/_carousel.html.erb +97 -0
  36. data/app/views/studio/_fizz.html.erb +26 -0
  37. data/app/views/studio/_fizz_layer.html.erb +4 -1
  38. data/app/views/studio/_hold_button.html.erb +1 -2
  39. data/app/views/studio/_page_header.html.erb +55 -13
  40. data/app/views/studio/_section_header.html.erb +23 -0
  41. data/app/views/studio/agent_chat/_chat.html.erb +7 -4
  42. data/app/views/studio/agent_chat/_talk_modal.html.erb +58 -0
  43. data/app/views/studio/agent_chat/_turn.html.erb +1 -1
  44. data/app/views/studio/loader/_infinite_scroll.html.erb +42 -0
  45. data/app/views/studio/loader/_loader.html.erb +20 -3
  46. data/app/views/studio/loader/_pull_to_refresh.html.erb +20 -0
  47. data/app/views/studio/loader/_skeleton.html.erb +37 -0
  48. data/app/views/style/_components.html.erb +3 -5
  49. data/app/views/style/_host.html.erb +3 -6
  50. data/app/views/style/_loaders.html.erb +118 -0
  51. data/app/views/style/_loaders_feed_page.html.erb +16 -0
  52. data/app/views/style/_modal_specimen.html.erb +5 -5
  53. data/app/views/style/_modals.html.erb +15 -5
  54. data/app/views/style/_site_footer.html.erb +1 -1
  55. data/app/views/style/_specimen.html.erb +21 -10
  56. data/app/views/style/_tasks.html.erb +3 -5
  57. data/app/views/style/_theme.html.erb +2 -2
  58. data/app/views/style/_tricks.html.erb +218 -217
  59. data/app/views/style/index.html.erb +21 -11
  60. data/app/views/style/loaders_feed.html.erb +7 -0
  61. data/lib/studio/crumbs.rb +150 -0
  62. data/lib/studio/loader.rb +30 -2
  63. data/lib/studio/version.rb +1 -1
  64. data/lib/studio.rb +3 -0
  65. metadata +22 -2
@@ -3,26 +3,29 @@
3
3
  application.html.erb, inheriting that app's navbar and theme, so every
4
4
  specimen restyles per app and in dark or light automatically.
5
5
 
6
- Five ENGINE sections, reached by the page header below and rendered
6
+ Six ENGINE sections, reached by the page header below and rendered
7
7
  as sibling partials: Theme is the landing section (the color foundation - it
8
8
  owns the role tokens/swatches, the theme editor, the live preview, and the
9
- logos and type), then Modals (the shared host + engine card blocks), Tricks
10
- (the button/surface/motion/effect/leveling primitives, framed as a board of
11
- copy-paste-for-an-agent snippets), Tasks (the shared board primitive), and
9
+ logos and type), then Tricks (the button/surface/motion/effect/leveling
10
+ primitives, framed as a board of copy-paste-for-an-agent snippets; second by
11
+ Alex McRitchie's ruling of 2026-11-05), Loaders (the eight loading primitives,
12
+ docs/LOADER.md), Modals (the shared host + engine card blocks), Tasks (the
13
+ shared board primitive), and
12
14
  Components (the ViewComponent previews, linking into the gallery at
13
15
  /admin/style/components).
14
16
 
15
- A SIXTH section is the host app's own, and appears only on an app that asks
17
+ A SEVENTH section is the host app's own, and appears only on an app that asks
16
18
  for it: define app/views/style/host/_modals.html.erb in the consuming app
17
19
  and the guide grows a section (and a nav pill) for that app's own modals,
18
- between Modals and Tricks. The gem still renders the section chrome; the app
20
+ between Modals and Tasks. The gem still renders the section chrome; the app
19
21
  supplies the specimens. Full contract, including which modal store a host
20
22
  specimen drives and why it is not the one the Modals section uses:
21
23
  app/views/style/_host.html.erb.
22
24
 
23
25
  HEADINGS wear the type ladder, on trial on this page only (style/_type_ladder): the one
24
26
  <h1> is studio/_page_header's, at type-h2; a section is an <h2> at type-h3; a subsection
25
- is an <h3> at type-h4.
27
+ is an <h3> at type-h4. The section's <h2> is studio/_section_header's, in type-h3's numbers,
28
+ which sticks under the page row until the next section's takes its place.
26
29
 
27
30
  Specimens use ONLY the engine's own classes (engine.css component layer plus
28
31
  engine-motion.css motion/effect/leveling layer), so a consumer that imports
@@ -43,8 +46,11 @@
43
46
  # The section chrome and the consumer contract live in style/_host.html.erb.
44
47
  host_modals = lookup_context.exists?("modals", ["style/host"], true)
45
48
 
49
+ # Tricks second, after the landing Theme (Alex McRitchie, 2026-11-05).
46
50
  sections = [
47
51
  ["theme", "Theme"],
52
+ ["tricks", "Tricks"],
53
+ ["loaders", "Loaders"],
48
54
  ["modals", "Modals"]
49
55
  ]
50
56
  # Adjacent to Modals, not appended: this page groups by SUBJECT, and a host's
@@ -52,14 +58,13 @@
52
58
  # name, so the pill says whose section it is.
53
59
  sections << ["host-modals", Studio.app_name] if host_modals
54
60
  sections += [
55
- ["tricks", "Tricks"],
56
61
  ["tasks", "Tasks"],
57
62
  ["components", "Components"]
58
63
  ]
59
64
  %>
60
65
  <div class="max-w-6xl mx-auto px-4 pt-6 pb-16" data-type-ladder>
61
66
  <%# The page's title and its section tabs: studio/_page_header, the row of the
62
- triple stack (docs/PAGE_HEADER.md). It sits at --z-sticky, above --z-raised,
67
+ pinned stack (docs/PAGE_HEADER.md). It sits at --z-sticky, above --z-raised,
63
68
  so the live hold button's portal bubbles scroll under it rather than over it.
64
69
 
65
70
  The style-section-nav controller moves the highlight as the page scrolls
@@ -67,6 +72,7 @@
67
72
  right before the module loads. %>
68
73
  <%= render "studio/page_header",
69
74
  title: "Style",
75
+ crumbs: studio_crumbs_here,
70
76
  meta: "v#{Studio::VERSION}",
71
77
  meta_title: "studio-engine",
72
78
  label: "Style guide sections",
@@ -77,13 +83,17 @@
77
83
  data: { style_section_nav_target: "link" } }
78
84
  } %>
79
85
 
80
- <div class="space-y-16 pt-10">
86
+ <%# 6rem between sections: a first jump from the top of the page lands by the navbar's
87
+ collapse below the line, and the section above must be gone from the bar by then
88
+ (76px on the hub at phone width). %>
89
+ <div class="space-y-24 pt-10">
81
90
  <%= render "style/theme" %>
91
+ <%= render "style/tricks" %>
92
+ <%= render "style/loaders" %>
82
93
  <%= render "style/modals" %>
83
94
  <% if host_modals %>
84
95
  <%= render "style/host" %>
85
96
  <% end %>
86
- <%= render "style/tricks" %>
87
97
  <%= render "style/tasks" %>
88
98
  <%= render "style/components" %>
89
99
  </div>
@@ -0,0 +1,7 @@
1
+ <%# The Loaders section's demo feed, one page (StyleController#loaders_feed). A
2
+ whole page, so "Load more" opened with no script lands somewhere real; a
3
+ frame request takes just the frame it asked for out of it. %>
4
+ <div class="max-w-xl mx-auto px-4 py-10 space-y-4">
5
+ <h1 class="type-h3 text-heading">Loaders demo feed</h1>
6
+ <%= render "style/loaders_feed_page", page: @page, feed_path: admin_style_loaders_feed_path %>
7
+ </div>
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Studio
4
+ # THE PARENT MAP behind studio/_page_header's crumb trail (docs/PAGE_HEADER.md, "Crumbs").
5
+ # An app names each page that has children once, with its label, its address and its own
6
+ # parent. A page then names only its parent, and the trail builds itself up the chain.
7
+ #
8
+ # # config/initializers/studio_crumbs.rb
9
+ # Studio.crumbs.draw do
10
+ # page :admin, "Admin", :admin_dashboard_path
11
+ # page :deployments, "Deployments", :deployments_path, parent: :admin
12
+ # page :release, ->(release:) { release.slug }, ->(release:) { release_path(release) },
13
+ # parent: :deployments
14
+ # end
15
+ #
16
+ # <%= render "studio/page_header", title: "Deployments", crumbs: studio_crumbs(:deployments) %>
17
+ # <%= render "studio/page_header", title: @task.title, crumbs: studio_crumbs(parent: :deployments) %>
18
+ #
19
+ # A label or an address is a String, a Symbol (a route helper, read in the view, then on
20
+ # main_app) or a lambda the view runs with the page's own keywords.
21
+ class Crumbs
22
+ Page = Struct.new(:key, :label, :path, :parent, keyword_init: true)
23
+
24
+ class UnknownPage < KeyError; end
25
+ class Cycle < ArgumentError; end
26
+
27
+ def initialize
28
+ @pages = {}
29
+ end
30
+
31
+ def draw(&block)
32
+ instance_eval(&block)
33
+ self
34
+ end
35
+
36
+ def page(key, label, path, parent: nil)
37
+ @pages[key.to_sym] = Page.new(key: key.to_sym, label: label, path: path, parent: parent&.to_sym)
38
+ end
39
+
40
+ def clear
41
+ @pages.clear
42
+ self
43
+ end
44
+
45
+ def key?(key) = @pages.key?(key.to_sym)
46
+
47
+ def fetch(key)
48
+ @pages.fetch(key.to_sym) { raise UnknownPage, "Studio.crumbs has no page #{key.inspect}" }
49
+ end
50
+
51
+ # The keys from the root down to `key`, `key` included.
52
+ def chain(key)
53
+ keys = []
54
+ current = key&.to_sym
55
+ while current
56
+ raise Cycle, "Studio.crumbs loops at #{current.inspect}" if keys.include?(current)
57
+
58
+ keys.unshift(current)
59
+ current = fetch(current).parent
60
+ end
61
+ keys
62
+ end
63
+
64
+ # The trail for one page: its ancestors, root first, as [{ label:, href: }]. Name the page
65
+ # itself (`key`, the page in the map) or only its parent (`parent:`, a page not in the map).
66
+ def trail(view, key = nil, parent: nil, **locals)
67
+ raise ArgumentError, "name the page or its parent, not both" if key && parent
68
+
69
+ keys = key ? chain(key)[0...-1] : chain(parent)
70
+ keys.map do |name|
71
+ page = fetch(name)
72
+ { label: resolve(view, page.label, locals).to_s, href: resolve(view, page.path, locals).to_s }
73
+ end
74
+ end
75
+
76
+ # The map's page at `path`, or nil: for a page the engine draws (the style guide, the
77
+ # model pages), which cannot know the host's key for it. Only pages whose address
78
+ # needs no keywords can match.
79
+ def key_for_path(view, path)
80
+ wanted = normalize(path)
81
+ @pages.each_value do |page|
82
+ next if page.path.is_a?(Proc) && keywords(page.path, required: true).any?
83
+
84
+ address = begin
85
+ resolve(view, page.path, {}).to_s
86
+ rescue StandardError
87
+ next
88
+ end
89
+ return page.key if normalize(address) == wanted
90
+ end
91
+ nil
92
+ end
93
+
94
+ # A name the map lacks, or a loop in it, is a mistake in the app's own code. In
95
+ # development and test it raises, so the mistake shows at once. Anywhere else (QA
96
+ # dynos run as production, so not Rails.env.production?) the page renders with no
97
+ # trail and the error is logged: a missing crumb is not worth a 500.
98
+ def self.strict?(env = (::Rails.env if defined?(::Rails) && ::Rails.respond_to?(:env)))
99
+ return true if env.nil?
100
+
101
+ env.development? || env.test?
102
+ end
103
+
104
+ REPORTED = {}
105
+ REPORTED_LOCK = Mutex.new
106
+
107
+ # Once per message per process: a layout renders on every request.
108
+ def self.report(error)
109
+ first = REPORTED_LOCK.synchronize { REPORTED[error.message] ? false : (REPORTED[error.message] = true) }
110
+ return unless first
111
+
112
+ if defined?(::ErrorLog) && ::ErrorLog.respond_to?(:capture!)
113
+ ::ErrorLog.capture!(error)
114
+ elsif defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
115
+ ::Rails.logger.error("[studio-crumbs] no trail drawn: #{error.class}: #{error.message}")
116
+ end
117
+ rescue StandardError
118
+ nil # reporting is best-effort: the page still renders
119
+ end
120
+
121
+ private
122
+
123
+ # An address with no trailing slash; the root stays "/".
124
+ def normalize(path) = path.to_s.chomp("/").presence || "/"
125
+
126
+ def resolve(view, value, locals)
127
+ case value
128
+ when Proc then view.instance_exec(**locals.slice(*keywords(value)), &value)
129
+ when Symbol then route(view, value)
130
+ else value
131
+ end
132
+ end
133
+
134
+ def keywords(proc, required: false)
135
+ kinds = required ? %i[keyreq] : %i[key keyreq]
136
+ proc.parameters.filter_map { |kind, name| name if kinds.include?(kind) }
137
+ end
138
+
139
+ def route(view, name)
140
+ return view.public_send(name) if view.respond_to?(name)
141
+ return view.main_app.public_send(name) if view.respond_to?(:main_app)
142
+
143
+ raise NoMethodError, "Studio.crumbs: no route helper #{name}"
144
+ end
145
+ end
146
+
147
+ def self.crumbs
148
+ @crumbs ||= Crumbs.new
149
+ end
150
+ end
data/lib/studio/loader.rb CHANGED
@@ -23,6 +23,11 @@
23
23
  # The icon is drawn underneath as an image; the art is inlined over it. An art
24
24
  # file that is missing, outside public/, or has no marked layer falls back to
25
25
  # breathe, so a typo can never blank the loader.
26
+ #
27
+ # PLAIN styles draw no icon, only the theme's colours: spinner, dots and bar.
28
+ # They are the small, everyday members of the same loader family, for a view to
29
+ # name with studio_loader(style:). They are not an app's brand, so the app's own
30
+ # config.loader refuses them.
26
31
  module Studio
27
32
  module Loader
28
33
  class InvalidConfig < ArgumentError; end
@@ -37,7 +42,20 @@ module Studio
37
42
  SETTING = "Studio.loader"
38
43
  GENERIC_STYLES = %i[breathe liquid orbit steel].freeze
39
44
  ART_STYLES = { weld_bead: "outline", sparkle: "spark" }.freeze
45
+ # The styles an app may declare as its loader: they draw its icon.
40
46
  STYLES = (GENERIC_STYLES + ART_STYLES.keys).freeze
47
+ # The styles a view may name that draw no icon (docs/LOADER.md, "The family").
48
+ PLAIN_STYLES = %i[spinner dots bar].freeze
49
+ # The size each style draws at when the caller names none: the icon styles at
50
+ # 96px, a spinner beside text, dots in a line, and a bar across its parent.
51
+ DEFAULT_SIZES = { spinner: 20, dots: 32, bar: "100%" }.freeze
52
+ DEFAULT_SIZE = 96
53
+ # The style guide's live infinite-scroll specimen: a demo feed of this many
54
+ # pages of this many items (style/_loaders_feed_page). Here, not on
55
+ # StyleController, so a view can read them without loading a controller that
56
+ # needs the host's ApplicationController.
57
+ DEMO_FEED_PAGES = 3
58
+ DEMO_FEED_PER_PAGE = 4
41
59
  DEFAULT_STYLE = :breathe
42
60
  KEYS = %i[style icon art].freeze
43
61
 
@@ -62,6 +80,10 @@ module Studio
62
80
  raise InvalidConfig, "#{SETTING} has unknown key :#{unknown.first} (takes #{KEYS.join(', ')})" if unknown.any?
63
81
 
64
82
  style = style!(config[:style] || DEFAULT_STYLE)
83
+ if PLAIN_STYLES.include?(style)
84
+ raise InvalidConfig, "#{SETTING} style :#{style} draws no icon, so it cannot be the app's loader " \
85
+ "(one of #{STYLES.map(&:inspect).join(', ')}); a view names it with studio_loader(style: :#{style})"
86
+ end
65
87
  icon = path!(config[:icon], "#{SETTING}[:icon]")
66
88
  art = path!(config[:art], "#{SETTING}[:art]")
67
89
  if ART_STYLES.key?(style) && art.nil?
@@ -72,13 +94,19 @@ module Studio
72
94
  { style: style, icon: icon, art: art }.freeze
73
95
  end
74
96
 
97
+ # A style name as a Symbol, or InvalidConfig. Every style is a name a view may
98
+ # use; normalize refuses the plain ones for the app's own loader.
75
99
  def style!(value)
76
100
  style = value.to_s.strip.tr("-", "_").to_sym
77
- return style if STYLES.include?(style)
101
+ return style if STYLES.include?(style) || PLAIN_STYLES.include?(style)
78
102
 
79
- raise InvalidConfig, "#{SETTING} style must be one of #{STYLES.map(&:inspect).join(', ')} (got #{value.inspect})"
103
+ raise InvalidConfig, "#{SETTING} style must be one of #{(STYLES + PLAIN_STYLES).map(&:inspect).join(', ')} (got #{value.inspect})"
80
104
  end
81
105
 
106
+ def plain_style?(style) = PLAIN_STYLES.include?(style)
107
+
108
+ def default_size(style) = DEFAULT_SIZES.fetch(style, DEFAULT_SIZE)
109
+
82
110
  def path!(value, label)
83
111
  return nil if value.nil?
84
112
  return value.strip if value.is_a?(String) && !value.strip.empty?
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.129.0"
2
+ VERSION = "0.131.0"
3
3
  end
data/lib/studio.rb CHANGED
@@ -77,6 +77,7 @@ require "studio/cable"
77
77
  require "studio/survey"
78
78
  require "studio/survey/breakdown"
79
79
  require "studio/survey/export"
80
+ require "studio/crumbs"
80
81
 
81
82
  module Studio
82
83
  mattr_accessor :app_name, default: "Studio"
@@ -2248,6 +2249,8 @@ module Studio
2248
2249
 
2249
2250
  # The living style guide (StyleController#index).
2250
2251
  get "admin/style", to: "style#index", as: :admin_style
2252
+ # One page of the Loaders section's demo feed (its live infinite scroll).
2253
+ get "admin/style/loaders_feed", to: "style#loaders_feed", as: :admin_style_loaders_feed
2251
2254
 
2252
2255
  # The component gallery: Lookbook over the engine's ViewComponent previews.
2253
2256
  # The router refuses anyone but a signed-in admin, so a visitor and a
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.129.0
4
+ version: 0.131.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-11-05 00:00:00.000000000 Z
11
+ date: 2026-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -482,6 +482,7 @@ files:
482
482
  - app/helpers/studio/admin_models_table_helper.rb
483
483
  - app/helpers/studio/agent_chat_helper.rb
484
484
  - app/helpers/studio/at_time_helper.rb
485
+ - app/helpers/studio/crumbs_helper.rb
485
486
  - app/helpers/studio/fizz_helper.rb
486
487
  - app/helpers/studio/geo_helper.rb
487
488
  - app/helpers/studio/link_preview_helper.rb
@@ -502,17 +503,20 @@ files:
502
503
  - app/javascript/studio/birthday.js
503
504
  - app/javascript/studio/board.js
504
505
  - app/javascript/studio/booking.js
506
+ - app/javascript/studio/carousel.js
505
507
  - app/javascript/studio/confirm_interstitial.js
506
508
  - app/javascript/studio/controllers/agent_chat_controller.js
507
509
  - app/javascript/studio/controllers/agent_chat_voice_controller.js
508
510
  - app/javascript/studio/controllers/board_controller.js
509
511
  - app/javascript/studio/controllers/booking_controller.js
512
+ - app/javascript/studio/controllers/carousel_controller.js
510
513
  - app/javascript/studio/controllers/copy_text_controller.js
511
514
  - app/javascript/studio/controllers/email_banner_scale_controller.js
512
515
  - app/javascript/studio/controllers/footer_map_controller.js
513
516
  - app/javascript/studio/controllers/geo_settings_controller.js
514
517
  - app/javascript/studio/controllers/hold_button_controller.js
515
518
  - app/javascript/studio/controllers/identity_mini_controller.js
519
+ - app/javascript/studio/controllers/infinite_scroll_controller.js
516
520
  - app/javascript/studio/controllers/knowledge_transcript_controller.js
517
521
  - app/javascript/studio/controllers/link_preview_card_controller.js
518
522
  - app/javascript/studio/controllers/link_sidebar_controller.js
@@ -520,9 +524,11 @@ files:
520
524
  - app/javascript/studio/controllers/modal_host_controller.js
521
525
  - app/javascript/studio/controllers/nav_collapse_controller.js
522
526
  - app/javascript/studio/controllers/page_header_controller.js
527
+ - app/javascript/studio/controllers/pull_to_refresh_controller.js
523
528
  - app/javascript/studio/controllers/style_modals_controller.js
524
529
  - app/javascript/studio/controllers/style_section_nav_controller.js
525
530
  - app/javascript/studio/controllers/survey_controller.js
531
+ - app/javascript/studio/controllers/talk_to_agent_controller.js
526
532
  - app/javascript/studio/controllers/toast_controller.js
527
533
  - app/javascript/studio/controllers/voice_profile_controller.js
528
534
  - app/javascript/studio/controllers/voice_recorder_controller.js
@@ -537,6 +543,7 @@ files:
537
543
  - app/javascript/studio/host_fetchers.js
538
544
  - app/javascript/studio/identity_mini.js
539
545
  - app/javascript/studio/image_upload.js
546
+ - app/javascript/studio/infinite_scroll.js
540
547
  - app/javascript/studio/knowledge_transcript.js
541
548
  - app/javascript/studio/lazy_controllers.js
542
549
  - app/javascript/studio/level_meter.js
@@ -555,6 +562,7 @@ files:
555
562
  - app/javascript/studio/page_transitions.js
556
563
  - app/javascript/studio/pinned_stack.js
557
564
  - app/javascript/studio/profile_form.js
565
+ - app/javascript/studio/pull_to_refresh.js
558
566
  - app/javascript/studio/sticky_table_header.js
559
567
  - app/javascript/studio/stimulus.js
560
568
  - app/javascript/studio/studio_confetti.js
@@ -562,6 +570,7 @@ files:
562
570
  - app/javascript/studio/style_modals.js
563
571
  - app/javascript/studio/style_section_nav.js
564
572
  - app/javascript/studio/survey.js
573
+ - app/javascript/studio/talk_to_agent.js
565
574
  - app/javascript/studio/theme_editor.js
566
575
  - app/javascript/studio/toast.js
567
576
  - app/javascript/studio/transition_snapshot.js
@@ -638,13 +647,16 @@ files:
638
647
  - app/views/studio/_at_time_script.html.erb
639
648
  - app/views/studio/_birthday_assets.html.erb
640
649
  - app/views/studio/_board_assets.html.erb
650
+ - app/views/studio/_carousel.html.erb
641
651
  - app/views/studio/_confirm_interstitial.html.erb
642
652
  - app/views/studio/_cropper_assets.html.erb
653
+ - app/views/studio/_fizz.html.erb
643
654
  - app/views/studio/_fizz_layer.html.erb
644
655
  - app/views/studio/_hold_button.html.erb
645
656
  - app/views/studio/_hold_button_guard.html.erb
646
657
  - app/views/studio/_leveling_activity_assets.html.erb
647
658
  - app/views/studio/_page_header.html.erb
659
+ - app/views/studio/_section_header.html.erb
648
660
  - app/views/studio/_session_stamp.html.erb
649
661
  - app/views/studio/admin_models/_arenas_table.html.erb
650
662
  - app/views/studio/admin_models/_sessions_table.html.erb
@@ -655,6 +667,7 @@ files:
655
667
  - app/views/studio/agent_chat/_chat.html.erb
656
668
  - app/views/studio/agent_chat/_form.html.erb
657
669
  - app/views/studio/agent_chat/_header.html.erb
670
+ - app/views/studio/agent_chat/_talk_modal.html.erb
658
671
  - app/views/studio/agent_chat/_turn.html.erb
659
672
  - app/views/studio/agent_chat/_typing.html.erb
660
673
  - app/views/studio/agent_chat/_voice.html.erb
@@ -705,8 +718,11 @@ files:
705
718
  - app/views/studio/knowledge_docs/preview.html.erb
706
719
  - app/views/studio/knowledge_docs/show.html.erb
707
720
  - app/views/studio/links/confirm.html.erb
721
+ - app/views/studio/loader/_infinite_scroll.html.erb
708
722
  - app/views/studio/loader/_loader.html.erb
709
723
  - app/views/studio/loader/_overlay.html.erb
724
+ - app/views/studio/loader/_pull_to_refresh.html.erb
725
+ - app/views/studio/loader/_skeleton.html.erb
710
726
  - app/views/studio/local_emails/index.html.erb
711
727
  - app/views/studio/mailers/_layered_banner.html.erb
712
728
  - app/views/studio/marketing_kit/_assets.html.erb
@@ -793,6 +809,8 @@ files:
793
809
  - app/views/style/_components.html.erb
794
810
  - app/views/style/_glass.html.erb
795
811
  - app/views/style/_host.html.erb
812
+ - app/views/style/_loaders.html.erb
813
+ - app/views/style/_loaders_feed_page.html.erb
796
814
  - app/views/style/_modal_specimen.html.erb
797
815
  - app/views/style/_modals.html.erb
798
816
  - app/views/style/_scrims.html.erb
@@ -804,6 +822,7 @@ files:
804
822
  - app/views/style/_type_ladder.html.erb
805
823
  - app/views/style/board/_demo_card.html.erb
806
824
  - app/views/style/index.html.erb
825
+ - app/views/style/loaders_feed.html.erb
807
826
  - app/views/style/modals/_age_gate.html.erb
808
827
  - app/views/style/modals/_birthday.html.erb
809
828
  - app/views/style/modals/_ds_close_x.html.erb
@@ -855,6 +874,7 @@ files:
855
874
  - lib/studio/cable.rb
856
875
  - lib/studio/color_scale.rb
857
876
  - lib/studio/component_gallery.rb
877
+ - lib/studio/crumbs.rb
858
878
  - lib/studio/email.rb
859
879
  - lib/studio/email_image_generator.rb
860
880
  - lib/studio/email_smoke.rb