Documente la feature ajoutée en V1.1 dans toute la doc cadre : - **CLAUDE.md** : "Pure-player relance" nuancé en "La relance reste l'âme du produit", extension douce assumée. Périmètre V1/IN enrichi avec l'éditeur de factures. Glossaire enrichi (facture native, numéro de séquence, snapshot, Factur-X). Stack : ajout @react-pdf/renderer + pointeurs vers pdf-templates et les routes /parametres/facturation et /factures/nouvelle. - **docs/produit.md** : nouvelle section 4.2bis "Édition native des factures" — scope V1.1 minimal, snapshots immuables, numérotation strict séquentielle, roadmap Factur-X V1.5 / PDP V2. - **docs/flow.md** : nouvelle section 11bis (3 sources d'une facture, flow utilisateur de création, génération PDF, numérotation, snapshots, cas limites). Tableau "Ce que Rubis ne fait PAS" mis à jour (édition oui mais pas devis/avoirs/Factur-X V1). - **docs/decisions.md** : ADR-025 "Édition native des factures + roadmap Factur-X" (rationale extension douce, choix techniques notables, alternatives écartées). - **docs/tech/architecture.md** : section 6.1bis (flow technique édition native, points d'attention numérotation atomique + lazy PDF regenerate), ajout @react-pdf à la stack, routes /native + /preview-pdf + /invoice-themes + /invoice-settings documentées. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
424 lines
28 KiB
Markdown
424 lines
28 KiB
Markdown
# 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-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.
|