@autobusal/providers 1.44.2 → 1.44.4

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.
package/Setup/Preload.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from 'react';
2
- import { JsonLd, setServerToday } from '@autobusal/common';
2
+ import { JsonLd, hasMetaMounted, setServerToday } from '@autobusal/common';
3
3
  import { SettingsData } from '../types/settings';
4
4
  import { useSystemTheme } from '@autobusal/hooks';
5
5
 
@@ -8,6 +8,42 @@ interface Props {
8
8
  children: JSX.Element
9
9
  }
10
10
 
11
+ /**
12
+ * Whether this page already carries the brand's fonts, served from its own
13
+ * domain.
14
+ *
15
+ * Claude - Date: 2026-09-16
16
+ *
17
+ * WHY GOOGLE'S STYLESHEET IS NOW THE FALLBACK. Measured with Lighthouse on a
18
+ * throttled phone against busmagus.com: the stylesheet below blocked the
19
+ * first paint for 854ms, before a single character of the page could draw,
20
+ * because it lives on a third origin that needs its own DNS, TCP and TLS
21
+ * before the request is even sent - and then a fourth origin for the font
22
+ * files it names. It was the largest single cost PageSpeed reported, ahead
23
+ * of every script and image on the page.
24
+ *
25
+ * A brand's build can now download the pair named in its theme and inline
26
+ * the @font-face rules into index.html, marked
27
+ * `<style data-fonts="Title|Content">`, with the files served from its own
28
+ * domain and the most-used ones preloaded (see magus's scripts/fonts.js).
29
+ * When that tag is present AND names the fonts this theme asks for, this
30
+ * component has nothing to add.
31
+ *
32
+ * The family check is not decoration. Fonts are chosen in the admin and a
33
+ * build only knows the pair it downloaded: change them afterwards and the
34
+ * inlined faces describe the OLD fonts until the next deploy. Comparing the
35
+ * names means the page falls back to Google for the new pair instead of
36
+ * quietly rendering in the wrong typeface. A brand whose build does not do
37
+ * this at all has no tag, and gets exactly the stylesheet it always had.
38
+ */
39
+ const hasSelfHostedFonts = (title: string, content: string): boolean => {
40
+ if (typeof document === 'undefined') {
41
+ return false;
42
+ }
43
+
44
+ return document.querySelector('style[data-fonts]')?.getAttribute('data-fonts') === `${ title }|${ content }`;
45
+ };
46
+
11
47
  // React 19 automatically hoists any <title>/<meta>/<link> rendered anywhere
12
48
  // in the tree into <head> - no wrapper component needed. Replaced
13
49
  // react-helmet, which had gone silently non-functional under React 19 (see
@@ -40,17 +76,45 @@ const Preload = ({ data, children }: Props): JSX.Element => {
40
76
  // sold them quite happily.
41
77
  setServerToday(data.today);
42
78
 
43
- // the app-shell title, for the moment before a page's own <Meta> mounts
79
+ /*
80
+ * The app-shell title, for the moment before a page's own <Meta> mounts -
81
+ * AND MARKED, so that page can take it away again.
82
+ *
83
+ * Edited: Claude - Date: 2026-09-16
84
+ *
85
+ * Assigning document.title when <head> holds no <title> does not just set a
86
+ * string: the browser CREATES a <title> element for it. On a route whose
87
+ * chunk is still loading there is nothing else in there - main.tsx has
88
+ * already stripped index.html's build-time tag - so this fallback made one,
89
+ * and when the page's own <Meta> arrived React hoisted a second beside it.
90
+ * Both were then captured into the prerendered snapshot, which is why Bing's
91
+ * site scan reported "More than one title tag" on 112 pages. A browser reads
92
+ * the first, which is the right one, so nothing looked wrong; a crawler
93
+ * reading the HTML sees two, and the stray one says only the brand name.
94
+ *
95
+ * Meta removes any element carrying this marker as soon as it owns the head
96
+ * (see useSoleMetaOwnership in @autobusal/common), so the fallback lasts
97
+ * exactly as long as it is the only title there is.
98
+ */
44
99
  useEffect(() => {
45
- if (!document.head.querySelector('title')) {
46
- document.title = data.preferences.company;
100
+ // Nothing to stand in for: the page owns the title, or already put one
101
+ // up. hasMetaMounted is the half of this that a marker cannot cover -
102
+ // see the note on it in @autobusal/common's Meta.
103
+ if (hasMetaMounted() || document.head.querySelector('title')) {
104
+ return;
47
105
  }
106
+
107
+ document.title = data.preferences.company;
108
+
109
+ document.head.querySelector('title')?.setAttribute('data-shell-title', '');
48
110
  }, [ data.preferences.company ]);
49
111
 
50
112
  // we arrange the font family names before inserting them
51
113
  const fontTitle = data.theme.fontFamily.title.replace(/ /g, '+');
52
114
  const fontContent = data.theme.fontFamily.content.replace(/ /g, '+');
53
115
 
116
+ const selfHosted = hasSelfHostedFonts(data.theme.fontFamily.title, data.theme.fontFamily.content);
117
+
54
118
  const theme = useSystemTheme();
55
119
 
56
120
  const primaryColor = data.colors[theme].primary.normal;
@@ -101,7 +165,9 @@ const Preload = ({ data, children }: Props): JSX.Element => {
101
165
  <meta name="msapplication-config" content={ `${ data.url }/favicons/browserconfig.xml` } />
102
166
  <meta name="theme-color" content={ primaryColor } />
103
167
 
104
- <link href={ `https://fonts.googleapis.com/css2?family=${ fontTitle }:wght@700&family=${ fontContent }:wght@300;400;700&display=swap` } rel="stylesheet" />
168
+ { !selfHosted && (
169
+ <link href={ `https://fonts.googleapis.com/css2?family=${ fontTitle }:wght@700&family=${ fontContent }:wght@300;400;700&display=swap` } rel="stylesheet" />
170
+ ) }
105
171
 
106
172
  <JsonLd data={ organizationLd } />
107
173
  <JsonLd data={ websiteLd } />
@@ -77,10 +77,44 @@ const analytics = (container: string | null | undefined): void => {
77
77
  document.head.appendChild(script);
78
78
  };
79
79
 
80
- if (typeof window.requestIdleCallback === 'function') {
81
- window.requestIdleCallback(load, { timeout: 2000 });
80
+ /*
81
+ * AFTER THE PAGE HAS LOADED, then in the first quiet moment.
82
+ *
83
+ * Edited: Claude - Date: 2026-09-16
84
+ *
85
+ * The idle wait above was not enough on its own, because "idle" arrives
86
+ * early on a page that is still downloading: requestIdleCallback fires
87
+ * whenever the main thread has a gap, and a phone waiting on the network
88
+ * has plenty of gaps. Lighthouse on a throttled phone still found Tag
89
+ * Manager and the Google Analytics tag it pulls in - 287 KB between them,
90
+ * 131 KB of it never executed - arriving inside the page's own loading
91
+ * window, competing for the same connection as the bundle and the images.
92
+ *
93
+ * Waiting for `load` first puts them after everything the page itself
94
+ * asked for. The idle wait then keeps them off the main thread while the
95
+ * page settles, with a ceiling so they are never deferred indefinitely.
96
+ *
97
+ * What this costs is measurement, not function, and it is worth naming: a
98
+ * visitor who leaves within a couple of seconds of the page finishing
99
+ * loading may not be counted. Everything else is kept. The dataLayer is
100
+ * created and filled BEFORE any of this, so page views and commerce
101
+ * events announced while the container is on its way are queued and
102
+ * processed when it boots, not dropped.
103
+ *
104
+ * Safari has no requestIdleCallback, so a short timeout stands in for it.
105
+ */
106
+ const whenIdle = (): void => {
107
+ if (typeof window.requestIdleCallback === 'function') {
108
+ window.requestIdleCallback(load, { timeout: 3000 });
109
+ } else {
110
+ window.setTimeout(load, 1500);
111
+ }
112
+ };
113
+
114
+ if (document.readyState === 'complete') {
115
+ whenIdle();
82
116
  } else {
83
- window.setTimeout(load, 1200);
117
+ window.addEventListener('load', whenIdle, { once: true });
84
118
  }
85
119
  };
86
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/providers",
3
- "version": "1.44.2",
3
+ "version": "1.44.4",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts",