docs(concepts): migrate project docs into 7 concepts + code-grounded audit
Migrate .project/{knowledge,decisions,briefs} and the always-loaded
AGENTS.md/CLAUDE.md into the in-repo `concept` system (hook-delivered,
typed leaves). Then audit the actual code to verify the migrated doctrine
and capture knowledge that lived only in the source.
Concepts (53 leaves):
- functional-domain — produit : point de rencontre greffé, acteurs, déduplication
- app-architecture — modules, invariant d'imports, routing, écrans, styling-system,
screen-pattern, cookbook d'ajout d'écran
- tech-stack — Bun-first, APIs, build pipeline, deployment (Dockerfile), commandes
- data-layer — NextGraph mono-store, shapes, modes, règles + caveats (suppression,
champs non persistés, internals du contexte)
- bdd-testing — Cucumber multi-couches, contrat de couches, harness, cookbook
- app-security — posture actuelle (mono-store, confiance broker), auth wallet,
brief matrice d'autorisations cible
- nextgraph-platform — NextGraph système externe + briefs (multi-store, shim, fork)
Audit corrections:
- décision SPARQL-delete annulée (superseded) → caveat (le code utilise ngSet.delete,
persistance possiblement partielle)
- divergences relevées : routing path-based (pas hash), thème moderne sous components/sketchy,
ConnectScreen hors registre, build:orm au chemin périmé, champs d'event perdus en connecté
Strip migrated sources; AGENTS.md/CLAUDE.md réduits au cœur (but, invariants,
carte des concepts) + pointeurs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
---
|
||||
type: _overview
|
||||
summary: Couche données NextGraph telle qu'utilisée AUJOURD'HUI (mono-store) — stack ORM/SHEX, modes connected/demo, entités, seed, et 3 règles d'écriture critiques
|
||||
triggers:
|
||||
keywords: [nextgraph, useShape, ORM, SHEX, shape, store, private_store, "@graph", NURI, sparql, sparql_update, seed, wallet, RepoNotFound, FestipodData, ngGraph, bootstrap]
|
||||
paths: ["src/shared/shapes/**", "src/shared/hooks/useShape*", "src/shared/context/NextGraphContext.tsx", "src/shared/context/FestipodDataContext.tsx", "src/shared/utils/ng*", "src/shared/data/seedData.ts"]
|
||||
---
|
||||
|
||||
# Data layer
|
||||
|
||||
Comment Festipod **persiste ses données aujourd'hui** via NextGraph (P2P, local-first, chiffré). État actuel : **mono-store** — tout atterrit dans le `private_store` de l'utilisateur connecté.
|
||||
|
||||
> Distinction importante : ce concept décrit le **code actuel**. Le modèle *cible* (multi-store, multi-user, autorisations) est de la doctrine **prospective** qui vit dans le concept `nextgraph-platform` (briefs). NextGraph comme **système externe** (stores, permissions, inbox, SDK) y est aussi documenté.
|
||||
|
||||
**À lire avant de toucher aux écritures :** les 3 règles ci-dessous — chacune corrige un bug réel (`RepoNotFound`, suppression non persistée, redirect intempestif).
|
||||
|
||||
## Règles d'écriture (chacune adossée à une décision)
|
||||
|
||||
- [[rule_private-store-scope]] ← [[decision_2026-03-17_private-store-nuri-scope]]
|
||||
- [[rule_conditional-ng-init]] ← [[decision_2026-03-13_conditional-ng-init-broker-detection]]
|
||||
|
||||
## Pièges (lire avant de toucher au contexte / aux suppressions / aux champs d'event)
|
||||
|
||||
- [[knowledge_context-internals]] — currentUser `@mariedupont`, auto-seed dev, `participantCount` cache, IRI vide, no-op local
|
||||
- [[caveat_participation-deletion]] — `leaveEvent` via `ngSet.delete()` (décision SPARQL annulée), persistance possiblement partielle
|
||||
- [[caveat_event-fields-not-persisted]] — `startTime`/`themes`… perdus en connecté (SHEX incomplet)
|
||||
|
||||
## Modèle & données
|
||||
|
||||
- [[knowledge_nextgraph-stack]] — paquets `@ng-org/*`, SHEX, ORM, `build:orm`
|
||||
- [[knowledge_data-modes]] — connected vs disconnected/demo, providers selon le statut NG
|
||||
- [[knowledge_entities]] — types `Fp*` et shapes
|
||||
- [[knowledge_seed-data]] — données de seed, `CURRENT_USER_ID`
|
||||
|
||||
> Sécurité/confidentialité (mono-store, confiance broker) : concept `app-security`.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
type: caveat
|
||||
summary: Le type FpEventData et le seed portent startDate/endDate/startTime/endTime/themes, mais le SHEX Event ne les définit pas — ces champs sont silencieusement perdus en mode connected (NextGraph)
|
||||
last_checked: 2026-06-15
|
||||
---
|
||||
|
||||
# Caveat : champs d'événement non persistés en mode connected
|
||||
|
||||
Le type app `FpEventData` (`src/shared/data/types.ts`) et le seed (`seedData.ts`) portent des champs **`startDate`, `endDate`, `startTime`, `endTime`, `themes`** — mais la **shape SHEX `Event`** (`src/shared/shapes/shex/festipodShapes.shex`) ne les définit **pas**. La shape ne couvre que : `title, description, date, location, distance, participantCount, coverImage, hostName, hostInitials` (à vérifier dans le `.shex`).
|
||||
|
||||
## Conséquence
|
||||
|
||||
En **mode connected** (NextGraph), le mapping (`mapEvent` dans `FestipodDataContext.tsx`) ne lit/écrit que les champs de la shape. Les champs hors-shape sont **silencieusement perdus** : remplis par des defaults ou vides. Or des écrans **les affichent** (ex. `startTime`/`endTime` dans `EventDetailScreen`) — donc en mode démo (seed local) ils apparaissent, mais en connecté ils disparaissent. Décalage observable seulement à l'usage.
|
||||
|
||||
## Pour corriger (si on veut les persister)
|
||||
|
||||
Ajouter les champs à `festipodShapes.shex` puis `bun run build:orm`, et étendre `mapEvent`. C'est aussi un prérequis de la modélisation complète du point de rencontre (cf. concept `nextgraph-platform`, [[brief_2026-05-21_fork-nextgraph-inbox]] §Couche 3). Tant que ce n'est pas fait, **ne pas se fier aux champs date/heure/thèmes en mode connecté**.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
type: caveat
|
||||
summary: La suppression de Participation (leaveEvent) se fait via ngSet.delete() — le bug de non-persistance qui avait motivé SPARQL DELETE est en grande partie corrigé, mais la persistance peut rester partielle ; vérifier après refresh
|
||||
last_checked: 2026-06-15
|
||||
---
|
||||
|
||||
# Caveat : suppression de Participation via `ngSet.delete()`
|
||||
|
||||
**État actuel du code** (`src/shared/context/FestipodDataContext.tsx`, `leaveEvent` en mode NG) : la suppression d'une `Participation` se fait via **`participationsShape.ngSet.delete(ngPart)`** — pas via `ng.sparql_update()` DELETE WHERE.
|
||||
|
||||
## Histoire (important)
|
||||
|
||||
Une décision antérieure ([[decision_2026-03-17_sparql-delete-for-orm-objects]], **annulée le 2026-06-15**) imposait SPARQL DELETE car `ngSet.delete()` ne persistait pas (l'objet réapparaissait au refresh). Ce **bug du `@ng-org/orm` a depuis été en grande partie corrigé** : `ngSet.delete()` est redevenu le chemin utilisé.
|
||||
|
||||
## Le piège (pourquoi un caveat et pas une règle)
|
||||
|
||||
La correction **semble partielle** : selon les cas, la suppression via `ngSet.delete()` peut ne **pas se propager complètement** au broker. Donc :
|
||||
|
||||
- **Ne pas tenir pour acquis** que `leaveEvent` persiste à coup sûr — **vérifier après un vrai refresh** que la participation a bien disparu côté wallet.
|
||||
- Si une suppression se révèle non persistée, le repli connu reste `ng.sparql_update()` avec `DELETE WHERE { GRAPH <…> { <…> ?p ?o } }` (le mécanisme décrit dans la décision annulée). **Ne pas combiner** les deux (conflit CRDT — c'était l'autre enseignement de la décision).
|
||||
- Re-tester ce point à chaque montée de version de `@ng-org/orm`.
|
||||
|
||||
> À valider : ouvrir `FestipodDataContext.tsx` → `leaveEvent` (mode NG, `console.log('Deleting participation via ngSet.delete()')`). Si le code est repassé à `sparql_update`, mettre ce caveat à jour ou le promouvoir en règle.
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
---
|
||||
type: decision
|
||||
summary: Décision 2026-03-13 — auto-init NextGraph seulement quand dans l'iframe broker (window.self !== window.top), sinon initNgWeb() redirige la page et casse le dev/démo standalone
|
||||
---
|
||||
|
||||
# Conditional NextGraph Init Based on Broker Iframe Detection
|
||||
|
||||
**Date:** 2026-03-13 14:00
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
`initNgWeb()` de `@ng-org/web` teste `window.self === window.top`. En standalone (hors iframe), il redirige toute la page vers `nextgraph.net/redir/` pour déclencher l'auth broker. Résultat : l'app redirigeait à chaque chargement — même en dev ou quand l'utilisateur n'avait pas cliqué « Se connecter ».
|
||||
|
||||
## Options Considered
|
||||
|
||||
### Option A: toujours auto-init NG au mount
|
||||
- Plus simple (pas de branchement).
|
||||
- **Contre** : redirect immédiat vers le broker en standalone ; casse le workflow de dev ; l'utilisateur voit la page de login broker au lieu de l'app.
|
||||
|
||||
### Option B: auto-init conditionnel selon détection iframe
|
||||
- En iframe, le broker a déjà authentifié → auto-init sûr ; en standalone, l'utilisateur doit cliquer « Se connecter » ; préserve l'expérience démo/dev ; calque la propre logique de détection de `@ng-org/web`.
|
||||
- **Contre** : repose sur l'heuristique `window.self !== window.top` (théoriquement faillible si embarqué dans une iframe non-broker).
|
||||
|
||||
## Decision
|
||||
|
||||
**Option B.** `NextGraphContext` calcule `isInsideBroker = typeof window !== 'undefined' && window.self !== window.top` au niveau module. `useEffect` n'auto-appelle `initNg()` que si `isInsideBroker`. Le callback `connect()` reste disponible pour la connexion explicite. De plus, `FestipodDataContext` rend des données vides (pas le seed) pendant `connecting` pour éviter de flasher le contenu démo.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positif :** l'app charge sans rediriger (standalone dev/démo) ; en iframe broker, connexion fluide et automatique ; pas de flash de seed pendant la connexion.
|
||||
**Négatif :** aucun significatif.
|
||||
**Risque :** si `@ng-org/web` change sa logique de détection, notre garde peut diverger — les garder alignés.
|
||||
|
||||
> Règle dérivée : [[rule_conditional-ng-init]].
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
type: decision
|
||||
summary: Décision 2026-03-17 — utiliser private_store_id comme scope useShape ET @graph (calqué sur expense-tracker-rdf) pour que orm_start_graph ouvre le repo et que les écritures ne lèvent plus RepoNotFound
|
||||
---
|
||||
|
||||
# Use private_store_id as useShape scope and @graph
|
||||
|
||||
**Date:** 2026-03-17 16:00
|
||||
**Status:** Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Cliquer « Charger données de test » chargeait les données en mémoire (signaux ORM) mais produisait des `RepoNotFound` sur `doc_create` et `orm_frontend_update`. Les données disparaissaient au reload car les écritures SPARQL n'atteignaient jamais le broker. La HashMap `self.repos` du verifier ne contenait pas le repo du private store → `resolve_target()` échouait.
|
||||
|
||||
## Options Considered
|
||||
|
||||
### Option A: `did:ng:i` scope + `doc_create` pour @graph
|
||||
- `did:ng:i` bien documenté comme scope d'abonnement, `doc_create` renvoie un vrai NURI.
|
||||
- **Contre** : `did:ng:i` passe par `NuriTargetV0::UserSite` qui n'ouvre pas les repos individuels ; `doc_create` appelle `resolve_target(PrivateStore)` qui exige le repo dans `self.repos` → échoue ; exige une logique de retry/timing complexe.
|
||||
|
||||
### Option B: `private_store_id` comme scope ET @graph
|
||||
- Calque exact de l'exemple `expense-tracker-rdf` qui fonctionne ; `orm_start_graph` avec le NURI du private store ouvre le repo dans `self.repos` ; les écritures `orm_frontend_update` trouvent ensuite le repo. Simple, sans retry.
|
||||
- **Contre** : un peu moins flexible que `did:ng:i` (scopé à un store) ; exige de passer la session à `useShapeWithDefaults`.
|
||||
|
||||
### Option C: `did:ng:i` scope + réutiliser le @graph d'une entité existante
|
||||
- Marche pour les users qui ont déjà des données.
|
||||
- **Contre** : échoue pour les wallets vides (aucune entité à réutiliser) ; retombe sur `doc_create` et le même `RepoNotFound`.
|
||||
|
||||
## Decision
|
||||
|
||||
**Option B** : `did:ng:${session.private_store_id}` comme scope `useShape` ET `@graph` d'écriture, exactement comme `expense-tracker-rdf`. `useShapeWithDefaults` accepte un `storeNuri` ; `FestipodDataContext.useNgData()` récupère la session via `useNextGraph()` et passe le NURI du private store. `ensureGraphNuri()` simplifié : entités existantes d'abord (optimisation), sinon fallback `private_store`.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positif :** écritures immédiates après connexion (sans retry) ; persistance au reload ; aligné sur les exemples officiels ; les 7 scénarios e2e passent (dont la persistance).
|
||||
**Négatif :** signature de `useShapeWithDefaults` modifiée (param `storeNuri`).
|
||||
**Risque :** si NextGraph change le comportement du private store, ça casse.
|
||||
|
||||
> Règle dérivée : [[rule_private-store-scope]]. Décision *remise en cause* par le futur multi-store : [[brief_2026-05-17_multi-store-refactor]].
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
type: decision
|
||||
summary: Décision 2026-03-17 — supprimer les objets ORM via ng.sparql_update (DELETE WHERE) seul, car ngSet.delete() ne persiste pas et les combiner crée un conflit CRDT
|
||||
---
|
||||
|
||||
# Use SPARQL DELETE instead of ORM ngSet.delete() for object removal
|
||||
|
||||
**Date:** 2026-03-17 18:00
|
||||
**Status:** ~~Accepted~~ → **Superseded (2026-06-15)**
|
||||
|
||||
> **Annulée le 2026-06-15.** Le bug de non-persistance de `ngSet.delete()` qui motivait cette décision a depuis été en grande partie corrigé côté `@ng-org/orm` : le code (`leaveEvent`) est repassé à `ngSet.delete()`. La persistance reste toutefois possiblement partielle — l'état courant et le repli SPARQL sont décrits dans [[caveat_participation-deletion]]. Décision conservée comme mémoire d'arbitrage (le conflit CRDT « ne pas combiner les deux » reste vrai).
|
||||
|
||||
## Context
|
||||
|
||||
Quitter un event exige de supprimer l'objet `Participation` du store NextGraph. `DeepSignalSet.delete()` met à jour l'état réactif local (UI immédiate) mais **ne persiste pas** au broker — après refresh, la participation réapparaît.
|
||||
|
||||
## Options Considered
|
||||
|
||||
### Option A: ORM `ngSet.delete(item)`
|
||||
- API officielle (README ORM), update réactif local instantané.
|
||||
- **Contre** : ne persiste pas en pratique (`delete()` renvoie `true`, set local à jour, mais objet de retour après refresh) ; `graph_orm_update` semble mal gérer les patches "remove" pour objets de set top-level (bug moteur probable) ; échoue silencieusement.
|
||||
|
||||
### Option B: `ng.sparql_update()` avec SPARQL DELETE
|
||||
- `DELETE WHERE { GRAPH <graph> { <subject> ?p ?o } }` retire tous les triples RDF.
|
||||
- **Pour** : persiste (survit au refresh) ; le broker confirme via `GraphOrmUpdate` remove qui retire réactivement l'item du set ORM ; contrôle direct.
|
||||
- **Contre** : pas instantané (round-trip SPARQL + callback broker, ~50ms) ; ne doit pas être combiné avec `ngSet.delete()`.
|
||||
|
||||
### Option C: les deux ensemble
|
||||
- **Ne marche pas** : le patch ORM `.delete()` et le DELETE SPARQL entrent en conflit CRDT → ni UI ni persistance.
|
||||
|
||||
## Decision
|
||||
|
||||
**Option B : SPARQL DELETE seul.** Le broker renvoie un `GraphOrmUpdate` `op: "remove"` qui retire réactivement l'item du set ORM (UI à jour, juste pas synchrone). **Ne pas** appeler `ngSet.delete()` à côté.
|
||||
|
||||
```typescript
|
||||
// FestipodDataContext.tsx leaveEvent():
|
||||
const session = await sessionPromise;
|
||||
await ng.sparql_update(
|
||||
session.session_id,
|
||||
`DELETE WHERE { GRAPH <${partGraph}> { <${partId}> ?p ?o } }`,
|
||||
partGraph,
|
||||
);
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positif :** suppression persistée ; source de vérité unique (broker → ORM → UI).
|
||||
**Négatif :** léger délai UI (~50ms) ; diverge des exemples README ORM.
|
||||
**Risque :** si `ng.sparql_update` change, ça casse ; toute future suppression doit suivre le même pattern ; revisiter si `ngSet.delete()` est corrigé en montée de version.
|
||||
|
||||
> État courant (la règle a été retirée) : [[caveat_participation-deletion]].
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Pièges internes de FestipodDataContext — currentUser NG résolu par username '@mariedupont' (fallback users[0]), auto-seed dev-only après 3s sans retry, participantCount muté en place (cache), currentUserId vide → IRI invalide, mutations no-op en mode local malgré le toast
|
||||
last_checked: 2026-06-15
|
||||
---
|
||||
|
||||
# Internals & pièges de `FestipodDataContext`
|
||||
|
||||
Comportements non évidents de `src/shared/context/FestipodDataContext.tsx` à connaître avant de toucher au contexte de données.
|
||||
|
||||
## Résolution du `currentUser` (mode NG)
|
||||
|
||||
En mode connected, le currentUser n'est **pas** `CURRENT_USER_ID` ('user-1', qui ne vaut qu'en mode local). Il est résolu par **`users.find(u => u.username === '@mariedupont') || users[0]`** (vers ligne 286). Pièges :
|
||||
- **Fallback silencieux** sur `users[0]` si `@mariedupont` absent → currentUser arbitraire.
|
||||
- Si le wallet est **vide** (`users.length === 0`), `currentUserId` devient `''` → toute `Participation` créée a un `user: ''` (**IRI invalide**), sans alerte. Bug silencieux possible à la première connexion sur un wallet vierge.
|
||||
- L'IRI du currentUser diffère entre mode local (ID de seed statique) et mode NG (IRI NextGraph dynamique) — ne pas comparer les deux.
|
||||
|
||||
## Auto-seed de dev
|
||||
|
||||
Un auto-seed se déclenche (vers lignes 263-283) **uniquement hors production** (`process.env.NODE_ENV !== 'production'`), après un **`setTimeout` de ~3s**, si les sets events ET users sont vides. Pièges :
|
||||
- **Pas de retry** : `hasTriedAutoSeed` (useRef) est posé une fois ; si le seed échoue, jamais réessayé (écran vide, juste un `console.error`).
|
||||
- Le délai de 3s est **heuristique** : si l'hydratation ORM est lente, le seed peut partir alors que des données arrivent.
|
||||
|
||||
## `participantCount` muté en place
|
||||
|
||||
`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.
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Deux modes (connected = NextGraph ORM, disconnected/demo = état local seedé) ; FestipodDataContext choisit le provider selon le statut NextGraphContext, tous les écrans passent par useFestipodData()
|
||||
---
|
||||
|
||||
# Modes de données & contextes
|
||||
|
||||
L'app a **deux modes**, tous deux consommés via le hook `useFestipodData()` :
|
||||
|
||||
1. **Connected** — shapes ORM NextGraph (P2P, chiffré, local-first)
|
||||
2. **Disconnected / Demo** — état React local seedé depuis `seedData.ts` (voir [[knowledge_seed-data]])
|
||||
|
||||
## NextGraphContext (`src/shared/context/NextGraphContext.tsx`)
|
||||
|
||||
- Cycle de connexion : `disconnected` → `connecting` → `connected` | `error`.
|
||||
- Fournit la session avec les IDs de stores (private, protected, public).
|
||||
- **Auto-init conditionnel** : voir [[rule_conditional-ng-init]] (n'auto-initialise que dans l'iframe broker).
|
||||
|
||||
## FestipodDataContext (`src/shared/context/FestipodDataContext.tsx`)
|
||||
|
||||
- Enveloppe les shapes via `useShapeWithDefaults()`.
|
||||
- Expose `useFestipodData()` (consommé par tous les écrans) + CRUD (`createEvent`, `updateEvent`, etc.).
|
||||
- **Provider selon le statut NG** :
|
||||
- `disconnected` → `LocalDataProvider` avec seed (démo)
|
||||
- `connecting` → `LocalDataProvider` **vide** (évite de flasher le seed avant le chargement du wallet)
|
||||
- `connected` → `NgDataProvider` (données réelles du wallet)
|
||||
- `error` → `LocalDataProvider` avec seed (fallback gracieux)
|
||||
|
||||
> Réserve : certaines mutations (`joinEvent`/`leaveEvent`) sont encore des **no-ops** (`console.log`) en attendant le chantier données — cf. [[brief_2026-05-21_fork-nextgraph-inbox]] §Couche 3.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Types de données Fp* (Event, UserProfile, Participation persistés NextGraph ; MeetingPoint et Friendship encore local-only)
|
||||
---
|
||||
|
||||
# Entités de données
|
||||
|
||||
`src/shared/data/types.ts` :
|
||||
|
||||
| Type | Persistance | Champs clés |
|
||||
|---|---|---|
|
||||
| `FpEventData` | NextGraph (shape Event) | id, title, date, location, distance, themes |
|
||||
| `FpUserData` | NextGraph (shape UserProfile) | id, name, username, bio, city, counts |
|
||||
| `FpParticipationData` | NextGraph (shape Participation) | eventId + userId + confirmed |
|
||||
| `FpMeetingPointData` | **local-only** | eventId, location, time, host |
|
||||
| `FpFriendshipData` | **local-only** | userId + friendId |
|
||||
|
||||
`MeetingPoint` et `Friendship` n'ont **pas encore de shape SHEX** ni de persistance NextGraph (cf. [[knowledge_nextgraph-stack]]). Les brancher au store est un prérequis du multi-user — voir les briefs du concept `nextgraph-platform`.
|
||||
|
||||
> Piège : même pour `FpEvent` (persisté), plusieurs champs du type app ne sont **pas** dans la shape et sont perdus en connecté — voir [[caveat_event-fields-not-persisted]].
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Paquets @ng-org/* (web, orm, shex-orm, alien-deepsignals), shapes SHEX festipodShapes, bindings ORM générés, régénérés via build:orm
|
||||
---
|
||||
|
||||
# Stack NextGraph (côté app)
|
||||
|
||||
```
|
||||
@ng-org/web # Runtime navigateur (proxy postMessage vers l'iframe)
|
||||
@ng-org/orm # ORM réactif basé sur les shapes RDF (useShape…)
|
||||
@ng-org/shex-orm # Génération SHEX → TypeScript
|
||||
@ng-org/alien-deepsignals # Pont de signaux réactifs
|
||||
```
|
||||
|
||||
Installés depuis npm (`@ng-org/*`, versions alpha). Pour développer contre un build local non publié de `nextgraph-rs`, `scripts/build-ng-packages.sh` pack le monorepo en tarballs et repointe `package.json` (cf. `nextgraph-platform` — le pattern d'origine du projet, réactivable pour un fork).
|
||||
|
||||
## Shapes SHEX
|
||||
|
||||
`src/shared/shapes/shex/festipodShapes.shex` définit :
|
||||
- **Event** — titre, description, dates, lieu, thèmes, participants
|
||||
- **UserProfile** — nom, username, bio, ville, visibilité
|
||||
- **Participation** — lie event + user, statut de confirmation
|
||||
|
||||
Bindings ORM dans `src/shared/shapes/orm/` (`*.schema.ts`, `*.shapeTypes.ts`, `*.typings.ts`). **Régénérer** avec `bun run build:orm` après toute modif `.shex`.
|
||||
|
||||
> Manque côté shapes : **pas de `MeetingPoint`** ni d'entité notification — le point de rencontre est aujourd'hui local-only côté types (voir [[knowledge_entities]]). Leur modélisation est un chantier de [[brief_2026-05-21_fork-nextgraph-inbox]].
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: seedData.ts fournit des fixtures déterministes (10 users, events, participations) avec CURRENT_USER_ID = 'user-1' (Marie Dupont) ; utilisé en mode démo et par les tests @ui
|
||||
---
|
||||
|
||||
# Seed data
|
||||
|
||||
`src/shared/data/seedData.ts` fournit des fixtures **déterministes** :
|
||||
|
||||
- 10 users — **Marie Dupont = utilisateur courant**, `user-1`
|
||||
- Plusieurs events (dates, lieux, thèmes)
|
||||
- Participations, meeting points, friendships
|
||||
- `CURRENT_USER_ID = 'user-1'`
|
||||
|
||||
Ces fixtures servent (a) le **mode démo** (`LocalDataProvider`, cf. [[knowledge_data-modes]]) et (b) les tests **`@ui`** qui rendent les écrans avec ces données prévisibles (`Marie Dupont`/`@mariedupont` = currentUser, `Jean Durand`/`@jeandurand` existe, etc. — voir concept `bdd-testing`).
|
||||
|
||||
> `bootstrapWallet()` (`src/shared/utils/ngBootstrap.ts`) seede ces données dans le wallet NG en mode connected — déclenché uniquement par action explicite de l'utilisateur (« Charger données de test »). Sa refonte par documents/périmètres est un point des briefs `nextgraph-platform`.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
type: rule
|
||||
summary: N'auto-initialiser NextGraph que dans l'iframe broker (window.self !== window.top) ; en standalone, initNgWeb() redirige toute la page — attendre un connect() explicite
|
||||
---
|
||||
|
||||
# Règle : auto-init NextGraph seulement dans l'iframe broker
|
||||
|
||||
`initNgWeb()` de `@ng-org/web` teste `window.self === window.top`. **Hors iframe** (app standalone), il **redirige toute la page** vers `nextgraph.net/redir/` pour déclencher l'auth broker.
|
||||
|
||||
Donc `NextGraphContext` calcule `isInsideBroker = window.self !== window.top` et **n'auto-appelle `initNg()` que si `isInsideBroker`**. En standalone, la connexion attend un `connect()` explicite (clic « Se connecter ») — sinon l'app redirige à chaque chargement et casse le dev/démo.
|
||||
|
||||
De plus, `FestipodDataContext` rend des données **vides** (pas le seed) pendant la phase `connecting`, pour éviter de flasher du contenu démo avant le chargement du wallet (voir [[knowledge_data-modes]]).
|
||||
|
||||
> Garder ce garde **aligné** sur la détection interne de `@ng-org/web` : si leur heuristique change, le nôtre doit suivre. Pourquoi + alternatives : [[decision_2026-03-13_conditional-ng-init-broker-detection]].
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
type: rule
|
||||
summary: Utiliser did:ng:${private_store_id} comme scope useShape ET comme @graph d'écriture ; ne jamais utiliser did:ng:i comme scope (casse toutes les écritures par RepoNotFound)
|
||||
---
|
||||
|
||||
# Règle : scope = `@graph` = private_store_id
|
||||
|
||||
Pour lire **et** écrire via l'ORM NextGraph :
|
||||
|
||||
- **Scope** : `useShape(shapeType, \`did:ng:${session.private_store_id}\`)`
|
||||
- **`@graph`** (cible des écritures) : `did:ng:${session.private_store_id}`
|
||||
|
||||
C'est critique : `orm_start_graph` avec le NURI du private_store **ouvre explicitement le repo** dans la HashMap `self.repos` du verifier. Sans ça, `orm_frontend_update` échoue en `RepoNotFound`.
|
||||
|
||||
## Interdit
|
||||
|
||||
**Ne pas utiliser `did:ng:i` comme scope.** Il s'abonne au site entier de l'utilisateur via un chemin de code spécial (`NuriTargetV0::UserSite`) qui **n'ouvre pas les repos individuels** → casse toutes les écritures.
|
||||
|
||||
## Fichiers porteurs
|
||||
|
||||
- `src/shared/hooks/useShapeWithDefaults.ts` — accepte un `storeNuri`, le passe à `useShape`.
|
||||
- `src/shared/utils/ngGraph.ts` — `ensureGraphNuri()` retourne le `@graph` (entités existantes d'abord, sinon fallback `private_store`).
|
||||
- `src/shared/utils/ngBootstrap.ts` — seede en utilisant `ensureGraphNuri()`.
|
||||
|
||||
> Le *pourquoi* complet et les alternatives écartées : [[decision_2026-03-17_private-store-nuri-scope]]. **Ce scope mono-store est précisément ce que le chantier multi-store viendra remplacer** — voir [[brief_2026-05-17_multi-store-refactor]].
|
||||
Reference in New Issue
Block a user