# Log de décisions — Rubis Sur l'Ongle

> Format ADR-light. Chaque décision : contexte, décision, rationale, alternatives, statut. Append-only — on n'efface jamais une décision passée, on en ajoute une nouvelle qui l'annule si besoin.

---

## ADR-023 · Plan Free : 2 factures actives (vs 5 initialement)

- **Date** : 2026-05-18
- **Statut** : ✅ Validée
- **Contexte** : le plan Free initial autorisait 5 factures actives en relance — un seuil défini *avant* d'avoir une vraie lecture du marché. À l'arrivée des premiers signaux qualitatifs (cf. audit `docs/tech/landing-optimisations.md`) il apparaît que **le segment cœur de Rubis** (freelance solo, artisan, consultant indépendant, kiné en libéral) émet **typiquement moins de 5 factures par mois**. Conséquence : on offre gratuitement le produit aux gens qui devraient être nos premiers payeurs, et on ne capture en payant que les boîtes plus grosses (déjà servies par Pennylane, Sellsy, Qonto recouvrement intégré, etc.).
- **Décision** : abaisser la limite Free à **2 factures actives**. Le ratio est calibré pour permettre de *tester* l'outil (créer une facture, voir une relance partir, recevoir un check-in) sans le rendre utilisable en production solo. La bascule Pro devient incontournable dès que l'usage est réel.
- **Rationale** :
  - **2 vs 0** : on garde une vraie expérience Free, indispensable pour permettre aux prospects méfiants de tester sans engagement. Free vide = friction d'inscription, on perd le funnel d'évangélisation.
  - **2 vs 3-4** : 3 factures par mois reste le bas du segment cœur (kiné, freelance ultra-petit) — laisser 3 maintient le piège. 2 force la décision dès que l'activité de relance devient récurrente.
  - **Source de vérité unique** : `PLAN_CAPS.free.activeInvoicesLimit` dans `apps/api/app/services/billing.ts`. L'UI et la copy l'utilisent en lecture (PlanLimitBanner) ou la dupliquent statiquement (CGV §5, landing Pricing, /parametres/abonnement).
- **Alternatives écartées** :
  - **Supprimer le plan Free** (essai 14 j uniquement) : plus radical, mais coupe le canal d'évangélisation. Risque trop fort tant qu'on n'a pas validé le funnel payant.
  - **Free dégradé en lecture seule après 14 jours** (création/relances coupées, historique lisible) : plus subtil mais plus complexe à implémenter UX + back, et ne règle pas le fond — les utilisateurs habitués à un Free actif vont s'énerver à la bascule.
- **Conséquences** :
  - Les orgs créées avant cette décision conservent leur grace period historique (3 mois post-signup, posée par la migration `1778157876956_alter_organizations_table`) — *pendant* la grâce aucun cap, *après* elles tombent sur la nouvelle limite à 2 (et non plus à 5).
  - La transition vers un vrai "essai 14 j Pro avec CB à l'inscription" (Stripe Setup Intent) est tracée séparément — cf. `docs/tech/landing-optimisations.md` §3 et §6, ADR à venir au moment de l'implémentation.
  - **À mesurer 30 jours après déploiement** : taux Free → Pro, churn signup à T+1 jour (les ultra-réticents abandonnent-ils plus vite ?).

---

## ADR-022 · Changelog en Markdown versionné (pas en DB)

- **Date** : 2026-05-11
- **Statut** : ✅ Validée
- **Contexte** : on a besoin d'un changelog public (SEO + tenir les users au courant) avec un toast in-app qui linke vers l'entrée correspondante. Question : où vit la donnée ?
- **Décision** : MD files versionnés dans le repo (`apps/landing/src/content/changelog/<x.y.z>.md`), chargés par les content collections d'Astro (schéma Zod). Pas de DB, pas d'admin.
- **Rationale** :
  - **Le changelog change avec le code, pas indépendamment.** Une nouvelle entrée = une release = une PR. Versionner dans Git aligne naturellement les deux.
  - **Pas d'admin à maintenir.** Le blog a une UI admin parce qu'il est généré par IA hebdomadaire + needs human review. Le changelog est écrit par le dev qui ship — il n'a pas besoin d'une UI séparée.
  - **Review en PR** — l'entrée passe sous les yeux comme tout autre code, attrape les fautes et le ton off-brand avant publication.
  - **SSG = LCP optimal.** Contenu figé au build, pas d'appel API au render.
  - **Schéma typé** (`version`/`date`/`type`/`highlights[]`) validé à build-time par Zod → impossible de pusher un .md mal formé.
