Skip to content

MartinM-781/sidekick

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sidekick

Le copilote admin des ostéopathes libéraux. Dicte 60 secondes après la consultation, retrouve une note structurée (motif / anamnèse / examen / traitement / conseils) et archivée par patient. Conforme à l'obligation de conservation 20 ans.

CI

Stack Next.js 14 (App Router, TS strict) · Supabase (Auth + Postgres + Storage EU) · Mistral AI (Voxtral transcription + Mistral Large structuration, Paris/UE) · Stripe (billing) · PostHog EU (analytics) · @react-pdf/renderer (export) · IndexedDB + Service Worker (offline) · Tailwind + shadcn/ui · Vercel cdg1 · Vitest + Playwright + GitHub Actions.


Sommaire


Prérequis

Outil Version Comment vérifier
Node.js ≥ 20 (LTS, idéalement 22) node --version
npm (livré avec Node) ou pnpm npm ≥ 10, pnpm ≥ 9 npm --version ou pnpm --version
Docker Desktop dernière Lancé, icône baleine 🐳 verte
Clé Mistral AI avec accès Voxtral + Mistral Large https://console.mistral.ai/api-keys

Note Windows : si tu installes pnpm via npm install -g pnpm et que tu es encore en Node 20, utilise pnpm 9 explicitement (npm install -g pnpm@9) — pnpm 10+ exige Node 22.13+ et plantera avec ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite.

Si tu n'as pas envie d'installer pnpm, npm marche pareil : remplace pnpm par npm run dans toutes les commandes (npm run dev, npm run db:reset, etc.). Pas de fonctionnalité bloquée, juste un peu plus lent à l'install.

À quoi sert Docker ici ?

Supabase est un ensemble de services (Postgres, API REST, Auth, Storage, mailbox locale Inbucket, Studio). En dev, la CLI Supabase lance ~7 conteneurs Docker pour répliquer tout ça sur ta machine. En prod, plus aucun Docker — tu utilises le service hébergé supabase.com.

Tu peux complètement zapper Docker si tu préfères : utilise un projet Supabase free tier directement → voir Setup sans Docker plus bas.

Extensions VS Code recommandées

  • esbenp.prettier-vscode
  • dbaeumer.vscode-eslint
  • bradlc.vscode-tailwindcss
  • ms-playwright.playwright
  • supabase.supabase-vscode

Setup en 10 minutes (1re fois)

Toutes les commandes sont sous PowerShell (Windows) — adapte pour bash si tu es sur macOS/Linux. Substitue pnpm par npm run partout si tu n'utilises pas pnpm.

1. Cloner et installer les dépendances

cd C:\chemin\vers\Sidekick
npm install         # ou : pnpm install

Compte ~2-5 min selon ta connexion. Tu verras added 800+ packages à la fin.

2. Lancer Docker Desktop

Ouvre l'app Docker Desktop, attends que l'icône baleine soit verte (état "Engine running"). Sans Docker lancé, l'étape 3 plante.

3. Démarrer Supabase local

npm run db:start    # ou : pnpm db:start

La 1re fois ça télécharge les images Docker (~500 Mo, 2-3 min). Ensuite la sortie affiche les valeurs à copier — garde-la sous les yeux :

API URL: http://127.0.0.1:54321
DB URL:  postgresql://postgres:postgres@127.0.0.1:54322/postgres
Studio:  http://127.0.0.1:54323
Inbucket: http://127.0.0.1:54324
anon key:        eyJh...
service_role key: eyJh...

4. Configurer .env.local

Copy-Item .env.local.example .env.local
notepad .env.local       # ou ton éditeur préféré

