/** * Live-client detection — "is this a live client (vs. a server render)?" * * The async layer must not auto-run fetchers during a server render (the * server walk installs its own `_useAsync` provider and serializes results * instead). Historically that gate was `typeof window !== 'undefined'`, * which really tests "is this a browser" — wrong for non-web renderers * (signalxjs/lynx, signalxjs/terminal): their runtimes have no `window`, * yet they ARE live clients and their reads must fetch. * * Non-web platform-identity modules call `declareLiveClient()` once on * import; the browser check remains the fallback so web behavior is * unchanged without any declaration. * * ⚠️ `@sigx/runtime-dom/platform` must NOT call `declareLiveClient()`: * the `sigx` umbrella imports it unconditionally, so server-side SSR code * evaluates it too — declaring there would defeat the server guard. Web * relies on the window fallback; only genuinely windowless clients declare. * * A declaration also stamps `globalThis.__SIGX_LIVE_CLIENT__` (rfc-server * rev 2, N.2): `@sigx/server` reads that global — never this module — so its * real `serverFn` wrapper can refuse to execute server bodies that leaked * into a live client's bundle. The `typeof window` fallback deliberately * does NOT stamp: web SSR evaluates this module too, and a stamp there * would trip the server-side guard. */ let declared: boolean | null = null; /** * Declare this runtime a live client (or explicitly not one). Called by * non-web platform-identity modules on import — never by app code. */ export function declareLiveClient(live = true): void { declared = live; // `boolean`, not `true`: an explicit `declareLiveClient(false)` stamp is // a legal not-live override — docs/seams.md carries the contract. // // `defineProperty` so the seam is NON-ENUMERABLE: it is pack-internal // wiring, not a page payload, so it does not belong in // `Object.keys(globalThis)`. A platform module that assigns the name // directly still works and inherits the descriptor. // // `enumerable` is spelled even though `false` is the default for a NEW // property: an embedder — or an older copy of this module — may have // created it by plain assignment, and a partial descriptor would preserve // that property's `enumerable: true`. Object.defineProperty(globalThis, '__SIGX_LIVE_CLIENT__', { value: live, writable: true, configurable: true, enumerable: false }); } /** Declaration wins; `typeof window` is the fallback. */ export function isLiveClient(): boolean { return declared ?? typeof window !== 'undefined'; }