21 Commits

Author SHA1 Message Date
ordinarthur
fb248553a8 docs(audit-3/3): aligner brand + marketing sur le code livré
Audit cross-doc/code, batch brand & marketing : nettoyage de l'or
accent obsolète, correction des chemins cassés, retrait du hashtag
#recouvrement des templates LinkedIn (mot interdit en com publique
par marque.md).

docs/brand-identity.html
- §3 Palette : retrait du swatch « Or discret #B89F6B » et de la
  description « accent or à doser » (CLAUDE.md indiquait le caveat
  depuis le début, mais le HTML lui-même n'avait jamais été
  nettoyé). Reformulation alignée avec marque.md (rubis +
  neutres only).
- §Notes finales : « Or accent : à utiliser très peu » → « 
  retiré de la palette » avec renvoi vers marque.md
- (Variable CSS --gold gardée car référencée dans deux contextes
  inactifs lignes 24/203 — non visible côté affichage. Non
  bloquant.)

docs/marque.md
- §Préambule : lien `/brand-identity.html` → `/docs/brand-identity.html`
  (chemin réel)
- §Typographie : « Source Google Fonts » → « self-hosted via
  @fontsource-variable/{bricolage-grotesque,inter} » (réalité
  package.json)
- Header bumpé 2026-05-05 v0.1 → 2026-05-09 v0.2

docs/munitions-marketing.md
- §Pricing : retrait de la promesse « intégration banking » sur
  le plan Business — explicitement V2+ (CLAUDE.md V1 OUT). Note
  le check-in email comme alternative V1.

docs/marketing/playbook.md
- §Templates LinkedIn : hashtag `#recouvrement` → `#impayés` +
  warning explicite renvoyant à marque.md:151
- §Annexe ressources : chemin obsolète `/landing/index.html` →
  chemin réel `apps/landing/src/pages/index.astro`

docs/marketing/launch-kit.md (NOUVEAU — était untracked)
- Tracked avec correction des 2 hashtags #recouvrement →
  #impayés (variantes A et B des posts LinkedIn). La stat
  AFDCC ligne 39 (« taux de recouvrement ») est gardée — terme
  technique cité dans un contexte factuel, pas un positionnement.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 19:17:27 +02:00
ordinarthur
9eaac0c7ef docs(audit-2/3): aligner doc tech sur le code livré
Audit cross-doc/code, batch tech : architecture, backend, frontend,
dev-setup. Corrige les claims qui pouvaient induire un dev en erreur
(noms de services K3s, hostnames Traefik, Tuyau, queue wrapper,
seeders, env vars, polices).

architecture.md
- Composants : status « À écrire » → «  Déployé » (apps/web,
  apps/api, packages/shared) ; ajout Redis Deployment K3s ; OCR =
  Mistral choisi ; mail = Resend (sortant) + OVH MX (entrant)
  validés
- §7.5 Pods K3s : noms réels (rubis-api / rubis-web / rubis-landing
  / rubis-redis, pas de *-svc) ; pas d'IngressRoute api.rubis.pro
  (l'API est servie via app.rubis.pro/api/* proxifié par nginx du
  pod web) ; PG/MinIO en URL directe dans la ConfigMap, pas de
  Service ExternalName
- §10 Décisions en attente : ADRs 019-024 mises à jour
  (tranchées / obsolètes), suppression du wording « à venir » pour
  les choix déjà figés dans le code

backend.md
- Note de cohérence en tête : pointe vers start/routes.ts comme
  source de vérité de la surface API (~80 routes — Stripe,
  Demo, AI, Microsoft SSO, admin blog, posts publics, KPIs
  timeseries) que cette doc n'inventorie pas exhaustivement
- §1 Vue d'ensemble : Tuyau marqué « non utilisé en pratique »
  (présent en deps mais zéro import côté SPA), partage de types
  via packages/shared. OCR Mistral choisi. Mail Resend choisi.
  BullMQ direct (workers inline pod API). Sentry ADR-024.
