Ng eventually #1
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user