docs(concept): solder la doc-debt des 6 concepts
Dette accumulée depuis le 13/07 (27 marqueurs). Au-delà du vidage, trois
corrections de doctrine réellement fausse — c'est ce que le reconcile devait
attraper :
- app-security : `sharedWallet.ts` capture le mot de passe à l'ÉVALUATION du
module. Tant qu'un repli existait, un global posé trop tard ne faisait que
dégrader ; depuis que le wallet partagé est l'unique mode, il rend la barrière
INUTILISABLE (écran d'erreur, aucun champ). Conséquence non anticipée de la
décision shared-wallet-only → nouveau caveat.
- bdd-testing : la doctrine rendait des tests faux-verts. `ctx.newPage()` sur le
profil persistant relit l'IndexedDB local et ne prouve JAMAIS la durabilité
broker ; seul un contexte partagé neuf tranche. Un agent suivant la doctrine
écrivait un test qui passe sans rien vérifier → nouveau caveat.
- app-architecture : `knowledge_routing` décrivait encore une route `/login`
disparue, et `knowledge_screen-pattern` citait `LoginScreen` qui n'existe
plus. Nouveau caveat sur les deux espaces d'id vus depuis un écran.
Aussi : data-layer/knowledge_context-internals décrit la jointure
participation→profil et corrige un mécanisme de changement d'identité périmé ;
tech-stack raccroche la table des scripts au vrai point d'entrée cucumber ;
functional-domain note qu'« implémenté » ≠ « durable ».
Trois marqueurs soldés comme sans objet : ils visaient
`reconnexion-socket-mort.{feature,steps.ts}`, absents de l'arbre ET de tout
l'historique — expérience abandonnée avant tout commit. Ce qu'elle devait
établir est capturé ailleurs (caveat de durabilité, post-mortem polyfill, fiche
INBOX socket-death).
Liens morts vers une décision disparue avec le concept `nextgraph-platform`
réparés. Reste au lint : le brief 07-06 (superseded) porte des file:line et des
références aux internes NextGraph — laissé intact, il décrit l'Option-B encore
implémentée et se dissoudra à la graduation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
# Doc-debt — app-architecture
|
||||
|
||||
> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file.
|
||||
> One block = one "big change": `why` + `files` + `verify` (leaves to review).
|
||||
|
||||
## Raw markers (consolidate into blocks, then delete)
|
||||
- TOUCHED src/shared/context/FestipodDataContext.tsx @2026-07-14 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
- TOUCHED src/modules/auth/screens/AccessGateScreen.tsx @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
- TOUCHED src/shared/context/AccountContext.tsx @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
- TOUCHED src/modules/event/screens/EventDetailScreen.tsx @2026-07-20 (session 0b064e8b-1717-421f-a20e-a4318ad217b1)
|
||||
@@ -19,6 +19,7 @@ Comment le code de l'app est **structuré** et **assemblé**. Architecture *feat
|
||||
- [[knowledge_routing]] — routing path-based (History API), table de routes, hooks
|
||||
- [[knowledge_screens]] — inventaire des écrans, registre, lib de composants
|
||||
- [[knowledge_screen-pattern]] — anatomie canonique d'un écran (sans props, layout flex, showToast)
|
||||
- [[caveat_identity-ids-in-screens]] — `currentUserId` (principal) vs `currentUser.id` (NURI de profil) : deux espaces d'id non interchangeables
|
||||
- [[knowledge_styling-system]] — `src/index.css`, classes `app-*`, vars, pièges (Tailwind non-utilisé, `user-content` inerte)
|
||||
- [[cookbook_add-screen]] — procédure pour câbler un nouvel écran (registre + router + shell)
|
||||
- `tech-stack` — build, bundler Bun, commandes
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
type: caveat
|
||||
summary: Un écran manipule DEUX ids de l'utilisateur courant qui ne sont pas interchangeables — currentUserId (principal urn:festipod:user:…) pour les queries de participation/amitié, currentUser.id (NURI du profil) pour comparer aux profils rendus ; se tromper ne lève aucune erreur, ça rend une liste vide ou se compte soi-même comme un participant inconnu
|
||||
last_checked: 2026-07-27
|
||||
---
|
||||
|
||||
# Piège : deux ids de l'utilisateur courant dans un écran
|
||||
|
||||
`useFestipodData()` expose **deux** identifiants de l'utilisateur courant. Ils vivent dans des **espaces différents** et ne sont **jamais égaux en mode connecté** :
|
||||
|
||||
| Valeur | Espace | À quoi elle sert |
|
||||
|---|---|---|
|
||||
| `currentUserId` | **principal** stable dérivé de l'identifiant de connexion (`urn:festipod:user:<clé>`) | c'est ce que les **participations** et **amitiés** stockent |
|
||||
| `currentUser.id` | **NURI du document de profil** (`did:ng:…`) | c'est ce que portent les **profils** rendus |
|
||||
|
||||
En mode seed/demo les deux coïncident (`user-1`) — **le piège ne se manifeste qu'en connecté**, et jamais sous forme d'erreur : juste un résultat faux.
|
||||
|
||||
## La règle
|
||||
|
||||
- Les queries qui **filtrent des participations/amitiés** — `getUserEvents(userId)`, `isParticipating(eventId, userId?)`, `getFriends(userId?)` — attendent le **principal**. Leur valeur par défaut (`currentUserId`) est correcte ; **ne leur passe pas** un `user.id` de profil, sinon la liste revient **vide**.
|
||||
- `getEventParticipants(eventId)` rend des **profils**. Toute comparaison sur son résultat (typiquement « me retirer de la liste ») se fait donc sur **`currentUser?.id`**, jamais sur `currentUserId`.
|
||||
|
||||
## Ce que coûte l'erreur (observé)
|
||||
|
||||
- Comparer `participant.id !== currentUserId` pour se filtrer soi-même **ne retire rien** : on apparaît dans sa propre liste, et comme la ligne n'est plus reconnue elle s'affiche en « participant inconnu ».
|
||||
- Symétriquement, un écran qui affiche les événements d'**un autre utilisateur** à partir de son **id de profil** (`getUserEvents(viewedUser.id)`) rend une liste vide en connecté — même cause.
|
||||
|
||||
La jointure participation→profil elle-même n'est **pas** l'affaire de l'écran : elle est faite dans le provider (`resolveParticipantUser`), à travers l'identifiant normalisé. Mécanique complète et invariant d'écriture/lecture : concept `data-layer`, [[knowledge_context-internals]].
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: src/app/ est le shell réel de l'app — App.tsx empile les providers (Theme > NextGraph > FestipodData > Router) et bascule l'écran selon la route
|
||||
summary: src/app/ est le shell réel de l'app — App.tsx empile les providers (Theme > NextGraph > Account > FestipodData > Router), AuthGate garde tout écran routé derrière la barrière d'accès, et le shell bascule l'écran selon la route
|
||||
last_checked: 2026-07-27
|
||||
---
|
||||
|
||||
# App shell
|
||||
@@ -15,12 +16,28 @@ summary: src/app/ est le shell réel de l'app — App.tsx empile les providers (
|
||||
|
||||
```
|
||||
ThemeProvider
|
||||
└ NextGraphProvider (cycle de connexion NextGraph — concept data-layer)
|
||||
└ FestipodDataProvider (données, mode connected/demo — concept data-layer)
|
||||
└ RouterProvider (route courante + navigate)
|
||||
└ NextGraphProvider (cycle de connexion NextGraph — concept data-layer)
|
||||
└ AccountProvider (identité courante = l'identifiant — concept app-security)
|
||||
└ FestipodDataProvider (données, mode connected/demo — concept data-layer)
|
||||
└ RouterProvider (route courante + navigate)
|
||||
└ div.app-container
|
||||
├ AuthGate (barrière d'accès)
|
||||
│ └ AppContent (switch route.page → écran)
|
||||
└ ToastContainer
|
||||
```
|
||||
|
||||
Le composant racine lit `useRouter()` pour résoudre `route.page` → écran à rendre.
|
||||
`AppContent` lit `useRouter()` pour résoudre `route.page` → écran à rendre.
|
||||
|
||||
### Invariants d'ordre (ce qui casse si tu déplaces une couche)
|
||||
|
||||
- **`AccountProvider` est AU-DESSUS de `FestipodDataProvider`.** Le provider de données appelle `useAccount()` pour dériver son principal (`currentUserId`) *et* pour réinitialiser sa session au changement d'identité. Inverser l'ordre casse toute la résolution d'identité, silencieusement.
|
||||
- **`AuthGate` est À L'INTÉRIEUR du router** : il lit `useRouter()`/`useNavigate()` pour quitter la route d'accueil déconnectée une fois connecté **et** identifié. Le sortir du `RouterProvider` le casse.
|
||||
- **`AuthGate` enveloppe TOUT écran routé.** Tant que le wallet n'est pas ouvert **ou** que l'identifiant n'est pas résolu, `AccessGateScreen` est rendu **à la place** d'`AppContent`. Conséquence : **aucun écran ne peut supposer qu'il est atteignable sans identité** — sauf barrière désactivée (voir ci-dessous).
|
||||
- **`ToastContainer` est HORS d'`AuthGate`** (mais dans `.app-container`) : il est monté quel que soit l'état de la barrière.
|
||||
|
||||
### Désactivation de la barrière (deux consommateurs)
|
||||
|
||||
`AuthGate` est **ON par défaut** ; il ne s'efface que si `globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ === true`, posé soit par `build.ts` (depuis `ACCESS_GATE_DISABLED=1`, build sans barrière), soit par le harness de test via `addInitScript` pour les `@e2e` (qui exercent les écrans, pas le flux d'auth). **Impact** : le flux de barrière n'est donc **pas** couvert par les `@e2e` — ses gardes sont des tests `@ui` (concept `bdd-testing`).
|
||||
|
||||
## Points d'entrée
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Chaque module peut contenir :
|
||||
| Répertoire | Contenu |
|
||||
|---|---|
|
||||
| `components/` | Lib de composants UI (voir [[knowledge_screens]]) |
|
||||
| `context/` | `ThemeContext`, `NextGraphContext`, `FestipodDataContext` (voir concept `data-layer`) |
|
||||
| `context/` | `ThemeContext`, `NextGraphContext`, `AccountContext` (identité courante — concept `app-security`), `FestipodDataContext` (concept `data-layer`) ; leur **ordre d'empilement** est contraint, voir [[knowledge_app-shell]] |
|
||||
| `data/` | User stories, `features.ts` (auto-généré), `seedData.ts`, `types.ts` |
|
||||
| `hooks/` | `useShapeWithDefaults` (NextGraph) |
|
||||
| `shapes/` | SHEX + bindings ORM (voir concept `data-layer`) |
|
||||
|
||||
@@ -12,7 +12,6 @@ Routing **path-based** via l'History API — router maison dans `src/app/router.
|
||||
| Path | Écran |
|
||||
|---|---|
|
||||
| `/` | WelcomeScreen |
|
||||
| `/login` | LoginScreen |
|
||||
| `/home` | HomeScreen |
|
||||
| `/events` | EventsScreen |
|
||||
| `/events/new` | CreateEventScreen |
|
||||
@@ -29,7 +28,7 @@ Routing **path-based** via l'History API — router maison dans `src/app/router.
|
||||
| `/users/:id` | UserProfileScreen |
|
||||
| `/settings` | SettingsScreen |
|
||||
|
||||
> Cette table reflète `parsePath()` dans `router.tsx` — y revenir si elle évolue, c'est la source de vérité.
|
||||
> Cette table reflète `parsePath()` dans `router.tsx` — y revenir si elle évolue, c'est la source de vérité. Il n'y a **pas** de route d'authentification : la barrière d'accès n'est pas routée, elle est rendue *au-dessus* du switch de routes par `AuthGate` (voir [[knowledge_app-shell]]).
|
||||
|
||||
## Hooks
|
||||
|
||||
|
||||
@@ -34,7 +34,10 @@ export function MyScreen() { // fonction nommée, JAMAIS de props
|
||||
|
||||
## Invariants
|
||||
|
||||
- **Zéro prop** : l'écran ne reçoit rien ; tout vient du contexte/hooks (`useFestipodData`, `useNavigate`, `useParams`). Exceptions légitimes : `LoginScreen`/`WelcomeScreen` n'utilisent pas `useFestipodData` (auth/intro).
|
||||
- **Zéro prop** : l'écran ne reçoit rien ; tout vient du contexte/hooks (`useFestipodData`, `useNavigate`, `useParams`). Deux exceptions, de nature différente :
|
||||
- `WelcomeScreen` n'utilise pas `useFestipodData` (intro) — mais reste sans props. (`LoginScreen`/`ConnexionScreen` n'existent plus.)
|
||||
- **`AccessGateScreen` est la seule vraie exception au zéro-prop** : ce n'est **pas un écran routé**, il est rendu par `src/app/AuthGate.tsx` qui lui passe `status`/`error`/`initialIdentifier`/`onEnter`. Il est donc **hors registre et hors table de routes**, et n'a accès ni au router ni aux données. Voir [[knowledge_screens]] et [[knowledge_app-shell]].
|
||||
- **Identité : deux espaces d'id.** `currentUserId` (principal) et `currentUser.id` (NURI de profil) ne sont **pas** interchangeables selon la query — voir [[caveat_identity-ids-in-screens]] avant de comparer un id dans un écran.
|
||||
- **Layout** : flex colonne pleine hauteur ; `Header` en haut, contenu en `flex:1; overflow:auto`, `BottomNav` en bas **uniquement pour les écrans hub** (Home, Events, Profile, Friends). Les écrans de flux (création, édition, détail) n'ont pas de `BottomNav`.
|
||||
- **Feedback** : `showToast(message, 'success'|'info'|'error')` (mécanisme `ToastContainer` exporté par `sketchy/`).
|
||||
- **Libellés** : **français, en dur** — aucun i18n, aucune clé de traduction dans le projet.
|
||||
|
||||
@@ -30,7 +30,9 @@ Utilisé notamment par Storybook (voir concept `tech-stack`) pour parcourir les
|
||||
- **home/** : `welcome`, `home`, `settings`
|
||||
- **event/** : `events`, `event-detail`, `create-event`, `update-event`, `invite`, `participants-list`, `meeting-points`
|
||||
- **user/** : `profile`, `update-profile`, `user-profile`, `friends-list`, `share-profile`
|
||||
- **auth/** : `AccessGateScreen` — la **barrière d'accès** (login NextGraph + saisie de l'identifiant), rendue par `src/app/AuthGate.tsx`, **hors registre/routing** (ce n'est pas un écran routé). Les anciens `LoginScreen` puis `ConnexionScreen` ont été retirés (cf. concept `app-security`, [[knowledge_authentication]]).
|
||||
- **auth/** : `WelcomeScreen` (intro, routé `/`) et `AccessGateScreen` — la **barrière d'accès** (login NextGraph + saisie de l'identifiant), rendue par `src/app/AuthGate.tsx`, **hors registre/routing** (ce n'est pas un écran routé) et **pilotée par props** (`status`/`error`/`initialIdentifier`/`onEnter`), seule exception au zéro-prop ([[knowledge_screen-pattern]]). Les anciens `LoginScreen` puis `ConnexionScreen` ont été retirés (cf. concept `app-security`, [[knowledge_authentication]]).
|
||||
|
||||
Structurellement, cet écran ne rend **pas** un layout d'écran standard mais un **choix entre trois branches d'accès** mutuellement exclusives, pilotées par `status` + la présence d'un wallet partagé. **Impact** : un nouveau cas d'accès s'ajoute comme une branche ici, **pas** comme une route. Le contenu et l'ordre des branches sont doctrine `app-security` ([[knowledge_authentication]]) — ne pas les redéfinir depuis ici.
|
||||
|
||||
> Le mapping path → écran est dans [[knowledge_routing]]. La plupart des écrans consomment `useFestipodData()` (concept `data-layer`) ; exceptions : `WelcomeScreen` et la barrière `AccessGateScreen`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user