- §2 Stack : queue = BullMQ direct (pas @rlanz/bull-queue, qui
  n'est pas installé) ; type-sharing = packages/shared
- §2 Dépendances : remplacé la todo-list pré-livraison par la
  liste réelle des packages dans apps/api/package.json
- §3 Repo layout : `database/factories/` (dossier) → `factories.ts`
  (mono-fichier) ; `database/seeders/{default_plans,demo_data}` →
  inexistants, services à la place
- §13.2 Jobs : ProcessOcrJob + RecomputeKpisJob retirés
  (n'existent pas — OCR synchrone via services/import_batch.ts,
  KPIs calculés on-the-fly). Liste des jobs réels :
  send_relance, send_checkin, send_payment_thanks
- env vars : MINIO_* → S3_* (cf. .env.example + manifest k3s) ;
  bucket prod = rubis-prod-invoices

frontend.md
- Note de cohérence en tête : Tuyau pas utilisé, tokens dans
  packages/ui (pas inline), polices @fontsource-variable (pas
  Google Fonts via <link>)
- §1 Vue d'ensemble : client API = fetch minimaliste dans
  apps/web/src/lib/api.ts ; périmètre livré = ~15 routes _app/
- §3 Polices : section Google Fonts → @fontsource-variable
  (avec note preload woff2 critique sur la landing Astro)
- §4 Routes : arbo `_onboarding/` (faux) → `onboarding/`
  (réel, segment URL) + ajout admin.blog*, clients_.$id, insights,
  parametres_.abonnement, plans_.nouveau, factures_.import
- §6 Tuyau : section marquée « historique, non utilisé en V1 »
  avec note explicative en tête
- §10 env vars : VITE_API_URL=https://api.rubis.pro → vide
  (proxifié same-origin par nginx) + ajout VITE_USE_MOCKS,
  VITE_SENTRY_DSN_WEB, VITE_APP_VERSION

dev-setup.md
- Mailhog → Mailpit (3 occurrences) — c'est ce qui tourne dans
  docker-compose.dev.yml

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 19:13:44 +02:00
ordinarthur
801168fc74 docs(audit-1/3): aligner top-level + produit sur le code
Issu d'un audit de cohérence doc-vs-code. Corrige les claims périmés
sur le périmètre V1, la nomenclature des plans, les statuts de
facture et les sources typographiques.

CLAUDE.md
- IN/V1 : ajout Microsoft SSO + Stripe billing + mode démo (livrés)
- IN/V1 : « admin blog (à venir PR3) » → admin blog livré
- IN/V1 : 4 plans nommés concrètement (Standard B2B, Rapide, Patient,
  Ferme) au lieu de la mention vague « 4 plans fournis »
- Glossaire : « Check-in » → « Confirmation » (terme migré dans
  flow.md depuis ADR-008, alignement)
- Brand identité : typo Bricolage + Inter sont self-hosted via
  @fontsource-variable, pas Google Fonts via <link>
- Brand : lien `/brand-identity.html` → `/docs/brand-identity.html`
  (chemin réel) + retrait du caveat « or accent obsolète » (HTML
  sera nettoyé dans le batch brand)
- Bumpé la dernière maj 2026-05-07 → 2026-05-09

docs/produit.md
- §4.1 : ajout Microsoft SSO
- §4.3 : 4 plans réels (Standard B2B / Rapide / Patient / Ferme),
  variables Mustache réelles ({{client.name}}, {{amount}},
  {{dueDate}}), tons réels (amical | courtois | ferme |
  mise_en_demeure)
- §7 : trial commercial 30 jours (vs 14j historique). Note
  l'incohérence avec la « grâce 3 mois » dans billing.ts à fixer
  dans un PR séparé
- §8 : statut `relanced` → `in_relance` (constants/index.ts) +
  « check-in » → « confirmation »
- Header bumpé 2026-05-05 v0.1 → 2026-05-09 v0.2

docs/flow.md
- §6.1 : tons d'étapes corrigés (était amical|ferme|stricte —
  réel : amical|courtois|ferme|mise_en_demeure)
- Slug exemple : `standard-b2b` → `standard-30j` (réel)

docs/wireframes-mvp.html
- Compteur « 13 écrans » nuancé : c'était le scope low-fi initial,
  la SPA livrée en compte ~15 + onboarding multi-étapes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 19:06:59 +02:00
ordinarthur
eda5436d12 fix(seo): smart title suffix + OG image par défaut 1200×630
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 1m1s
Audit SEO révélait deux pertes de valeur :

