rubis/docs/decisions.md
ordinarthur 8d3bab6a89 feat: scaffold frontend monorepo + first /login screen
Monorepo Turborepo (pnpm workspaces) avec 3 packages :

- apps/web : SPA React 19 + Vite 8 + Tailwind v4 (CSS-first)
  • TanStack Router (file-based, auto code-splitting), Query, Form
  • Radix primitives bruts + CVA + clsx + tailwind-merge
  • MSW pour mocker l'API tant qu'Adonis n'est pas branché
  • Polices Bricolage Grotesque + Inter self-hostées via fontsource
  • Tokens marque (rubis, cream, ink) exposés via @theme
  • Primitives maison : Gem, Brand, Eyebrow, Button, Input, Field
  • Route /login full flow : TanStack Form + Zod + mutation Query

- apps/api : Adonis 7 (kit api, scaffold via create-adonisjs)
  • Auth access tokens (Bearer) — cf. ADR-017
  • Tuyau core déjà câblé pour la génération de types
  • Routes /api/v1/auth/{signup,login} + /api/v1/account/{profile,logout}
  • Minimal — uniquement le pont front ↔ back

- packages/shared : types TS + schemas Zod + constantes
  • Source unique de vérité partagée api ↔ web
  • Domaines : User, Org, Auth, Client, Invoice, Plan

Tooling racine : Turbo, ESLint v9 flat, Prettier, husky, lint-staged.

CLAUDE.md et docs/decisions.md mis à jour avec ADR-014 à ADR-018
(stack, monorepo, PG existant, Bearer tokens, MinIO existant)
et le pointeur vers docs/tech/architecture.md.

Logo Rubis déplacé de landing/assets/ vers /assets/ (source unique
réutilisée par la landing et l'app).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 10:10:48 +02:00

19 KiB

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-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 (organizationusersinvoices) 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à

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

## 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.