11 Commits

Author SHA1 Message Date
ordinarthur
3207f873e9 feat(banking): mode "Bientôt disponible" pendant la fenêtre KYC Powens
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 36s
Build & Deploy API / build-and-deploy (push) Successful in 1m29s
Ajoute un état intermédiaire entre "feature désactivée silencieusement"
et "feature pleinement active" : un teaser visible dans /parametres
pour les Pro/Business qui annonce que la connexion bancaire arrive,
avec une note rassurante sur la lecture seule. Permet d'annoncer la
feature aux users payants pendant le délai d'agrément AISP / KYC
Powens, sans risque de cliquer dans le vide.

- Nouveau flag d'env `BANKING_TEASER_ENABLED` (boolean, default false)
- `GET /banking/status` renvoie désormais `{ enabled, comingSoon }`
  où comingSoon = !enabled && BANKING_TEASER_ENABLED
- `BankingSection` : nouveau composant `ComingSoonCard` (halo glow,
  copy explicite sur l'agrément AISP en cours, rassurance lecture
  seule) affiché quand comingSoon=true et l'org est Pro/Business
- `parametres.tsx` : la section "Banque" apparaît si enabled OU
  comingSoon (au lieu de uniquement enabled)
- ConfigMap K3s : `BANKING_TEASER_ENABLED='true'` en prod pour
  annoncer la feature aux clients payants pendant le KYC

Trois états possibles désormais :
  enabled=true                       → feature active (post-KYC)
  enabled=false + comingSoon=true    → teaser "Bientôt disponible"
  enabled=false + comingSoon=false   → section invisible (kill switch dur)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:14:33 +02:00
ordinarthur
51217175ad feat(banking): intégration Powens AISP + auto-réconciliation factures
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 38s
Build & Deploy API / build-and-deploy (push) Successful in 1m36s
Module banking complet en lecture seule via Powens (ex-Budget Insight)
pour détecter automatiquement les paiements clients et arrêter les
relances dès qu'une facture est payée. Réservé plans Pro / Business,
kill switch global BANKING_ENABLED désactivé en prod tant que le KYC
Powens n'est pas validé (cf. .claude/deploy-memory.md).

Backend (apps/api)
- PowensClient bas niveau : init user, code temporaire 30s, build
  webview URL, list/get/delete connections, accounts, transactions,
  vérif HMAC SHA-256 timing-safe pour webhook.
- BankingService : ensurePowensUser (chiffrement token via Adonis
  encryption / APP_KEY), createWebviewUrl avec state HMAC anti-CSRF
  (TTL 10 min), handleCallback (upsert connection + accounts +
  fire-and-forget mail + sync 90j + reconcile), disconnect (DELETE
  Powens + soft-revoke en DB), setReconciliationMode.
- Réconciliation : match transactions ↔ factures sur montant exact
  + label normalisé (numero ou nom client, NFD strip + alphanum).
  Confiance HIGH (label matche) vs LOW (montant seul). Mode auto +
  HIGH → invoice.status=paid + bonus rubis + cancel relances +
  enqueuePaymentThanks (client) + sendInvoiceAutoPaidNotification
  (user). Mode manual ou LOW → match_status='suggested' (UI V2).
- Webhook /webhooks/powens : vérif HMAC, lookup org par
  powens_user_id, dispatch CONNECTION_SYNCED / NEW_TRANSACTIONS /
  USER_SYNC_ENDED → sync incrémental 7j + reconcile, CONNECTION_ERROR
  / SCA_REQUIRED → update state + last_error. Réponse 200 immédiate
  puis processing fire-and-forget pour ne pas timeout côté Powens.
- 4 migrations : bank_connections, bank_accounts, bank_transactions
  + colonnes powens_user_id (chiffré APP_KEY) et reconciliation_mode
  sur organizations.
- 2 templates React Email : BankConnectedEmail (post-connection,
  récap comptes + lien settings) et InvoiceAutoPaidNotificationEmail
  (notif user après match auto, lien direct facture + libellé
  bancaire détecté). Toujours en branding Rubis (notif Rubis → user,
  jamais marque blanche).
- 2 commandes ace : banking:reconcile (rejoue le reconcile sans
  reconnecter la banque) et banking:simulate-payment (injecte une
  bank_transaction synthétique qui matche une facture, pour test E2E
  sans devoir attendre un vrai virement sandbox).
- Kill switch isBankingEnabled() : flag BANKING_ENABLED + check des
  credentials Powens. Endpoint public GET /banking/status renvoie
  { enabled }, /banking/powens/init throw 503 banking_disabled si OFF.
- Fix handler exceptions : UNIQUE violation composite (org, X)
  rapporte désormais la vraie colonne en faute (numero/slug/…) avec
  message lisible « Le numéro de facture "F2026-0013" existe déjà »,
  au lieu d'un message ambigu sur organization_id.

Frontend (apps/web)
- /parametres : nouvelle SettingsSection "Banque" gated par kill
  switch + plan Pro/Business. Si Free → upsell card avec CTA vers
  /parametres/abonnement. Si Pro/Business sans banque → CTA "Connecter
  une banque". Si banque connectée → carte avec accounts (IBAN
  masqué FR76 **** **** **** 1234), solde, last sync, bouton
  Déconnecter. Toggle Manuel/Auto pour reconciliation_mode.
- /parametres/banque/success : nouvelle route dédiée post-callback
  avec badge ✓ animé + halo glow rubis, récap des comptes
  synchronisés, 2 CTAs ("Voir mes paramètres" / "Retour dashboard"),
  note sécurité "lecture seule, aucun déplacement de fonds".
- Hooks : useBankingStatus, useBankConnections (avec opt-out via
  { enabled }), useInitBanking, useDisconnectBank, useBankingSettings,
  useUpdateBankingSettings.

Infrastructure (k3s)
- ConfigMap rubis-api-config : BANKING_ENABLED='false' par défaut,
  BANKING_PROVIDER='powens', POWENS_DOMAIN='rubis',
  POWENS_API_BASE_URL='https://rubis.biapi.pro/2.0/',
  POWENS_REDIRECT_URI='https://app.rubis.pro/api/v1/banking/powens/callback'.
- Secret rubis-app-secrets : 3 nouvelles clés POWENS_CLIENT_ID,
  POWENS_CLIENT_SECRET, POWENS_WEBHOOK_SECRET (valeurs sandbox posées
  via kubectl patch, à remplacer post-KYC).

Sécurité
- Token Powens chiffré au repos via Adonis encryption (AES-256-GCM,
  clé APP_KEY).
- State HMAC SHA-256 signé sur APP_KEY pour le flow webview
  (anti-CSRF + porte l'org_id à travers le redirect).
- Webhook HMAC SHA-256 sur header BI-Signature avec
  POWENS_WEBHOOK_SECRET, comparaison timing-safe.
- IBAN masqué côté API (transformer).
- Scope par org sur tous les endpoints (anti-IDOR).
- Rate limiting via le middleware Adonis existant.
- Idempotence DB : UNIQUE (org, powens_connection_id), (connection,
  powens_account_id), (account, powens_id) → rejouer un event ou un
  callback ne pose pas de problème.

Documentation
- /docs/tech/banking-setup.md : procédure complète setup dev avec
  Cloudflare Quick Tunnel, compte sandbox Powens, whitelist URLs.
- /.claude/deploy-memory.md : section "Banking (Powens) — activation
  prod" avec procédure en 6 étapes (KYC → secrets → ConfigMap →
  flip flag → smoke test), snippet kubectl patch pour rotation
  ciblée de secrets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:03:32 +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
ff8fe64be2 feat(mail): templates HTML React Email + brand "Rubis sur l'ongle"
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 1m1s
Build & Deploy API / build-and-deploy (push) Successful in 2m3s
Templates HTML stylés DA Rubis pour les 2 emails sortants — fini le
plain text moche.

apps/api/app/mails/
  ├── _brand.ts          : tokens couleur + spacing partagés
  ├── _layout.tsx        : squelette commun (header rubis-deep + footer)
  ├── checkin_email.tsx  : email envoyé À L'USER avec 2 boutons CTA
  │                        Oui (rubis primary) / Non (outlined)
  └── relance_email.tsx  : email envoyé AU CLIENT, body texte du plan
                           + card récap (numéro, montant, échéance,
                           badge retard rubis-deep)