- **Alternatives écartées** :
  - Mêmes tables `posts` que le blog → forçait un admin UI inutile, et la cadence (chaque release vs chaque semaine) n'a rien à voir
  - JSON unique avec toutes les versions → difficile à diff en PR, conflits de merge sur les fichiers volumineux
  - GitHub Releases / Gitea Releases → pas indexable Google, pas brandé, pas dans la DA
- **Conséquences** :
  - Le bump de `apps/web/src/version.ts` et l'ajout du `.md` doivent être **dans le même commit**, sinon le toast SPA pointe sur une ancre absente
  - Skill `/push` (cf. `.claude/skills/push/SKILL.md`) automatise ce ritual

---

## ADR-001 · Conversion 1 rubis = 10 minutes libérées

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : la gamification "rubis gagnés" a besoin d'une conversion défendable et tangible.
- **Décision** : 1 rubis = 10 minutes libérées (≈ une relance manuelle complète).
- **Rationale** :
  - Cohérent avec le bench *5h gagnées par semaine* (8h → <3h après automatisation).
  - 10 min est un chiffre rond mémorisable dans la copy.
  - Borne basse défensible — beaucoup d'études parlent de 15-20 min, on est conservateur, donc incontestable.
  - Permet ~30 rubis/mois pour une PME type, ~150 pour les power users — bon flywheel de gamification.
- **Alternatives écartées** :
  - 1 rubis = 1 € de trésorerie débloquée → trop financier, rompt la promesse temps
  - 1 rubis = 1 facture encaissée → trop discret pour les utilisateurs faible volume
  - 1 rubis = 12 ou 15 minutes → moins mémorisable, pas plus défendable
- **À reconfirmer** : après MVP, via user testing — si les utilisateurs disent "10 min c'est trop" ou "trop peu", on ajustera.

---

## ADR-002 · Direction de logo : A (gem facetté)

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : 4 directions explorées (A gem facetté, B rubis brut, C wordmark, D ongle littéral).
- **Décision** : Direction A (gem facetté géométrique) en logo principal V1.
- **Rationale** :
  - Iconique, scalable de 16 px à billboard
  - Naturellement cohérent avec le ◆ utilisé dans les wireframes pour la gamification
  - Marche en mono, knockout, filigrane
  - Symbole autonome — fonctionne en favicon et app icon sans wordmark
- **Alternatives** :
  - B (rubis brut) : trop joaillerie, risque Cartier
  - D (ongle littéral) : polarisante, risque "manucure / cosmétique"
  - C (wordmark) : retenue mais à monter en complément plus tard, pas en V1
- **Suivi** : à monter le wordmark direction C en V2 pour signature email, doc, contextes éditoriaux.

---

## ADR-003 · Couleur primaire #9F1239

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : choisir le rouge de la marque parmi plusieurs options.
- **Décision** : `#9F1239` — rubis profond légèrement violacé.
- **Rationale** : sophistiqué, distinctif, anti-Coca-Cola. Évoque le rubis "pigeon blood" (vivid red avec hints purple). Différencie radicalement des rouges tech génériques.
- **Alternatives** :
  - `#B91C3C` (rose 700) : plus punchy, plus tech
  - `#881337` : plus dark, plus austère
  - `#BE123C` : Tailwind rose-700, plus standard
- **À surveiller** : le contraste sur blanc pur peut être limite pour texte <14 px → utiliser `#771328` (rubis profond) dans ces cas.

---

## ADR-004 · Pas d'or accent

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : la palette initiale incluait un or `#B89F6B` comme accent rare.
- **Décision** : abandon total de l'or dans la marque.
- **Rationale** : "fait très cheap" (Arthur). Risque d'évoquer banque ou bling-bling. La marque tient sans or — rubis + neutres chauds suffit.
- **Conséquence** : tout document antérieur mentionnant l'or est obsolète sur ce point. La règle devient *rubis + neutres chauds, point*.

