Ng eventually #1

Open
Sylvain wants to merge 110 commits from ng-eventually into main
2 changed files with 39 additions and 1 deletions
Showing only changes of commit 6ceec5e161 - Show all commits
@@ -25,6 +25,12 @@ Un auto-seed se déclenche **uniquement hors production** (`process.env.NODE_ENV
`joinEvent`/`leaveEvent`/`updateEvent` **mutent directement** `ngEvent.participantCount` (`+1`/`-1`) — c'est un **cache** du nombre de `Participation`, pas une valeur recalculée. Il peut **désynchroniser** des objets `Participation` réels (ex. après un crash, un rejeu, ou la suppression partielle décrite dans [[caveat_participation-deletion]]). Ne pas s'y fier comme source de vérité du nombre de participants.
## Changement d'identité = session fraîche (isolation)
Le jeu de lecture par besoin (`publicDocs`/`protectedDocs`) **accumule** les docs de scope de l'identité courante (pour ne pas perdre un doc juste créé avant la re-liste). Or le stopgap wallet-partagé garde **un seul arbre React** au travers d'un faux-logout + re-login sous un **autre identifiant** (pas de rechargement — `AccountContext.login` ne fait que réécrire l'identifiant en localStorage, `AuthGate` ne remonte rien). Sans réinitialisation, **les docs PROTECTED de l'identité précédente (ses participations) survivent dans le jeu de lecture de la nouvelle identité et fuient** via la lecture union : le cap gate ne peut pas les filtrer quand le registre de caps (en mémoire) ne gouverne pas ce doc *cette* session (doc persisté d'un run antérieur, ou chargement frais où les caps sont vides). Symptôme observé : un utilisateur B voyait la participation de A (et l'événement de A apparaissait sur l'**accueil** de B, car l'accueil = `getUserEvents(currentUserId)`, cf. concept `app-architecture`).
**Règle** : traiter **tout changement d'identifiant** comme une session fraîche — un `useEffect([username])` (ref-gardé pour ne pas tirer au premier mount) vide `publicDocs`/`protectedDocs`, appelle `resetCaps()` + `resetRegistryCache()`, puis bump le read tick ; l'effet de listing reconstruit le jeu **borné à la nouvelle identité**. L'isolation reste par-document/émulée (concept `app-security`, [[knowledge_trust-model]]) ; ce reset ne fait que supprimer le report d'état inter-identités.
## Mutations no-op en mode local
En mode local/demo (`useLocalData`), `createEvent`/`joinEvent`/`leaveEvent`/`updateEvent` sont des **no-ops** (`console.log`, l'état ne change pas) — mais les écrans affichent quand même un **toast de succès** (« Tu participes »). UX potentiellement trompeuse : l'utilisateur croit s'être inscrit alors que rien n'a changé. Voir [[knowledge_data-modes]] pour le choix du provider selon le statut.
+33 -1
View File
@@ -29,7 +29,8 @@ import { useAccount, normalizeUsername } from './AccountContext';
// Relationship is a Festipod concept: the app keeps its own bilateral registry
// and hands the SDK only directed read grants (see shared/utils/connections).
import { declareConnections } from '../utils/connections';
import { listMyEntityDocs, createEntityDoc } from '../utils/storeRegistry';
import { listMyEntityDocs, createEntityDoc, resetRegistryCache } from '../utils/storeRegistry';
import { resetCaps } from '@ng-eventually/client/polyfill';
import { submitEventToIndex, readDiscoveredEvents } from '../data/discovery';
import { readEntities } from '../data/readEntities';
import { writeEntity, updateEntityField, ENTITY_TYPE, str, int, flt, bool, iri } from '../data/entityWrites';
@@ -253,6 +254,37 @@ function useNgData(): FestipodDataContextValue {
setReadTick(t => t + 1);
}, []);
// IDENTITY SWITCH = FRESH SESSION (isolation). The by-need read set
// (publicDocs/protectedDocs) ACCUMULATES the current identity's own scope docs
// (`listMyEntityDocs(username, …)`) so a just-created doc isn't dropped before
// the re-list. But the shared-wallet stopgap keeps ONE React tree across a faux
// logout + re-login under a DIFFERENT identifier (no page reload — see
// AuthGate/AccountContext), so without a reset the PREVIOUS identity's PROTECTED
// docs (its participations) survive in the new identity's read set and leak
// through the union read: the cap gate cannot filter them when the cap registry
// does not govern that doc THIS session (a doc persisted in a prior run, or a
// fresh load where caps are empty). Treat every identity change as a fresh
// session: drop the accumulated read set (the listing effect rebuilds it bounded
// to the NEW identity), and reset the emulated caps + registry cache so nothing
// from the old identity lingers. Ref-guarded so it fires only on a real change,
// not on the first mount (empty sets already).
const prevOwnerRef = useRef<string | null | undefined>(undefined);
useEffect(() => {
if (prevOwnerRef.current === undefined) {
prevOwnerRef.current = username;
return;
}
if (prevOwnerRef.current === username) return;
prevOwnerRef.current = username;
// Fresh session for the new identity: clear the previous identity's read set
// and the emulated isolation state, then let the listing effect rebuild.
setPublicDocs([]);
setProtectedDocs([]);
resetCaps();
resetRegistryCache();
setReadTick(t => t + 1);
}, [username]);
// Resolve the by-need doc NURIs — READ BY NEED, never an all-accounts fan-out
// (the OLD `listEntityDocs('public'|'protected')` enumerated EVERY account and
// tried to open/sync other accounts' unsynced docs → HANG ~75s; see