studio-engine 0.56.2 → 0.56.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51de95ce00b9b0ac0f2997767b9ea0f886b15767672b21e86f130832f804b12c
4
- data.tar.gz: c0ac5d5a343f6bdcfe0d303ca04ad4c94f5f51ae2d7330a061d97828483aa0c7
3
+ metadata.gz: 96609f446a3aab29cce64201a8ffbad206cdbc4c14ad1e50b43b838d854d384a
4
+ data.tar.gz: 468731ef76905afad9bcae15a189c2a0352d3c30626d0d134a61a0afec2a2392
5
5
  SHA512:
6
- metadata.gz: e11d32da6a57ade5bd9b07bbd568aeac64ba9160acf738416057697886d2cdd99c9ac8536c3f9453ccf502b7a10412b3b4e27deaf7b837d6e45c022943b1a306
7
- data.tar.gz: 145865333911d7a6d4966a7933cd93674048d0e0bd4e9315d91fa45aa840dd6358ce69ef5dd7c167bfed9115557c7ada9c6a58e5cbc4cb96fc6dce3f9886b7d7
6
+ metadata.gz: 5137e8db160f8f0a13d2341db981115a39de7415b289d76a759fb2e92eefe89b6a3e3095c65b647a89b218c1493f5a0b4685552fd72a9f7aea42d1194fb7e7ac
7
+ data.tar.gz: a6382097a1123c2b52fbd19ad40f25f307274f4c41b2cae82b0a9e73f8d4bf418d7b2239ff7e1d88159c8411f4d7e6986b1c609378c6232d2502f431d6bf77ae
data/CHANGELOG.md CHANGED
@@ -78,6 +78,45 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
78
78
  transparent border twice the blur radius and its wedges stop at the padding
79
79
  box. Without that room the halo ends on a hard line at its own edge.
80
80
 
81
+ ### Changed
82
+
83
+ - **Montserrat is vendored into the engine and can no longer re-measure the page.**
84
+ `layouts/studio/_head` linked the family from `fonts.googleapis.com` with
85
+ `display=swap`. The stylesheet blocks the load event; the font FILES do not — so
86
+ every app went interactive in the fallback font and re-measured every glyph when
87
+ Montserrat landed. That reflow swallowed a synthesized click three times in one CI
88
+ day (root-caused in `close-board-filter-flake`: `pointerdown` and `pointerup` hit
89
+ different elements, so the browser fired `click` on their common ancestor), and on a
90
+ cold cache it moved a filter chip under a real finger. Montserrat was the last
91
+ third-party asset in the head; Alpine, SortableJS and canvas-confetti were already
92
+ vendored.
93
+
94
+ What ships:
95
+
96
+ - **Two woff2 files** — `studio/montserrat-latin.woff2` (35KB) and
97
+ `studio/montserrat-latin-ext.woff2` (68KB), served through the asset pipeline and
98
+ precompiled for Sprockets hosts. The `fonts.googleapis.com` and `fonts.gstatic.com`
99
+ preconnects are gone, and the head reaches no third party at all.
100
+ - **`font-display: optional`, not `swap`.** This is the fix, not the self-hosting.
101
+ `swap` has an unbounded swap period, so a late font always reflows wherever it is
102
+ served from; `optional` has none, so a font that misses the block period is
103
+ abandoned for that navigation instead of shifting the layout. The cost is named: a
104
+ first visit on a slow link can render one page in the fallback. A `crossorigin`
105
+ preload of the latin subset is what keeps that rare.
106
+ - **One face per subset spanning `100 900`.** Montserrat v31 is a VARIABLE font — the
107
+ six weights the old link requested all resolved to the same bytes — so every weight
108
+ the fleet uses renders from real outlines rather than a synthesized fake, and asking
109
+ for fewer weights would not have saved a byte.
110
+ - **Only latin and latin-ext.** cyrillic, cyrillic-ext and vietnamese occur zero times
111
+ in the engine's views, the consumers' views, and their seed and locale data, so they
112
+ are not shipped. `unicode-range` means a page only downloads the subsets it renders,
113
+ so keeping latin-ext (for roster names like Modrić and Çalhanoğlu) costs an English
114
+ page nothing.
115
+
116
+ No consumer action is required: the family, the weights, and the Tailwind `sans` stack
117
+ are unchanged. An app that sets its own `font-display` or preloads Montserrat itself
118
+ should drop that.
119
+
81
120
  ### Fixed
82
121
 
83
122
  - **`.conic-surface` keeps its wash inside its own box.** The wash was an
@@ -17,9 +17,76 @@
17
17
 
18
18
  <%= studio_theme_css_tag %>
19
19
 