Stack :
  - @react-email/components + @react-email/render
  - Tous les styles inline (compatible Gmail / Outlook / Apple Mail)
  - HTML + plain text en fallback (anti-spam, accessibility)

mail_dispatcher.ts :
  - sendRelanceEmail : .html(rendered) + .text(body)
  - sendCheckinEmail : .html(rendered) + .text(body)
  - daysLate calculé via clock.now (démo-aware)

send_test_email :
  - Nouveau flag --template=checkin (default) | relance | plain pour
    tester chaque rendu via Mailpit sans créer de vraie facture.

Brand & landing :
  - "Rubis Sur l'Ongle" → "Rubis sur l'ongle" partout (config, mail,
    PDF, Stripe appInfo)
  - Nouvelle env var LANDING_URL (default https://rubis.arthurbarre.fr)
  - Footer email rend "Rubis sur l'ongle" comme <a> rubis cliquable
    vers la landing — l'user qui reçoit le mail connaît la marque
    derrière l'envoi
  - .env.example mis à jour avec LANDING_URL pour les autres devs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 18:10:27 +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
7521e1fff6 feat(auth): Microsoft 365 SSO + factorisation helper SSO partagé
Backend
- Custom Ally driver Microsoft (Oauth2Driver) — Microsoft n'est pas dans
  les providers built-in, mais le driver dérive de Oauth2Driver en quelques
  lignes. Endpoints v2.0 (Microsoft Identity Platform), Graph /me pour le
  profil, fallback userPrincipalName si mail null (comptes perso).
- Tenant configurable via MICROSOFT_TENANT (défaut 'common' — accepte
  work/school + perso ; 'organizations' pour M365 strict).
- Migration 1400 : ajout microsoft_id nullable unique sur users.
- AuthMicrosoftController : redirect + callback (même pattern que Google).
- Refacto : extraction d'un service sso_session.ts (findOrCreateUserFromSso,
  nextRouteAfterSso, emitSsoSessionAndRedirect) → AuthGoogle + AuthMicrosoft
  partagent la logique.
- Routes /api/v1/auth/microsoft/{redirect,callback}.

Frontend
- Composant SsoButton générique (provider="google"|"microsoft") avec logo
  officiel inline pour chaque. Remplace l'ancien GoogleButton.
- Login + signup : pile verticale "Continuer avec Google" + "Continuer
  avec Microsoft", puis séparateur "ou", puis form email/password.
- Route SPA renommée /auth/google/complete → /auth/sso/complete (partagée
  entre les deux providers, la callback API redirige toujours dessus).
- Erreurs SSO sur /login : ?google=... ET ?microsoft=... → toast contextuel.

K3s
- ConfigMap rubis-api-config : ajout MICROSOFT_TENANT + MICROSOFT_CALLBACK_URL.
- Secret rubis-app-secrets : ajout MICROSOFT_CLIENT_ID + MICROSOFT_CLIENT_SECRET.

Doc
- .claude/deploy-memory.md : procédure Azure / Entra ID app registration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 09:38:38 +02:00
ordinarthur
ea539cd1d4 feat(auth): Google SSO via @adonisjs/ally
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 55s
Build & Deploy API / build-and-deploy (push) Successful in 1m35s
Backend
- @adonisjs/ally installé + provider Google configuré (config/ally.ts)
  scopes: userinfo.email + userinfo.profile (non-sensibles, validation
  auto par Google)
