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

12 KiB
Raw Blame History

Story 4.1: GDPR Cookie Consent Management

Status: ready-for-dev

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.tstrackClientEvent() / 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).
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.

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

// 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