20
- <link rel="preconnect" href="https://fonts.googleapis.com">
21
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
22
- <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
20
+ <%# Montserrat is VENDORED into the engine (studio/montserrat-latin.woff2 and
21
+ studio/montserrat-latin-ext.woff2) and shipped through the asset pipeline, no CDN,
22
+ CSP-safe (same-origin :self), mirroring alpine, canvas_confetti and sortable below.
23
+ PINNED AT Google Fonts v31.
24
+
25
+ WHAT THIS REPLACED, and why it was never merely a third-party tidy-up:
26
+
27
+ <link rel="preconnect" href="https://fonts.googleapis.com">
28
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
29
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@...&display=swap" rel="stylesheet">
30
+
31
+ The STYLESHEET blocks the load event. The font FILES it names do not. So the page
32
+ went interactive in the fallback font and every glyph was re-measured when
33
+ Montserrat landed a moment later. Root-caused in close-board-filter-flake: the
34
+ board filter chips changed width AFTER the page was interactive, a synthesized
35
+ click had its pointerdown and pointerup land on different elements, and the browser
36
+ fired click on their COMMON ANCESTOR, so the toggle never ran. Three CI reds in one
37
+ day (chip found at 45ms, clicked at 68ms). click_when_settled now guards the TEST
38
+ lane; it does nothing for a real finger on a cold cache, which is what this removes.
39
+
40
+ font-display: optional, NOT swap, and that is the substance of the fix.
41
+ swap has an UNBOUNDED swap period, so a late font always reflows — self-hosting
42
+ shortens that window but cannot close it. optional gives the browser a block period
43
+ of about 100ms and NO swap period: if the file is not ready in time the page keeps
44
+ the fallback for that navigation and never swaps. The reflow is not shortened, it is
45
+ removed, and removed by construction rather than by being fast enough. block and
46
+ fallback both retain a swap window and so retain the defect. THE COST, NAMED: a
47
+ first visit on a slow link can render one page in system-ui. The preload below is
48
+ what makes that rare, and the file is in the HTTP cache for every navigation after.
49
+
50
+ WHY crossorigin ON A PRELOAD OF OUR OWN FILE. Fonts are fetched in CORS mode
51
+ whatever their origin, so a preload without crossorigin does not match the request
52
+ the font-face makes and the browser fetches the file TWICE. Only latin is
53
+ preloaded: latin-ext is 68KB that most pages never render a glyph from.
54
+
55
+ LATIN AND LATIN-EXT ONLY, measured rather than copied across. The old link pulled
56
+ five subsets. cyrillic, cyrillic-ext and vietnamese occur ZERO times in the engine
57
+ views, in all three consumers views, and in their seed, fixture and locale data.
58
+ latin-ext earns its place on real rendered content: turf-monster seeds roster names
59
+ including Modric, Kovacic, Gvardiol, Dzeko, Calhanoglu, Yildiz and Soucek. Dropping
60
+ the three unused subsets keeps 56KB out of the gem, and unicode-range means a page
61
+ only ever downloads the subsets it actually renders — so keeping latin-ext costs an
62
+ English page nothing at all.
63
+
64
+ ONE FILE PER SUBSET COVERS EVERY WEIGHT. Montserrat v31 is a VARIABLE font, which
65
+ the six-weight link obscured: 400, 500, 600, 700, 800 and 900 all resolved to the
66
+ SAME bytes. Verified by fetching them and reading the fvar table, not assumed. So
67
+ each face below declares the whole 100-900 axis instead of six static instances.
68
+ Asking for fewer weights would not have saved a byte, and every weight the fleet
69
+ uses (measured: 400, 500, 600, 700, 800 as utilities, 900 in level-badge-10 and the
70
+ theme-settings specimen) renders from real outlines rather than a synthesized fake. %>
71
+ <link rel="preload" href="<%= asset_path("studio/montserrat-latin.woff2") %>" as="font" type="font/woff2" crossorigin>
72
+ <style>
73
+ @font-face {
74
+ font-family: 'Montserrat';
75
+ font-style: normal;
76
+ font-weight: 100 900;
77
+ font-display: optional;
78
+ src: url('<%= asset_path("studio/montserrat-latin.woff2") %>') format('woff2');
79
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
80
+ }
81
+ @font-face {
82
+ font-family: 'Montserrat';
83
+ font-style: normal;
84
+ font-weight: 100 900;
85
+ font-display: optional;
86
+ src: url('<%= asset_path("studio/montserrat-latin-ext.woff2") %>') format('woff2');
87
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
88
+ }
89
+ </style>
23
90
 
24
91
  <%# Alpine is VENDORED into the engine (studio/alpine.js) and shipped through the
25
92
  asset pipeline — no CDN, CSP-safe (same-origin :self), mirroring canvas_confetti
data/lib/studio/engine.rb CHANGED
@@ -47,6 +47,8 @@ module Studio
47
47
  studio/canvas_confetti.js
48
48
  studio/studio_confetti.js
49
49
  studio/sortable.js
50
+ studio/montserrat-latin.woff2
51
+ studio/montserrat-latin-ext.woff2
50
52
  ]
51
53
 
52
54
  # The INHERITED default email banners (Studio::EmailImage). They ride the
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.56.2"
2
+ VERSION = "0.56.3"
3
3
  end
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.56.2
4
+ version: 0.56.3
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-08-19 00:00:00.000000000 Z
11
+ date: 2026-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -168,6 +168,8 @@ files:
168
168
  - Gemfile
169
169
  - LICENSE
170
170
  - README.md
171
+ - app/assets/fonts/studio/montserrat-latin-ext.woff2
172
+ - app/assets/fonts/studio/montserrat-latin.woff2
171
173
  - app/assets/images/emails/email-change-confirmation.gif
172
174
  - app/assets/images/emails/logo-horizontal.png
173
175
  - app/assets/images/emails/magic-link-background.gif