Skip to content

feat: cookie-consent as the first engine plugin (new 'plugin' canonical) - #9

Merged
odenirdev merged 2 commits into
mainfrom
feat/cookie-consent-plugin
Jul 4, 2026
Merged

feat: cookie-consent as the first engine plugin (new 'plugin' canonical)#9
odenirdev merged 2 commits into
mainfrom
feat/cookie-consent-plugin

Conversation

@odenirdev

Copy link
Copy Markdown
Contributor

Summary

Ships cookie consent (LGPD/GDPR) for press sites as the first member of the engine's plugin family, introducing the 'plugin' canonical entity.

The plugin family (press-web, major)

  • Entity gains 'plugin' (additive) and the new PressPlugin<Id> contract (extends Canonical<'plugin'>, synthetic urn:plugin:{id}, enabled flag)
  • No runtime registry: config component on Site Settings → pure mapper → ResolvedPressConfig.plugins.<key> → explicit mount in the host layout. A second plugin (consent-gated scripts) costs exactly what the first did
  • BREAKING: ResolvedPressConfig gains a REQUIRED plugins field — hand-constructed literals (fixtures/mocks) need plugins: { cookieConsent: ... }; runtime is additive via getSiteConfig

Cookie consent

  • Category-based banner (necessary locked, analytics/marketing toggles), decision persisted in a versioned first-party cookie (press_consent, SameSite=Lax, 180d)
  • Public consent API: hasConsent('analytics') (fail-closed), useConsent(), acceptAll/rejectAll/setConsent/resetConsent, parseConsentCookie (server-side seam for future script gating)
  • Pre-paint anti-flash: inline <head> script stamps <html data-press-consent="decided"> so a decided visitor never sees the banner; never reads cookies() in the RSC tree, so static/ISR stays intact
  • Fails open: CMS unreachable → banner still shows with engine default copy (a consent gate must not vanish on a CMS hiccup); hasConsent stays independently fail-closed
  • CMS side (minor): press.cookie-consent/press.cookie-category config components, Site Settings attribute + deep populate, run-once seed writing only the enabled booleans (default copy lives web-side; editor disable respected forever)

Design decisions in docs/superpowers/specs/2026-07-04-cookie-consent-design.md (cited as cookie-consent Spec §N); CLAUDE.md gained the living "Engine plugins + cookie consent" section.

Test plan

  • pnpm -r --if-present typecheck — all packages green
  • pnpm -r test — 294 tests green (cms 63, web 210 incl. 8 interactive jsdom banner tests, cli 21)
  • 3-agent review (simplicity / correctness / conventions): no bugs, no convention violations; 2 refinements applied
  • Manual QA on the playground: pnpm build && pnpm dev → check the Cookie Consent component in the admin, banner on first visit, persistence across reloads, pre-paint hide after deciding

Notes

  • Interactive tests use a minimal act()+createRoot jsdom harness instead of the approved @testing-library/react: under the Strapi-required node-linker=hoisted layout, the only materialized RTL copy is Strapi-admin's react-19 variant, incompatible with this package's react 18 (documented in CLAUDE.md)
  • Known follow-ups (out of scope by design): a persistent "cookie preferences" reopen affordance (resetConsent() is the seam) and the script plugin (GTM/GA) as plugin Spec 5 deploy guide #2

🤖 Generated with Claude Code

odenirdev and others added 2 commits July 4, 2026 12:37
…labels

- Added `info.icon` to every engine component JSON to improve the admin "Pick one component" dialog, replacing the generic grid fallback with specific icons for each block.
- Introduced a new admin bundle (`./strapi-admin`) to register human-readable category labels for the component picker, resolving titles through react-intl.
- Implemented placement-scoped adopter categories for custom blocks, allowing specific components to be admitted into designated Dynamic Zones.
- Seeded a "Privacy Policy" page template at bootstrap, ensuring it is created only once and respects existing pages on the same slug.
- Updated various component JSON files to include icons for better visual representation in the admin interface.
Introduces the engine plugin family and ships cookie consent as plugin #1.

press-web (major):
- Entity gains 'plugin' (additive); new PressPlugin<Id> contract with a
  synthetic urn:plugin:{id} identity, mirroring urn:site-setting:default
- ResolvedPressConfig gains the REQUIRED plugins named map
  (plugins.cookieConsent) — resolved TOTAL even when the CMS is unreachable
- mapCookieConsent fails OPEN (banner enabled + default copy on CMS outage);
  hasConsent stays independently fail-closed (no decision => false)
- CookieConsentBanner (first stateful client component), useConsent hook,
  consent store over a versioned first-party cookie (press_consent, 180d),
  and buildConsentBootstrapScript() for pre-paint anti-flash — the visitor's
  decision is never read via cookies() in the RSC tree (static/ISR intact)
- Closed consent categories (necessary | analytics | marketing): editors
  toggle and re-word, never rename keys

press-cms (minor):
- press.cookie-consent + press.cookie-category config components (injected,
  never DZ-admitted), cookieConsent attribute on Site Settings, deep populate
  (nested categories + privacy page slug)
- seedCookieConsent: run-once (cookieConsentSeeded flag), booleans only —
  default copy lives web-side; editor disable respected forever; the flag is
  not set when the record is missing so a broken bootstrap order self-heals

Tests: 8 interactive banner tests via a minimal act()+createRoot jsdom
harness — deliberately not @testing-library/react, whose only materialized
copy under the Strapi-required node-linker=hoisted layout is the react-19
variant and cannot render this package's react-18 elements.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@odenirdev
odenirdev merged commit 21c793b into main Jul 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant