cursedbelt 5.12.0 → 5.13.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.
@@ -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
- /** Whether this tab has already tried reloading for this URL. Never throws. */
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;AAGvF,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;AAMD,+EAA+E;AAC/E,wBAAgB,yBAAyB,IAAI,OAAO,CAQnD;AAUD,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,gCAsC7E,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 URL and explains itself if that does not help.
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
- * URL. The second failure stops and says what is actually wrong, which is a
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
- /** One key per tab per URL — a reload that did not help must not be repeated. */
106
- const attemptKey = () => `cwip.staleChunkReload:${typeof window === 'undefined' ? '' : window.location.pathname}`;
107
- /** Whether this tab has already tried reloading for this URL. Never throws. */
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
- try {
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 (staleChunkReloadAttempted()) {
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,kEAAkE;QAClE,IAAI,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,KAAC,iBAAiB,IAAC,KAAK,EAAE,KAAK,GAAI,CAAC;QACzE,IAAI,QAAQ,IAAI,kBAAkB,CAAC,KAAK,CAAC;YACvC,OAAO,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,KAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAI,CAAC;QACrF,IAAI,OAAO,QAAQ,KAAK,UAAU;YAAE,OAAO,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC;QAE5C,OAAO,CACL,KAAC,KAAK,IAAC,IAAI,EAAC,OAAO,EAAC,KAAK,EAAC,sBAAsB,YAC7C,KAAK,CAAC,OAAO,GACR,CACT,CAAC;IACJ,CAAC;CACF;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,oBAAoB,GAAG;IAC3B,6CAA6C,EAAE,WAAW;IAC1D,2CAA2C,EAAE,UAAU;IACvD,kCAAkC,EAAE,SAAS;IAC7C,uBAAuB,EAAE,wBAAwB;IACjD,8BAA8B,EAAE,4CAA4C;CAC7E,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IAChC,MAAM,OAAO,GAAG,CACd,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAC1E,CAAC,WAAW,EAAE,CAAC;IAChB,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,GAAG,GAAW,EAAE,CAC9B,yBAAyB,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;AAE3F,+EAA+E;AAC/E,MAAM,UAAU,yBAAyB;IACvC,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;QAC7E,wEAAwE;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB;IAC3B,IAAI,CAAC;QACH,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,gFAAgF;IAClF,CAAC;AACH,CAAC;AASD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,EAAE,KAAK,EAAE,SAAS,EAA0B,EAAE,EAAE;IAChF,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAC1C,IAAI,yBAAyB,EAAE,EAAE,CAAC;YAChC,YAAY,CAAC,IAAI,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QACD,oBAAoB,EAAE,CAAC;QACvB,2EAA2E;QAC3E,qDAAqD;QACrD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,SAAS;gBAAE,SAAS,EAAE,CAAC;;gBACtB,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAChC,CAAC,EAAE,GAAG,CAAC,CAAC;QACR,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,IAAI,SAAS;QACX,OAAO,CACL,MAAC,KAAK,IAAC,IAAI,EAAC,OAAO,EAAC,KAAK,EAAC,+BAA+B,aACvD,yJAGM,EACL,KAAK,CAAC,CAAC,CAAC,cAAK,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,YAAG,KAAK,CAAC,OAAO,GAAO,CAAC,CAAC,CAAC,IAAI,IAC3E,CACT,CAAC;IAEJ,OAAO,CACL,KAAC,KAAK,IAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,4BAA4B,YACnD,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,aAC3D,KAAC,OAAO,IAAC,IAAI,EAAC,IAAI,GAAG,EACrB,+FAAyE,IACrE,GACA,CACT,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../../src/react/components/ErrorBoundary.tsx"],"names":[],"mappings":"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"}
@@ -17,7 +17,15 @@
17
17
  * boundary, rather than reloading in a loop.
18
18
  *
19
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.
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.
21
29
  *
22
30
  * `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
23
31
  * is refused, so the next lazy view cannot quietly reopen this.
@@ -31,6 +39,15 @@ export interface StaleChunkEnv {
31
39
  reload: () => void;
32
40
  now: () => number;
33
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;
34
51
  /** Wrap a dynamic `import()` so a chunk a deploy removed reloads the page instead of failing. */
35
52
  export declare function reloadOnStaleChunk<T>(load: () => Promise<T>, env?: () => StaleChunkEnv): () => Promise<T>;
36
53
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"staleChunk.d.ts","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;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;AAYD,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,CAsBlB;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
@@ -17,7 +17,15 @@
17
17
  * boundary, rather than reloading in a loop.
18
18
  *
19
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.
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.
21
29
  *
22
30
  * `guardrailsEnforce`'s `no-raw-lazy` is the WHETHER: a bare `React.lazy` in an app
23
31
  * is refused, so the next lazy view cannot quietly reopen this.
@@ -26,16 +34,46 @@ import { lazy } from 'react';
26
34
  export const STALE_CHUNK_RELOADED_AT_KEY = 'cursedbelt.staleChunkReloadAt';
27
35
  /** A reload within this long of the last one is a loop, not a stale page. */
28
36
  export const STALE_CHUNK_RELOAD_WINDOW_MS = 60_000;
29
- function browserEnv() {
37
+ export function browserEnv() {
30
38
  let storage = null;
31
39
  try {
32
40
  storage = window.sessionStorage;
33
41
  }
34
42
  catch {
35
- // Storage refused (private mode, blocked site data) — reload without the guard's memory.
43
+ // Storage refused (blocked site data) — no memory, so no reload: the guard fails closed.
36
44
  }
37
45
  return { storage, reload: () => window.location.reload(), now: Date.now };
38
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
+ }
39
77
  /** Wrap a dynamic `import()` so a chunk a deploy removed reloads the page instead of failing. */
40
78
  export function reloadOnStaleChunk(load, env = browserEnv) {
41
79
  return async () => {
@@ -43,23 +81,10 @@ export function reloadOnStaleChunk(load, env = browserEnv) {
43
81
  return await load();
44
82
  }
45
83
  catch (error) {
46
- const { storage, reload, now } = env();
47
- let last = 0;
48
- try {
49
- last = Number(storage?.getItem(STALE_CHUNK_RELOADED_AT_KEY) ?? 0) || 0;
50
- }
51
- catch {
52
- // Unreadable memory is treated as no reload yet.
53
- }
54
- if (last > 0 && now() - last < STALE_CHUNK_RELOAD_WINDOW_MS)
84
+ const current = env();
85
+ if (!claimStaleChunkReload(current))
55
86
  throw error;
56
- try {
57
- storage?.setItem(STALE_CHUNK_RELOADED_AT_KEY, String(now()));
58
- }
59
- catch {
60
- // Unwritable: the reload still happens; the guard just cannot remember it.
61
- }
62
- reload();
87
+ current.reload();
63
88
  return new Promise(() => undefined);
64
89
  }
65
90
  };
@@ -1 +1 @@
1
- {"version":3,"file":"staleChunk.js","sourceRoot":"","sources":["../../../src/react/lazy/staleChunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;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,SAAS,UAAU;IACjB,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,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,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;YACvC,IAAI,IAAI,GAAG,CAAC,CAAC;YACb,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,2BAA2B,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;YACzE,CAAC;YAAC,MAAM,CAAC;gBACP,iDAAiD;YACnD,CAAC;YACD,IAAI,IAAI,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,4BAA4B;gBAAE,MAAM,KAAK,CAAC;YACzE,IAAI,CAAC;gBACH,OAAO,EAAE,OAAO,CAAC,2BAA2B,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC/D,CAAC;YAAC,MAAM,CAAC;gBACP,2EAA2E;YAC7E,CAAC;YACD,MAAM,EAAE,CAAC;YACT,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":"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"}
@@ -17,6 +17,13 @@ interface Rule {
17
17
  export declare function walk(dir: string, out?: string[]): string[];
18
18
  export declare function setReimplementedNames(names: readonly string[]): void;
19
19
  export declare const RULES: Record<string, Rule>;
20
+ /**
21
+ * Rules that are about cursedbelt's OWN source and mean nothing in an app's.
22
+ * `control-height-tokens` reads a fixed list of the belt's control-class files.
23
+ */
24
+ export declare const BELT_INTERNAL_RULES: ReadonlySet<string>;
25
+ /** What `--all-app-rules` scans: every rule an app's source can break. */
26
+ export declare const APP_RULES: readonly string[];
20
27
  export declare const ALLOWED_FILE_CATEGORIES: Set<string>;
21
28
  export interface FileAllow {
22
29
  rules: Set<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";AAuMA,UAAU,SAAS;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;sEAGkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,IAAI;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;CACzD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA6B9D;AA66BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAEpE;AA+mBD,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAqBtC,CAAC;AASF,eAAO,MAAM,uBAAuB,aAAkD,CAAC;AAGvF,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AAED;2FAC2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA0C3F;AAED,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAmGhC,wBAAgB,0BAA0B,CAAC,SAAS,GAAE,MAAwB,GAAG,MAAM,EAAE,CAyBxF"}
1
+ {"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";AA+MA,UAAU,SAAS;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;sEAGkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,IAAI;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;CACzD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA6B9D;AA66BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAEpE;AA+mBD,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAqBtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAuC,CAAC;AAE5F,0EAA0E;AAC1E,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAAwE,CAAC;AAShH,eAAO,MAAM,uBAAuB,aAAkD,CAAC;AAGvF,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AAED;2FAC2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA0C3F;AAED,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAmGhC,wBAAgB,0BAA0B,CAAC,SAAS,GAAE,MAAwB,GAAG,MAAM,EAAE,CAyBxF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt",
3
- "version": "5.12.0",
3
+ "version": "5.13.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.",
@@ -26,6 +26,21 @@ import { compileStylesheet } from './generateUtilityStyles';
26
26
  */
27
27
  const noRepoEnv = (dir: string) => ({ ...process.env, GIT_CEILING_DIRECTORIES: dirname(dir) });
28
28
 
29
+ /** `Bun.spawnSync`-shaped, kept async — bun 1.4.2 can lose a child's exit under `bun test --parallel`
30
+ * when the wait is synchronous; the async form fails at the test's timeout instead of hanging the worker. */
31
+ async function spawnAsync(
32
+ argv: string[],
33
+ opts: { cwd?: string; env?: Record<string, string> } = {},
34
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
35
+ const child = Bun.spawn(argv, { cwd: opts.cwd, env: opts.env, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' });
36
+ const [stdout, stderr, exitCode] = await Promise.all([
37
+ new Response(child.stdout).text(),
38
+ new Response(child.stderr).text(),
39
+ child.exited,
40
+ ]);
41
+ return { exitCode, stdout, stderr };
42
+ }
43
+
29
44
  const BELT = {
30
45
  union: `@layer utilities { .flex { display: flex } .rounded-full { border-radius: 9999px } .hover\\:bg-muted { &:hover { @media (hover: hover) { background: var(--muted) } } } .recharts-tip { opacity: .5; } }`,
31
46
  areas: {
@@ -89,23 +104,23 @@ describe('checkAreaStyles', () => {
89
104
  expect([...tokensInJs(`a="[&>*]:w-full p-2" b='x'`)]).toContain('[&>*]:w-full');
90
105
  });
91
106
 
92
- test('the CLI exits 2 on a directory with nothing to compare, 1 on a miss, 0 when complete', () => {
107
+ test('the CLI exits 2 on a directory with nothing to compare, 1 on a miss, 0 when complete', async () => {
93
108
  const dir = mkdtempSync(join(tmpdir(), 'belt-area-check-'));
94
109
  try {
95
110
  const run = () =>
96
- Bun.spawnSync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), dir], { cwd: dir, env: noRepoEnv(dir), stdout: 'pipe', stderr: 'pipe' });
97
- expect(run().exitCode).toBe(2);
111
+ spawnAsync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), dir], { cwd: dir, env: noRepoEnv(dir) });
112
+ expect((await run()).exitCode).toBe(2);
98
113
  const { union } = readBeltSheets();
99
114
  // `truncate` is a core utility in every build of the union.
100
115
  expect(classesInCss(union).has('truncate')).toBe(true);
101
116
  mkdirSync(join(dir, 'assets'));
102
117
  writeFileSync(join(dir, 'assets', 'index.js'), 'const c="truncate";');
103
118
  writeFileSync(join(dir, 'assets', 'index.css'), '.app{color:red}');
104
- const miss = run();
119
+ const miss = await run();
105
120
  expect(miss.exitCode).toBe(1);
106
- expect(miss.stderr.toString()).toContain('truncate');
121
+ expect(miss.stderr).toContain('truncate');
107
122
  writeFileSync(join(dir, 'assets', 'index.css'), '.truncate{overflow:hidden}');
108
- expect(run().exitCode).toBe(0);
123
+ expect((await run()).exitCode).toBe(0);
109
124
  } finally {
110
125
  rmSync(dir, { recursive: true, force: true });
111
126
  }
@@ -239,15 +254,15 @@ describe('🔴 the ORDER check — a base utility after a variant that sets the
239
254
  expect(hasVariantPrefix('text-[color:var(--x)]')).toBe(false);
240
255
  });
241
256
 
242
- test('the CLI exits 1 on an out-of-order build even when every class is styled', () => {
257
+ test('the CLI exits 1 on an out-of-order build even when every class is styled', async () => {
243
258
  const dir = mkdtempSync(join(tmpdir(), 'belt-order-check-'));
244
259
  try {
245
260
  mkdirSync(join(dir, 'assets'));
246
261
  writeFileSync(join(dir, 'assets', 'index.js'), 'const c="truncate";');
247
262
  writeFileSync(join(dir, 'assets', 'index.css'), '@layer utilities{@media(min-width:40rem){.sm\\:flex{display:flex}}.truncate{overflow:hidden}.hidden{display:none}}');
248
- const r = Bun.spawnSync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), dir], { cwd: dir, env: noRepoEnv(dir), stdout: 'pipe', stderr: 'pipe' });
263
+ const r = await spawnAsync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), dir], { cwd: dir, env: noRepoEnv(dir) });
249
264
  expect(r.exitCode).toBe(1);
250
- expect(r.stderr.toString()).toContain('.hidden (later) overrides .sm:flex');
265
+ expect(r.stderr).toContain('.hidden (later) overrides .sm:flex');
251
266
  } finally {
252
267
  rmSync(dir, { recursive: true, force: true });
253
268
  }
@@ -301,16 +316,16 @@ describe('🔴 the THEME VARIABLE check — a `var()` of a theme variable no bui
301
316
  expect([...variablesDeclared('@property --tw-x{syntax:"*"}.a{--b:1;color:var(--c)}')].sort()).toEqual(['--b', '--tw-x']);
302
317
  });
303
318
 
304
- test('the CLI exits 1 on a build that reads an undeclared theme variable, naming it', () => {
319
+ test('the CLI exits 1 on a build that reads an undeclared theme variable, naming it', async () => {
305
320
  const dir = mkdtempSync(join(tmpdir(), 'belt-theme-var-check-'));
306
321
  try {
307
322
  mkdirSync(join(dir, 'assets'));
308
323
  writeFileSync(join(dir, 'assets', 'index.js'), `const c="truncate";${STATION_JS}`);
309
324
  writeFileSync(join(dir, 'assets', 'index.css'), '@layer theme{:root{--color-muted:var(--muted);--color-accent:var(--accent)}}@layer utilities{.truncate{overflow:hidden}}');
310
- const r = Bun.spawnSync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), dir], { cwd: dir, env: noRepoEnv(dir), stdout: 'pipe', stderr: 'pipe' });
325
+ const r = await spawnAsync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), dir], { cwd: dir, env: noRepoEnv(dir) });
311
326
  expect(r.exitCode).toBe(1);
312
- expect(r.stderr.toString()).toContain('--color-card');
313
- expect(r.stderr.toString()).not.toContain('--color-muted');
327
+ expect(r.stderr).toContain('--color-card');
328
+ expect(r.stderr).not.toContain('--color-muted');
314
329
  } finally {
315
330
  rmSync(dir, { recursive: true, force: true });
316
331
  }
@@ -319,7 +334,7 @@ describe('🔴 the THEME VARIABLE check — a `var()` of a theme variable no bui
319
334
 
320
335
  describe('🔴 the SCAN-LEAK check — a scanned file ignored only by the global gitignore (5.3.1, family 0a298a5)', () => {
321
336
  /** A git repo whose "machine-global" excludes file is a temp file — the real ~/.gitignore_global is never read. */
322
- function fixture() {
337
+ async function fixture() {
323
338
  const base = mkdtempSync(join(tmpdir(), 'belt-scan-leak-'));
324
339
  const repo = join(base, 'app');
325
340
  mkdirSync(repo);
@@ -328,7 +343,7 @@ describe('🔴 the SCAN-LEAK check — a scanned file ignored only by the global
328
343
  const gitconfig = join(base, 'gitconfig');
329
344
  writeFileSync(gitconfig, `[core]\n\texcludesFile = ${globalIgnore}\n`);
330
345
  const env = { ...process.env, GIT_CONFIG_GLOBAL: gitconfig, GIT_CONFIG_NOSYSTEM: '1' };
331
- expect(Bun.spawnSync(['git', 'init', '-q', repo], { env }).exitCode).toBe(0);
346
+ expect((await spawnAsync(['git', 'init', '-q', repo], { env })).exitCode).toBe(0);
332
347
  writeFileSync(join(repo, '.gitignore'), 'dist/\n');
333
348
  mkdirSync(join(repo, 'src'));
334
349
  writeFileSync(join(repo, 'src', 'App.tsx'), 'export const A = () => <div className="flex" />;');
@@ -346,8 +361,8 @@ describe('🔴 the SCAN-LEAK check — a scanned file ignored only by the global
346
361
  return { base, repo, env, globalIgnore };
347
362
  }
348
363
 
349
- test('RED: each globally-ignored TEXT path is named with the file and pattern that matched', () => {
350
- const { base, repo, env, globalIgnore } = fixture();
364
+ test('RED: each globally-ignored TEXT path is named with the file and pattern that matched', async () => {
365
+ const { base, repo, env, globalIgnore } = await fixture();
351
366
  try {
352
367
  const leaks = scanLeaks(repo, env);
353
368
  expect(leaks?.map((l) => l.path)).toEqual(['.agent.noindex/', 'src/probe.agent.tsx']);
@@ -362,8 +377,8 @@ describe('🔴 the SCAN-LEAK check — a scanned file ignored only by the global
362
377
  }
363
378
  });
364
379
 
365
- test("GREEN once the repo's OWN .gitignore names them — the global file still matches, but no longer decides", () => {
366
- const { base, repo, env } = fixture();
380
+ test("GREEN once the repo's OWN .gitignore names them — the global file still matches, but no longer decides", async () => {
381
+ const { base, repo, env } = await fixture();
367
382
  try {
368
383
  writeFileSync(join(repo, '.gitignore'), 'dist/\n.agent.noindex/\n*.agent.*\n');
369
384
  expect(scanLeaks(repo, env)).toEqual([]);
@@ -372,8 +387,8 @@ describe('🔴 the SCAN-LEAK check — a scanned file ignored only by the global
372
387
  }
373
388
  });
374
389
 
375
- test('.git/info/exclude is not the repo\'s .gitignore either — RED', () => {
376
- const { base, repo, env } = fixture();
390
+ test('.git/info/exclude is not the repo\'s .gitignore either — RED', async () => {
391
+ const { base, repo, env } = await fixture();
377
392
  try {
378
393
  writeFileSync(join(repo, '.gitignore'), 'dist/\n.agent.noindex/\n*.agent.*\n');
379
394
  writeFileSync(join(repo, 'notes.md'), 'bg-primary');
@@ -384,8 +399,8 @@ describe('🔴 the SCAN-LEAK check — a scanned file ignored only by the global
384
399
  }
385
400
  });
386
401
 
387
- test('an app root that is itself git-ignored cannot be measured — it throws (the CLI exits 2)', () => {
388
- const { base, repo, env } = fixture();
402
+ test('an app root that is itself git-ignored cannot be measured — it throws (the CLI exits 2)', async () => {
403
+ const { base, repo, env } = await fixture();
389
404
  try {
390
405
  expect(() => scanLeaks(join(repo, '.agent.noindex'), env)).toThrow(/itself git-ignored/);
391
406
  } finally {
@@ -402,27 +417,25 @@ describe('🔴 the SCAN-LEAK check — a scanned file ignored only by the global
402
417
  }
403
418
  });
404
419
 
405
- test('the CLI exits 1 on a leak even when every class is styled, and 0 once the repo ignores it', () => {
406
- const { base, repo, env } = fixture();
420
+ test('the CLI exits 1 on a leak even when every class is styled, and 0 once the repo ignores it', async () => {
421
+ const { base, repo, env } = await fixture();
407
422
  try {
408
423
  writeFileSync(join(repo, 'dist', 'index.css'), '.truncate{overflow:hidden}');
409
424
  const run = () =>
410
- Bun.spawnSync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), 'dist'], { cwd: repo, env, stdout: 'pipe', stderr: 'pipe' });
411
- const red = run();
425
+ spawnAsync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), 'dist'], { cwd: repo, env });
426
+ const red = await run();
412
427
  expect(red.exitCode).toBe(1);
413
- expect(red.stderr.toString()).toContain('.agent.noindex/');
414
- expect(red.stderr.toString()).toContain('src/probe.agent.tsx');
428
+ expect(red.stderr).toContain('.agent.noindex/');
429
+ expect(red.stderr).toContain('src/probe.agent.tsx');
415
430
  // --root reads the same repo from anywhere.
416
- const elsewhere = Bun.spawnSync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), join(repo, 'dist'), '--root', repo], {
431
+ const elsewhere = await spawnAsync(['bun', join(import.meta.dir, 'checkAreaStyles.ts'), join(repo, 'dist'), '--root', repo], {
417
432
  cwd: base,
418
433
  env,
419
- stdout: 'pipe',
420
- stderr: 'pipe',
421
434
  });
422
435
  expect(elsewhere.exitCode).toBe(1);
423
436
  writeFileSync(join(repo, '.gitignore'), 'dist/\n.agent.noindex/\n*.agent.*\n');
424
- const green = run();
425
- expect(green.stderr.toString()).toBe('');
437
+ const green = await run();
438
+ expect(green.stderr).toBe('');
426
439
  expect(green.exitCode).toBe(0);
427
440
  } finally {
428
441
  rmSync(base, { recursive: true, force: true });