---

## ADR-005 · Typographies Bricolage Grotesque + Inter

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : choisir une paire display + body, libre et accessible.
- **Décision** :
  - **Display** : Bricolage Grotesque (500-800)
  - **Body** : Inter (400-700)
  - Les deux sur Google Fonts.
- **Rationale** :
  - Bricolage apporte un caractère "français contemporain" qui différencie de la galaxie Stripe/Linear/Notion sans tomber dans le designer-flex
  - Inter est le workhorse incontesté pour l'UI moderne, hyper-lisible, neutre
  - Les deux sont gratuites, installables en 30 secondes
- **Alternatives** :
  - General Sans : retenue mais plus neutre, moins ownable
  - Söhne : payante, hors scope MVP
  - Inter Display + Inter : trop monochrome, pas de personnalité

---

## ADR-006 · Iconographie : Lucide

- **Date** : 2026-05-05
- **Statut** : ✅ Validée (tranche-claude)
- **Contexte** : choisir un set d'icônes pour l'UI.
- **Décision** : Lucide en regular weight (1.5px stroke).
- **Rationale** :
  - 1 400+ icônes, couvre tous les use cases
  - Licence MIT, pas de dépendance commerciale
  - Cohérent avec le ton "précis pas froid"
  - Bien intégré dans l'écosystème JS (React, Vue, Svelte, Vanilla)
- **Alternatives** :
  - Phosphor Icons : très joli mais moins universel
  - Heroicons : trop associé Tailwind
  - Custom set : hors scope MVP, retour sur investissement faible
- **Règle** : le ◆ (gem) reste un SVG custom, jamais une icône Lucide.

---

## ADR-007 · Mise en demeure : validation manuelle obligatoire

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : l'étape J+20 (ou équivalent) du plan ferme est une mise en demeure. Doit-elle être envoyée automatiquement comme les autres relances ?
- **Décision** : **non**. Toute mise en demeure passe par une **modale de confirmation** où l'utilisateur clique explicitement "Envoyer la mise en demeure".
- **Rationale** :
  - Risque légal : la mise en demeure a des conséquences juridiques
  - Risque relationnel : peut casser une relation client durablement
  - Le moment où l'utilisateur veut reprendre le contrôle, c'est exactement à cette étape
- **UX** : la modale rappelle le contexte légal (loi LME), affiche le contenu de l'email, et propose deux boutons distincts ("Envoyer maintenant" / "Reporter à plus tard").
- **Anti-pattern à éviter** : checkbox d'auto-envoi, même opt-in. Pas négociable.

---

## ADR-008 · Banking V1 : check-in email à l'utilisateur

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : sans intégration bancaire en V1, comment éviter de relancer un client qui a déjà payé hors plateforme ?
- **Décision** : avant chaque envoi de relance, Rubis envoie un email **à l'utilisateur** (pas au client) demandant : "Avez-vous été payé pour la facture X ?" avec deux boutons (Oui / Non).
  - L'utilisateur configure la cadence et le timing (ex. T-2 jours avant la relance suivante)
  - "Oui" → facture marquée encaissée, plan stoppé
  - "Non" ou pas de réponse → la relance part comme prévu
- **Rationale** :
  - Évite les relances fantômes sur factures déjà payées
  - Garde l'utilisateur informé sans le surcharger
  - Architecture compatible V2 banking : le check-in V1 devient simplement un fallback quand on ajoutera Bridge / Tink / GoCardless
- **Contrainte architecturale** : modèle d'événement abstrait `payment_status_check` qui peut être déclenché par email-réponse OU webhook bancaire. L'app ne doit pas savoir d'où vient le signal.

---

## ADR-009 · SMS et multi-utilisateurs : V2, plans payants

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : décider du périmètre V1 vs V2 sur deux features demandées.
- **Décision** :
  - **SMS** : V2 uniquement, **plan le plus cher seulement** (Business)
  - **Multi-utilisateurs** : V2 uniquement, **plans payants seulement** (Pro et Business, pas Free)
