Ng eventually #1

Open
Sylvain wants to merge 110 commits from ng-eventually into main
2 changed files with 60 additions and 11 deletions
Showing only changes of commit 2295af610a - Show all commits
@@ -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]].
+10 -11
View File
@@ -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<string, unknown>).__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;