1. **Titres trop longs** (86 chars sur la home, 71 chars sur les articles).
   Google tronque à ~60 chars dans le SERP. Le suffixe automatique
   `— Rubis sur l'ongle` (20 chars) écrasait le message-clé.

   Layout.astro fait maintenant un suffix smart :
   * Si title <45 chars ET ne contient pas "Rubis" → suffix `— Rubis` (8 chars)
   * Sinon → titre tel quel (la brand est déjà couverte par og:site_name +
     JSON-LD publisher + le hostname rubis.pro visible dans la SERP).

   Résultat : home 64 chars, article 51 chars, "Mentions légales" → 24 chars
   avec suffix.

2. **Pas d'`og:image` sur les pages sans hero** (home, légal). Sur
   LinkedIn/X/Slack, aucune preview image — perte d'engagement énorme.

   Ajout d'un og-default.png 1200×630 (165 KB optimisé) dans
   apps/landing/public/, monté par fallback par Layout.astro quand la
   page ne fournit pas d'`ogImage` explicite. Twitter:card devient
   toujours `summary_large_image`.

   L'image a été générée via le nouvel outil HTML
   docs/marketing/assets/og-default.html (clone de la mécanique du
   linkedin-banner.html — clic sur "Télécharger PNG" et go).
   Compose : brand + tagline + mock card 124 rubis + CTA rubis.pro.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 18:14:28 +02:00
ordinarthur
e5530930b3 feat: refactor frontend en stack React unifiée (Astro + packages/ui)
Some checks failed
Build & Deploy API / build-and-deploy (push) Failing after 17s
Build & Deploy Web / build-and-deploy (push) Successful in 1m15s
Build & Deploy Landing / build-and-deploy (push) Failing after 3m43s
Trois surfaces partagent désormais le même design system, Tailwind v4
et React 19 — au lieu d'avoir landing en HTML vanilla, app en React, et
blog en Adonis SSR :

* packages/ui — design system partagé (tokens Tailwind v4 + composants
  TSX) extrait depuis apps/web : Brand, Gem, Button, Card, Chip, Eyebrow,
  EmptyState. apps/web migre 41 imports vers @rubis/ui.

* apps/landing — nouvelle app Astro 6 SSR (rubis.pro), remplace l'ancienne
  landing nginx vanilla. Embarque :
  - Landing complète portée en sections React (Hero, Stats, Promise,
    HowItWorks, Gamification, Legal, Pricing, FAQ, FinalCTA, Footnotes)
  - Pages légales (mentions, confidentialité, CGV) via LegalLayout.astro
  - Blog SSR (/blog, /blog/:slug) qui consomme /api/v1/posts
  - sitemap.xml, blog/rss.xml, robots.txt en endpoints Astro
  - SEO complet (canonical, hreflang, OG, Twitter Card, JSON-LD
    Article/BreadcrumbList/Blog/SoftwareApplication)

* apps/api — BlogController réduit à 2 endpoints JSON (GET /api/v1/posts
  + GET /api/v1/posts/:slug). Suppression des templates SSR Adonis
  (apps/api/app/blog/), de l'alias #blog/*, des deps react-dom et
  @types/react-dom. PostTransformer + PostSummaryTransformer ajoutés.
  Le service blog_renderer + le seeder + les 3 articles fondateurs
  restent intacts (réutilisés par futurs admin + cron IA).

