studio-engine 0.127.0 → 0.128.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 +116 -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 +519 -37
- 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 +14 -3
- data/app/javascript/studio/agent_chat_voice.js +42 -4
- 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/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 +1 -1
- data/app/views/style/_glass.html.erb +83 -0
- data/app/views/style/_host.html.erb +1 -1
- data/app/views/style/_modals.html.erb +11 -11
- data/app/views/style/_scrims.html.erb +1 -1
- data/app/views/style/_site_footer.html.erb +1 -1
- data/app/views/style/_tasks.html.erb +1 -1
- data/app/views/style/_theme.html.erb +56 -72
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3abb879eda738b9ebe2d7ba3aa0c7400e4e335eccdf5775c654b8810db8f96e2
|
|
4
|
+
data.tar.gz: 440cde0469a8d8d8905a7250da40fbd2576e3f9b5a6b9c2a68036368486759cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e73f00423880d25f49324ed5ef4c3c152caf27f7bc144815ea4928242933e675c1e879393bdd2777d40659ddb472ddae8c23eb3ad9f0ae2f3f9e25e04e443033
|
|
7
|
+
data.tar.gz: '09e30076328185194bbd9773a059c1ee3a984e60e2376ff08aaf0f785ee3888c85e056dbffc38c69fa3477d03adff476beb7832bb60c974d2dc5f010a42fbc5d'
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,122 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.128.0 — 2026-11-05
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **The modified-click booking spec no longer waits on a Playwright event that can stay silent.**
|
|
12
|
+
`e2e/booking_frame.spec.js` timed out on two `accepted` runs because Playwright 1.62.1 sometimes
|
|
13
|
+
never fires its `page` event for a tab Chromium did open. The spec now reads the new tab from
|
|
14
|
+
Chromium's target list. It still needs one new tab at the booking URL, no dialog, and the first
|
|
15
|
+
page unmoved. No host change: a modified click on a booking link always opened its tab.
|
|
16
|
+
|
|
17
|
+
- **Firefox runs the shared head's import map.** The head's two URL module tags (`studio/alpine`,
|
|
18
|
+
`studio/sticky_table_header`) sent a `Link: rel=modulepreload` response header, and Firefox then
|
|
19
|
+
refused the import map: every bare `studio/*` import failed and no panel opened, on every host
|
|
20
|
+
that renders the head. Both tags now pass `preload_links_header: false`. They keep their place in
|
|
21
|
+
the document, their `data-turbo-track` and their CSP treatment. Hosts: keep a module tag of your
|
|
22
|
+
own below the import map, and pass the same option if it loads by URL.
|
|
23
|
+
|
|
24
|
+
- **Hold to talk, before the phone test.** Studio's shared voice chat, from the review of
|
|
25
|
+
`engine-chat-takes-voice`:
|
|
26
|
+
- A tap under `TAP_MS` (300ms) on a hold button is now the recorder's `tapped` state: the
|
|
27
|
+
line says to hold the button, and it does not count as a silent take, so the help no longer
|
|
28
|
+
opens by itself. A refused microphone and two real silent takes in a row still open it; a
|
|
29
|
+
tap between two silent takes ends the run.
|
|
30
|
+
- A held button keeps its whole box through a hold at phone width, stuck or not. The status
|
|
31
|
+
line reserves two lines (one in a composer 36rem wide) and fixes its height for the hold,
|
|
32
|
+
the chip stays one line, the timer sits on the button, and the recorder's own failed and
|
|
33
|
+
refused lines moved to the chat's one line. The chat's chip prompt is now "Microphone: hold
|
|
34
|
+
to allow".
|
|
35
|
+
- A `site_footer_chat_path` callable that raises hides the Chat link and reports it
|
|
36
|
+
(`SiteFooter.reporter` on each render, `ErrorLog.capture!` once per error class).
|
|
37
|
+
- A turn with status `held` says it was received and won't get an automatic answer, and
|
|
38
|
+
never asks for a resend. The hub still reports these as `failed` until
|
|
39
|
+
`chat-caps-count-held-messages` ships; they keep the failed line until then.
|
|
40
|
+
- A later typed send clears "Sent. Spoken by ..." (`studio:agent-chat:sent`).
|
|
41
|
+
- A key held on the talk button no longer dismisses the refused-microphone help.
|
|
42
|
+
- The composer's picker scrolls within a short window, so the chip stays on screen.
|
|
43
|
+
- Firefox's `AbortError` reads as a busy microphone, and Esc in the open picker stops there.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **`/admin/style` Theme: logos first, Typography beside the editor, a preview that waits for an edit.** A
|
|
48
|
+
host may supply its own logo block in `app/views/style/host/_logos.html.erb`.
|
|
49
|
+
- **`Studio.surface_style = :glass`: opt-in translucent cards over a soft ambient page.** A host
|
|
50
|
+
sets it and writes `data-surface-style` on its root. It reaches `.card`, `.card-hover`,
|
|
51
|
+
`.empty-state` and `.bg-surface.rounded-xl.border`. Menus stay solid. Real blur is only on
|
|
52
|
+
`.surface-glass-blur`, because a dense board cannot afford it on every card.
|
|
53
|
+
- **`Studio.sidebar_style = :glass`: an opt-in floating panel for the link sidebar.** The panel
|
|
54
|
+
insets from the page's edge, in both themes. Where the browser supports `backdrop-filter` it
|
|
55
|
+
blurs the page through a 72% fill, and a nearly solid fill replaces that under the
|
|
56
|
+
reduced-transparency setting or with no support. The blur is read in Chromium. It is not read
|
|
57
|
+
in a real Safari, and a headless WebKit paints the fill with no visible blur, so the fill alone
|
|
58
|
+
holds resting text at 4.5:1 over the worst page for each theme. It draws a link's `icon:`, marks
|
|
59
|
+
the current page's row with `aria-current="page"` in the app's primary color and a ring on
|
|
60
|
+
keyboard focus, and turns off the separate blurred edge. It hangs from the navbar, with square
|
|
61
|
+
top corners, and its spacing is five tokens on the panel's shell. The default stays `:default`,
|
|
62
|
+
so no app moves until it opts in.
|
|
63
|
+
- **`studio_icon(name, size:)`: one line icon set, as inline SVG in `currentColor`.** It holds the
|
|
64
|
+
Lucide icons the sidebar needs, vendored under `lib/studio/icons`, with their ISC and MIT texts
|
|
65
|
+
beside them. An unknown name renders nothing, so a caller can fall back to an emoji.
|
|
66
|
+
- **Link sidebar: a section may show its header.** A section in `Studio.sidebar_sections` with
|
|
67
|
+
`header: true` shows its title above its rows, in the `label-upper` style. The default stays no
|
|
68
|
+
header, so an app changes only when it opts in. The divider stays, and there is no ADMIN chip.
|
|
69
|
+
The group keeps its accessible name and the word on screen is `aria-hidden`, so a screen reader
|
|
70
|
+
announces the title once. The header is plain text outside the rows: it takes no click, and a
|
|
71
|
+
row keeps its height, its centering and its no-jump description. A section with a blank title
|
|
72
|
+
draws no header. The engine lab shows the case at `/lab/sidebar_panels?sidebar=1&headers=1`.
|
|
73
|
+
- **`Studio.sidebar_geo_signpost`: a host may take the Geo row out of the link sidebar.** It
|
|
74
|
+
defaults to true, so no app changes. False draws no Geo row in the panel. The partial
|
|
75
|
+
`components/_geo_signpost` stays, so a host that moves Geo renders it on its own page.
|
|
76
|
+
- **One loader pace for every app.** Under 350ms no loader and no added wait; once shown, at
|
|
77
|
+
least 750ms (`Studio::Loader::OVERLAY_DELAY_MS`, `OVERLAY_MIN_SHOW_MS`; `min_show:` beside
|
|
78
|
+
`delay:` on `studio_loader_overlay`). `layouts/studio/_head` now carries the overlay in a
|
|
79
|
+
`<template>`, and `studio/loader` adds it to the body, so an app with no overlay of its own gets
|
|
80
|
+
one on this bump. `config.loader_overlay = false` opts out. A slow visit in such an app now shows
|
|
81
|
+
the overlay, and a visit that answers between 350ms and 1100ms lands later than it did. The wait
|
|
82
|
+
holds a visit's good HTML response ahead of the view transition's old picture, so the move plays
|
|
83
|
+
after the loader goes. Failures, downloads, frames, forms, a full-load answer and
|
|
84
|
+
`StudioLoader.hide()` never wait. See `docs/LOADER.md`, *The standard pace*.
|
|
85
|
+
- **`config.page_transition_style`,** `:slide` (the default) or `:unfold`: one app plays one move.
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- **Page transitions draw nothing at random.** Alex McRitchie's ruling of 2026-11-04 replaces
|
|
90
|
+
the random draw per page change of 2026-11-03. Every page change and full load plays the app's
|
|
91
|
+
one move, and with no style set that move is the slide. A consumer that reads
|
|
92
|
+
`data-page-transition` or `Math.random` around a visit must change: `fade-*` is now `slide-*`,
|
|
93
|
+
and `pickStyle` and `DIRECTIONS` are gone.
|
|
94
|
+
- **Page transitions paint the app's page color behind the move, and pages stay opaque.** The
|
|
95
|
+
black backdrop and the 0.3 opacity are gone, so blank areas no longer darken, a full-width hero
|
|
96
|
+
has no dark strip beside it, a light theme no longer greys, and an unfold has no black wedges. The
|
|
97
|
+
leaving page of an unfold stays opaque too.
|
|
98
|
+
|
|
99
|
+
- **The browser lane counts its own specs.** `bin/lib/e2e_spec_census.rb` computes the spec count
|
|
100
|
+
from `e2e/`, as the hub does, and `bin/e2e-executed-set-check` reads it. `config/e2e_lane.yml`
|
|
101
|
+
loses `total_specs` and `executed`, so adding a spec moves no number and two PRs no longer
|
|
102
|
+
conflict on them. `bin/e2e-census-check` compares Playwright's `--list` with the census.
|
|
103
|
+
- **`CHANGELOG.md` merges with `merge=union`,** so parallel PRs stop conflicting on it. A branch
|
|
104
|
+
forked before a roll can land an entry under the rolled heading: `docs/RELEASE.md`, *Rolling
|
|
105
|
+
`Unreleased` into a version*, says how to read and fix that.
|
|
106
|
+
- **Link sidebar: the blurred edge reaches further and fades with no visible end.** The band is
|
|
107
|
+
100px wide, up from 40px, with the same 6px blur. Its mask is now an eased multi-stop gradient:
|
|
108
|
+
whole at the panel, half at 60px, clear at 100px, flat at both ends. The old hold and straight
|
|
109
|
+
fade showed as a line over a photo. It stays flush with the panel, takes no clicks, and draws
|
|
110
|
+
nothing below `md`. The engine lab shows it over the hub's Denver photo at
|
|
111
|
+
`/lab/sidebar_panels?sidebar=1&busy=photo`.
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
- **A late survey autosave can no longer change a completed response.** `record_answer` rewrote
|
|
116
|
+
the whole answers hash from the copy it had loaded, so a save that overlapped the submit could
|
|
117
|
+
replace the finished answers with a stale set, and two saves at once could lose each other's
|
|
118
|
+
keys. It now reads and writes under a row lock, and it refuses a completed row. The submit holds
|
|
119
|
+
one lock from its first answer through the completion. `on_survey_completed` still fires once,
|
|
120
|
+
after the commit. The stepper cancels any save still in flight when the form submits. A save that
|
|
121
|
+
loses the race answers 409, and the stepper shows the visitor no error. A host needs no change.
|
|
122
|
+
|
|
7
123
|
## 0.127.0 — 2026-11-04
|
|
8
124
|
|
|
9
125
|
### Added
|
data/README.md
CHANGED
|
@@ -41,7 +41,7 @@ resolved.
|
|
|
41
41
|
- **Geo**: `Studio::GeoDetection` places every visitor (IP → country + subdivision, session-cached), `Studio::GeoSetting` stores the operator's blocked countries and regions, `require_geo_allowed` locks whichever surfaces an app chooses, and the shared badge + `/admin/geo` manager ship with it. See [`docs/GEO.md`](docs/GEO.md).
|
|
42
42
|
- **Site identity and link previews**: `Studio::SiteIdentity` holds the app's title, description and image, edited at `/admin/link_preview` beside a live unfurl card and read anywhere through `Studio.site_identity`. Every page unfurls with it unless it calls `link_preview image:, title:, description:`, and `Studio::LinkPreviewBots` serves preview fetchers a slim page under iMessage's 1 MiB limit. Adopt with `bin/rails g studio:site_identity`. See [`docs/LINK_PREVIEW.md`](docs/LINK_PREVIEW.md).
|
|
43
43
|
- **Branded loader**: `studio_loader`, `studio_loader_pending` and `studio_loader_overlay` draw the app's own icon in its theme colours for page loads, long waits and pending buttons, in the style `config.loader` names (Breathe by default). See [`docs/LOADER.md`](docs/LOADER.md).
|
|
44
|
-
- **App-to-app page transitions**: `config.page_transition_direction` (`:left`, `:right`, `:top`, `:bottom`) moves an app's pages one way on every page change, full page loads included, with the navbar and banners held still.
|
|
44
|
+
- **App-to-app page transitions**: `config.page_transition_direction` (`:left`, `:right`, `:top`, `:bottom`) moves an app's pages one way on every page change, full page loads included, with the navbar and banners held still. `config.page_transition_style` (`:slide`, the default, or `:unfold`) names the one move an app plays; nothing is drawn at random. The direction turns smooth-load on. See [`docs/PAGE_TRANSITIONS.md`](docs/PAGE_TRANSITIONS.md).
|
|
45
45
|
- **Public user page**: `/u/:username` shows a user's avatar and username (nothing else), and unfurls with their avatar, falling back to the site image. Link any username to it with `link_to_user_profile(user)` or `studio_user_profile_path(user)`. Opt in with `config.draw_public_user_routes = true`. See [`docs/PUBLIC_USER_PAGE.md`](docs/PUBLIC_USER_PAGE.md).
|
|
46
46
|
- **Transactional emails**: `Studio::EmailCatalog` — every email an app sends, its type, a live preview, and its banner — plus the shared `/admin/emails` page. Every app inherits the standard emails and their artwork on day one, and can register its own workflows and upload its own banners. See [Transactional emails](#transactional-emails).
|
|
47
47
|
- **Site footer**: `<%= studio_site_footer %>` in the layout renders the public site's footer. It is on by default: with nothing configured it prints the app's name, logo and the legal links the app has routes for, and no address. `config.site_footer` declares the app's own facts (brand, social profiles, link columns, an address with a live map, legal links), every part optional; `config.site_footer_address` adds a location in one setting; `config.site_footer = false` turns the footer off; `config.site_footer_chat_path` adds a standard Chat link to the Contact column. Leaflet is vendored and served by the engine. See [`docs/SITE_FOOTER.md`](docs/SITE_FOOTER.md).
|
|
@@ -171,6 +171,7 @@ Studio.configure do |config|
|
|
|
171
171
|
# the fresh response arrives.
|
|
172
172
|
config.smooth_load = true
|
|
173
173
|
# config.page_transition_direction = :left # the app's page move; also turns smooth-load on
|
|
174
|
+
# config.page_transition_style = :unfold # default :slide
|
|
174
175
|
# Nav spinner minimum display (default 2500). Smooth-load apps typically
|
|
175
176
|
# drop to ~300; keep the high floor if multi-second ops ride the spinner.
|
|
176
177
|
config.nav_spinner_min_ms = 300
|
|
@@ -782,6 +783,9 @@ than assuming drop-in equivalence:
|
|
|
782
783
|
the engine's: Theme, Modals, Tricks, Tasks. An app grows a **fifth section, its
|
|
783
784
|
own**, by defining one partial:
|
|
784
785
|
|
|
786
|
+
A host may also define `app/views/style/host/_logos.html.erb`, which replaces the Theme section's
|
|
787
|
+
default logo grid.
|
|
788
|
+
|
|
785
789
|
```erb
|
|
786
790
|
<%# app/views/style/host/_modals.html.erb %>
|
|
787
791
|
<div class="card p-4 space-y-3">
|
|
@@ -823,7 +827,9 @@ of `app/views/style/_host.html.erb`.
|
|
|
823
827
|
Two seams, both empty by default, so the navbar is unchanged until an app opts in.
|
|
824
828
|
|
|
825
829
|
- `sidebar_sections` fills the slide-out link sidebar (the cog trigger in the icon
|
|
826
|
-
rail). Rules: `lib/studio/sidebar_sections.rb`.
|
|
830
|
+
rail). Rules: `lib/studio/sidebar_sections.rb`. `Studio.sidebar_style = :glass` floats it
|
|
831
|
+
as an inset translucent panel with line icons (`studio_icon`, `lib/studio/icons/README.md`);
|
|
832
|
+
see `docs/NAVBAR_SETUP.md`.
|
|
827
833
|
- `navbar_links` fills the navbar's own link slots: the desktop bar beside the
|
|
828
834
|
logo and the phone row under it. Rules: `lib/studio/navbar_links.rb`.
|
|
829
835
|
|
|
@@ -1095,7 +1101,7 @@ config.navbar_account_sidebar = { flag: "gearOpen", controls: %w[gear-sidebar ge
|
|
|
1095
1101
|
`components/_sidebar_panel` takes three opt-in locals; unset, the panel renders
|
|
1096
1102
|
as before:
|
|
1097
1103
|
|
|
1098
|
-
- `edge: true` draws a soft blurred band along the panel's inner edge from `md`
|
|
1104
|
+
- `edge: true` draws a soft blurred band, 100px wide, along the panel's inner edge from `md`
|
|
1099
1105
|
up, through a `data-sidebar-edge` hook and engine.css. It takes no clicks. The
|
|
1100
1106
|
link sidebar passes it. It softens the page beside the panel, so leave it off
|
|
1101
1107
|
a panel kept open beside live content.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// CLASSIC AND SYNCHRONOUS ON PURPOSE. pagereveal fires before the first render, and a listener
|
|
6
6
|
// bound after that misses it for good. A module is deferred, so it can be too late.
|
|
7
7
|
//
|
|
8
|
-
// It holds no table of styles. The leaving page
|
|
8
|
+
// It holds no table of styles. The leaving page read the style with studio/page_transitions
|
|
9
9
|
// at pageswap and left it here. No style means the leaving page did not plan this one (its
|
|
10
10
|
// module had not run, or it was the same path), so the load is plain, not the browser's fade.
|
|
11
11
|
(function () {
|