Files
Momento/docs/4-1-gdpr-cookie-consent.md
Antigravity 96e7902f01
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m22s
CI / Deploy production (on server) (push) Has been skipped
feat: publication IA (magazine/brief/essay) + fixes critique
Publication IA:
- 4 templates (magazine, brief, essay, simple) avec CSS riche
- Rewrite IA (article/exercises/tutorial/reference/mixed)
- Modération avec timeout 12s + fallback safe
- Quotas publish_enhance par tier (basic=2, pro=15, business=100)
- Détection contenu stale (hash)
- Migration DB publishedContent/publishedTemplate/publishedSourceHash

Fixes:
- cheerio v1.2: Element -> AnyNode (domhandler), decodeEntities cast
- _isShared ajouté au type Note (champ virtuel serveur)
- callout colors PDF export: extraction fonction pure testable
- admin/published: guard note.userId null
- Cmd+S fonctionne en mode dialog (pas seulement fullPage)

i18n:
- 23 clés publish* traduites dans les 15 locales
- Extension Web Clipper: 13 locales mise à jour

Tests:
- callout-colors.test.ts (6 tests)
- note-visible-in-view.test.ts (5 tests)
- entitlements.test.ts + byok-entitlements.test.ts: mock usageLog + unstubAllEnvs
- 199/199 tests passent

Tracker: user-stories.md sync avec sprint-status.yaml
2026-06-28 07:32:57 +00:00

215 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Story 4.1: GDPR Cookie Consent Management
Status: ready-for-dev
<!-- Ultimate context engine analysis completed - comprehensive developer guide created -->
## Story
As a visitor,
I want to granularly accept or reject analytics and tracking cookies,
so that my ePrivacy rights are respected.
**Epic:** Epic 4 — Enterprise Compliance & Privacy (B2B Requirements)
**FR coverage:** NFR-GDPR1 (granular accept/reject of analytics and tracking cookies; strictly necessary cookies remain enforced)
**Out of scope for this story:** Story 4.4 (explicit AI processing consent modal), Story 4.2 (account deletion), PostHog/Umami full integration (gate only; no analytics vendor required to ship 4.1)
---
## Acceptance Criteria
1. [AC1] **First-visit banner (NFR-GDPR1):** On any route (including `/login`, `/register`, marketing/home), if no valid consent record exists, a **fixed bottom banner** appears. It cannot be dismissed without an explicit choice: **Accept essentials only**, **Reject non-essential**, or **Manage preferences**.
2. [AC2] **Granular toggles:** In “Manage preferences”, the user can independently toggle **Analytics** (and optionally **Marketing** if reserved for future ads pixels — default off, disabled or hidden until used). **Strictly necessary** cookies are listed as always on, not toggleable, with short explanations.
3. [AC3] **Strictly necessary enforced:** Auth/session cookies (NextAuth), security, and functional preference cookies (`user-language`, theme-related storage) continue to work **without** analytics consent. No analytics/tracking script or non-essential third-party beacon loads before consent.
4. [AC4] **Persistence:** Consent is stored client-side (`localStorage` + optional `memento-cookie-consent` cookie for SSR hints) with schema version, timestamp, and category flags. Re-opening preferences from Settings updates the same record.
5. [AC5] **Authenticated sync:** When a logged-in user accepts analytics, persist `UserAISettings.anonymousAnalytics = true` via existing `updateAISettings`. On reject, set `false`. Guests rely on local storage only.
6. [AC6] **Analytics gate:** Introduce a single choke-point `lib/analytics/track.ts` (or `lib/consent/analytics.ts`) where all future product analytics calls no-op unless `hasAnalyticsConsent()`. Do **not** add PostHog/Umami packages in this story; wire `ErrorReporter` and any future trackers through the gate policy documented below.
7. [AC7] **Settings re-entry:** **Settings → General** (or About) includes a “Cookie preferences” control that reopens the same manage UI without clearing prior choices.
8. [AC8] **i18n & design:** All user-visible strings via `memento-note/locales/*.json` (15 files; FR + EN as content reference). Banner uses existing design tokens (`--ink`, `--concrete`, `--border`, `--memento-paper`, uppercase micro-labels) — no new blue/legacy theme colors; matches `docs/ux-design-specification.md` §Emplacement Légal.
9. [AC9] **Regression:** Language cookie/localStorage flow in `general-settings-client.tsx`, NextAuth login, theme/direction initializers, sidebar, billing (3.6), BYOK (3.5), and AI features work unchanged when user chooses essentials-only.
10. [AC10] **No destructive DB:** No `prisma migrate reset`. If schema change is needed for consent audit log, prefer **no migration** in 4.1 (client + `anonymousAnalytics` only).
---
## Tasks / Subtasks
- [ ] Task 1: Consent model & utilities (AC: #2, #4, #5)
- [ ] Subtask 1.1: Create `lib/consent/cookie-consent.ts` — types `ConsentCategories`, `ConsentRecord`, `CONSENT_VERSION`, `getConsent()`, `setConsent()`, `hasAnalyticsConsent()`, `hasMarketingConsent()`
- [ ] Subtask 1.2: Mirror consent to `document.cookie` (`memento-cookie-consent=`, `SameSite=Lax`, 1y) for optional SSR read; primary source remains `localStorage`
- [ ] Subtask 1.3: `hooks/use-cookie-consent.ts` — subscribe to storage events for banner hide/show
- [ ] Task 2: UI components (AC: #1, #2, #8)
- [ ] Subtask 2.1: `components/legal/cookie-consent-banner.tsx` — bottom-fixed, z-index above content, below modals; actions: essentials / reject / manage
- [ ] Subtask 2.2: `components/legal/cookie-preferences-dialog.tsx` — toggles + save; list necessary cookies (session, language, theme)
- [ ] Subtask 2.3: Mount `<CookieConsentRoot />` in `app/layout.tsx` inside `SessionProviderWrapper` so visitors and authed users both see banner
- [ ] Task 3: Analytics gate & ErrorReporter policy (AC: #3, #6)
- [ ] Subtask 3.1: `lib/analytics/track.ts``trackClientEvent()` / `trackServerEvent()` no-op unless analytics consent (document for future PostHog per `saas-deployment-prep.md` §D)
- [ ] Subtask 3.2: **Decision (implement):** Treat `ErrorReporter``/api/debug/client-error` as **strictly necessary** (authenticated operational logging only; no third-party). Add code comment in `error-reporter.tsx`. Do not send marketing analytics there.
- [ ] Subtask 3.3: Grep for any `Script` third-party loads in `app/` — none today; add lint comment in `track.ts` that new scripts must call consent check
- [ ] Task 4: Settings & i18n (AC: #5, #7, #8)
- [ ] Subtask 4.1: Add “Cookie preferences” button to `general-settings-client.tsx` opening preferences dialog
- [ ] Subtask 4.2: On save with analytics on/off, call `updateAISettings({ anonymousAnalytics })` when session exists
- [ ] Subtask 4.3: Add keys under `consent.*` (and `settings.cookiePreferences` if needed) in **all 15** `memento-note/locales/*.json`
- [ ] Subtask 4.4: Optional: set footer `legal.link2Href` to `/settings/general#cookies` or anchor — do not build full legal CMS in 4.1
- [ ] Task 5: Manual verification (AC: all)
- [ ] Subtask 5.1: Clear site data → banner shows on `/` and `/login`
- [ ] Subtask 5.2: Essentials only → banner hidden; refresh persists; AI/login still works
- [ ] Subtask 5.3: Accept analytics → `anonymousAnalytics` true in DB for logged-in user
- [ ] Subtask 5.4: `npm run build` in `memento-note/`
---
## Dev Notes
### Epic context (Epic 4)
| Story | Scope | Dependency on 4.1 |
|-------|--------|-------------------|
| 4.1 | Cookie banner + category consent | — |
| 4.2 | Hard account deletion | Independent |
| 4.3 | Data export portability | Independent |
| 4.4 | **AI processing consent** (just-in-time modal) | **Separate UI** — do not merge into cookie banner |
| 4.5 | EU data residency | Independent |
| 4.6 | SSO/SAML + audit logs | Independent |
Epic goal: B2B legal blockers for EU buyers. Cookie consent is the **first** Epic 4 deliverable and unblocks marketing/analytics work later.
### Critical brownfield reality
**Already in codebase:**
- `UserAISettings.anonymousAnalytics` (`Boolean @default(false)`) — field exists, **no UI** exposes it today [`prisma/schema.prisma`].
- `updateAISettings` already allows `anonymousAnalytics` in allowlist [`app/actions/ai-settings.ts`].
- Functional cookie pattern for language: `localStorage['user-language']` + `user-language` cookie in `general-settings-client.tsx` and inline script in `app/layout.tsx`.
- Footer i18n placeholders for Privacy / Terms / Cookie Policy (`landing.footer.legal.link*`) — hrefs still `#`.
- `ErrorReporter` in root layout posts client errors to `/api/debug/client-error` (auth required) — **first-party**, not a tracking pixel.
**Not implemented (this story):**
- No `CookieConsent` / banner components.
- No `lib/consent/*` or analytics gate.
- No PostHog/Umami in `package.json` (planned in `memento-note/docs/saas-deployment-prep.md` only).
### Cookie classification (implement exactly)
| Category | Examples in Memento | Consent required |
|----------|---------------------|------------------|
| **Strictly necessary** | NextAuth session, CSRF (if any), `user-language` cookie, theme/direction localStorage, consent record itself | No — always on |
| **Analytics** | Future PostHog/Umami/Plausible events, product funnel, feature flags tied to identity | Yes — opt-in |
| **Marketing** | Future ad pixels, retargeting | Yes — opt-in (hide toggle until used) |
**Do not** block the app shell on analytics rejection — only block **non-essential** scripts/events.
### UX specification (must follow)
From `docs/ux-design-specification.md`:
- Banner: **persistent, thin, bottom-anchored** on main UI; actions **“Accept essentials”** and **“Manage”** (map “Reject non-essential” to essentials-only path).
- **No new top-level pages** for consent — overlay/banner + settings re-entry only (Platform Strategy).
- AI consent modal is **Story 4.4** — triggered on first AI action, not on login.
Visual: reuse architectural-grid tokens; avoid isolated admin-style topbars.
### Files to create
```
memento-note/
├── lib/consent/cookie-consent.ts # NEW — source of truth
├── lib/analytics/track.ts # NEW — gated no-op + future hook
├── hooks/use-cookie-consent.ts # NEW
├── components/legal/cookie-consent-banner.tsx # NEW
├── components/legal/cookie-preferences-dialog.tsx # NEW
├── components/legal/cookie-consent-root.tsx # NEW — client wrapper
```
### Files to update
| File | Change |
|------|--------|
| `app/layout.tsx` | Render `<CookieConsentRoot />` after providers |
| `app/(main)/settings/general/general-settings-client.tsx` | “Cookie preferences” button |
| `memento-note/locales/*.json` (×15) | `consent.banner.*`, `consent.preferences.*` |
| `components/error-reporter.tsx` | Comment: necessary cookie / operational, not analytics |
**Do not modify** unless required: `middleware.ts`, `auth.ts`, Stripe/billing routes, BYOK, entitlements.
### Suggested consent record shape
```typescript
type ConsentRecord = {
version: 1
necessary: true // always true
analytics: boolean
marketing: boolean
updatedAt: string // ISO
}
```
Storage keys: `localStorage['memento-consent-v1']`; cookie name `memento-cookie-consent` (JSON base64 or simple flags).
### Authenticated user sync
```typescript
// On accept analytics:
await updateAISettings({ anonymousAnalytics: true })
// On reject:
await updateAISettings({ anonymousAnalytics: false })
```
Load initial dialog state: if logged in, prefer DB `anonymousAnalytics` **only when** local consent missing (first merge); after user sets banner, local record wins until they change preferences again.
### Analytics future-proofing
`saas-deployment-prep.md` documents PostHog (§D) and Umami (docker). For 4.1:
- Implement `hasAnalyticsConsent()` check only.
- When Epic 3+/growth work adds PostHog, initialize in a client provider **inside** `if (hasAnalyticsConsent())` — EU host `https://eu.i.posthog.com` when configured.
### Previous epic intelligence (Epic 3)
| Story | Relevance |
|-------|-----------|
| 3.6 Stripe billing | Billing UI at `/settings/billing` — unaffected; no Stripe cookies in 4.1 |
| 3.5 BYOK | Unrelated to cookie categories |
| 3.13.4 Quotas | Unaffected |
No previous story in Epic 4 (`story_num === 1`).
### Git intelligence (recent)
Recent work focused on design system, brainstorm, billing meter, dead-code cleanup — **no consent code** in recent commits. Safe greenfield within `components/legal/` and `lib/consent/`.
### Project structure notes
- App root: `memento-note/`
- Settings layout: `app/(main)/settings/layout.tsx` + `SettingsNav.tsx`
- i18n: `lib/i18n/LanguageProvider.tsx`, 15 locales under `locales/`
- **Database safety:** No reset; no migration required for 4.1 if using existing `anonymousAnalytics` column only
### Testing
Per project policy: **no new automated tests** unless user explicitly requests. Use Task 5 manual checklist only.
### References
- [Source: docs/epics.md — Epic 4, Story 4.1]
- [Source: docs/ux-design-specification.md — Flux d'Onboarding Légal, Emplacement Légal]
- [Source: docs/epics.md — NFR-GDPR1]
- [Source: memento-note/docs/saas-deployment-prep.md — §D PostHog/GDPR]
- [Source: memento-note/app/layout.tsx — root scripts & ErrorReporter]
- [Source: memento-note/app/actions/ai-settings.ts — anonymousAnalytics]
- [Source: memento-note/prisma/schema.prisma — UserAISettings.anonymousAnalytics]
---
## Dev Agent Record
### Agent Model Used
{{agent_model_name_version}}
### Debug Log References
### Completion Notes List
### File List