Creating an event resolved its inbox four times at once -- from `createEvent`, from the materializer, from the watch callback and from the watch wiring -- with nothing serialising them. Three inboxes were registered for that one document inside 0.3 s, so the owner watched one while the sign-up deposited into another. That is the whole of the asymmetry: on any later connection nothing re-registers, both sides agree, and withdrawal converged immediately while a sign-up never did. Measured before: 2 of 3 fresh sign-ups NEVER converged, the deposit unfindable on every later connection. Measured after, twice: one inbox, one caller joining the in-flight resolution instead of opening a second, the deposit read back, and nothing failing to converge. The fix is two primitives rather than a lock in the middle of the data context, each unit-tested on its own: a resolve-once-per-key whose rejection is NOT memoized (unknown is not absent), and a serial task whose mid-run requests coalesce into one follow-up and which a failure cannot wedge. The single-flight wrapper is now the only caller of the underlying entry, so every call site is covered without touching any of them. Also closed on the same path: the write guard carries a monotonic cycle number, so a cycle from an earlier effect run cannot overwrite a fresher count; the field update is one statement instead of DELETE-then-INSERT, closing the window where a reader saw the field absent and read zero; and the materializer's before-value comes from a ref instead of a stale closure. What is NOT fixed, deliberately: the count still takes one connection to appear. A deposit you make into an inbox you watch produces no push, and neither does a write to your own document -- both are questions for the provider, and any app-side substitute would be the polling the doctrine forbids.
Festipod
Festipod permet aux utilisateurs de créer des points de rencontre qui viennent se « greffer » sur des événements publics existants. L'objectif est de favoriser les rencontres autour de ces événements.
L'événement public (festival, conférence, salon…) n'est qu'un prétexte et un point d'ancrage temporel et géographique : la valeur produite par l'app, c'est le point de rencontre que les utilisateurs viennent y greffer pour se retrouver.
Application web mobile-first. Stack : Bun + React + NextGraph (P2P, local-first, chiffré de bout en bout).
Modèle fonctionnel
Tous les utilisateurs sont authentifiés — il n'y a pas d'accès anonyme à l'app.
Acteurs
- Utilisateur — toute personne ayant un compte (un wallet NextGraph). Tous les acteurs ci-dessous sont des spécialisations d'un utilisateur dans un contexte donné.
- Connexion (« ami ») — un autre utilisateur avec qui je suis connecté. Sert à scoper les listes (« mes amis qui participent à… ») et la confiance.
- Déclarant d'un événement — l'utilisateur qui a inséré l'événement dans Festipod. N'est pas (forcément) un organisateur de l'événement réel : c'est juste quelqu'un qui le référence pour que d'autres puissent y attacher des points de rencontre.
- Hôte d'un point de rencontre — l'utilisateur qui a créé un point de rencontre rattaché à un événement.
- Inscrit à un point de rencontre — un utilisateur qui s'est inscrit à un point de rencontre. De fait, il devient participant à l'événement parent.
- Membre d'une communauté d'intérêt — un utilisateur abonné à une communauté pour découvrir les événements qu'elle référence.
Concepts métier
- Point de rencontre — l'unité de valeur de l'app. Un moment de rencontre proposé par un hôte à un endroit et à un horaire donnés, greffé sur un événement public. C'est ce à quoi on s'inscrit (on ne s'inscrit pas à un événement). Sans points de rencontre, un événement Festipod n'a pas d'intérêt.
- Événement — l'ancrage. Un événement public réel (festival, conférence, salon, exposition…) référencé dans Festipod pour servir de support à des points de rencontre. C'est simplement un prétexte (titre, dates, lieu, thèmes) ; le déclarant n'est pas l'organisateur officiel de l'événement, juste celui qui l'a inscrit dans Festipod.
- Communauté d'intérêt — un groupement thématique d'utilisateurs. Sert principalement à découvrir des événements (via abonnement) et à délimiter les périmètres de référencement.
- Liste curated — une liste d'événements éditorialisée (par un utilisateur ou une communauté), distincte de « les événements que j'ai déclarés » ou « les événements de la communauté ». Permet d'organiser/recommander.
- Connexion — lien de confiance entre deux utilisateurs (équivalent « ami »).
Fonctionnalités actuelles
Implémentées dans le code (écrans visibles via le router) :
- Authentification via wallet NextGraph
- Cycle de vie d'événement (déclaration, consultation, mise à jour)
- Cycle de vie de point de rencontre (rattaché à un événement)
- Inscription / désinscription à un point de rencontre
- Liste des participants à un événement
- Profil utilisateur, mise à jour, partage de profil
- Liste d'amis (connexions)
- Profil d'un autre utilisateur
Voir l'inventaire des routes et des écrans dans le concept app-architecture.
Défis ouverts
- Déduplication des événements en infra décentralisée. NextGraph étant P2P, rien n'empêche deux utilisateurs de déclarer indépendamment le même événement public (par ex. « Eurockéennes 2027 ») et de produire deux entrées distinctes. La dispersion qui en résulte fragmente les points de rencontre greffés et réduit leur visibilité — ce qui va à l'encontre de la fonction première de l'app. Pistes envisagées, non tranchées :
- proposer à l'utilisateur, lors de la déclaration, les événements déjà déclarés dans son réseau / ses communautés qui correspondent à sa saisie (recherche avant création) ;
- utiliser un identifiant externe canonique (URL officielle de l'événement, Wikidata, schema.org/Event) pour reconnaître les doublons et les présenter comme un seul événement à l'affichage ;
- laisser des curators (humains ou communautaires) fusionner / vetter les entrées canoniques.
Évolutions à venir
Identifiées comme nécessaires (notamment pour la scalabilité et la découverte) mais pas encore implémentées :
- Abonnement à une communauté d'intérêt pour découvrir ses événements (mécanisme de discovery distribué).
- Abonnement à un utilisateur pour suivre les événements qu'il déclare (sans nécessairement être ami).
- Listes curated — créer et partager des sélections d'événements éditorialisées.
- Multi-utilisateurs collaboratif : aujourd'hui chaque utilisateur a ses données isolées dans son wallet. Le passage en mode collaboratif (un point de rencontre vu par plusieurs personnes) suppose un refactor de la couche données. Voir le concept nextgraph-platform (briefs multi-store, matrice d'autorisations, wallet partagé, fork inbox).
Quick Start
bun install
bun run dev # Dev server avec HMR (port 3000)
Commandes utiles
bun run build # Build production vers dist/
bun run storybook # Parcourir écrans et composants
bun run test:cucumber # Tests BDD
bun run features:parse # Régénérer features.ts depuis les .feature
bun run steps:extract # Extraire les step definitions pour les tooltips
bun run build:orm # Régénérer l'ORM depuis les SHEX shapes
Documentation
- AGENTS.md — cœur : but produit, invariants, carte des concepts
- .project/concepts/ — toute la doctrine projet (savoir, règles, décisions, briefs), typée et livrée par hook au moment pertinent. 6 concepts :
functional-domain,app-architecture,tech-stack,data-layer,bdd-testing,nextgraph-platform.