Colle les valeurs affichées par db:start à la place des placeholders. Ajoute ta clé Mistral dans MISTRAL_API_KEY=... (obtenue sur https://console.mistral.ai/api-keys).

Les variables Stripe / PostHog / Sentry sont optionnelles — laisse les valeurs par défaut, l'app marche en mode no-op pour ces intégrations.

5. Appliquer les migrations

npm run db:reset    # ou : pnpm db:reset

Ça applique les ~12 fichiers SQL de supabase/migrations/ dans l'ordre. Tu verras Applying migration 20260515000000_init_schema.sql... etc. Si tu vois Database reset complete! à la fin, c'est OK.

6. (Optionnel) Générer les types TS depuis le schéma

npm run db:types

Ça écrit types/database.ts. Pas obligatoire pour faire tourner l'app.

7. Lancer Next.js

npm run dev         # ou : pnpm dev

→ Ouvre http://localhost:3000.

Pour te connecter

  1. Clique « Commencer » sur la landing → tape n'importe quel email (ex. osteo@test.fr).
  2. Va sur Inbucket : http://127.0.0.1:54324 — c'est la boîte mail locale Supabase, tous les emails dev arrivent là.
  3. Ouvre le dernier email, clique « Me connecter » → tu arrives sur /app.

Studio Supabase (inspecter la DB)

http://127.0.0.1:54323 — UI pour voir les tables, les rows, les policies RLS, lancer du SQL ad hoc.


Setup sans Docker (alternative)

Si Docker est compliqué chez toi (machine d'entreprise, pas envie d'installer), tu peux pointer Sidekick vers un projet Supabase cloud free tier (500 Mo de DB gratuit).

# 1. Crée un compte sur https://supabase.com
# 2. New project → région "Europe (Paris)" eu-west-3 → free tier
# 3. Project Settings → API → copie l'URL et les 2 clés (anon + service_role)
# 4. Édite .env.local avec ces valeurs cloud (au lieu de http://127.0.0.1:54321)

# 5. Lie ton repo local au projet cloud
npx supabase login
npx supabase link --project-ref <ton-ref>     # le ref est dans l'URL du dashboard

# 6. Push les migrations vers la DB cloud
npx supabase db push

# 7. Lance Next.js comme d'habitude
npm run dev

Différence avec le mode local : Inbucket n'existe pas en cloud → les magic links partent vers ta vraie boîte mail (configure le SMTP dans Supabase → Auth → SMTP Settings, ou laisse Supabase envoyer via leur SMTP par défaut limité à 3 emails/h). Le Studio est accessible depuis le dashboard web supabase.com, pas en localhost.

Trade-off : tu touches une vraie DB. Un db reset efface tes vraies données. Latence réseau légère. Plus de chance de garder ton state entre les sessions.


Problèmes courants

'pnpm' n'est pas reconnu comme commande

pnpm n'est pas installé. Soit installe-le (npm install -g pnpm@9 pour rester compatible Node 20), soit utilise npm run X au lieu de pnpm X.

ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite quand tu lances pnpm

Tu as installé pnpm v10+ qui exige Node ≥ 22.13. Soit upgrade Node vers 22 LTS, soit downgrade pnpm : npm install -g pnpm@9.

'next' / 'vitest' / 'tsc' / 'supabase' n'est pas reconnu

Tu as oublié de lancer npm install (ou pnpm install) dans le dossier du projet. Les binaires sont dans ./node_modules/.bin/, npm les trouve automatiquement quand tu lances npm run X depuis le dossier.

404 Not Found - GET https://registry.npmjs.org/... pendant l'install

Une dépendance demande une version qui n'existe pas sur npm. Note le nom du package et corrige la version demandée dans package.json. Tu peux aussi tenter npm install --legacy-peer-deps qui est plus tolérant.

Docker affiche un port déjà utilisé

Quelqu'un d'autre tourne sur 54321/54322/54323/54324. Soit tue le process (netstat -ano | findstr :54321), soit change les ports dans supabase/config.toml.

supabase db reset échoue avec "permission denied"

Docker Desktop n'est pas lancé OU le user Docker n'a pas les droits sur les volumes. Relance Docker en admin la première fois, ou docker volume prune puis re-essaie.

Magic link ne marche pas

Vérifie que tu lis Inbucket (http://127.0.0.1:54324) et pas ta vraie boîte mail — en local, les emails ne sortent jamais. Si Inbucket est vide, regarde supabase logs dans la console Supabase Studio.


Multi-praticien / cabinet partagé

Depuis la v0.6, Sidekick supporte le travail en équipe (cabinet de groupe, collaborations). Chaque ostéopathe a au minimum une team perso "Mon cabinet" créée automatiquement à son signup. Un praticien peut être membre de plusieurs teams. Rôles techniques (en base) : owner, coach, assistant — le rôle coach est conservé tel quel pour ne pas casser la migration de schéma, malgré le pivot ostéo (cf. NEXT.md).

Toutes les RLS basculent sur team_id in (select user_team_ids(auth.uid())). Le helper Postgres user_team_ids est en SECURITY DEFINER (pattern Supabase canonique pour casser la récursion sur team_members).

→ Procédure d'invitation + test manuel des RLS dans docs/TEAMS.md.

⚠️ Test manuel recommandé après pnpm db:reset : la séquence test 1-8 de TEAMS.md vérifie que l'isolation RLS marche. À exécuter avant tout push prod sur une fresh DB.


Scripts

Chaque commande marche en pnpm <X> ou en npm run <X> (substituable).

Commande Rôle
dev Next.js en dev (http://localhost:3000)
build Build prod
start Serveur prod
lint ESLint
lint:fix ESLint + autofix
format Prettier write
format:check Prettier check (utilisé en CI)
typecheck tsc --noEmit
test Vitest run
test:watch Vitest mode watch
test:coverage Vitest + couverture v8
test:e2e Playwright
test:e2e:ui Playwright UI mode
test:install Installe les navigateurs Playwright
db:start Démarre Supabase local (Docker)
db:stop Arrête Supabase local
db:reset Drop + remigre + seed
db:types Génère types/database.ts depuis le schéma local
stripe:listen Stripe CLI → forwarde le webhook vers localhost
cap:sync Sync la config Capacitor + plugins (iOS/Android)
cap:ios Ouvre le projet iOS dans Xcode
cap:android Ouvre le projet Android dans Android Studio

Arborescence

app/
  (auth)/login              magic link (form server-side)
  auth/callback             handler du code OTP
  (app)/                    routes protégées par middleware
    page.tsx                dashboard
    clients/                CRUD + historique + archivage
    notes/                  recorder, détail, retry, édition
    settings/               compte, plan, CGU/Privacy, RGPD
  legal/                    cgu, privacy (publiques)
  error.tsx, not-found.tsx, loading.tsx, robots.ts, sitemap.ts, manifest.ts, icon.svg

lib/
  supabase/                 server, client, middleware, admin (service_role)
  ai/                       transcribe + structure (+ retry backoff) + prompts
  actions/                  server actions : auth, clients, notes, account
  data/                     lectures DB (RSC-friendly)
  schemas/                  Zod : client, note, plan limits (+ tests)
  rate-limit.ts             sliding window in-memory (+ tests)
  logger.ts                 logger structuré JSON pour Vercel Logs
  observability.ts          wrapper Sentry-compatible (passthrough actuel)
  constants.ts              ROUTES, AUDIO, AI, RATE_LIMIT, APP

components/
  ui/                       shadcn (button, input, textarea, label, card, skeleton, toaster)
  MobileNav, UsageBadge, PublicFooter

supabase/
  config.toml               région, ports, magic link template
  migrations/               schema + RLS + storage
  seed.sql, templates/magic_link.html

test/setup.ts               vitest jsdom + env mock
e2e/landing.spec.ts         Playwright smoke
instrumentation.ts          hook Next.js boot
middleware.ts               refresh session + redirection auth
.github/workflows/ci.yml    lint + typecheck + tests + build + e2e

Pipeline IA

[Recorder.tsx]
  └─ MediaRecorder → Blob → FormData → useTransition
     └─ Server Action createNoteFromAudioAction
        ├─ rate_limit_check (12/h par user)            ──► sinon ?limit=...
        ├─ canCreateNote(plan, count)                  ──► sinon ?limit=...
        ├─ Zod parse FormData + MIME + size            ──► sinon ?error=...
        ├─ INSERT session + note(status=processing)
        ├─ Storage.upload('audio/{coach_id}/{note_id}.{ext}')
        ├─ runPipelineAndPersist():
        │   ├─ withRetry(voxtral)        max 2 retries, backoff 1.5/3s
        │   ├─ withRetry(mistral-large) + zod.parse → rejette les hallus
        │   ├─ UPDATE note(status='ready', motif, anamnese, examen, traitement, conseils, raw_transcript)
        │   └─ DELETE audio (RGPD + coût zéro)
        │   ↳ catch: UPDATE note(status='failed', error_message) — AUDIO CONSERVÉ pour retry
        └─ redirect /app/notes/{id}

Retry : si status='failed', la page note affiche un bouton "Réessayer la mise en forme". L'action retryNoteAction retélécharge l'audio depuis Storage et relance runPipelineAndPersist. Si l'audio n'existe plus (suppression, RGPD), on propose le réenregistrement.


Tests

# Tests unitaires (rapide, isolé)
pnpm test

# Watch mode pendant le dev
pnpm test:watch

# Couverture (seuils 70 % sur lib/ hors actions/supabase)
pnpm test:coverage

# E2E Playwright
pnpm test:install   # 1re fois seulement
pnpm test:e2e
pnpm test:e2e:ui    # mode interactif

Couverture cible : les fonctions pures (schemas Zod, plan limits, rate-limit, retry, utils) sont testées à fond. Les server actions sont volontairement non testées en unit — testées via Playwright avec un Supabase local. Pour aller plus loin → ajouter Vitest sur les actions en mockant Supabase.

Voir TESTING.md pour la procédure de test manuelle bout-en-bout.


Observabilité

  • Logs structurés (JSON sur stdout, parsé par Vercel Logs) via lib/logger.ts. Événements clés : note_pipeline_start, note_pipeline_ok, note_pipeline_failed, note_retry_start, magic_link_sent, rate_limit_hit, sub_upserted. Les emails et user IDs sont hashés (hashPii).
  • PostHog analytics : lib/analytics/ (server + client). Funnel d'activation signup_completed → first_client_added → first_note_started → first_note_ready. Region EU. No-op si la clé manque.
  • Sentry non branché par défaut. Pour activer : décommente dans instrumentation.ts et lib/observability.ts, ajoute SENTRY_DSN à l'env. Les captureException() sont déjà placés aux bons endroits.
  • Rate limiter in-memory → suffisant pour 1 instance Vercel. Pour multi-instance, swap vers Upstash Redis (cf. NEXT.md).

Sécurité & RGPD

  • Headers : HSTS, X-Frame-Options DENY, X-Content-Type-Options, Referrer-Policy, Permissions-Policy (microphone=(self)).
  • Noindex sur /app/* (header + robots.ts).
  • Cookies : uniquement strictement nécessaires (session auth).
  • PII dans les logs : emails et user IDs hashés (hashPii non crypto, juste anti-corrélation passive).
  • Audio : supprimé automatiquement après transcription réussie. Conservé seulement si échec, jusqu'à retry ou suppression manuelle.
  • Effacement total : page /app/settings → bouton "Effacer toutes mes données" → cascade DB + cleanup Storage + suppression auth.users.
  • CGU + Privacy : /legal/cgu et /legal/privacy.

Variables d'env

Nom Côté Rôle
NEXT_PUBLIC_SUPABASE_URL client + server Endpoint Supabase
NEXT_PUBLIC_SUPABASE_ANON_KEY client + server Clé anonyme (RLS-safe)
SUPABASE_SERVICE_ROLE_KEY server only Bypass RLS pour cleanup audio + suppression
MISTRAL_API_KEY server only Voxtral + Mistral Large
NEXT_PUBLIC_APP_URL client + server Base URL (emails, sitemap, redirects)
SENTRY_DSN (optionnel) server Active Sentry quand renseigné

.env.local n'est jamais commit (cf. .gitignore).


Déploiement

Supabase cloud (1re fois)

  1. Crée un projet dans la région eu-west-3 (Paris) via dashboard.
  2. supabase link --project-ref <ref>
  3. supabase db push (applique les 3 migrations)
  4. Dans Auth → URL Configuration : ajoute https://<ton-domaine>/auth/callback aux Redirect URLs.
  5. Dans Auth → Email Templates : copie/colle supabase/templates/magic_link.html dans le template "Magic Link".

Vercel

  1. Importe le repo, région cdg1 (Paris).
  2. Variables d'env : copie les 5 (Supabase URL + anon + service_role + Mistral + APP_URL prod).
  3. Configure le custom domain.
  4. Vérifie les Logs après le 1er déploiement → tu dois voir app_boot au démarrage.

Stripe (1re fois)

  1. Crée 2 prix dans Dashboard Stripe → Produits → "Sidekick Pro" : mensuel 29 € et annuel 290 €. Copie les price_xxx dans STRIPE_PRICE_MONTHLY et STRIPE_PRICE_ANNUAL.
  2. Active le Customer Portal : Dashboard → Settings → Billing → Customer portal → Activate.
  3. Configure le webhook prod : https://<domaine>/api/webhooks/stripe. Events nécessaires : checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, invoice.payment_failed. Copie le whsec_xxx dans STRIPE_WEBHOOK_SECRET.
  4. En dev, pnpm stripe:listen forwarde le webhook local. Le whsec_xxx affiché va dans .env.local.
  5. Test card : 4242 4242 4242 4242, date future, CVC libre.

PostHog (optionnel)

  1. Projet sur https://eu.posthog.com → API key → NEXT_PUBLIC_POSTHOG_KEY et POSTHOG_KEY.
  2. Crée un funnel signup_completed → first_client_added → first_note_started → first_note_ready.
  3. Si vide → les modules lib/analytics/ sont no-op, l'app marche sans.

Post-déploiement checklist

  • Magic link arrive (pas dans spam)
  • CGU et Privacy à jour (raison sociale, adresse postale)
  • Stripe webhook → test card → profile.plan='pro' (Studio)
  • PostHog reçoit signup_completed au 1er login
  • Service Worker enregistré (DevTools → Application → Service Workers)
  • Export PDF d'un client avec ≥ 1 note → fichier propre
  • Mode offline : Network → Offline → enregistre → badge "1 note en attente" → repasse online → upload auto
  • CI verte sur main

Trade-offs assumés

  • Service Worker pas de Background Sync API : iOS Safari ne le supporte pas. À la place : drain auto sur online + bouton manuel. Couvre 95 % des cas usage entre deux consultations.
  • Rate limiter in-memory : 1 instance Vercel = un store séparé. Swap Upstash Redis quand le trafic l'exige.
  • PDF export server-side (@react-pdf/renderer, Node, ~2 MB bundle) plutôt que client jsPDF : meilleure typo, code réutilisable pour futurs templates.
  • Stripe apiVersion: "2024-09-30.acacia" figée : un bump vers 2025-03-31.basil déplace Subscription.current_period_end (breaking silencieux). Commentaire de garde dans lib/stripe.ts.
  • RootLayout async : récupère le profil pour identifier PostHog en SSR. Coût : la landing perd la SSG statique. Trade-off OK pour un MVP analytique-first.

Conventions

  • TypeScript strict + noUncheckedIndexedAccess + pas de any.
  • Server Actions pour les mutations (sauf callbacks → Route Handlers).
  • Zod valide les entrées utilisateur ET les sorties LLM.
  • Mobile-first : bouton hero atteignable au pouce (96px Ø, position basse, safe-area iOS).
  • Vocabulaire UI : « Ta note s'écrit toute seule » — repris du field report Reddit, jamais « Génération IA en cours ».
  • Commits : feat:, fix:, refactor:, test:, docs:, chore: (Conventional Commits, pas de commitlint forcé).
  • PR : remplit le template .github/PULL_REQUEST_TEMPLATE.md.

Voir aussi


Licence

Code source publié à titre de démonstration (portfolio). © 2026 Martin Mialaret — tous droits réservés : pas de réutilisation commerciale sans accord écrit.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages