feat(data): reactive cross-session reads (doc_subscribe), + real 2-browser e2e
Wire the app read path to the lib's per-doc reactive subscription so a change made in ANOTHER session propagates without a reload or local action: - useNgData subscribes the by-need set via subscribeDocs(allReadDocs, bumpRead) — one doc_subscribe per NURI, per-doc error isolation (never the ORM fan-out). Any patch (own write or broker-synced from a remote peer) re-runs readUnion. - Reactive discovery: watchDiscoveredEvents(relist) subscribes the global index → a new public event from another session enters the read set (and gets its own sub). - Loop-safe: the sub effect is keyed on a stable sorted-NURI key (readDocKey); a fire→bumpRead→read never changes the doc set, so no re-subscribe loop. Identity switch empties the set → clean unsubscribe → rebuild → re-subscribe (no leak). - readUnion stays the one-shot tolerant reader; subscriptions only trigger re-reads. Real 2-browser e2e (e2e-multibrowser.feature): B registers → A's EventDetailScreen shows participantCount 1→2 and an 'unknown' participant WITHOUT A reloading, via A's doc_subscribe on the public event doc (event-driven). Isolated run 12/12 green. Count mechanism unchanged (P4/Option-B is next); the joiner still writes the public event doc's participantCount — which is exactly what the observer sees change live. Gates: @data auth 4/4, @data isolation 4/4, build + tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,6 @@ But : B s'inscrit → l'`EventDetailScreen` de A montre `participantCount` incr
|
||||
> **Hooks réactifs du SDK** (précision) : l'adaptateur React de NextGraph expose `useShape` (shapes RDF réactives) et `useDiscrete` (docs CRDT discrets) — pas de `useQuery`. La lib ré-expose `useShape`. Pour la lecture UNION de N docs (le cas de Festipod), `useShape`/l'ORM en fan-out *hangue* ; le chemin réactif app passe donc par `subscribeDocs` (par-doc) + re-`readUnion`, éventuellement enveloppé en un hook de lecture réactive côté lib (à décider en P3).
|
||||
|
||||
Autres points à trancher :
|
||||
- **Ordre de phasage :** ~~(P1) lib : `subscribeDoc` + variante multi-doc + tests D.1~~ **FAIT (`c0498a6`)** ; ~~(P2) lib : remplacer `inbox.watch`/`discovery.watchIndex` par `doc_subscribe`~~ **FAIT (`c0498a6`)** ; **(P3) app : brancher la souscription par-doc dans `useNgData` (bumpRead poussé) + découverte réactive — PROCHAIN** ; (P4) app : Option B join (retirer le write compteur de l'inscrit, matérialisation propriétaire) ; (P5) app : Option B leave symétrique ; (P6) e2e D.2. P1→P3 livrent la réactivité ; P4→P6 le compteur correct. On peut livrer P1–P3 avant P4–P6.
|
||||
- **Ordre de phasage :** ~~(P1) lib : `subscribeDoc` + variante multi-doc + tests D.1~~ **FAIT (`c0498a6`)** ; ~~(P2) lib : remplacer `inbox.watch`/`discovery.watchIndex` par `doc_subscribe`~~ **FAIT (`c0498a6`)** ; ~~(P3) app : brancher la souscription par-doc dans `useNgData` (bumpRead poussé) + découverte réactive~~ **FAIT (branche `ng-eventually`, non commité)** — `useNgData` monte un effet `subscribeDocs(allReadDocs, …)` clé sur un join trié des NURIs (`readDocKey`, anti-boucle : un patch → `bumpRead` → re-`readUnion` ne change pas le set → pas de re-souscription ; le reset d'identité `prevOwnerRef` vide le set → `readDocKey=''` → cleanup unsubscribe, puis re-listing → re-souscription sur le set reconstruit) + un effet de découverte réactive `watchDiscoveredEvents()` (wrapper app sur `discovery.watchIndex`, déjà `doc_subscribe`) → `relist()`. `readUnion` reste le lecteur one-shot tolérant. **Prouvé par l'e2e D.2** (`e2e-multibrowser.feature`, scénario « Un participant apparaît réactivement… », @multibrowser @shared-wallet, 12 steps verts en isolation) : B s'inscrit → A voit `participantCount === 2` + un participant « inconnu » **sans reload ni action**, via `doc_subscribe` sur le doc public de l'événement (le join en P3 écrit encore ce compteur, cf. §B.5 — c'est ce qui valide P3 avant P4). ; (P4) app : Option B join (retirer le write compteur de l'inscrit, matérialisation propriétaire) ; (P5) app : Option B leave symétrique ; ~~(P6) e2e D.2~~ **FAIT avec P3** (le scénario réactif ci-dessus ; la symétrie désinscription réactive reste à ajouter avec P5). P1→P3 livrent la réactivité ; P4→P6 le compteur correct. On peut livrer P1–P3 avant P4–P6.
|
||||
- **Idempotence de la matérialisation** : le `uid` par-dépôt (`RegistrationPayload.uid`, `registration.ts:56`) est le pivot ; la (référence) enregistrée par le propriétaire doit être consultée avant tout incrément/décrément pour ne jamais double-compter (rejeu de sync) ni « ressusciter » un compte.
|
||||
- **Migration inbox natif** : aujourd'hui l'inbox est émulée sur le wallet partagé (`inbox.ts` post/read RDF). À la migration vers l'inbox broker natif (`inbox_post`/`inbox_pop_for_user`, scellé), le flux Option B **reste valide** (dépôt non-membre autorisé, lecture réservée aux *readers* = propriétaire), mais le wrapper `subscribeDoc` sur l'inbox devra viser le mécanisme natif de notification de dépôt. À vérifier au moment de la migration.
|
||||
|
||||
Reference in New Issue
Block a user