@autobusal/common 1.8.2 → 1.8.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/Meta.tsx +32 -0
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,36 @@
3
3
  All notable changes to `@autobusal/common` are documented here. This project follows
4
4
  [Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [1.8.3] - 2026-07-31
7
+
8
+ ### Fixed
9
+
10
+ - **`Meta.tsx`'s `useSoleMetaOwnership` no longer runs on every render.**
11
+ It fired on every render of every `<Meta>` usage (136 call sites, no
12
+ dependency array) and directly removed `<head>` children via raw DOM
13
+ `.remove()` calls each time. The duplicate-tag problem it exists for -
14
+ React's `createRoot` not reconciling against tags baked into the
15
+ prerendered static HTML - can only exist before React's first render;
16
+ every render after that, `<title>`/`<meta>`/`<link>` in `<head>` are
17
+ exclusively React 19's own hoisting mechanism's doing, and it already
18
+ correctly swaps them out as components mount/unmount with no help
19
+ needed. Re-running this raw removal on every navigation instead raced
20
+ that hoisting: it could delete a node mid-transition that React's own
21
+ reconciler still held a reference to, and the next time React tried to
22
+ remove or replace that now-detached node, `parentNode` was null and it
23
+ threw an uncaught `TypeError: Cannot read properties of null (reading
24
+ 'removeChild')` - not render-phase, so no `ErrorBoundary` catches it,
25
+ silently aborting whatever navigation was in flight (URL updates,
26
+ page never re-renders). This is the actual root cause of the frozen-
27
+ navigation bug worked around piecemeal in `@autobusal/providers`
28
+ 1.6.6-1.6.8, `@autobusal/hooks` 1.3.1, and `@autobusal/auth`
29
+ 1.5.2-1.5.4 - all of which targeted `GoogleReCaptchaProvider` as a
30
+ contributing trigger, but the crash reproduced identically with that
31
+ provider removed entirely, on ~any route rendering `<Meta>`. Now
32
+ scoped to a module-level flag so the cleanup runs exactly once, on the
33
+ app's first `<Meta>` mount - it does its one real job (wiping stale
34
+ prerendered tags before first paint) and never touches the DOM again.
35
+
6
36
  ## [1.8.2] - 2026-07-30
7
37
 
8
38
  ### Fixed
package/Meta.tsx CHANGED
@@ -52,8 +52,40 @@ const withBrand = (value: string): string => (brand ? `${ value } - ${ brand }`
52
52
  // them, since nothing here can claim the page has one.
53
53
  type OwnedTag = { selector: string, attr: 'text' | string, value?: string };
54
54
 
55
+ // Edited: Ferjolt Ozuni - Date: 2026-07-31
56
+ // Bug: this ran on EVERY render of EVERY <Meta> usage (no dependency array),
57
+ // removing head children via raw DOM .remove() calls each time. But the
58
+ // duplicate-tag problem this exists for is a ONE-TIME artifact of the
59
+ // prerendered static HTML `createRoot` never reconciles against - it can
60
+ // only exist before React's first render. Every render after that, these
61
+ // tags are exclusively React 19's own hoisting mechanism's doing, and
62
+ // React already correctly swaps them out as components mount/unmount with
63
+ // no help needed. Re-running this raw removal on every subsequent
64
+ // navigation raced that hoisting instead: this effect could delete a
65
+ // <title>/<meta> node mid-transition that React's own reconciler still
66
+ // held a reference to (e.g. while the outgoing page's <Meta> was still
67
+ // completing its unmount), leaving React's fiber pointing at a detached
68
+ // node - the next time React tried to remove or replace it, `parentNode`
69
+ // was null and it threw an uncaught `TypeError: Cannot read properties of
70
+ // null (reading 'removeChild')`. That's not render-phase, so no
71
+ // ErrorBoundary catches it, and it silently aborted whatever navigation
72
+ // was in flight: the URL would update but the page never re-rendered -
73
+ // reproduced on ~every route that renders <Meta>, i.e. nearly every page.
74
+ // Scoping the cleanup to the app's first-ever <Meta> mount (module-scope
75
+ // flag, not per-component-instance state, since every route mounts a NEW
76
+ // Meta instance) keeps it doing its one real job - wiping stale prerendered
77
+ // tags before React's first paint - without ever again running once React
78
+ // is the sole owner of this part of the DOM.
79
+ let hasCleanedPrerenderedTags = false;
80
+
55
81
  const useSoleMetaOwnership = (tags: OwnedTag[]): void => {
56
82
  useEffect(() => {
83
+ if (hasCleanedPrerenderedTags) {
84
+ return;
85
+ }
86
+
87
+ hasCleanedPrerenderedTags = true;
88
+
57
89
  tags.forEach(({ selector, attr, value }) => {
58
90
  const elements = [...document.querySelectorAll(`head > ${ selector }`)];
59
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"