- **Rationale** :
  - SMS = coût marginal direct (Twilio/OVH/etc.) → doit être monétisé sur le palier le plus rentable
  - Multi-utilisateurs ajoute de la complexité (rôles, invitations, audit) → out of MVP, et c'est un argument upgrade naturel pour les plans payants
- **Conséquence sur l'archi** :
  - DB structurée multi-tenant dès V1 (`organization` → `users` → `invoices`) pour ne pas refactorer
  - Modèle d'envoi message abstrait (un message peut être email V1, ou SMS/email V2)

---

## ADR-010 · Pas d'intégration banking en V1

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : l'intégration banking (Bridge / Tink / Powens) automatiserait la réconciliation des paiements.
- **Décision** : pas en V1. Remplacée par check-in email (ADR-008).
- **Rationale** :
  - Coût d'implémentation important (DSP2, agréments, gestion des erreurs banking)
  - Impose de toucher aux données financières du client → friction RGPD/sécurité
  - Le check-in email résout 80 % du problème pour 10 % du coût
- **À prévoir en V1** : architecture événementielle qui rendra l'intégration V2 mécanique (cf. ADR-008).

---

## ADR-011 · "3 clics maximum" est une règle de design, pas un compteur

- **Date** : 2026-05-05
- **Statut** : ✅ Validée (clarification)
- **Contexte** : la promesse "3 clics" est dans le pitch et la landing. Doit-on la respecter strictement à chaque parcours ?
- **Décision** : c'est une **règle de design** (l'utilisateur doit faire le minimum d'actions possible) plus qu'un compteur strict. Si le plan par défaut est bien configuré, 2 clics suffisent. Si le contexte demande plus, on peut aller à 4.
- **Rationale** : "3 clics" est une promesse marketing claire et mémorable. Le vrai principe sous-jacent est "minimum d'actions utilisateur".
- **Application** :
  - Le storyboard hero (drop → valider → récompense) doit absolument tenir en 3 clics ou moins
  - Les flows secondaires (changer un plan, archiver) peuvent dépasser sans casser la promesse
  - On ne sacrifie pas la clarté pour économiser un clic

---

## ADR-013 · Tagline V1 « Vos factures relancées toutes seules pendant que vous travaillez »

- **Date** : 2026-05-05
- **Statut** : ✅ Validée pour la landing V1
- **Contexte** : 5 taglines candidates explorées dans `/docs/munitions-marketing.md` (douces vs polarisantes vs factuelles).
- **Décision** : retenir *« Vos factures relancées toutes seules pendant que vous travaillez. »*
- **Rationale** :
  - Image concrète et duale : vous travaillez / l'app travaille — facile à se représenter
  - Respecte les 4 principes de marque (chaleureuse, action, précise, rubis-centric implicite)
  - Promet le bénéfice (libération de temps) plutôt que le problème (impayés) — différencie de tous les concurrents
  - Tient en signature email, en hero de landing, en pitch oral
- **Alternatives écartées** :
  - *« Ne mendiez plus jamais ce qu'on vous doit »* : trop polarisante pour la landing. Garde en réserve pour campagnes ads ciblées.
  - *« 3 clics. Plus jamais de relance manuelle »* : trop factuelle, pas d'émotion.
  - *« Arrêtez de courir après votre argent »* : impératif négatif, casse le ton chaleureux.
  - *« L'argent, c'est sérieux. La relance, ça ne devrait plus l'être »* : trop intellectualisée.
- **À reconfirmer** : après MVP, A/B testable sur Google Ads contre la version polarisante.

---

## ADR-014 · Stack technique

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Contexte** : choix du stack initial pour démarrer le développement de Rubis. Bloquant pour les ADR suivants (domain model, repo layout, etc.).
- **Décision** :
  - **Backend** : AdonisJS v7 (TypeScript, MVC, Lucid ORM, auth & jobs intégrés)
  - **Frontend** : React + Vite + TanStack Router + TanStack Query
  - **Base de données** : PostgreSQL
  - **Hosting** : cluster Proxmox personnel + K3s (déjà en place pour la landing)
