cursedbelt 5.113.0 → 5.114.0

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.
@@ -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;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,+BAA+B,sBAAsB,gCAsCtF,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,+BAA+B,sBAAsB,gCA8CtF,CAAC"}
@@ -2,7 +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
+ import { claimStaleChunkReload, leavingFor, staleChunkReloadedRecently } from '../lazy/staleChunk.js';
6
6
  import { ConnectionHealer, isUnreachableError } from './ConnectionHealer.js';
7
7
  const keysChanged = (a, b) => {
8
8
  if (!a || !b)
@@ -129,18 +129,28 @@ export const StaleDeployHealer = ({ error, onRecover, ...rest }) => {
129
129
  useEffect(() => {
130
130
  if (typeof window === 'undefined')
131
131
  return;
132
- if (!claimStaleChunkReload()) {
133
- setExhausted(true);
134
- return;
135
- }
136
- // A tick of delay so the banner paints first — an instant reload on a slow
137
- // connection reads as a flicker with no explanation.
138
- const timer = setTimeout(() => {
139
- if (onRecover)
140
- onRecover();
141
- else
142
- window.location.reload();
143
- }, 400);
132
+ let timer;
133
+ const heal = () => {
134
+ if (!claimStaleChunkReload()) {
135
+ setExhausted(true);
136
+ return;
137
+ }
138
+ // A tick of delay so the banner paints first — an instant reload on a slow
139
+ // connection reads as a flicker with no explanation.
140
+ timer = setTimeout(() => {
141
+ if (onRecover)
142
+ onRecover();
143
+ else
144
+ window.location.reload();
145
+ }, 400);
146
+ };
147
+ // A chunk cut off by the page being left (Safari cancels its loads at `beforeunload`) is
148
+ // not stale, and a reload would pull the tab back from where it was going — `staleChunk.ts`.
149
+ const leaving = leavingFor();
150
+ if (leaving > 0)
151
+ timer = setTimeout(heal, leaving);
152
+ else
153
+ heal();
144
154
  return () => clearTimeout(timer);
145
155
  }, [onRecover]);
146
156
  if (exhausted)
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from 'react';
1
2
  export interface InlineEditableTextProps {
2
3
  value: string;
3
4
  /** Called with the trimmed next value, only when it actually changed. */
@@ -35,6 +36,15 @@ export interface InlineEditableTextProps {
35
36
  * would otherwise replace everything they wrote.
36
37
  */
37
38
  caret?: 'select-all' | 'end';
39
+ /**
40
+ * How the value reads when it is NOT being edited — the click-to-edit display and the
41
+ * read-only text alike; the editor still edits the raw string. For text with structure
42
+ * the string only encodes (a read table's tab-separated rows drawn as a grid). The
43
+ * editable display is a `<button>`, so this returns phrasing content: spans, styled
44
+ * into whatever shape the text needs. Never called for an empty value — the
45
+ * placeholder shows then.
46
+ */
47
+ display?: (value: string) => ReactNode;
38
48
  /** The editor's character ceiling (default 10,000) — the server's limit, so the editor stops where a save would be refused. */
39
49
  maxLength?: number;
40
50
  ariaLabel?: string;
@@ -47,5 +57,5 @@ export interface InlineEditableTextProps {
47
57
  * selects its text on mount so typing replaces (and the cursor is never stranded
48
58
  * at position 0) — or, with `caret='end'`, puts the caret after the text.
49
59
  */
50
- export declare function InlineEditableText({ value, onChange, canEdit, multiline, placeholder, className, inputClassName, trigger, wrap, caret, maxLength, ariaLabel, ...rest }: InlineEditableTextProps): import("react").JSX.Element | null;
60
+ export declare function InlineEditableText({ value, onChange, canEdit, multiline, placeholder, className, inputClassName, trigger, wrap, caret, maxLength, ariaLabel, display, ...rest }: InlineEditableTextProps): import("react").JSX.Element | null;
51
61
  //# sourceMappingURL=InlineEditableText.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"InlineEditableText.d.ts","sourceRoot":"","sources":["../../../src/react/components/InlineEditableText.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;gEAC4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;gEAC4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;sDAGkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;IAC7B,+HAA+H;IAC/H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,WAAyB,EACzB,SAAS,EACT,cAAc,EACd,OAAgB,EAChB,IAAW,EACX,KAAoB,EACpB,SAAkB,EAClB,SAAS,EACT,GAAG,IAAI,EACR,EAAE,uBAAuB,sCAgJzB"}
1
+ {"version":3,"file":"InlineEditableText.d.ts","sourceRoot":"","sources":["../../../src/react/components/InlineEditableText.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAyB,MAAM,OAAO,CAAC;AAS9D,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;gEAC4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;gEAC4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;sDAGkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;IAC7B;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC;IACvC,+HAA+H;IAC/H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,WAAyB,EACzB,SAAS,EACT,cAAc,EACd,OAAgB,EAChB,IAAW,EACX,KAAoB,EACpB,SAAkB,EAClB,SAAS,EACT,OAAO,EACP,GAAG,IAAI,EACR,EAAE,uBAAuB,sCAkJzB"}
@@ -16,7 +16,7 @@ import { RAW_BUTTON } from '../lib/cn.js';
16
16
  * selects its text on mount so typing replaces (and the cursor is never stranded
17
17
  * at position 0) — or, with `caret='end'`, puts the caret after the text.
18
18
  */
19
- export function InlineEditableText({ value, onChange, canEdit = true, multiline = false, placeholder = 'Add text…', className, inputClassName, trigger = 'text', wrap = true, caret = 'select-all', maxLength = 10_000, ariaLabel, ...rest }) {
19
+ export function InlineEditableText({ value, onChange, canEdit = true, multiline = false, placeholder = 'Add text…', className, inputClassName, trigger = 'text', wrap = true, caret = 'select-all', maxLength = 10_000, ariaLabel, display, ...rest }) {
20
20
  const [editing, setEditing] = useState(false);
21
21
  const [draft, setDraft] = useState(value);
22
22
  const begin = () => {
@@ -74,13 +74,14 @@ export function InlineEditableText({ value, onChange, canEdit = true, multiline
74
74
  // Read-only with no value → render nothing.
75
75
  if (!canEdit && !value)
76
76
  return null;
77
+ const shown = value && display ? display(value) : value || placeholder;
77
78
  // Read-only: plain text.
78
79
  if (!canEdit)
79
- return _jsx("span", { ...rest, className: className, children: value });
80
+ return _jsx("span", { ...rest, className: className, children: shown });
80
81
  // Icon trigger: text is inert (rows where clicking the text means open/select);
81
82
  // the pencil is the explicit edit affordance.
82
83
  if (trigger === 'icon') {
83
- return (_jsxs("span", { ...rest, className: 'inline-flex min-w-0 items-center gap-1', children: [_jsx("span", { className: cn('truncate', !value && 'italic text-muted-foreground', className), children: value || placeholder }), _jsx(IconButton, { size: 'sm', label: ariaLabel ? `Edit ${ariaLabel}` : 'Edit', onClick: begin, children: _jsx(Pencil, {}) })] }));
84
+ return (_jsxs("span", { ...rest, className: 'inline-flex min-w-0 items-center gap-1', children: [_jsx("span", { className: cn('truncate', !value && 'italic text-muted-foreground', className), children: shown }), _jsx(IconButton, { size: 'sm', label: ariaLabel ? `Edit ${ariaLabel}` : 'Edit', onClick: begin, children: _jsx(Pencil, {}) })] }));
84
85
  }
85
86
  // Editable display: a real <button> so Enter/Space activation + focus come free,
86
87
  // styled to read as inline text.
@@ -103,6 +104,6 @@ export function InlineEditableText({ value, onChange, canEdit = true, multiline
103
104
  // measuring gate (and any tool that reasons about layout) still sees 24px.
104
105
  // `DESIGN.md` §5's footprint is the floor and UX_STANDARDS §0 records that it
105
106
  // wins over the band — so the box grows too, and the band stays as the extra.
106
- TAP_TARGET_HEIGHT, 'pointer-coarse:before:absolute pointer-coarse:before:top-[calc(var(--hit-slop-y)*-1)] pointer-coarse:before:bottom-[calc(var(--hit-slop-y)*-1)] pointer-coarse:before:-left-1 pointer-coarse:before:-right-1 pointer-coarse:before:content-[""]', wrap ? 'whitespace-pre-wrap break-words' : 'block w-full truncate', !value && 'italic text-muted-foreground', className), children: value || placeholder }) }));
107
+ TAP_TARGET_HEIGHT, 'pointer-coarse:before:absolute pointer-coarse:before:top-[calc(var(--hit-slop-y)*-1)] pointer-coarse:before:bottom-[calc(var(--hit-slop-y)*-1)] pointer-coarse:before:-left-1 pointer-coarse:before:-right-1 pointer-coarse:before:content-[""]', wrap ? 'whitespace-pre-wrap break-words' : 'block w-full truncate', !value && 'italic text-muted-foreground', className), children: shown }) }));
107
108
  }
108
109
  //# sourceMappingURL=InlineEditableText.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InlineEditableText.js","sourceRoot":"","sources":["../../../src/react/components/InlineEditableText.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"InlineEditableText.js","sourceRoot":"","sources":["../../../src/react/components/InlineEditableText.tsx"],"names":[],"mappings":"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"}
@@ -27,6 +27,17 @@
27
27
  * because a chunk genuinely missing from the deployed build would otherwise reload
28
28
  * forever with nothing able to remember trying.
29
29
  *
30
+ * 🔴 A chunk cut off by the page being LEFT is not stale. Safari (WebKit) cancels the
31
+ * old document's in-flight loads the moment a document navigation begins, right after
32
+ * `beforeunload`, and every lazy import still on its way rejects "Importing a module
33
+ * script failed." — the same words a deleted chunk gets. Reloading then sends the tab
34
+ * back to the page it was leaving: measured 2026-11-04 on roms' stage, a `goto
35
+ * /settings` 0–300 ms after the library loaded was "interrupted by another navigation
36
+ * to /" in 9 of 12 WebKit runs (forge task 4425). So a failure inside
37
+ * {@link LEAVING_GRACE_MS} of `beforeunload` waits instead: a page that goes never
38
+ * runs the timer, and one still here after it (the navigation was canceled, or was a
39
+ * download) retries the chunk once before treating it as stale.
40
+ *
30
41
  * `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
31
42
  * is refused, so the next lazy view cannot quietly reopen this.
32
43
  */
@@ -34,12 +45,20 @@ import { type ComponentType, type LazyExoticComponent } from 'react';
34
45
  export declare const STALE_CHUNK_RELOADED_AT_KEY = "cursedbelt.staleChunkReloadAt";
35
46
  /** A reload within this long of the last one is a loop, not a stale page. */
36
47
  export declare const STALE_CHUNK_RELOAD_WINDOW_MS = 60000;
48
+ /** How long after `beforeunload` a failed chunk is read as the navigation's doing, not a deploy's. */
49
+ export declare const LEAVING_GRACE_MS = 10000;
37
50
  export interface StaleChunkEnv {
38
51
  storage: Pick<Storage, 'getItem' | 'setItem'> | null;
39
52
  reload: () => void;
40
53
  now: () => number;
54
+ /** When this page last heard `beforeunload` (0: never, or it came back from the bfcache since). */
55
+ leftAt?: () => number;
56
+ /** A timer — one that never fires on a page that has gone. */
57
+ sleep?: (ms: number) => Promise<void>;
41
58
  }
42
59
  export declare function browserEnv(): StaleChunkEnv;
60
+ /** How much longer a chunk failure now is the page being left (0: it is not being left). */
61
+ export declare function leavingFor(env?: StaleChunkEnv): number;
43
62
  /**
44
63
  * Claim this tab's one stale-chunk reload for the window: true when a reload may
45
64
  * happen now, and the claim is already recorded. False when one happened inside the
@@ -1 +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"}
1
+ {"version":3,"file":"staleChunk.d.ts","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;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,sGAAsG;AACtG,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,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;IAClB,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAqBD,wBAAgB,UAAU,IAAI,aAAa,CAQ1C;AAED,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,GAAG,GAAE,aAA4B,GAAG,MAAM,CAIpE;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,CAoBlB;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"}
@@ -27,6 +27,17 @@
27
27
  * because a chunk genuinely missing from the deployed build would otherwise reload
28
28
  * forever with nothing able to remember trying.
29
29
  *
30
+ * 🔴 A chunk cut off by the page being LEFT is not stale. Safari (WebKit) cancels the
31
+ * old document's in-flight loads the moment a document navigation begins, right after
32
+ * `beforeunload`, and every lazy import still on its way rejects "Importing a module
33
+ * script failed." — the same words a deleted chunk gets. Reloading then sends the tab
34
+ * back to the page it was leaving: measured 2026-11-04 on roms' stage, a `goto
35
+ * /settings` 0–300 ms after the library loaded was "interrupted by another navigation
36
+ * to /" in 9 of 12 WebKit runs (forge task 4425). So a failure inside
37
+ * {@link LEAVING_GRACE_MS} of `beforeunload` waits instead: a page that goes never
38
+ * runs the timer, and one still here after it (the navigation was canceled, or was a
39
+ * download) retries the chunk once before treating it as stale.
40
+ *
30
41
  * `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
31
42
  * is refused, so the next lazy view cannot quietly reopen this.
32
43
  */
