cursedbelt 5.11.0 → 5.12.1
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/dist/react/components/ErrorBoundary.d.ts +10 -1
- package/dist/react/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/react/components/ErrorBoundary.js +16 -22
- package/dist/react/components/ErrorBoundary.js.map +1 -1
- package/dist/react/lazy/index.d.ts +2 -0
- package/dist/react/lazy/index.d.ts.map +1 -0
- package/dist/react/lazy/index.js +2 -0
- package/dist/react/lazy/index.js.map +1 -0
- package/dist/react/lazy/staleChunk.d.ts +60 -0
- package/dist/react/lazy/staleChunk.d.ts.map +1 -0
- package/dist/react/lazy/staleChunk.js +99 -0
- package/dist/react/lazy/staleChunk.js.map +1 -0
- package/dist/react/media/ImageEditor.d.ts.map +1 -1
- package/dist/react/media/ImageEditor.js +3 -2
- package/dist/react/media/ImageEditor.js.map +1 -1
- package/dist/react/media-gallery/MediaGallery.d.ts +1 -1
- package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
- package/dist/react/media-gallery/MediaGallery.js +12 -11
- package/dist/react/media-gallery/MediaGallery.js.map +1 -1
- package/dist/react/nav/SideNav.d.ts.map +1 -1
- package/dist/react/nav/SideNav.js +5 -4
- package/dist/react/nav/SideNav.js.map +1 -1
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
- package/package.json +8 -2
- package/scripts/guardrails-baseline.json +2 -1
- package/scripts/guardrailsEnforce.spec.ts +44 -0
- package/scripts/guardrailsEnforce.ts +53 -0
- package/scripts/styleAreas.ts +2 -1
- package/src/react/components/ErrorBoundary.tsx +16 -24
- package/src/react/components/StaleDeployHealer.spec.tsx +32 -1
- package/src/react/lazy/index.ts +1 -0
- package/src/react/lazy/staleChunk.spec.tsx +138 -0
- package/src/react/lazy/staleChunk.ts +110 -0
- package/src/react/media/ImageEditor.tsx +3 -2
- package/src/react/media-gallery/MediaGallery.tsx +12 -11
- package/src/react/nav/SideNav.tsx +5 -4
|
@@ -43,7 +43,16 @@ export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBo
|
|
|
43
43
|
* rather than "the app is broken" or "the server is unreachable".
|
|
44
44
|
*/
|
|
45
45
|
export declare function isStaleChunkError(error: unknown): boolean;
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Whether this tab already reloaded for a stale chunk inside the last minute — and
|
|
48
|
+
* true when storage is unavailable, so a reload loop is impossible in the one
|
|
49
|
+
* environment where we cannot remember trying. Never throws.
|
|
50
|
+
*
|
|
51
|
+
* 🔴 The memory is `lazyWithReload`'s (`../lazy/staleChunk.ts`), shared on purpose.
|
|
52
|
+
* Until 2026-09-27 this was its own flag, once per tab per URL FOREVER: a tab left
|
|
53
|
+
* open across a SECOND deploy — a film playing for hours — got "could not be
|
|
54
|
+
* loaded" instead of a reload, and the two guards could add up to two reloads.
|
|
55
|
+
*/
|
|
47
56
|
export declare function staleChunkReloadAttempted(): boolean;
|
|
48
57
|
export interface StaleDeployHealerProps {
|
|
49
58
|
/** The caught error, shown as detail when the reload has already been tried. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AAIvF,OAAO,EAAwC,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAerG;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC,CAAC;IACxE,iEAAiE;IACjE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAClD,qFAAqF;IACrF,SAAS,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC/B;;;kFAG8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,GAAG,iBAAiB,CAAC;CACxC;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAQD,qBAAa,aAAc,SAAQ,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAClF,KAAK,EAAE,kBAAkB,CAAmB;IAE5C,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;IAIjE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAItD,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAMlD,KAAK,QAAO,IAAI,CAEd;IAEF,MAAM,IAAI,SAAS;CA0BpB;AA4CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAGnD;AAED,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,sBAAsB,sBAAsB,gCAqC7E,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Component, useEffect, useState } from 'react';
|
|
3
3
|
import { Alert } from './Alert.js';
|
|
4
4
|
import { Spinner } from './Spinner.js';
|
|
5
|
+
import { claimStaleChunkReload, staleChunkReloadedRecently } from '../lazy/staleChunk.js';
|
|
5
6
|
import { ConnectionHealer, isUnreachableError } from './ConnectionHealer.js';
|
|
6
7
|
const keysChanged = (a, b) => {
|
|
7
8
|
if (!a || !b)
|
|
@@ -39,7 +40,7 @@ export class ErrorBoundary extends Component {
|
|
|
39
40
|
// visualizer, the owner's report of 2026-08-22), and `isUnreachableError` matches
|
|
40
41
|
// the substring `failed to fetch`, so `autoHeal` would have claimed the server was
|
|
41
42
|
// unreachable while it answered every request. `StaleDeployHealer` reloads exactly
|
|
42
|
-
// once per tab per
|
|
43
|
+
// once per tab per minute and explains itself if that does not help.
|
|
43
44
|
if (isStaleChunkError(error))
|
|
44
45
|
return _jsx(StaleDeployHealer, { error: error });
|
|
45
46
|
if (autoHeal && isUnreachableError(error))
|
|
@@ -78,7 +79,7 @@ export class ErrorBoundary extends Component {
|
|
|
78
79
|
* hashes, and the route loads. If the chunk is genuinely missing from the
|
|
79
80
|
* deployed artifact rather than merely stale, reloading would loop — so the
|
|
80
81
|
* attempt is recorded in `sessionStorage` and only ever made ONCE per tab per
|
|
81
|
-
*
|
|
82
|
+
* minute (the memory `lazyWithReload` shares). The second failure stops and says what is actually wrong, which is a
|
|
82
83
|
* broken deployment and a sentence the owner can act on.
|
|
83
84
|
*
|
|
84
85
|
* Since 2026-08-22 the origin answers a missing build asset with a 404 rather
|
|
@@ -102,26 +103,20 @@ export function isStaleChunkError(error) {
|
|
|
102
103
|
const message = (error instanceof Error ? `${error.name} ${error.message}` : String(error)).toLowerCase();
|
|
103
104
|
return STALE_CHUNK_PATTERNS.some((p) => message.includes(p));
|
|
104
105
|
}
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Whether this tab already reloaded for a stale chunk inside the last minute — and
|
|
108
|
+
* true when storage is unavailable, so a reload loop is impossible in the one
|
|
109
|
+
* environment where we cannot remember trying. Never throws.
|
|
110
|
+
*
|
|
111
|
+
* 🔴 The memory is `lazyWithReload`'s (`../lazy/staleChunk.ts`), shared on purpose.
|
|
112
|
+
* Until 2026-09-27 this was its own flag, once per tab per URL FOREVER: a tab left
|
|
113
|
+
* open across a SECOND deploy — a film playing for hours — got "could not be
|
|
114
|
+
* loaded" instead of a reload, and the two guards could add up to two reloads.
|
|
115
|
+
*/
|
|
108
116
|
export function staleChunkReloadAttempted() {
|
|
109
|
-
|
|
110
|
-
return window.sessionStorage.getItem(attemptKey()) !== null;
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// Private mode / disabled storage: treat as "already tried" so a reload loop
|
|
114
|
-
// is impossible in the one environment where we cannot remember trying.
|
|
117
|
+
if (typeof window === 'undefined')
|
|
115
118
|
return true;
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
function markStaleChunkReload() {
|
|
119
|
-
try {
|
|
120
|
-
window.sessionStorage.setItem(attemptKey(), '1');
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
// Nothing to do — `staleChunkReloadAttempted` fails closed for the same reason.
|
|
124
|
-
}
|
|
119
|
+
return staleChunkReloadedRecently();
|
|
125
120
|
}
|
|
126
121
|
/**
|
|
127
122
|
* Drop-in fallback for a stale-chunk error: reloads once, automatically, and if
|
|
@@ -134,11 +129,10 @@ export const StaleDeployHealer = ({ error, onRecover }) => {
|
|
|
134
129
|
useEffect(() => {
|
|
135
130
|
if (typeof window === 'undefined')
|
|
136
131
|
return;
|
|
137
|
-
if (
|
|
132
|
+
if (!claimStaleChunkReload()) {
|
|
138
133
|
setExhausted(true);
|
|
139
134
|
return;
|
|
140
135
|
}
|
|
141
|
-
markStaleChunkReload();
|
|
142
136
|
// A tick of delay so the banner paints first — an instant reload on a slow
|
|
143
137
|
// connection reads as a flicker with no explanation.
|
|
144
138
|
const timer = setTimeout(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAkC,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAA0B,MAAM,uBAAuB,CAAC;AA+CrG,MAAM,WAAW,GAAG,CAAC,CAAsB,EAAE,CAAsB,EAAW,EAAE;IAC9E,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC,CAAC;AAEF,MAAM,OAAO,aAAc,SAAQ,SAAiD;IAClF,KAAK,GAAuB,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAE5C,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,iBAAiB,CAAC,KAAY,EAAE,IAAe;QAC7C,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,kBAAkB,CAAC,IAAwB;QACzC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1E,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,GAAG,GAAS,EAAE;QACjB,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,MAAM;QACJ,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAC7B,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;QAEvC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAC1C,gFAAgF;QAChF,+EAA+E;QAC/E,+EAA+E;QAC/E,8EAA8E;QAC9E,2EAA2E;QAC3E,kFAAkF;QAClF,mFAAmF;QACnF,mFAAmF;QACnF,
|
|
1
|
+
{"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/lazy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/lazy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAsB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A lazily-loaded chunk that a deploy has deleted.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 Every deploy renames hashed chunks (`Player-<hash>.js`), and a Worker serves
|
|
5
|
+
* only the current build — so a tab opened BEFORE a deploy asks for a file that no
|
|
6
|
+
* longer exists the first time it reaches a lazy view. Measured on 2026-09-27 in
|
|
7
|
+
* flix: `GET /assets/Player-Cb0NpjEV.js 404` from a viewer's tab minutes after a
|
|
8
|
+
* deploy. The import rejected and the view never appeared. Every app that
|
|
9
|
+
* `React.lazy`s its own modules — and the belt itself, whose lazies are chunks of
|
|
10
|
+
* the app's build — has the same trap, so the fix lives here once.
|
|
11
|
+
*
|
|
12
|
+
* The page is simply out of date, and the one thing that fixes it is loading the
|
|
13
|
+
* current one. So the first failure RELOADS — once, remembered for this tab for a
|
|
14
|
+
* minute — and hands the Suspense fallback a promise that never settles, so
|
|
15
|
+
* nothing flashes an error in the meantime. A second failure inside that minute is
|
|
16
|
+
* not a stale page (a reload just fetched a fresh one) and is thrown to the error
|
|
17
|
+
* boundary, rather than reloading in a loop.
|
|
18
|
+
*
|
|
19
|
+
* The memory is `sessionStorage`, which is per tab AND per origin, so one generic
|
|
20
|
+
* key serves every app without two of them ever reading each other's. It is a TIME,
|
|
21
|
+
* not a flag: a tab left open across two deploys (a film playing for hours) heals
|
|
22
|
+
* both times. `ErrorBoundary`'s `StaleDeployHealer` reads and writes the same
|
|
23
|
+
* memory, so the two never add up to more than one reload a minute.
|
|
24
|
+
*
|
|
25
|
+
* 🔴 It fails CLOSED. Storage this tab cannot read or write (site data blocked)
|
|
26
|
+
* means no reload at all — the error goes to the boundary, which explains it —
|
|
27
|
+
* because a chunk genuinely missing from the deployed build would otherwise reload
|
|
28
|
+
* forever with nothing able to remember trying.
|
|
29
|
+
*
|
|
30
|
+
* `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
|
|
31
|
+
* is refused, so the next lazy view cannot quietly reopen this.
|
|
32
|
+
*/
|
|
33
|
+
import { type ComponentType, type LazyExoticComponent } from 'react';
|
|
34
|
+
export declare const STALE_CHUNK_RELOADED_AT_KEY = "cursedbelt.staleChunkReloadAt";
|
|
35
|
+
/** A reload within this long of the last one is a loop, not a stale page. */
|
|
36
|
+
export declare const STALE_CHUNK_RELOAD_WINDOW_MS = 60000;
|
|
37
|
+
export interface StaleChunkEnv {
|
|
38
|
+
storage: Pick<Storage, 'getItem' | 'setItem'> | null;
|
|
39
|
+
reload: () => void;
|
|
40
|
+
now: () => number;
|
|
41
|
+
}
|
|
42
|
+
export declare function browserEnv(): StaleChunkEnv;
|
|
43
|
+
/**
|
|
44
|
+
* Claim this tab's one stale-chunk reload for the window: true when a reload may
|
|
45
|
+
* happen now, and the claim is already recorded. False when one happened inside the
|
|
46
|
+
* window, or when the memory cannot be read or written — fail closed, see the header.
|
|
47
|
+
*/
|
|
48
|
+
export declare function claimStaleChunkReload(env?: StaleChunkEnv): boolean;
|
|
49
|
+
/** Whether this tab reloaded for a stale chunk inside the window — true, too, when it cannot tell. */
|
|
50
|
+
export declare function staleChunkReloadedRecently(env?: StaleChunkEnv): boolean;
|
|
51
|
+
/** Wrap a dynamic `import()` so a chunk a deploy removed reloads the page instead of failing. */
|
|
52
|
+
export declare function reloadOnStaleChunk<T>(load: () => Promise<T>, env?: () => StaleChunkEnv): () => Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* `React.lazy`, except a chunk a deploy removed reloads the page once instead of
|
|
55
|
+
* leaving the view blank. Use it everywhere `React.lazy` would go.
|
|
56
|
+
*/
|
|
57
|
+
export declare function lazyWithReload<C extends ComponentType<any>>(load: () => Promise<{
|
|
58
|
+
default: C;
|
|
59
|
+
}>, env?: () => StaleChunkEnv): LazyExoticComponent<C>;
|
|
60
|
+
//# sourceMappingURL=staleChunk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleChunk.d.ts","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAQ,MAAM,OAAO,CAAC;AAE3E,eAAO,MAAM,2BAA2B,kCAAkC,CAAC;AAE3E,6EAA6E;AAC7E,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;IACrD,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,wBAAgB,UAAU,IAAI,aAAa,CAQ1C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,aAA4B,GAAG,OAAO,CAShF;AAED,sGAAsG;AACtG,wBAAgB,0BAA0B,CAAC,GAAG,GAAE,aAA4B,GAAG,OAAO,CASrF;AAED,iGAAiG;AACjG,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,GAAG,GAAE,MAAM,aAA0B,GACpC,MAAM,OAAO,CAAC,CAAC,CAAC,CAWlB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,CAAC,GAAG,CAAC,EACzD,IAAI,EAAE,MAAM,OAAO,CAAC;IAAE,OAAO,EAAE,CAAC,CAAA;CAAE,CAAC,EACnC,GAAG,CAAC,EAAE,MAAM,aAAa,GACxB,mBAAmB,CAAC,CAAC,CAAC,CAExB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A lazily-loaded chunk that a deploy has deleted.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 Every deploy renames hashed chunks (`Player-<hash>.js`), and a Worker serves
|
|
5
|
+
* only the current build — so a tab opened BEFORE a deploy asks for a file that no
|
|
6
|
+
* longer exists the first time it reaches a lazy view. Measured on 2026-09-27 in
|
|
7
|
+
* flix: `GET /assets/Player-Cb0NpjEV.js 404` from a viewer's tab minutes after a
|
|
8
|
+
* deploy. The import rejected and the view never appeared. Every app that
|
|
9
|
+
* `React.lazy`s its own modules — and the belt itself, whose lazies are chunks of
|
|
10
|
+
* the app's build — has the same trap, so the fix lives here once.
|
|
11
|
+
*
|
|
12
|
+
* The page is simply out of date, and the one thing that fixes it is loading the
|
|
13
|
+
* current one. So the first failure RELOADS — once, remembered for this tab for a
|
|
14
|
+
* minute — and hands the Suspense fallback a promise that never settles, so
|
|
15
|
+
* nothing flashes an error in the meantime. A second failure inside that minute is
|
|
16
|
+
* not a stale page (a reload just fetched a fresh one) and is thrown to the error
|
|
17
|
+
* boundary, rather than reloading in a loop.
|
|
18
|
+
*
|
|
19
|
+
* The memory is `sessionStorage`, which is per tab AND per origin, so one generic
|
|
20
|
+
* key serves every app without two of them ever reading each other's. It is a TIME,
|
|
21
|
+
* not a flag: a tab left open across two deploys (a film playing for hours) heals
|
|
22
|
+
* both times. `ErrorBoundary`'s `StaleDeployHealer` reads and writes the same
|
|
23
|
+
* memory, so the two never add up to more than one reload a minute.
|
|
24
|
+
*
|
|
25
|
+
* 🔴 It fails CLOSED. Storage this tab cannot read or write (site data blocked)
|
|
26
|
+
* means no reload at all — the error goes to the boundary, which explains it —
|
|
27
|
+
* because a chunk genuinely missing from the deployed build would otherwise reload
|
|
28
|
+
* forever with nothing able to remember trying.
|
|
29
|
+
*
|
|
30
|
+
* `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
|
|
31
|
+
* is refused, so the next lazy view cannot quietly reopen this.
|
|
32
|
+
*/
|
|
33
|
+
import { lazy } from 'react';
|
|
34
|
+
export const STALE_CHUNK_RELOADED_AT_KEY = 'cursedbelt.staleChunkReloadAt';
|
|
35
|
+
/** A reload within this long of the last one is a loop, not a stale page. */
|
|
36
|
+
export const STALE_CHUNK_RELOAD_WINDOW_MS = 60_000;
|
|
37
|
+
export function browserEnv() {
|
|
38
|
+
let storage = null;
|
|
39
|
+
try {
|
|
40
|
+
storage = window.sessionStorage;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Storage refused (blocked site data) — no memory, so no reload: the guard fails closed.
|
|
44
|
+
}
|
|
45
|
+
return { storage, reload: () => window.location.reload(), now: Date.now };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Claim this tab's one stale-chunk reload for the window: true when a reload may
|
|
49
|
+
* happen now, and the claim is already recorded. False when one happened inside the
|
|
50
|
+
* window, or when the memory cannot be read or written — fail closed, see the header.
|
|
51
|
+
*/
|
|
52
|
+
export function claimStaleChunkReload(env = browserEnv()) {
|
|
53
|
+
if (staleChunkReloadedRecently(env))
|
|
54
|
+
return false;
|
|
55
|
+
const stamp = String(env.now());
|
|
56
|
+
try {
|
|
57
|
+
env.storage?.setItem(STALE_CHUNK_RELOADED_AT_KEY, stamp);
|
|
58
|
+
return env.storage?.getItem(STALE_CHUNK_RELOADED_AT_KEY) === stamp;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Whether this tab reloaded for a stale chunk inside the window — true, too, when it cannot tell. */
|
|
65
|
+
export function staleChunkReloadedRecently(env = browserEnv()) {
|
|
66
|
+
const { storage, now } = env;
|
|
67
|
+
if (!storage)
|
|
68
|
+
return true;
|
|
69
|
+
try {
|
|
70
|
+
const last = Number(storage.getItem(STALE_CHUNK_RELOADED_AT_KEY) ?? 0) || 0;
|
|
71
|
+
return last > 0 && now() - last < STALE_CHUNK_RELOAD_WINDOW_MS;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Wrap a dynamic `import()` so a chunk a deploy removed reloads the page instead of failing. */
|
|
78
|
+
export function reloadOnStaleChunk(load, env = browserEnv) {
|
|
79
|
+
return async () => {
|
|
80
|
+
try {
|
|
81
|
+
return await load();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const current = env();
|
|
85
|
+
if (!claimStaleChunkReload(current))
|
|
86
|
+
throw error;
|
|
87
|
+
current.reload();
|
|
88
|
+
return new Promise(() => undefined);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `React.lazy`, except a chunk a deploy removed reloads the page once instead of
|
|
94
|
+
* leaving the view blank. Use it everywhere `React.lazy` would go.
|
|
95
|
+
*/
|
|
96
|
+
export function lazyWithReload(load, env) {
|
|
97
|
+
return lazy(reloadOnStaleChunk(load, env));
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=staleChunk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleChunk.js","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAgD,IAAI,EAAE,MAAM,OAAO,CAAC;AAE3E,MAAM,CAAC,MAAM,2BAA2B,GAAG,+BAA+B,CAAC;AAE3E,6EAA6E;AAC7E,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC;AAQnD,MAAM,UAAU,UAAU;IACxB,IAAI,OAAO,GAA6B,IAAI,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,yFAAyF;IAC3F,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAqB,UAAU,EAAE;IACrE,IAAI,0BAA0B,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;QACzD,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,2BAA2B,CAAC,KAAK,KAAK,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,0BAA0B,CAAC,MAAqB,UAAU,EAAE;IAC1E,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,2BAA2B,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5E,OAAO,IAAI,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,4BAA4B,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,kBAAkB,CAChC,IAAsB,EACtB,MAA2B,UAAU;IAErC,OAAO,KAAK,IAAI,EAAE;QAChB,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC;gBAAE,MAAM,KAAK,CAAC;YACjD,OAAO,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO,IAAI,OAAO,CAAI,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAmC,EACnC,GAAyB;IAEzB,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageEditor.d.ts","sourceRoot":"","sources":["../../../src/react/media/ImageEditor.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ImageEditor.d.ts","sourceRoot":"","sources":["../../../src/react/media/ImageEditor.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMnD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,MAAM,EACN,OAAO,EACP,IAAW,EACX,IAAa,EACb,WAAW,EACX,sBAAsB,EACtB,iBAAiB,EACjB,aAAqB,EACrB,QAAQ,GACT,EAAE,gBAAgB,+BA0ElB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Suspense, useCallback } from 'react';
|
|
3
|
+
import { lazyWithReload } from '../lazy/staleChunk.js';
|
|
3
4
|
import { imageFormatToMime } from 'cursedbelt-core/media';
|
|
4
5
|
import { Modal } from '../components/Modal.js';
|
|
5
6
|
import { Spinner } from '../components/Spinner.js';
|
|
@@ -7,7 +8,7 @@ import { filerobotDesignToPipelineSpec, savedImageToBlob, tabsForOperations, } f
|
|
|
7
8
|
import { MaskPainter } from './MaskPainter.js';
|
|
8
9
|
// react-filerobot-image-editor is large + browser-only (canvas/konva). Code-split it
|
|
9
10
|
// so it loads only when the editor opens and never enters the base bundle / SSR.
|
|
10
|
-
const FilerobotImageEditor =
|
|
11
|
+
const FilerobotImageEditor = lazyWithReload(() => import('react-filerobot-image-editor'));
|
|
11
12
|
/**
|
|
12
13
|
* Full-screen image editor on react-filerobot-image-editor, hosted in the cursedbelt
|
|
13
14
|
* {@link Modal} (xl). On save it calls `onSave`; the app performs the upload (auth/ctgr
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageEditor.js","sourceRoot":"","sources":["../../../src/react/media/ImageEditor.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ImageEditor.js","sourceRoot":"","sources":["../../../src/react/media/ImageEditor.tsx"],"names":[],"mappings":"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"}
|
|
@@ -50,7 +50,7 @@ export declare function galleryTileLine(item: MediaGalleryItem, options?: {
|
|
|
50
50
|
*
|
|
51
51
|
* 🔴 The return type is spelled out rather than imported as `PlayerVideo` from
|
|
52
52
|
* `../media/VideoPlayer`, and the reason is BYTES, not taste. The player is reached from here
|
|
53
|
-
* through `
|
|
53
|
+
* through `lazyWithReload(() => import(…))` on purpose, and `scripts/styleAreas.ts`'s closure walk
|
|
54
54
|
* reads STATIC specifiers — an `import type` is one, and tsc erases it long after that walk has
|
|
55
55
|
* decided. Measured 2026-09-18: naming the type pulled the player's whole static closure into
|
|
56
56
|
* the `media-gallery` area stylesheet, 2023 candidates to 2190, for a line that emits nothing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaGallery.d.ts","sourceRoot":"","sources":["../../../src/react/media-gallery/MediaGallery.tsx"],"names":[],"mappings":"AAAA,OAAO,oBAAoB,CAAC;AA4D5B,OAAO,KAAK,EAAE,EACZ,KAAK,SAAS,EAEd,KAAK,SAAS,EAMf,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"MediaGallery.d.ts","sourceRoot":"","sources":["../../../src/react/media-gallery/MediaGallery.tsx"],"names":[],"mappings":"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"}
|
|
@@ -35,6 +35,7 @@ import './mediaGallery.css';
|
|
|
35
35
|
import { ArrowLeft, ArrowRightToLine, Camera, Crop, Download, Image as ImageIcon, LayoutGrid, ListVideo, Pencil, Pin, Play, Repeat, Repeat1, RotateCw, Rows3, Scissors, Shuffle, SkipForward, SquareCheck, Sun, Tags, Timer, X, } from 'lucide-react';
|
|
36
36
|
import { formatBytes } from 'cwip/format';
|
|
37
37
|
import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
38
|
+
import { lazyWithReload } from '../lazy/staleChunk.js';
|
|
38
39
|
import { CATEGORY_LABEL, FileTypeIcon } from '../components/FileTypeIcon.js';
|
|
39
40
|
import { DeleteButton } from '../components/DeleteButton.js';
|
|
40
41
|
import { useCheckboxSelection, } from '../hooks/useCheckboxSelection.js';
|
|
@@ -60,17 +61,17 @@ import { TruncatedText } from '../components/TruncatedText.js';
|
|
|
60
61
|
// fullscreen toggle. Presentation + dispatch only: apps map their records to
|
|
61
62
|
// `MediaGalleryItem` and keep auth/signing/deletion on their side.
|
|
62
63
|
// Heavy viewers stay off the critical path — each loads only when a matching file opens.
|
|
63
|
-
const LazyVideoPlayer =
|
|
64
|
-
const LazyPdfViewer =
|
|
65
|
-
const LazyWorkbookViewer =
|
|
66
|
-
const LazyMarkdownDoc =
|
|
64
|
+
const LazyVideoPlayer = lazyWithReload(() => import('../media/VideoPlayer.js').then((m) => ({ default: m.VideoPlayer })));
|
|
65
|
+
const LazyPdfViewer = lazyWithReload(() => import('../pdf-viewer/PdfViewer.js').then((m) => ({ default: m.PdfViewer })));
|
|
66
|
+
const LazyWorkbookViewer = lazyWithReload(() => import('../workbook-viewer/WorkbookViewer.js').then((m) => ({ default: m.WorkbookViewer })));
|
|
67
|
+
const LazyMarkdownDoc = lazyWithReload(() => import('../markdown/MarkdownDoc.js').then((m) => ({ default: m.MarkdownDoc })));
|
|
67
68
|
// The rotate/crop/re-light dialog, and its own stylesheet, only once somebody opens it — so an
|
|
68
69
|
// app that never edits a picture pays nothing for the capability being available.
|
|
69
|
-
const LazyPictureEditor =
|
|
70
|
-
const LazyFrameGrabber =
|
|
70
|
+
const LazyPictureEditor = lazyWithReload(() => import('../media/PictureEditor.js').then((m) => ({ default: m.PictureEditor })));
|
|
71
|
+
const LazyFrameGrabber = lazyWithReload(() => import('../media/FrameGrabber.js').then((m) => ({ default: m.FrameGrabber })));
|
|
71
72
|
// The same four gestures for FOOTAGE — a separate dialog from the picture editor because a
|
|
72
73
|
// video's turn can only ever be stored, never baked. See `VideoTurner`'s header.
|
|
73
|
-
const LazyVideoTurner =
|
|
74
|
+
const LazyVideoTurner = lazyWithReload(() => import('../media/VideoTurner.js').then((m) => ({ default: m.VideoTurner })));
|
|
74
75
|
/**
|
|
75
76
|
* The turn dialog, over the item's RESOLVED media — never over the raw row.
|
|
76
77
|
*
|
|
@@ -125,7 +126,7 @@ function EditPictureDialog({ item, cache, allowReplace, onClose, onSave, }) {
|
|
|
125
126
|
// has no pipeline to execute a cut list never passes `onTrimVideo`, so it never loads a byte
|
|
126
127
|
// of this — which is what makes "enabled for collections only, for now" a fact about the
|
|
127
128
|
// bundle rather than a feature flag somebody has to remember to check.
|
|
128
|
-
const LazyVideoCutEditor =
|
|
129
|
+
const LazyVideoCutEditor = lazyWithReload(() => import('../media/VideoCutEditor.js').then((m) => ({ default: m.VideoCutEditor })));
|
|
129
130
|
/**
|
|
130
131
|
* The trim dialog, over the item's RESOLVED media — never over the raw row. Same defect, same
|
|
131
132
|
* fix and the same shared cache as {@link TurnVideoDialog}, whose header carries the whole
|
|
@@ -144,7 +145,7 @@ function TrimVideoDialog({ item, cache, note, onClose, onSubmit, }) {
|
|
|
144
145
|
// Naming the parts of a clip. The same bargain as the cut editor one line up: an app that has
|
|
145
146
|
// nowhere to PUT a chapter list never passes `onSaveChapters`, so it never loads a byte of
|
|
146
147
|
// this dialog — the capability and the bundle cost turn on together, with no flag between them.
|
|
147
|
-
const LazyVideoChapterEditor =
|
|
148
|
+
const LazyVideoChapterEditor = lazyWithReload(() => import('../media/VideoChapterEditor.js').then((m) => ({ default: m.VideoChapterEditor })));
|
|
148
149
|
/**
|
|
149
150
|
* The sections dialog, over the item's RESOLVED media — never over the raw row. Same defect,
|
|
150
151
|
* same fix and the same shared cache as {@link TurnVideoDialog}; what is particular here is
|
|
@@ -182,7 +183,7 @@ function GrabFrameDialog({ item, at, cache, onClose, onSaveThumbnail, onSaveSnap
|
|
|
182
183
|
// with nowhere to PUT a tag never passes `onSaveMeta`, so it never loads a byte of this
|
|
183
184
|
// dialog — and the gallery keeps reading whatever tags and descriptions its items already
|
|
184
185
|
// carry either way, because reading was never the capability that was missing.
|
|
185
|
-
const LazyMediaMetaEditor =
|
|
186
|
+
const LazyMediaMetaEditor = lazyWithReload(() => import('./MediaMetaEditor.js').then((m) => ({ default: m.MediaMetaEditor })));
|
|
186
187
|
/**
|
|
187
188
|
* One glyph per end-behaviour — the five values the cycling pill used to spell out.
|
|
188
189
|
*
|
|
@@ -476,7 +477,7 @@ function BytesStage({ item, category, url, loadBytes, cache, }) {
|
|
|
476
477
|
*
|
|
477
478
|
* 🔴 The return type is spelled out rather than imported as `PlayerVideo` from
|
|
478
479
|
* `../media/VideoPlayer`, and the reason is BYTES, not taste. The player is reached from here
|
|
479
|
-
* through `
|
|
480
|
+
* through `lazyWithReload(() => import(…))` on purpose, and `scripts/styleAreas.ts`'s closure walk
|
|
480
481
|
* reads STATIC specifiers — an `import type` is one, and tsc erases it long after that walk has
|
|
481
482
|
* decided. Measured 2026-09-18: naming the type pulled the player's whole static closure into
|
|
482
483
|
* the `media-gallery` area stylesheet, 2023 candidates to 2190, for a line that emits nothing
|