Skip to content

Latest commit

 

History

History
1790 lines (1188 loc) · 140 KB

File metadata and controls

1790 lines (1188 loc) · 140 KB

Changelog

All notable changes to this project will be documented in this file. See commit-and-tag-version for commit guidelines.

6.1.0 (2026-08-06)

A security and correctness release. Two vulnerabilities in the published package are fixed, one prop that was documented but inert now works, and four more are marked deprecated. Nothing is removed and no signature loses a parameter, so upgrading is a version bump — but read Behavior Changes below if you pass showPreview.

Released alongside @tantainnovative/ndpr-recipes@0.4.0 and @tantainnovative/create-ndpr@0.5.1. The unscoped create-ndpr@1.0.0 alias is unchanged and still delegates to the latest scoped CLI.

Security

  • policy export: consumer-supplied customCSS could escape the <style> block in exportHTML. <style> is a raw-text element, so the parser ends it at the first </style followed by whitespace, /, or > — the previous filter matched only the literal </style>, letting </style >, </style/>, and </style\n> through as live markup. Deleting the token is also unsound because one pass can reassemble it from fragments (</st</styleyle). Every < is now escaped as the CSS escape \3C; > is untouched so child combinators still work.
  • policy templates: four polynomial-ReDoS sites in the {{var}} matchers (findUnfilledTokens, both generatePolicyText branches, and substituteVariables). A run of { was consumed in full before the closing }} failed, once per starting offset, so a malformed template could stall an export. The token class now excludes {, which fails at the first character instead.
  • cli: ndpr audit --init checked existsSync then wrote as a separate operation, so a file created in between was silently overwritten — defeating the "Refusing to overwrite existing" guard it exists to provide. The write now uses wx, making the refusal atomic.

Behavior Changes

  • policy: PolicyGenerator's showPreview prop is now respected. It was documented with @default true but never read, so showPreview={false} still showed the preview step. Passing false now generates and calls onGenerate directly, and the step indicator drops its third entry. Required-variable validation still runs first. If you currently pass showPreview={false} and depend on the preview appearing anyway, remove the prop.
  • breach: useBreach({ categories }) is now optional. It was required but never read, so every caller had to construct an array purely to have it ignored. Existing call sites that still pass it keep working.

Deprecations

None are removed in 6.1.0; each carries a @deprecated note explaining what to use instead, and all are slated for removal in the next major.

  • DSRTracker.buttonClassName — the component renders no buttons, only the timeframe <select>. Use classNames or unstyled.
  • PolicyPreview.sections and PolicyPreview.variables — the component renders from content and derives its table of contents from that markdown. Use findUnfilledTokens if you need to detect tokens that escaped substitution.
  • useBreach({ categories }) — pass category lists straight to whatever renders the picker.

Packages

  • recipes: the @tantainnovative/ndpr-toolkit peer moves from the exact 6.0.0 to >=6.1.0 <7.0.0. The exact pin forced a recipes republish for every toolkit patch; the range still records what it was verified against.
  • create-ndpr: generated scaffolds now pin @tantainnovative/ndpr-toolkit@6.1.0.

Bug Fixes

  • cli: close the TOCTOU race in ndpr audit --init (06d23ca)
  • deps: clear all 16 advisories and unblock the dependabot backlog (e479250), closes #100, references #94 #89 #92 #93 #94 #95 #96 #97 #98 #99 #100
  • deps: record the override specifier for postcss in the lockfile (be379f3)
  • examples: clear the four astro advisories in the SSR example (ac987ad)
  • policy: make showPreview work, and mark the other dead props deprecated (b440f64), references #130
  • security: resolve every open CodeQL alert outside dist (ee380d1)

Documentation

  • add 5.7 to 6.0 migration guide (ff4d315)

6.0.0 (2026-07-19)

Hardens production boundaries: compliance duties stay applicable until complete correlated evidence establishes otherwise, and backend tenant/actor authority moves from request input to server configuration and verified sessions. Nothing was renamed or removed, so most builds keep compiling — but output changes for unchanged input, so re-run audits. Full upgrade path in /docs/guides/migrating-5-7-to-6-0.

⚠ BREAKING CHANGES

  • breach: highRisk and notificationRequired are force-on-only options (true). A duty may resolve to false only from complete risk evidence correlated to the same breach; missing, malformed, mismatched, or impossible evidence now fails closed.
  • recipes: tenant, subject, actor, reporter, assessor, and role authority now comes from server configuration or verified request context. Existing copied integrations must set NDPR_TENANT_ID, connect resolveVerifiedNDPRActor, and preserve the tenant predicates and atomic audit writes.
  • recipes schema: consent, DSR, breach, ROPA, DPIA, lawful-basis, cross-border, and audit persistence is tenant-scoped. Populated @tantainnovative/ndpr-recipes@0.1.x or 0.2.0 databases must use the reviewed migrations/0.3.0 procedure; do not use a blind Prisma or Drizzle schema push.
  • generated APIs: consent responses expose canonical ConsentSettings rather than raw persistence rows, and evidence DELETE operations archive records instead of physically deleting them.

Features

  • create-ndpr: generate and strictly verify 15 complete Next.js App/Pages, root/src, Express, Prisma, Drizzle, and no-ORM combinations with fail-closed request-context seams.
  • recipes: add production request-context, operational-indicator, server-storage, atomic persistence, replay-safe consent, evidence archival, and complete migration artifacts.
  • packages: release @tantainnovative/create-ndpr@0.5.0 and @tantainnovative/ndpr-recipes@0.3.0; the unscoped create-ndpr@1.0.0 alias remains unchanged and delegates to the latest scoped CLI.

Bug Fixes

  • consent: preserve canonical safe-integer client timestamps across create, replay, replacement, persistence, and load paths; reject fractional timestamps before database access.
  • breach: enforce ordered incident/evidence chronology and prevent bare boolean false values or incomplete assessments from waiving notification duties.
  • car: reject noncanonical, inherited, accessor-backed, out-of-range, cross-year, or impossible deadline overrides and malformed ruleset provenance without invoking hostile getters.
  • recipes: reject malformed Express JSON bodies before database access and derive reporter/assessor/approval fields from verified server context.

Compliance Rule Changes

  • Bundle the reviewed 2026 CAR filing extension to 30 May with explicit ruleset provenance. Re-run CAR and aggregate audit output after upgrading and verify current NDPC guidance before filing.
  • Treat Commission and high-risk data-subject breach duties as applicable until complete correlated assessment evidence establishes otherwise. Re-run stored breach-readiness assessments after upgrading.

Security and Release Integrity

  • Build fresh toolkit declarations before recipe typechecking so stale dist output cannot hide package-boundary errors.
  • Verify the exact npm tarball later published, recheck its SHA-256 before upload, and exercise all 22 ESM, CJS, TypeScript, and stylesheet subpaths.
  • Preserve historical consent and evidence records through revocation/soft archival and keep business plus accountability writes in one transaction.

5.7.4 (2026-06-27)

Documentation

  • clarify NDPA Toolkit / NDPA 2023 positioning while preserving NDPR package compatibility
  • add the NDPR vs NDPA naming guide and link it from README and docs navigation
  • update site, docs, recipes, and component metadata to lead with NDPA Toolkit while retaining NDPR search/package context

5.7.3 (2026-06-26)

Features

  • site: add approved Used By section to the landing page

Bug Fixes

  • ci: add explicit serial, coverage, and local verification scripts

Documentation

  • refresh architecture and contributor guidance for v5 source roots

5.7.2 (2026-06-06)

5.7.1 (2026-06-05)

Features

  • site: complete sitemap, add missing canonicals, standardise locale to en_NG (7340b28)

Bug Fixes

  • correct NDPA citations and harden CLI/adapter edge cases (86f0698)

Code Refactoring

  • remove dead code and avoidable any casts (2cd0053)

5.7.0 (2026-06-02)

Features

  • consent: surface the cookie scanner in ConsentBanner (cdafcd8)

5.6.0 (2026-06-01)

Features

  • consent: add cookie scanner (declared vs present audit) (d74366f)
  • packages: refresh create-ndpr and ndpr-recipes for GAID 2025 (bf709cf)
  • site: make the /score audit a full-screen cinematic experience (52cc0e8)
  • site: turn the /score audit into a guided step-by-step wizard (18f26ad)

Bug Fixes

  • packages: use canonical bin path (no ./ prefix) in create-ndpr manifests (600d989)

Documentation

  • marketing: refresh the X thread + LinkedIn post for current state (58c6fca)
  • site: add Cookie Scanner guide page (5813686)

5.5.1 (2026-05-31)

Features

  • site: cross-link the ndpr audit CLI from the score and DPIA marketing pages (ca35869)
  • site: surface the GAID 2025 layer and ndpr audit CLI on the landing page (e84ea43)

Bug Fixes

  • dcpmi: only UHL and EHL file CAR; OHL renews registration annually (07a7bc6)

Documentation

  • blog: add four NDPC GAID 2025 articles (0b25725)
  • correct DCPMI registration fees and the OHL CAR rule (b1f8ed4)
  • link the audit CLI guide from the backend integration guide (4dfaab7)

5.5.0 (2026-05-31)

Features

  • cli: add ndpr audit CLI compliance gate (8a8bdc5)

5.4.0 (2026-05-30)

Features

  • breach: show live NDPC notification readiness in BreachReportForm (86e2394)

5.3.0 (2026-05-30)

Features

  • breach: add NDPA S.40 / GAID 2025 Article 33 notification completeness checker (2a6bdf2)

Documentation

  • document the breach notification completeness checker (c05ee84)

5.2.0 (2026-05-30)

Features

  • car: add NDPC GAID 2025 Compliance Audit Returns scheduler (30d399a)
  • dcpmi: add NDPC GAID 2025 DCPMI tier classifier (179a5c1)

Bug Fixes

  • address findings from the A–Z verification pass (4ede74f)

Documentation

  • document DCPMI classifier and Compliance Audit Returns (a7fefe1)

5.1.4 (2026-05-28)

Bug Fixes

  • deps: remove unused tailwind-merge peer dependency (5aec54b)

5.1.3 (2026-05-28)

Bug Fixes

  • deps: widen optional tailwind-merge peer to ^2.6.0 || ^3.0.0 (f7d6ed8)

5.1.2 (2026-05-28)

Features

  • site: add real NDPA privacy policy page; fix site-wide OG image (7c4938e)

Bug Fixes

  • align NDPA section citations with the Act's Arrangement of Sections (69ea70c)
  • correct dead npm URL, stale SEO version, and broken doc links (ceec6de)
  • correct wrong NDPA section labels in shipped components (bcf223c)
  • deps: patch dev/example advisories and make overrides work under pnpm 11 (d2e868f)
  • docs: correct component API-reference tables and samples to match v5.1.1 (6d1c0d2)
  • docs: correct NDPA 2023 section citations in compliance-score weights table (c132aa0)
  • docs: drop stale v3 framing and correct locale count (fd2d346)
  • docs: sweep renamed/removed APIs out of current guides and demos (04149e3)
  • examples: astro demo requires Node 22.12+ for astro 6 (b70baf1)
  • examples: bump SSR demos to patched astro 6 / vite 6 stack (32a06f3)
  • landing/docs: correct broken code samples and stale references (cc246a2)
  • landing: correct stale version branding and wrong stats (a7965bc)
  • standardize privacy-policy generator citation on NDPA Section 27 (cc92ec5)

5.1.1 (2026-05-28)

Completes the jspdf security fix from 5.1.0.

Changed

  • jspdf peer tightened: ^3.0.3 || ^4.2.1^4.2.1. 5.1.0 widened the range but left ^3.0.3 in it — and every jspdf 3.x is vulnerable (the advisory is <=4.2.0; there is no safe 3.x). Keeping 3.x bought zero compatibility while letting a consumer satisfy the peer with a vulnerable 3.0.4. Dropping it means the peer range now contains only patched versions, so a vulnerable jspdf can't satisfy it.

    This narrows an optional peer range — technically breaking per semver, shipped as a patch because the removed versions are 100% vulnerable with no safe alternative and the toolkit's jspdf API usage (new jsPDF(...), text/vector primitives) is identical across 3↔4. Consumers already on jspdf 4.2.1+ or not using PDF export are unaffected; consumers pinned to jspdf 3.x get a peer-range warning that correctly nudges them to the patched line.

Verification

  • pnpm jest --no-coverage, pnpm verify:tarball, npx tsc --noEmit -p tsconfig.json — all green
  • README + exportPDF JSDoc already specified jspdf ≥ 4.2.1 (5.1.0); no doc changes needed

5.1.0 (2026-05-28)

Security hygiene for the optional PDF-export peer. No change to the toolkit's own code — peer range + docs only.

Changed

  • jspdf peer range widened: ^3.0.3^3.0.3 || ^4.2.1. jspdf ≤ 4.2.0 carries three advisories — GHSA-67pg-wm7f-q7fj (High, CVSS 8.7: addImage GIF out-of-memory DoS), GHSA-cjw8-79x6-5cj4 (Medium: addJS shared-state cross-user data leakage in concurrent server-side use), and a Critical path-traversal/LFI item. jspdf 4.2.1 clears all of them (npm audit: 0 vulnerabilities). The old ^3.0.3 peer pinned consumers to vulnerable 3.x; the widened range lets them install the patched 4.2.1+.

    The toolkit's exportPDF only uses core jsPDF text/vector primitives — it never calls addImage, addJS, or .html() — so the toolkit's own PDF path was never a sink for these CVEs. The bump is for consumers who install jspdf and want a clean audit. jspdf stays an optional peer (dynamic import('jspdf')); consumers who don't export PDFs never install it.

Docs

  • README + exportPDF JSDoc now note that PDF export needs jspdf ≥ 4.2.1, and that installing it with --omit=optional (npm) / --no-optional (pnpm) drops jspdf's optional deps (canvg, core-js, dompurify, html2canvas) for a dependency-free PDF surface — the toolkit uses none of them.

Verification

  • npm audit against jspdf@4.2.1 — 0 vulnerabilities
  • pnpm jest --no-coverage, pnpm verify:tarball, npx tsc --noEmit -p tsconfig.json — all green

5.0.1 (2026-05-28)

Docs-only patch. No runtime code change.

  • README refresh for 5.0: structured-result validator examples (route-handler + client branching), new Internationalization section listing all seven shipped locales (en, yo, ig, ha, pcm, ar, fr) with RTL guidance, /server and /core import examples switched to validateConsentStructured, cookieAdapter default documented as 180 days with NDPA Section 26 rationale, and every raw.githubusercontent.com/.../vN.N.N/... image URL pinned to the v5.0.0 tag.

The npm page now matches the v5 surface. No code, types, or exports changed — ^5.0.0 consumers do not need to upgrade unless they want the new README on their npm page.

5.0.0 (2026-05-27)

Closes the deprecation window opened by 4.1.0. Every removal here was already deprecated and dev-warned in 4.1, so if your 4.1.x dev console was clean, your 5.0 upgrade is a one-line bump. Full migration table in /docs/guides/migrating-4-1-to-5-0.

Breaking changes

Uniform list-manager callbacks — legacy module-specific names removed

The three list-manager components and the useROPA hook now use onAdd / onUpdate / onArchive exclusively. The legacy module-specific aliases are gone.

Component / Hook Removed Use instead
LawfulBasisTracker onAddActivity, onUpdateActivity, onArchiveActivity onAdd, onUpdate, onArchive
CrossBorderTransferManager onAddTransfer, onUpdateTransfer, onRemoveTransfer onAdd, onUpdate, onArchive
ROPAManager onAddRecord, onUpdateRecord, onArchiveRecord onAdd, onUpdate, onArchive
useROPA (hook options) onRecordAdd, onRecordUpdate, onRecordArchive onAdd, onUpdate, onArchive
ROPAProvider (compound) onRecordAdd, onRecordUpdate, onRecordArchive onAdd, onUpdate, onArchive

The 4.1 dev-warn pointed at every renamed callsite. The new names are short, locale-independent, and match across all three modules.

NDPRDPIA.onComplete(answers) removed; use onResult(result)

The 4.x onComplete(answers) callback only delivered the raw answer map, discarding the computed risk assessment. 4.1 added onResult(result: DPIAResult) alongside it. 5.0 drops onComplete.

- <NDPRDPIA onComplete={(answers) => save(answers)} />
+ <NDPRDPIA onResult={(result) => save(result)} />

DPIAResult exposes answers, overallRiskLevel, canProceed, conclusion, and recommendations — branch on the outcome without re-running the assessment.

ROPAManagerLite.records removed; ropa is now required

ROPAManagerLite previously accepted a flat records: ProcessingRecord[]. The full ROPAManager always took ropa: RecordOfProcessingActivities. 5.0 unifies on the richer shape.

- <ROPAManagerLite records={records} />
+ <ROPAManagerLite ropa={{ records, lastUpdated: Date.now(), version: '1.0', /* ... */ }} />

Legacy string-returning validators removed

Four validators previously returned string[] or Record<string, string> of English error messages. 4.1 added structured-result siblings ({ field, code, message }[]). 5.0 removes the legacy shapes.

Removed Use instead
validateConsent validateConsentStructured
validateConsentOptions validateConsentOptionsStructured
validateDsrSubmission validateDsrSubmissionStructured
formatDSRRequest formatDSRRequestStructured

The structured shape is locale-independent — consumers can branch on stable code values (email_invalid_format, consent_stale, etc.) instead of regex-matching English strings. useConsent().validationErrors is now StructuredValidationError[] (was string[]); the same change applies to the Consent.Provider compound context.

Also removed: the DsrSubmissionValidationResult type (replaced by StructuredValidationResult<DsrSubmissionPayload>).

Internal

  • useConsent and Consent.Provider switched their internal validation call from validateConsent to validateConsentStructured. validationErrors now exposes structured { field, code, message } entries throughout.
  • useDSR.formatRequest and DSRDashboard switched to formatDSRRequestStructured internally; the consumer-facing formatRequest() return shape is unchanged.
  • scripts/verify-tarball.mjs PROBES updated to assert validateConsentStructured on the ././core/./server subpaths.

Verification

  • pnpm jest --no-coverage — 1249 pass / 90 suites / 0 fail (-28 from legacy-validator and onComplete test removals)
  • pnpm verify:tarball — all 22 subpaths resolve in ESM + CJS + TS
  • npx tsc --noEmit -p tsconfig.json — clean

4.1.0 (2026-05-27)

Additive minor. Closes the post-4.0 audit work and opens the deprecation window for 5.0. No breaks: every change here is opt-in or a new alias. If your 4.0 install is clean, drop in 4.1 with no code changes — then if you're using any of the legacy callback names below, your dev console will tell you what to rename ahead of 5.0.

Features

Two new shipped locales: Arabic + French

ar (Modern Standard Arabic, neutral register; "NDPA"/"NDPC" kept as identifiers; Western Arabic numerals for NDPA section references per Nigerian legal convention) and fr (Francophone West African register; uses GDPR-equivalent French terms — "personne concernée", "AIPD" — where they carry meaning).

import { ar, fr } from '@tantainnovative/ndpr-toolkit/locales';
<NDPRProvider locale={ar}>...</NDPRProvider>

Both export from the /locales subpath and roll up into NDPRLocale without type changes.

RTL-correct CSS via logical properties

Converted 13 physical directional declarations in styles/styles.css to logical equivalents (margin-leftmargin-inline-start, text-align: lefttext-align: start, left: 0; right: 0inset-inline: 0, etc.). Setting dir="rtl" on a parent now mirrors the layout correctly — Arabic users no longer see consent banner buttons hanging off the wrong edge.

Uniform onAdd / onUpdate / onArchive callbacks across list managers

LawfulBasisTracker, CrossBorderTransferManager, ROPAManager, and the useROPA hook each gain onAdd / onUpdate / onArchive callback names. The legacy module-specific names (onAddActivity, onAddTransfer, onAddRecord, onRecordAdd, etc.) still fire — both are called when set, so consumer code doesn't break — but they emit a one-shot dev-only deprecation warn.

Slated for 5.0 removal: onAddActivity, onUpdateActivity, onArchiveActivity, onAddTransfer, onUpdateTransfer, onRemoveTransfer, onAddRecord, onUpdateRecord, onArchiveRecord, and the matching useROPA options onRecordAdd, onRecordUpdate, onRecordArchive.

NDPRDPIA gains onResult(result: DPIAResult)

The preset previously fired onComplete(answers) with just the raw answer map, discarding the computed risk score. The new onResult callback receives the full DPIAResultoverallRiskLevel, canProceed, conclusion, recommendations, all of it — so consumers can branch on the outcome without re-running the assessment client-side. onComplete still fires for back-compat (and dev-warns ahead of 5.0).

ROPAManagerLite accepts the full ropa shape

ROPAManagerLite now accepts a ropa?: RecordOfProcessingActivities prop alongside the legacy records?: ProcessingRecord[]. When ropa is set, it wins; otherwise the component wraps the legacy records array in a stub. This matches the full ROPAManager API. Legacy records dev-warns for 5.0.

Structured validator family — validate*Structured()

New sibling functions return { valid; errors: Array<{ field; code; message }>; data? } instead of string[]:

  • validateConsentStructured (codes: consents_required, timestamp_required, timestamp_invalid, version_required, method_required, has_interacted_required, consent_stale)
  • validateConsentOptionsStructured (options_required, purpose_required)
  • validateDsrSubmissionStructured (payload_not_object, request_type_required, request_type_not_allowed, data_subject_required, full_name_required, email_required, email_invalid_format, phone_invalid_type, identifier_type_required, identifier_value_required, submitted_at_invalid, additional_info_invalid_type, payload_final_narrowing_failed)
  • formatDSRRequestStructured (request_id_required, request_type_required, request_status_required, created_at_required, subject_name_required, subject_email_required)

Consumers can now branch on stable code values instead of regex-matching English strings — and the new shape is locale-independent. Legacy string-returning versions still work and dev-warn once per module-load. Slated for 5.0 removal: legacy string-returning shape.

Repo hygiene

  • Deleted six dead inner config files from packages/ndpr-toolkit/ (tsup.config.ts, tsconfig.json, tsconfig.lib.json, eslint.config.mjs, jest.config.js, rollup.config.js). All build/test/lint config lives at the repo root; the inner copies haven't been read by any tool since 3.7. The inner package.json stays for now (still private: true) — its exports field is referenced by some IDE tooling for jump-to-definition.

Verification

  • pnpm jest --no-coverage — 1277 pass / 90 suites / 0 fail (+14 new structured-validator cases)
  • pnpm verify:tarball — all 22 subpaths resolve in ESM + CJS + TS
  • npx tsc --noEmit -p tsconfig.json — clean

4.0.0 (2026-05-27)

The consolidated breaking-change window. Every removal here was already deprecated and dev-warned in 3.13.x — if your 3.13.x dev console was clean, your 4.0 upgrade is a one-line bump. Full migration table in /docs/guides/migrating-3-13-to-4-0.

Breaking changes

React 17 dropped from peer range

peerDependencies.react is now ^18.0.0 || ^19.0.0 (was ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0). The toolkit uses React.useId (18+) in several components, so the previous claim was a lie — React 17 consumers installed cleanly then hit cryptic errors at runtime. This release just makes the peer range match reality.

Migration: if you're on React 17 or earlier, upgrade your app to React 18+ before bumping. React 18 / 19 consumers: no action.

formDescription removed; use description

BreachReportForm and NDPRBreachReport previously accepted both description and a legacy formDescription alias (with a dev warn). The alias is gone.

Migration: rename every formDescription to description. (3.13's dev warn told you where.)

initialActivities / initialTransfers removed; use initialData

NDPRLawfulBasis and NDPRCrossBorder presets now accept initialData only, matching NDPRROPA. The module-specific aliases are gone.

Migration:

- <NDPRLawfulBasis initialActivities={activities} />
+ <NDPRLawfulBasis initialData={activities} />

- <NDPRCrossBorder initialTransfers={transfers} />
+ <NDPRCrossBorder initialData={transfers} />

extraOptions removed from NDPRConsent

The one-off pattern that extended the toolkit's default options is gone. Pass the full options array via options instead.

Migration: copy the default option set (or build your own) and pass it via options. Example in the migration guide.

useDSR default storage flipped to in-memory

useDSR is the admin tracker hook — its state contains data subjects' PII. The previous default (useLocalStorage: true) persisted that PII to the admin's browser's localStorage, which is rarely appropriate. The default is now useLocalStorage: false.

Migration:

  • If you intentionally relied on localStorage persistence, pass useLocalStorage: true explicitly (and reconsider whether you actually want it).
  • If you want production persistence, pass adapter: apiAdapter('/api/dsr') (or any other StorageAdapter).
  • If you don't need persistence, no action — the tracker now starts empty on every reload, which is correct for most admin UI use cases.

examples/dsr-backend-prod renamed to examples/dsr-backend-reference

The "prod" name oversold what the example does. It demonstrates DSR receipt and confirmation — not the full fulfilment pipeline (identity verification, audit logging, status transitions, request-type-specific handling, deadline tracking, DPO routing). The renamed example ships a "What you still need to build" checklist in its README so consumers don't ship it as-is.

Migration: if you bookmarked or cloned examples/dsr-backend-prod/, update to examples/dsr-backend-reference/. The contents and contract are unchanged.

New

  • New migration guide page at /docs/guides/migrating-3-13-to-4-0 walks through each break with diffs.

Not in this 4.0

The plan originally also included:

  • list-manager callback rename (onArchiveActivity / onRemoveTransfer / onArchiveRecord → uniform onArchive)
  • NDPRDPIA.onComplete(result: DPIAResult) instead of onComplete(answers)
  • ROPAManagerLite accepts ropa: RecordOfProcessingActivities instead of records: ProcessingRecord[]
  • pure structured validator returns (legacy string returns removed)
  • discriminated-union onSubmitError
  • delete the inner packages/ndpr-toolkit/{tsup.config.ts, tsconfig.json, etc.} (already marked private: true in 3.10.5)

Each adds API churn for marginal gain. Deferred to 4.1 so 4.0 stays focused on the items already deprecated in the 3.13 window. The inner-package deletion can land any time; behaviour change otherwise.

Verification

  • Jest: 1258 / 1258 passing (no test count drift — deprecated-prop tests were updated to use the canonical names)
  • tsc --noEmit -p tsconfig.json clean
  • pnpm verify:tarball --skip-ts clean across all 22 subpaths

3.13.0 (2026-05-27)

Release 5 of 6 on the post-audit roadmap. The deprecation-window minor — strictly additive, but every prop alias and behavior change that 4.0 will commit to gets fired here first as a dev-mode warning. Existing 3.x consumers see no breakage; new code can adopt the canonical surface.

i18n is real now — useNDPRLocale wired into 17+ components

The big one. The toolkit shipped 5 locale files (en, yo, ig, ha, pcm) and exposed useNDPRLocale(), but only 3 components consumed the hook (post-3.10.4: ConsentBanner, DSRRequestForm, BreachReportForm). The remaining ~17 components ignored the locale prop, making <NDPRProvider locale={…}> mostly cosmetic. The audit's B14 report called this out as a top-tier finding.

Newly wired components (each follows the prop-override → locale → English default precedence):

  • DSR: DSRDashboard, DSRTracker
  • Breach: BreachRiskAssessment, BreachNotificationManager, RegulatoryReportGenerator
  • DPIA: DPIAQuestionnaire (next/prev/submit buttons), DPIAReport (report title)
  • Policy: PolicyGenerator, PolicyPreview, PolicyExporter, AdaptivePolicyWizard
  • Lawful basis: LawfulBasisTracker + LawfulBasisTrackerLite
  • Cross-border: CrossBorderTransferManager + CrossBorderTransferManagerLite
  • ROPA: ROPAManager + ROPAManagerLite
  • Consent extras: ConsentManager (title/desc/save/reset)
  • NDPRProvider error-boundary fallback now reads locale.common.error

The NDPRLocale type at src/types/locale.ts was expanded with new keys (consent.managerTitle, dsr.dashboardTitle, breach.riskAssessmentTitle, policy.generatorTitle, plus top-level lawfulBasis, crossBorder, ropa groups). All 5 shipped locale files got the matching translations.

21 new i18n regression tests (3 cases × 7 modules) confirm each component honors the locale prop. Pattern matches ConsentBanner-i18n.test.tsx from 3.10.4.

Preset API harmonization (sets up 4.0 deprecation window)

  • submitTo / submitOptions / onSubmitSuccess / onSubmitError lifted from NDPRSubjectRights to NDPRBreachReport and NDPRDPIA. The latter two now support the same typed POST-and-route pattern. NDPRDPIA posts on last-section completion; both preserve the legacy onSubmit / onComplete callbacks so existing consumers see no change.
  • copy? prop added to 6 presets (NDPRBreachReport, NDPRDPIA, NDPRLawfulBasis, NDPRCrossBorder, NDPRROPA, NDPRPrivacyPolicy). Each ships a typed NDPR<Name>Copy interface, exported from src/index.ts. NDPRConsent already had this pattern; the 6 presets now match.
  • description prop added alongside formDescription on BreachReportForm + NDPRBreachReport. When both set, description wins; when only formDescription is set, a one-shot dev warn says [ndpr-toolkit/breach] BreachReportFormProps.formDescription is deprecated; rename to 'description'. Will be removed in 4.0.
  • initialData prop added alongside initialActivities on NDPRLawfulBasis and initialTransfers on NDPRCrossBorder. Same alias precedence + dev warn for the legacy names.

Privacy guard on useDSR default storage

useDSR() is the admin tracker hook — its state contains OTHER PEOPLE'S PII (DSR requests submitted by data subjects). The current default writes to localStorage on the admin's browser, which is rarely the right place.

Added a fire-once dev-only console.warn when a consumer uses the default localStorage path without an explicit adapter:

[ndpr-toolkit/dsr] useDSR() is using the default localStorageAdapter, which stores DSR requests (including data subjects' PII) in the admin's browser. For production deployments, pass an explicit adapter… In 4.0, the default will switch to in-memory; localStorage will require explicit opt-in.

Other dev-mode deprecations (queued for removal in 4.0)

Every dev warn is guarded by process.env.NODE_ENV !== 'production' AND a useRef-based fire-once flag — production builds see no overhead, dev consoles see each warning once per component instance.

  • NDPRConsentProps.extraOptions — one-off pattern not used by other presets. Warn fires when passed.
  • formDescription on BreachReportForm / NDPRBreachReport — see above.
  • initialActivities on NDPRLawfulBasis, initialTransfers on NDPRCrossBorder — see above.

Deferred from the original 3.13.0 plan

  • useConsent generic over options array — non-trivial type-level refactor that needs the existing tests to be re-checked. Higher value if shipped together with the 4.0 type-cleanup window; deferring there.
  • useDSR.requestType generic — same shape, same reasoning.
  • ROPAManager.updateEditingField generic over keyof ProcessingRecord — internal refactor, defer to 4.0.
  • onSubmitError discriminated union — the current { error?, response? } shape works for everyone today. Discriminated-union variant is breaking-ish (TS narrows differently); defer to 4.0.
  • validate*Structured() returning { field, code, message }[] — additive, but the new shape needs its own docs page and migration story. Defer to 4.0 alongside the validator-message harmonization.
  • [ndpr-toolkit/<module>] prefix on every utility error — bulk-mechanical; defer (already happening organically — every dev warn we added in 3.13.0 carries the prefix).
  • Arabic locale + RTL CSS conversion — defer to a focused i18n release after 4.0. The shape is now in place (ar.ts can be one file); the logical-property CSS conversion is its own focused chore.
  • French locale (fr.ts) — same; one data file once the type/wiring is stable.

Verification

  • Jest: 1258 / 1258 passing (was 1237; +21 new i18n tests)
  • tsc --noEmit -p tsconfig.json clean
  • pnpm verify:tarball --skip-ts clean across all 22 subpaths

3.12.0 (2026-05-27)

Release 4 of 6 on the post-audit roadmap. Accessibility + performance + test coverage. No public API changes — everything is additive or behaviour-preserving internal work. Existing consumers see no breakage; users of assistive tech and consumers of large managers see real improvements.

Accessibility — 6 WCAG 2.2 fixes

  • BreachNotificationManager (components/breach/BreachNotificationManager.tsx) — the breach list row was a clickable <div> without role/tabIndex/keyboard handler. Now role="button", tabIndex={0}, aria-selected, aria-label, and Enter/Space activation (matching the existing DSRDashboard pattern). Keyboard users can finally drill into a breach row.
  • dpia/StepIndicator — the step wedge had onClick without button semantics. Now conditionally adds role="button" / tabIndex / keydown when interactive (clickable && onStepClick); when display-only, those attributes are undefined so it stays a non-interactive progress marker.
  • CrossBorderTransferManager — 8 form fields had <label> without htmlFor and the matching <input>/<select> had no id. Screen readers announced "edit text, blank" for all 8. Added stable cb-transfer-<field> id pairs: riskLevel, estimatedDataSubjects, recipientContactPhone, recipientContactAddress, frequency, status, ndpcApprovalReference, tiaReference.
  • BreachReportForm file upload<label> had no htmlFor, <input type="file"> had no id. Added htmlFor="breach-attachments" + matching id. Decorative paperclip SVG got aria-hidden="true" focusable="false".
  • Touch targets.ndpr-consent-banner__button and .ndpr-button were ~33px tall (padding: 0.5rem 1rem). Added min-height: 44px to both — matches iOS HIG and WCAG 2.2 enhanced target. Existing padding is unchanged so visual layout doesn't shift.
  • DPIAQuestionnaire — section title heading now has aria-live="polite" so screen readers announce the new section title when the user clicks Next.

Performance — 3 hot-path improvements

  • useROPA (hooks/useROPA.ts) — derivers getSummary / exportCSV / getComplianceGaps previously recomputed on every call. Hook now also returns summary / csv / complianceGaps as useMemo-cached values keyed on ropa. The existing callable functions still work (marked @deprecated in JSDoc, points at the cached fields). Pure additive — no break.
  • CrossBorderTransferManagersummaryData collapsed from 4× transfers.filter(...) calls (O(4n)) to a single for loop (O(n)) that accumulates totalActiveTransfers, pendingApproval, highRiskTransfers, missingTIA in one pass. Same output shape; meaningfully faster with many transfers.
  • Hoisted lookup tablesSTATUS_BADGE_CLASSES / STATUS_BADGE_LABELS / BASIS_BADGE_CLASSES / BASIS_BADGE_LABELS (LawfulBasisTracker), RISK_BADGE_CLASSES / CB_STATUS_BADGE_* (CrossBorderTransferManager), ROPA_STATUS_BADGE_* / ROPA_BASIS_BADGE_LABELS (ROPAManager) moved from inside useCallback bodies to module scope. Eliminates per-row object allocation on every render.

Two perf items from the audit were inspected and intentionally skipped with documented reasons:

  • useDefaultPrivacyPolicy was already cached via a useRef-gated init (not a typical useMemo shape, but functionally equivalent — the comment at line 167 calls out the intentional semantics).
  • ROPAManager inline onChange closures were already paired with useCallback-stable updateEditingField / updateControllerField refs, so the existing setup doesn't pay the per-keystroke closure-allocation cost we'd hoped to remove. Extracting a <FormField> component would touch >400 lines and risk subtle behavioural change for marginal gain.

Test coverage — +25 cases

Suite jumped from 1212 to 1237 / 1237 passing. New cases target gaps the audit's B4 report flagged as critical:

  • useFocusTrap (7 cases, new file) — restore-focus-on-deactivation, Tab cycling forward, Shift+Tab cycling backward, onEscape, listener cleanup, autoFocus: false. Previously zero dedicated tests for 128 LOC of WCAG-critical focus-trap logic.
  • useComplianceScore (2 cases, new file) — memoisation by deep equality, recompute on inner-field change.
  • NDPRDashboard preset (2 cases, new file) — ComplianceInput → score ring; degraded input lowers score.
  • NDPRThemeProvider (2 added cases) — mode: 'light' sets data-theme="light"; empty theme={{}} produces no style attribute.
  • assessDPIARisk (2 added cases) — empty-risks array → low-level; all-mitigated high-risk → canProceed: true.
  • NDPRProvider (3 cases, new file) — useNDPRConfig returns provided config; useNDPRLocale merges partial overrides with English defaults; nested-provider innermost-wins.
  • preset-callbacks (7 cases, new file) — submit / save / complete callbacks across NDPRConsent, NDPRBreachReport (with adapter), NDPRDPIA, NDPRLawfulBasis, NDPRCrossBorder, NDPRROPA. (One preset deferred: NDPRPrivacyPolicy.onComplete only fires after a multi-step wizard flow with valid data — would duplicate useAdaptivePolicyWizard's own tests.)

Deferred from the original 3.12.0 plan

  • /server split into /server/utils, /server/policy, /server/locales. Inspected: the 261-line /server re-exports translate to ~40 chunks pulled by tsup, but consumers already have narrower subpaths (/dsr, /policy, etc.) for the same use cases. The split adds 3 entry points + 3 PROBES + 3 docs entries for marginal benefit. Slated for re-evaluation in the 4.x roadmap discussion alongside the B20 strategic items.

Verification

  • Jest: 1237 / 1237 passing (was 1212; +25 new tests)
  • tsc --noEmit -p tsconfig.json clean
  • pnpm verify:tarball --skip-ts clean across all 22 subpaths

3.11.0 (2026-05-27)

Release 3 of 6 on the post-audit roadmap. Strictly additive — every change here adds to the public surface or fixes a docs lie. Existing consumers keep working without changes.

Type-export baseline — *Props interfaces, adapter ecosystem types, hook return types now reachable

Every component's Props interface is now re-exported from src/index.ts (the default entry):

ConsentBannerProps, ConsentManagerProps, ConsentStorageProps, DSRRequestFormProps, DSRDashboardProps, DSRTrackerProps, DPIAQuestionnaireProps, DPIAReportProps, StepIndicatorProps, BreachReportFormProps, BreachRiskAssessmentProps, BreachNotificationManagerProps, RegulatoryReportGeneratorProps, PolicyGeneratorProps, PolicyPreviewProps, PolicyExporterProps, LawfulBasisTrackerProps, CrossBorderTransferManagerProps, ROPAManagerProps.

Consumers can now type-safely wrap the toolkit's components — e.g.

import type { ConsentBannerProps, NDPRThemeProvider } from '@tantainnovative/ndpr-toolkit';

function MyConsentBanner(props: ConsentBannerProps & { brand?: string }) {  }

Adapter ecosystem types reachable too:

  • ApiAdapterErrorContext<T>, ApiAdapterSuccessContext<T>, ApiAdapterRetryConfig, ApiAdapterMethod from /adapters and root
  • CookieAdapterOptions, StorageAdapter<T> from /adapters and root

DSR validator types reachable:

  • DsrSubmissionPayload, DsrSubmissionValidationResult, ValidateDsrSubmissionOptions from /server (canonical) and root

Hook option/return interfaces promoted to export interface and re-exported through hooks-entry.ts (auto-flows to /headless):

  • UseConsentOptions/Return, UseDSROptions, UseBreachOptions, UseDPIAOptions, UseLawfulBasisOptions, UseCrossBorderTransferOptions, UseComplianceScoreOptions, UsePrivacyPolicyOptions.

JSDoc @example blocks on every public hook + adapter

Added @example blocks to all 10 public hooks (useConsent, useDSR, useBreach, useDPIA, useLawfulBasis, useCrossBorderTransfer, usePrivacyPolicy, useROPA, useAdaptivePolicyWizard, useComplianceScore) and 5 adapters (cookieAdapter, localStorageAdapter, sessionStorageAdapter, memoryAdapter, composeAdapters). useComplianceScore previously had zero JSDoc; full block added (description + @param + @returns + @example).

Internal: validateDsrSubmission type-guard refactor

Replaced the as string cast chain in utils/dsr.ts with a isDsrPayloadRaw(payload): payload is DsrPayloadRaw type guard. External signature unchanged. Safer narrowing; no consumer-visible difference.

Docs

9 new component pages under src/app/docs/components/, all wired into the sidebar nav:

  • ndpr-theme-provider, ndpr-provider, ndpr-dashboard, adaptive-policy-wizard, policy-page, legal-notice (previously had no dedicated pages)
  • lawful-basis-tracker-lite, cross-border-transfer-manager-lite, ropa-manager-lite (Lite variants previously only mentioned inline)

Fixed broken docs that taught fictitious symbol names:

  • 5 useDSR() calls without the required requestTypes arg, in components/data-subject-rights/page.tsx, components/hooks/page.tsx, and guides/data-subject-requests/page.tsx. All now show useDSR({ requestTypes }) with the correct API.
  • getRequestByIdgetRequest, filterRequestsByStatusgetRequestsByStatus, filterRequestsByTypegetRequestsByType (the docs were inventing names that don't exist). deleteRequest removed — no such function ships.
  • onSubmit prop on the DSR component page now typed DSRFormSubmission, not the fictitious DSRFormData.

README compact pass

  • Bumped version refs v3.10.3v3.11.0 (6 occurrences: header release link + 5 screenshot URL tags).
  • Replaced the 3-File Quickstart's throwaway let store: unknown = null API route with a clean 2-file quickstart that uses localStorageAdapter by default and shows cookieAdapter / apiAdapter / composeAdapters as opt-ins. The throwaway demo always looked unprofessional next to claims of production-readiness.
  • New "When NOT to use this toolkit" section between Adapters and Pluggable Storage. Honest framing: non-React stacks, banner-only use cases, GDPR-primary regimes, enterprise CMP shoppers should pick something else. Builds trust with the people who are the right fit.
  • "What's new" notice rewritten for 3.11.0 (less narrative, more skimmable).

CONTRIBUTING.md rewritten

The previous file was generic boilerplate. The new one covers the practical things contributors actually need: pnpm 10 / Node ≥20 setup, repo layout (with a callout that the root package.json is the publish surface, not the inner one), how to run a single test, the verify:tarball gate, the release flow, branch conventions, patch/minor/major decision table, i18n contribution guide.

Verification

  • Jest: 1212 / 1212 passing (no behaviour changes)
  • tsc --noEmit -p tsconfig.json — clean
  • pnpm verify:tarball --skip-ts — clean across all 22 subpaths

3.10.6 (2026-05-27)

Release 2 of 6 on the post-audit roadmap. CI / repo plumbing only — zero dist/ changes, zero behaviour changes for consumers.

CI workflows hardened

  • ci.yml — synced the Verify entry points loop with publish.yml (17 → 22 entries). Pre-3.10.6 a PR could pass CI while failing the publish workflow at release time. Both now check the same 22 entries plus dist/styles.css. Also added the verify:tarball step: the same ESM + CJS + TS resolution gate that runs in publish.yml now runs on every PR, so the 3.8.0–3.10.2 missing-exports class of bug can never reach a tag again.
  • concurrency: groups + timeout-minutes: added to all three workflows.
  • publish.ymlnpm install -g npm@11 (was npm@latest). Pin deliberately so a future npm major can't break release day.
  • nextjs.yml — moved id-token: write from workflow-level to the deploy job only (least privilege). Added a docs-site typecheck step on PR builds, closing the "docs site never typechecks" gap. PRs build without deploying.

New workflows

  • .github/workflows/codeql.yml — CodeQL SAST on push + PR + weekly cron.
  • .github/dependabot.yml — weekly automated PRs for github-actions + npm, grouped.

Governance docs

  • SECURITY.md, CODE_OF_CONDUCT.md, .github/FUNDING.yml, CODEOWNERS, .github/PULL_REQUEST_TEMPLATE.md, .github/ISSUE_TEMPLATE/{bug_report,feature_request,config}.{md,yml}.

Example apps hygiene

  • engines.node: >=20.0.0 added to all 14 example package.json files (was 2 of 14).
  • .gitignore added to examples/ssr/{remix,astro}. examples/ssr/remix/public/.gitkeep so the conventional dir exists.
  • examples/dsr-backend-prod/README.md — added a "Switching to PostgreSQL for production" subsection with the schema diff.

README

  • Tests badge swapped from a static tests-1192 passing shield to a live CI badge driven by the actual workflow status.

Known follow-up

  • The plan called for switching all three workflows to --frozen-lockfile. Defer: significant pre-existing lockfile drift (next 16.2.2 → 16.2.6, the docs-site self-dep, @phosphor-icons/react addition all pre-date proper lockfile maintenance). Tightening alongside the lockfile regeneration warrants its own dedicated release rather than risking unrelated breakage here.

Verification

  • Workflow YAML lints; CI green on the new entry-point loop and verify-tarball step.
  • Jest: 1212 / 1212 passing (no functional changes).
  • tsc --noEmit -p tsconfig.json clean for the docs site.
  • pnpm verify:tarball clean across all 22 subpaths.

3.10.5 (2026-05-27)

First of six releases on the post-audit roadmap (3.10.5 → 3.10.6 → 3.11.0 → 3.12.0 → 3.13.0 → 4.0.0). This patch covers the "real bugs consumers actively hit" tier — no API changes observable to consumers.

ConsentManager no longer resets user toggles on parent re-render

The options-init useEffect previously depended on the options reference identity. When a parent re-rendered with an inline-literal options array (the common case), the effect re-ran and reset every toggled consent. The effect now depends on a stable hash of (id + defaultValue) pairs, so re-init only fires when the option set actually changes. Regression test added (ConsentManager-stable-options.test.tsx).

apiAdapter default error handler surfaces the response body

When a save() or remove() failed with a 4xx/5xx, the default console.warn previously only printed the status code — the body (often {"error":"…","fields":{…}}) was dropped. The new handler emits the status line synchronously, then asynchronously clones the response and appends up to 256 chars of body text. Robust against mock-Response objects that lack .clone().

PolicyExporter failure message is actionable

The catch-all "An error occurred during export. Please try again." string masked the real cause (typically a missing jspdf / docx peer dep). The new message includes the underlying error text (capped 200 chars) with the [ndpr-toolkit/policy] prefix.

cookieAdapter default expires 365 → 180 days

6 months is a more privacy-conservative consent-refresh cadence and aligns with typical NDPA + GDPR commentary on consent longevity. Existing callers can pass expires: 365 explicitly to preserve the old behaviour.

examples/dsr-backend-prod PII hygiene

  • The Resend mock-mode stdout transport no longer prints the full email body by default (the body contains the requester's full name and reference). Set DSR_MOCK_PRINT_BODY=1 in dev if you really want the body. Only the metadata (from/to/subject) prints by default.
  • The Prisma persist-failure log path was spreading err.meta, which on Prisma errors often contains the failing column VALUES (email, fullName, identifierValue). It now logs { code, message: truncated(200) } only.

Build / dependency / repo hygiene

  • next bumped to ^16.2.6 (clears 7 dev-only High advisories).
  • lucide-react removed from root peerDependencies + peerDependenciesMeta. Zero internal importers exist, so listing it as a peer was bloat for consumers (an optional install they'd never actually use).
  • Root package.json#devDependencies self-reference to @tantainnovative/ndpr-toolkit widened from ^3.7.0 to ^3.10.0. (Initially attempted removal — turns out the docs site's import …from '@tantainnovative/ndpr-toolkit/{core,policy,server}' paths rely on the self-dep for TS resolution under pnpm. Properly killing it requires switching to tsconfig.paths mapping at the docs site, deferred to 3.11.0.)
  • packages/ndpr-toolkit/package.json marked "private": true so it's clear it's the drift surface, not the publish surface. Slated for full removal in 4.0.
  • Deleted 3 dead root tsconfigs (tsconfig.lib.json, tsconfig.lib-check.json, tsconfig.declarations.json) — referenced from nowhere.
  • Deleted orphan files: examples/consent-examples.tsx, UPGRADE_SUMMARY.md (April-2025 doc superseded by the docs site upgrade guides), public/screenshots/dpia-demo.png (referenced from nowhere).
  • Untracked test-installation/ (already in .gitignore).
  • CHANGELOG.md: 155 wrong-org links repaired (tantainnovative/ndpr-toolkitmr-tanta/ndpr-toolkit) so historical compare-links resolve.

Verification

  • Jest: 1212 / 1212 passing (was 1211 — +1 ConsentManager stability test)
  • tsc --noEmit clean
  • pnpm verify:tarball --skip-ts: clean across all 22 subpaths

3.10.4 (2026-05-26)

A 20-agent audit surfaced six ship-blocker-class issues. All six are fixed in this patch.

Security: XSS in policy export

exportHTML rendered policy.organizationInfo.website straight into an <a href="..."> after HTML-escaping but without URL-scheme validation. A javascript:, data:, or vbscript: URL would execute on click. The risk escalated to critical for multi-tenant SaaS where one tenant's policy is rendered to another tenant's users.

Fix: added an explicit http / https / mailto allowlist (safeUrl() helper in utils/policy-export/html.ts). Disallowed schemes are stripped — the original text still renders, just not as a clickable link. 6 new tests cover javascript:, data:, vbscript:, scheme-less URLs (auto-upgraded to https://), and mailto local-part smuggling attempts.

Bug: NDPA section citations contradicted each other across files

Multiple modules had different statutory citations in different files. The CHANGELOG of 3.5.2 declared corrections that were only partially applied. After this patch, the canonical mapping is enforced everywhere:

  • DPIA: Section 28 (was inconsistently §28 / §38 / §39)
  • DSR: Part VI §34-38 (was Part IV §29-36 in several files)
  • Cross-border: Part VIII §41-43 (was Part VI §41-45)
  • ROPA: Section 29 (was §28(2) in some places)
  • Privacy Policy: Section 27
  • Breach: Section 40

39 files updated covering library source, types, demos, docs, examples, Yoruba/Igbo/Hausa locales, and the regulatoryReferences table in compliance-score.ts (which had a systematically misaligned section-to-right mapping). Two test assertions updated to match. CHANGELOG history and the migrating-3-5-to-3-8 guide deliberately left intact since they are historical records.

Bug: docs taught fictitious API names

Two guide pages documented symbols that don't exist:

  • /docs/guides/compound-components referenced useConsentContext, Consent.Title, Consent.Description, Consent.CategoryList, Consent.AcceptAllButton, Consent.RejectAllButton, Consent.PreferencesButton — none of which are exported.
  • /docs/guides/presets invented ndprPreset, ConsentPreset, DSRPreset, DPIAPreset, BreachPreset, PrivacyPolicyPreset, LawfulBasisPreset, ROPAPreset and an NDPRProvider preset={...} prop that doesn't exist.

Both rewritten against the real API: Consent.Provider / .OptionList / .AcceptButton / .RejectButton / .SaveButton / .Banner / .Settings / .Storage, plus useConsentCompound; the 9 real presets (NDPRConsent, NDPRSubjectRights, NDPRBreachReport, NDPRPrivacyPolicy, NDPRDPIA, NDPRLawfulBasis, NDPRCrossBorder, NDPRROPA, NDPRComplianceDashboard). The rewrites also document gaps honestly — e.g. only NDPRConsent exposes a typed copy prop today; only consent/dsr/policy have narrowed subpaths.

Bug: examples/nextjs-app (the headline StackBlitz CTA) was broken

The example linked from the README's "Open in StackBlitz" / "Open in CodeSandbox" header buttons had two issues:

  • app/layout.tsx was missing import "@tantainnovative/ndpr-toolkit/styles" — the consent banner and every toolkit component rendered unstyled.
  • app/dsr/page.tsx alerted submission.id — a field that doesn't exist on DSRFormSubmission (the form-level payload has no server-issued reference). The alert printed undefined.

Both fixed. The DSR handler now reports the requester's own email instead, and points users at examples/dsr-backend-prod for the full server-side flow.

Bug: async storage adapters silently failed in two presets

NDPRLawfulBasis and NDPRCrossBorder called adapter.load() synchronously and ignored the returned Promise. With cookieAdapter or apiAdapter (both async), saved state never seeded after mount — the presets always rendered with empty initialActivities / initialTransfers, no warning, no error. Only NDPRROPA did it correctly.

Fix: both presets now mirror the NDPRROPA pattern — a useEffect that awaits adapter.load() and rehydrates state when the Promise resolves. The synchronous fast-path is preserved for sync adapters (localStorage, memory) so there's no flash. 3 new regression tests confirm async adapters now seed correctly.

Bug: <NDPRProvider locale={...}> had no visible effect

The toolkit shipped 5 locale files (English, Yoruba, Igbo, Hausa, Pidgin), NDPRProvider exposed a locale prop, and useNDPRLocale() was exported and documented — but no component actually consumed the hook. Passing a locale to the provider changed nothing visible. The i18n docs page misled consumers.

Fix: wired useNDPRLocale() into the three most-visible components — ConsentBanner, DSRRequestForm, BreachReportForm. Resolution chain: explicit prop wins, then provider locale, then English default. Existing consumers passing per-prop overrides see no change; consumers passing a locale to the provider now see their translations rendered. 10 new tests cover the three precedence cases plus partial-locale fallback. Wiring the remaining 15+ components is a tracked follow-up for 3.11.x.

Verification

  • Jest: 1211 / 1211 passing (was 1192 — +19 new tests: 6 XSS, 3 async-adapter, 10 i18n)
  • tsc --noEmit clean for the docs site and the toolkit package
  • verify:tarball gate passes (would have run on tag push)

3.10.3 (2026-05-25)

Bug fix: 4 missing subpath exports

The published exports map in the root package.json was missing four subpaths whose dist files were always being built and shipped in the tarball but were never wired into the import resolver. Affected paths:

  • ./headless — added in 3.10.0 (alias of /hooks)
  • ./lawful-basis/lite — added in 3.8.0
  • ./cross-border/lite — added in 3.8.0
  • ./ropa/lite — added in 3.8.0

Consumers running import { … } from '@tantainnovative/ndpr-toolkit/headless' (or any of the three /lite paths) on 3.8.0 through 3.10.2 got a Node resolution error despite the dist files being present in their node_modules. The bug was that the inner packages/ndpr-toolkit/package.json had the exports right, but only the root package.json is consumed by the publish workflow — and the root never got them.

3.10.3 adds all four entries to both the exports map and the typesVersions["*"] block in the root package.json. Verified by inspecting the published 3.10.3 tarball's package.json to confirm npm view @tantainnovative/ndpr-toolkit@3.10.3 exports lists all 23 paths.

Docs

  • New /docs/guides/upgrading-3-7-to-3-10 guide — concise upgrade path for the common case of consumers stuck on 3.7.0 (the last version that reached npm before the publish-pipeline regression). Covers what's new, what didn't change, the post-bump verify checklist, and the reason 3.10.3 is the right target rather than any intermediate.
  • README — compacted the "What's new" notice. The 700-word historical narrative is now a brief 3.10.x summary with links to the new upgrade guide and the full CHANGELOG. Reverted the StackBlitz / CodeSandbox "Open in" links from examples/ecommerce-starter back to examples/nextjs-app (the comprehensive all-in-one demo — the ecommerce-starter is great as a deeper example but examples/nextjs-app is the better headline first-look).

Verification

  • All 23 exports keys present in both exports and typesVersions["*"] (was 19 in 3.10.2)
  • Build outputs verified locally — every claimed import path resolves to an existing dist/<name>.{js,mjs,d.ts}
  • Tests: 1192 / 1192 passing (no runtime changes)
  • tsc --noEmit clean for the docs site

3.10.2 (2026-05-25)

README-only patch — runtime is byte-identical to 3.10.1. Fixes the npm-rendered README so it reflects the current state of the toolkit.

What was wrong

The npm publish workflow runs npm publish from working-directory: . (the repo root), so the README that lands on the npm package page is /README.md, not packages/ndpr-toolkit/README.md. Three earlier releases (3.8.1, 3.9.0, 3.10.0) updated the inner README — wrong file — leaving the npm-visible header stuck at the v3.4.0 "What's new" notice with the v3.4.0 release link and the 1098 passing tests badge.

What changed in this patch

  • "What's new" notice — v3.4.0 paragraph → consolidated 3.5 → 3.10.x highlights (NDPRThemeProvider, /headless, production DSR backend example, ecommerce starter, SSR-safe templates, Bun quickstart, typed DSR callbacks, Lite manager variants, focus management).
  • Header release link — v3.4.0 Releasev3.10.1 Release.
  • Tests badge — 1098 passing1192 passing.
  • StackBlitz / CodeSandbox "Open in" links — examples/nextjs-app (no longer exists) → examples/ecommerce-starter.
  • Screenshot image URLs — pinned tag v3.5.2v3.10.1 so the images come from the current release tree.

The body of the README already had the correct content for everything below the header (Bun quickstart, Choose Your Layer, Adapters, Live Demos table, etc.).

3.10.1 (2026-05-25)

Hotfix release. Code and runtime are identical to 3.10.0 — this patch fixes the build/publish pipeline that silently failed every release from 3.8.0 onward, so 3.10.1 is the first version since 3.7.0 to actually reach npm.

Root cause

The dts-rollup post-build step (scripts/rollup-dts.mjs) deletes "leftover" hash-suffixed declaration files that tsup emits alongside each rolled-up entry. The 3.8.0 release added new entry names with two hyphens (lawful-basis-lite, cross-border-lite) — and the hash-suffix regex /-[A-Za-z0-9_-]{8,}\.d\.m?ts$/ matched those legitimate entries because the trailing basis-lite / border-lite segments fall inside the 8-char dash-inclusive window. The sweep then deleted dist/lawful-basis-lite.d.ts and dist/cross-border-lite.d.ts right after rollup, the workflow's entry-points check then failed, and npm publish never ran. The git tags / GitHub releases for 3.8.0, 3.8.1, 3.9.0, and 3.10.0 existed; only npm was stuck on 3.7.0.

Fix

  • Replaced the regex-based sweep with an explicit allowlist (new Set([...ENTRIES, 'styles'])). Any .d.ts / .d.mts whose basename isn't in the allowed entries gets swept. No more pattern matching against names.
  • Wired the 3.10.0 /headless entry into the root tsup.config.ts (it had only been wired in packages/ndpr-toolkit/tsup.config.ts, which the publish workflow doesn't use) and added it to CLIENT_ENTRIES so the "use client" banner is injected.
  • Workflow's Verify entry points step now covers 22 entries (was 21) — added headless.

Recovery

After this patch lands and 3.10.1 publishes successfully, the failed tags (v3.8.0, v3.8.1, v3.9.0, v3.10.0) will be backfilled to npm by cherry-picking this fix onto each tagged commit and re-running the workflow.

3.10.0 (2026-05-25)

Phase J of the feedback work — new API surface (theme provider + headless alias) and a production-grade DSR backend reference. Fully additive — no breaking changes.

NDPRThemeProvider — typed theme object → CSS variables

A small React Context provider that takes a TypeScript theme object and injects matching --ndpr-* CSS custom properties on a wrapping div. Syntactic sugar over the existing CSS-variable theming model — unset fields fall through to stylesheet defaults.

import { NDPRThemeProvider, type NDPRTheme } from '@tantainnovative/ndpr-toolkit';

const theme: NDPRTheme = {
  colors: { primary: '22 163 74', primaryHover: '21 128 61' },  // RGB triplets
  radius: { base: '0.75rem' },
  font: { sans: '"Inter", system-ui, sans-serif' },
};

<NDPRThemeProvider theme={theme}>
  <App />
</NDPRThemeProvider>

The NDPRTheme interface keys are derived 1:1 from the variables actually defined in styles.css — no inventing tokens. Setting mode: 'dark' stamps data-theme="dark" on the wrapper to activate the stylesheet's dark-mode block. 6 new tests cover variable injection, key isolation, dark-mode wiring, and className passthrough. Exported from the default entry only (not /core) to keep the RSC-safe surface clean.

/headless subpath — alias of /hooks for discoverability

A pure rename of the existing /hooks entry under a more discoverable name. Same exports, same source, same identifiers — export * from hooks-entry.ts. Useful when consumers grep for "headless" looking for a hooks-only API.

import { useConsent, useDSR, useFocusTrap } from '@tantainnovative/ndpr-toolkit/headless';

12 new tests verify that /headless and /hooks export identical keys and identity-equal values (so the alias can never silently drift). Wired into package.json exports, typesVersions, and tsup.config.ts.

examples/dsr-backend-prod/ — production-grade DSR backend

A 14-file Next.js 15 / React 19 reference implementation that wires NDPRSubjectRights to a real backend pipeline:

  1. Validate via validateDsrSubmission from /server — 400 with { error, fields } on invalid payloads.
  2. Defense-in-depth disposable-email blocklist (configurable via DSR_BLOCKED_EMAIL_DOMAINS).
  3. Persist to a Prisma DSRRequest model with a 30-day estimatedCompletionAt (NDPA Part IV §29-36).
  4. Confirm by sending a Resend transactional email — best-effort: the request still succeeds if email fails.

Both Prisma and Resend are wrapped in dual-mode shims: real clients when DATABASE_URL / RESEND_API_KEY are set, otherwise a Map-backed mock and a stdout logger. The example runs out of the box with no infrastructure.

The 201 response is exactly { referenceId, status, estimatedCompletionAt } — the shape NDPRSubjectRights.onSubmitSuccess consumers read from body per the 3.8.1 contract.

Three new docs guides

Wired into the Implementation Guides sidebar:

  • /docs/guides/themingNDPRThemeProvider, the full NDPRTheme reference, RGB-triplet convention, dark mode, when not to use the provider
  • /docs/guides/headless/headless quickstart, the 10 hooks it exposes, adapter wiring, bundle-size note, the relationship to /hooks
  • /docs/guides/production-dsr-backend — walks through the examples/dsr-backend-prod pipeline, response contract, and how to swap in your own database/email stack

Verification

  • tsc --noEmit clean for the docs site
  • Full Jest suite: 1192/1192 passing (was 1173 — +19: 6 ThemeProvider + 12 headless-parity + 1 default-entry exports check)
  • No changes to existing prop types, hook signatures, or storage adapters

3.9.0 (2026-05-25)

Examples expansion + Bun quickstart + SSR-safe storage docs. Fully additive — no breaking changes.

Runnable example apps

  • examples/ecommerce-starter — multi-page Next.js 15 / React 19 storefront ("Zuri Market") wiring the toolkit into a realistic Nigerian ecommerce flow: home page, checkout, privacy notice, data-subject rights portal (with submitTo + onSubmitSuccess rendering a server-issued reference), and an editable cookie preferences page using useConsent. Demonstrates the /presets/consent, /presets/dsr, /presets/policy, and /hooks subpaths together.
  • examples/ssr/nextjs-app-router — SSR-safe consent on Next.js 15. Layout reads the consent cookie via next/headers, parses it with a small typed helper, and passes the result to a client ConsentRoot so the banner hydrates already-resolved (no flash, no hydration warning).
  • examples/ssr/remix — the same cookie-bridge pattern wired from a Remix loader and Vite's ?url asset import for the stylesheet.
  • examples/ssr/astroAstro.cookies.get in the page frontmatter, then <ConsentRoot client:load> as a React island.

Each example is self-contained (own package.json, tsconfig.json, next.config.mjs / vite.config.ts / astro.config.mjs) so you can copy a directory and bun install && bun dev to run it.

Bun quickstart in the README

Root README now includes copy-pasteable Bun + Vite + React and Bun + Next.js 15 (App Router) recipes that go from zero to a working banner in three commands. The npm-published README also gets a one-line Bun install command alongside pnpm/npm.

SSR-safe storage guide

New docs page at /docs/guides/server-side-storage documenting the cookie-bridge pattern in detail: why localStorageAdapter flashes on SSR, how cookieAdapter solves it, and worked examples for Next.js App Router, Remix, Astro, and SvelteKit. Wired into the docs sidebar under Implementation Guides.

npm-published README brought current (3.4.0 → 3.9.0)

The README that ships to npm was stale at the 3.4.0 highlights. It now leads with a 3.9.0 "what's new", references back to 3.8.1 (onSubmitSuccess), 3.8.0 (Lite Manager variants), 3.6.0 (onSubmitError), and 3.5.x (focus management + reduced motion). Adds a "Runnable Examples" section, a typed DSR callbacks snippet in the Presets section, and a Bun install command. Screenshot URLs bumped from v3.5.2v3.9.0. Tests badge bumped to 1173.

Verification

  • tsc --noEmit clean for the docs site
  • Full Jest suite: 1173/1173 passing (unchanged from 3.8.1 — no logic changes in the published package)
  • No changes to runtime exports, prop types, or storage adapters

3.8.1 (2026-05-25)

Documentation + small API addition patch. Fully additive — no breaking changes.

NDPRSubjectRights — typed onSubmitSuccess callback

Counterpart to the existing onSubmitError. Fires when the submitTo POST returns a 2xx response. Receives the Response, the submitted DSRFormSubmission payload, and the parsed JSON body (or undefined if the response had no body or was not valid JSON).

<NDPRSubjectRights
  submitTo="/api/dsr"
  onSubmitSuccess={({ response, data, body }) => {
    const ref = (body as { referenceId?: string })?.referenceId;
    if (ref) router.push(`/dsr-confirmation?ref=${ref}`);
  }}
  onSubmitError={({ error, response }) => {
    console.error('DSR submit failed', error, response?.status);
  }}
/>

The body field is unknown to force consumers to narrow it themselves before reading fields. 4 new tests cover both success and failure paths.

Documentation

  • DSR submission payload contract documented on the Data Subject Rights component page (/docs/components/data-subject-rights#submission-payload). Includes the canonical DSRFormSubmission TypeScript interface, a concrete JSON example, the recommended response shape, and a working Next.js App Router backend handler.
  • Accessibility sections added to ConsentBanner and DSRRequestForm docs pages. Each lists CONCRETE a11y features verified against the source — useFocusTrap, escape-to-dismiss, prefers-reduced-motion block in the BEM stylesheet, role="alert" on errors, aria-required + aria-invalid wiring on form fields, etc. Features that aren't implemented (e.g. auto-focusing the first invalid field) are NOT claimed — they're suggested as consumer-side recipes instead.
  • Migration guide 3.5 → 3.8 at /docs/guides/migrating-3-5-to-3-8. Covers every minor and patch from 3.5.4 through 3.8.1 with a "what changed / action required" breakdown, a net migration diff for the most common upgrade path (adapter → submitTo + onSubmitSuccess), and a verify checklist. Sidebar entry added under Implementation Guides.

Verification

  • tsc --noEmit clean for both the library and the docs site
  • Full Jest suite: 1173/1173 passing (was 1169 — +4 new submit-callback tests)
  • No changes to the existing onSubmitError, submitTo, or submitOptions props (backward compatible)

3.8.0 (2026-05-24)

Lite (read-only) variants of the heavy Manager components

Three new components — LawfulBasisTrackerLite, ROPAManagerLite, CrossBorderTransferManagerLite — that render the same list-and-summary view as their Full counterparts without the form, validation, write callbacks, or CSV-export utilities. Built for dashboards, audit pages, embedded compliance widgets, and customer-facing transparency pages where users only need to see the records.

Exposed at new subpath entries:

import { LawfulBasisTrackerLite } from '@tantainnovative/ndpr-toolkit/lawful-basis/lite';
import { ROPAManagerLite } from '@tantainnovative/ndpr-toolkit/ropa/lite';
import { CrossBorderTransferManagerLite } from '@tantainnovative/ndpr-toolkit/cross-border/lite';

Bundle deltas (minified + tree-shaken, pre-gzip; transitive closure of each subpath):

Module Full Lite Saved
/lawful-basis/lawful-basis/lite 36.7 KB 12.7 KB 65%
/cross-border/cross-border/lite 53.3 KB 5.6 KB 89%
/ropa/ropa/lite 36.9 KB 13.2 KB 64%

The cross-border Lite saves the most because it does NOT import the 624-row country-adequacy dataset — Lite displays adequacyStatus from each transfer record directly instead of recomputing it.

Each Lite component accepts an optional onActivityClick / onRecordClick / onTransferClick callback so consumers can render their own detail view on row click. All other props (title, description, unstyled, classNames, showSummary, showComplianceGaps/showRiskAlerts) carry the same names and semantics as the Full versions.

Existing /lawful-basis, /cross-border, and /ropa entries are unchanged. This release is fully additive — no consumers need to migrate.

See the new guide: Lite vs Full managers.

Other

  • Added lawful-basis-lite, cross-border-lite, ropa-lite to the publish workflow's entry-point verification loop. Total verified entries: 21.

3.6.2 (2026-05-24)

Compressed Phase D + E + F patch — companion-CLI templates, per-module StackBlitz scaffolds, and a real backend for the /score lead-magnet. Main @tantainnovative/ndpr-toolkit library has no code changes; the bump is for changelog alignment.

Companion packages

  • @tantainnovative/create-ndpr@0.3.0 — the 9 remaining route templates now support all three ORM choices via {{#if ORM=prisma|drizzle|none}} conditional blocks. Picking ORM=none now scaffolds a fully working in-memory app for every endpoint, not just consent:
    • Next.js routes: dsr, breach, dpia, lawful-basis, cross-border
    • Express routes: consent, dpia, lawful-basis, cross-border
  • create-ndpr@1.0.0 (unscoped alias)packages/create-ndpr-unscoped/PUBLISHING.md documents the one-time manual publish + Trusted Publisher setup so future updates flow through CI.

Docs

  • 8 per-module StackBlitz scaffolds under examples/stackblitz/{consent,dsr,dpia,breach,policy,lawful-basis,cross-border,ropa}/. Each is a minimal Next.js 15 + React 19 app that demonstrates ONE preset. Boot any of them in StackBlitz / CodeSandbox with one click — no clone, no install. README has a per-module badge table.
  • Each recipe page (/docs/recipes/*) now has "Open in StackBlitz" + "Open in CodeSandbox" badges pointing at the matching module's scaffold.
  • /score email capture now uses a real backend (Web3Forms) when NEXT_PUBLIC_WEB3FORMS_KEY is set, with the mailto: path preserved as a graceful fallback when the env var is missing or the request fails. Lead emails arrive as structured fields (name, email, org, score, top recommendations) instead of free-form mail-client output.

No library code changes

@tantainnovative/ndpr-toolkit package is unchanged; consumers don't need to upgrade. The version bump keeps the repo, npm, and CHANGELOG in lockstep so the next library feature (3.8.0 with manager-component bundle splitting) lands cleanly.

3.7.0 (2026-05-24)

Phase B — Template + Recipe Library. Closes the dev-feedback content gap (feedback items #7 and #9). New API additions are all backward-compatible — 3.6.x consumers upgrade without changes.

Features

  • 5 org-specific privacy-policy templates. New templateContextFor(id, overrides?) factory exported from /, /core, /server, and /policy. Pre-fills a complete TemplateContext with sector-appropriate data categories, lawful-basis defaults, and the right flags for children/sensitive/financial/cross-border/automated-decisions:
    • saas — startup-shape, cross-border on (overseas cloud), no sensitive
    • ecommerce — financial on, cross-border on (payment processors), automated-decisions on (fraud)
    • schoolhasChildrenData: true (NDPA Section 31 parental consent), education industry
    • healthcarehasSensitiveData: true (Section 30 medical/biometric), financial on (insurance)
    • procurement — government industry, financial on, BVN + gov IDs selected
  • <NDPRPrivacyPolicy template="…" /> prop. Drop a template id and the wizard opens already populated. Optional templateOverrides for org name/DPO/address. Existing adapter + onComplete API unchanged.
  • ORG_POLICY_TEMPLATE_REGISTRY constant — listing of all 5 templates with id, label, description, and example org types. Useful for building a template-picker UI.
  • initialContext prop added to <AdaptivePolicyWizard> and useAdaptivePolicyWizard() — lets advanced consumers bypass the template lookup and seed with a hand-built TemplateContext.

Docs

  • 5 new recipe pages at /docs/recipes/* — production-tested patterns drawn from real adopter feedback:
    • ecommerce-consent — checkout flow, cart-abandonment cookies, marketing-pixel gating with useConsent
    • newsletter-consent — Section 26 affirmative opt-in (no pre-checked boxes), double opt-in pattern, audit-trail snippet
    • contact-form-disclosure — minimum-viable Section 27 notice on a public contact form, data-minimisation guidance
    • careers-rights — applicant data lawful basis, retention by candidate outcome, automated CV-screening disclosure (Section 37)
    • admin-dsr-management — DPO/staff-side workflow: queue, identity verification, 30-day deadline tracking, audit trail
  • Recipes section added to docs nav + sitemap. Each recipe page has full SEO meta and is canonicalised.

Tests

  • 12 new tests for the org templates (1 per template + override behaviour + registry consistency). Suite now at 1146 passing.

3.6.1 (2026-05-24)

Phase A of the post-3.6.0 backlog — tooling foundation. Pure plumbing; no API changes on the main library, but create-ndpr (scoped) bumps to 0.2.0 and a new unscoped create-ndpr alias package ships.

Companion packages

  • @tantainnovative/create-ndpr@0.2.0 — scaffolder no longer emits broken Prisma imports when you pick ORM=None. Templates now support {{#if ORM=prisma|drizzle|none}} conditional blocks. The consent route template emits working code for all three ORMs (Prisma queries, Drizzle queries, or an in-memory stub with TODO comments). Other route templates (still hardcoded to Prisma) are skipped with a clear message when ORM=none instead of generating broken output. Also fixes the StorageAdapter<unknown> type error in the generated ndpr-layout.tsx (now correctly types the apiAdapter as StorageAdapter<ConsentSettings> and adds a CSRF header example).
  • create-ndpr@1.0.0 (NEW, unscoped) — 30-line alias that delegates to the scoped CLI via npx. Lets npm create ndpr@latest, npx create-ndpr, pnpm create ndpr, and bun create ndpr all work alongside the existing npx @tantainnovative/create-ndpr.

Docs

  • README install block now shows all four idiomatic CLI invocations (npm create ndpr@latest, npx create-ndpr, the scoped form, pnpm and bun variants).
  • README header now has "Open in StackBlitz" and "Open in CodeSandbox" badges that boot examples/nextjs-app zero-install in either environment.
  • New "Bundle size guidance" subsection under "Available Import Paths" explains: (1) prefer narrow subpaths over root, (2) use /presets/{consent,dsr,policy} over the full /presets barrel when only one preset is needed, (3) the 3 manager components are heavy by design (they're full table+filter+modal UIs) — import from /hooks if you only need the hook, (4) /server carries zero React for SSR/edge/CI use.

No library code changes

The main @tantainnovative/ndpr-toolkit package is unchanged from 3.6.0; the version bump is for changelog clarity and so the repo, npm, and CHANGELOG all reference the same version.

3.6.0 (2026-05-24)

Features (developer feedback)

This release lands changes flagged by integrating teams using the toolkit in production. All additions are backward-compatible — 3.5.x consumers can upgrade without changes.

  • apiAdapter is now production-ready. Adds credentials (defaults to 'same-origin', set 'include' for cross-origin), dynamic headers (function form for runtime CSRF token lookup), loadMethod/saveMethod overrides (e.g. PUT for upsert APIs), unwrap (transform { data: ... } envelopes), configurable retry with exponential backoff and a shouldRetry predicate (default: retry on network errors + 5xx, skip 4xx), and onError/onSuccess hooks for telemetry. The pre-3.6.0 console.warn behavior is preserved when no onError is configured.
  • NDPRConsent exposes a copy prop. Override title / description / acceptAll / rejectAll / customize / save strings without dropping to the lower-level <ConsentBanner> API.
  • NDPRSubjectRights adds public-form submitTo mode. Public sites can POST to their backend instead of being state-managed by an adapter. Pairs with submitOptions (credentials, headers) and onSubmitError. The state-managed adapter mode is unchanged.
  • Per-preset subpath entries for bundle-size-sensitive consumers:
    • @tantainnovative/ndpr-toolkit/presets/consent — just NDPRConsent (~4KB vs ~8KB for the full barrel)
    • @tantainnovative/ndpr-toolkit/presets/dsr — just NDPRSubjectRights
    • @tantainnovative/ndpr-toolkit/presets/policy — just NDPRPrivacyPolicy The full /presets barrel is unchanged. These are additive narrower entries.

Docs

  • README install block now shows Bun, npm, and Yarn alongside pnpm.

Coming next (3.6.1+)

  • Recipe pages for ecommerce / newsletter / contact-form / careers / admin DSR patterns
  • Org-specific privacy policy templates (SaaS, ecommerce, school, healthcare, procurement)
  • Continued bundle reduction

3.5.5 (2026-05-24)

Features (tests + types)

  • Tests: 8 new tests for the 72-hour NDPC notification deadline (useBreach.getBreachesRequiringNotification) — covers 1h / 24h / 48h / 71.5h / expired / sort-by-urgency / already-notified cases. Test suite now at 1134 passing (up from 1126).
  • CI: Tests now run with --coverage and the report uploads as a workflow artifact on each run. Thresholds re-set as a ratchet (45% branches, 50% functions, 65% lines/statements — at or just below current to catch regressions, raised in follow-up patches).
  • types: DPIAAnswerMap and DPIAAnswerValue exported from / and /hooks. DPIAProvider and <NDPRDPIA> props now use these instead of Record<string, any>, restoring callsite type-safety on onComplete, initialAnswers, and adapter.

Bug Fixes (API contract)

  • useDSR: submitRequest previously declared Omit<DSRRequest, 'id' | 'status' | 'submittedAt' | 'updatedAt' | 'estimatedCompletionDate'> — but submittedAt and estimatedCompletionDate don't exist on DSRRequest. Corrected to Omit<DSRRequest, 'id' | 'status' | 'createdAt' | 'updatedAt' | 'dueDate'> so the type contract matches the implementation.
  • useDSR: getRequestsByStatus now accepts DSRStatus | RequestStatus instead of only the deprecated RequestStatus. Callers using the modern DSRStatus literals (e.g. 'awaitingVerification') no longer get a type error.

3.5.4 (2026-05-23)

Features (accessibility)

  • useFocusTrap: New shared hook that captures document.activeElement on activation, traps Tab cycling inside the container, optionally handles Escape, and restores focus on deactivation (WCAG 2.4.3). Exported from /, /hooks. Drops a previously-missing piece — closing <ConsentBanner> now correctly returns focus to whatever triggered it.
  • prefers-reduced-motion: Stylesheet now neutralises all toolkit animations (slide-in, fade-in, scale-in) and transitions when the user sets prefers-reduced-motion: reduce at the OS level (WCAG 2.3.3). Applies to banners, modals, dashboards, and policy previews.

Bug Fixes (accessibility)

  • ConsentBanner: Internal focus trap now restores focus on close (was previously leaving focus at <body>). Replaced the duplicated trap implementation with the new shared useFocusTrap hook.
  • BreachReportForm: Icon-only "remove attachment" button now has an accessible label (aria-label="Remove attachment {filename}") and a 44×44 px touch target. SVG marked aria-hidden. WCAG 4.1.2 / 2.5.5.
  • BreachReportForm: dataTypes fieldset is now properly wired to its error message via aria-invalid + aria-describedby="dataTypes-error". Required-asterisk now announced via <span className="sr-only">(required)</span> (was visual-only). WCAG 1.3.1 / 3.3.2.

3.5.3 (2026-05-23)

Bug Fixes (developer experience)

  • dsr: <DSRRequestForm> now throws a clear [ndpr-toolkit] <DSRRequestForm requestTypes={...}> requires an array of RequestType[] error when the required prop is missing — previously crashed deep in a minified chunk with Cannot read properties of undefined (reading 'find'). Points users at the <NDPRSubjectRights> preset for defaults.
  • dsr: Default form description updated from "NDPA Part IV, Sections 29-36" to "NDPA Part VI" (matching the 3.5.2 citation fixes).

Features

  • package.json: sideEffects: ["*.css"] is now declared on both root and workspace manifests — bundlers can reliably tree-shake unused subpaths.
  • package.json: engines.node: ">=18.0.0" declared. Quiets installer warnings on Node 16 setups and matches React 18/19 requirements.
  • package.json: funding field added (GitHub Sponsors URL).
  • keywords: Added high-intent search terms developers actually type — ndpa-2023, nigeria-compliance, data-privacy, compliance-tools, nitda, gdpr, gdpr-nigeria, africa, cookie-banner, nextjs. Improves npm search ranking for queries like "react NDPA" and "cookie consent Nigeria".
  • root exports: useComplianceScore and useAdaptivePolicyWizard now re-exported from the root entry. Previously only on /hooks — caused silent discoverability gap when users autocompleted from the bare @tantainnovative/ndpr-toolkit.

3.5.2 (2026-05-23)

⚠️ Legal correctness — please re-review generated artifacts

3.5.2 corrects NDPA 2023 section citations across the toolkit against the gazetted text of the Act. If you embedded any pre-3.5.2 output (privacy policy text, DPIA labels, breach report templates) in a regulatory submission, please regenerate or manually update the citations. See the migration table below.

Bug Fixes (legal correctness)

  • dsr: Right citations now follow NDPA Part VI as gazetted — access = Section 34(1)(a)–(b), rectification = Section 34(1)(c), erasure = Section 34(1)(d) + 34(2), restriction = Section 34(1)(e), withdraw consent = Section 35, object = Section 36, automated decisions = Section 37, portability = Section 38. Previously cited Sections 30–36 in a way that did not match the Act.
  • dpia: DPIA + NDPC prior-consultation citations corrected from Section 38/39 to Section 28 (which covers both per Section 28(1) and 28(2)).
  • cross-border: Transfer-mechanism citations now follow NDPA Part VIII — adequacy decision = Section 42, SCCs / BCRs = Section 41(1)(a), NDPC-approved instruments = Section 42(5), and all derogations = Section 43(1)(a)–(f). Previously cited Sections 41–45 in a way that did not match the Act.
  • lawful-basis, breach: Sensitive personal data citation corrected from Section 27 to Section 30.
  • policy: Privacy notice provisions now cite Section 27 (provision of information) rather than Section 29 (controller obligations).
  • country-adequacy: Removed fabricated NDPC adequacy claims. NDPC has not (as of publication) published a Section 42 adequacy list. The 22 entries previously labelled recognizedBy: 'NDPC' are now recognizedBy: 'self-assessment' with a clear disclaimer in the module header.
  • dsr (types): DSRType union extends with 'withdraw_consent' to reflect the explicit Section 35 right.

Features

  • legal-notice: New LEGAL_DISCLAIMER_SHORT, LEGAL_DISCLAIMER_LONG, and legalDisclaimerBlock() exports (from /, /core, /server), plus a <LegalNotice> component. Injected into all generated artifacts (HTML / Markdown / DOCX / PDF policy exports, regulatory breach report output, DPIA report footer, breach form preview).
  • breach (NDPC report content): BreachReport / BreachFormSubmission extended with the fields the NDPC actually requires under Section 40(2)–(3) — approximateRecordCount, dataSubjectCategories, likelyConsequences, mitigationMeasures, dpoContact, isPhasedReport, supplementsReportId. BreachReportForm now collects them; RegulatoryReportGenerator includes them in the printed NDPC report.
  • core: StorageAdapter is now re-exported from /core for ergonomics (the concrete adapters still live in /adapters).

Build / packaging

  • build: Sourcemaps are no longer published. Pass NDPR_SOURCEMAPS=1 to emit them for local debugging. Cuts the published tarball by ~3 MB.
  • build: dist/ is no longer tracked in git. CI rebuilds from source on every publish.
  • packaging: Workspace packages/ndpr-toolkit/package.json exports now match the root manifest (drops accidental -entry suffixes that pointed at non-existent files). Workspace tsup config also aligned.
  • packaging: README is now synced from the repo root via prepublishOnly, with absolute image URLs so npm renders the hero correctly.

Release pipeline

  • publish workflow: Now publishes with --provenance (sigstore attestation) and checks out the tagged commit (not main HEAD).
  • repo: main is now branch-protected — PRs + 1 approval + green CI required, no force-push, no deletions.

Docs

  • README: Fixed broken code examples — Consent.Provider uses onChange (not onSave); getComplianceScore example now includes all 8 required module inputs; StorageAdapter re-export documented.

Migration table — old → new NDPA citation

Concept Old (pre-3.5.2) Correct (3.5.2+)
Right of access Section 30 Section 34(1)(a)–(b)
Right to rectification Section 31 Section 34(1)(c)
Right to erasure Section 32 Section 34(1)(d), Section 34(2)
Right to restrict processing Section 33 Section 34(1)(e)
Right to portability Section 34 Section 38
Right to object Section 35 Section 36
Right to automated-decision opt-out Section 36 Section 37
Right to withdraw consent (missing) Section 35
DPIA + prior consultation Section 38 / 39 Section 28
Sensitive personal data Section 27 Section 30
Cross-border adequacy Section 41 Section 42
Cross-border SCCs / BCRs Section 42 / 43 Section 41(1)(a)
Cross-border derogations Section 45(a)–(e) Section 43(1)(a)–(f)
Right to complain to NDPC (missing) Section 46(1)

The toolkit produces guidance artifacts only — not legal advice. Verify with your DPO or qualified Nigerian privacy counsel before relying on any output for a regulatory submission.

3.5.1 (2026-05-03)

Bug Fixes

  • ndpr-toolkit: TOC anchor links + unblock docs site deploy (c2f977b)

3.5.0 (2026-05-03)

Features

  • ndpr-toolkit: persist alias, validateDsrSubmission, hooks plugin v7, dts rollup (f1fd29d)

Documentation

  • site: fix homepage + docs code snippets to match published API (0ed4b13)

3.4.1 (2026-05-03)

Bug Fixes

  • ndpr-toolkit: {{var}} substitution + dark-mode opt-in + use-client + effectiveDate (d8deab0)

Documentation

  • phase1: annotate triage doc with v3.4.0 ship status (9fb1744)
  • readme: sync to v3.4.0 — styled defaults, /server entry, real logo (bf7f632)

3.4.0 (2026-05-03)

Features

  • ndpr-toolkit: complete BEM migration for all remaining components (3.5.x) (052827a)
  • ndpr-toolkit: ship /server subpath for RSC-safe pure-logic imports (5dbb24d)
  • ndpr-toolkit: styled-defaults UI strategy (B4 + S1) (b875d3b)

Bug Fixes

  • ndpr-toolkit: auto-init useDefaultPrivacyPolicy + isolate PolicyPage CSS (550e015)
  • ndpr-toolkit: packaging — style export, CHANGELOG, RSC directives (d0bf85b)
  • ndpr-toolkit: policy templates no longer ship literal placeholders to production (6c373a8)
  • ndpr-toolkit: unblock pnpm build, document core entry honestly (ab2f5d7)
  • ndpr-toolkit: use direct localStorage check to detect rehydration race (df81d9b)

Documentation

  • phase1: add v3.3.1 integration feedback triage (37b4a92)
  • rename external-feedback references for public release (afb7fa0)

3.3.1 (2026-04-23)

Features

  • add Next.js App Router example project (8772478), closes #13
  • add Nigerian Pidgin (pcm) locale support (dc31a10), closes #12

Bug Fixes

  • patch 6 Dependabot vulnerabilities via pnpm overrides (51695a6)
  • resolve all audit findings — docs, hydration, hooks, DX, and tests (a0fa3a5)
  • resolve type errors in DSR test files after any-to-unknown migration (6412195)
  • update outdated test counts and stats across docs and marketing (b8d3845)

3.3.0 (2026-04-23)

Features

  • i18n, accessibility, type safety, backend completion, and DX improvements (8fa6d83)

Bug Fixes

  • resolve type errors in adapter test files for CI (7c4b5b9)

Documentation

  • add comprehensive NDPA compliance guide for React/Next.js (9be4502)
  • add screenshots and visual badges to README (4aff0df), closes #11
  • add v3.2.1 release blog post covering all 42 fixes (1dd9310)

3.2.1 (2026-04-15)

Features

  • add adapter support and compound components to Breach module (27bc8c4)
  • add adapter support and compound components to CrossBorder module (810d04f)
  • add adapter support and compound components to DPIA module (87f15fb)
  • add adapter support and compound components to DSR module (0099a4f)
  • add adapter support and compound components to LawfulBasis module (a973de4)
  • add adapter support and compound components to Policy module (4b58327)
  • add adapter support and compound components to ROPA module (22ee47c)
  • add adapter support to useConsent hook (3aee8fe)
  • add adapters entry point (13971d1)
  • add adaptive policy section generators (7bb28a6)
  • add blog system with MDX posts and SEO (8388e0f)
  • add classNames + unstyled props to all 19 components (c1731ff)
  • add compliance checker sidebar and draft save indicator (aaa34df)
  • add compliance score engine (fc6d92a)
  • add composeAdapters for multi-target persistence (b92f072)
  • add comprehensive design system and site components (28fc38f)
  • add Consent compound sub-components (598ea5a)
  • add Consent.Provider compound component (8b1c622)
  • add create-ndpr CLI scaffolder (60ae4dd)
  • add cross-border transfer assessment module (NDPA Part VI) (36e8160)
  • add DOCX policy export with optional dependency (2ead6a3)
  • add Drizzle ORM schema and adapters (dab8a2e)
  • add Drizzle schema, integration example, and documentation (fe95508)
  • add Express routes and middleware (35fcf69)
  • add HTML and Markdown policy export (f30ea3d)
  • add i18n locale support with default English strings (a0aa702)
  • add JSON-LD structured data to key pages for Google rich results (ebf9298)
  • add lawful basis tracker module (NDPA Section 25) (cdd39e3)
  • add localStorage adapter (a49e565)
  • add NDPA policy compliance checker with 15 requirements (596fb27)
  • add NDPRConsent zero-config preset (1720afa)
  • add NDPRDashboard compliance dashboard component (8579c5d)
  • add Next.js App Router API routes (0053bdc)
  • add PDF policy export with cover page and TOC (6aa2515)
  • add policy engine types and template context (b5fea8c)
  • add policy review step with section cards and export panel (c00e697)
  • add policy wizard step components (about, data, processing) (3be7142)
  • add PolicyPage, update preset and entry points for adaptive wizard (b2da198)
  • add PostHog analytics integration (f154d41)
  • add Prisma ORM adapters for all modules (8200979)
  • add record of processing activities (ROPA) module (54ab04e)
  • add REST API adapter (42207bc)
  • add sessionStorage, cookie, and memory adapters (cc419e5)
  • add site header/footer, 5 SEO blog posts, updated sitemap (5278c18)
  • add StorageAdapter interface (98eec0a)
  • add useAdaptivePolicyWizard hook (f6abf13)
  • add zero-config presets for all remaining modules (9ecc714)
  • auto-update version and stats across the site (cfe468f)
  • complete site redesign with blue primary palette (410fac6), closes #2563
  • comprehensive SEO overhaul for organic discovery (97d3b79)
  • export compliance score from core and hooks entry points (3310797)
  • export Consent compound components from consent entry point (be2ea70)
  • improve internal linking and update sitemap (2b50f33)
  • modular imports and lightweight core — v2.1.0 (3cbb5a6)
  • privacy policy upgrade — adaptive wizard, compliance checker, professional exports (a84e4f9)
  • redesign 7 implementation guide pages (19711fa)
  • redesign consent, dsr, dpia, breach demo pages (5373a2f)
  • redesign consent, dsr, dpia, breach, policy component doc pages (248ee0f)
  • redesign demo site with interactive playgrounds (1cedd1b)
  • redesign docs landing and demos landing pages (27de488)
  • redesign homepage with new design system (31a74ee)
  • redesign lawful-basis, cross-border, ropa, hooks doc pages (c2455cb)
  • redesign policy, lawful-basis, cross-border, ropa demo pages (9e00802)
  • redesign v3 guide pages (adapters, compounds, presets, score, backend, styling) (5bbc7e3)
  • scaffold ndpr-recipes package with Prisma schema (20b6943)
  • update policy demo page with AdaptivePolicyWizard (428176b)
  • update SEO metadata, sitemap, and structured data for v3 (aea1a06)
  • use published AdaptivePolicyWizard in policy demo (0b2d7fc)
  • use published toolkit imports in consent and DSR demos (8d86102)
  • use published toolkit imports in lawful-basis, cross-border, ropa demos (bac2b3a)
  • v2.2.0 — fully customizable styling with classNames + unstyled (3558c27)
  • v2.3.0 — theming, portal, typed callbacks, templates, provider (9743c44)
  • v2.4.0 — resolve 30 developer feedback items (01a9930)
  • v3.0.0 — layered architecture, adapters, compound components, presets, compliance score, backend recipes, CLI scaffolder, dashboard (d46b641)

Bug Fixes

  • add @testing-library/dom peer dependency for CI compatibility (626b517)
  • add adapters and presets to root package.json exports, exclude template packages from tsc (178bf7f)
  • add dark-mode overrides for Related Guides links and Tailwind theme classes in docs (e26aef6)
  • add force-static export to robots and sitemap for static build (b1a5791)
  • add PostHog env variables to build:lib step (5401268)
  • add presets entry to tsup config and fix types paths (d3805da)
  • consent demo toggles now update state correctly (fc710d2)
  • correct all ComplianceInput and ComplianceReport field names in docs (2e64943)
  • correct code snippets in consent demo (b6c3edf)
  • correct consent component and hook API references in docs (c5b6b84)
  • correct consent demo code examples to match actual API (5040aad)
  • correct documentation URLs to ndprtoolkit.com.ng (7be38fe)
  • correct StorageAdapter interface in docs, expand compound components guide (92fd16e)
  • deep QA audit — 50+ fixes across entire project (adbd94c)
  • eliminate all 404s, broken links, and misinformation (8d1bdb7)
  • eliminate all vulnerabilities (52 → 0) (e6dc1b7)
  • explicitly add PostHog env variables to Next.js config (5ba341d)
  • export DSRFormSubmission and BreachFormSubmission from per-module entry points (ae5cf79), closes #14
  • handle missing PostHog env variables gracefully (6851e24)
  • improve mobile responsiveness across all demo pages and homepage (1027de7)
  • improve privacy policy demo UI with comprehensive dark-mode overrides (ce7f3ea), closes #2563
  • move force-static export before function declaration in sitemap (36ee87a)
  • pass onGenerate prop to PolicyGenerator in demo (dff0c05)
  • preserve code block indentation and add language labels (4cb8fca)
  • QA audit — resolve bugs, type conflicts, and stale references (4884735)
  • redesign footer with proper attribution (d8c9bac)
  • redesign navbar with better contrast and SVG logo (4946b10)
  • reduce vulnerabilities from 60 to 14 (9dd5614)
  • remove blue left border from demo cards (a112678)
  • reorganize static HTML for proper GitHub Pages routing (256b31f)
  • replace rainbow color palette with unified blue brand identity (c140ff0)
  • resolve 42 bugs across security, hooks, adapters, components, and utils (d5f160f)
  • resolve picomatch and flatted security vulnerabilities (82cb446)
  • resolve strict TypeScript errors in test files (085ca52)
  • resolve type mismatches in cross-border, ropa, lawful-basis demos (1269e6e)
  • standardize docs to use pnpm and per-module imports (fbdbf16)
  • transparent nav background, explicit gradient on CTA button (45bd144)
  • unify color scheme, fix links, remove legacy-peer-deps (e35d4c1)
  • update demo imports to use local source and fix React bundling (831a501)
  • update homepage hero with accurate data (b931230)
  • update nav colors and design tokens (b9fb7c2)
  • use async useEffect for adapter load in NDPRROPA preset (a1b3820)
  • use constants for PostHog env variables to ensure build-time inlining (4ed9c1c)
  • use existing PolicyGenerator in demo until adaptive wizard is published (d5f195f)
  • useDPIA isComplete() no longer mutates state during render (fd7ea9d)
  • wrap consent demo page in ConsentProvider to fix loading issue (8ba7cf0)

Documentation

  • add import paths guide to docs site (6d7e19a)
  • add internationalization and CLI scaffolder guide pages (2dab79a)
  • add privacy and analytics disclosure to README (aac2c69)
  • add v3 guides for adapters, compounds, presets, compliance score, and backend integration (47503e2)
  • add v3 quick start sections to consent, dsr, breach component docs (1519501)
  • expand backend integration guide and add NDPRDashboard docs (592d63d)
  • overhaul documentation and demo site for NDPA 2023 (cde6517)
  • rewrite README for v3.0.0 (97fca14)
  • update site homepage and docs landing for v3.0.0 (9f6c37c)

Code Refactoring

  • migrate existing modules from NDPR to NDPA 2023 (abbec76)
  • update type definitions for NDPA 2023 (db6e5e3)

[3.2.0] — 2026-04-14

Added

  • Adaptive Policy Wizard (AdaptivePolicyWizard) — 4-step question-driven privacy policy generator
    • Step 1: Organization details + industry + size
    • Step 2: Data category selection (16 categories across 5 groups)
    • Step 3: Processing purposes, third-party processors, cross-border/automated toggles
    • Step 4: Review, edit, reorder sections, add custom sections, export
  • Policy section engine (assemblePolicy()) — context-aware section generator producing 13 possible sections based on user answers, with industry-adapted language
  • NDPA compliance checker (evaluatePolicyCompliance()) — real-time 15-requirement checklist (115 points max) with auto-fix suggestions
  • Professional PDF export (exportPDF()) — cover page, table of contents, page numbers, headers/footers, NDPA compliance badge
  • DOCX export (exportDOCX()) — Word document with heading styles, headers, footers (optional docx peer dependency)
  • HTML export (exportHTML()) — self-contained responsive HTML with embedded styles, print CSS, semantic markup
  • Markdown export (exportMarkdown()) — clean markdown with TOC and metadata
  • PolicyPage component — embeddable React component for rendering policies at /privacy-policy
  • Draft auto-save — adapter-backed persistence with debounced save and restore-on-reload
  • Custom sections — UI to add, edit, reorder, and delete custom policy sections (max 10)
  • Compliance checker sidebar — collapsible panel with score ring, requirement checklist, and "Fix it" buttons
  • useAdaptivePolicyWizard hook with full state management, compliance tracking, and export functions
  • docx added as optional peer dependency for Word export

Changed

  • NDPRPrivacyPolicy preset now renders the AdaptivePolicyWizard instead of the basic PolicyGenerator

[3.1.0] — 2026-04-14

Added

  • Complete site redesign with new design system (dark theme, blue primary palette)
  • Shared UI component library (Button, Card, Badge, Section, CodeBlock, FeatureCard, Grid, Tabs, CTASection)
  • DemoLayout component for consistent demo page presentation
  • Redesigned SiteHeader with sticky blur, gradient logo, and responsive mobile menu
  • Redesigned SiteFooter with multi-column layout
  • Redesigned DocLayout with collapsible sidebar navigation
  • i18n locale support — NDPRLocale type, defaultLocale (English), mergeLocale() utility
  • locale prop on NDPRProvider for global text customization
  • Comprehensive SEO updates (sitemap, robots.txt, structured data)
  • Prepared @tantainnovative/create-ndpr and @tantainnovative/ndpr-recipes for npm publishing

Changed

  • All documentation pages restyled with dark theme and consistent design system
  • All 8 demo pages wrapped in shared DemoLayout
  • Homepage hero with animated code example and 3-file quickstart tabs
  • Blog listing and post pages redesigned

[3.0.0] — 2026-04-14

Architecture

v3 introduces a layered architecture that separates UI, state, and storage concerns. Every module now supports pluggable persistence, composable sub-components, and zero-config presets.

Added

  • StorageAdapter pattern — all 8 hooks accept an optional adapter prop for pluggable persistence

    • Built-in adapters: localStorageAdapter, sessionStorageAdapter, cookieAdapter, apiAdapter, memoryAdapter, composeAdapters
    • New ./adapters entry point: import { apiAdapter } from '@tantainnovative/ndpr-toolkit/adapters'
    • composeAdapters() writes to multiple targets simultaneously (e.g. localStorage + API)
    • isLoading state on all hooks for async adapter support
  • Compound components — all 8 modules decomposed into composable sub-components

    • Consent.Provider, Consent.Banner, Consent.AcceptButton, Consent.RejectButton, Consent.OptionList, Consent.SaveButton, Consent.Settings, Consent.Storage
    • DSR.Provider, DSR.Form, DSR.Dashboard, DSR.Tracker
    • DPIA.Provider, DPIA.Questionnaire, DPIA.Report, DPIA.StepIndicator
    • Breach.Provider, Breach.ReportForm, Breach.RiskAssessment, Breach.NotificationManager, Breach.ReportGenerator
    • Policy.Provider, Policy.Generator, Policy.Preview, Policy.Exporter
    • LawfulBasis.Provider, LawfulBasis.Tracker
    • CrossBorder.Provider, CrossBorder.Manager
    • ROPA.Provider, ROPA.Manager
  • Zero-config presets — 8 components that work with zero required props

    • NDPRConsent, NDPRSubjectRights, NDPRBreachReport, NDPRPrivacyPolicy, NDPRDPIA, NDPRLawfulBasis, NDPRCrossBorder, NDPRROPA
    • New ./presets entry point: import { NDPRConsent } from '@tantainnovative/ndpr-toolkit/presets'
    • Each preset includes NDPA-compliant defaults and accepts optional overrides
  • Compliance score enginegetComplianceScore() and useComplianceScore()

    • Scores 0–100 across all 8 modules with NDPA section references
    • Weighted scoring (consent 20%, DSR 15%, breach 15%, policy 12%, DPIA 12%, lawful basis 10%, ROPA 8%, cross-border 8%)
    • Prioritised recommendations with effort estimates
    • Available from /core (no React) and /hooks
  • @tantainnovative/ndpr-recipes — backend integration package (separate)

    • Prisma schema (5 NDPA compliance tables)
    • Drizzle ORM schema
    • Next.js App Router API routes (consent, DSR, breach, ROPA, compliance)
    • Express routes and middleware
    • Prisma and Drizzle ORM adapters implementing StorageAdapter
    • Consent verification middleware
    • Integration examples

Changed

  • All hooks now use useCallback and useRef for stable references
  • storageOptions prop on useConsent deprecated in favor of adapter prop
  • storageKey/useLocalStorage props on other hooks deprecated in favor of adapter

Migration from v2

All v2 APIs continue to work unchanged. The deprecated storageOptions/storageKey/useLocalStorage props are mapped to built-in adapters internally. To adopt v3 features:

  1. Adapters: Replace storageOptions with adapter prop on any hook
  2. Compound components: Use <Consent.Provider> + sub-components for custom layouts
  3. Presets: Replace boilerplate with zero-config components like <NDPRConsent />
  4. Compliance score: Add getComplianceScore() calls to assess compliance posture

[2.4.0] — 2026-04-07

Added

  • CSS custom properties applied to ALL 19 components (was only consent)
  • Consent analytics callback (onAnalytics prop with ConsentAnalyticsEvent)
  • Consent version enforcement — auto-shows banner when policy version changes
  • Consent audit trail utility (createAuditEntry, getAuditLog, appendAuditEntry)
  • manageStorage prop on ConsentBanner to prevent storage race conditions
  • isSubmitting prop on DSRRequestForm and BreachReportForm with loading state
  • defaultValues and onReset props on DSRRequestForm and BreachReportForm
  • useDefaultPrivacyPolicy convenience hook (zero-config privacy policy)
  • primaryButton/secondaryButton classNames aliases across 13 components
  • Select All / Deselect All toggle on ConsentBanner customize panel
  • Smooth transition animation on ConsentBanner customize panel
  • sanitizeInput utility for XSS prevention on form submissions
  • data-ndpr-component attribute with baseline focus-visible styles
  • Migration guide blog post (v1.x → v2.x)
  • Full classNames reference table in styling documentation (169 keys)

Fixed

  • show prop changes after mount now properly sync ConsentBanner visibility
  • Email validation regex strengthened (was accepting "a@b.c")
  • Color contrast: text-gray-500 → text-gray-600 across 14 components (WCAG AA)
  • ARIA: aria-invalid, aria-describedby, role="alert" on form validation errors
  • ARIA: focus management on ConsentBanner open
  • Responsive: buttons stack vertically on mobile
  • Input sanitization prevents XSS in form submissions

Changed

  • Zero runtime dependencies (uuid replaced with crypto.randomUUID)
  • lucide-react peer dep widened from ^0.507.0 to >=0.400.0
  • TypeScript module declaration added for ./styles import
  • All 15 components have NDPA section reference JSDoc comments
  • NDPA section references added to default component descriptions

[2.3.0] — 2026-04-07

Added

  • CSS custom properties (design tokens) for framework-agnostic theming
    • --ndpr-primary, --ndpr-primary-hover, --ndpr-background, etc.
    • Dark mode tokens via .dark and [data-theme="dark"] selectors
    • Consumers can theme all components by overriding CSS variables once
  • NDPRProvider context for shared configuration across components
    • Provides organizationName, dpoEmail, theme, unstyled globally
    • useNDPRConfig() hook to access config from any component
  • Default policy templates — PolicyGenerator now works without props
    • DEFAULT_POLICY_SECTIONS (8 NDPA-compliant sections)
    • DEFAULT_POLICY_VARIABLES (8 common variables)
    • createBusinessPolicyTemplate() factory function
  • onValidationError callback on DSRRequestForm and BreachReportForm
  • ConsentBanner inline position option for embedding without portal
  • ConsentBanner zIndex prop (default: 9999)
  • Escape key dismisses ConsentBanner

Fixed

  • ConsentBanner now renders via createPortal to document.body — properly overlays page content instead of rendering inline
  • ConsentBanner center position now shows with backdrop overlay
  • onSubmit callbacks typed — DSRFormSubmission and BreachFormSubmission replace any
  • Consent components use CSS variables instead of hardcoded blue-600

Changed

  • PolicyGenerator sections and variables props are now optional
  • ./styles CSS export now includes design tokens (was animation-only)

[2.2.0] — 2026-04-07

Added

  • classNames prop on all 19 components for granular CSS class overrides
  • unstyled prop to strip all default Tailwind classes (BYO CSS)
  • resolveClass utility exported from /core and all module paths
  • 194 customizable class sections across all components
  • Styling & Customization guide in documentation
  • Blog post: "Fully Customizable Styling"

Changed

  • Components now work with any CSS framework (Bootstrap, CSS Modules, vanilla CSS)
  • Default Tailwind styling preserved — zero breaking changes
  • All ClassNames type interfaces exported from barrel files

Fixed

  • Eliminated all npm audit vulnerabilities (52 → 0)
  • Reduced package size 67% (512 KB → 170 KB) by excluding source maps
  • Upgraded jspdf 3.x → 4.x, replaced abandoned standard-version

[2.1.2] — 2026-04-06

Changed

  • Exclude source maps from published package (512 KB → 168 KB, 67% reduction)
  • Use granular files globs instead of blanket dist/ include

[2.1.1] — 2026-04-04

Fixed

  • useDPIA hook: isComplete() no longer mutates state during render (caused infinite re-renders)
  • 15 incorrect NDPA section references in PolicyGenerator and ComplianceChecklist
  • NDPC naming consistency ("Nigerian" → "Nigeria" Data Protection Commission)
  • Breach demo reference number flickering
  • DSR demo rejected request timeline, sample due date, and scroll behavior
  • DPIA demo step indicator now clickable, critical risk level reachable
  • CSV export double-quote escaping per RFC 4180
  • 32 form label associations for accessibility
  • ARIA attributes on risk sliders and file inputs
  • 22 GitHub links corrected to mr-tanta/ndpr-toolkit
  • 11 incorrect API examples in READMEs
  • 12 fabricated component names removed from docs
  • Added MIT LICENSE file
  • Removed dead tslib dependency
  • Added sideEffects field for tree-shaking
  • Added metadataBase for social sharing

[2.1.0] — 2026-04-04

Added

  • Modular import paths: /core, /hooks, /consent, /dsr, /dpia, /breach, /policy, /lawful-basis, /cross-border, /ropa
  • Zero-dependency /core entry point with all types and utility functions
  • React-only /hooks entry point for hook consumers
  • Per-module entry points for granular tree-shaking
  • TypeScript typesVersions for IDE auto-completion on all subpaths
  • Code splitting via tsup for optimal bundle size

Changed

  • UI dependencies (Radix UI, lucide-react, jspdf, class-variance-authority, clsx, tailwind-merge) moved from dependencies to optional peerDependencies
  • Consumers using only /core or /hooks no longer install any UI dependencies
  • Bundle is now split into shared chunks for better tree-shaking

[2.0.0] — 2026-04-04

Breaking Changes

  • Rebranded from NDPR focus to NDPA 2023 (Nigeria Data Protection Act)
  • NotificationRequirement.nitdaNotificationRequiredndpcNotificationRequired (old field deprecated)
  • NotificationRequirement.nitdaNotificationDeadlinendpcNotificationDeadline (old field deprecated)
  • RegulatoryNotification.nitdaContactndpcContact (old field deprecated)

Added

  • Lawful Basis Tracker module (NDPA Section 25)
  • Cross-Border Transfer Assessment module (NDPA Part VI)
  • Record of Processing Activities (ROPA) module
  • New DSR types: 'information' (Section 29) and 'automated_decision_making' (Section 36)
  • NDPC consultation fields in DPIA results
  • Lawful basis field in consent settings
  • NDPC registration number in organization info
  • Transfer Impact Assessment types

Changed

  • All legal references updated from NDPR to NDPA 2023
  • Regulatory body references updated from NITDA to NDPC
  • Section references updated to NDPA sections
  • Privacy policy templates updated for NDPA compliance
  • Breach notification workflow targets NDPC instead of NITDA
  • PostHog moved from dependencies to devDependencies

1.0.12 (2025-09-30)

Bug Fixes

  • reorganize static HTML for proper GitHub Pages routing (256b31f)

1.0.11 (2025-09-29)

Bug Fixes

  • support custom domain and update author contact info (cc30680)

1.0.10 (2025-09-29)

Documentation

  • update author information (8f866ef)

1.0.9 (2025-09-29)

Bug Fixes

  • correct ConsentManager usage in demo page and add gh-pages (c742a9d)
  • migrate ESLint to v9 flat config and configure lint-staged (dd6d5ad)
  • resolve GitHub Actions failures - remove husky prepare script and fix pnpm lockfile compatibility (c787ba8)

[1.0.7] - 2025-01-10

Added

  • Headless Mode: Complete separation of state management from UI components
  • Enhanced useConsent Hook: Added all requested methods (hasUserConsented, showBanner, showSettings, etc.)
  • Unstyled Components: New unstyled component variants for complete design freedom
  • Render Props Pattern: Support for maximum flexibility in custom implementations
  • Component Composition: Mix and match components as needed
  • Event System: Comprehensive event-driven consent management with useConsentManager
  • Position & Animation Controls: Full control over banner positioning and animations
  • TypeScript Generics: Support for custom consent categories with full type safety
  • Exported Utilities: All core utilities and contexts are now accessible
  • Cookie Utils: Helper functions for cookie management
  • Consent Storage: Utilities for persisting consent data

Changed

  • Moved React and React DOM to peerDependencies for better compatibility
  • Improved package structure with proper exports for different module systems
  • Enhanced documentation with comprehensive examples

Fixed

  • Removed circular dependency (package depending on itself)
  • Fixed TypeScript declarations generation
  • Improved build configuration for library mode

[1.0.6] - Previous Releases

  • Initial public release with core NDPR compliance features
  • Consent management system
  • Data subject rights portal
  • Privacy policy generator
  • DPIA assessment tool
  • Breach notification module