- **Rationale** :
  - **AdonisJS v7** : "batteries included" en TS first — Lucid pour le SQL relationnel, Auth pour les sessions/tokens, Bouncer pour les permissions, Bull pour les jobs, Mailer pour l'email. Évite l'assemblage Express/Fastify + 12 libs.
  - **React + Vite** : DX moderne, build rapide, écosystème massif. Maturité éprouvée pour un SaaS B2B.
  - **TanStack Router** : routing client-side type-safe avec search params réactifs (idéal pour les filtres de la liste de factures). Pas couplé à un framework SSR.
  - **TanStack Query** : cache + invalidation + retry + optimistic updates pour le state serveur. Évite Redux pour gérer du data API.
  - **PostgreSQL** : transactions ACID indispensables pour la facturation, support JSON pour les payloads OCR, full-text search natif si besoin.
  - **Proxmox + K3s** : maîtrise totale du runtime, coût marginal nul (infrastructure existante), pas de vendor lock-in. Le pipeline Gitea CI → registry → K3s rollout est déjà rodé sur la landing.
- **Conséquences architecturales** :
  - **API REST séparée du SPA** (pas Inertia.js) — TanStack Router signifie que le client gère son propre routing. CORS et type-sharing entre back et front à organiser proprement.
  - **TypeScript end-to-end** rend possible un dossier de types partagés (ex. `packages/shared/`) ou la génération auto via OpenAPI/Zod.
  - Le build front (Vite) produit des assets statiques — soit servis par AdonisJS (`/public/build/`), soit par nginx en sidecar du pod, soit déployés en parallèle.
- **Alternatives écartées** :
  - **Next.js / Remix** : trop SSR-centric pour un SaaS transactionnel. Le backend des frameworks meta-React reste plus faible que ce qu'offre Adonis.
  - **Express ou Fastify + ORM (Prisma/Drizzle)** : assemblage de 12 libs pour atteindre ce qu'Adonis livre out-of-the-box.
  - **MongoDB / NoSQL** : pas adapté aux relations facture-client-relance ni aux transactions financières.
  - **Vercel / Render / Fly.io** : coûts récurrents évitables — Proxmox déjà en place et payé.
- **Décisions à formaliser dans la foulée** : repo layout (mono vs split), hébergement Postgres, auth flow (cookie vs token), file storage des PDF, domain model.

---

## ADR-015 · Repo layout : monorepo (apps/api + apps/web)

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Décision** : un seul repo Git, deux applications dans `apps/api/` (AdonisJS) et `apps/web/` (React/Vite), un dossier `packages/shared/` pour les types TS partagés. Workspaces gérés en pnpm.
- **Rationale** :
  - Le type-sharing entre API et SPA est gratuit (un import depuis `@rubis/shared`)
  - Une seule release coordonnée → pas de problèmes de version drift entre API et SPA
  - CI/CD unique, scripts npm racine
  - Solo dev TS = monorepo natural fit
- **Alternatives écartées** :
  - **Deux repos séparés** : friction sur le type-sharing, releases à coordonner manuellement
  - **Adonis monolithe avec front intégré (Inertia)** : aurait écarté TanStack Router (couplage Adonis routing). Adopté seulement si on retire TanStack Router de la stack.

---

## ADR-016 · PostgreSQL : LXC Proxmox existant

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Décision** : utiliser le serveur PostgreSQL déjà provisionné dans le LXC Proxmox d'Arthur. Créer une base `rubis` dédiée + un user `rubis_user` avec les permissions nécessaires.
- **Rationale** :
  - Infrastructure existante, zéro coût d'infra additionnel
  - Backups Proxmox existants (snapshots LXC) couvrent la base
  - Performance native (pas la couche K3s), latence faible avec le cluster K3s sur le même réseau
  - Hors cluster K3s = isolement des changements applicatifs (rollouts ne touchent jamais la DB)
- **À mettre en place** :
  - Créer une `database rubis` + user dédié + grants minimum (CREATE/SELECT/INSERT/UPDATE/DELETE sur les tables de la base)
  - Service K3s ou ExternalName pour exposer la connexion PG aux pods API
  - Sauvegarde dump PG quotidienne dans MinIO (script cron côté LXC)

---

