UNPKG

@tantainnovative/ndpr-toolkit

Version:

Nigeria Data Protection Toolkit — enterprise-grade compliance components for the Nigeria Data Protection Act (NDPA) 2023

650 lines (387 loc) 133 kB
# Changelog All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. ## [5.7.0](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.6.0...v5.7.0) (2026-06-02) ### Features * **consent:** surface the cookie scanner in ConsentBanner ([cdafcd8](https://github.com/mr-tanta/ndpr-toolkit/commit/cdafcd89179302d8c7c95dd0e2797df7faf1a6ea)) ## [5.6.0](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.5.1...v5.6.0) (2026-06-01) ### Features * **consent:** add cookie scanner (declared vs present audit) ([d74366f](https://github.com/mr-tanta/ndpr-toolkit/commit/d74366f8356857621a94729eb79225cdfbef2024)) * **packages:** refresh create-ndpr and ndpr-recipes for GAID 2025 ([bf709cf](https://github.com/mr-tanta/ndpr-toolkit/commit/bf709cf4f40176e65f21f2db61a946542fac7479)) * **site:** make the /score audit a full-screen cinematic experience ([52cc0e8](https://github.com/mr-tanta/ndpr-toolkit/commit/52cc0e8b6b889df0ca6c1842a71d25029f4e4aaa)) * **site:** turn the /score audit into a guided step-by-step wizard ([18f26ad](https://github.com/mr-tanta/ndpr-toolkit/commit/18f26ad36726f4b3ef9228e032e3d60eb4de1786)) ### Bug Fixes * **packages:** use canonical bin path (no ./ prefix) in create-ndpr manifests ([600d989](https://github.com/mr-tanta/ndpr-toolkit/commit/600d989f1f73fe5174f4d3fc035980bfa6c8fa27)) ### Documentation * **marketing:** refresh the X thread + LinkedIn post for current state ([58c6fca](https://github.com/mr-tanta/ndpr-toolkit/commit/58c6fca46899765bf3a09eee5cc0fca387f6cd99)) * **site:** add Cookie Scanner guide page ([5813686](https://github.com/mr-tanta/ndpr-toolkit/commit/58136865de16e9f1f8b74c7918294fdcd0c5b78f)) ## [5.5.1](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.5.0...v5.5.1) (2026-05-31) ### Features * **site:** cross-link the ndpr audit CLI from the score and DPIA marketing pages ([ca35869](https://github.com/mr-tanta/ndpr-toolkit/commit/ca358690d0817363a5b8c65513c61a02152068e4)) * **site:** surface the GAID 2025 layer and ndpr audit CLI on the landing page ([e84ea43](https://github.com/mr-tanta/ndpr-toolkit/commit/e84ea43232f23c5baba4bddeba224f9f9ab8ebaf)) ### Bug Fixes * **dcpmi:** only UHL and EHL file CAR; OHL renews registration annually ([07a7bc6](https://github.com/mr-tanta/ndpr-toolkit/commit/07a7bc6e7f4d13d960a9fc5f008429834a583359)) ### Documentation * **blog:** add four NDPC GAID 2025 articles ([0b25725](https://github.com/mr-tanta/ndpr-toolkit/commit/0b25725c871b1e72baada1113ca919fcdaae47f9)) * correct DCPMI registration fees and the OHL CAR rule ([b1f8ed4](https://github.com/mr-tanta/ndpr-toolkit/commit/b1f8ed4a03feef086730bf1f26902addb069951e)) * link the audit CLI guide from the backend integration guide ([4dfaab7](https://github.com/mr-tanta/ndpr-toolkit/commit/4dfaab7b908b44b756c8411dd8d809bf598e0390)) ## [5.5.0](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.4.0...v5.5.0) (2026-05-31) ### Features * **cli:** add ndpr audit CLI compliance gate ([8a8bdc5](https://github.com/mr-tanta/ndpr-toolkit/commit/8a8bdc53ddc322a8d92a90fd86353a4a22df9cdc)) ## [5.4.0](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.3.0...v5.4.0) (2026-05-30) ### Features * **breach:** show live NDPC notification readiness in BreachReportForm ([86e2394](https://github.com/mr-tanta/ndpr-toolkit/commit/86e23943756680ad64bd939e60edb896884680c2)) ## [5.3.0](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.2.0...v5.3.0) (2026-05-30) ### Features * **breach:** add NDPA S.40 / GAID 2025 Article 33 notification completeness checker ([2a6bdf2](https://github.com/mr-tanta/ndpr-toolkit/commit/2a6bdf25af11431c6b4603c0892d3db44f0bf348)) ### Documentation * document the breach notification completeness checker ([c05ee84](https://github.com/mr-tanta/ndpr-toolkit/commit/c05ee848afd899ae916b40fa55023886380392ae)) ## [5.2.0](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.1.4...v5.2.0) (2026-05-30) ### Features * **car:** add NDPC GAID 2025 Compliance Audit Returns scheduler ([30d399a](https://github.com/mr-tanta/ndpr-toolkit/commit/30d399a38b51632f8a3fdcb01a329f983eb7ca06)) * **dcpmi:** add NDPC GAID 2025 DCPMI tier classifier ([179a5c1](https://github.com/mr-tanta/ndpr-toolkit/commit/179a5c1d6a7b9a3fba89947ff2d943b2159a142c)) ### Bug Fixes * address findings from the A–Z verification pass ([4ede74f](https://github.com/mr-tanta/ndpr-toolkit/commit/4ede74f9aece2c74d8377a91068522d534daa6fc)) ### Documentation * document DCPMI classifier and Compliance Audit Returns ([a7fefe1](https://github.com/mr-tanta/ndpr-toolkit/commit/a7fefe1a07b58d1b4d9e6f0a8b20b3a4c8f4fef6)) ## [5.1.4](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.1.3...v5.1.4) (2026-05-28) ### Bug Fixes * **deps:** remove unused tailwind-merge peer dependency ([5aec54b](https://github.com/mr-tanta/ndpr-toolkit/commit/5aec54ba4fbdc9cdb69368f7477b87016af7755d)) ## [5.1.3](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.1.2...v5.1.3) (2026-05-28) ### Bug Fixes * **deps:** widen optional tailwind-merge peer to ^2.6.0 || ^3.0.0 ([f7d6ed8](https://github.com/mr-tanta/ndpr-toolkit/commit/f7d6ed851f36d38605e74b128b961aa789060ed9)) ## [5.1.2](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.1.1...v5.1.2) (2026-05-28) ### Features * **site:** add real NDPA privacy policy page; fix site-wide OG image ([7c4938e](https://github.com/mr-tanta/ndpr-toolkit/commit/7c4938e9b4fb0098aa3ceb92dd9b656a5cc4e8e6)) ### Bug Fixes * align NDPA section citations with the Act's Arrangement of Sections ([69ea70c](https://github.com/mr-tanta/ndpr-toolkit/commit/69ea70c0d39ea7836ba1d54e04ae864368459c6b)) * correct dead npm URL, stale SEO version, and broken doc links ([ceec6de](https://github.com/mr-tanta/ndpr-toolkit/commit/ceec6dee15954829876a0799ce7c87def5b0bf0b)) * correct wrong NDPA section labels in shipped components ([bcf223c](https://github.com/mr-tanta/ndpr-toolkit/commit/bcf223ca847e71e346df53b7f31d253729711ddf)) * **deps:** patch dev/example advisories and make overrides work under pnpm 11 ([d2e868f](https://github.com/mr-tanta/ndpr-toolkit/commit/d2e868fcb03c39beff35d48d37930f610a2d414b)) * **docs:** correct component API-reference tables and samples to match v5.1.1 ([6d1c0d2](https://github.com/mr-tanta/ndpr-toolkit/commit/6d1c0d2b6d154508d5df0544cb5a129e5c2d4fda)) * **docs:** correct NDPA 2023 section citations in compliance-score weights table ([c132aa0](https://github.com/mr-tanta/ndpr-toolkit/commit/c132aa05d5181ab3f9a147fee8d5323ff6cea68b)) * **docs:** drop stale v3 framing and correct locale count ([fd2d346](https://github.com/mr-tanta/ndpr-toolkit/commit/fd2d3466dad7b6f19aa1f3d16a6382d4f371f794)) * **docs:** sweep renamed/removed APIs out of current guides and demos ([04149e3](https://github.com/mr-tanta/ndpr-toolkit/commit/04149e3f8c52e59865abd8d042b2168fda38d1c0)) * **examples:** astro demo requires Node 22.12+ for astro 6 ([b70baf1](https://github.com/mr-tanta/ndpr-toolkit/commit/b70baf1a6f9ff5f26d284dfeceb089731dac121d)) * **examples:** bump SSR demos to patched astro 6 / vite 6 stack ([32a06f3](https://github.com/mr-tanta/ndpr-toolkit/commit/32a06f306fd9b7cf2d417bd3ff3c23c7820fc6c1)) * **landing/docs:** correct broken code samples and stale references ([cc246a2](https://github.com/mr-tanta/ndpr-toolkit/commit/cc246a25bfba9cb212ee6ea517b43709e220735f)) * **landing:** correct stale version branding and wrong stats ([a7965bc](https://github.com/mr-tanta/ndpr-toolkit/commit/a7965bc1cfc35a50a79e0445165f15e0919803a6)) * standardize privacy-policy generator citation on NDPA Section 27 ([cc92ec5](https://github.com/mr-tanta/ndpr-toolkit/commit/cc92ec5eaabd4907ecf8b2d61133e7a39e6e8c87)) ## [5.1.1](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.1.0...v5.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](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.0.1...v5.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](https://github.com/mr-tanta/ndpr-toolkit/compare/v5.0.0...v5.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](https://github.com/mr-tanta/ndpr-toolkit/compare/v4.1.0...v5.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`](https://ndprtoolkit.com.ng/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`. ```diff - <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. ```diff - <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](https://github.com/mr-tanta/ndpr-toolkit/compare/v4.0.0...v4.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). ```ts 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-left` → `margin-inline-start`, `text-align: left` → `text-align: start`, `left: 0; right: 0` → `inset-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 `DPIAResult` — `overallRiskLevel`, `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](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.13.0...v4.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`](https://ndprtoolkit.com.ng/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:** ```diff - <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](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.12.0...v3.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](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.11.0...v3.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. - **`CrossBorderTransferManager`** — `summaryData` 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 tables** — `STATUS_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](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.10.6...v3.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. ```ts 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. - `getRequestById` → `getRequest`, `filterRequestsByStatus` → `getRequestsByStatus`, `filterRequestsByType` → `getRequestsByType` (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.3` → `v3.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](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.10.5...v3.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.yml`** — `npm 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](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.10.4...v3.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-toolkit` → `mr-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](https://github.com/mr-tanta/ndpr-toolkit/compare/v3.10.3...v3.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)