- Blueprint définitif (docs/dev/BLUEPRINT-V2.md + JSON) : panel 4 visions + 3 juges, corpus doctrinal (Mon nom est personne, Une économie du don, SejeteralO), 4 critiques d'intégration, 3 vérificateurs, réparations - Corpus d'intention + lectures brutes archivés - Moteur porté Python→TS pur : threshold (inertie WoT, Smith, ComTech), nuancé 6 niveaux, DSL modeParams — 45 tests vitest, parité croisée vérifiée (Forgeron W=7224 T=120 ⇒ 94) - Contrat de domaine v2 (types/domain.ts) + lexique UI (lexicon.ts) - vitest + idb-keyval en dépendances Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
120 KiB
libreDecision v2 — Blueprint définitif
Généré par la ronde de conception ultracode du 2026-08-11 : panel de 4 visions + 3 juges, corpus doctrinal (« Mon nom est personne », « Une économie du don », SejeteralO), 4 critiques d'intégration, architecte final, 3 vérificateurs adversariaux, réparations intégrées.
Manifeste
Personne n'a jamais vécu en démocratie — alors nous la rêvons de jour, et nous la commençons à notre échelle. libreDecision attrape chaque décision en une phrase et propose le chemin le plus court : décide seul et trace-le, décide sous mandat, écoute puis décide, ou décidez ensemble — jamais plus lourd que l'enjeu. Ici on ne délègue pas, on mandate, et on reprend ; on n'est pas consulté, on s'instruit, puis on se prononce ; on ne s'affronte pas en deux camps, on règle des curseurs — et la position de chacun reste visible dans le faisceau, la médiane n'écrase personne, la dernière main est toujours humaine. Les concernés en premier lieu ont voix par construction, chacun voit pourquoi ; lever la main « ça me concerne » est un droit, pas une demande. Chaque décision dit ce qu'elle engage, ce qu'elle cherche à changer, et repasse l'épreuve du réel : rien n'est définitif, toute décision en vigueur garde sa poignée, se tromper est une donnée qu'on consigne. Les règles du jeu elles-mêmes — le Pacte — s'écrivent au fil de l'eau, par ceux qu'elles gouvernent, sous les boussoles Autonomie, Équilibre, Liaison. Du trio d'amis à la toile Ğ1 : le même geste, dix secondes, et l'observatoire qui apprend de nous. La ville change, le puits demeure.
Delta vs blueprint v1 (chaque changement justifié par la doctrine)
Δ1
Changement. CollectiveSettings.protocolByRange.inertialBinary renommé 'large' (Id résolu par le Pacte, le nom ne présume plus la méthode). Gabarits blank/informal/association/cooperative/commune/symmetric : 'large' pointe le protocole nuancé 6 niveaux (nuancedMinParticipants/nuancedThresholdPct existants). SEULS free-currency et les seeds Ğ1 pointent le binaire inertiel. R5 >50 hors clause : défaut nuancé ; R3 (clause visée) garde clauseByInertia — héritage Ğ1 (33+59 clauses, D30M50B.1G.2) intact, à sa place.
Raison doctrinale. « Le clivage binaire est un fléau contre la réflexion, un ennemi fatal : il la tue » ; « proscrire le binaire oui/non comme format par défaut » ; le binaire n'est légitime que « remis à sa place dans la boîte à outils » — jamais destination naturelle des grands corpus (le RIC disqualifié : « tir à la corde »).
Δ2
Changement. VoteMethod += 'parametric' (UI « Réglage collectif / décider au curseur »). Decision.paramSpec { params ≤7, constraint 'none'|'sum100', impactAttrKey? } ; ParamDef { key, label métier obligatoire, kind 'slider'|'share', min/max/step, unit?, baseline?, derived? } ; Vote.values?: number[] ; engine/parametric.ts (validateVote, resolveDerived, medianByElement, computeMyImpact, detectBimodality, crystallize). SPECS VERROUILLÉES (réparations) : médiane BASSE par élément (nombre pair ⇒ élément central inférieur — chaque valeur médiane est une valeur effectivement votée, donc conforme au step par construction, testée vitest) ; sum100 ⇒ EXACTEMENT une part 'derived' exigée à la création du paramSpec ; au vote, la géométrie clampe dynamiquement les parts votées pour que le derived reste dans ses bornes ET validateVote rejette sinon ; à la cristallisation, le derived n'est jamais agrégé : il est résolu depuis la médiane des parts votées, et s'il sort de ses bornes ⇒ clamp à la borne + renormalisation proportionnelle des parts non-derived (somme 100 restaurée, spec testée). Dégénérescences : 0 vote ⇒ baseline, jamais d'écran vide. Jamais dans le chemin 10 s : alternative « C'est un réglage » + suggérée quand la phrase contient nombre/%/montant.
Raison doctrinale. « Raisonner curseur / table de mixage » est LE geste central des deux livres ; leçons SejeteralO « voter = régler une forme, pas choisir un camp », « médiane élément par élément » avec l'invariant « le résultat est lui-même une position que chacun aurait pu proposer » — invariant qui EXIGE la médiane basse et la résolution sous contrainte ; « robustesse numérique de série : dégénérescences spécifiées ».
Δ3
Changement. RÉPARATION — Cristallisation = geste humain daté : la clôture paramétrique passe par VoteSession.status 'frozen' à closesAt (votes figés, médiane figée affichée) puis le GARANT (stewardIds, à défaut l'auteur) fait le geste « Cristalliser » → 'closed' + crystallizedById/crystallizedAt, la médiane devient position adoptée (ou rejected si quorum parametricMinParticipants non atteint — constaté au même geste). Relance au Fil du garant s'il tarde. Si detectBimodality positif, le geste affiche en évidence « Deux positions distinctes — cristalliser quand même / scinder / reformuler » : jamais de cristallisation automatique. Consent/nuancé/binaire gardent la clôture automatique (le moteur ne fait qu'y constater un seuil sur un contenu formulé par des humains) ; l'élection clôt automatiquement SAUF égalité (geste requis, cf. delta élection).
Raison doctrinale. Tension du corpus explicitement tranchée : « l'agrégat éclaire une délibération, la cristallisation reste un geste humain distinct et daté » ; chez SejeteralO la publication est distincte de l'agrégation vivante ; un calcul qui produit un contenu adopté sans geste humain = « décision algorithmique », repoussoir absolu.
Δ4
Changement. Matière : Decision.brief { context?, sources?, symptomsVsCauses?, effects: Effect[] } avec Effect { label, target?, measured? }. RÉPARATION — garde restreinte : ≥1 effet recherché exigé (state.ts, framing→voting / draft→voting) UNIQUEMENT pour les sessions collectives de poids binding/structural, gouverné par la clause de Pacte 'triage.requireEffects' ∈ 'none'|'structural'|'binding' (défaut 'binding' ; 'none' seedé dans blank ET informal) ; quand elle s'applique au structural, ≥1 effet doit porter une cible mesurable (target). Le consentement léger (weight light) et le trio n'y sont JAMAIS soumis ; le tunnel ne l'affiche jamais — la matière s'écrit en framing. L'épreuve du réel réutilise effects : tableau « recherché / cible / constaté » au-dessus des 3 boutons, mesures saisies par les measurerIds.
Raison doctrinale. « Il n'est pas consulté, il est instruit » ; « aucun vote sans matière » — mais « jamais plus lourd que l'enjeu » (manifeste) : le collectif règle sa propre exigence (couche méta), et la boucle « acquisition collective des expériences » est fermée par le même champ.
Δ5
Changement. RÉPARATION — Bloc « S'instruire » condensé : en salle de vote et fiche (framing/voting), un RÉSUMÉ COMPACT toujours visible avant les boutons (titre, effets recherchés, « Ce que ça engage ») avec versions+diff, avis/objections et provenance en DÉPLIABLES internes ; le bloc entier se replie automatiquement aux visites suivantes de la même personne (préférence locale par décision) — voter sur un titre nu reste impossible, re-voter reste léger.
Raison doctrinale. « Aucun vote sans matière » concilié avec la leçon SejeteralO « agir d'abord, comprendre ensuite » : pas de mur de scroll mobile à chaque re-vote d'un vote révisable.
Δ6
Changement. Ressources : Decision.resources { note, amount?, unit 'heures'|'€'|'DU'|string } + Decision.measurerIds à côté de stewardIds. Huile/eau : DU et € JAMAIS convertis ni additionnés (Observatoire : lignes par unité). RÉPARATION — localisation de la garde : la ligne « Ce que ça engage » est SUGGÉRÉE-dépliée dans le tunnel (toujours facultative, ne bloque jamais) ; l'exigence (weight != 'light' hors solo) vit UNIQUEMENT dans la garde d'ouverture de fenêtre/session de state.ts, même mécanique que brief.effects — le tunnel n'affiche jamais un champ obligatoire qui ne change pas le chemin.
Raison doctrinale. « Chaque décision implique une allocation de ressources, donc une décision connexe incontournable » (MNP p.89) ; « des équipes pour réaliser et des équipes pour mesurer » ; l'huile et l'eau ne se mélangent jamais ; et la métrique produit « 2 gestes / 10 s » reste intacte pour le cas nominal.
Δ7
Changement. Suppression de toute jauge de confiance sur les mandats. Bloc « Exercice du mandat » : faits comptés jamais synthétisés (« 12 décisions tracées · 1 objection · 2/2 rapports », listes cliquables). Le flux public devient « Feux de la rampe » (« toutes les décisions prises sous ce mandat — contestables »). Rendu seulement, zéro champ.
Raison doctrinale. « On peut évaluer des choses ; mais les personnes ? Alerte rouge » ; « la confiance exclut le contrôle » — une jauge de CONFIANCE sur un pouvoir contrôlé est un oxymore ; « Feux de la rampe » est le mot doctrinal exact.
Δ8
Changement. Protocol.ballot: 'open' | 'secret' (défaut 'open'), gouverné par le Pacte. Mode secret v2 : décomptes agrégés et commentaires anonymisés seuls, étiquette « secret d'affichage — démo » ; crypto réelle avec la synchro (v3, noté au contrat des stores).
Raison doctrinale. « Modalité de vote secret vs ouvert comme paramètre de chaque décision » (leçon fouloscopie) — « l'outil doit savoir faire les deux, et le périmètre en décide » ; même honnêteté d'étiquetage que la gravure.
Δ9
Changement. RÉPARATION — Historique des re-votes jamais opposable : la chaîne supersedesVoteId n'est JAMAIS affichée publiquement sous le nom d'un votant — visible du seul auteur du vote ; même en ballot 'open', seul le DERNIER vote actif d'une personne apparaît sous son nom, et le décompte agrégé est recalculé. Libellé : « Ton vote est remplaçable — ton historique n'est visible que de toi. » Invariant écrit au modèle + test.
Raison doctrinale. Tension « Mémoire vive vs droit à l'erreur » tranchée par le corpus : « archiver les mandats, pardonner les positions ; l'historique des votes révisés n'est jamais opposable à une personne » — sinon contrôle des personnes entre elles, le glas doctrinal.
Δ10
Changement. Gabarit 'blank' « Page blanche — observatoire d'abord » en tête de /creer : Pacte d'une seule clause (« nous consignons nos décisions ; le reste s'écrira au fil de l'eau ») + clause A1 finalité. RÉPARATION — cohérence settings : blank seede AU MINIMUM le protocole « Consentement » ; protocolByRange.consent est OBLIGATOIRE (invariant : tout collectif a un protocole de consentement, validé à l'import), les autres clés optionnelles ; resolveSettings spécifie le REPLI : toute clé non résolue retombe sur consent ; si consent introuvable (bundle corrompu), le triage route 'advice' avec bannière « Aucun protocole — crée-le ou décide sur avis » — jamais un crash. Les 5 gabarits existants réétiquetés « point de départ — tout est amendable par décision ».
Raison doctrinale. « L'observatoire des décisions AVANT de protocoliser » ; contrat social « écrit au fil de l'eau, sans préfigurer » ; « une chorégraphie du don ne se télécharge pas en .ods » — et la robustesse spécifiée (« jamais d'écran vide ») fait partie de la leçon SejeteralO.
Δ11
Changement. Gabarit 'symmetric' « Institution symétrique » : Pacte en transparence totale, TextDoc reference « Déclaration de symétrie », première décision « Déclarer notre symétrie », 2 mandats suggérés Ambassadeur et Négociateur. Données pures templates.ts.
Raison doctrinale. « Tu ne veux pas — Ô Institution — tant pis, je crée une symétrie » (chapitre entier de MNP) ; le poids = le volume des personnes qui l'habitent ; représentants « ambassadeurs et négociateurs, jamais décideurs ».
Δ12
Changement. DecisionRoute += 'record' + Decision.decidedHow. Alternative permanente « Déjà tranché — je le consigne » → adopted immédiat, zéro fenêtre (2 gestes). /decisions/memoire → /decisions/observatoire « L'Observatoire » ; ratio consigné/outillé = maturité ; maturation (famille R6, jamais bloquante) : ≥3 consignations partageant ≥2 tags ⇒ « Cette pratique est mûre — protocolise-la en quelques phrases » pré-remplissant une clause de Pacte.
Raison doctrinale. « Consigner comment elles se prennent dans les faits, avant de protocoliser » puis « traduire en protocoles de quelques phrases quand c'est mûr » — le flux consigner→observer→protocoliser devient le chemin ordinaire ; l'inversion des noms (Mémoire/Observatoire) est corrigée.
Δ13
Changement. chainKind += 'element' + Concern.priority?: 0|1|2|3. « Découper en micro-décisions » sur toute décision structural en framing ; onglet « Éléments » + « Cartographie de clôture » auto-générée. RÉPARATION — clôture du dossier : nouvelle transition framing→closed du parent, gardée par « tous les éléments dans un état terminal » ; jamais automatique : quand la garde est vraie, carte au Fil du garant « Le dossier est complet — clore et publier la cartographie de clôture » ; le geste clôt, la cartographie devient le PV du dossier. Libellé UI : « décision incrémentielle » réservé à CE mode.
Raison doctrinale. La « décision incrémentielle » est le nom PROPOSÉ PAR L'AUTEUR (modèle Taïwan) : « chacun navigue dans les points qui le concernent, pondère ses préférences » ; « cartographie de clôture visible de tous » — et la clôture est un geste humain, cohérent avec la cristallisation.
Δ14
Changement. Decision.baselineNote?: string (« Aujourd'hui : … ») suggéré routes advice/collective (jamais solo). Fiche : « Aujourd'hui → Proposé » ; ParamDef.baseline = marqueur « actuel » ; l'épreuve du réel relit l'écart. Les clauses gardent leur diff (qui EST leur baseline).
Raison doctrinale. Leçon SejeteralO : « la baseline : toujours afficher l'existant — une décision se lit comme un écart au statu quo, mesurable ».
Δ15
Changement. Person.attributes?: Record<string, number> (déclarés, édités dans /donnees, jamais affichés à autrui ni agrégés) + ParamSpec.impactAttrKey. RÉPARATION — computeMyImpact 'linear-share' SPÉCIFIÉ : ne s'applique QUE si constraint='sum100' ET resources.amount présent ET impactAttrKey présent ; pour chaque part p (derived inclus) : montant(p) = resources.amount × p/100 ; ma quote-part(p) = montant(p) × attr(moi) / Σ attr(déclarants du corpus) ; résultat par curseur + total, calculé aux trois positions (la mienne / médiane / baseline). attr(moi) absent ou Σ=0 ⇒ null (jamais un chiffre inventé — la carte montre alors seulement l'écart collectif en valeurs de curseurs). L'exemple seedé « budget d'ateliers 1 200 € × 'heures/mois' » devient calculable. Étiquette « estimation sur données déclarées ».
Raison doctrinale. Principe produit n°5 « qu'est-ce que ça change pour moi » ; SejeteralO « Mon prix au m³ » et son triptyque ma-position/médiane/référence — version local-first honnête, bornée, et désormais calculable ; le moteur sur données réelles importées reste v3.
Δ16
Changement. Composant Faisceau : nuancé → histogramme des 6 niveaux sous la jauge + commentaires en liste — jamais un simple adopté/rejeté ; paramétrique → strip-plot par curseur (points translucides anonymes, médiane trait épais, baseline marqueur creux). detectBimodality ⇒ bannière non bloquante « Deux positions distinctes se dessinent sur X — scinder la question ou changer de référentiel ? » ouvrant le découpage ou une reformulation.
Raison doctrinale. « La diversité reste visible : le faisceau » ; « la médiane peut masquer une distribution bimodale » ; le « changement de référentiel » doctrinal devient un geste produit.
Δ17
Changement. Garde d'affluence dans state.ts : concernEscalateRatio atteint ⇒ la fenêtre/session NE PEUT PAS se clore sans traitement — élargir d'un cran (1 tap, re-triage journalisé) ou maintenir avec Decision.scopeKeptNote motivée publique, affichée en évidence.
Raison doctrinale. La montée d'échelle est DÉCIDÉE (« on en change quand le peuple le décide ») — l'asymétrie de dérogation appliquée au périmètre, LE lieu doctrinal de la subsidiarité.
Δ18
Changement. Circle.kind?: 'place'|'theme'|'team' — iconographie, groupement, ordre des suggestions seulement, jamais un droit. Hint non bloquant si 'team' > 12. Gabarits typent leurs cercles.
Raison doctrinale. Les 3 vecteurs du périmètre : le topologique gagne une existence déclarative sans machinerie géo ; « au-delà de douze, les complications commencent ».
Δ19
Changement. Collective.lineage?: { sourceSlug, exportedAt, sha256 } posé à l'import d'un bundle étranger (« essaimé de … »). PAS de parentCollectiveId ; pattern v3 documenté : une grappe = un collectif ordinaire dont les membres sont des mandataires d'autres collectifs.
Raison doctrinale. La « scission féconde » exige la mémoire d'origine ; le corpus proscrit la fédération dans le modèle (« représentation, voix unique ») — composition pure.
Δ20
Changement. Clause « A1 — Notre finalité » seedée dans chaque gabarit (texte sous vote permanent, défaut portant les boussoles), affichée sous le nom du collectif ; préambule de Pacte proposé et modifiable portant Autonomie–Équilibre–Liaison.
Raison doctrinale. « Chaque groupe doit se donner une finalité » ; « boussoles proposées, jamais imposées » ; coexistence « sans être concurrents ».
Δ21
Changement. Passerelles hors-écran : (1) CSS print A4 + « Imprimer » sur fiche (PV) et salle (affiche de session) ; (2) « agir en tant que » requalifié « mode atelier » via recordedById?: Id sur Vote/Advice/Objection/Assent (« saisi par X pour Y — atelier du ») ; (3) micro-copy « à trancher en présence ? » sur structural.
Raison doctrinale. « Toute décision numérique doit être praticable sans écran » ; « intermédiation humaine pour les démunis du numérique » ; « plus le numérique prend de place, plus il faut en donner à la relation humaine ».
Δ22
Changement. frontend/app/lexicon.ts : source UNIQUE des libellés français + test vitest anti-lexique (déléguer/délégation, dette, retard, en souffrance, corpus, snapshot, CONTRE, verdict, triage, oligarchie, peuple adulte…). Trois règles de ton : (1) tutoyer la personne dans ses gestes ; (2) actes collectifs à l'infinitif (« Adopter notre Pacte ») ; (3) sujet grammatical des phrases générées = le collectif ou la légitimité, jamais la formule/le système.
Raison doctrinale. « L'auteur voudra retrouver ses mots exacts » ; « politiquement explicable » : c'est la légitimité qui exige, pas les maths — l'algorithme au service, jamais souverain.
Δ23
Changement. Renommages UI (types inchangés) : carte /decider « Le chemin » (explanation seule, code R* au dépliable/PV/journal) ; « Concerné·e en premier lieu / en second lieu » (pastille pleine / main levée) ; chip « Concernés » ; panneau « Périmètre » ; « Mandats — les pouvoirs confiés » ; Fil « À toi de décider » compteur nu ; « Qui vote : 11 personnes — liste arrêtée le 12/08 » (W/T/M/B/G/C à l'Atelier seulement) ; échelle nuancée « Pas du tout · Plutôt non · Réservé · Plutôt oui · Oui · Tout à fait » ; revoyure « L'épreuve du réel » / « Le réel a-t-il suivi ? » / « Ça tient / À revoir / À révoquer » ; « C'est urgent » + badge « Décidé en urgence — le collectif ratifie » ; « Révoquées — ce qu'on en a appris » ; routes à la première personne ; sous-titre du Pacte « Notre contrat social — sacralisé, jamais immuable » ; fenêtre framing « N jours pour s'instruire et formuler des contre-propositions ».
Raison doctrinale. « On ne délègue pas, on abandonne » ; « périmètre » est LE concept pivot ; le registre dette/peur est le couple de l'asservissement ; « le verdict n'est jamais un juge » ; « toute étiquette évacue la moitié de la population ».
Δ24
Changement. Bouton « Explorer » en salle paramétrique : sandbox de la médiane, curseurs libres, dérivés + « Pour moi » recalculés en live, « Partir de la médiane / du statu quo / de ma position », « En faire ma position », étiquette « exploration — ne compte pas ». Rétro-manipulation résultat→contrainte : v3.
Raison doctrinale. Leçon SejeteralO « manipulation de l'agrégat pour explorer » ; « déplacer, évaluer, ajuster, recommencer » ; abaisse le coût psychologique du premier vote.
Δ25
Changement. Dépersonnalisation d'affichage : contenu et diff d'abord, auteur replié (visible au tap) dans le bloc formulation et la salle. Pas de mode anonyme réel en v2 mono-poste.
Raison doctrinale. « On débat du contenu, pas de l'auteur » (@personne) — anti ad-hominem, anti-sauveur.
Δ26
Changement. Désirabilité : tampon 井 incliné -10° « Décidé » (translateY 0.12s) + « C'est décidé — et révisable, comme tout ici » ; /creer se signe « Osons, nous verrons » ; manifeste public sans vocabulaire de livraison, clos sur « La ville change, le puits demeure » ; termes-diagnostics réservés à la page-manifeste.
Raison doctrinale. « Rendre désirable plutôt que convaincre » exige que décider soit beau ; le couple plaisir+rêve.
Δ27
Changement. RÉPARATION — Silence-vaut-accord résolu par l'entité Assent : « Ça me va » est stocké (Assent { decisionId, personId, recordedById? }, append-only, dans le Bundle). Règle unique des fenêtres d'objection : à échéance sans objection ouverte — reversibility 'easy' ⇒ adopted (silence vaut accord) ; costly/irreversible ⇒ adopted SEULEMENT si ≥1 Assent d'un concerné autre que l'auteur, sinon la fenêtre se PROLONGE d'un cran (windowEndsAt + objectionWindowHours, relance au Fil des concernés) — jamais d'adoption par pur silence hors easy. La contradiction v1 est levée, le comportement à échéance est défini.
Raison doctrinale. « Il ne cède pas un consentement, il négocie — chaque virage » : hors du réversible, l'accord est un geste, pas une absence ; et le silence-vaut-accord réservé au réversible était déjà l'engagement du blueprint — désormais tenu mécaniquement.
Δ28
Changement. RÉPARATION — Élection codable : Vote.choicePersonId?: Id (invariant : EXACTEMENT un de value / values / choicePersonId selon la méthode, validé par le store). Vote déposé sans choicePersonId = blanc (compte pour la participation, pas pour la désignation). engine/threshold.ts += electionResult(votes, corpus) : quorum optionnel electionMinParticipants (FormulaParams) sinon rejected ; désignation à la PLURALITÉ simple des derniers votes actifs ; ÉGALITÉ ⇒ résultat 'tie' avec exAequoIds — l'outil ne départage JAMAIS : la clôture propose une session de départage chaînée restreinte aux ex æquo (pré-remplie), ou le tirage au sort entre ex æquo si le protocole du Pacte l'a prévu d'avance. Salle élection : désignation par avatar, bouton « Vote blanc », règle affichée en une phrase.
Raison doctrinale. L'élection « remise à sa place dans la boîte à outils » doit être réellement outillée pour les mandats ; « l'outil représente, agrège et éclaire — il ne décide jamais » : une égalité n'est pas tranchée par un algorithme, et le tirage au sort n'est légitime que décidé d'avance par le Pacte.
Δ29
Changement. RÉPARATION — canTransition étendu : engine/state.ts expose canTransition(decision, to, ctx: { concerns: Concern[], settings: CollectiveSettings, session?: VoteSession, children?: Decision[], assents?: Assent[], objections?: Objection[] }) — les gardes d'affluence (|déclarés| vs |calculés|), de matière (requireEffects), de ressources, de frontière (windowSuspendedAt), d'Assent et de clôture de dossier (children terminaux) ont leurs entrées dans la signature ; le contexte est assemblé par le store, la fonction reste pure et testable.
Raison doctrinale. Machine à états UNIQUE réellement implémentable : toute mutation passe par canTransition, et les gardes doctrinales (subsidiarité, instruction, consentement) sont du code testé, pas des intentions.
Δ30
Changement. Verdict enrichi : parametricHint (nombre/%/montant détecté), alternatives permanentes « C'est un réglage — décidez au curseur », « Déjà tranché — je le consigne », binaire avec son coût (« deux camps, un perdant ») ; TriageContext.similarRecordedCount ; suggestion.kind += 'protocolize'.
Raison doctrinale. « L'outil représente, agrège et éclaire — il ne décide jamais » : les modalités doctrinales sont proposées, jamais imposées.
Modèle de domaine
// ─────────────────────────────────────────────────────────────
// libreDecision v2 — noyau de domaine DÉFINITIF (frontend/app/types/domain.ts)
// Local-first, prêt-synchro. Identifiants anglais, UI française via app/lexicon.ts.
// INVARIANT 1 : LA DÉCISION EST L'OBJET PIVOT — mandats, clauses, révocations,
// ratifications, éléments de dossier en sont des spécialisations chaînées.
// INVARIANT 2 : l'unification est un fait d'architecture, JAMAIS un vocabulaire d'UI.
// INVARIANT 3 : aucun agrégat/score sur une personne, nulle part — on évalue des choses.
// INVARIANT 4 : aucun calcul ne produit un contenu adopté sans geste humain daté
// (cristallisation paramétrique, départage d'élection, clôture de dossier).
// ─────────────────────────────────────────────────────────────
type Id = string // UUIDv7 — triable par temps, prêt pour la synchro
type ISODate = string // ISO 8601 UTC
type Json = string | number | boolean | null | Json[] | { [k: string]: Json }
// Toute entité : scoping collectif + horloge de synchro + soft-delete.
// Prêt-synchro : jamais de suppression physique (archivedAt) ;
// Vote/Advice/Objection/Assent append-only (merge trivial) ; le reste LWW par updatedAt.
interface Entity {
id: Id
collectiveId: Id
createdAt: ISODate
updatedAt: ISODate
archivedAt?: ISODate
}
// ── Le tenant ────────────────────────────────────────────────
type CollectiveTemplate =
| 'blank' // « Page blanche — observatoire d'abord » : Pacte 1 clause + A1, protocole Consentement SEUL
| 'informal' | 'association' | 'cooperative' | 'commune'
| 'free-currency' // héritage Ğ1 — SEUL gabarit où protocolByRange.large = binaire inertiel
| 'symmetric' // « Institution symétrique » — le geste fondateur militant (MNP)
interface Collective {
id: Id
slug: string
name: string
color: string // hex accent
icon: string // i-lucide-*
template: CollectiveTemplate
isTransparent: boolean
pactDocId: Id // TextDoc role='pact' — les réglages EN document voté
rootCircleId: Id
lineage?: { // essaimage : posé auto à l'import d'un bundle d'un autre collectif
sourceSlug: string; exportedAt: ISODate; sha256: string
} // PAS de parentCollectiveId — la fédération n'est jamais une table
createdAt: ISODate
updatedAt: ISODate
}
// ── Les gens ─────────────────────────────────────────────────
interface Person extends Entity {
displayName: string
isMe: boolean // profil local (v2 mono-poste ; « mode atelier » pour la saisie en présence)
attributes?: Record<string, number> // attributs DÉCLARÉS par la personne (clés libres : 'heures/mois'…),
// édités dans /donnees — servent UNIQUEMENT la carte « Pour moi » ;
// jamais affichés à autrui, jamais agrégés (invariant 3)
duniterAddress?: string // SS58 — réservé auth Duniter (hors scope v2)
wotStatus?: 'member' | 'smith'
}
interface Circle extends Entity {
name: string
purpose: string
kind?: 'place' | 'theme' | 'team' // lieu / thème / équipe — iconographie, groupement des sélecteurs,
// ordre des suggestions SEULEMENT, jamais un droit ;
// hint non bloquant si 'team' > 12 (« au-delà de douze… »)
memberIds: Id[] // v2 : liste nominative = SEULE règle d'appartenance (auditable)
parentCircleId?: Id // emboîtement (couches d'oignon) — racine = rootCircle
domains: string[] // tags — SUGGESTION/découverte uniquement, jamais un droit de vote
}
// Corpus d'une décision = union CALCULABLE : membres des cercles du scope
// ∪ personnes nommées ∪ titulaires des mandats dont le domaine intersecte.
// Chaque inclusion garde sa raison (Concern.reason) — auditable en un tap.
// ── LA DÉCISION (pivot) ──────────────────────────────────────
type Reversibility = 'easy' | 'costly' | 'irreversible'
type Weight = 'light' | 'binding' | 'structural'
type DecisionRoute =
| 'solo' // « Je décide » (trace facultative hors mandat)
| 'mandate' // « Je décide, sous mandat » (trace obligatoire + fenêtre d'objection)
| 'transmit' // « Je transmets » — le mandat d'un autre couvre
| 'advice' // « J'écoute, puis je décide »
| 'collective' // « Nous décidons » (modalité du protocole)
| 'record' // « Déjà tranché — je le consigne » : l'observatoire en un geste, adopted immédiat
type DecisionStatus =
| 'draft' | 'advice' | 'objection' | 'framing' | 'voting'
| 'adopted' | 'rejected' | 'revoked' | 'transmitted' | 'closed'
type TriageRule = 'R-U' | 'R0a' | 'R0b' | 'R0c' | 'R2' | 'R3' | 'R4' | 'R5' | 'R6'
// La matière à réflexion — « il n'est pas consulté, il est instruit »
interface Effect {
label: string // effet recherché, une ligne
target?: string // cible mesurable optionnelle (« ≤ 400 € », « +5 membres »)
measured?: { note: string; at: ISODate; byId: Id } // constaté — saisi par les measurerIds à la revoyure
}
interface Brief {
context?: string
sources?: { label: string; url: string }[]
symptomsVsCauses?: string // qualification doctrinale du sujet
effects: Effect[] // GARDE (state.ts, ouverture de session collective UNIQUEMENT) :
// exigé si weight ∈ requireEffects du Pacte ('binding' couvre binding+structural) ;
// structural sous garde ⇒ ≥1 effet avec target. JAMAIS pour weight 'light',
// JAMAIS dans le tunnel, JAMAIS en solo/record.
}
// La décision connexe de ressources — « chaque décision implique une allocation »
interface Resources {
note: string // « Ce que ça engage » en une phrase
amount?: number
unit?: 'heures' | '€' | 'DU' | string // huile/eau : unités JAMAIS converties ni additionnées entre elles
}
// Le réglage collectif — « raisonner curseur / table de mixage »
type ParamKind = 'slider' | 'share' // 'curve' réservé v3 (SejeteralO complet)
interface ParamDef {
key: string
label: string // libellé métier OBLIGATOIRE — jamais de a, b, c bruts
kind: ParamKind
min: number; max: number; step: number
unit?: string
baseline?: number // statu quo — marqueur creux toujours affiché
derived?: boolean // résolu par la contrainte, jamais voté — étiquette « calculé »
}
interface ParamSpec {
params: ParamDef[] // ≤ 7 (leçon SejeteralO : petit espace manipulable)
constraint: 'none' | 'sum100' // sum100 : les 'share' totalisent 100 ; EXACTEMENT UNE part 'derived'
// exigée (validé à la création du spec) — la variable d'absorption ;
// résolue linéairement (100 − Σ autres)
impactAttrKey?: string // clé de Person.attributes → carte « Pour moi » ('linear-share')
}
interface Decision extends Entity {
authorId: Id
title: string // LA phrase de capture — seul champ obligatoire
body?: string // progressive disclosure
brief?: Brief // matière — cf. garde d'ouverture ci-dessus
baselineNote?: string // « Aujourd'hui : … » — suggéré routes advice/collective, jamais solo
resources?: Resources // SUGGÉRÉ-déplié au tunnel (jamais bloquant) ; EXIGÉ à la garde
// d'ouverture de fenêtre/session (state.ts) si weight != 'light' hors solo
tags: string[] // découverte + récurrence — JAMAIS un corpus
// — verdict de triage (pré-rempli par le moteur, ajustable) —
reversibility: Reversibility
weight: Weight
urgent: boolean
scope: {
selfOnly: boolean
circleIds: Id[] // plancher : élargir libre, JAMAIS restreindre sous le calcul
personIds: Id[]
}
route: DecisionRoute
triageRule: TriageRule // relégué au dépliable « pourquoi ? », PV, journal — jamais en carte
routeOverridden: boolean
overrideNote?: string // OBLIGATOIRE si allègement (asymétrie de dérogation)
scopeKeptNote?: string // affluence atteinte + périmètre maintenu ⇒ note motivée PUBLIQUE obligatoire
// — spécialisations chaînées (le pliage génératif) —
underMandateId?: Id // décidée SOUS ce mandat → feux de la rampe du mandat
createsMandate?: MandateDraft
amendsClauseId?: Id
protocolId?: Id // résolu par le Pacte, ajustable en alourdissant
paramSpec?: ParamSpec // si modalité paramétrique (« Réglage collectif »)
parentDecisionId?: Id
chainKind?: 'ratification' | 'revision' | 'revocation' | 'element'
// 'element' : micro-décision d'un dossier découpé (« décision incrémentielle »).
// Parent d'éléments : transition framing→closed gardée par
// « tous les enfants element en état terminal » — GESTE du garant
// (carte au Fil « Le dossier est complet — clore »), jamais automatique ;
// la cartographie de clôture devient le PV du dossier.
decidedHow?: string // route 'record' : « comment ça s'est décidé », une phrase
// — temps & fenêtres —
status: DecisionStatus
windowEndsAt?: ISODate
windowSuspendedAt?: ISODate // objection de frontière → compte à rebours SUSPENDU (visible)
decidedAt?: ISODate
review?: Review // obligatoire si irreversible || structural
sunsetAt?: ISODate
stewardIds: Id[] // garants — RÉALISENT (et cristallisent le paramétrique, clôturent le dossier)
measurerIds: Id[] // mesureurs — CONSTATENT (saisissent Effect.measured) : la paire doctrinale
// — preuve & visibilité —
visibility: 'private' | 'scope' | 'collective' // anti-flicage : trace maîtrisée hors mandat
engraving?: Engraving
}
interface Review {
dueAt: ISODate
verdict?: 'confirmed' | 'revise' | 'revoke' // UI : « Ça tient / À revoir / À révoquer »
note?: string
decidedAt?: ISODate
}
interface Engraving {
sha256: string
engravedAt: ISODate
proofLevel: 'local' // v2 : UNIQUEMENT 'local' — « empreinte locale — démo »
ipfsCid?: string // réservé v3 — jamais affiché tant que non réel
chainRef?: string
}
// ── Inclusion & fenêtres ─────────────────────────────────────
interface Concern extends Entity {
decisionId: Id
personId: Id
origin: 'computed' | 'declared' // UI : « concerné·e en premier lieu » / « en second lieu »
reason: string // « membre du cercle Forgerons » — affichée au tap
declaredNote?: string
beforeSnapshot: boolean // avant liste arrêtée ⇒ vote ; après ⇒ voix consultative
priority?: 0 | 1 | 2 | 3 // « pondère tes enjeux » — par élément d'un dossier ;
// nourrit la cartographie de clôture, jamais le droit de vote
}
interface Assent extends Entity { // « Ça me va » STOCKÉ — l'accord explicite des fenêtres d'objection
decisionId: Id
personId: Id
recordedById?: Id // mode atelier
}
// RÈGLE DES FENÊTRES D'OBJECTION (state.ts) : à échéance sans objection ouverte —
// easy ⇒ adopted (silence vaut accord) ; costly/irreversible ⇒ adopted SEULEMENT si
// ≥1 Assent d'un concerné ≠ auteur, sinon PROLONGATION d'un cran (+objectionWindowHours,
// relance au Fil) — jamais d'adoption par pur silence hors easy.
interface Objection extends Entity {
decisionId: Id
personId: Id
kind: 'content' | 'boundary' // boundary SUSPEND la fenêtre — la frontière prime sur le fond
argument: string
status: 'open' | 'withdrawn' | 'integrated' | 'escalated'
resolutionNote?: string
recordedById?: Id // mode atelier : « saisi par X pour Y — atelier du <date> »
}
interface Advice extends Entity {
decisionId: Id
personId: Id
position: 'favorable' | 'reserved' | 'unfavorable'
note?: string
recordedById?: Id // mode atelier
}
// ── Le pouvoir ───────────────────────────────────────────────
interface Mandate extends Entity {
title: string
holderId: Id
originDecisionId: Id // le mandat EST né d'une décision — cliquable
domain: { circleIds: Id[]; tags: string[] }
startsAt: ISODate
endsAt: ISODate // toujours borné
electorCircleId: Id // corpus d'élection = corpus de révocation
nominationMethod: 'ratified-self' | 'election-no-candidate' | 'nuanced-vote' | 'consent' | 'draw' | 'rotation'
reports: MandateReport[]
status: 'proposed' | 'active' | 'expired' | 'revoked'
// AUCUN agrégat/jauge : la fiche affiche des faits comptés (« Exercice du mandat »),
// jamais synthétisés ; le flux public = « Feux de la rampe »
}
interface MandateReport { dueAt: ISODate; deliveredAt?: ISODate; content?: string }
interface MandateDraft {
title: string
domainCircleIds: Id[]
domainTags: string[]
durationDays: number
reportEveryDays?: number
}
// ── Les textes ───────────────────────────────────────────────
interface TextDoc extends Entity {
slug: string
title: string
role: 'pact' | 'reference'
description: string // Pacte seedé : « Notre contrat social — sacralisé, jamais immuable »
provenance?: Provenance
}
interface Provenance {
sources: { title: string; url: string; date?: string; version?: string }[]
voteRecord?: {
url: string; modeParams: string; period: string
result: { for: number; against: number; invalid?: number; wotSize: number; thresholdRequired: number; status: string }
}
contributors?: string[]
notes?: string // typos des textes votés : SIGNALÉES, jamais corrigées sans vote
}
type InertiaPreset = 'low' | 'standard' | 'high' | 'max'
// Câblage RÉEL : low={M:50,G:0.1} standard={M:50,G:0.2} high={M:60,G:0.4} max={M:66,G:0.6} (B=0.1, C=0)
interface Clause extends Entity {
docId: Id
section: string
position: number
code: string
title: string
inertia: InertiaPreset
currentVersionId?: Id
settingKey?: string // clauses du Pacte : ex. 'triage.smallGroupMax'
}
interface ClauseVersion extends Entity {
clauseId: Id
decisionId: Id // TOUTE version naît d'une décision (y compris fondatrice seedée)
versionLabel: string
content: string
settingValue?: Json
status: 'current' | 'proposed' | 'superseded' | 'rejected'
adoptedAt?: ISODate
}
// ── Modalités & vote ─────────────────────────────────────────
type VoteMethod = 'consent' | 'binary' | 'nuanced' | 'parametric' | 'election'
// UI : consentement · pour/contre (outil de dernier recours) · nuancé ·
// « Réglage collectif » (au curseur) · élection
interface Protocol extends Entity {
name: string
method: VoteMethod
description: string
durationDays: number
ballot: 'open' | 'secret' // décidé par le Pacte — défaut 'open' (feux de la rampe).
// v2 mono-poste : « secret d'affichage — démo » (décomptes agrégés
// et commentaires anonymisés seuls) ; crypto réelle avec la synchro (v3)
formula: FormulaParams
modeParams: string // DSL DÉRIVÉ affiché : 'D30M50B.1G.2S.1' — jamais édité à la main
pactClauseId?: Id // référencé par le Pacte ⇒ le modifier = amender le Pacte
}
interface FormulaParams {
majorityPct: number // M
baseExponent: number // B (défaut 0.1)
gradientExponent: number // G — l'inertie
constantBase: number // C (défaut 0)
smithExponent?: number
techcommExponent?: number
nuancedMinParticipants?: number
nuancedThresholdPct?: number
parametricMinParticipants?: number // quorum du réglage collectif — non atteint ⇒ rejected au geste
electionMinParticipants?: number // quorum de l'élection — non atteint ⇒ rejected
tieBreak?: 'runoff' | 'draw' // départage d'élection DÉCIDÉ D'AVANCE par le Pacte
// (défaut 'runoff' : session de départage chaînée entre ex æquo ;
// 'draw' : tirage au sort entre ex æquo — jamais choisi par le moteur)
}
interface VoteSession extends Entity {
decisionId: Id
protocolId: Id
corpusPersonIds: Id[] // SNAPSHOT à l'ouverture — UI : « liste arrêtée le … »
corpusSize: number // W — plus jamais 0 (lettre réservée à l'Atelier)
opensAt: ISODate
closesAt: ISODate
status: 'open' | 'frozen' | 'closed'
// 'frozen' : PARAMÉTRIQUE uniquement — à closesAt les votes sont figés, la médiane figée est affichée,
// et la session ATTEND le geste humain du garant (relance au Fil s'il tarde).
crystallizedById?: Id // le geste « Cristalliser » : qui, quand — daté et signé
crystallizedAt?: ISODate
outcome?: 'adopted' | 'rejected' | 'tie'
// 'tie' : élection à égalité — l'outil ne départage JAMAIS : session de départage chaînée
// (restreinte aux ex æquo) ou tirage au sort SI le protocole l'a prévu (tieBreak).
// Consent/nuancé/binaire : open→closed automatique à closesAt (le moteur constate un seuil
// sur un contenu formulé par des humains). Paramétrique : open→frozen→closed (geste).
// INVARIANT : tallies, histogrammes ET médianes TOUJOURS recalculés depuis Vote[] — jamais dénormalisés.
}
interface Vote extends Entity {
sessionId: Id
voterId: Id
value?: 'for' | 'against' | 0 | 1 | 2 | 3 | 4 | 5
// nuancé UI : 0 Pas du tout · 1 Plutôt non · 2 Réservé · 3 Plutôt oui · 4 Oui · 5 Tout à fait
values?: number[] // paramétrique — ordre de paramSpec.params HORS derived
choicePersonId?: Id // élection — désignation ; vote déposé sans choicePersonId = BLANC
// (compte pour la participation, pas pour la désignation)
// INVARIANT : EXACTEMENT UN de value / values / choicePersonId selon la méthode (validé par le store) —
// sauf blanc d'élection (aucun des trois, méthode 'election' seulement)
comment?: string // OBLIGATOIRE si négatif (against, 0, 1)
supersedesVoteId?: Id // re-vote audité — PRIVACY : la chaîne n'est JAMAIS affichée
// publiquement sous le nom d'un votant ; visible du SEUL auteur ;
// même en ballot 'open', seul le DERNIER vote actif apparaît sous un nom,
// le décompte agrégé est recalculé (« pardonner les positions »)
recordedById?: Id // mode atelier
}
// ── Réglages résolus du Pacte (fonction pure — PAS une table settings) ──
interface CollectiveSettings { // resolveSettings(pactClauses, versions)
triage: {
smallGroupMax: number // défaut 5
collectiveMin: number // défaut 50
consentMax: number // défaut 7
objectionWindowHours: number // défaut 48
adviceWindowHours: number // défaut 72
framingDays: number // défaut 14 — « s'instruire et formuler des contre-propositions »
concernEscalateRatio: number // défaut 0.5 — atteint ⇒ traitement OBLIGATOIRE (garde state.ts)
recurrenceThreshold: number // défaut 3
reviewDelayDays: number // défaut 90
requireEffects: 'none' | 'structural' | 'binding'
// clé 'triage.requireEffects' — gouverne la garde matière des sessions COLLECTIVES :
// 'binding' = binding ET structural (défaut association/cooperative/commune/free-currency/symmetric) ;
// 'structural' = structural seul ; 'none' seedé dans blank et informal.
// Sous garde, structural ⇒ ≥1 effet avec cible mesurable (target).
}
protocolByRange: {
consent: Id // OBLIGATOIRE — invariant : tout collectif a un protocole Consentement
// (tout gabarit le seede, l'import le valide)
nuanced?: Id
large?: Id // ex-inertialBinary : le nom ne présume plus la méthode.
// Gabarits standard → nuancé ; free-currency → binaire inertiel
parametric?: Id // « Réglage collectif »
election?: Id
clauseByInertia?: Record<InertiaPreset, Id> // l'héritage Ğ1 vit ici, intact
}
// REPLI SPÉCIFIÉ (resolveSettings) : toute clé optionnelle non résolue retombe sur consent ;
// consent introuvable (bundle corrompu) ⇒ le triage route 'advice' avec bannière
// « Aucun protocole — crée-le ou décide sur avis » — JAMAIS un crash.
}
// ── Moteurs purs (frontend/app/engine/ — implémentation UNIQUE, vitest) ──
// threshold.ts : wotThreshold(W,T,{M,B,G,C}) — port exact de threshold.py (ceil) ;
// + smithThreshold, techcommThreshold, nuancedResult (histogramme 6 niveaux exposé) ;
// + electionResult(votes, corpus, formula) → { winnerId } | { outcome:'tie', exAequoIds }
// | { outcome:'rejected' } — pluralité simple des derniers votes actifs, blancs comptés
// en participation, quorum electionMinParticipants, JAMAIS de départage moteur.
// SPEC EXÉCUTABLE : cas Forgeron W=7224 T=120 M50 B.1 G.2 ⇒ 94.
// parametric.ts : validateVote(spec, values) — bornes/step/clamp dynamique sum100, sanitize NaN/inf ;
// resolveDerived(spec, values) — sum100 linéaire (100 − Σ autres), étiquette « calculé » ;
// medianByElement(votes) — MÉDIANE BASSE par curseur (pair ⇒ élément central inférieur :
// chaque valeur médiane est une valeur effectivement votée ⇒ step respecté par
// construction — « une position que chacun aurait pu proposer », testé) ;
// crystallize(spec, votes) — médiane basse des parts votées PUIS resolveDerived ;
// derived hors bornes ⇒ clamp à la borne + renormalisation proportionnelle des parts
// non-derived (somme 100 restaurée) — spec testée ;
// computeMyImpact(spec, resources, values, myAttr, corpusAttrs) — 'linear-share' :
// UNIQUEMENT si constraint='sum100' && resources.amount && impactAttrKey ;
// montant(p) = amount × p/100 ; maPart(p) = montant(p) × myAttr / Σ corpusAttrs ;
// par curseur + total, aux trois positions (mienne/médiane/baseline) ;
// myAttr absent ou Σ=0 ⇒ null — JAMAIS un chiffre inventé ;
// detectBimodality(values[]) — plus grand écart entre valeurs triées vs dispersion,
// jamais bloquant. Dégénérescences : 0 vote ⇒ baseline, jamais d'écran vide.
// triage.ts : triage(input, ctx, settings) → Verdict (règles ci-dessous)
// modeParams.ts : parse/format du DSL
// settings.ts : resolveSettings(clauses, versions) → CollectiveSettings (défauts + replis spécifiés)
// state.ts : canTransition(decision, to, ctx: { concerns: Concern[], settings: CollectiveSettings,
// session?: VoteSession, children?: Decision[], assents?: Assent[],
// objections?: Objection[] }) — machine à états UNIQUE, gardes :
// windowSuspendedAt (frontière) ; affluence (ratio ⇒ élargir ou scopeKeptNote) ;
// matière (requireEffects) ; ressources (weight != light hors solo) ;
// Assent (fenêtres non-easy) ; dossier (framing→closed si children terminaux, geste) ;
// cristallisation (paramétrique : closed exige crystallizedById).
// impact.ts : computeConcerned(scope, circles, mandates) → { personId, reason }[]
// app/lexicon.ts : source UNIQUE des libellés français + test vitest anti-lexique
// (déléguer, délégation, dette, retard, en souffrance, corpus, snapshot,
// CONTRE, verdict, triage, oligarchie, peuple adulte…).
// Règles de ton : (1) tutoyer la personne dans ses gestes ;
// (2) actes collectifs à l'infinitif (« Adopter notre Pacte ») ;
// (3) sujet des phrases générées = personnes/collectif, JAMAIS la formule.
interface TriageInput {
title: string; tags: string[]
scope: Decision['scope']
reversibility: Reversibility
weight: Weight
urgent: boolean
amendsClauseId?: Id
}
interface TriageContext { // assemblé par le store, consommé pur — testable sans I/O
myActiveMandates: Mandate[]
otherActiveMandates: Mandate[]
matchingClauses: Clause[] // index Q0 « déjà décidé ? » — le MÊME index que Cmd+K
similarRecentCount: number // décisions adoptées partageant ≥2 tags sur 90 j
similarRecordedCount: number // consignations 'record' partageant ≥2 tags — maturation
computedConcernedIds: Id[]
}
interface Verdict {
route: DecisionRoute
rule: TriageRule
explanation: string // UNE phrase française — affichée SEULE, sans code de règle
protocolId?: Id
windowHours?: number
framingDays?: number
reviewRequired: boolean
engravingSuggested: boolean
conservatoryChain?: boolean
parametricHint?: boolean // nombre/%/montant détecté ⇒ « C'est un réglage » mis en avant
alternatives: { route: DecisionRoute; label: string; cost: string }[]
// toujours présentes : « C'est un réglage — décidez au curseur » (parametric) ;
// « Déjà tranché — je le consigne » (record) ; le binaire avec son coût (« deux camps, un perdant »)
suggestion?: { kind: 'claim-mandate' | 'create-rule' | 'protocolize'; prefill: Json }
}
// ── Persistance locale & synchro future ──────────────────────
interface Bundle { // export/import ET seeds — le MÊME chemin de code
schemaVersion: 2
exportedAt: ISODate
collective: Collective
people: Person[]; circles: Circle[]
decisions: Decision[]; concerns: Concern[]; objections: Objection[]; advices: Advice[]
assents: Assent[]
mandates: Mandate[]
docs: TextDoc[]; clauses: Clause[]; versions: ClauseVersion[]
protocols: Protocol[]; sessions: VoteSession[]; votes: Vote[]
}
// data/persistence.ts : IndexedDB (idb-keyval), clé `ld2:<collectiveId>`, débounce 500 ms.
// importBundle() valide protocolByRange.consent + pose Collective.lineage si bundle étranger.
// Prêt-synchro : UUIDv7, updatedAt (LWW), archivedAt, Vote/Advice/Objection/Assent append-only.
// Contrat des stores Pinia inchangé — l'adaptateur FastAPI futur ne change AUCUN écran.
// NOTES contrat : ballot 'secret' devra devenir cryptographique côté serveur (v3) ;
// la privacy des chaînes supersedesVoteId (visibles du seul auteur) est un invariant du contrat.
Cycle de vie
ÉMERGENCE — la capture : une phrase dans « Qu'est-ce que tu décides ? » (Fil ou FAB). Crée Decision{status:'draft'}. Dix secondes visées, aucun autre champ obligatoire. Alternative permanente : « Déjà tranché — je le consigne » (route 'record') → adopted immédiat avec decidedHow, zéro fenêtre — l'observatoire en un geste, 2 gestes comme le solo.
TRIAGE — le chemin : triage() pur recalcule à chaque geste ; valider fait passer draft → l'état de départ de la route. Toujours expliqué (une phrase française, jamais de code), toujours contournable (alourdir libre 1 tap ; alléger = note obligatoire + fenêtre d'objection imposée — asymétrie journalisée). La ligne « Ce que ça engage » se DÉPLIE en suggestion si weight binding/structural hors solo mais ne bloque JAMAIS le tunnel ; « Aujourd'hui : … » suggéré sur advice/collective.
FORMULATION (route collective, poids binding/structural) : status 'framing' — « N jours pour s'instruire et formuler des contre-propositions » (framingDays, défaut 14). La matière (brief) s'édite ICI ; contre-propositions bornées (ClauseVersions ou variantes de body avec diff, contenu d'abord, auteur replié). Sur toute décision structural en framing : « Découper en micro-décisions » — chaque élément part en /decider?parent=&chain=element avec son propre triage/état/modalité ; les concernés pondèrent leurs enjeux (Concern.priority). GARDES D'OUVERTURE (framing→voting ou draft→voting, state.ts) : (a) matière — ≥1 effet recherché exigé si weight ∈ requireEffects du Pacte ('binding' couvre binding+structural ; 'none' dans blank/informal) ; structural sous garde ⇒ ≥1 effet mesurable (target) ; (b) ressources — resources.note exigé si weight != 'light' hors solo. Les deux gardes vivent à l'ouverture, jamais au tunnel.
DÉLIBÉRATION — selon la route :
- 'solo' selfOnly : draft → adopted immédiat (2 gestes). Trace facultative, visibility 'private' par défaut.
- 'record' : draft → adopted immédiat, decidedHow posé, trace pure.
- 'mandate' et solo-avec-tiers-dérogatoire : draft → objection (fenêtre 48 h, concernés notifiés au Fil, « Ça me va / J'objecte » — le « Ça me va » crée un Assent). À échéance sans objection ouverte : easy ⇒ adopted (silence vaut accord) ; costly/irreversible ⇒ adopted SEULEMENT si ≥1 Assent d'un concerné ≠ auteur, sinon la fenêtre se PROLONGE d'un cran (+objectionWindowHours, relance au Fil) — jamais d'adoption par pur silence hors easy. Objection 'content' maintenue → escalade framing/voting. Objection 'boundary' → windowSuspendedAt, compte à rebours SUSPENDU.
- 'advice' : draft → advice (72 h) ; avis déposés ; l'auteur décide → adopted (avis annexés). Objection formelle → escalade collective/consent.
- 'collective' : framing? → voting : VoteSession créée, liste arrêtée (union calculée + déclarés avant arrêt), W fixé. Modalités : consentement (zéro objection maintenue) ; nuancé (nuancedResult, histogramme visible en cours) ; binaire inertiel (votesFor ≥ wotThreshold) ; PARAMÉTRIQUE : curseurs nommés + statu quo + « calculé » + faisceau + médiane vivante + « Pour moi » + Explorer — values[] révisable jusqu'à closesAt (supersedesVoteId, historique visible du seul auteur) ; élection (désignation par avatar, blanc possible). CLÔTURE : consent/nuancé/binaire → automatique à closesAt (le moteur constate un seuil sur un contenu formulé par des humains). PARAMÉTRIQUE → à closesAt la session passe 'frozen' (votes figés, médiane figée affichée) puis le GARANT cristallise (« Cristalliser » → 'closed', crystallizedById/At, médiane basse = position adoptée ; quorum parametricMinParticipants non atteint ⇒ rejected constaté au même geste ; relance au Fil s'il tarde ; bimodalité ⇒ le geste affiche « Deux positions distinctes — cristalliser quand même / scinder / reformuler », jamais d'automatisme). ÉLECTION → automatique (pluralité, quorum electionMinParticipants) SAUF égalité : outcome 'tie', l'outil ne départage jamais — session de départage chaînée entre ex æquo, ou tirage au sort si le Pacte l'a prévu (tieBreak).
GARDE D'AFFLUENCE (transversale, state.ts) : si |déclarés| ≥ concernEscalateRatio × |calculés|, la fenêtre/session NE PEUT PAS se clore sans traitement — élargir d'un cran (1 tap, re-triage journalisé) OU scopeKeptNote motivée publique.
ADOPTION : decidedAt posé. Micro-célébration : tampon 井 « Décidé » + « C'est décidé — et révisable, comme tout ici ». amendsClauseId → ClauseVersion 'current' automatique. createsMandate → Mandate 'active'. structural/irreversible → review.dueAt + gravure proposée. sunsetAt optionnel. Mode secret : décomptes agrégés seuls (« secret d'affichage — démo »).
RÉALISATION / ÉVALUATION : stewardIds réalisent ; measurerIds constatent (Effect.measured). À review.dueAt : « L'ÉPREUVE DU RÉEL » — « Le réel a-t-il suivi ? », tableau recherché/cible/constaté AU-DESSUS des 3 boutons « Ça tient / À revoir / À révoquer » ; écart à baselineNote relu ; ressources réexaminées. 'confirmed' : review close (one-shot → 'closed') ; 'revise'/'revoke' : décision enfant chaînée sous protocole et inertie D'ORIGINE.
DOSSIER DÉCOUPÉ : quand tous les éléments sont en état terminal, carte au Fil du garant « Le dossier est complet — clore et publier la cartographie de clôture » ; le geste fait framing→closed du parent, la cartographie devient le PV du dossier. Jamais automatique.
RÉVISION / RÉVOCATION : toute décision adoptée porte « Remettre en question » — /decider pré-rempli, protocole et inertie d'origine affichés. Enfant 'revocation' adoptée → parent 'revoked' (« Révoquées — ce qu'on en a appris »).
URGENCE : « C'est urgent » — si non irréversible, voie la plus légère MAINTENANT (badge « Décidé en urgence — le collectif ratifie », fenêtre ×2) + ratification chaînée automatique. Irréversible ⇒ refusé : « Irréversible : l'urgence ne peut pas contourner le collectif ».
TRANSMISSION : 'transmit' → 'transmitted' (terminal pour l'auteur) ; le mandataire reprend ou l'auteur conteste le mandat.
MACHINE À ÉTATS UNIQUE (engine/state.ts) : draft→{advice,objection,framing,voting,adopted,transmitted} ; advice→{adopted,voting} ; objection→{adopted,framing,voting} ; framing→{voting,closed} (closed : dossier complet, geste) ; voting→{adopted,rejected} ; adopted→{revoked,closed} ; + gardes windowSuspendedAt, affluence, matière, ressources, Assent, cristallisation. Toute mutation passe par canTransition(decision, to, ctx).
Algorithme de triage
MOTEUR : engine/triage.ts — fonction PURE triage(input, ctx, settings) → Verdict. Tous les seuils viennent des clauses du Pacte (resolveSettings, replis spécifiés → consent) — le triage est gouverné par ceux qu'il gouverne. « Triage » et « verdict » ne sortent JAMAIS du code : l'UI dit « Le chemin ». La carte affiche l'explanation SEULE (code R* au dépliable « pourquoi ? », au PV et au journal de dérogation). Sujet grammatical : toujours le collectif ou la légitimité, jamais la formule.
ENTRÉES EXPLICITES (3 chips max) : 1. Concernés → scope (pré-calculé tags×domains, corrigeable, plancher non restrictible). 2. Réversible ? → easy/costly/irreversible. 3. Ça engage quoi ? → light/binding/structural — si binding/structural hors solo, la ligne « Ce que ça engage » (resources.note + montant/unité) se DÉPLIE en suggestion, jamais bloquante (l'exigence vit à la garde d'ouverture de state.ts). ENTRÉES INFÉRÉES : mandats couvrants, clauses qui matchent (index Q0 = Cmd+K), récurrence, consignations similaires, « C'est urgent », clause visée, nombre/%/montant dans la phrase ⇒ parametricHint.
RÈGLES, DANS L'ORDRE — première qui matche (libellés français FINAUX) :
- R-U URGENCE (overlay) : urgent && !irreversible ⇒ voie la plus légère MAINTENANT (badge « Décidé en urgence — le collectif ratifie », fenêtre ×2) + ratification chaînée auto. urgent && irreversible ⇒ REFUS : « Irréversible : l'urgence ne peut pas contourner le collectif. »
- R0a DÉJÀ DÉCIDÉ : clause en vigueur couvre ⇒ « C'est déjà décidé (E9, Acte Forgeron) — agis. » avec lien, OU « Conteste la règle » ⇒ R3 avec l'inertie de la clause.
- R0b MON MANDAT : ⇒ 'mandate' : « Ton mandat Modération couvre — décide, c'est tracé. » Trace obligatoire aux feux de la rampe + fenêtre d'objection 48 h.
- R0c MANDAT D'UN AUTRE : ⇒ 'transmit' : « Le mandat de Cgeek couvre — transmets-lui. » Alternative : « Conteste ce mandat ».
- R2 MOI SEUL : selfOnly ⇒ 'solo' : « Personne d'autre n'est concerné — décide. » Zéro fenêtre ; si irréversible, revoyure pré-cochée retirable (sur soi l'outil ne s'interpose pas).
- R3 CLAUSE VISÉE : amendsClauseId ⇒ 'collective', protocole = clauseByInertia[clause.inertia] (repli → consent si absent) : « Tu proposes une version de E9 — son inertie s'applique : vote de ceux qu'elle gouverne. » L'héritage Ğ1 (binaire inertiel des clauses) survit ICI, intact.
- R4 PETIT CERCLE RÉVERSIBLE : easy && |concernés| ≤ smallGroupMax(5) ⇒ 'advice' : « Réversible et 3 personnes concernées — demande leur avis puis décide. » 72 h ; objection formelle ⇒ escalade R5/consent.
- R5 COLLECTIF (défaut de l'escalade) : sinon ⇒ 'collective', modalité par taille : ≤ consentMax(7) → consentement (« Vous êtes 6 — un tour d'accord suffit : sans objection, c'est adopté. ») ; ≤ collectiveMin(50) → nuancé (« Vous êtes 23 — vote nuancé : chacun se prononce en nuances, pas en camps. ») ; > collectiveMin → protocole protocolByRange.large résolu par le Pacte (repli → consent) — gabarits standard : « Vous êtes plus de 50 — vote nuancé, la modalité que votre Pacte a choisie. » ; free-currency/Ğ1 : binaire inertiel hérité (« … la formule d'inertie que votre communauté a adoptée »). structural ⇒ + framing + matière selon requireEffects + revoyure + gravure suggérée + inertie haute ; irreversible ⇒ revoyure obligatoire. Si parametricHint : la bascule « régler plutôt que trancher » proposée sous le chemin.
- R6 RÉCURRENCE & MATURATION (overlay, jamais bloquant) : similarRecentCount ≥ 3 ⇒ « 3e fois — réclame un mandat » (createsMandate pré-rempli) ou « crée une règle » ; similarRecordedCount ≥ 3 (≥2 tags partagés) ⇒ « Cette pratique est mûre — protocolise-la en quelques phrases » (pré-remplit une clause de Pacte depuis les traces réelles).
ALTERNATIVES PERMANENTES (« Je choisis autrement ») : « C'est un réglage (montant, taux, répartition) — décidez au curseur » (parametric, mise en avant si parametricHint) ; « Déjà tranché — je le consigne » (record) ; le binaire pour/contre avec son coût affiché (« deux camps, un perdant ») ; chaque alternative avec son coût.
DÉROGATION ASYMÉTRIQUE : ALOURDIR libre — 1 tap journalisé. ALLÉGER = overrideNote obligatoire + fenêtre d'objection 48 h imposée, affiché en évidence. MÊME ASYMÉTRIE AU PÉRIMÈTRE : affluence atteinte ⇒ élargir (1 tap) ou scopeKeptNote motivée publique — jamais d'évaporation silencieuse. Le chemin n'est jamais un juge : il recommande, explique, et laisse faire.
MÉTRIQUE PRODUIT : chemin nominal solo = phrase + Entrée + bouton = 2 gestes, < 10 secondes ; le tunnel n'affiche JAMAIS un champ obligatoire qui ne change pas le chemin (les gardes de matière et de ressources vivent à l'ouverture de fenêtre/session, pas au tunnel) ; brief et curseurs vivent APRÈS le tunnel.
Modalités
CINQ MODALITÉS, UNE BOÎTE À OUTILS HIÉRARCHISÉE PAR LA DOCTRINE (chaque protocole est un Protocol nommé, référencé par une clause du Pacte — le modifier = amender le Pacte ; Protocol.ballot open/secret décidé par le Pacte, défaut open « feux de la rampe », secret v2 = « secret d'affichage — démo » ; dans TOUTES les modalités, l'historique de re-vote supersedesVoteId n'est visible que de son auteur — seul le dernier vote actif apparaît sous un nom) :
-
CONSENTEMENT (« Ça me va / J'objecte ») — proposé par R5 pour ≤ consentMax(7), par les escalades d'objection, et REPLI universel de protocolByRange (tout collectif en a un — invariant). Zéro vote : le « Ça me va » est stocké (Assent), tour d'objections argumentées, zéro objection maintenue = adopté. Hors du réversible, ≥1 Assent est requis à l'échéance (sinon prolongation) — l'accord est un geste, pas une absence.
-
NUANCÉ 6 NIVEAUX — proposé par R5 pour 8..collectiveMin(50) ET défaut des grands corpus (protocolByRange.large des gabarits standard). Libellés : 0 « Pas du tout » · 1 « Plutôt non » · 2 « Réservé » · 3 « Plutôt oui » · 4 « Oui » · 5 « Tout à fait » — jamais « CONTRE ». Commentaire OBLIGATOIRE sur 0-1. Histogramme des 6 niveaux visible en cours et à la clôture + commentaires en liste — jamais un simple adopté/rejeté. Valeurs 0-5 et résultats Ğ1 intacts. Clôture automatique à closesAt.
-
PARAMÉTRIQUE — « Réglage collectif » / décider au curseur. JAMAIS routé d'office : proposé dans « Je choisis autrement », mis en avant si parametricHint. paramSpec : ≤7 curseurs à libellés métier ('slider' borné ou 'share' répartition), contrainte 'sum100' avec EXACTEMENT une part 'derived' (variable d'absorption, résolue 100 − Σ autres) — étiquettes « contrainte / calculé ». Salle : curseurs + statu quo + bloc calculé non éditable + faisceau translucide + médiane trait épais + carte « Pour moi » (linear-share spécifié : quote-parts de resources.amount au prorata de l'attribut déclaré — sans attribut, jamais de chiffre inventé) + « Explorer » (sandbox « exploration — ne compte pas »). Vote values[] révisable jusqu'à la clôture. Agrégation NOMMÉE : « Le collectif retient la médiane de chaque curseur — robuste aux extrêmes, et c'est une position que chacun aurait pu proposer. » (médiane BASSE : l'invariant est vrai par construction, step respecté). À closesAt : session 'frozen' — puis CRISTALLISATION = GESTE HUMAIN DATÉ du garant (« Cristalliser », crystallizedById/At ; quorum parametricMinParticipants sinon rejected ; relance au Fil ; bimodalité affichée en évidence au moment du geste : « cristalliser quand même / scinder / reformuler » — jamais d'automatisme). Pour les DOSSIERS complexes : le découpage en micro-décisions (chainKind 'element', « décision incrémentielle ») combine plusieurs modalités élément par élément, avec pondération (Concern.priority) et cartographie de clôture publiée par le geste de clôture du garant.
-
ÉLECTION — reléguée aux seules problématiques d'échelle et de mandats : la nomination EST une décision createsMandate (6 méthodes : auto-désignation ratifiée, élection sans candidat, nuancé, consentement, tirage au sort, rotation). Jamais proposée pour une question de fond. CODÉE : Vote.choicePersonId désigne une personne du corpus ; vote déposé sans désignation = BLANC (participation, pas désignation) ; clôture automatique à closesAt : quorum electionMinParticipants sinon rejected, désignation à la PLURALITÉ simple des derniers votes actifs ; ÉGALITÉ ⇒ outcome 'tie' — l'outil ne départage JAMAIS : session de départage chaînée entre ex æquo (pré-remplie), ou tirage au sort entre ex æquo UNIQUEMENT si le protocole du Pacte l'a prévu d'avance (tieBreak:'draw'). Règle affichée en une phrase dans la salle.
-
BINAIRE INERTIEL (pour/contre + Seuil = C + B^W + (M + (1−M)(1−(T/W)^G))·max(0,T−C)) — HÉRITAGE REMIS À SA PLACE : proposé par R3 pour l'amendement des clauses Ğ1 (clauseByInertia — les 59 Forgeron votées ainsi, D30M50B.1G.2 intact) et par R5 >50 UNIQUEMENT dans free-currency. Partout ailleurs : disponible dans « Je choisis autrement » avec son coût (« deux camps, un perdant ») ; description seedée : « l'outil de dernier recours des très grands corps — remis à sa place dans la boîte à outils ». L'Atelier des formules reste son école (seul lieu des lettres W/T/M/B/G/C et de KaTeX).
RÉSOLUTION : protocolByRange { consent (obligatoire), nuanced?, large?, parametric?, election?, clauseByInertia? } — Ids résolus par le Pacte, repli spécifié → consent, ajustables en alourdissant. L'adoption applique automatiquement (clause current, mandat créé) — zéro bouton d'acceptation manuel ; tallies/médianes toujours recalculés depuis Vote[].
Périmètres et inclusion
LE MOT INSTALLÉ : « Périmètre » = qui est concerné par CETTE décision (panneau de la fiche, frise du chemin) ; « Cercle » = groupe durable de personnes (entité Circle, listes nominatives auditables). Les 3 vecteurs doctrinaux : ÉCHELLE D'ENJEU portée par weight/urgent/reversibility ; THÉMATIQUE par tags/domains (suggestion, jamais un droit) ; TOPOLOGIQUE déclaratif via Circle.kind 'place'|'theme'|'team' (iconographie, groupement, ordre des suggestions — jamais un droit ; hint non bloquant à 12 membres pour les 'team'). Le renommage réserve la place des vecteurs calculés (géo/WoT, v3) sans casser un libellé.
DEUX PORTES, UNE LISTE, CHAQUE ENTRÉE SOURCÉE (Concern horodaté) :
- CONCERNÉS EN PREMIER LIEU (origin 'computed', pastille pleine) : à la validation du chemin, engine/impact.ts calcule l'union CALCULABLE (membres des cercles du scope ∪ personnes nommées ∪ titulaires des mandats intersectants). Chaque inclusion porte sa raison en clair — auditable en un tap, jamais « la machine t'a désigné ». PLANCHER ANTI-EXCLUSION : élargir libre, JAMAIS restreindre sous le calcul.
- CONCERNÉS EN SECOND LIEU (origin 'declared', main levée) : bouton « Ça me concerne » sur toute décision visible, SANS justification — un droit, pas une demande ; motif optionnel public. Déclaré AVANT la liste arrêtée ⇒ vote ; APRÈS ⇒ voix consultative — règle affichée en compte à rebours.
OBJECTION DE FRONTIÈRE (suspensive — la contestation des frontières prime sur le fond) : « J'ai été oublié / Untel manque » ⇒ windowSuspendedAt, compte à rebours SUSPENDU (pastille pause, « compte à rebours suspendu — frontière contestée »), priorité maximale au Fil de l'auteur, jusqu'à intégration ou réponse motivée.
MONTÉE D'ÉCHELLE — DÉCIDÉE, JAMAIS ÉVAPORÉE : si |déclarés| ≥ concernEscalateRatio(0.5) × |calculés|, bannière « Le périmètre déborde — élargis d'un cran » ET garde dans state.ts : la fenêtre/session ne peut pas se clore sans traitement — élargissement au cercle parent (couches d'oignon, 1 tap, re-triage journalisé — chemin nominal), ou maintien avec scopeKeptNote motivée PUBLIQUE, affichée en évidence comme un allègement. L'asymétrie de dérogation appliquée au lieu doctrinal de la subsidiarité.
GRADIENT D'ENGAGEMENT (affiché sur la fiche) : observateur (lecture) < concerné (avis, objection, « Ça me va ») < lié (vote — liste arrêtée) < garant/mesureur (stewardIds réalisent et cristallisent, measurerIds constatent).
PONDÉRATION PERSONNELLE DES ENJEUX : sur un dossier découpé, chaque concerné pose Concern.priority 0-3 (« pondère tes enjeux ») depuis le Fil ou la fiche — nourrit la hiérarchie des enjeux de la cartographie de clôture, jamais le droit de vote.
ANTI-BRIGADING PAR CONSTRUCTION : déclarations publiques + liste arrêtée + entrée tardive consultative + escalade non-ignorable — et la formule inertielle absorbe l'afflux (gonfler W sans que T suive fait MONTER le seuil). ANTI-FLICAGE (ligne rouge) : hors mandat, trace facultative et visibility choisie ('private' par défaut en solo) ; sous mandat, trace obligatoire visible du cercle électeur — la redevabilité s'applique au pouvoir, jamais aux personnes ; Person.attributes jamais affichés à autrui ni agrégés ; l'historique des re-votes n'est jamais opposable à une personne (visible du seul auteur).
FRACTALITÉ : Collective.lineage (« essaimé de … ») mémorise l'essaimage par bundle ; AUCUNE structure fédérale dans le modèle (choix doctrinal documenté) — pattern v3 : une grappe = un collectif ordinaire dont les membres sont des mandataires d'autres collectifs, la fédération = échange de bundles/PV, jamais une table.
Instruction et évaluation
LE PRINCIPE : « Il n'est pas consulté, il est instruit » — pas de vote collectif engageant sans matière, et la même matière sert l'évaluation : la boucle d'« acquisition collective des expériences » est fermée par construction, sans jamais devenir plus lourde que l'enjeu.
LA MATIÈRE (Decision.brief) : contexte, sources {label,url}, symptômes vs causes (la qualification doctrinale du sujet), et effects — les EFFETS RECHERCHÉS { label une ligne, target cible mesurable optionnelle, measured constaté }. GARDE DÉFINITIVE (state.ts, ouverture de session collective UNIQUEMENT — framing→voting / draft→voting) : ≥1 effet recherché exigé si weight ∈ clause de Pacte 'triage.requireEffects' ('binding' = binding et structural, défaut des gabarits structurés ; 'structural' ; 'none' seedé dans blank ET informal — le collectif règle sa propre exigence) ; sous garde, structural ⇒ ≥1 effet avec cible mesurable. Le weight 'light' (le trio, « on part le 12 ») n'y est JAMAIS soumis ; le chemin solo/record ne voit JAMAIS ces champs — 2 gestes / 10 s préservés ; la matière s'écrit en framing, pas dans le tunnel.
LE BLOC « S'INSTRUIRE » (condensé — réparation intégrée) : en tête de la salle de vote et de la fiche (framing/voting) — un RÉSUMÉ COMPACT toujours visible avant les boutons : titre, effets recherchés, « Ce que ça engage » ; en DÉPLIABLES internes : body, versions + diff (contenu d'abord, auteur replié), avis et objections déposés, provenance (cas Ğ1 : voteRecord). Le bloc entier se replie automatiquement aux visites suivantes de la même personne (préférence locale par décision) — voter sur un titre nu reste structurellement impossible en collectif, re-voter reste léger (« agir d'abord, comprendre ensuite »). La phrase de fenêtre : « 14 jours pour s'instruire et formuler des contre-propositions ».
LE STATU QUO (double ancre SejeteralO) : Decision.baselineNote (« Aujourd'hui : … ») suggéré routes advice/collective — en-tête de fiche « Aujourd'hui → Proposé » ; ParamDef.baseline = marqueur « actuel » sur chaque curseur + colonne de la carte « Pour moi » ; les clauses gardent leur diff (qui EST leur baseline). Une décision se lit comme un écart au statu quo, mesurable.
LA DÉCISION CONNEXE (Decision.resources) : « Ce que ça engage » — note + montant/unité ('heures'|'€'|'DU'|libre). SUGGÉRÉE-dépliée au tunnel (jamais bloquante), EXIGÉE à la garde d'ouverture de fenêtre/session si weight != 'light' hors solo (même mécanique que la matière). Affichée fiche + résumé S'instruire + PV, réexaminée à l'épreuve du réel. Huile/eau : DU et € jamais convertis ni additionnés — agrégats de l'Observatoire par unité, lignes séparées. La paire doctrinale : stewardIds RÉALISENT (et cristallisent), measurerIds MESURENT. resources.amount est aussi le multiplicande de la carte « Pour moi » (linear-share).
L'ÉPREUVE DU RÉEL (Review) : à review.dueAt — « Le réel a-t-il suivi ? », tableau effet recherché / cible / constaté (Effect.measured saisi par les measurerIds) AU-DESSUS des 3 boutons « Ça tient / À revoir / À révoquer » ; écart à la baseline relu ; ressources confrontées. Jamais un jugement dans le vide : « évaluer les expériences avec la curiosité du chercheur », le droit de se tromper est une donnée.
L'OBSERVATOIRE (/decisions/observatoire) : comment nous décidons, DANS LES FAITS. Stats d'autonomie (ratio seul/mandat/avis/collectif/consigné, temps médians, participation, dérogations alourdies/allégées/périmètres maintenus, ressources engagées par unité) ; épreuves du réel rendues et dues ; « Révoquées — ce qu'on en a appris » ; élagage suggéré (protocole jamais invoqué, mandat sans trace). LA CONSIGNATION (route 'record') : « Déjà tranché — je le consigne » + decidedHow — consigner AVANT de protocoliser ; ratio consigné/outillé = maturité ; MATURATION (famille R6, jamais bloquante) : ≥3 consignations partageant ≥2 tags ⇒ « Cette pratique est mûre — protocolise-la en quelques phrases » pré-remplissant une clause de Pacte depuis les traces réelles. Le flux consigner→observer→protocoliser est le chemin ordinaire du produit, symétrique de R6 : le contrat social s'écrit au fil de l'eau, depuis la pratique.
Navigation
SHELL : layouts/default.vue. Header : logo-stamp (icône puits, rotation -10°) + « libreDecision » ; WorkspaceSelector (collectifs locaux + finalité A1 en une ligne + « Créer un collectif ») ; recherche globale Cmd+K (index unique clauses/décisions/mandats = le MÊME index que Q0) ; MoodSwitcher ; bouton « Décider » (desktop). FAB « Décider » 56 px mobile, au-dessus du sceau. Sceau hexagramme #48 Tsing 井 fixe bas-droite (géométrie SVG canonique). layouts/bare.vue pour /creer.
SIDEBAR — 4 ENTRÉES, pas une de plus :
- Aujourd'hui (/) — le Fil + la capture. À 3 personnes, c'est tout l'outil.
- Décisions (/decisions) — le registre + l'Observatoire.
- Textes (/textes) — les documents sous vote permanent, Pacte épinglé.
- Mandats (/mandats) — les pouvoirs confiés.
DISSOLUTIONS CONSERVÉES : protocoles dans le Pacte + étape modalité du chemin ; Atelier des formules = /textes/formules (lié depuis CHAQUE seuil, seule maison des lettres W/T/M/B/G/C) ; Sanctuaire = badge 井 + fiche de preuve + filtre ; pas de page réglages (les réglages SONT le Pacte) ; préférences locales = /donnees (mood, export/import, identité, attributs personnels, mode atelier).
ROUTES : / — Aujourd'hui · /decider — Le chemin (pré-remplissable ?clause= ?parent=&chain= ?mandat=) · /decisions — registre · /decisions/observatoire — L'Observatoire · /decisions/[id] — fiche décision (URL permanente) · /decisions/[id]/vote — salle de vote · /textes — bibliothèque · /textes/formules — Atelier des formules · /textes/[slug] — document vivant · /mandats — liste · /mandats/nouveau — wizard · /mandats/[id] — fiche mandat · /creer — onboarding (bare) · /donnees — données locales.
COUCHES CODE : app/types/domain.ts (noyau) ; app/lexicon.ts (source unique des libellés FR + test anti-lexique) ; app/engine/ (threshold avec electionResult, parametric avec crystallize, triage, modeParams, settings, state avec ctx, impact — purs, vitest) ; app/data/ (persistence.ts IndexedDB + seeds/*.bundle.json + templates.ts 7 gabarits) ; app/stores/ (contrat commun prêt adaptateur FastAPI — invariants privacy inclus) ; app/composables/ (useMood, useSettings, useFeed, useSearch) ; app/components/ par domaine (chemin/, decisions/, votes/ dont Faisceau et Curseurs, texts/, mandates/, circles/, common/). Le Fil est un SÉLECTEUR PUR — zéro table notification (il dérive : fenêtres, votes, épreuves dues, rapports dus, frontières, affluences, cristallisations en attente, dossiers complets, suggestions R6/maturation). Budget 400 lignes max par page. CSS print A4 (PV + affiche de session).
Écrans
/creer — Créer un collectif
Onboarding en 3 écrans — le premier usage EST le tutoriel ; s'ouvre sur le rêve
Ouverture : « Une démocratie qui n'a jamais existé — à votre échelle. » Écran 1 : nom, couleur, mood. Écran 2 : gabarits en 7 cartes qui TRADUISENT — « Page blanche — observatoire d'abord » EN TÊTE (Pacte : clause « nous consignons nos décisions ; le reste s'écrira au fil de l'eau » + A1 finalité, protocole Consentement SEUL installé, routes solo/consignation/avis/consentement) ; puis Informel / Association / Coopérative / Commune / Communauté monnaie libre réétiquetés « point de départ — tout est amendable par décision » ; et « Institution symétrique » (Déclaration de symétrie, mandats Ambassadeur/Négociateur suggérés, première décision « Déclarer notre symétrie »). Chaque gabarit installe Pacte pré-rempli (préambule Autonomie–Équilibre–Liaison proposé et modifiable, clause A1, clauses settingKey dont requireEffects) + protocoles (au minimum Consentement — invariant) + cercle racine typé. Langage concret (« qui doit être d'accord ? »). Écran 3 : moi + membres initiaux. Imports « Explorer Duniter Ğ1 » / « Explorer l'Atelier du Canal » par le MÊME chemin qu'un import utilisateur. Atterrissage sur / avec la première décision proposée : « Adopter notre Pacte » (collective, consentement). Signature finale « Osons, nous verrons ». Réussite < 10 minutes, testée avec des non-théoriciens.
/ — Aujourd'hui
L'écran quotidien unique : capture + Fil personnel — la boîte de réception de la démocratie
Barre de capture sticky « Qu'est-ce que tu décides ? » (Entrée → /decider). Header « À toi de décider » avec compteur nu (jamais de rouge — interdits : dette, retard, en souffrance). Fil trié par échéance, sections vides masquées : fenêtres d'objection (« Ça me va / J'objecte » inline — le Ça me va crée un Assent ; compte à rebours ; pastille pause si frontière contestée ; mention « il manque un accord explicite » si fenêtre prolongée) ; avis demandés (3 boutons inline) ; votes ouverts (jauge miniature, lien salle) ; À CRISTALLISER (garant d'un réglage collectif figé : « Les votes sont figés — cristallise la médiane », relance persistante) ; DOSSIER COMPLET (garant : « Le dossier est complet — clore et publier la cartographie de clôture ») ; égalité d'élection à départager (session chaînée proposée) ; épreuves du réel dues (« Ça tient / À revoir / À révoquer » inline) ; rapports de mandat dus ; objections de frontière à traiter (priorité haute) ; périmètre débordant (élargir / maintenir motivé) ; pondération d'enjeux demandée ; suggestions de la boucle apprenante (« 3e fois — réclame ce mandat », « Cette pratique est mûre — protocolise-la », « ce protocole n'a jamais servi — élague »). Ensuite l'activité du collectif (selon visibility). Tout est sélecteur Pinia pur. Mobile-first absolu.
/decider — Le chemin (tunnel)
Une phrase → un chemin exécutable en un tap — 10 s pour le routinier, 3 min pour le stratégique
Phrase en haut (pré-remplie par capture ou query clause/parent/chain/mandat). Résultats Q0 « déjà décidé ? » inline dès la frappe. 3 chips : Concernés (avatars pré-calculés, plancher non restrictible), Réversible ?, Ça engage quoi ? — si binding/structural hors solo, la ligne « Ce que ça engage » se DÉPLIE en suggestion (note + montant/unité, jamais € et DU additionnés) mais ne bloque JAMAIS la validation (l'exigence vit à l'ouverture de fenêtre/session) ; « Aujourd'hui : … » suggéré sur avis/collectif. Toggle « C'est urgent » discret. Carte LE CHEMIN live : grande icône de route, l'explication en une phrase SEULE (aucun code R*), frise le périmètre / combien de temps / quel seuil, revoyure et gravure pré-cochées si exigées, UN bouton principal. « Je choisis autrement » toujours visible : alternatives avec coût, dont « C'est un réglage — décidez au curseur » (mise en avant si nombre/%/montant détecté), « Déjà tranché — je le consigne » (→ adopted immédiat + « comment ça s'est décidé »), binaire avec « deux camps, un perdant ». Alourdir 1 tap journalisé ; alléger = note + fenêtre imposée. « Pourquoi ? » dépliable (le code de règle y vit). Suggestions R6/maturation sous le chemin. Valider crée la Decision structurée dans le bon état.
/decisions — Registre des décisions
Toutes les décisions par état — SectionLayout conservé
Pills d'état en français courant (préparation / avis / objection / formulation / vote / figé / en vigueur / clos / rejeté / révoqué) aux couleurs du cycle. Filtres : cercle (groupés par kind lieu/thème/équipe), tag, « me concerne », poids, gravées 井, consignées. Formes courtes des routes en pills (seul / sous mandat / transmis / sur avis / ensemble / consigné). Cartes : titre, icône de route, poids, échéance, jauge miniature, badge 井, badge « Décidé en urgence — le collectif ratifie ». Lien vers l'Observatoire. Zéro création ici — on crée par la capture.
/decisions/[id] — Fiche décision
URL permanente de l'objet pivot — progressive disclosure par état
En-tête : titre, « Aujourd'hui → Proposé » si baselineNote, route en français, poids, badges (urgence, allègement avec note, périmètre maintenu avec scopeKeptNote en évidence, 井). Timeline verticale du cycle. Panneau PÉRIMÈTRE : concernés en premier lieu (pastille pleine, tap → raison) et en second lieu (main levée, note publique), « Ça me concerne », compte à rebours de la liste arrêtée, bannière d'affluence NON-IGNORABLE (élargir 1 tap / maintenir motivé), objection de frontière (pastille pause). Bloc S'INSTRUIRE (framing/voting) : résumé compact toujours visible (titre, effets, ce que ça engage) + dépliables (body, versions/diff auteur replié, avis/objections, provenance) — auto-replié aux visites suivantes. Selon l'état : bloc fenêtre (boutons inline, silence-vaut-accord affiché seulement si réversible, sinon « un accord explicite est requis » avec compteur d'Assents) ; bloc formulation (« N jours pour s'instruire… », « Découper en micro-décisions » sur structural) ; onglet ÉLÉMENTS si dossier (filtre « me concernent », états, échéances, pondération 0-3) + CARTOGRAPHIE DE CLÔTURE auto-générée + carte « Le dossier est complet — clore » quand tous terminaux (geste du garant) ; bloc session (lien salle, résultat, « Qui vote : N personnes — liste arrêtée le … », bandeau « Votes figés — en attente de cristallisation » si frozen) ; bloc vigueur (garants, mesureurs, ressources, sunset, épreuve du réel due : tableau recherché/constaté au-dessus des 3 boutons) ; chaînage cliquable. Poignée « Remettre en question » sur tout adopté (protocole et inertie D'ORIGINE affichés). Actions : Imprimer (PV A4), graver (« empreinte locale — démo »), micro-copy « à trancher en présence ? » sur structural. Budget 400 lignes.
/decisions/[id]/vote — Salle de vote
Le geste démocratique central — instruit, puis prononcé
Bloc S'INSTRUIRE condensé en tête : résumé compact toujours visible avant les boutons (titre, effets recherchés, ce que ça engage) + dépliables (texte/diff, avis et objections, provenance) — replié automatiquement aux visites suivantes. « Qui vote : 11 personnes — liste arrêtée le 12/08 » (avatars). Selon la méthode : CONSENTEMENT (« Ça me va » stocké / « J'objecte » argumentée) ; NUANCÉ 6 niveaux (« Pas du tout » → « Tout à fait », commentaire obligatoire inline sur 0-1, HISTOGRAMME sous la jauge) ; BINAIRE hérité (jauge inertielle : arc animé, seuil qui descend quand la participation monte, phrase sujet-collectif : « 120 votants sur 7 224 : à si faible participation, il faut presque l'unanimité — 94 pour. Plus de monde vote, plus le seuil descend. », lien « comprendre ce seuil » → Atelier pré-réglé) ; PARAMÉTRIQUE « Réglage collectif » : un curseur par paramètre (libellé métier, marqueur statu quo creux, alternative clavier + champ numérique), bloc « calculé » non éditable (étiquettes contrainte/calculé), FAISCEAU strip-plot (points translucides anonymes, médiane basse trait épais), carte « POUR MOI » (ma position / médiane / statu quo — quote-parts de « ce que ça engage » au prorata de mon attribut déclaré, « estimation sur données déclarées », jamais de chiffre inventé), « EXPLORER » (sandbox « exploration — ne compte pas », partir de la médiane/du statu quo/de ma position, « En faire ma position »), phrase d'agrégation « Le collectif retient la médiane de chaque curseur — robuste aux extrêmes, et c'est une position que chacun aurait pu proposer », bannière bimodalité non bloquante ; à closesAt bandeau « Votes figés — le garant cristallise » puis bouton « CRISTALLISER » (garant seul, quorum vérifié, bimodalité rappelée : « cristalliser quand même / scinder / reformuler ») ; ÉLECTION (désignation par avatar, bouton « Vote blanc », règle en une phrase : « La personne la plus désignée est nommée ; en cas d'égalité, vous départagez — jamais l'outil » ; égalité ⇒ session de départage chaînée ou tirage au sort si le Pacte l'a prévu). Mode secret : décomptes agrégés et commentaires anonymisés seuls, « secret d'affichage — démo ». Mon vote remplaçable — « ton historique n'est visible que de toi » (chaîne supersedes jamais publique, dernier vote seul affiché sous un nom en ballot open). Bouton « Imprimer » : affiche de session A4. Clôture : distribution complète + arguments — jamais un simple adopté/rejeté ; adoption ⇒ application automatique + tampon 井 « C'est décidé — et révisable, comme tout ici ».
/decisions/observatoire — L'Observatoire
Comment nous décidons, dans les faits — le miroir et l'apprentissage
Titre « L'Observatoire », sous-titre « comment nous décidons, dans les faits ». Stats d'autonomie : ratio seul/mandat/avis/collectif/CONSIGNÉ (consigné/outillé = maturité), temps médians par route, taux d'objection, participation, dérogations (alourdies/allégées/périmètres maintenus), ressources engagées PAR UNITÉ (heures / € / DU — lignes séparées, jamais additionnées). Épreuves du réel : dues, rendues (tenues/à revoir/révoquées). « Révoquées — ce qu'on en a appris » : chaque révoquée avec sa chaîne — curiosité du chercheur, pas de lugubre. Consignations : liste des « déjà tranché » avec decidedHow ; maturation : ≥3 consignations partageant ≥2 tags ⇒ carte « Cette pratique est mûre — protocolise-la en quelques phrases » (pré-remplit une clause de Pacte). Élagage : protocole jamais invoqué, mandat sans trace 6 mois → revue suggérée. Décisions jamais revues : relance puis archivage proposé. Filtres personne/cercle/tag/période. Graphiques sobres (skill dataviz).
/textes — Textes
La bibliothèque des documents sous vote permanent
Pacte épinglé en tête : badge « les règles du jeu » + sous-titre « Notre contrat social — sacralisé, jamais immuable ». Cartes documents : titre, rôle, clauses, sessions en cours (mini-jauges), provenance Ğ1 (Certification 33 clauses, Forgeron 59 v2.0.0-fr, Comité Technique). « Déclaration de symétrie » pour les collectifs symmetric. Lien Atelier des formules. Filtre gravées.
/textes/[slug] — Document vivant
Clauses versionnées, boucle vote⇒texte fermée
Sections repliables + scroll-spy. Chaque clause : code, titre, pastille d'inertie CÂBLÉE au protocole d'amendement, mini-jauge sur session réelle, statut. Tap → détail : version courante, propositions avec diff (contenu d'abord, auteur replié), décision fondatrice et amendements chaînés. « Proposer une version » → /decider?clause= (R3, inertie de la clause). Vue projetée conservée. InertiaSlider pédagogique → Atelier pré-réglé. Provenance/GenesisBlock (sources, voteRecord 97/23/19, notes — typos signalées jamais corrigées). Spécifique Pacte : clauses settingKey en clair (« Petit groupe : jusqu'à 5 personnes », « Matière exigée : décisions engageantes et structurantes »), effet IMMÉDIAT à l'adoption (resolveSettings), clause d'inertie protégée par l'inertie maximale ; clause A1 — Notre finalité en tête ; préambule Autonomie–Équilibre–Liaison.
/textes/formules — Atelier des formules
La pédagogie du seuil — seule maison des lettres
FormulaEditor : sliders M/B/G/C (+Smith/ComTech), KaTeX de la formule inertielle, table de participation, presets d'inertie comparés. Branché sur engine/threshold.ts UNIQUE. Accessible depuis chaque seuil affiché, pré-réglé (« pourquoi 94 ? »). Cas Forgeron 2026 en un clic (W=7224, T=120 ⇒ 94). Le binaire présenté comme outil d'exception (« l'outil de dernier recours des très grands corps »). Section « Réglage collectif » : la médiane basse par curseur expliquée (robuste aux extrêmes, une position que chacun aurait pu proposer), mini-démo interactive, et la règle de cristallisation (« l'agrégat éclaire, le geste cristallise »).
/mandats — Mandats
La carte des pouvoirs confiés : qui peut décider quoi, jusqu'à quand
Cartes par statut (proposé/actif/expiré/révoqué) : titulaire, domaine (cercles + tags), échéance, prochain rapport dû, COMPTEURS BRUTS (« 12 décisions tracées · 1 objection · 2/2 rapports ») — AUCUNE jauge, aucun agrégat. Bouton « Réclamer un mandat » → wizard (souvent pré-rempli par R6).
/mandats/[id] — Fiche mandat
Le pouvoir confié, visible et reprenable
Titulaire, origine (la décision créatrice — cliquable), domaine explicite, période, cercle électeur. Bloc « EXERCICE DU MANDAT » : faits comptés jamais synthétisés — n décisions tracées, objections ouvertes/intégrées/escaladées (liste cliquable), rapports rendus/dus. Bloc « FEUX DE LA RAMPE » (sous-titre « toutes les décisions prises sous ce mandat — contestables ») : flux des décisions sous mandat, chacune avec sa fenêtre d'objection. Rapports de redevabilité. « Demander la révocation » en un geste : /decider chaîné (revocation) au protocole de nomination D'ORIGINE, cercle électeur comme périmètre — jamais improvisée, jamais plus coûteuse que l'élection.
/mandats/nouveau — Wizard mandat
Le meilleur flux v1, conservé et branché sur le pivot
3 étapes : (1) rôle et domaine (titre, cercles, tags, mission en une phrase) ; (2) modalité de nomination parmi 6 (auto-désignation ratifiée, élection sans candidat — avec sa règle de clôture affichée : pluralité, blanc, départage par vous, jamais par l'outil —, nuancé, consentement, tirage au sort, rotation) avec pros/cons pédagogiques ; (3) durée bornée + cadence de rapports + cercle électeur. Sortie : une Decision createsMandate routée collective — la nomination EST une décision.
/donnees — Données locales
Local-first assumé : tes données, ton fichier, ta machine
Export bundle JSON (schemaVersion 2), import avec validation (protocole Consentement requis, collisions) + pose de lineage (« essaimé de … »), réinitialisation par collectif. Seeds (Duniter Ğ1, Atelier du Canal). Identité locale (displayName, isMe) + MES ATTRIBUTS (Person.attributes, clés libres : 'heures/mois'… — « servent uniquement ta carte Pour moi, jamais montrés aux autres »). MODE ATELIER (ex-« agir en tant que ») : saisie assistée en présence — chaque saisie porte « saisi par X pour Y — atelier du » ; l'étiquette démo reste pour les personnages seedés. Niveaux de preuve honnêtes (empreinte locale / IPFS / chaîne — jamais affichés tant que non réels). Note synchro : « un serveur pourra se brancher plus tard, tes données ne changent pas de forme ».
Vocabulaire UI
| Contexte | Terme final | Remplace |
|---|---|---|
| Carte centrale de /decider et titre du tunnel — le type TS Verdict reste en code ; « verdict » et « triage » ne sortent jamais du code | Le chemin | Le Verdict |
| Popover/panneau Périmètre — pastille pleine ; « la machine t'a désigné » est banni | Concerné·e en premier lieu | calculé (glyphe calculator) |
| Popover/panneau — la main levée reste ; « Ça me concerne » inchangé | Concerné·e en second lieu — s'est déclaré·e | déclaré |
| Chip du tunnel /decider | Concernés | Touchés |
| Panneau de la fiche + frise du chemin ; « Cercle » = groupe durable seulement ; « scope » reste code | Périmètre | Cercles (panneau) / « qui est convoqué » |
| Sidebar + rôle de /mandats/[id] ; tout dérivé de « déléguer » banni de l'UI (test anti-lexique) | Mandats — les pouvoirs confiés | les pouvoirs délégués |
| Header du Fil, compteur nu — interdits : dette, retard, en souffrance ; « dette décisionnelle » survit en métrique interne dev | À toi de décider | dette décisionnelle |
| Fiche et cartes mandat — faits comptés, jamais synthétisés ; « confiance » banni des agrégats | Exercice du mandat | Jauge de confiance |
| Flux public des décisions prises sous un mandat — contestables | Feux de la rampe | Observatoire (bloc fiche mandat) |
| /decisions/observatoire — « comment nous décidons, dans les faits » | L'Observatoire | Mémoire |
| Salle et fiche ; W/T/M/B/G/C vivent uniquement à l'Atelier des formules | Qui vote : 11 personnes — liste arrêtée le 12/08 | corpus visible : W, T / snapshot |
| Échelle nuancée — valeurs 0-5 et commentaire obligatoire 0-1 inchangés, résultats Ğ1 intacts | Pas du tout · Plutôt non · Réservé · Plutôt oui · Oui · Tout à fait | 0=CONTRE … 5=TOUT À FAIT |
| Bloc revoyure — fiche et Fil | L'épreuve du réel / « Le réel a-t-il suivi ? » | Verdict de réalité |
| 3 boutons de l'épreuve du réel (Review.verdict inchangé en code) | Ça tient / À revoir / À révoquer | confirmée / réviser / révoquer |
| Toggle du tunnel + badge ; « conservatoire » survit dans le PV et la doc | C'est urgent / « Décidé en urgence — le collectif ratifie » | décision conservatoire |
| Protocole et mode de la salle — « raisonner curseur / table de mixage » | Réglage collectif / décider au curseur | —(nouveau : VoteMethod 'parametric') |
| Clôture paramétrique : « Les votes sont figés — cristallise la médiane » ; « l'agrégat éclaire, le geste cristallise » — jamais automatique | Cristalliser | —(nouveau : geste du garant) |
| Bandeau de la salle/fiche entre closesAt et le geste du garant | Votes figés — en attente de cristallisation | —(nouveau : status 'frozen') |
| Découpage d'un dossier en micro-décisions — le nom de l'auteur réservé à CE mode (Taïwan) | Décision incrémentielle | —(nouveau : chainKind 'element') |
| Section auto-générée du dossier découpé ; publiée par le geste « clore » du garant, devient le PV du dossier | Cartographie de clôture | —(nouveau) |
| Étiquettes du paramétrique : cadre en lecture seule vs part dérivée — le votant sait ce qu'il contrôle | contrainte / calculé | —(nouveau) |
| Carte d'impact (ma position / médiane / statu quo) — « estimation sur données déclarées », jamais de chiffre inventé | Pour moi | —(nouveau) |
| Bloc de tête salle/fiche : résumé compact toujours visible + dépliables, auto-replié aux visites suivantes | S'instruire | —(nouveau, regroupe l'existant) |
| Ligne suggérée du tunnel + bloc fiche/PV — la décision connexe de ressources ; exigée à l'ouverture, jamais au tunnel | Ce que ça engage | —(nouveau : resources) |
| Alternative permanente du tunnel — l'observatoire en un geste | Déjà tranché — je le consigne | —(nouveau : route 'record') |
| Statu quo en en-tête de fiche ; marqueur « actuel » sur chaque curseur | Aujourd'hui : … / Aujourd'hui → Proposé | —(nouveau : baselineNote) |
| Libellés des routes (lexicon.ts) — le chemin est un geste, pas une catégorie ; formes courtes en pills de filtres | Je décide · Je décide, sous mandat · Je transmets · J'écoute, puis je décide · Nous décidons | Solo / Mandat / Transmettre / Avis / Collectif |
| Salle de vote : la chaîne supersedes n'est jamais publique sous un nom ; seul le dernier vote actif apparaît en ballot open | Ton vote est remplaçable — ton historique n'est visible que de toi | re-vote audité — l'historique reste (indéterminé) |
| Élection : vote déposé sans désignation — compte pour la participation, pas pour la désignation | Vote blanc | —(nouveau : élection) |
| Règle d'élection affichée en salle ; départage = session chaînée entre ex æquo ou tirage au sort si le Pacte l'a prévu | En cas d'égalité, vous départagez — jamais l'outil | —(nouveau : outcome 'tie') |
| Fenêtre d'objection non réversible prolongée : ≥1 « Ça me va » requis — le silence ne vaut accord que pour le réversible | Il manque un accord explicite | —(nouveau : Assent) |
| Description seedée du protocole binaire + Atelier ; libellé Ğ1 : « héritage Toile de Confiance » | pour/contre — « l'outil de dernier recours des très grands corps » | vote binaire (neutre) |
| Première décision proposée — actes collectifs à l'infinitif (règle de ton n°2) | Adopter notre Pacte | Adoptez votre Pacte |
| Section de l'Observatoire — droit à l'erreur, curiosité du chercheur | Révoquées — ce qu'on en a appris | Cimetière des révoquées |
| /donnees — saisie assistée en présence, passerelle hors-écran | mode atelier — « saisi par X pour Y — atelier du » | agir en tant que (pur démo) |
| Étiquette du sandbox Explorer de la salle paramétrique | exploration — ne compte pas | —(nouveau) |
| Métadonnée d'affichage d'un collectif né d'un bundle d'un autre | essaimé de … | —(nouveau : lineage) |
| Pastille pause de l'objection de frontière | compte à rebours suspendu — frontière contestée | (conservé v1 — il est juste) |
Seeds
EXTRACTION — script backend/scripts/export_seed_bundle.py (venv existant) : importe seed.py (33 items Certification, 59 Forgeron, GENESIS, VOTER_NAMES ×11, mandates ×3, protocoles) et émet frontend/app/data/seeds/duniter-g1.bundle.json au schéma Bundle v2 (avec assents:[]). TEST DE NON-PERTE (vitest, bloquant) : 33+59 clauses, presets d'inertie conservés, provenance.voteRecord == {for:97, against:23, invalid:19, wotSize:7224, thresholdRequired:97, modeParams:'D30M50B.1G.2S.1'}, 11 personnes dont 5 smiths, 3 mandats, protocoles complets. Diff exigé AVANT dépréciation de seed.py. Typos des textes votés : Provenance.notes, JAMAIS corrigées.
MAPPING Ğ1 (v1 conservé, amendé) : Collective{template:'free-currency', isTransparent:true} ; 11 Person (5 smiths) ; Circles typés kind — « Toile de confiance » (racine), « Forgerons » (team), « Comité Technique » (team) ; TextDocs Certification (33 clauses, presets câblés), Forgeron (59 clauses v2.0.0-fr, voteRecord complet), Comité Technique ; pacte-duniter-g1 avec clauses settingKey (smallGroupMax=5, collectiveMin=50, consentMax=7, fenêtres 48/72, framingDays=14, concernEscalateRatio=0.5, recurrenceThreshold=3, reviewDelayDays=90, requireEffects='binding', clause A1 « Notre finalité » adaptée à la toile), clause d'inertie en preset 'max', description du Pacte « Notre contrat social — sacralisé, jamais immuable ». protocolByRange : consent → « Consentement » (D7, OBLIGATOIRE partout), large → « Vote WoT standard » (binaire inertiel D30M50B.1G.2, description « l'outil de dernier recours des très grands corps — héritage Toile de Confiance ») — L'HÉRITAGE À SA PLACE ; clauseByInertia mappé sur les 4 presets ; + « Réglage collectif » (parametric, D14, parametricMinParticipants), « Vote nuancé » (D30), « Vote forgeron Smith » (S.1), « Vote ComTech » (T.1), « Élection sans candidat » (D14, electionMinParticipants, tieBreak:'runoff') — tous ballot:'open'. Chaque clause courante reçoit sa DÉCISION FONDATRICE. 3 Mandates (ComTech Cgeek active + rapport dû ; Administrateur Forgerons proposed EN VOTE nuancé ; Modération Forum active). Décisions historiques : Runtime Upgrade 1100 (adopted, structural, gravée, revoyure posée + brief.effects rétro-documentés), Évolution licence v0.4.0 (framing avec versions).
DEUXIÈME COLLECTIF — atelier-du-canal.bundle.json (test permanent de marque blanche) : association fictive 12 membres, Pacte à seuils DIFFÉRENTS (smallGroupMax=4, fenêtres courtes, requireEffects='binding'), protocolByRange.large → NUANCÉ (la démonstration anti-binaire), Règlement intérieur 8 clauses, cercles typés Bureau (team) / Ateliers (theme) / Tous (racine), mandat Trésorerie (rapport dû), Person.attributes seedés ('heures/mois'). Décisions à TOUS les états : draft, advice en fenêtre, objection SUSPENDUE par frontière, objection non-easy avec 1 Assent posé (démonstration « accord explicite »), framing avec 2 versions et diff, voting nuancé en cours (histogramme vivant), adopted avec épreuve du réel due (brief.effects + measurerIds + resources posés — boucle complète), revoked (« ce qu'on en a appris »), conservatoire + ratification chaînée, UNE CONSIGNATION 'record' (« Le marché du samedi est reconduit — décidé au café, comme d'habitude ») et UN DOSSIER DÉCOUPÉ (2 éléments dont 1 terminal, priorités posées, cartographie de clôture en construction) ; et LE PARAMÉTRIQUE D'EXEMPLE : « Répartir le budget d'ateliers 2026 » en voting — resources { note:'budget annuel ateliers', amount:1200, unit:'€' }, paramSpec sum100 avec 3 parts nommées + EXACTEMENT 1 part 'derived' « Réserve (calculé) », baselines posées, impactAttrKey 'heures/mois', 5-6 votes seedés → faisceau, médiane basse et « Pour moi » calculables et vivants à l'ouverture ; une SECONDE session paramétrique 'frozen' (closesAt passé) avec la carte « À cristalliser » au Fil du garant — la cristallisation-geste démontrée. Tout contenu Ğ1 en dur dans le code est structurellement impossible.
GABARITS (/creer, app/data/templates.ts) — SEPT : blank « Page blanche — observatoire d'abord » (Pacte : clause consignation + A1, protocole Consentement SEUL — l'invariant protocolByRange.consent satisfait, toutes les autres clés en repli → consent, requireEffects='none') ; informal (requireEffects='none') / association / cooperative / commune réétiquetés « point de départ — tout est amendable par décision », protocolByRange.large → nuancé partout, requireEffects='binding' ; free-currency (mapping Ğ1, large → binaire inertiel, unité DU par défaut dans resources) ; symmetric « Institution symétrique » (Pacte transparence totale, TextDoc « Déclaration de symétrie », première décision « Déclarer notre symétrie », mandats suggérés Ambassadeur et Négociateur). Chaque gabarit : préambule Autonomie–Équilibre–Liaison proposé et modifiable + clause A1 (défaut : boussoles vitalité/émancipation) + cercles typés kind + protocole Consentement au minimum. Les seeds s'importent par le MÊME chemin que l'import utilisateur ; lineage posé si bundle d'origine étrangère ; l'import valide l'invariant consent.
Directives de design
ICONOGRAPHIE DES ROUTES (vocabulaire visuel fixe) : solo = i-lucide-zap ; sous mandat = i-lucide-key-round ; transmettre = i-lucide-send ; avis = i-lucide-message-circle ; collectif = i-lucide-users ; consignation = i-lucide-notebook-pen ; urgence = i-lucide-siren ; déjà décidé = i-lucide-book-open-check ; récurrence→mandat = i-lucide-repeat ; réglage collectif = i-lucide-sliders-horizontal ; cristallisation = i-lucide-stamp (le geste). Carte Le chemin : icon-container 14 px, nom de route en 700 À LA PREMIÈRE PERSONNE, l'explication en une phrase dessous (jamais de code R*), frise périmètre/durée/seuil, bouton principal pill 20 px plein accent, alternatives en pills fantômes. Teintes déclinées des custom properties du mood.
PÉRIMÈTRE : stacks d'avatars (initiales, teinte déterministe de l'id), débord -8 px, « +N ». Premier lieu = pastille pleine en coin (glyphe calculator SUPPRIMÉ) ; second lieu = main levée + fond légèrement teinté. Tap → popover chaîne d'inclusion. Plancher verrouillé visuellement (pas de croix de retrait sur les premiers-lieu). Cercles emboîtés en chips imbriquées ; icône par kind (lieu/thème/équipe).
CYCLE DE VIE : timeline verticale à pastilles pleines reliées (pas de bordure) — draft neutre, avis/objection ambre + compte à rebours, vote bleu + mini-jauge, FIGÉ bleu-gris avec icône tampon en attente (« votes figés — en attente de cristallisation »), en vigueur vert, épreuve du réel violette, révoqué terre cuite, clos gris, rejeté barré. SUSPENSION : pastille pause + fond strié + « compte à rebours suspendu — frontière contestée ». Fenêtre prolongée (Assent manquant) : ambre soutenu + « il manque un accord explicite ». Un seul mapping état→couleur.
JAUGE INERTIELLE (signature, héritage) : arc 270°, seuil qui DESCEND quand la participation monte, chiffres clairs, phrase générée sujet-collectif. KaTeX réservé à l'Atelier.
NUANCÉ : 6 segments en gradient sémantique construit depuis la palette du mood (skill dataviz, jamais rouge/vert crus) ; HISTOGRAMME des 6 niveaux sous la jauge (la distribution, pas seulement le résultat) ; commentaire obligatoire inline sous 0-1.
CURSEURS (Réglage collectif) : poignées tactiles ≥ 2.25 rem AVEC alternative clavier + champ numérique (correction SejeteralO) ; libellé métier au-dessus, unité, valeur courante en 700 ; marqueur statu quo = losange creux sur le rail ; part « calculé » = rail estompé non saisissable + étiquette « calculé » ; étiquette « contrainte » sur le cadre ; clamp dynamique visible (le rail se raccourcit quand la réserve dérivée approche de ses bornes). FAISCEAU : strip-plot par curseur — chaque position = point translucide anonyme (opacity ~0.25), médiane = trait épais accent, baseline = marqueur creux ; « transparence des agrégats, discrétion des personnes ». Carte « Pour moi » : fond légèrement teinté accent, trois colonnes ma position / médiane / statu quo, mention « estimation sur données déclarées ». Sandbox Explorer : bandeau « exploration — ne compte pas », curseurs en teinte neutre. CRISTALLISATION : bouton-tampon réservé au garant, confirmation avec rappel de bimodalité le cas échéant — le geste est solennel mais léger (un tap + confirmation), jamais une cérémonie.
MICRO-CÉLÉBRATION D'ADOPTION : le tampon 井 incliné -10° s'appose « Décidé » (translateY du système, 0.12s ease, aucune fanfare) + « C'est décidé — et révisable, comme tout ici ».
GRAVURE ET PREUVE : badge 井 tampon incliné sur objets gravés ; fiche de preuve sha256 mono tronqué copiable + « empreinte locale — démo ». Sceau hexagramme #48 Tsing : géométrie SVG canonique (viewBox 112×105, 6 traits, trait 1 en bas = dernier rect, opacity ~0.28, drop-shadow CSS), fixe bas-droite, jamais recouvert ; FAB au-dessus avec décalage.
PRINT A4 : feuille CSS print pour le PV (/decisions/[id]) et l'affiche de session (/decisions/[id]/vote) — titre, texte, qui vote, seuil, échéance, comment participer ; noir sur blanc, sans moods. La cartographie de clôture d'un dossier s'imprime comme PV.
MOODS v2 (champ lexical du puits, 2 light + 2 dark) : Source (light bleu d'eau), Margelle (light sable/pierre), Nappe (dark bleu profond), Minuit (dark encre). Socle : borderless (background + box-shadow), arrondis 16/12/20/14, hover cards translateY(-3px) 0.12s, mobile-first clamp(), breakpoints 480/768/1024, touch ≥ 2.25 rem. useMood.ts conservé.
Hors scope v2
- Décision paramétrique COMPLÈTE : équation de réalité pluggable (viabilité auto-calculée type p0 non linéaire), éditeur de courbes 'curve' (Bézier, ParamKind réservé), impact sur données réelles importées du périmètre, rétro-manipulation résultat→contrainte — v2 livre curseurs bornés + répartition sum100 (un derived, clamp/renormalisation spécifiés) + médiane basse + faisceau + baseline + impact linear-share sur attribut déclaré ; le solveur et le registre importé en v3
- Builder d'indicateurs de l'observatoire (indicateurs propres définis par le collectif) — construire l'éditeur avant d'avoir observé la pratique contredirait l'observatoire lui-même ; v2 = stats fixes + effets recherchés/constatés + consignations
- Vecteur topologique calculé (bassin de vie, commune pivot, géo) et règles d'appartenance WoT — v2 : listes nominatives + Circle.kind déclaratif ; enum de règle extensible sans migration
- Vote secret cryptographique et pseudonymat certifié @personne (deux identités découplées) — v2 : ballot 'secret' en secret d'affichage honnête + dépersonnalisation d'ordre d'affichage ; la crypto arrive avec la synchro et l'auth Duniter
- Économie du don outillée : allocation en DU effective, budget de gratitude, caisses fléchées, coefficients relatifs, rétribution des mandats pilotée par le collectif — v2 se limite à l'unité 'DU' descriptive dans resources (gabarit free-currency) ; l'intégration exige Ğ1/GrateWizard
- Grappes et fédération inter-collectifs opérationnelles — v2 pose lineage + le pattern documenté « une grappe = un collectif ordinaire dont les membres sont des mandataires d'autres collectifs ; la fédération = échange de bundles, jamais une table » ; jamais de parentCollectiveId
- Engagements fermes à seuil critique (crowdaction : recueillir, ne déclencher qu'au quota) — nouveau type d'objet et de cycle de vie, backlog v3
- Cartographie thématique vectorielle (liaisons, interdépendances, symptômes/causes visualisés) — v2 : tags plats + champ symptomsVsCauses du brief ; la carte manipulable est un produit en soi
- Événements de brassage générés par l'outil et multi-postes simultanés pour ateliers physiques — v2 livre l'imprimable A4 + le mode atelier mono-poste (intermédiation humaine, doctrinalement acceptable)
- Délégation liquide / procuration — si elle arrive en v3, la nommer « mandater son vote » (jamais « déléguer » — le test anti-lexique l'attrapera)
- IA repositionnée sur la recherche des biais décisionnels (seul usage autorisé par la doctrine) — exige corpus réel + backend ; jamais de suggestion prescriptive
- Synchronisation backend / adaptateur FastAPI — contrat des stores et Bundle prêts (invariants privacy des re-votes et ballot secret notés au contrat) ; brancher l'API ne change aucun écran
- Auth Duniter challenge-réponse Ed25519 — identités locales v2, duniterAddress réservé
- Notifications sortantes (email, digest) et WebSocket temps réel — tout est in-app dans le Fil ; silence-vaut-accord réservé au réversible en conséquence, et l'Assent couvre le reste
- IPFS et ancrage on-chain — proofLevel 'local' uniquement, champs réservés jamais affichés
- Jugement majoritaire, catalogue 18 modalités × 9 nominations, ProcessTemplate opérationnels, exports juridiques signés — bibliothèque documentaire ; le PV markdown/A4 reste « registre de légitimité, jamais substitut légal »
- Passerelles hors-écran complètes (relais humains institués, restitutions événementielles) et horizon des institutions symétriques réelles — consignés dans la page-manifeste/README comme horizon nommé, pas comme dette
Plan d'implémentation
-
- SOCLE, LEXIQUE ET MOTEURS — branche v2, shell vers layouts/, moods 4 ambiances, types/domain.ts complet (ci-dessus, avec Assent, frozen, choicePersonId), app/lexicon.ts (source unique + test vitest anti-lexique + 3 règles de ton) DÈS CE SPRINT, engine/ TS pur (threshold avec electionResult, nuanced, parametric avec crystallize, modeParams, settings avec replis, triage, state avec canTransition(decision,to,ctx), impact) avec vitest portant test_threshold/test_qualifier/test_nuanced/test_mode_params + specs parametric (médiane BASSE step-safe ; sum100 exactement-un-derived + clamp/renormalisation ; computeMyImpact linear-share ; detectBimodality ; dégénérescences 0-vote) + specs election (pluralité, blanc, quorum, tie) + specs state (Assent, affluence, matière, dossier) — le cas Forgeron W=7224 T=120 ⇒ 94 vert AVANT tout écran
-
- PERSISTANCE ET SEEDS — data/persistence.ts (IndexedDB, export/import, validation consent obligatoire, lineage à l'import), export_seed_bundle.py, bundles duniter-g1 + atelier-du-canal (paramétrique d'exemple calculable avec resources.amount, session frozen à cristalliser, consignation, dossier découpé, attributs, Assent démontré), tests de non-perte bloquants (33/59 clauses, 97/23/19/7224, 11 personnes, 3 mandats, protocoles), templates.ts 7 gabarits (blank en tête avec Consentement seul, large→nuancé partout sauf free-currency, requireEffects par gabarit)
-
- SHELL ET ONBOARDING — header (WorkspaceSelector + finalité A1, Cmd+K, MoodSwitcher, Décider), FAB, sceau 井 canonique, /creer 3 écrans (phrase du rêve, 7 gabarits, « Adopter notre Pacte », signature « Osons, nous verrons »), /donnees (export/import/reset, identité, attributs, mode atelier)
-
- LE GESTE — /decider complet (Q0 inline, 3 chips, ligne « Ce que ça engage » suggérée jamais bloquante, baselineNote suggéré, carte Le chemin sans code de règle, C'est urgent, dérogation asymétrique, alternatives permanentes réglage/consignation/binaire-avec-coût, suggestions R6+maturation, création structurée) + / Aujourd'hui (capture sticky, Fil sélecteurs purs incluant cartes À cristalliser / Dossier complet / accord explicite manquant, « À toi de décider » compteur nu, boutons inline) — MESURER : 2 gestes < 10 s, et la consignation aussi
-
- CYCLE DE VIE — /decisions (registre) + /decisions/[id] (timeline avec état figé, panneau Périmètre premier/second lieu auditable, Ça me concerne, objection de frontière suspensive, garde d'affluence non-ignorable + scopeKeptNote, bloc S'instruire condensé auto-repliable, fenêtres : silence-vaut-accord easy seulement + Assent + prolongation, gardes d'ouverture matière/ressources, chaînage, Remettre en question au protocole d'origine, PV + print A4, gravure honnête)
-
- LE VOTE — /decisions/[id]/vote : consentement (Assent stocké), nuancé (libellés définitifs, histogramme, commentaire 0-1), binaire hérité (jauge inertielle, phrase sujet-collectif, lien Atelier), PARAMÉTRIQUE (curseurs + clavier, statu quo, contrainte/calculé, clamp dynamique, faisceau, médiane basse expliquée, Pour moi, Explorer, frozen + geste Cristalliser du garant + quorum + bimodalité), ÉLECTION (désignation, blanc, pluralité, tie → départage humain), ballot secret d'affichage, liste arrêtée, re-vote à historique privé, mode atelier (recordedById), affiche A4, adoption ⇒ application automatique + micro-célébration 井
-
- LES TEXTES — /textes + document vivant (clauses, versions/diff auteur replié, vue projetée, inertie câblée, Provenance/GenesisBlock, Pacte effectif : resolveSettings lu par le triage avec replis, A1 finalité, préambule boussoles, sous-titre, clause requireEffects en clair) + /textes/formules (FormulaEditor sur moteur unique + section médiane/cristallisation du Réglage collectif + binaire repositionné en outil d'exception)
-
- LES MANDATS — /mandats (compteurs bruts, AUCUNE jauge), fiche (Exercice du mandat, Feux de la rampe, rapports, révocation chaînée au protocole d'origine), wizard 3 étapes pré-rempli par R6 (règle d'élection affichée)
-
- L'OBSERVATOIRE ET LA BOUCLE APPRENANTE — /decisions/observatoire (stats + consigné/outillé, ressources par unité huile/eau, épreuves du réel, Révoquées — ce qu'on en a appris, élagage), l'épreuve du réel complète (tableau recherché/constaté, measurerIds saisissent), suggestions du Fil (récurrence⇒mandat, maturation⇒protocolisation, sunset, élagage) — le système se simplifie lui-même
-
- DÉCISION INCRÉMENTIELLE — découpage en micro-décisions (chainKind element), onglet Éléments, pondération des enjeux (Concern.priority), cartographie de clôture auto-générée, clôture du dossier par le geste du garant (framing→closed gardée) — livrée après le socle car elle compose des primitives déjà construites
-
- FINITION LIVRABLE — QA mobile+desktop zéro régression (480/768/1024, touch 2.25 rem), parcours complets sur les 2 seeds ET un collectif Page blanche (« Adopter notre Pacte » < 10 min avec un non-théoricien ; consigner→observer→protocoliser déroulé ; cristallisation et départage d'élection déroulés), test anti-lexique vert, a11y clavier (curseurs inclus)/contraste, print A4 vérifié, README + CONTRIBUTING + page-manifeste publique (sans vocabulaire de livraison, close sur « La ville change, le puits demeure »), npm run build zéro erreur, screenshots