## ADR-017 · Auth : access tokens (Bearer)

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Décision** : authentification via **access tokens stateless** (Bearer header) via `@adonisjs/auth` v7. Pas de session cookie côté API.
- **Rationale** :
  - Architecture API propre, agnostique du client (web V1, mobile V2, intégrations partenaires V3+ supportées sans refactoring)
  - Tokens stockés en base (`auth_access_tokens` Adonis) → révocation possible côté admin
  - Permet des **abilities/scopes par token** (utile en V2 pour donner un token "lecture seule" au comptable)
- **Implémentation** :
  - **API** : middleware `auth` Adonis sur les routes protégées, sortie `Bearer token` à l'inscription/login
  - **SPA** : token stocké en mémoire (variable de module/closure) + refresh token en cookie httpOnly pour persistance après reload, pour limiter le risque XSS
  - **TTL** : access token 30 min, refresh token 30 jours (à valider au moment de l'implémentation)
- **Alternatives écartées** :
  - **Session cookies @adonisjs/auth** : plus simple en V1 mais oblige à tout refaire au moment d'ajouter une API mobile/tiers
  - **Tokens en localStorage seul** : exposé XSS (lecture par n'importe quel script tiers), pas safe pour des données financières

---

## ADR-018 · File storage : MinIO Proxmox existant

- **Date** : 2026-05-05
- **Statut** : ✅ Validée
- **Décision** : utiliser le MinIO déjà provisionné sur l'infra Proxmox d'Arthur pour stocker les PDF de factures et les pièces jointes.
- **Rationale** :
  - Infrastructure existante, zéro coût additionnel
  - API S3-compatible → utilise n'importe quelle lib AWS SDK
  - Migration future vers cloud S3 (R2/B2) triviale (mêmes appels)
  - Pre-signed URLs natives MinIO pour les téléchargements client-side sécurisés
- **À mettre en place** :
  - Bucket `rubis-invoices` (PDF + images de factures)
  - Bucket `rubis-attachments` (pièces jointes utilisateurs : signatures, logos)
  - Credentials Access Key/Secret dédiés avec permissions limitées à ces 2 buckets
  - Politique de retention : pas de purge auto en V1 (les factures sont des documents légaux)
- **Alternatives écartées** :
  - **Local pod + PVC** : ne scale pas si plusieurs replicas, backup non trivial
  - **Cloudflare R2 / Backblaze B2** : sortie de l'infra perso pour rien — MinIO existe déjà

---

## ADR-024 · Error monitoring : Sentry SaaS (free tier)

- **Date** : 2026-05-08
- **Statut** : ✅ Validée
- **Décision** : utiliser **Sentry SaaS** (free tier) pour le monitoring d'erreurs et le replay session, avec 2 projets distincts `rubis-api` (Node) et `rubis-web` (React).
- **Rationale** :
  - Free tier suffisant pour V1 (5 K events/mois, 50 replays/mois) — on calibre les sample rates pour rester dedans.
  - Stack standard, vaste écosystème d'intégrations (AdonisJS, React, Vite plugin).
  - **Source maps uploadées au build** par `@sentry/vite-plugin` puis SUPPRIMÉES du `dist/` final (`filesToDeleteAfterUpload`) → stack traces désobfusquées dans Sentry, mais pas exposées publiquement par nginx.
  - **Releases trackées au sha git** : `APP_VERSION=${{ github.sha }}` injecté en build-arg (web) et runtime env (api). Une régression ↔ un commit, sans ambiguïté.
- **Sample rates** :
  - traces : 10 % prod, 100 % dev (debug)
  - profiles : 100 % (sampled par traces)
  - replay session : **0 %** (pas de replay sans erreur — économie quota)
  - replay sur erreur : **100 %** (capture les 30 s précédant le crash, debug post-mortem)
- **Privacy / PII** :
  - User context Sentry : **`user.id` UUID seulement**, pas d'email ni nom (minimisation PII).
  - Replay : `maskAllText: true` + `blockAllMedia: true` (sécurité par défaut, on relâche après si besoin).
  - Tags Sentry : on log le **pattern de route** (`/api/v1/checkin/:token/paid`) pas l'URL réelle, sinon les codes OAuth (`?code=...`) et les tokens de check-in fuiteraient dans des champs indexés.
  - 4xx attendues filtrées dans `beforeSend` côté API (validation, auth invalide → bruit, pas une erreur).
- **Implémentation** :
  - API : `apps/api/start/sentry.ts` chargé en 1er dans `bin/server.ts`, `Sentry.captureException` dans `app/exceptions/handler.ts:report` pour les 5xx.
  - Web : `apps/web/src/lib/sentry.ts` chargé en 1er dans `main.tsx`, `Sentry.ErrorBoundary` autour de l'app, `Sentry.setUser` dans `authStore.setSession/clear`.
  - DSN web : variable `VITE_SENTRY_DSN_WEB` (publique by-design — bake-able dans le bundle).
  - DSN api : variable `SENTRY_DSN_API` (env runtime K3s).
  - Auth token CI : `SENTRY_AUTH_TOKEN` secret Gitea, lu uniquement au build pour upload des sourcemaps. Jamais en runtime.
- **Alternatives écartées** :
  - **Self-hosted Sentry** : opérationnellement coûteux pour V1 solo founder. À reconsidérer si quota free tier explosé.
  - **Datadog / New Relic / Bugsnag** : cher au-delà du free tier, overkill pour la taille V1.
  - **Pas de monitoring** : insoutenable en prod B2B SaaS — un bug silencieux peut perdre un client en 24h.

---

## ADR-025 · Édition native des factures + roadmap Factur-X

- **Date** : 2026-05-14
- **Statut** : ✅ Validée (V1.1)
- **Contexte** : utilisateurs cibles TPE-PME — beaucoup n'ont pas d'outil de facturation et émettent leurs factures à la main (Word, Excel, parfois rien). Friction à l'adoption de Rubis : "je dois d'abord créer ma facture ailleurs, puis l'uploader ici". Question : Rubis peut-il aussi émettre les factures, ou rester pure-player relance ?
- **Décision** : ajouter une **édition native des factures** en V1.1 (`/factures/nouvelle`) comme **extension douce** au cœur relance, **pas** comme pivot vers un outil de facturation complet. Périmètre V1.1 minimal : factures simples avec lignes, TVA, 4 thèmes pré-faits, numérotation strict séquentielle, snapshots immuables. Pas de devis, pas d'avoirs, pas d'acomptes, pas de récurrence (tout V2+).
- **Rationale** :
  - **Marché adressable élargi** : on capture les TPE-PME qui n'ont aucun outil de facturation (segment "Excel" ou "Word + papier"). Coûte peu de complexité produit et ne nous met pas en concurrence frontale avec Pennylane/Sellsy (qui font CRM + comptabilité + multi-fonctions).
  - **Cohérence avec la promesse** : la relance reste l'âme. La création de facture est un *moyen* pour amener plus vite à la relance ("vous créez, on relance"), pas une feature de premier rang dans la com publique.
  - **Snapshots immuables** : une facture émise ne change jamais — c'est une preuve comptable. `client_snapshot` et `issuer_snapshot` figés à l'émission, le PDF stocké sur MinIO. Modifier l'adresse du client ou les settings de l'org n'altère pas les factures déjà émises.
  - **Numérotation strict séquentielle** : l'art. 242 nonies A du CGI exige une chronologie continue, sans rupture. Compteur per-org alloué en transaction avec `SELECT FOR UPDATE` sur la ligne `organizations`, brouillons exclus (qui peuvent être supprimés sans créer de gap). Choisi vs flexible parce que la conformité prime sur l'ergonomie de "je veux mettre n'importe quel numéro".
  - **Génération PDF côté serveur via `@react-pdf/renderer`** : composants TSX dans `apps/api/app/pdf-templates/`, dispatcher par slug. Léger (pas de Chromium dans K3s, image Docker reste petite), composants React → preview client possible plus tard si nécessaire. Pour V1.1, la preview web passe par `POST /invoices/preview-pdf` → Blob → objectURL → iframe (debounce 500 ms côté éditeur). Single source of truth pour le rendu.
  - **4 thèmes pré-faits + accent paramétrable** : couvre 80 % des besoins esthétiques. Plus simple à livrer qu'un éditeur WYSIWYG drag-and-drop, plus différenciant qu'un seul template.
- **Roadmap conformité Factur-X (réforme 2026-2027)** :
  - **V1 (maintenant)** : PDF classique avec mentions fr-FR complètes (pénalités L441-10, escompte L441-9, identité émetteur). Suffit jusqu'à l'échéance d'émission TPE-PME au **1er septembre 2027**.
  - **V1.5 (Q3-Q4 2026)** : génération **Factur-X natif** (PDF/A-3 + XML CII embarqué). On reste l'émetteur direct, pas besoin de devenir PDP. Compatible avec les PDP des destinataires (réception côté ETI/GE obligatoire 1er sept 2026).
  - **V2 (S1 2027)** : intégration **PDP partenaire** pour la transmission via le PPF si demandes clients. Choix du partenaire (Pennylane Connect, Cegid, Tiime…) à benchmarker au moment.
- **Alternatives écartées** :
  - **Rester pure-player relance** : laisse de côté un segment significatif (TPE qui facturent dans Excel). On gagne en simplicité mais on perd en TAM.
  - **Pivot complet vers facturation** : énorme chantier (devis, avoirs, acomptes, récurrence, multi-TVA, multi-devises, FEC, e-invoicing PDP), concurrence frontale avec des outils établis, dilue la promesse "relance toutes seules".
  - **Mode brouillon interne uniquement** (factures non-légales, watermark "Brouillon") : pas de valeur réelle — un utilisateur qui édite chez Rubis veut envoyer la facture au client, pas un brouillon.
  - **Éditeur WYSIWYG drag-and-drop** : équivalent d'un Figma simplifié dans le navigateur, plusieurs mois de boulot. Galerie de 4 thèmes pré-faits couvre l'usage avec une fraction du coût.
  - **Pas de Factur-X** : ignorer la réforme = laisser nos clients en infraction au 1er sept 2027. Inacceptable.
  - **Intégration PDP partenaire en V1** : prématuré (PDP encore peu matures), coûts récurrents partagés, dépendance forte. À reconsidérer si le marché se consolide d'ici 2027.
- **Conséquences** :
  - Le `CLAUDE.md` est nuancé : "*La relance reste l'âme du produit*" remplace "*Pure-player relance*". La V1.1 est une extension, pas un pivot.
  - Nouvelle stack côté API : `@react-pdf/renderer` ajouté aux dependencies. Image Docker grossit peu (pas de Chromium).
  - Nouvelle table jamais : tout passe par enrichissement de `invoices` (jsonb) et `organizations.invoice_settings` (jsonb). Migrations rétro-compatibles (factures OCR existantes restent intactes, `is_native = false`).
  - Marketing : positionnement reste "Vos factures relancées toutes seules". L'éditeur natif est un *feature secondaire* dans la landing, pas la tagline.

---

## Décisions à venir (en attente)

| # | Sujet | Pourquoi en attente |
|---|---|---|
| 019 | Domain model / DB schema (entités, relations, index) | À écrire — débloqué par 014-018, prochaine étape technique |
| 020 | Provider OCR (Mindee, Document AI, Textract, Tesseract self-hosted) | À benchmarker (coût + qualité sur factures FR) |
| 021 | Provider email outbound (Resend, Postmark, SendGrid, AWS SES) | À benchmarker (deliverability FR + prix au volume) |
| 022 | Pricing exact (Free 5 factures ? Pro 19 € ? Business 49 €) | À tester avant figer |
| 023 | Endpoint waitlist (Resend / Formspree / Tally / API Adonis perso) | Choix simple au moment du push de la landing |

---

## Comment ajouter une décision

```markdown
## ADR-XXX · Titre court

- **Date** : YYYY-MM-DD
- **Statut** : ✅ Validée | 🟡 En cours | ❌ Annulée par ADR-YYY
- **Contexte** : la situation qui appelle la décision
- **Décision** : ce qu'on fait
- **Rationale** : pourquoi (avec preuves/data si possible)
- **Alternatives écartées** : ce qu'on n'a pas fait, et pourquoi
- **Conséquences** : impact sur l'archi, le produit, la marque
```

Quand une décision est remise en cause, on **ajoute** un ADR qui annule l'ancien (jamais éditer rétroactivement). C'est ça qui rend le log fiable comme mémoire d'équipe.
