diff --git a/.project/concepts/data-layer/knowledge_context-internals.md b/.project/concepts/data-layer/knowledge_context-internals.md index 63913fa..b0474a5 100644 --- a/.project/concepts/data-layer/knowledge_context-internals.md +++ b/.project/concepts/data-layer/knowledge_context-internals.md @@ -14,12 +14,35 @@ En mode connected, le **principal** du currentUser (`currentUserId`) n'est **pas - L'objet `currentUser` (le profil affiché) est, lui, résolu par `users.find(u => normalizeUsername(u.username) === identifiant)` avec **fallback** `@mariedupont` puis `users[0]` — un fallback silencieux si l'identifiant ne correspond à aucun profil (l'identifiant est un id d'espace, pas forcément le `username` d'un profil seedé). - Sans identifiant connecté (dev/demo), `currentUserId` retombe sur l'IRI du profil lu (ou `''` si le wallet est vide → `Participation` avec `user: ''` invalide) : ne créer une participation qu'une fois le principal résolu. +## Lecture = `watchShape` (surface SDK), plus de machinerie bespoke + +**Depuis 2026-07-10** : `useNgData` lit via `useShapeQuery(shape, scope)` (binding +`useSyncExternalStore` sur `watchShape` du polyfill) — TROIS lectures useQuery-shaped +(events/public, users/protected, participations/protected) + adaptateurs Fp +(`shapeAdapters.ts`). Supprimés : `readEntities`, `subscribeDocs`+`bumpRead`+`readTick`, +le listing manuel (`publicDocs`/`protectedDocs`/`registerDoc` pour la lecture), +`relist`. `ready` = combinaison des `isSuccess`. Cf. [[rule_app-uses-sdk-surface-only]]. + +**Visibilité immédiate des mutations = overlay OPTIMISTE** (pas de `registerDoc`) : +`createEvent`/`joinEvent`/`leaveEvent` alimentent `pendingAddEvents`/ +`pendingAddParticipations`/`pendingRemoveIds` ; l'état exposé = merge(réactif, adds) +moins removes, dédupé par id (id = NURI du doc). Réconciliation auto au push +(un add qui apparaît dans le réactif / un remove qui en disparaît est retiré) — +jamais de poll ([[rule_no-broker-polling]]). Vidé au changement d'identité. + ## Auto-seed de dev -Un auto-seed se déclenche **uniquement hors production** (`process.env.NODE_ENV !== 'production'`), après un `setTimeout` de ~3s, si events ET users sont vides. Pièges : -- **Un seul seed à la fois** : `loadTestData()` pose `hasTriedAutoSeed` et le callback de l'auto-seed le re-teste, donc un chargement explicite **supprime** l'auto-seed en attente (sinon deux `bootstrapWallet` concurrents écrivent en double). Un signal de re-liste (`relist`) fait entrer les docs fraîchement seedés dans le jeu de lecture. -- Le seed est **possédé par l'identité courante** (`bootstrapWallet(…, owner)`), pas par un propriétaire fixe : les entités protégées seedées (profils) passent ainsi le cap de lecture par-document du propriétaire (sinon elles seraient masquées et jamais relues). -- **Pas de retry** au-delà : si le seed échoue, écran vide + `console.error`. Le délai de 3s reste heuristique. +Un auto-seed se déclenche **uniquement hors production** (`NODE_ENV !== 'production'`), +si events ET users sont vides — **gardé sur `isSuccess`** (la readiness de `watchShape`), +PLUS sur un `setTimeout` de 3s : on ne décide « wallet vide » qu'une fois la sync +**confirmée** (`isSuccess`), sinon la lecture pas-encore-finie était prise pour un +wallet vide → re-seed à chaque reconnexion (bug corrigé). Pièges restants : +- **Un seul seed à la fois** : `loadTestData()` pose `hasTriedAutoSeed`, l'auto-seed le + re-teste → un chargement explicite supprime l'auto-seed en attente (sinon deux + `bootstrapWallet` concurrents écrivent en double). +- Le seed est **possédé par l'identité courante** (`bootstrapWallet(…, owner)`) : les + entités protégées seedées passent le cap de lecture par-document du propriétaire. +- **Pas de retry** : si le seed échoue, écran vide + `console.error`. ## `participantCount` — dérivé et possédé par le propriétaire (Option B)