- Migration : ajoute google_id (nullable unique) sur users + rend password
  nullable (un user créé via Google n'a pas de mdp en base, il pourra
  l'activer plus tard via "mot de passe oublié")
- AuthGoogleController.redirect : entrée OAuth (le bouton SPA pointe ici)
- AuthGoogleController.callback : matche par google_id puis email,
  crée org+plans+user si nouveau, pose le refresh cookie httpOnly,
  redirige le browser vers le SPA /auth/google/complete?next=...
  (next = / pour user complet, /onboarding/entreprise pour nouveau)
- Routes : GET /api/v1/auth/google/{redirect,callback}
- Env : GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL

Frontend
- Composant GoogleButton réutilisable (full-page redirect, pas fetch —
  OAuth nécessite navigation pour les cookies cross-origin Google)
- AuthDivider "ou" entre SSO et formulaire email/password
- Boutons ajoutés sur /login et /signup
- Route /auth/google/complete : appelle POST /api/v1/auth/refresh (le
  cookie posé par la callback est auto-envoyé), stocke access token +
  user dans authStore, navigue vers `next`. Échec → /login + toast.
- Toast d'erreur sur /login si on revient avec ?google=denied|error|...

K3s
- ConfigMap rubis-api-config : ajout GOOGLE_CALLBACK_URL prod
- Secret rubis-app-secrets : ajout GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET
  (posés via kubectl, pas dans le manifest)

Doc
- .claude/deploy-memory.md mis à jour avec la procédure Google Cloud
  Console (créer OAuth client, redirect URIs, écran de consentement)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 09:24:27 +02:00
ordinarthur
94263c6447 feat(api): check-in flow — email à l'user + endpoints publics paid/pending
Le check-in remplace l'intégration banking V1 (cf. CLAUDE.md → Glossaire) :
avant que la 1re relance ne parte, on demande à l'user "as-tu été payé ?"
via email, et il clique sur l'un des 2 liens publics.

Service checkin_token.ts : génération + hash SHA-256. 32 bytes random base64url, plain dans le mail, hash en DB (CheckinTask.token_hash unique).

Service checkin_scheduler.ts :
- scheduleCheckinForInvoice(invoice) : crée 1 CheckinTask à dueDate (now+1min si dueDate dans le passé). Idempotent par invoice — cancel les scheduled précédents avant.
- cancelCheckinForInvoice(invoiceId) : appelé par mark-paid pour stopper.

Job send_checkin_job.ts : worker queue 'checkins', skip si invoice paid/cancelled (no-op), construit l'URL avec le plain token (passé dans le payload du job, pas relu DB), appelle sendCheckinEmail.

mail_dispatcher.ts : sendCheckinEmail() — texte brut, destinataire = user (pas client !), 2 URLs (paid / pending), TTL 24h annoncé.

Controller CheckinController :
- GET /api/v1/checkin/:token/paid : status=answered + answer=paid + mark invoice paid (mêmes effets que POST /invoices/:id/mark-paid : rubis +1, ActivityEvent invoice_paid avec label "via check-in", cancelFutureRelances). Idempotent : 2e click → redirect "already_answered".
- GET /api/v1/checkin/:token/pending : status=answered + answer=still_pending. Les relances suivent leur cours.
- Validation : lookup hash, expiry (sentAt + 24h), redirects propres pour invalid / expired / already_answered.

Routes : nouveau group public `checkin` (PAS de middleware.auth) à côté du group auth, sous /api/v1.

Triggers branchés :
- InvoicesController.store et ImportBatchesController.validateDraft → scheduleCheckinForInvoice après création
- InvoicesController.markPaid → cancelCheckinForInvoice dans la tx

start/queue.ts : registerWorker('checkins', sendCheckinJob).

env : nouveau WEB_URL (URL du SPA pour redirects), default localhost:5173 en dev.

Bruno : nouveau dossier 08-Checkin avec doc complète du flow + 2 requêtes (paid / pending). var d'env `checkinToken` à remplir manuellement après avoir reçu l'email dans Mailpit.
2026-05-06 15:31:40 +02:00
ordinarthur
274f2a8270 feat(api): install + configure bouncer, mail, limiter, drive, bullmq
Stack backend complète selon docs/tech/backend.md §2 :

- @adonisjs/bouncer : configure standard, middleware initialize_bouncer simplifié (API JSON-only, pas d'Edge views).
- @adonisjs/limiter : store Redis par défaut, throttler global défini dans start/limiter.ts.
- @adonisjs/mail : transports SMTP (Mailpit en dev) + Resend (prod).
- @adonisjs/drive : services fs (fallback) + S3 (MinIO en dev, prod plus tard).
- bullmq + ioredis : config queue.ts définit la connection Redis et la liste des queues (ocr, relances, checkins, kpis). Worker à câbler dans le commit suivant.
- @aws-sdk/client-s3 + s3-request-presigner pour le driver flydrive S3.

Pas de @rlanz/bull-queue : peer Adonis 6.5, plus maintenu — on consomme BullMQ directement.
2026-05-06 13:25:00 +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
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