diff --git a/.project/concepts/data-layer/rule_app-uses-sdk-surface-only.md b/.project/concepts/data-layer/rule_app-uses-sdk-surface-only.md new file mode 100644 index 0000000..b6c6f08 --- /dev/null +++ b/.project/concepts/data-layer/rule_app-uses-sdk-surface-only.md @@ -0,0 +1,50 @@ +--- +type: rule +summary: L'app se comporte EXACTEMENT comme si NextGraph était fini et sans défaut — elle ne consomme QUE des surfaces SDK-shaped (`useShape`, `docs`, `inbox`…) et ne raisonne JAMAIS sur un problème courant de NextGraph (hang du fan-out ORM, cold-open, etc.). La raison d'être du polyfill est le WALLET VIRTUEL ; tout contournement interne (read-model union, subscribeDoc, open-repo…) vit DANS le polyfill, invisible à l'app. +--- + +# L'app n'utilise que la surface SDK — jamais les internes du polyfill + +## La règle + +L'app Festipod traite `@ng-eventually/client` comme un **SDK NextGraph fini et sans +défaut**. Concrètement : + +1. **Lecture réactive = `useShape`** (la surface SDK-shaped, fournie par le polyfill, + **scopée au wallet virtuel**). L'app ne lit PAS via des internes du polyfill + (`readModel.readUnion`, `subscribeDoc`, un read-model maison…), et ne monte PAS sa + propre réactivité (re-run sur signal). +2. **L'app ne raisonne JAMAIS sur l'état courant de NextGraph** : pas de code ni de + commentaire du type « on fait X parce que le fan-out ORM hang / parce que la lecture + à froid rend 0 ». Ces problèmes n'existent pas du point de vue de l'app. + +## La raison d'être du polyfill = le WALLET VIRTUEL + +Le polyfill existe pour émuler le **wallet virtuel** (plusieurs identités sur un seul +wallet physique), que NextGraph ne fournit pas encore nativement. **Ce n'est PAS** +« parce que le fan-out ORM hang » — ça, c'est un simple **détail d'implémentation +interne** de la façon dont le polyfill livre un `useShape` fonctionnel. Tous les +contournements (read-model union à la place du fan-out ORM, `open-repo`, readiness +miroir de `readyPromise`, émulation de caps…) sont **internes au polyfill** et +n'apparaissent jamais dans l'app. + +## Déviation actuelle (dette à corriger) + +`src/shared/data/readEntities.ts` + `FestipodDataContext` lisent via +`readModel.readUnion` + `subscribeDocs` + `bumpRead`, avec un commentaire qui explique +que ça « remplace le fan-out `useShape` qui hang ». C'est la fuite exacte que cette +règle interdit. + +**Cible** : le polyfill expose un `useShape` **réactif, scopé au wallet virtuel**, dont +la **forme suit TanStack `useQuery`** — `{ data, isPending/isLoading, isSuccess, isError, +… }` — **en anticipation de la mise à jour PRÉVUE de `useShape` par NextGraph** (qui va +adopter ce fonctionnement). Ce n'est donc pas une invention : c'est une API future de +NextGraph, émulée d'avance, qui s'aligne quand NextGraph la livre. Elle **distingue +nativement** `isPending` (sync en cours) de `isSuccess` + `data` vide (synchronisé, +réellement vide) — exactement le besoin. En interne, le hook encapsule readUnion sur +`subscribeDoc` + le scoping identité (invisible à l'app). L'app **supprime** sa +machinerie bespoke (`readEntities`/`subscribeDocs`/`bumpRead`) et lit via ce hook. + +Le bug d'auto-seed (chronomètre 3 s) est un **symptôme** : avec `isSuccess`, l'auto-seed +décide « vide » seulement une fois la sync confirmée, au lieu de deviner un délai. Voir +[[rule_no-broker-polling]] et [[knowledge_nextgraph-stack]]. diff --git a/src/shared/utils/ngSession.ts b/src/shared/utils/ngSession.ts index 2243c62..9bd7f36 100644 --- a/src/shared/utils/ngSession.ts +++ b/src/shared/utils/ngSession.ts @@ -10,25 +10,24 @@ import { configure } from "@ng-eventually/client/polyfill"; // SDK-shaped surface used by ngSession itself — taken from the lib, not @ng-org. import { ng, init as initNgWeb, initNg as initNgSignals } from "@ng-eventually/client"; -// DIAGNOSTIC (shared-wallet isolation): turn on the SDK's OFF-by-default access -// log to SEE every read/write prefixed by the active identity — the way to catch -// a doc read under the wrong identity in the REAL app (the e2e harness can't -// reproduce it — cross-invocation reads return nothing there). Runtime toggle, no -// rebuild: in the browser console run -// localStorage.setItem('festipod.debug.accessLog','1') // then reload -// (unset / '0' turns it off). Also honoured: window.__FESTIPOD_ACCESS_LOG__. +// DIAGNOSTIC (shared-wallet isolation): the SDK access log SEES every read/write +// prefixed by the active identity — the way to catch a doc read under the wrong +// identity in the REAL app. Now ON BY DEFAULT (the opt-in toggle was fragile / +// easy to miss). To silence: in the browser console run +// localStorage.setItem('festipod.debug.accessLog','0') // then reload function accessLogEnabled(): boolean { try { if (typeof localStorage !== "undefined" - && localStorage.getItem("festipod.debug.accessLog") === "1") return true; + && localStorage.getItem("festipod.debug.accessLog") === "0") return false; } catch { /* storage may be unavailable */ } - return typeof window !== "undefined" - && (window as unknown as Record).__FESTIPOD_ACCESS_LOG__ === true; + return true; } +const __accessLogOn = accessLogEnabled(); +console.log("[NG session] access-log:", __accessLogOn ? "ON" : "OFF (localStorage festipod.debug.accessLog=0)"); configure({ ng: realNg, useShape: realUseShape, init: realInit, initNg: realInitNg, - debugAccessLog: accessLogEnabled(), + debugAccessLog: __accessLogOn, }); export let session: NextGraphSession | undefined;