@@ -34,6 +45,25 @@ import { lazy } from 'react';
34
45
  export const STALE_CHUNK_RELOADED_AT_KEY = 'cursedbelt.staleChunkReloadAt';
35
46
  /** A reload within this long of the last one is a loop, not a stale page. */
36
47
  export const STALE_CHUNK_RELOAD_WINDOW_MS = 60_000;
48
+ /** How long after `beforeunload` a failed chunk is read as the navigation's doing, not a deploy's. */
49
+ export const LEAVING_GRACE_MS = 10_000;
50
+ let leftAt = 0;
51
+ let watching = false;
52
+ /** Note when the page starts being left. Installed on import, so it hears the first navigation. */
53
+ function watchLeaving() {
54
+ if (watching || typeof window === 'undefined' || typeof window.addEventListener !== 'function')
55
+ return;
56
+ watching = true;
57
+ window.addEventListener('beforeunload', () => {
58
+ leftAt = Date.now();
59
+ });
60
+ // Back from the bfcache: the navigation that was leaving it is over.
61
+ window.addEventListener('pageshow', () => {
62
+ leftAt = 0;
63
+ });
64
+ }
65
+ watchLeaving();
66
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
37
67
  export function browserEnv() {
38
68
  let storage = null;
39
69
  try {
@@ -42,7 +72,14 @@ export function browserEnv() {
42
72
  catch {
43
73
  // Storage refused (blocked site data) — no memory, so no reload: the guard fails closed.
44
74
  }
45
- return { storage, reload: () => window.location.reload(), now: Date.now };
75
+ return { storage, reload: () => window.location.reload(), now: Date.now, leftAt: () => leftAt, sleep };
76
+ }
77
+ /** How much longer a chunk failure now is the page being left (0: it is not being left). */
78
+ export function leavingFor(env = browserEnv()) {
79
+ const at = env.leftAt?.() ?? 0;
80
+ if (at <= 0)
81
+ return 0;
82
+ return Math.max(0, at + LEAVING_GRACE_MS - env.now());
46
83
  }
47
84
  /**
48
85
  * Claim this tab's one stale-chunk reload for the window: true when a reload may
@@ -81,7 +118,17 @@ export function reloadOnStaleChunk(load, env = browserEnv) {
81
118
  return await load();
82
119
  }
83
120
  catch (error) {
84
- const current = env();
121
+ let current = env();
122
+ const wait = leavingFor(current);
123
+ if (wait > 0) {
124
+ await (current.sleep ?? sleep)(wait);
125
+ try {
126
+ return await load();
127
+ }
128
+ catch {
129
+ current = env();
130
+ }
131
+ }
85
132
  if (!claimStaleChunkReload(current))
86
133
  throw error;
87
134
  current.reload();
@@ -1 +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
+ {"version":3,"file":"staleChunk.js","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt",
3
- "version": "5.113.0",
3
+ "version": "5.114.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
@@ -1,7 +1,7 @@
1
1
  import { Component, type ErrorInfo, type ReactNode, useEffect, useState } from 'react';
2
2
  import { Alert } from './Alert.js';
3
3
  import { Spinner } from './Spinner.js';
4
- import { claimStaleChunkReload, staleChunkReloadedRecently } from '../lazy/staleChunk.js';
4
+ import { claimStaleChunkReload, leavingFor, staleChunkReloadedRecently } from '../lazy/staleChunk.js';
5
5
  import { ConnectionHealer, isUnreachableError, type ServerHealOptions } from './ConnectionHealer.js';
6
6
 
7
7
  /* ───────────────────────────────────────────────────────────────────────────
@@ -191,16 +191,24 @@ export const StaleDeployHealer = ({ error, onRecover, ...rest }: StaleDeployHeal
191
191
 
192
192
  useEffect(() => {
193
193
  if (typeof window === 'undefined') return;
194
- if (!claimStaleChunkReload()) {
195
- setExhausted(true);
196
- return;
197
- }
198
- // A tick of delay so the banner paints first — an instant reload on a slow
199
- // connection reads as a flicker with no explanation.
200
- const timer = setTimeout(() => {
201
- if (onRecover) onRecover();
202
- else window.location.reload();
203
- }, 400);
194
+ let timer: ReturnType<typeof setTimeout> | undefined;
195
+ const heal = () => {
196
+ if (!claimStaleChunkReload()) {
197
+ setExhausted(true);
198
+ return;
199
+ }
200
+ // A tick of delay so the banner paints first — an instant reload on a slow
201
+ // connection reads as a flicker with no explanation.
202
+ timer = setTimeout(() => {
203
+ if (onRecover) onRecover();
204
+ else window.location.reload();
205
+ }, 400);
206
+ };
207
+ // A chunk cut off by the page being left (Safari cancels its loads at `beforeunload`) is
208
+ // not stale, and a reload would pull the tab back from where it was going — `staleChunk.ts`.
209
+ const leaving = leavingFor();
210
+ if (leaving > 0) timer = setTimeout(heal, leaving);
211
+ else heal();
204
212
  return () => clearTimeout(timer);
205
213
  }, [onRecover]);
206
214
 
@@ -83,4 +83,29 @@ describe('InlineEditableText', () => {
83
83
  expect(queryByRole('button')).toBeNull();
84
84
  expect(queryByRole('textbox')).toBeNull();
85
85
  });
86
+
87
+ test('display draws the value when not editing; the editor still edits the raw text', () => {
88
+ const table = 'Size\tTemp\nSmall\t220 C';
89
+ const grid = (v: string) => <span data-testid='grid'>{v.split('\n').length} rows</span>;
90
+ const onChange = mock(() => {});
91
+ const { getByRole, getByTestId, queryByTestId } = render(
92
+ <InlineEditableText multiline value={table} display={grid} onChange={onChange} />,
93
+ );
94
+ expect(getByTestId('grid').textContent).toBe('2 rows');
95
+ fireEvent.click(getByRole('button'));
96
+ expect(queryByTestId('grid')).toBeNull();
97
+ expect((getByRole('textbox') as HTMLTextAreaElement).value).toBe(table);
98
+ cleanup();
99
+ const locked = render(<InlineEditableText canEdit={false} value={table} display={grid} onChange={() => {}} />);
100
+ expect(locked.getByTestId('grid').textContent).toBe('2 rows');
101
+ });
102
+
103
+ test('display is never called for an empty value — the placeholder shows', () => {
104
+ const display = mock((v: string) => <span>{v}</span>);
105
+ const { getByRole } = render(
106
+ <InlineEditableText value='' placeholder='Type it' display={display} onChange={() => {}} />,
107
+ );
108
+ expect(getByRole('button').textContent).toBe('Type it');
109
+ expect(display).not.toHaveBeenCalled();
110
+ });
86
111
  });
@@ -1,5 +1,5 @@
1
1
  import { Pencil } from 'lucide-react';
2
- import { useCallback, useState } from 'react';
2
+ import { type ReactNode, useCallback, useState } from 'react';
3
3
  import { cn } from '../lib/cn.js';
4
4
  import { AutoGrowTextarea } from './AutoGrowTextarea.js';
5
5
  import { IconButton } from './IconButton.js';
@@ -45,6 +45,15 @@ export interface InlineEditableTextProps {
45
45
  * would otherwise replace everything they wrote.
46
46
  */
47
47
  caret?: 'select-all' | 'end';
48
+ /**
49
+ * How the value reads when it is NOT being edited — the click-to-edit display and the
50
+ * read-only text alike; the editor still edits the raw string. For text with structure
51
+ * the string only encodes (a read table's tab-separated rows drawn as a grid). The
52
+ * editable display is a `<button>`, so this returns phrasing content: spans, styled
53
+ * into whatever shape the text needs. Never called for an empty value — the
54
+ * placeholder shows then.
55
+ */
56
+ display?: (value: string) => ReactNode;
48
57
  /** The editor's character ceiling (default 10,000) — the server's limit, so the editor stops where a save would be refused. */
49
58
  maxLength?: number;
50
59
  ariaLabel?: string;
@@ -71,6 +80,7 @@ export function InlineEditableText({
71
80
  caret = 'select-all',
72
81
  maxLength = 10_000,
73
82
  ariaLabel,
83
+ display,
74
84
  ...rest
75
85
  }: InlineEditableTextProps) {
76
86
  const [editing, setEditing] = useState(false);
@@ -161,8 +171,10 @@ export function InlineEditableText({
161
171
  // Read-only with no value → render nothing.
162
172
  if (!canEdit && !value) return null;
163
173
 
174
+ const shown = value && display ? display(value) : value || placeholder;
175
+
164
176
  // Read-only: plain text.
165
- if (!canEdit) return <span {...rest} className={className}>{value}</span>;
177
+ if (!canEdit) return <span {...rest} className={className}>{shown}</span>;
166
178
 
167
179
  // Icon trigger: text is inert (rows where clicking the text means open/select);
168
180
  // the pencil is the explicit edit affordance.
@@ -170,7 +182,7 @@ export function InlineEditableText({
170
182
  return (
171
183
  <span {...rest} className='inline-flex min-w-0 items-center gap-1'>
172
184
  <span className={cn('truncate', !value && 'italic text-muted-foreground', className)}>
173
- {value || placeholder}
185
+ {shown}
174
186
  </span>
175
187
  <IconButton size='sm' label={ariaLabel ? `Edit ${ariaLabel}` : 'Edit'} onClick={begin}>
176
188
  <Pencil />
@@ -212,7 +224,7 @@ export function InlineEditableText({
212
224
  className,
213
225
  )}
214
226
  >
215
- {value || placeholder}
227
+ {shown}
216
228
  </button>
217
229
  </Tooltip>
218
230
  );
@@ -136,3 +136,30 @@ describe('StaleDeployHealer shares lazyWithReload\'s memory', () => {
136
136
  expect(recovered).toBe(0);
137
137
  });
138
138
  });
139
+
140
+ /**
141
+ * 🔴 Safari cancels the old page's chunks at `beforeunload`, with a stale chunk's words: a boundary
142
+ * that reloaded for it pulled the tab back from where it was going (forge task 4425).
143
+ */
144
+ describe('StaleDeployHealer leaves a page that is being left alone', () => {
145
+ afterEach(() => {
146
+ window.dispatchEvent(new Event('pageshow'));
147
+ });
148
+
149
+ test('no reload and no claim within the grace after beforeunload', async () => {
150
+ window.dispatchEvent(new Event('beforeunload'));
151
+ let recovered = 0;
152
+ render(<StaleDeployHealer error={new TypeError('Importing a module script failed.')} onRecover={() => recovered++} />);
153
+ await new Promise((r) => setTimeout(r, 800));
154
+ expect(recovered).toBe(0);
155
+ expect(window.sessionStorage.getItem(STALE_CHUNK_RELOADED_AT_KEY)).toBeNull();
156
+ });
157
+
158
+ test('back from the bfcache, it heals as before', async () => {
159
+ window.dispatchEvent(new Event('beforeunload'));
160
+ window.dispatchEvent(new Event('pageshow'));
161
+ let recovered = 0;
162
+ render(<StaleDeployHealer error={OWNERS_ERROR} onRecover={() => recovered++} />);
163
+ await waitFor(() => expect(recovered).toBe(1), { timeout: 2000 });
164
+ });
165
+ });
@@ -15,13 +15,23 @@ import {
15
15
 
16
16
  function fakeEnv(at: number) {
17
17
  const store = new Map<string, string>();
18
- const state = { reloads: 0, now: at };
18
+ const state = { reloads: 0, now: at, leftAt: 0, slept: [] as number[], wake: () => {} };
19
19
  const env: StaleChunkEnv = {
20
20
  storage: { getItem: (k) => store.get(k) ?? null, setItem: (k, v) => void store.set(k, v) },
21
21
  reload: () => {
22
22
  state.reloads += 1;
23
23
  },
24
24
  now: () => state.now,
25
+ leftAt: () => state.leftAt,
26
+ // A timer the test fires by hand — or never, as on a page that has gone.
27
+ sleep: (ms) =>
28
+ new Promise<void>((resolve) => {
29
+ state.slept.push(ms);
30
+ state.wake = () => {
31
+ state.now += ms;
32
+ resolve();
33
+ };
34
+ }),
25
35
  };
26
36
  return { env: () => env, state, store };
27
37
  }
@@ -110,6 +120,58 @@ describe('a chunk a deploy removed', () => {
110
120
  });
111
121
  });
112
122
 
123
+ /*
124
+ * 🔴 Safari cancels the old page's in-flight chunks the moment a navigation begins (right after
125
+ * `beforeunload`), with the very words a deleted chunk gets. Reloading for it pulled roms' tab back
126
+ * to the library from the /settings it was going to (forge task 4425, 9 of 12 WebKit runs).
127
+ */
128
+ describe('a chunk cut off by the page being left', () => {
129
+ /** `staleChunk.ts`'s `LEAVING_GRACE_MS`, pinned: a test of the bug must not need the fix to compile. */
130
+ const LEAVING_GRACE_MS = 10_000;
131
+ const cutOff = () => Promise.reject(new TypeError('Importing a module script failed.'));
132
+
133
+ test('never reloads, and claims no reload, while the page goes', async () => {
134
+ const { env, state, store } = fakeEnv(1_000_000);
135
+ state.leftAt = state.now - 50;
136
+ const pending = reloadOnStaleChunk(cutOff, env)();
137
+ expect(await settled(pending)).toBe(false);
138
+ expect(state.reloads).toBe(0);
139
+ expect(store.get(STALE_CHUNK_RELOADED_AT_KEY)).toBeUndefined();
140
+ expect(state.slept).toEqual([LEAVING_GRACE_MS - 50]);
141
+ });
142
+
143
+ test('a page still here after the grace retries the chunk, and a chunk that loads is the view', async () => {
144
+ const { env, state } = fakeEnv(1_000_000);
145
+ state.leftAt = state.now;
146
+ let calls = 0;
147
+ const pending = reloadOnStaleChunk(() => (++calls === 1 ? cutOff() : Promise.resolve('view')), env)();
148
+ await tick();
149
+ state.wake();
150
+ expect(await pending).toBe('view');
151
+ expect(calls).toBe(2);
152
+ expect(state.reloads).toBe(0);
153
+ });
154
+
155
+ test('a page still here whose chunk fails again is stale after all, and reloads once', async () => {
156
+ const { env, state } = fakeEnv(1_000_000);
157
+ state.leftAt = state.now;
158
+ const pending = reloadOnStaleChunk(cutOff, env)();
159
+ await tick();
160
+ state.wake();
161
+ expect(await settled(pending)).toBe(false);
162
+ expect(state.reloads).toBe(1);
163
+ });
164
+
165
+ test('a beforeunload older than the grace is no excuse: a gone chunk reloads at once', async () => {
166
+ const { env, state } = fakeEnv(1_000_000);
167
+ state.leftAt = state.now - LEAVING_GRACE_MS;
168
+ void reloadOnStaleChunk(gone, env)();
169
+ await tick();
170
+ expect(state.slept).toEqual([]);
171
+ expect(state.reloads).toBe(1);
172
+ });
173
+ });
174
+
113
175
  describe('lazyWithReload', () => {
114
176
  test('renders the view when its chunk loads', async () => {
115
177
  const { env } = fakeEnv(1_000_000);
@@ -27,6 +27,17 @@
27
27
  * because a chunk genuinely missing from the deployed build would otherwise reload
28
28
  * forever with nothing able to remember trying.
29
29
  *
30
+ * 🔴 A chunk cut off by the page being LEFT is not stale. Safari (WebKit) cancels the
31
+ * old document's in-flight loads the moment a document navigation begins, right after
32
+ * `beforeunload`, and every lazy import still on its way rejects "Importing a module
33
+ * script failed." — the same words a deleted chunk gets. Reloading then sends the tab
34
+ * back to the page it was leaving: measured 2026-11-04 on roms' stage, a `goto
35
+ * /settings` 0–300 ms after the library loaded was "interrupted by another navigation
36
+ * to /" in 9 of 12 WebKit runs (forge task 4425). So a failure inside
37
+ * {@link LEAVING_GRACE_MS} of `beforeunload` waits instead: a page that goes never
38
+ * runs the timer, and one still here after it (the navigation was canceled, or was a
39
+ * download) retries the chunk once before treating it as stale.
40
+ *
30
41
  * `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
31
42
  * is refused, so the next lazy view cannot quietly reopen this.
32
43
  */
@@ -37,11 +48,37 @@ export const STALE_CHUNK_RELOADED_AT_KEY = 'cursedbelt.staleChunkReloadAt';
37
48
  /** A reload within this long of the last one is a loop, not a stale page. */
38
49
  export const STALE_CHUNK_RELOAD_WINDOW_MS = 60_000;
39
50
 
51
+ /** How long after `beforeunload` a failed chunk is read as the navigation's doing, not a deploy's. */
52
+ export const LEAVING_GRACE_MS = 10_000;
53
+
40
54
  export interface StaleChunkEnv {
41
55
  storage: Pick<Storage, 'getItem' | 'setItem'> | null;
42
56
  reload: () => void;
43
57
  now: () => number;
58
+ /** When this page last heard `beforeunload` (0: never, or it came back from the bfcache since). */
59
+ leftAt?: () => number;
60
+ /** A timer — one that never fires on a page that has gone. */
61
+ sleep?: (ms: number) => Promise<void>;
62
+ }
63
+
64
+ let leftAt = 0;
65
+ let watching = false;
66
+
67
+ /** Note when the page starts being left. Installed on import, so it hears the first navigation. */
68
+ function watchLeaving(): void {
69
+ if (watching || typeof window === 'undefined' || typeof window.addEventListener !== 'function') return;
70
+ watching = true;
71
+ window.addEventListener('beforeunload', () => {
72
+ leftAt = Date.now();
73
+ });
74
+ // Back from the bfcache: the navigation that was leaving it is over.
75
+ window.addEventListener('pageshow', () => {
76
+ leftAt = 0;
77
+ });
44
78
  }
79
+ watchLeaving();
80
+
81
+ const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
45
82
 
46
83
  export function browserEnv(): StaleChunkEnv {
47
84
  let storage: StaleChunkEnv['storage'] = null;
@@ -50,7 +87,14 @@ export function browserEnv(): StaleChunkEnv {
50
87
  } catch {
51
88
  // Storage refused (blocked site data) — no memory, so no reload: the guard fails closed.
52
89
  }
53
- return { storage, reload: () => window.location.reload(), now: Date.now };
90
+ return { storage, reload: () => window.location.reload(), now: Date.now, leftAt: () => leftAt, sleep };
91
+ }
92
+
93
+ /** How much longer a chunk failure now is the page being left (0: it is not being left). */
94
+ export function leavingFor(env: StaleChunkEnv = browserEnv()): number {
95
+ const at = env.leftAt?.() ?? 0;
96
+ if (at <= 0) return 0;
97
+ return Math.max(0, at + LEAVING_GRACE_MS - env.now());
54
98
  }
55
99
 
56
100
  /**
@@ -90,7 +134,16 @@ export function reloadOnStaleChunk<T>(
90
134
  try {
91
135
  return await load();
92
136
  } catch (error) {
93
- const current = env();
137
+ let current = env();
138
+ const wait = leavingFor(current);
139
+ if (wait > 0) {
140
+ await (current.sleep ?? sleep)(wait);
141
+ try {
142
+ return await load();
143
+ } catch {
144
+ current = env();
145
+ }
146
+ }
94
147
  if (!claimStaleChunkReload(current)) throw error;
95
148
  current.reload();
96
149
  return new Promise<T>(() => undefined);