# To think about

Open product / design questions we don't want to lose track of, to resolve when the corresponding feature gets built.

## Lead capture

- **Dedupe key for "same site tested"** — when enforcing "second audit on the same site → sign-in mandatory", on what do we dedupe?
  - Per IP + hostname?
  - Per cookie + hostname?
  - Per hostname alone, forever?
  - Hybrid (cookie + IP fallback)?

  Trade-offs to weigh: hostname-only is hostile (one curious team locks out their whole company). IP-only fails on NAT / corporate networks (whole office shares one IP). Cookie-only is trivially bypassable in incognito. Hybrid covers more cases but is more code.

## Infra / deployment

- **Unified Docker setup (Next.js + Postgres in one compose)** — currently the Next.js app runs from the host (`pnpm dev`) and Postgres is either local brew install (dev) or a separate Dokploy service (prod). Later, package everything into a single `docker-compose.yml` (or `Dockerfile` multi-service) so a clean `docker compose up` starts both the app and a Postgres with pg_cron+pg_net preloaded. Already have the building blocks: `postgres/Dockerfile` for the DB image with extensions, just need to add a service for the Next.js app + the right env wiring + a healthcheck dependency chain.

## Billing / Upgrade UX (à concevoir avant Phase 5 + Phase 6)

Quand on implémente `checkQuota` (Phase 5) et Stripe Checkout (Phase 6), il faut décider toute la séquence UX d'upgrade-on-quota-exhaustion. Sources : `docs/pricing/pricing-proposal.md` (limites par tier), `docs/superpowers/specs/2026-05-25-org-roles-stripe-billing-design.md` §5.5 (`PLAN_LIMITS`), §7.6 (UX past_due / cancel — mais **pas** la nudge proactive).

### Questions ouvertes

1. **Soft cap vs hard cap.** Quand un Free user lance son 16ᵉ test du mois :
   - **Hard cap** : refus net, modal "Free tier épuisé — [Upgrade Starter 19€]", aucun test ne part. Plus simple, plus prévisible côté facturation.
   - **Soft cap + overage** : on laisse passer 1-2 tests "de courtoisie", on flag, et on facture le surplus s'ils sont sur un plan payant. Ne s'applique pas au Free (Free = pas de carte enregistrée).
   - **Recommandation pré-réflexion** : hard cap sur Free, soft cap + bandeau warning sur tiers payants (rare en pratique).

2. **Nudge progressif avant la limite** (UI dashboard) :
   - À 70% du quota (10/15 tests sur Free) : barre de progression jaune + "10/15 tests this month".
   - À 90% (13/15) : toast non-intrusif "Heads-up: 2 tests left this month. [See plans]".
   - À 100% (15/15) : lock du bouton "Lancer un test" + modal blocking "Upgrade to keep going" → Stripe Checkout direct sur le tier supérieur, prérempli avec le bon Price.
   - Variante : skip 70% (bruyant), garder 90% + 100% uniquement.

3. **Per-feature exhaustion** — message UI différent selon la limite touchée :
   - `testsPerMonth` → "Free tier: 15 tests / month. Upgrade Starter (100 / mo)."
   - `sites` → "Free tier: 1 site. Upgrade Starter (3 sites)." (touché en essayant d'ajouter un site n°2)
   - `seats` → "Free tier: 1 user. Upgrade Pro (3 users)." (touché en essayant d'inviter un membre — déjà géré côté plan §7.6 mais le wording diffère).
   - `retentionDays` → expire silencieusement, pas un blocker. Bandeau dans `/dashboard/tests` : "Tests older than 7 days are hidden. Upgrade Starter (30d retention)."

4. **Mapping limite → tier suggéré** (helper pur côté `lib/billing/upgrade-suggest.ts` ?) :
   - Suggérer le **plus petit** tier qui satisfait toutes les contraintes actuelles + un buffer 50%. Ex : un Free user qui voudrait 4 sites → suggérer Starter (3 sites, juste en dessous → upgrade direct Pro 15 sites). Si Pro suffit, ne pas pousser Business.

5. **Quota reset cadence**.
   - `testsPerMonth` : reset le 1er du mois calendaire OU 30 jours glissants ?
     - Calendaire = aligné mental users + simple à expliquer. Mais effet "rush en fin de mois".
     - Rolling 30d = plus juste mais opaque ("pourquoi je peux lancer un test aujourd'hui mais pas hier ?").
   - **Recommandation pré-réflexion** : calendaire pour Free + Starter (psychologie). Stripe gère le cycle pour les tiers payants — utiliser `current_period_start` de la subscription comme anchor pour Pro/Business.

6. **API intégration externe** (`/api/integrations/audit/*`).
   - Audits anonymes : pas de quota (déjà rate-limité par IP via `lib/rate-limit.ts`).
   - Audits authentifiés via clé API (`ELEVASPEED_INTEGRATION_API_KEY`) : actuellement bypassent toute notion d'org/user. Si on factore plus tard un compte → quota, à décider : compte la clé contre l'org propriétaire de la clé.

7. **Email d'avertissement** (Resend déjà branché) :
   - 90% du quota → email "You're approaching your Free tier limit"
   - 100% → email "Quota reached. [Upgrade now]"
   - Trigger : cron job qui scan les orgs chaque jour, ou check synchrone à chaque action ?
   - **Recommandation** : check synchrone (pas de cron supplémentaire) + flag `last_quota_warning_sent_at` sur `es_org_subscription` pour ne pas spammer.

8. **Comportement quand le quota dépasse APRÈS un downgrade** (déjà mentionné §7.6 du spec mais à affiner) :
   - Free → Pro → Free : si l'org avait 5 sites en Pro, retour Free (limite 1) → on **ne supprime pas** les sites, on les marque "over quota". L'UI dashboard les affiche en grisé read-only, on ne peut pas lancer de tests dessus tant qu'on n'a pas ramené le compte sous quota (delete sites OU re-upgrade). Idem pour seats.

9. **Page "Compare plans"** vs **Modal contextuel** :
   - Modal contextuel = clic depuis la nudge → direct Checkout sur le tier suggéré, pas de page intermédiaire.
   - Page `/pricing` détaillée = quand l'user a le temps de comparer (depuis settings ou homepage).
   - **Recommandation** : les deux. Modal contextuel pour "I just want to keep working", page pricing pour "let me compare".

10. **Annual upsell** :
    - Quand un user clique "Upgrade Starter monthly (19€)", proposer en plus dans le modal : "or save 17% with yearly (190€)" avec toggle. Pas de page séparée.

### Action concrète pour Phase 5/6

Avant d'écrire le plan d'impl Phase 5 (quotas) ou Phase 6 (Stripe Checkout), revisiter cette liste avec le founder et trancher au moins :

- Soft vs hard cap (Q1)
- Quota reset cadence (Q5)
- Cron vs sync pour les emails (Q7)
- Page pricing ou modal-only (Q9)

Les autres questions peuvent attendre l'écriture de la première UI nudge.

## Settings → Billing / Plan management UI (Phase 4 et/ou 6)

Page `/settings/billing` (réservée Owner) doit permettre :

- **Voir le plan courant** (Free / Starter / Pro / Business / Enterprise) + intervalle (monthly/yearly) + prochain renouvellement + status (active/past_due/canceled/trialing).
- **Voir l'usage du mois en cours** (tests utilisés / limite, sites actifs / limite, seats utilisés / limite) — barres de progression.
- **Upgrader/downgrader en 1 clic** :
  - Card per tier avec features + prix + CTA "Upgrade" / "Downgrade" / "Current plan".
  - Toggle Monthly / Yearly (avec mention "-17% économisés en annuel").
  - Click "Upgrade" → Stripe Checkout (Phase 6) prérempli avec le bon Price ID.
- **Customer Portal** : bouton "Manage subscription" → redirige vers `billingPortal.sessions.create` de Stripe (cancel / update payment / invoices).
- **Cancel inline** : raccourci sans passer par le Portal (Server Action `cancelAtPeriodEnd()`) si on veut un cancel rapide.
- **Bandeaux de status** persistants en haut de page :
  - `past_due` → rouge, lien "Update payment method".
  - `cancel_at_period_end = true` → orange, "Your plan ends on {date}. [Reactivate]".

Dépend de :

- Phase 5 (table `es_org_subscription` + `checkQuota` pour les barres usage).
- Phase 6 (Stripe Checkout + Customer Portal pour les CTAs).
- Phase 4 (guard `requireOrgMember({ minRole: "owner" })` pour la page entière).

Composants UI à construire (déjà listés dans le spec §7.4 mais à concrétiser ici) :

- `<PlanCard>` — carte tier avec features + prix + CTA
- `<PlanGrid>` — grille 4 cartes ('use client' pour toggle monthly/yearly)
- `<CurrentPlanBanner>` — "You're on Pro Monthly — renews 2026-06-25"
- `<UpgradeCta>` — bouton → Server Action createCheckoutSession
- `<PortalButton>` — bouton → openCustomerPortal
- `<UsageMeter>` — bar "120 / 500 tests this month" (alimenté par checkQuota)
- `<PastDueBanner>` / `<CancelScheduledBanner>` — bandeaux globaux dashboard layout

## Page `/pricing` publique (Phase 7 ou avant)

En plus de `/settings/billing` (privée, Owner-only), il **faut** une page `/pricing` **publique** qui :

- Liste les 4 tiers (Free / Starter / Pro / Business) + Enterprise sur devis avec features comparées (tableau).
- Toggle Monthly / Yearly (-17% annuel).
- CTA contextuel selon l'état du visiteur :
  - **Non connecté** → "Sign up" (redirect /signup avec `?plan=starter` pré-sélectionné).
  - **Connecté Free** → "Upgrade to {tier}" → Stripe Checkout direct.
  - **Connecté payant** → "Current plan" disabled sur leur tier + "Switch to {tier}" sur les autres → Customer Portal.
- SEO-ready (statique, métadonnées open-graph, sitemap entry).
- Reprend les mêmes composants que `/settings/billing` (`<PlanCard>`, `<PlanGrid>`) — DRY.
- Bouton "See live demo" / "Try free" → onboarding flow.

Différenciation `/settings/billing` (private) vs `/pricing` (public) : la page publique sert l'acquisition + le SEO + l'upgrade depuis le marketing ; la page settings sert la gestion par les Owners déjà payants. Les deux pointent vers le même Checkout Stripe en Phase 6.

Phase d'arrivée : à concevoir en Phase 7 (juste après le admin/orgs panel) ou plus tôt si on veut accélérer l'acquisition avant le full RBAC.

## Marketing surfaces (Phase 10+) — conseils Gemini 2026-05-25

Consultation Gemini pour rendre les plans visibles. Reco appliquées Phase 9 (sidebar Upgrade + link login/signup + FAQ pricing). Reste à faire :

### Homepage marketing publique (`/`)

Actuellement `/` redirige direct vers `/dashboard` ou `/login` — pas de marketing surface. Construire une vraie homepage :

- Hero + value prop ("Le monitoring perf alternative FR à GTmetrix")
- Block features (3-4 cards : multi-région, Lighthouse, alertes, API)
- **Block Pricing inline** avant footer (Gemini : "le processus de décision B2B inclut le prix dès la 1re visite")
- CTA "Commencer gratuitement" + lien discret "Voir les plans payants"
- 3 logos clients (social proof) — quand on en aura
- Footer avec liens (Pricing, Docs, Status, Contact, Legal)

### Header public (à construire)

- Logo + nav inline : Fonctionnalités · Tarifs · Docs · Status · Connexion · "Démarrer gratuitement" (CTA primaire)
- Ordre "Tarifs entre Fonctionnalités et Connexion" = standard B2B

### Feature gating (upgrade touchpoints)

- Badges "Pro" / cadenas sur features avancées (multi-régions, export PDF, API, alertes Slack/Teams)
- Click sur feature gated → modal upgrade contextuelle
- Historique grisé > 7 jours pour Free avec CTA "Débloquez 90 jours d'historique"
- Cards "Real User Monitoring" / autres premium → empty state "Passez Pro pour voir vos vrais utilisateurs"

### Banner promo top-bar dismissible

- Une top-bar (très fine, dismissible avec cookie) : _"Profitez de -20% sur le plan Pro avec le code LAUNCH20"_
- Persiste sa dismissal via localStorage
- À activer ponctuellement pour campaigns

### Anchor pricing + comparison table on /pricing

- Badge "Plus Populaire" sur Pro (49€) — l'ancre vs Business (149€) — **fait en Phase 9**
- Comparison table détaillée sous la grid (sites monitorés, fréquence, locations, API calls, etc.) — **fait en Phase 9**
- Toggle Annual avec "-17%" — **déjà en place Phase 6**

### FAQ section on /pricing

- 4-5 questions : annulation 1-clic, changement de plan, TVA EU, facturation, refund policy — **fait en Phase 9**

### Quota meter visuel (sidebar)

- Barre progression "Tests : 4/15" (UsageMeter Phase 5 ✓ déjà fait)
- Au-dessus de 80% : barre devient orange + lien "Augmenter ma limite" → /settings/billing
- À 100% : QuotaExhaustedDialog (Phase 5 ✓ déjà fait)

### Action immédiate (Phase 9 — done)

1. ✅ Lien /pricing depuis /login et /signup
2. ✅ Bouton "Upgrade" sticky en bas de sidebar dashboard (Free users seulement)
3. ✅ FAQ + comparison table sur /pricing

### Action différée (Phase 10+)

1. Homepage marketing publique
2. Header public avec nav
3. Feature gating + badges Pro
4. Banner promo dismissible
5. Top-bar quota orange threshold (currently goes straight to red at 100%)
