studio-engine 0.127.0 → 0.129.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 +161 -0
- data/README.md +9 -3
- data/app/assets/javascripts/studio/page_transition_reveal.js +1 -1
- data/app/assets/tailwind/studio_engine/engine.css +572 -37
- data/app/controllers/concerns/studio/error_handling.rb +17 -0
- data/app/controllers/studio/surveys_controller.rb +16 -3
- data/app/helpers/studio/loader_helper.rb +2 -2
- data/app/helpers/studio_icon_helper.rb +10 -0
- data/app/javascript/studio/agent_chat.js +88 -6
- data/app/javascript/studio/agent_chat_voice.js +59 -5
- data/app/javascript/studio/alpine_stores.js +80 -22
- data/app/javascript/studio/loader.js +130 -5
- data/app/javascript/studio/microphone_chip.js +28 -2
- data/app/javascript/studio/page_transitions.js +28 -19
- data/app/javascript/studio/survey.js +16 -3
- data/app/javascript/studio/theme_editor.js +4 -0
- data/app/javascript/studio/voice_recorder.js +18 -7
- data/app/models/studio/survey_response.rb +24 -17
- data/app/views/components/_geo_signpost.html.erb +1 -0
- data/app/views/components/_link_sidebar.html.erb +40 -12
- data/app/views/components/_sidebar_panel.html.erb +15 -5
- data/app/views/layouts/studio/_head.html.erb +18 -7
- data/app/views/layouts/studio/_smooth_load.html.erb +1 -0
- data/app/views/layouts/studio/component_preview.html.erb +1 -1
- data/app/views/registrations/new.html.erb +4 -3
- data/app/views/sessions/new.html.erb +4 -3
- data/app/views/studio/agent_chat/_chat.html.erb +2 -1
- data/app/views/studio/agent_chat/_turn.html.erb +3 -1
- data/app/views/studio/agent_chat/_voice.html.erb +28 -13
- data/app/views/studio/loader/_overlay.html.erb +4 -4
- data/app/views/style/_components.html.erb +2 -4
- data/app/views/style/_glass.html.erb +83 -0
- data/app/views/style/_host.html.erb +2 -10
- data/app/views/style/_modals.html.erb +38 -231
- data/app/views/style/_scrims.html.erb +2 -9
- data/app/views/style/_site_footer.html.erb +5 -13
- data/app/views/style/_tasks.html.erb +16 -27
- data/app/views/style/_theme.html.erb +56 -78
- data/app/views/style/_tricks.html.erb +13 -11
- data/app/views/style/_type_ladder.html.erb +33 -0
- data/app/views/style/index.html.erb +5 -1
- data/docs/SITE_FOOTER.md +1 -1
- data/lib/studio/agent_chat.rb +4 -2
- data/lib/studio/icons/README.md +12 -0
- data/lib/studio/icons/lucide/LICENSE +43 -0
- data/lib/studio/icons/lucide/activity.svg +15 -0
- data/lib/studio/icons/lucide/book-open.svg +16 -0
- data/lib/studio/icons/lucide/chart-column.svg +18 -0
- data/lib/studio/icons/lucide/diamond.svg +15 -0
- data/lib/studio/icons/lucide/door-stairwell.svg +19 -0
- data/lib/studio/icons/lucide/globe.svg +17 -0
- data/lib/studio/icons/lucide/hammer.svg +17 -0
- data/lib/studio/icons/lucide/inbox.svg +16 -0
- data/lib/studio/icons/lucide/key-round.svg +16 -0
- data/lib/studio/icons/lucide/layout-grid.svg +18 -0
- data/lib/studio/icons/lucide/log-out.svg +17 -0
- data/lib/studio/icons/lucide/message-circle.svg +15 -0
- data/lib/studio/icons/lucide/package.svg +18 -0
- data/lib/studio/icons/lucide/palette.svg +19 -0
- data/lib/studio/icons/lucide/plug.svg +18 -0
- data/lib/studio/icons/lucide/rocket.svg +18 -0
- data/lib/studio/icons/lucide/user.svg +16 -0
- data/lib/studio/icons.rb +52 -0
- data/lib/studio/loader.rb +7 -0
- data/lib/studio/sidebar_sections.rb +52 -4
- data/lib/studio/site_footer.rb +23 -2
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +54 -2
- metadata +24 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<%# Theme section of the living style guide (admin/style) — the LANDING section.
|
|
2
|
-
Theme owns "the colors": it leads with the
|
|
3
|
-
|
|
2
|
+
Theme owns "the colors": it leads with the role editor, which names each role's CSS
|
|
3
|
+
variable and the derived tokens, with a live dark/light preview, then the logos, the semantic tokens and
|
|
4
4
|
the type. GET /admin/theme redirects here.
|
|
5
5
|
|
|
6
6
|
Save-in-place: this section intercepts Save + Regenerate and submits them via
|
|
@@ -42,17 +42,14 @@
|
|
|
42
42
|
# Seed the live-preview editor with role -> hex, resolved from saved values.
|
|
43
43
|
seed_colors = roles.index_with { |role| current_hex.call(role) }
|
|
44
44
|
|
|
45
|
-
#
|
|
46
|
-
# are the semantic names
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
["--color-text", "Heading / text"],
|
|
54
|
-
["--color-border-strong", "Border (strong)"]
|
|
55
|
-
]
|
|
45
|
+
# Each role's CSS variable (there is no --color-heading or --color-primary var for a page to
|
|
46
|
+
# paint with: cta and text are the semantic names). Dark and light are each theme's page.
|
|
47
|
+
role_vars = { primary: "--color-cta", dark: "--color-page (dark)", light: "--color-page (light)", success: "--color-success",
|
|
48
|
+
warning: "--color-warning", danger: "--color-danger", accent: "--color-accent" }
|
|
49
|
+
|
|
50
|
+
# Not roles: Studio::ThemeResolver works these out from the roles, so they read here and
|
|
51
|
+
# no one edits them.
|
|
52
|
+
derived_tokens = [["Surface", "--color-surface"], ["Text", "--color-text"], ["Border (strong)", "--color-border-strong"]]
|
|
56
53
|
|
|
57
54
|
csrf_ok = respond_to?(:protect_against_forgery?) && protect_against_forgery?
|
|
58
55
|
%>
|
|
@@ -60,12 +57,8 @@
|
|
|
60
57
|
x-data="dsThemeEditor(<%= seed_colors.to_json %>)">
|
|
61
58
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
62
59
|
<div class="space-y-1">
|
|
63
|
-
<h2 class="
|
|
64
|
-
<p class="text-muted text-sm max-w-2xl">
|
|
65
|
-
The color foundation. The 7-role system, editable here. Edits preview live; Save writes them
|
|
66
|
-
and Regenerate clears the theme cache. Both persist <strong>in place</strong> — the page never
|
|
67
|
-
leaves. Every specimen on this page restyles from these roles.
|
|
68
|
-
</p>
|
|
60
|
+
<h2 class="type-h3 text-heading">Theme</h2>
|
|
61
|
+
<p class="text-muted text-sm max-w-2xl">Every specimen on this page restyles from these 7 color roles.</p>
|
|
69
62
|
</div>
|
|
70
63
|
<form action="/admin/theme/regenerate" method="post" accept-charset="UTF-8" @submit.prevent="regenerate($event)">
|
|
71
64
|
<% if csrf_ok %>
|
|
@@ -75,25 +68,32 @@
|
|
|
75
68
|
</form>
|
|
76
69
|
</div>
|
|
77
70
|
|
|
78
|
-
<%#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
71
|
+
<%# THE LOGOS, first in the section. A host may supply its own block in
|
|
72
|
+
app/views/style/host/_logos.html.erb, the same seam as host modals: the hub's has
|
|
73
|
+
one-click PNG downloads, which draw through a script only the hub carries. %>
|
|
74
|
+
<% if lookup_context.exists?("logos", ["style/host"], true) %>
|
|
75
|
+
<%= render "style/host/logos" %>
|
|
76
|
+
<% else %>
|
|
77
|
+
<% logos = Studio.respond_to?(:theme_logos) ? Array(Studio.theme_logos) : [] %>
|
|
78
|
+
<% if logos.any? %>
|
|
79
|
+
<div class="space-y-3">
|
|
80
|
+
<p class="label-upper">Logos</p>
|
|
81
|
+
<div class="grid gap-4 grid-cols-2 md:grid-cols-5">
|
|
82
|
+
<% logos.each do |logo| %>
|
|
83
|
+
<% file = logo.is_a?(Hash) ? logo[:file] : logo %>
|
|
84
|
+
<% title = logo.is_a?(Hash) ? logo[:title] : file %>
|
|
85
|
+
<div class="card p-4 space-y-3">
|
|
86
|
+
<p class="text-sm text-heading font-medium"><%= title %></p>
|
|
87
|
+
<div class="flex items-center justify-center w-20 h-20 rounded-lg" style="background-color: #ddd; background-image: linear-gradient(45deg, #777 25%, transparent 25%, transparent 75%, #777 75%), linear-gradient(45deg, #777 25%, transparent 25%, transparent 75%, #777 75%); background-size: 12px 12px; background-position: 0 0, 6px 6px;">
|
|
88
|
+
<img src="/<%= file %>" alt="<%= title %>" class="max-w-16 max-h-16 rounded">
|
|
89
|
+
</div>
|
|
90
|
+
<code class="font-mono text-2xs text-muted break-all"><%= file %></code>
|
|
92
91
|
</div>
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
<% end %>
|
|
93
|
+
</div>
|
|
95
94
|
</div>
|
|
96
|
-
|
|
95
|
+
<% end %>
|
|
96
|
+
<% end %>
|
|
97
97
|
|
|
98
98
|
<div class="grid gap-6 lg:grid-cols-2">
|
|
99
99
|
<%# ---- Role-color editor (posts to /admin/theme, intercepted in place) - %>
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
<% if csrf_ok %>
|
|
104
104
|
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" autocomplete="off">
|
|
105
105
|
<% end %>
|
|
106
|
-
<h3 class="
|
|
106
|
+
<h3 class="type-h4 text-heading">Role colors</h3>
|
|
107
107
|
|
|
108
108
|
<% roles.each do |role| %>
|
|
109
109
|
<% db_col = db_col_for.call(role) %>
|
|
@@ -119,6 +119,7 @@
|
|
|
119
119
|
<div class="flex items-center gap-2">
|
|
120
120
|
<span class="text-heading font-medium text-sm"><%= role.to_s.titleize %></span>
|
|
121
121
|
<% if has_custom %><span class="text-xs text-muted">(custom)</span><% end %>
|
|
122
|
+
<% if role_vars[role.to_sym] %><code class="font-mono text-2xs text-muted truncate"><%= role_vars[role.to_sym] %></code><% end %>
|
|
122
123
|
</div>
|
|
123
124
|
<input type="text"
|
|
124
125
|
name="theme_setting[<%= db_col %>]"
|
|
@@ -135,16 +136,29 @@
|
|
|
135
136
|
</div>
|
|
136
137
|
<% end %>
|
|
137
138
|
|
|
139
|
+
<div class="pt-4 border-t border-subtle space-y-2" data-test="style-derived-tokens">
|
|
140
|
+
<p class="label-upper">Derived, read only</p>
|
|
141
|
+
<% derived_tokens.each do |label, var| %>
|
|
142
|
+
<div class="flex items-center gap-4">
|
|
143
|
+
<div class="w-10 h-6 rounded border border-subtle shrink-0" style="background: var(<%= var %>)"></div>
|
|
144
|
+
<span class="text-heading font-medium text-sm"><%= label %></span>
|
|
145
|
+
<code class="font-mono text-2xs text-muted truncate"><%= var %></code>
|
|
146
|
+
</div>
|
|
147
|
+
<% end %>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
138
150
|
<div class="pt-4 border-t border-subtle">
|
|
139
151
|
<button type="submit" class="btn btn-primary" :disabled="saving"
|
|
140
152
|
x-text="saving ? 'Saving…' : 'Save theme'">Save theme</button>
|
|
141
153
|
</div>
|
|
142
154
|
</form>
|
|
143
155
|
|
|
144
|
-
<%#
|
|
145
|
-
|
|
156
|
+
<%# The half beside the editor: Typography, then the live dark/light preview, which
|
|
157
|
+
appears once a role color has been edited. %>
|
|
158
|
+
<div class="space-y-6 min-w-0">
|
|
159
|
+
<%= render "style/type_ladder" %>
|
|
160
|
+
<div class="space-y-3" x-show="edited" x-cloak data-test="style-theme-preview">
|
|
146
161
|
<p class="label-upper">Preview</p>
|
|
147
|
-
<p class="text-muted text-sm">A live dark/light mock — it tracks the editor as you type, before you save.</p>
|
|
148
162
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
149
163
|
<div class="rounded-xl border border-subtle overflow-hidden">
|
|
150
164
|
<div class="px-3 py-1.5 bg-surface-alt"><span class="text-2xs font-bold uppercase tracking-wider text-secondary">Dark</span></div>
|
|
@@ -168,28 +182,9 @@
|
|
|
168
182
|
</div>
|
|
169
183
|
</div>
|
|
170
184
|
</div>
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<% logos = Studio.respond_to?(:theme_logos) ? Array(Studio.theme_logos) : [] %>
|
|
174
|
-
<% if logos.any? %>
|
|
175
|
-
<div class="space-y-3">
|
|
176
|
-
<p class="label-upper">Logos</p>
|
|
177
|
-
<div class="grid gap-4 grid-cols-2 md:grid-cols-5">
|
|
178
|
-
<% logos.each do |logo| %>
|
|
179
|
-
<% file = logo.is_a?(Hash) ? logo[:file] : logo %>
|
|
180
|
-
<% title = logo.is_a?(Hash) ? logo[:title] : file %>
|
|
181
|
-
<div class="card p-4 space-y-3">
|
|
182
|
-
<p class="text-sm text-heading font-medium"><%= title %></p>
|
|
183
|
-
<div class="flex items-center justify-center w-20 h-20 rounded-lg" style="background-color: #ddd; background-image: linear-gradient(45deg, #777 25%, transparent 25%, transparent 75%, #777 75%), linear-gradient(45deg, #777 25%, transparent 25%, transparent 75%, #777 75%); background-size: 12px 12px; background-position: 0 0, 6px 6px;">
|
|
184
|
-
<img src="/<%= file %>" alt="<%= title %>" class="max-w-16 max-h-16 rounded">
|
|
185
|
-
</div>
|
|
186
|
-
<code class="font-mono text-2xs text-muted break-all"><%= file %></code>
|
|
187
|
-
</div>
|
|
188
|
-
<% end %>
|
|
189
|
-
</div>
|
|
190
185
|
</div>
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
</div>
|
|
187
|
+
|
|
193
188
|
<%# Semantic tokens: the utilities a page paints with, under the role colours. %>
|
|
194
189
|
<div class="space-y-3">
|
|
195
190
|
<p class="label-upper">Semantic tokens</p>
|
|
@@ -217,21 +212,4 @@
|
|
|
217
212
|
</div>
|
|
218
213
|
</div>
|
|
219
214
|
|
|
220
|
-
<%# Type in this app's own font, named by role: the font itself is the host's. %>
|
|
221
|
-
<div class="space-y-3">
|
|
222
|
-
<p class="label-upper">Typography</p>
|
|
223
|
-
<div class="card p-6 space-y-4">
|
|
224
|
-
<p class="text-heading text-3xl font-extrabold">Heading</p>
|
|
225
|
-
<p class="text-heading text-xl font-bold">Subheading</p>
|
|
226
|
-
<p class="text-body text-base">Body text. The quick brown fox jumps over the lazy dog.</p>
|
|
227
|
-
<p class="text-secondary text-sm">Secondary text. Additional context and metadata.</p>
|
|
228
|
-
<p class="text-muted text-xs">Muted text. Timestamps, hints and fine print.</p>
|
|
229
|
-
<hr class="border-subtle">
|
|
230
|
-
<div class="flex flex-wrap gap-4">
|
|
231
|
-
<% [400, 500, 600, 700, 800, 900].each do |weight| %>
|
|
232
|
-
<span class="text-heading text-lg" style="font-weight: <%= weight %>"><%= weight %></span>
|
|
233
|
-
<% end %>
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
215
|
</section>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
%>
|
|
34
34
|
<section id="tricks" class="space-y-12" style="scroll-margin-top: calc(var(--nav-h, 0px) + 5rem)">
|
|
35
35
|
<div class="space-y-1">
|
|
36
|
-
<h2 class="
|
|
36
|
+
<h2 class="type-h3 text-heading">Tricks</h2>
|
|
37
37
|
<p class="text-muted text-sm max-w-2xl">
|
|
38
38
|
A board of style tricks you can see and hand to an agent to use out of the box.
|
|
39
39
|
Each specimen surfaces the rendered primitive, its <strong>class name (one-click
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<!-- Buttons -->
|
|
46
46
|
<section class="space-y-5">
|
|
47
47
|
<div class="space-y-1">
|
|
48
|
-
<h3 class="
|
|
48
|
+
<h3 class="type-h4 text-heading">Buttons</h3>
|
|
49
49
|
<p class="text-muted text-sm">
|
|
50
50
|
<code class="font-mono text-2xs">.btn</code> base plus one role variant, and the
|
|
51
51
|
<code class="font-mono text-2xs">.btn-sm</code> / <code class="font-mono text-2xs">.btn-lg</code> sizes.
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
<!-- Page header -->
|
|
76
76
|
<section class="space-y-5" data-page-header-specimen>
|
|
77
77
|
<div class="space-y-1">
|
|
78
|
-
<h3 class="
|
|
78
|
+
<h3 class="type-h4 text-heading">Page header</h3>
|
|
79
79
|
<p class="text-muted text-sm">
|
|
80
80
|
<code class="font-mono text-2xs">studio/_page_header</code>: the title, optional meta beside
|
|
81
81
|
it and optional tabs on the right. This page's own title row is one: scroll to see it take the bar.
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
<!-- Surfaces, text and form -->
|
|
111
111
|
<section class="space-y-5">
|
|
112
112
|
<div class="space-y-1">
|
|
113
|
-
<h3 class="
|
|
113
|
+
<h3 class="type-h4 text-heading">Surfaces, text & form</h3>
|
|
114
114
|
<p class="text-muted text-sm">Cards, badges, inputs, the empty state, and the uppercase meta label.</p>
|
|
115
115
|
</div>
|
|
116
116
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
@@ -152,12 +152,14 @@
|
|
|
152
152
|
</section>
|
|
153
153
|
|
|
154
154
|
<!-- Tints and shades: the scrim primitive -->
|
|
155
|
+
<%= render "style/glass" %>
|
|
156
|
+
|
|
155
157
|
<%= render "style/scrims" %>
|
|
156
158
|
|
|
157
159
|
<!-- Time stamps: the "at" format -->
|
|
158
160
|
<section class="space-y-5">
|
|
159
161
|
<div class="space-y-1">
|
|
160
|
-
<h3 class="
|
|
162
|
+
<h3 class="type-h4 text-heading">Time stamps — the “at” format</h3>
|
|
161
163
|
<p class="text-muted text-sm">
|
|
162
164
|
<code class="font-mono text-2xs">at_time_tag</code> stamps WHEN something happened, on
|
|
163
165
|
the reader’s own clock. The stamps below are live: the server rendered them in this
|
|
@@ -231,7 +233,7 @@
|
|
|
231
233
|
%>
|
|
232
234
|
<section id="loader" class="space-y-5" style="scroll-margin-top: calc(var(--nav-h, 0px) + 5rem)">
|
|
233
235
|
<div class="space-y-1">
|
|
234
|
-
<h3 class="
|
|
236
|
+
<h3 class="type-h4 text-heading">Branded loader</h3>
|
|
235
237
|
<p class="text-muted text-sm max-w-2xl">
|
|
236
238
|
This app's own icon in its theme colours, in the style <code class="font-mono text-2xs">config.loader</code>
|
|
237
239
|
names (<strong><%= loader_config[:style].to_s.tr("_", " ") %></strong>). Page loads, long waits and pending
|
|
@@ -282,7 +284,7 @@
|
|
|
282
284
|
<!-- Motion primitives -->
|
|
283
285
|
<section class="space-y-5">
|
|
284
286
|
<div class="space-y-1">
|
|
285
|
-
<h3 class="
|
|
287
|
+
<h3 class="type-h4 text-heading">Motion primitives</h3>
|
|
286
288
|
<p class="text-muted text-sm">
|
|
287
289
|
The seven <code class="font-mono text-2xs">engine-motion.css</code> primitives. Each knob is a themeable
|
|
288
290
|
CSS variable - tune it inline with <code class="font-mono text-2xs">style="--knob: value"</code>.
|
|
@@ -369,7 +371,7 @@
|
|
|
369
371
|
<!-- Effects -->
|
|
370
372
|
<section class="space-y-5">
|
|
371
373
|
<div class="space-y-1">
|
|
372
|
-
<h3 class="
|
|
374
|
+
<h3 class="type-h4 text-heading">Effects</h3>
|
|
373
375
|
<p class="text-muted text-sm">
|
|
374
376
|
The <code class="font-mono text-2xs">engine-motion.css</code> visual-effect primitives beyond motion -
|
|
375
377
|
gradient text, a soft glow halo, a one- or two-color rotating <strong>selection glow</strong>
|
|
@@ -434,7 +436,7 @@
|
|
|
434
436
|
<!-- Hold to confirm -->
|
|
435
437
|
<section class="space-y-5">
|
|
436
438
|
<div class="space-y-1">
|
|
437
|
-
<h3 class="
|
|
439
|
+
<h3 class="type-h4 text-heading">Hold to confirm</h3>
|
|
438
440
|
<p class="text-muted text-sm max-w-2xl">
|
|
439
441
|
A press-and-hold CTA for an action you do not want taken by accident, and the
|
|
440
442
|
carbonation behind it. Ported from Turf Monster, where it confirms a contest entry.
|
|
@@ -497,7 +499,7 @@
|
|
|
497
499
|
<!-- Confetti & pulse -->
|
|
498
500
|
<section class="space-y-5">
|
|
499
501
|
<div class="space-y-1">
|
|
500
|
-
<h3 class="
|
|
502
|
+
<h3 class="type-h4 text-heading">Confetti & pulse</h3>
|
|
501
503
|
<p class="text-muted text-sm max-w-2xl">
|
|
502
504
|
Celebration effects lifted from Turf Monster into the engine. Confetti is
|
|
503
505
|
<code class="font-mono text-2xs">window.studioConfetti</code> - a zero-dependency,
|
|
@@ -544,7 +546,7 @@
|
|
|
544
546
|
<section class="space-y-5">
|
|
545
547
|
<div class="space-y-1">
|
|
546
548
|
<div class="flex flex-wrap items-center gap-3">
|
|
547
|
-
<h3 class="
|
|
549
|
+
<h3 class="type-h4 text-heading">Leveling</h3>
|
|
548
550
|
<% if leveling_on %>
|
|
549
551
|
<span class="badge" style="color: var(--color-success-ink); border-color: var(--color-success)">enabled on this app</span>
|
|
550
552
|
<% else %>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<%# Typography, in this app's own font. The type ladder, on trial on this page: each named
|
|
2
|
+
role, its pixel size at this page's density, and what it is for; then the text inks and
|
|
3
|
+
the weights. A role sets size, not the tag. Body and fine print follow the
|
|
4
|
+
page's base, so they read 14 and 10.5 on a dense admin page and 16 and 12 on a public one. %>
|
|
5
|
+
<% dense = respond_to?(:type_dense_page?) && type_dense_page? %>
|
|
6
|
+
<%
|
|
7
|
+
ladder = [
|
|
8
|
+
["type-h1", "48px", "Marketing headline"],
|
|
9
|
+
["type-h2", "30px", "Page title on a tool page"],
|
|
10
|
+
["type-h3", "20px", "Section"],
|
|
11
|
+
["type-h4", "16px", "Subsection"],
|
|
12
|
+
["type-body", dense ? "14px" : "16px", "Body text"],
|
|
13
|
+
["type-fine", dense ? "10.5px" : "12px", "Fine print"]
|
|
14
|
+
]
|
|
15
|
+
%>
|
|
16
|
+
<div class="card p-4 space-y-2" data-test="style-type-ladder">
|
|
17
|
+
<h3 class="type-h4 text-heading">Typography</h3>
|
|
18
|
+
<% ladder.each do |role, size, use| %>
|
|
19
|
+
<div class="flex flex-col gap-0.5 min-w-0" data-type-role="<%= role %>">
|
|
20
|
+
<code class="text-xs text-muted shrink-0"><%= role %> · <%= size %></code>
|
|
21
|
+
<span class="<%= role %> text-heading min-w-0"><%= use %></span>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
<hr class="border-subtle">
|
|
25
|
+
<p class="text-body text-sm">Body ink. The quick brown fox jumps over the lazy dog.</p>
|
|
26
|
+
<p class="text-secondary text-sm">Secondary ink. Additional context and metadata.</p>
|
|
27
|
+
<p class="text-muted text-xs">Muted ink. Timestamps, hints and fine print.</p>
|
|
28
|
+
<div class="flex flex-wrap gap-4 pt-1">
|
|
29
|
+
<% [400, 500, 600, 700, 800, 900].each do |weight| %>
|
|
30
|
+
<span class="type-h4 text-heading" style="font-weight: <%= weight %>"><%= weight %></span>
|
|
31
|
+
<% end %>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
specimen drives and why it is not the one the Modals section uses:
|
|
21
21
|
app/views/style/_host.html.erb.
|
|
22
22
|
|
|
23
|
+
HEADINGS wear the type ladder, on trial on this page only (style/_type_ladder): the one
|
|
24
|
+
<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.
|
|
26
|
+
|
|
23
27
|
Specimens use ONLY the engine's own classes (engine.css component layer plus
|
|
24
28
|
engine-motion.css motion/effect/leveling layer), so a consumer that imports
|
|
25
29
|
the engine's Tailwind entry (host.css: engine.css and engine-motion.css)
|
|
@@ -53,7 +57,7 @@
|
|
|
53
57
|
["components", "Components"]
|
|
54
58
|
]
|
|
55
59
|
%>
|
|
56
|
-
<div class="max-w-6xl mx-auto px-4 pt-6 pb-16">
|
|
60
|
+
<div class="max-w-6xl mx-auto px-4 pt-6 pb-16" data-type-ladder>
|
|
57
61
|
<%# The page's title and its section tabs: studio/_page_header, the row of the
|
|
58
62
|
triple stack (docs/PAGE_HEADER.md). It sits at --z-sticky, above --z-raised,
|
|
59
63
|
so the live hold button's portal bubbles scroll under it rather than over it.
|
data/docs/SITE_FOOTER.md
CHANGED
|
@@ -436,7 +436,7 @@ config.site_footer_chat_path = "/chat" # or ->(view) { view.chat_pat
|
|
|
436
436
|
|
|
437
437
|
| Setting | Meaning | Unset |
|
|
438
438
|
|---------|---------|-------|
|
|
439
|
-
| `site_footer_chat_path` | A path, or a callable taking the view (route helpers do not exist yet in an initializer). It answers nil to show no link on this page | No link |
|
|
439
|
+
| `site_footer_chat_path` | A path, or a callable taking the view (route helpers do not exist yet in an initializer). It answers nil to show no link on this page; one that raises hides the link and reports it (the log on each render, `ErrorLog` once per error class), and the page still renders | No link |
|
|
440
440
|
| `site_footer_chat_label` | The link's words | "Chat" |
|
|
441
441
|
| `site_footer_chat_allowed` | A callable taking the viewer: `current_user`, **nil when signed out**, so write `user&.admin?` | `Studio.agent_chat_allowed` (everyone, by default) |
|
|
442
442
|
|
data/lib/studio/agent_chat.rb
CHANGED
|
@@ -25,7 +25,8 @@ module Studio
|
|
|
25
25
|
# POST that still holds the visitor's anon token sends {email:, anon_token:},
|
|
26
26
|
# and the hub folds the anonymous thread into the person's. A turn is
|
|
27
27
|
# {id, role: "user"|"agent", text, created_at, status} with status
|
|
28
|
-
# pending|typing|answered|failed.
|
|
28
|
+
# pending|typing|answered|failed|held. held: stored, and no automatic answer is
|
|
29
|
+
# coming (a hub that predates it says failed for the same message).
|
|
29
30
|
#
|
|
30
31
|
# `voice` marks a spoken message: {check, score, duration_ms}, what POST
|
|
31
32
|
# /voice/takes answered for the take (voice_param). NEVER A NAME: the hub
|
|
@@ -104,12 +105,13 @@ module Studio
|
|
|
104
105
|
"silent" => { "button" => "Hold to talk" },
|
|
105
106
|
"failed" => { "button" => "Hold to try again" },
|
|
106
107
|
"denied" => { "button" => "Hold to talk" },
|
|
108
|
+
"tapped" => { "button" => "Hold to talk" },
|
|
107
109
|
"unsupported" => { "button" => "Hold to talk" }
|
|
108
110
|
}.freeze
|
|
109
111
|
|
|
110
112
|
# The microphone chip's first words (the engine's say "tap Record").
|
|
111
113
|
VOICE_CHIP_COPY = {
|
|
112
|
-
"prompt" => { "chip" => "Microphone: hold to
|
|
114
|
+
"prompt" => { "chip" => "Microphone: hold to allow",
|
|
113
115
|
"note" => "Hold the talk button once and allow the microphone. Then you can pick one here." }
|
|
114
116
|
}.freeze
|
|
115
117
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Vendored icons
|
|
2
|
+
|
|
3
|
+
`lucide/` holds the line icons `studio_icon` renders, copied unchanged from the
|
|
4
|
+
`lucide-static` npm package, version 1.68.0. Lucide is under the ISC license, and its
|
|
5
|
+
Feather-derived icons (`log-out` here) are under MIT. Both texts are in
|
|
6
|
+
[`lucide/LICENSE`](lucide/LICENSE), which ships with the gem.
|
|
7
|
+
|
|
8
|
+
The files are vendored, and render inline, because a strict CSP allows no icon CDN.
|
|
9
|
+
|
|
10
|
+
To add an icon, copy its `.svg` from the same package version, and read it first. Vendor
|
|
11
|
+
only what a view uses. `test/lib/studio_icons_test.rb` holds each file to the shape the
|
|
12
|
+
helper reads.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The following Lucide icons are derived from the Feather project:
|
|
20
|
+
|
|
21
|
+
airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
|
|
22
|
+
|
|
23
|
+
The MIT License (MIT) (for the icons listed above)
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2013-present Cole Bemis
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-activity"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" />
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-book-open"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M12 5v16" />
|
|
15
|
+
<path d="M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-chart-column"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M3 3v16a2 2 0 0 0 2 2h16" />
|
|
15
|
+
<path d="M18 17V9" />
|
|
16
|
+
<path d="M13 17V5" />
|
|
17
|
+
<path d="M8 17v-3" />
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-diamond"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41l-7.59-7.59a2.41 2.41 0 0 0-3.41 0Z" />
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-door-stairwell"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M12 17v-3a1 1 0 011-1h6" />
|
|
15
|
+
<path d="M19 17h-9a1 1 0 00-1 1v3" />
|
|
16
|
+
<path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16" />
|
|
17
|
+
<path d="M19 9h-3a1 1 0 00-1 1v3" />
|
|
18
|
+
<path d="M22 21H2" />
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-globe"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<circle cx="12" cy="12" r="10" />
|
|
15
|
+
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
|
16
|
+
<path d="M2 12h20" />
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-hammer"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9" />
|
|
15
|
+
<path d="m18 15 4-4" />
|
|
16
|
+
<path d="m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172v-.344a2 2 0 0 0-.586-1.414l-1.657-1.657A6 6 0 0 0 12.516 3H9l1.243 1.243A6 6 0 0 1 12 8.485V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5" />
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-inbox"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
|
|
15
|
+
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-key-round"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" />
|
|
15
|
+
<circle cx="16.5" cy="7.5" r=".5" fill="currentColor" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- @license lucide-static v1.68.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-layout-grid"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="2"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<rect width="7" height="7" x="3" y="3" rx="1" />
|
|
15
|
+
<rect width="7" height="7" x="14" y="3" rx="1" />
|
|
16
|
+
<rect width="7" height="7" x="14" y="14" rx="1" />
|
|
17
|
+
<rect width="7" height="7" x="3" y="14" rx="1" />
|
|
18
|
+
</svg>
|