@autobusal/providers 1.6.8 → 1.6.9

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/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to `@autobusal/providers` are documented here. This project
4
4
  adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.6.9] - 2026-07-31
8
+
9
+ ### Added (re-added)
10
+
11
+ - **`useRecoverFromDomCorruption()`'s global `removeChild`/`insertBefore`
12
+ listener is back**, generalized. Removed in 1.6.8 as a backstop
13
+ specifically for `GoogleReCaptchaProvider`'s remount corruption, on the
14
+ assumption that removing that provider removed the trigger. It didn't:
15
+ `@autobusal/common` 1.8.3 fixed a second, independent cause
16
+ (`Meta.tsx`'s stale-tag cleanup racing React 19's own `<title>`/`<meta>`
17
+ hoisting), and the exact same uncaught error still reproduced
18
+ intermittently with BOTH fixes shipped - some remaining React/router
19
+ timing edge case neither touches. Reinstated as a permanent safety net
20
+ rather than a fix for one specific bug: whatever the next contributing
21
+ cause turns out to be, a reload of the current URL has recovered
22
+ cleanly in every reproduction so far.
23
+
7
24
  ## [1.6.8] - 2026-07-31
8
25
 
9
26
  ### Removed
package/Providers.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { Suspense } from 'react';
1
+ import { Suspense, useEffect } from 'react';
2
2
  import Queries from './Queries/Queries';
3
3
  import Styles from './Styles/Styles';
4
4
  import Notifications from './Notifications/Notifications';
@@ -10,39 +10,90 @@ interface Props {
10
10
  children: JSX.Element
11
11
  }
12
12
 
13
- const Providers = ({ type, loading, children }: Props): JSX.Element => (
14
- // Styles and Setup both call useGetSettings() (a useSuspenseQuery) and sit
15
- // ABOVE the inner <Suspense> below, so on a cold settings cache their
16
- // suspension had no boundary to catch it and React threw error #426
17
- // ("a component suspended while responding to synchronous input"). This
18
- // outer boundary catches those two suspensions (showing `loading` during
19
- // the initial settings fetch). The QueryClient in <Queries> is created at
20
- // module scope, so remounting this subtree while suspended keeps the same
21
- // client and cache - no refetch loop.
22
- // Edited: Ferjolt Ozuni - Date: 2026-07-31
23
- // Setup also calls useGetMenu() (a SEPARATE useSuspenseQuery, its own
24
- // cache key/promise) in its own render body. Kept in its own inner
25
- // <Suspense> rather than sharing the outer one: a cold menu cache
26
- // suspending after Styles/Notifications above it have already committed
27
- // would otherwise hide/discard that already-committed content and remount
28
- // it once ready - harmless for Styles/Notifications themselves, but a
29
- // needless remount of anything stateful sitting there is worth avoiding on
30
- // principle.
31
- <Suspense fallback={ loading }>
32
- <Queries>
33
- <Styles>
34
- <Notifications>
35
- <Suspense fallback={ loading }>
36
- <Setup type={ type }>
37
- <Suspense fallback={ loading }>
38
- { children }
39
- </Suspense>
40
- </Setup>
41
- </Suspense>
42
- </Notifications>
43
- </Styles>
44
- </Queries>
45
- </Suspense>
46
- );
13
+ // Edited: Ferjolt Ozuni - Date: 2026-07-31
14
+ // Permanent safety net, not a fix for one specific bug. Two real, separate
15
+ // causes of a client-side navigation silently never landing were found and
16
+ // fixed - GoogleReCaptchaProvider's remount corruption (removed entirely,
17
+ // see this package's 1.6.6-1.6.8 and @autobusal/hooks 1.3.1) and Meta.tsx's
18
+ // stale-tag cleanup racing React 19's own <title>/<meta> hoisting (see
19
+ // @autobusal/common 1.8.3) - and the exact same uncaught "Cannot read
20
+ // properties of null (reading 'removeChild')" still reproduced
21
+ // intermittently with both gone: some remaining React/router timing edge
22
+ // case neither fix touches. That class of error is thrown outside React's
23
+ // render phase, so no ErrorBoundary catches it, and it silently aborts
24
+ // whatever navigation was in flight - the URL updates (the router's
25
+ // history.pushState already ran) but the page itself never re-renders, with
26
+ // no visible error and no way to navigate out. A reload of the current URL
27
+ // has recovered cleanly in every reproduction so far, so: watch for this
28
+ // exact error signature anywhere in the app and self-heal with one reload,
29
+ // cooldown-guarded so a genuinely persistent error can't loop. Logout.tsx/
30
+ // LoginAs.tsx carry a second, more targeted watchdog (a plain timer, not
31
+ // error-triggered) for the two pages users actually get stuck on during
32
+ // impersonation - this one is the general backstop for everywhere else.
33
+ const useRecoverFromDomCorruption = (): void => {
34
+ useEffect(() => {
35
+ const RECOVERY_KEY = 'autobusalDomRecoveryAt';
36
+
37
+ const onError = (event: ErrorEvent): void => {
38
+ const message = event.message || '';
39
+
40
+ if (!message.includes('removeChild') && !message.includes('insertBefore')) {
41
+ return;
42
+ }
43
+
44
+ const last = Number(sessionStorage.getItem(RECOVERY_KEY) || 0);
45
+
46
+ if (Date.now() - last < 10000) {
47
+ return;
48
+ }
49
+
50
+ sessionStorage.setItem(RECOVERY_KEY, String(Date.now()));
51
+ window.location.reload();
52
+ };
53
+
54
+ window.addEventListener('error', onError);
55
+
56
+ return () => window.removeEventListener('error', onError);
57
+ }, []);
58
+ };
59
+
60
+ const Providers = ({ type, loading, children }: Props): JSX.Element => {
61
+ useRecoverFromDomCorruption();
62
+
63
+ return (
64
+ // Styles and Setup both call useGetSettings() (a useSuspenseQuery) and
65
+ // sit ABOVE the inner <Suspense> below, so on a cold settings cache
66
+ // their suspension had no boundary to catch it and React threw error
67
+ // #426 ("a component suspended while responding to synchronous input").
68
+ // This outer boundary catches those two suspensions (showing `loading`
69
+ // during the initial settings fetch). The QueryClient in <Queries> is
70
+ // created at module scope, so remounting this subtree while suspended
71
+ // keeps the same client and cache - no refetch loop.
72
+ // Edited: Ferjolt Ozuni - Date: 2026-07-31
73
+ // Setup also calls useGetMenu() (a SEPARATE useSuspenseQuery, its own
74
+ // cache key/promise) in its own render body. Kept in its own inner
75
+ // <Suspense> rather than sharing the outer one: a cold menu cache
76
+ // suspending after Styles/Notifications above it have already committed
77
+ // would otherwise hide/discard that already-committed content and
78
+ // remount it once ready - harmless for Styles/Notifications themselves,
79
+ // but a needless remount of anything stateful sitting there is worth
80
+ // avoiding on principle.
81
+ <Suspense fallback={ loading }>
82
+ <Queries>
83
+ <Styles>
84
+ <Notifications>
85
+ <Suspense fallback={ loading }>
86
+ <Setup type={ type }>
87
+ <Suspense fallback={ loading }>
88
+ { children }
89
+ </Suspense>
90
+ </Setup>
91
+ </Suspense>
92
+ </Notifications>
93
+ </Styles>
94
+ </Queries>
95
+ </Suspense>
96
+ );
97
+ };
47
98
 
48
99
  export default Providers;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/providers",
3
- "version": "1.6.8",
3
+ "version": "1.6.9",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"