* Infra :
  - Dockerfile.landing (multi-stage Node 22 + tini, Astro standalone)
  - k3s/app/landing.yml (Deployment + Service rubis-landing:4321 +
    ConfigMap avec API_URL=http://rubis-api.rubis.svc.cluster.local:3333)
  - .gitea/workflows/deploy.yml mis à jour pour build rubis-landing
  - .gitea/workflows/deploy-web.yml + Dockerfile.web : prennent en
    compte packages/ui/ comme dépendance
  - Suppression du Dockerfile nginx legacy + k3s/{deployment,service}.yml
  - Suppression de landing/ (assets favicons migrés vers
    apps/landing/public/)

* Docs : architecture.md (vue d'ensemble + §4bis apps/landing complet,
  §3 endpoints JSON blog, layout monorepo), CLAUDE.md (stack technique,
  documents associés, déploiement).

Note infra : l'ancien Deployment "rubis" (nginx) et son Service ne sont
PAS supprimés par la CI — à nettoyer manuellement après validation que
Traefik a été repointé sur rubis-landing:4321 dans le repo proxmox.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 15:09:13 +02:00
ordinarthur
5127cd2c9e feat(landing): pages légales + CTAs trial 30 jours
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 19s
- CGV B2B SaaS (16 sections, conforme avec Stripe en place)
- Mentions légales allégées au strict minimum LCEN
- Politique de confidentialité resserrée :
  - retrait des détails infra (Proxmox, K3s, etc.)
  - sous-traitants par catégorie (Stripe / Resend / Mistral AI)
  - section sécurité standardisée
  - cookies simplifiés
- Période d'essai harmonisée à 30 jours partout (landing + CGV)
- Insistance sur l'hébergement et les données en France
2026-05-08 14:29:22 +02:00
ordinarthur
f33b2dd319 feat(observability): Sentry monitoring API + Web (ADR-024)
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 1m9s
Build & Deploy API / build-and-deploy (push) Successful in 2m2s
Intégration Sentry SaaS pour error monitoring + replay sur les 2 apps.

API (apps/api) :
- start/sentry.ts : init au plus tôt dans bin/server.ts (avant Ignitor)
  pour capturer les erreurs de bootstrap. No-op si SENTRY_DSN_API absent.
- app/exceptions/handler.ts:report : captureException sur les 5xx avec
  tags { url, method, status } et user.id (PII minimisée). 4xx filtrés
  par beforeSend dans start/sentry.ts (validation, auth invalide = bruit).
- start/env.ts : SENTRY_DSN_API + APP_VERSION optionnels.
- bin/server.ts : import #start/sentry en 1er.
- @sentry/node + @sentry/profiling-node ajoutés au package.json.

Web (apps/web) :
- src/lib/sentry.ts : init au plus tôt dans main.tsx, BrowserTracing +
  Replay (0% session, 100% sur erreur — économie quota free tier).
  maskAllText + blockAllMedia pour privacy par défaut.
- src/lib/auth.ts : Sentry.setUser({ id }) au login, setUser(null) au
  logout (corrélation cross-stack des erreurs front avec un user).
- src/main.tsx : ErrorBoundary autour de l'app avec FallbackError UX.
- vite.config.ts : @sentry/vite-plugin uploads les sourcemaps + les
  SUPPRIME du dist/ final (filesToDeleteAfterUpload) pour ne pas leak
  le code source via nginx en prod. Helper resolveAppVersion() pour
  injecter le sha git en dev (le shell n'étant pas évaluable dans .env).
- src/lib/env.ts : VITE_SENTRY_DSN_WEB + VITE_APP_VERSION optionnels.
- .env.development : VITE_SENTRY_DSN_WEB (préfixé correctement pour
  être exposé par Vite — l'ancienne SENTRY_DSN ne marchait pas).
- @sentry/react + @sentry/vite-plugin ajoutés au package.json.

CI Gitea :
- deploy-api.yml : kubectl set env APP_VERSION=${{ github.sha }}
  runtime → release Sentry trackable au commit pour l'API.
- deploy-web.yml : build-args VITE_SENTRY_DSN_WEB, VITE_APP_VERSION,
  SENTRY_AUTH_TOKEN, SENTRY_ORG injectés depuis les secrets Gitea.
- Dockerfile.web : ARG correspondants + propagation au stage build.

Privacy / sécurité (cf. ADR-024) :
- captureException tags : ctx.route?.pattern (pas l'URL réelle) →
  les codes OAuth (?code=...) et tokens de check-in n'apparaissent
  jamais dans les tags Sentry indexés.
- Sentry user context = user.id UUID seulement, pas d'email/nom.
- Sourcemaps en prod : uploadées à Sentry, supprimées du bundle.
- 4xx filtrées en amont (beforeSend) ET en aval (handler.ts:report).
- DSN public (by-design) commit-able, AUTH_TOKEN secret CI uniquement.

Sample rates (free tier 5K events / 50 replays par mois) :
- traces : 10% prod, 100% dev
- profiles : 100% (sampled par traces)
- replay session : 0% (économie quota)
- replay sur erreur : 100% (debug post-mortem)

Pré-requis runtime à configurer hors-repo :
- Secret K3s rubis-app-secrets : SENTRY_DSN_API
- Secrets Gitea Actions : SENTRY_DSN_WEB, SENTRY_AUTH_TOKEN, SENTRY_ORG

ADR-024 logué dans docs/decisions.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 13:38:12 +02:00
ordinarthur
1acb273c1d docs: email infra rubis.pro (Resend sortant + OVH MX entrant)
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 24s
Documentation post-migration du setup email :

- /docs/tech/backend.md §12.5 : architecture des 2 flux
  (Resend pour le sortant transactionnel via send.rubis.pro,
  OVH MX Plan pour l'entrant humain via @ rubis.pro)
- /CLAUDE.md : tableau récap email infra + maj domaine principal
  rubis.pro / app.rubis.pro, suppression de la question ouverte
  "domaine définitif" (résolue) et "endpoint waitlist" (remplacé
  par CTA app)
- /.claude/deploy-memory.md : section migration rubis.pro marquée
   avec checklist décommissionnement legacy
- /landing/confidentialite.html : remplace privacy@rubis.pro
  par contact@rubis.pro (alignement avec les boîtes OVH créées)

Adresses opérationnelles :
- contact@rubis.pro (général + RGPD)
- dev@rubis.pro (notifs techniques)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 21:48:35 +02:00
ordinarthur
1c5a58e09a chore(domain): migrate rubis.arthurbarre.fr → rubis.pro
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 26s
Build & Deploy Landing / build-and-deploy (push) Successful in 27s
Build & Deploy API / build-and-deploy (push) Successful in 1m18s
Bascule du domaine principal vers rubis.pro / app.rubis.pro :

- K3s ConfigMaps (api.yml, web.yml) : APP_URL, WEB_URL,
  COOKIE_DOMAIN, OAUTH callbacks pointent vers app.rubis.pro
- Dockerfile.web : ARG VITE_API_URL et VITE_PUBLIC_LANDING_URL
- Workflows Gitea : commentaires + build args web → rubis.pro
- Code API (mail_dispatcher, send_test_email, config/mail) :
  defaults env LANDING_URL et MAIL_FROM_ADDRESS migrés
- Templates env (.env.example) idem
- Docs (architecture, backend, frontend, brand-identity) idem
- AGENTS.md / CLAUDE.md / deploy-memory : pointeurs domaine MAJ

Note : MAIL_FROM_ADDRESS dans le secret K3s reste sur
rubis@arthurbarre.fr tant que le domaine rubis.pro n'est pas
Verified dans Resend. À switcher manuellement après vérif Resend.

Compat : un 301 Traefik redirige rubis.arthurbarre.fr → rubis.pro
(et app.X aussi) — config Ansible dans le repo proxmox.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 21:32:31 +02:00
ordinarthur
ab75f1f979 fix(checkin): bump invoice.status pending → awaiting_user_confirmation
All checks were successful
Build & Deploy API / build-and-deploy (push) Successful in 1m6s
Bug V1 documenté dans flow.md mais jamais corrigé : le job send_checkin_job
envoyait l'email + marquait la CheckinTask `sent`, mais ne touchait pas le
statut de la facture. Conséquence : l'user reçoit le mail check-in dans sa
boîte mais la modale in-app au refresh ne l'affiche pas (la modale liste
uniquement les `awaiting_user_confirmation` côté DB).

Fix : après l'envoi mail OK et le mark CheckinTask=sent, on bump
`Invoice.status = 'awaiting_user_confirmation'` SI elle est encore
en `pending`. Pas de bump si entre temps :
  - mark-paid (status=paid)
  - litigation/cancelled (transitions manuelles)
  - in_relance (impossible mais safe)

Doc flow.md mise à jour pour refléter le nouveau comportement (effets
de la transition pending → awaiting + déprécation de la note "TODO V1.5").

Pour les factures existantes en prod qui ont déjà reçu le mail mais
restent en `pending` (cas pré-fix) : backfill manuel via SQL :

  UPDATE invoices SET status = 'awaiting_user_confirmation'
  WHERE status = 'pending'
    AND id IN (
      SELECT invoice_id FROM checkin_tasks WHERE status = 'sent'
    );

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 17:42:52 +02:00
ordinarthur
1952265217 feat(billing): plans Free/Pro/Business + Stripe Checkout & Customer Portal
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 1m0s
Build & Deploy Landing / build-and-deploy (push) Successful in 31s
Build & Deploy API / build-and-deploy (push) Successful in 1m52s
Pricing V1 :
  - Free  : 5 factures actives, 1 user, 3 mois de grâce illimité au signup
  - Pro   : 19 €/mois ou 190 €/an, factures illimitées, 1 user
  - Business : 49 €/mois ou 490 €/an, illimité + 5 sièges (V2 multi-users)
              + reply-from-user-email (V2)

Backend :
  - Migration : plan, grace_period_ends_at, stripe_customer_id,
    stripe_subscription_id, subscription_status, billing_cycle,
    current_period_end sur `organizations`. Backfill grace_period auto.
  - `app/services/billing.ts` : PLAN_CAPS, countActiveInvoices,
    canCreateInvoices (enforce post-grace), getOrgSubscriptionState.
  - `app/services/stripe.ts` : client lazy + lookup_keys stables.
  - `app/controllers/billing_controller.ts` :
      • GET  /billing/subscription      → state pour l'UI
      • POST /billing/checkout          → crée une Checkout Session
      • POST /billing/portal            → Customer Portal Session
      • POST /billing/webhook (public)  → handle 4 events Stripe
        (checkout.completed, subscription.updated/deleted, invoice.payment_failed)
  - `commands/stripe_setup.ts` : `node ace stripe:setup` crée Products +
    Prices (idempotent via lookup_key).
  - Enforcement 402 `plan_limit_reached` sur :
      • POST /invoices (saisie manuelle)
      • POST /invoices/import-batch/:id/drafts/:draftId/validate (OCR)

Frontend :
  - `lib/billing.ts` : useSubscription, useStartCheckout, useOpenPortal,
    useIsAtFreeLimit.
  - `routes/_app/parametres_.abonnement.tsx` : page comparaison plans
    avec toggle mensuel/annuel, current plan + portail Stripe, CTA upgrade
    qui redirige vers Checkout hostée.
  - `routes/_app/parametres.tsx` : nouvelle section "Abonnement" qui
    affiche le plan courant + lien vers la page abonnement.
  - `components/billing/PlanLimitBanner.tsx` : banner sur /factures qui
    s'adapte selon période (grâce / approche / atteinte).
  - Toast dédié 402 sur la validation OCR avec action "Passer Pro".

Doc :
  - flow.md : nouvelle section §11 "Pricing & enforcement" qui couvre
    plans, grâce, webhook flow, Customer Portal, env vars.

Setup dev :
  1. STRIPE_SECRET_KEY (sk_test_...) dans apps/api/.env
  2. `stripe listen --forward-to localhost:3333/api/v1/billing/webhook`
     → copier whsec_... → STRIPE_WEBHOOK_SECRET
  3. `node ace stripe:setup` une fois pour créer Products+Prices
  4. Tester via /parametres/abonnement → checkout en mode test Stripe

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:03:28 +02:00
ordinarthur
d410ae014e docs: flow.md — cycle de vie facture, statuts, surfaces UI, check-in deep dive
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 22s
Nouvelle doc orientée comportement produit : explique précisément ce que
fait Rubis du point de vue user-lambda. Pour les arch/tech → architecture.md.
Pour la spec features → produit.md.

Sections :
  1. Modèle mental
  2. Glossaire (rubis, plan, étape, confirmation, mise en demeure, DSO, LME)
  3. Cycle de vie d'une facture (6 statuts + diagramme transitions ASCII +
     détails par transition avec effets en cascade)
  4. Surfaces UI où l'user agit (modale check-in, email check-in, fiche
     facture, slide-over démo) — avec différences mobile/desktop
  5. Mécanique de confirmation deep-dive (le coeur du produit)
  6. Plans de relance (structure, plans pré-fournis, wizard custom, vars)
  7. Mode démo (flag, fork point unique, horloge virtuelle)
  8. KPIs & calculs (rubis, encaissé, DSO, pipeline)
  9. Edge cases & règles
  10. Métriques produit à instrumenter
  11. Ce que Rubis ne fait PAS

CLAUDE.md mis à jour pour pointer vers cette doc dans la liste des
documents associés.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:26:59 +02:00
ordinarthur
8742cabebf add marketting 2026-05-06 22:22:42 +02:00
ordinarthur
27cfa9ac13 docs(bruno): collection complète des routes API + environnement local
Collection Bruno (.bru text files, comme Postman mais file-based versionable) qui couvre l'API V1 actuelle. Open Collection → bruno/ → sélectionner l'environnement "local".

Domaines couverts (22 requêtes) :
- 00-Auth : Signup, Login, Logout
- 01-Account : Get/Update profile
- 02-Organizations : Get/Update my org
- 03-Clients : List, List+stats, Search, Create, Create duplicate (409), Create without email (422), Get detail, Update
- 04-Plans : List, Get by slug, Update (steps remplacés)
- 05-Invoices : List, List+filters, Counts, Create, Get detail, Mark paid

Environnement local (bruno/environments/local.bru) :
- baseUrl, email/password/fullName en dur
- token, userId, organizationId, clientId, invoiceId remplis automatiquement par les script:post-response

Chaque requête a :
- assertions Chai (statut, shape de la réponse)
- bloc docs avec sémantique métier + erreurs typiques
- inheritance auth Bearer via folder.bru pour ne pas répéter le header

Mise à jour de docs/tech/dev-setup.md pour pointer vers la collection.

Le parcours recommandé Signup → Update org → Create client → Create invoice → Mark paid couvre le happy path et permet de checker rubisCount qui s'incrémente.
2026-05-06 14:40:55 +02:00
ordinarthur
1d3b6a3f8f chore(api): UUID partout pour les PK et FK
Convention dure : tous les identifiants applicatifs sont des UUID v4 générés par PG (default gen_random_uuid()), aucun increments/serial même pour les tables techniques.

- CLAUDE.md → "Conventions techniques" : règle énoncée explicitement (anti-énumération, multi-tenant, génération côté client, dumps propres).
- docs/tech/backend.md §4.0 : exemple de migration + raisons.
- 4 migrations existantes réécrites en uuid (users, auth_access_tokens, organizations, alter users.organization_id). Les access tokens d'Adonis acceptent un tokenable_id uuid sans changement côté provider.
- Transformers nettoyés : plus de String(id), les UUID sont déjà des string.
- DB régénérée from scratch (migrations sont éditées avant tout déploiement, pas un cas où un autre dev a une DB en prod).
2026-05-06 13:58:11 +02:00
ordinarthur
a8c7ab539a chore(dev): swap mailhog → mailpit (multi-arch, maintenu)
mailhog n'est plus maintenu et ne ship qu'en amd64 — sur Apple Silicon ça déclenche un warning Rosetta. Mailpit est le successeur drop-in (mêmes ports SMTP 1025 / UI 8025), multi-arch, activement maintenu.
2026-05-06 13:02:36 +02:00
ordinarthur
4a6c778e7c chore(api): docker-compose dev (PG/Redis/MinIO/Mailhog) + bascule sur Postgres
- docker-compose.dev.yml à la racine : PG 16, Redis 7, MinIO + bucket auto, Mailhog. Ports décalés (5433, 6380, 9100…) pour éviter les collisions locales.
- apps/api/config/database.ts : Postgres en default, SQLite reste accessible via DB_CONNECTION=sqlite.
- start/env.ts : validation des nouvelles vars (PG, Redis, S3, Mail, OCR, refresh tokens).
- .env.example complété, scripts pnpm dev:up/down/logs/reset à la racine.
- docs/tech/dev-setup.md pour expliquer la stack locale.
2026-05-06 12:57:42 +02:00
ordinarthur
c52f46468f docs: guide d'implémentation backend (docs/tech/backend.md)
Miroir de docs/tech/frontend.md, ancré sur :
- Le scaffold Adonis 7 déjà en place (apps/api/)
- Le contrat exact que le SPA consomme (handlers MSW =
  source de vérité du shape attendu)
- Les types/schemas dans packages/shared
- Les ADR 014 (stack), 015 (monorepo), 016 (PG), 017 (auth),
  018 (storage)

20 sections : vue d'ensemble, stack interne, repo layout,
domain models (Lucid), routes API par domaine, conventions
de réponse, auth Bearer + refresh httpOnly custom, Tuyau,
validation Vine, storage MinIO, OCR pipeline, email outbound,
background jobs (BullMQ), tests Japa, migrations + seeders,
variables d'env, Dockerfile + K3s deployment, pointeurs
vers l'existant, ADRs encore à trancher (019 à 025),
évolutions V2+.

Règle d'or rappelée plusieurs fois : avant de coder un
endpoint, regarder le handler MSW correspondant — le SPA
est déjà branché à cette surface, c'est exactement ce que
l'API doit servir.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 12:37:41 +02:00
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
ordinarthur
0bda0d6d69 init landing 2026-05-05 15:36:21 +02:00
ordinarthur
89ca81d839 init doc rubis 2026-05-05 15:29:20 +02:00