@jmruthers/pace-core 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (473) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/audit-tool/00-dependencies.cjs +394 -0
  4. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  5. package/audit-tool/audits/02-project-structure.cjs +255 -0
  6. package/audit-tool/audits/03-architecture.cjs +196 -0
  7. package/audit-tool/audits/04-code-quality.cjs +149 -0
  8. package/audit-tool/audits/05-styling.cjs +224 -0
  9. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  10. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  11. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  12. package/audit-tool/audits/09-operations.cjs +208 -0
  13. package/audit-tool/index.cjs +291 -0
  14. package/audit-tool/utils/code-utils.cjs +218 -0
  15. package/audit-tool/utils/file-utils.cjs +230 -0
  16. package/audit-tool/utils/report-utils.cjs +241 -0
  17. package/core-usage-manifest.json +93 -0
  18. package/cursor-rules/00-standards-overview.mdc +156 -0
  19. package/cursor-rules/01-pace-core-compliance.mdc +586 -0
  20. package/cursor-rules/02-project-structure.mdc +42 -4
  21. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
  22. package/cursor-rules/04-code-quality.mdc +419 -0
  23. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
  24. package/cursor-rules/06-security-rbac.mdc +518 -0
  25. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  26. package/cursor-rules/08-testing-documentation.mdc +324 -0
  27. package/cursor-rules/09-operations.mdc +365 -0
  28. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  29. package/dist/DataTable-7PMH7XN7.js +15 -0
  30. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  31. package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
  32. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  33. package/dist/api-Y4MQWOFW.js +4 -0
  34. package/dist/audit-MYQXYZFU.js +3 -0
  35. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  36. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  37. package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
  38. package/dist/chunk-6F3IILHI.js +62 -0
  39. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  40. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  41. package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
  42. package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
  43. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  44. package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
  45. package/dist/chunk-C7NSAPTL.js +1 -0
  46. package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
  47. package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
  48. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  49. package/dist/chunk-GHYHJTYV.js +994 -0
  50. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  51. package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
  52. package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
  53. package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
  54. package/dist/chunk-MBADTM7L.js +64 -0
  55. package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
  56. package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
  57. package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
  58. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  59. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  60. package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
  61. package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
  62. package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
  63. package/dist/components.d.ts +7 -5
  64. package/dist/components.js +46 -257
  65. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  66. package/dist/eslint-rules/index.cjs +35 -0
  67. package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
  68. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  69. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  70. package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
  71. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  72. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  73. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  74. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  75. package/dist/hooks.d.ts +6 -6
  76. package/dist/hooks.js +62 -172
  77. package/dist/icons/index.d.ts +1 -0
  78. package/dist/icons/index.js +1 -0
  79. package/dist/index.d.ts +12 -11
  80. package/dist/index.js +67 -660
  81. package/dist/providers.d.ts +2 -2
  82. package/dist/providers.js +8 -35
  83. package/dist/rbac/eslint-rules.d.ts +46 -44
  84. package/dist/rbac/eslint-rules.js +7 -4
  85. package/dist/rbac/index.d.ts +109 -586
  86. package/dist/rbac/index.js +14 -207
  87. package/dist/styles/index.js +2 -12
  88. package/dist/theming/runtime.d.ts +14 -1
  89. package/dist/theming/runtime.js +3 -19
  90. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  91. package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
  92. package/dist/types-t9H8qKRw.d.ts +55 -0
  93. package/dist/types.d.ts +1 -1
  94. package/dist/types.js +7 -94
  95. package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
  96. package/dist/utils.d.ts +24 -117
  97. package/dist/utils.js +54 -392
  98. package/docs/README.md +17 -7
  99. package/docs/api/README.md +4 -402
  100. package/docs/api/modules.md +301 -871
  101. package/docs/api-reference/components.md +21 -21
  102. package/docs/api-reference/deprecated.md +31 -6
  103. package/docs/api-reference/hooks.md +80 -80
  104. package/docs/api-reference/rpc-functions.md +78 -3
  105. package/docs/api-reference/types.md +1 -1
  106. package/docs/api-reference/utilities.md +1 -1
  107. package/docs/architecture/README.md +1 -1
  108. package/docs/core-concepts/events.md +3 -3
  109. package/docs/core-concepts/organisations.md +6 -6
  110. package/docs/core-concepts/permissions.md +6 -6
  111. package/docs/documentation-index.md +12 -18
  112. package/docs/getting-started/cursor-rules.md +3 -23
  113. package/docs/getting-started/dependencies.md +650 -0
  114. package/docs/getting-started/documentation-index.md +1 -1
  115. package/docs/getting-started/examples/README.md +4 -4
  116. package/docs/getting-started/examples/full-featured-app.md +1 -1
  117. package/docs/getting-started/faq.md +2 -2
  118. package/docs/getting-started/installation-guide.md +20 -7
  119. package/docs/getting-started/quick-reference.md +4 -4
  120. package/docs/getting-started/quick-start.md +23 -12
  121. package/docs/implementation-guides/authentication.md +15 -15
  122. package/docs/implementation-guides/component-styling.md +1 -1
  123. package/docs/implementation-guides/data-tables.md +126 -33
  124. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  125. package/docs/implementation-guides/dynamic-colors.md +3 -3
  126. package/docs/implementation-guides/file-upload-storage.md +2 -2
  127. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  128. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  129. package/docs/implementation-guides/large-datasets.md +3 -2
  130. package/docs/implementation-guides/organisation-security.md +2 -2
  131. package/docs/implementation-guides/performance.md +2 -2
  132. package/docs/implementation-guides/permission-enforcement.md +5 -1
  133. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  134. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  135. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  136. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  137. package/docs/rbac/README.md +17 -8
  138. package/docs/rbac/advanced-patterns.md +6 -6
  139. package/docs/rbac/api-reference.md +20 -20
  140. package/docs/rbac/edge-functions-guide.md +376 -0
  141. package/docs/rbac/event-based-apps.md +3 -3
  142. package/docs/rbac/examples.md +41 -41
  143. package/docs/rbac/getting-started.md +37 -37
  144. package/docs/rbac/performance.md +1 -1
  145. package/docs/rbac/quick-start.md +52 -52
  146. package/docs/rbac/secure-client-protection.md +1 -35
  147. package/docs/rbac/troubleshooting.md +1 -1
  148. package/docs/security/README.md +5 -5
  149. package/docs/standards/0-standards-overview.md +220 -0
  150. package/docs/standards/1-pace-core-compliance-standards.md +986 -0
  151. package/docs/standards/2-project-structure-standards.md +949 -0
  152. package/docs/standards/3-architecture-standards.md +606 -0
  153. package/docs/standards/4-code-quality-standards.md +728 -0
  154. package/docs/standards/5-styling-standards.md +348 -0
  155. package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
  156. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  157. package/docs/standards/8-testing-documentation-standards.md +401 -0
  158. package/docs/standards/9-operations-standards.md +1102 -0
  159. package/docs/standards/README.md +185 -57
  160. package/docs/troubleshooting/README.md +4 -4
  161. package/docs/troubleshooting/common-issues.md +2 -2
  162. package/docs/troubleshooting/debugging.md +9 -9
  163. package/docs/troubleshooting/migration.md +4 -4
  164. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  165. package/eslint-config-pace-core.cjs +33 -6
  166. package/package.json +35 -23
  167. package/scripts/install-cursor-rules.cjs +25 -6
  168. package/scripts/install-eslint-config.cjs +284 -0
  169. package/src/__tests__/fixtures/supabase.ts +1 -1
  170. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
  171. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  172. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  173. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  174. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
  175. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  176. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  177. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  178. package/src/__tests__/public-recipe-view.test.ts +38 -9
  179. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  180. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  181. package/src/__tests__/templates/component.test.template.tsx +18 -15
  182. package/src/components/Button/Button.tsx +5 -1
  183. package/src/components/Calendar/Calendar.tsx +201 -47
  184. package/src/components/ContextSelector/ContextSelector.tsx +106 -119
  185. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  186. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  187. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  188. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  189. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  190. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  191. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  192. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  193. package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
  194. package/src/components/DataTable/components/EditFields.tsx +23 -3
  195. package/src/components/DataTable/components/EditableRow.tsx +12 -9
  196. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  197. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  198. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  199. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  200. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  201. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  202. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  203. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  204. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  205. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  206. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  207. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  208. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  209. package/src/components/DataTable/components/index.ts +2 -1
  210. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  211. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  212. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  213. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  214. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  215. package/src/components/DataTable/types.ts +5 -18
  216. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  217. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  218. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  219. package/src/components/DateTimeField/DateTimeField.tsx +10 -9
  220. package/src/components/Dialog/Dialog.test.tsx +128 -104
  221. package/src/components/Dialog/Dialog.tsx +742 -24
  222. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  223. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
  224. package/src/components/FileDisplay/FileDisplay.tsx +23 -17
  225. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  226. package/src/components/FileUpload/FileUpload.tsx +112 -130
  227. package/src/components/Form/Form.test.tsx +6 -8
  228. package/src/components/Form/Form.tsx +365 -4
  229. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  230. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  231. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  232. package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
  233. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  234. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  235. package/src/components/Progress/Progress.tsx +2 -4
  236. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  237. package/src/components/Select/Select.tsx +109 -98
  238. package/src/components/Select/types.ts +4 -1
  239. package/src/components/UserMenu/UserMenu.tsx +9 -6
  240. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  241. package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
  242. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  243. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  244. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  245. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  246. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  247. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  248. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  249. package/src/hooks/public/usePublicEvent.ts +67 -195
  250. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  251. package/src/hooks/public/usePublicEventLogo.ts +24 -14
  252. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  253. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  254. package/src/hooks/useAppConfig.ts +28 -26
  255. package/src/hooks/useEventTheme.test.ts +217 -239
  256. package/src/hooks/useEventTheme.ts +16 -28
  257. package/src/hooks/useFileDisplay.ts +2 -2
  258. package/src/hooks/useOrganisationPermissions.ts +5 -7
  259. package/src/hooks/useQueryCache.ts +0 -1
  260. package/src/hooks/useSessionDraft.ts +380 -0
  261. package/src/hooks/useSessionRestoration.ts +3 -1
  262. package/src/icons/index.ts +27 -0
  263. package/src/index.ts +5 -0
  264. package/src/providers/OrganisationProvider.tsx +23 -14
  265. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  266. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  267. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  268. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  269. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  270. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  271. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  272. package/src/providers/services/EventServiceProvider.tsx +1 -24
  273. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  274. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  275. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
  276. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  277. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  278. package/src/rbac/adapters.tsx +7 -295
  279. package/src/rbac/api.test.ts +44 -56
  280. package/src/rbac/api.ts +10 -17
  281. package/src/rbac/cache-invalidation.ts +0 -1
  282. package/src/rbac/compliance/index.ts +10 -0
  283. package/src/rbac/compliance/pattern-detector.ts +553 -0
  284. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  285. package/src/rbac/components/AccessDenied.tsx +150 -0
  286. package/src/rbac/components/NavigationGuard.tsx +12 -20
  287. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  288. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  289. package/src/rbac/components/index.ts +3 -41
  290. package/src/rbac/eslint-rules.js +1 -1
  291. package/src/rbac/hooks/index.ts +0 -3
  292. package/src/rbac/hooks/permissions/index.ts +0 -3
  293. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  294. package/src/rbac/hooks/usePermissions.ts +0 -3
  295. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  296. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  297. package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
  298. package/src/rbac/hooks/useResourcePermissions.ts +139 -48
  299. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  300. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  301. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  302. package/src/rbac/index.ts +7 -9
  303. package/src/rbac/utils/contextValidator.ts +9 -7
  304. package/src/services/AuthService.ts +130 -18
  305. package/src/services/EventService.ts +4 -97
  306. package/src/services/InactivityService.ts +16 -0
  307. package/src/services/OrganisationService.ts +7 -44
  308. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  309. package/src/services/base/BaseService.ts +0 -3
  310. package/src/styles/core.css +7 -0
  311. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  312. package/src/theming/parseEventColours.ts +22 -10
  313. package/src/types/database.generated.ts +4733 -3809
  314. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  315. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  316. package/src/utils/context/organisationContext.test.ts +13 -28
  317. package/src/utils/context/organisationContext.ts +21 -52
  318. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  319. package/src/utils/file-reference/index.ts +39 -15
  320. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  321. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  322. package/src/utils/index.ts +4 -1
  323. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  324. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  325. package/src/utils/persistence/keyDerivation.ts +304 -0
  326. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  327. package/src/utils/security/secureStorage.ts +5 -5
  328. package/src/utils/storage/README.md +1 -1
  329. package/src/utils/storage/helpers.ts +3 -3
  330. package/src/utils/supabase/createBaseClient.ts +147 -0
  331. package/src/utils/timezone/timezone.test.ts +1 -2
  332. package/src/utils/timezone/timezone.ts +1 -1
  333. package/src/utils/validation/csrf.ts +4 -4
  334. package/cursor-rules/00-pace-core-compliance.mdc +0 -331
  335. package/cursor-rules/01-standards-compliance.mdc +0 -244
  336. package/cursor-rules/04-testing-standards.mdc +0 -268
  337. package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
  338. package/cursor-rules/06-code-quality.mdc +0 -309
  339. package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
  340. package/cursor-rules/CHANGELOG.md +0 -119
  341. package/cursor-rules/README.md +0 -192
  342. package/dist/DataTable-AOVNCPTX.js +0 -175
  343. package/dist/DataTable-AOVNCPTX.js.map +0 -1
  344. package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
  345. package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
  346. package/dist/api-O6HTBX5Y.js +0 -52
  347. package/dist/api-O6HTBX5Y.js.map +0 -1
  348. package/dist/audit-V53FV5AG.js +0 -17
  349. package/dist/audit-V53FV5AG.js.map +0 -1
  350. package/dist/chunk-5DRSZLL2.js.map +0 -1
  351. package/dist/chunk-63FOKYGO.js.map +0 -1
  352. package/dist/chunk-6COVEUS7.js.map +0 -1
  353. package/dist/chunk-AFVQODI2.js +0 -263
  354. package/dist/chunk-AFVQODI2.js.map +0 -1
  355. package/dist/chunk-DGUM43GV.js.map +0 -1
  356. package/dist/chunk-E66EQZE6.js.map +0 -1
  357. package/dist/chunk-EFN2EIMK.js.map +0 -1
  358. package/dist/chunk-FFQEQTNW.js.map +0 -1
  359. package/dist/chunk-FMUCXFII.js.map +0 -1
  360. package/dist/chunk-G37KK66H.js.map +0 -1
  361. package/dist/chunk-G7QEZTYQ.js +0 -2053
  362. package/dist/chunk-G7QEZTYQ.js.map +0 -1
  363. package/dist/chunk-HU2C6SSC.js.map +0 -1
  364. package/dist/chunk-IHB5DR3H.js.map +0 -1
  365. package/dist/chunk-IVOFDYWT.js.map +0 -1
  366. package/dist/chunk-J36DSWQK.js.map +0 -1
  367. package/dist/chunk-JGRYX5UX.js.map +0 -1
  368. package/dist/chunk-KQCRWDSA.js +0 -1
  369. package/dist/chunk-KQCRWDSA.js.map +0 -1
  370. package/dist/chunk-L4OXEN46.js.map +0 -1
  371. package/dist/chunk-LMC26NLJ.js +0 -84
  372. package/dist/chunk-LMC26NLJ.js.map +0 -1
  373. package/dist/chunk-M43Y4SSO.js.map +0 -1
  374. package/dist/chunk-M7MPQISP.js.map +0 -1
  375. package/dist/chunk-NTM7ZSB6.js.map +0 -1
  376. package/dist/chunk-PWLANIRT.js.map +0 -1
  377. package/dist/chunk-QXHPKYJV.js.map +0 -1
  378. package/dist/chunk-RGAWHO7N.js.map +0 -1
  379. package/dist/chunk-UPPMRMYG.js.map +0 -1
  380. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  381. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  382. package/dist/components.js.map +0 -1
  383. package/dist/contextValidator-5OGXSPKS.js +0 -9
  384. package/dist/contextValidator-5OGXSPKS.js.map +0 -1
  385. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  386. package/dist/hooks.js.map +0 -1
  387. package/dist/index.js.map +0 -1
  388. package/dist/providers.js.map +0 -1
  389. package/dist/rbac/eslint-rules.js.map +0 -1
  390. package/dist/rbac/index.js.map +0 -1
  391. package/dist/styles/index.js.map +0 -1
  392. package/dist/theming/runtime.js.map +0 -1
  393. package/dist/types.js.map +0 -1
  394. package/dist/utils.js.map +0 -1
  395. package/docs/best-practices/README.md +0 -472
  396. package/docs/best-practices/accessibility.md +0 -601
  397. package/docs/best-practices/common-patterns.md +0 -516
  398. package/docs/best-practices/deployment.md +0 -1103
  399. package/docs/best-practices/performance.md +0 -1328
  400. package/docs/best-practices/security.md +0 -940
  401. package/docs/best-practices/testing.md +0 -1034
  402. package/docs/rbac/compliance/compliance-guide.md +0 -544
  403. package/docs/standards/01-architecture-standard.md +0 -44
  404. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  405. package/docs/standards/03-component-standard.md +0 -32
  406. package/docs/standards/04-code-style-standard.md +0 -32
  407. package/docs/standards/05-security-standard.md +0 -44
  408. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  409. package/docs/standards/pace-core-compliance.md +0 -432
  410. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  411. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  412. package/scripts/audit/core/checks/bundle.cjs +0 -142
  413. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  414. package/scripts/audit/core/checks/config.cjs +0 -54
  415. package/scripts/audit/core/checks/coverage.cjs +0 -84
  416. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  417. package/scripts/audit/core/checks/documentation.cjs +0 -268
  418. package/scripts/audit/core/checks/environment.cjs +0 -116
  419. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  420. package/scripts/audit/core/checks/forms.cjs +0 -172
  421. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  422. package/scripts/audit/core/checks/hooks.cjs +0 -334
  423. package/scripts/audit/core/checks/imports.cjs +0 -244
  424. package/scripts/audit/core/checks/performance.cjs +0 -325
  425. package/scripts/audit/core/checks/routes.cjs +0 -117
  426. package/scripts/audit/core/checks/state.cjs +0 -130
  427. package/scripts/audit/core/checks/structure.cjs +0 -65
  428. package/scripts/audit/core/checks/style.cjs +0 -584
  429. package/scripts/audit/core/checks/testing.cjs +0 -122
  430. package/scripts/audit/core/checks/typescript.cjs +0 -61
  431. package/scripts/audit/core/scanner.cjs +0 -199
  432. package/scripts/audit/core/utils.cjs +0 -137
  433. package/scripts/audit/index.cjs +0 -223
  434. package/scripts/audit/reporters/console.cjs +0 -151
  435. package/scripts/audit/reporters/json.cjs +0 -54
  436. package/scripts/audit/reporters/markdown.cjs +0 -124
  437. package/scripts/audit-consuming-app.cjs +0 -86
  438. package/src/components/DataTable/components/DataTableBody.tsx +0 -454
  439. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  440. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  441. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  442. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  443. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  444. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  445. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  446. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  447. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  448. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  449. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  450. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  451. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  452. package/src/components/DataTable/utils/debugTools.ts +0 -514
  453. package/src/eslint-rules/pace-core-compliance.js +0 -638
  454. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  455. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  456. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  457. package/src/rbac/components/NavigationProvider.tsx +0 -345
  458. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  459. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  460. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  461. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  462. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  463. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  464. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  465. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  466. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  467. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  468. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  469. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  470. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  471. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  472. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  473. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -21,7 +21,7 @@ export * from './validation';
21
21
  // Explicitly re-export commonly used validation utilities to ensure they're available
22
22
  // Import from source modules to avoid re-export issues
23
23
  export { validateUserInput, usernameSchema } from './validation/validationUtils';
24
- export { sanitizeUserInput, sanitizeFormData } from './validation/sanitization';
24
+ export { sanitizeUserInput, sanitizeFormData, sanitizeHtml } from './validation/sanitization';
25
25
  export { emailSchema, nameSchema, phoneSchema, urlSchema } from './validation/common';
26
26
  export { passwordSchema } from './validation/passwordSchema';
27
27
  export { pickSchema, combineSchemas } from './validation/schema';
@@ -178,3 +178,6 @@ export {
178
178
  getInFlightRequestStats,
179
179
  deduplicatedQuery
180
180
  } from './request-deduplication';
181
+
182
+ // Supabase client creation (restricted wrapper)
183
+ export { createBaseClient } from './supabase/createBaseClient';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @file Key Derivation Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Utils/Persistence/__tests__
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import {
9
+ deriveDataTableKey,
10
+ deriveDialogKey,
11
+ deriveFormKey,
12
+ hashStableFingerprint,
13
+ } from '../keyDerivation';
14
+
15
+ describe('keyDerivation', () => {
16
+ describe('deriveDataTableKey', () => {
17
+ it('should use rbacPageId as primary key', () => {
18
+ const key = deriveDataTableKey({
19
+ rbacPageId: 'user-management',
20
+ });
21
+ expect(key).toBe('datatable:user-management');
22
+ });
23
+
24
+ it('should fall back to title when rbacPageId not available', () => {
25
+ const key = deriveDataTableKey({
26
+ title: 'Users Table',
27
+ });
28
+ expect(key).toBe('datatable:users-table');
29
+ });
30
+
31
+ it('should use route pathname as fallback', () => {
32
+ const key = deriveDataTableKey(
33
+ {},
34
+ { pathname: '/users' }
35
+ );
36
+ expect(key).toBe('datatable:/users');
37
+ });
38
+
39
+ it('should use column IDs hash as last resort', () => {
40
+ const key = deriveDataTableKey({
41
+ columnIds: ['id', 'name', 'email'],
42
+ });
43
+ expect(key).toMatch(/^datatable:[a-f0-9]+$/);
44
+ });
45
+
46
+ it('should return null if no stable key can be determined', () => {
47
+ const key = deriveDataTableKey({});
48
+ expect(key).toBeNull();
49
+ });
50
+ });
51
+
52
+ describe('deriveDialogKey', () => {
53
+ it('should use title as primary key', () => {
54
+ const key = deriveDialogKey({
55
+ title: 'Edit User',
56
+ });
57
+ expect(key).toBe('dialog:edit-user');
58
+ });
59
+
60
+ it('should use route pathname as fallback', () => {
61
+ const key = deriveDialogKey(
62
+ {},
63
+ { pathname: '/users/edit' }
64
+ );
65
+ expect(key).toBe('dialog:/users/edit');
66
+ });
67
+
68
+ it('should return null if no stable key can be determined', () => {
69
+ const key = deriveDialogKey({});
70
+ expect(key).toBeNull();
71
+ });
72
+ });
73
+
74
+ describe('deriveFormKey', () => {
75
+ it('should use parent dialog title when available', () => {
76
+ const key = deriveFormKey(
77
+ {},
78
+ { dialogTitle: 'Edit User' }
79
+ );
80
+ expect(key).toBe('form:edit-user');
81
+ });
82
+
83
+ it('should use route + field names hash as fallback', () => {
84
+ const key = deriveFormKey(
85
+ {
86
+ fieldNames: ['name', 'email'],
87
+ },
88
+ null,
89
+ { pathname: '/users' }
90
+ );
91
+ expect(key).toMatch(/^form:\/users:[a-f0-9]+$/);
92
+ });
93
+
94
+ it('should use route pathname as last resort', () => {
95
+ const key = deriveFormKey(
96
+ {},
97
+ null,
98
+ { pathname: '/users' }
99
+ );
100
+ expect(key).toBe('form:/users');
101
+ });
102
+
103
+ it('should return null if no stable key can be determined', () => {
104
+ const key = deriveFormKey({});
105
+ expect(key).toBeNull();
106
+ });
107
+ });
108
+
109
+ describe('hashStableFingerprint', () => {
110
+ it('should create stable hash from array of strings', () => {
111
+ const hash1 = hashStableFingerprint(['a', 'b', 'c']);
112
+ const hash2 = hashStableFingerprint(['a', 'b', 'c']);
113
+ expect(hash1).toBe(hash2);
114
+ });
115
+
116
+ it('should be order-independent', () => {
117
+ const hash1 = hashStableFingerprint(['a', 'b', 'c']);
118
+ const hash2 = hashStableFingerprint(['c', 'b', 'a']);
119
+ expect(hash1).toBe(hash2);
120
+ });
121
+
122
+ it('should normalize strings (lowercase, trim)', () => {
123
+ const hash1 = hashStableFingerprint(['A', ' B ', 'C']);
124
+ const hash2 = hashStableFingerprint(['a', 'b', 'c']);
125
+ expect(hash1).toBe(hash2);
126
+ });
127
+
128
+ it('should filter out empty values', () => {
129
+ const hash1 = hashStableFingerprint(['a', '', 'b', 'c']);
130
+ const hash2 = hashStableFingerprint(['a', 'b', 'c']);
131
+ expect(hash1).toBe(hash2);
132
+ });
133
+ });
134
+ });
135
+
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @file Sensitive Field Detection Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Utils/Persistence/__tests__
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import {
9
+ isSensitiveField,
10
+ filterSensitiveFields,
11
+ getSensitiveFieldNames,
12
+ } from '../sensitiveFieldDetection';
13
+
14
+ describe('sensitiveFieldDetection', () => {
15
+ describe('isSensitiveField', () => {
16
+ it('should detect password input type', () => {
17
+ expect(isSensitiveField('password', 'password')).toBe(true);
18
+ expect(isSensitiveField('userPassword', 'password')).toBe(true);
19
+ });
20
+
21
+ it('should detect hidden input type', () => {
22
+ expect(isSensitiveField('token', 'hidden')).toBe(true);
23
+ });
24
+
25
+ it('should detect sensitive field names by pattern', () => {
26
+ expect(isSensitiveField('password', 'text')).toBe(true);
27
+ expect(isSensitiveField('user_password', 'text')).toBe(true);
28
+ expect(isSensitiveField('api_key', 'text')).toBe(true);
29
+ expect(isSensitiveField('secretToken', 'text')).toBe(true);
30
+ expect(isSensitiveField('credit_card', 'text')).toBe(true);
31
+ expect(isSensitiveField('ssn', 'text')).toBe(true);
32
+ });
33
+
34
+ it('should not detect non-sensitive fields', () => {
35
+ expect(isSensitiveField('name', 'text')).toBe(false);
36
+ expect(isSensitiveField('email', 'email')).toBe(false);
37
+ expect(isSensitiveField('age', 'number')).toBe(false);
38
+ });
39
+
40
+ it('should be case-insensitive', () => {
41
+ expect(isSensitiveField('PASSWORD', 'text')).toBe(true);
42
+ expect(isSensitiveField('Api_Key', 'text')).toBe(true);
43
+ });
44
+ });
45
+
46
+ describe('filterSensitiveFields', () => {
47
+ it('should filter out sensitive fields', () => {
48
+ const data = {
49
+ name: 'John',
50
+ email: 'john@example.com',
51
+ password: 'secret123',
52
+ api_key: 'key123',
53
+ };
54
+
55
+ const filtered = filterSensitiveFields(data, ['name', 'email', 'password', 'api_key']);
56
+
57
+ expect(filtered).toEqual({
58
+ name: 'John',
59
+ email: 'john@example.com',
60
+ });
61
+ expect(filtered).not.toHaveProperty('password');
62
+ expect(filtered).not.toHaveProperty('api_key');
63
+ });
64
+
65
+ it('should filter by input type', () => {
66
+ const data = {
67
+ name: 'John',
68
+ password: 'secret123',
69
+ };
70
+
71
+ const filtered = filterSensitiveFields(data, ['name', 'password'], {
72
+ password: 'password',
73
+ });
74
+
75
+ expect(filtered).toEqual({
76
+ name: 'John',
77
+ });
78
+ expect(filtered).not.toHaveProperty('password');
79
+ });
80
+
81
+ it('should return empty object if all fields are sensitive', () => {
82
+ const data = {
83
+ password: 'secret123',
84
+ api_key: 'key123',
85
+ };
86
+
87
+ const filtered = filterSensitiveFields(data, ['password', 'api_key']);
88
+
89
+ expect(filtered).toEqual({});
90
+ });
91
+
92
+ it('should return all fields if none are sensitive', () => {
93
+ const data = {
94
+ name: 'John',
95
+ email: 'john@example.com',
96
+ };
97
+
98
+ const filtered = filterSensitiveFields(data, ['name', 'email']);
99
+
100
+ expect(filtered).toEqual(data);
101
+ });
102
+ });
103
+
104
+ describe('getSensitiveFieldNames', () => {
105
+ it('should return list of sensitive field names', () => {
106
+ const sensitive = getSensitiveFieldNames(
107
+ ['name', 'email', 'password', 'api_key'],
108
+ {
109
+ password: 'password',
110
+ }
111
+ );
112
+
113
+ expect(sensitive).toEqual(['password', 'api_key']);
114
+ });
115
+
116
+ it('should return empty array if no sensitive fields', () => {
117
+ const sensitive = getSensitiveFieldNames(['name', 'email']);
118
+
119
+ expect(sensitive).toEqual([]);
120
+ });
121
+ });
122
+ });
123
+
@@ -0,0 +1,304 @@
1
+ /**
2
+ * @file Key Derivation Utilities
3
+ * @package @jmruthers/pace-core
4
+ * @module Utils/Persistence
5
+ * @since 1.0.0
6
+ *
7
+ * Utilities for automatically deriving stable persistence keys for components.
8
+ * Provides deterministic key generation based on component props, route, and context.
9
+ *
10
+ * Features:
11
+ * - Automatic key derivation from component props
12
+ * - Route-based fallbacks when props unavailable
13
+ * - Stable hashing for fingerprint-based keys
14
+ * - Support for nested component contexts
15
+ *
16
+ * @dependencies
17
+ * - React Router (optional) - For route-based keys
18
+ */
19
+
20
+ /**
21
+ * Simple hash function for creating stable fingerprints
22
+ * Uses djb2 algorithm for fast, stable hashing
23
+ */
24
+ function hashString(str: string): string {
25
+ let hash = 5381;
26
+ for (let i = 0; i < str.length; i++) {
27
+ hash = ((hash << 5) + hash) + str.charCodeAt(i);
28
+ hash = hash & hash; // Convert to 32-bit integer
29
+ }
30
+ // Convert to positive hex string
31
+ return Math.abs(hash).toString(16);
32
+ }
33
+
34
+ /**
35
+ * Create a stable hash from an array of strings
36
+ */
37
+ function hashStableFingerprint(values: string[]): string {
38
+ const normalized = values
39
+ .filter(Boolean)
40
+ .map((v) => String(v).toLowerCase().trim())
41
+ .sort()
42
+ .join('|');
43
+
44
+ return hashString(normalized);
45
+ }
46
+
47
+ /**
48
+ * Normalize a string for use in storage keys
49
+ * Removes special characters and converts to lowercase
50
+ * Preserves path structure by keeping slashes
51
+ */
52
+ function normalizeKey(str: string, preserveSlashes = false): string {
53
+ if (preserveSlashes) {
54
+ // For pathnames, preserve slashes but normalize other characters
55
+ const trimmed = str.toLowerCase().trim();
56
+ const hasLeadingSlash = trimmed.startsWith('/');
57
+ const parts = trimmed.split('/').map(segment => segment.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')).filter(Boolean);
58
+ const normalized = parts.join('/');
59
+ return hasLeadingSlash ? `/${normalized}` : normalized;
60
+ }
61
+ return str
62
+ .toLowerCase()
63
+ .trim()
64
+ .replace(/[^a-z0-9]+/g, '-')
65
+ .replace(/^-+|-+$/g, '');
66
+ }
67
+
68
+ /**
69
+ * Get route pathname if React Router is available
70
+ * Returns null if router is not available
71
+ */
72
+ function getRoutePathname(): string | null {
73
+ if (typeof window === 'undefined') {
74
+ return null;
75
+ }
76
+
77
+ try {
78
+ // Try to use React Router's useLocation if available
79
+ // Since we can't use hooks here, we'll use window.location as fallback
80
+ // Components using this should pass location from useLocation() hook
81
+ return window.location.pathname;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Props for DataTable key derivation
89
+ */
90
+ export interface DataTableKeyProps {
91
+ /** RBAC page ID (mandatory, always available) */
92
+ rbacPageId?: string;
93
+ /** Table title */
94
+ title?: string;
95
+ /** Column IDs for fingerprint fallback */
96
+ columnIds?: string[];
97
+ }
98
+
99
+ /**
100
+ * Derive a persistence key for DataTable component
101
+ *
102
+ * Priority order:
103
+ * 1. rbac.pageId (mandatory prop, always available)
104
+ * 2. title prop (normalized)
105
+ * 3. Hashed fingerprint of column IDs
106
+ * 4. Route pathname + component type (only if not root path)
107
+ *
108
+ * @param props - DataTable props
109
+ * @param location - Optional location from useLocation() hook
110
+ * @param userId - Optional user ID to scope persistence by user
111
+ * @returns Derived key or null if no stable key can be determined
112
+ */
113
+ export function deriveDataTableKey(
114
+ props: DataTableKeyProps,
115
+ location?: { pathname: string } | null,
116
+ userId?: string | null
117
+ ): string | null {
118
+ let baseKey: string | null = null;
119
+
120
+ // Priority 1: rbac.pageId (mandatory, always available)
121
+ if (props.rbacPageId) {
122
+ baseKey = `datatable:${normalizeKey(props.rbacPageId)}`;
123
+ }
124
+ // Priority 2: title prop
125
+ else if (props.title) {
126
+ baseKey = `datatable:${normalizeKey(props.title)}`;
127
+ }
128
+ // Priority 3: Hashed fingerprint of column IDs (before pathname fallback)
129
+ else if (props.columnIds && props.columnIds.length > 0) {
130
+ const fingerprint = hashStableFingerprint(props.columnIds);
131
+ if (fingerprint) {
132
+ baseKey = `datatable:${fingerprint}`;
133
+ }
134
+ }
135
+ // Priority 4: Route pathname + component type (only if not root path)
136
+ else {
137
+ const pathname = location?.pathname || getRoutePathname();
138
+ if (pathname && pathname !== '/') {
139
+ // Preserve path structure with slashes
140
+ const normalized = normalizeKey(pathname, true);
141
+ baseKey = `datatable:${normalized}`;
142
+ }
143
+ }
144
+
145
+ // No stable key can be determined
146
+ if (!baseKey) {
147
+ return null;
148
+ }
149
+
150
+ // Scope by user ID if provided (prevents data leakage between users)
151
+ if (userId) {
152
+ return `${baseKey}:user:${userId}`;
153
+ }
154
+
155
+ return baseKey;
156
+ }
157
+
158
+ /**
159
+ * Props for Dialog key derivation
160
+ */
161
+ export interface DialogKeyProps {
162
+ /** Dialog title */
163
+ title?: string;
164
+ /** Dialog description (for fingerprint fallback) */
165
+ description?: string;
166
+ }
167
+
168
+ /**
169
+ * Derive a persistence key for Dialog component
170
+ *
171
+ * Priority order:
172
+ * 1. title prop (if stable and provided)
173
+ * 2. Route pathname + component type
174
+ * 3. Hashed fingerprint of dialog content structure
175
+ *
176
+ * @param props - Dialog props
177
+ * @param location - Optional location from useLocation() hook
178
+ * @param userId - Optional user ID to scope persistence by user
179
+ * @returns Derived key or null if no stable key can be determined
180
+ */
181
+ export function deriveDialogKey(
182
+ props: DialogKeyProps,
183
+ location?: { pathname: string } | null,
184
+ userId?: string | null
185
+ ): string | null {
186
+ let baseKey: string | null = null;
187
+
188
+ // Priority 1: title prop
189
+ if (props.title) {
190
+ baseKey = `dialog:${normalizeKey(props.title)}`;
191
+ }
192
+ // Priority 2: Route pathname + component type
193
+ else {
194
+ const pathname = location?.pathname || getRoutePathname();
195
+ if (pathname && pathname !== '/') {
196
+ // Preserve path structure with slashes
197
+ const normalized = normalizeKey(pathname, true);
198
+ baseKey = `dialog:${normalized}`;
199
+ }
200
+ // Priority 3: Hashed fingerprint of dialog structure
201
+ else {
202
+ const fingerprintParts: string[] = [];
203
+ if (props.description) {
204
+ fingerprintParts.push(props.description);
205
+ }
206
+
207
+ if (fingerprintParts.length > 0) {
208
+ const fingerprint = hashStableFingerprint(fingerprintParts);
209
+ baseKey = `dialog:${fingerprint}`;
210
+ }
211
+ }
212
+ }
213
+
214
+ // No stable key can be determined
215
+ if (!baseKey) {
216
+ return null;
217
+ }
218
+
219
+ // Scope by user ID if provided (prevents data leakage between users)
220
+ if (userId) {
221
+ return `${baseKey}:user:${userId}`;
222
+ }
223
+
224
+ return baseKey;
225
+ }
226
+
227
+ /**
228
+ * Props for Form key derivation
229
+ */
230
+ export interface FormKeyProps {
231
+ /** Form field names (for fingerprint fallback) */
232
+ fieldNames?: string[];
233
+ /** Parent dialog title (if form is inside dialog) */
234
+ parentDialogTitle?: string;
235
+ }
236
+
237
+ /**
238
+ * Derive a persistence key for Form component
239
+ *
240
+ * Priority order:
241
+ * 1. Parent Dialog's title (if inside Dialog)
242
+ * 2. Route pathname + form field names hash
243
+ * 3. Route pathname + component type
244
+ *
245
+ * @param props - Form props
246
+ * @param parentContext - Optional parent context (e.g., Dialog title)
247
+ * @param location - Optional location from useLocation() hook
248
+ * @param userId - Optional user ID to scope persistence by user
249
+ * @returns Derived key or null if no stable key can be determined
250
+ */
251
+ export function deriveFormKey(
252
+ props: FormKeyProps = {},
253
+ parentContext?: { dialogTitle?: string } | null,
254
+ location?: { pathname: string } | null,
255
+ userId?: string | null
256
+ ): string | null {
257
+ let baseKey: string | null = null;
258
+
259
+ // Priority 1: Parent Dialog's title
260
+ const dialogTitle = parentContext?.dialogTitle || props.parentDialogTitle;
261
+ if (dialogTitle) {
262
+ baseKey = `form:${normalizeKey(dialogTitle)}`;
263
+ }
264
+ // Priority 2: Route pathname + form field names hash
265
+ else {
266
+ const pathname = location?.pathname || getRoutePathname();
267
+ if (pathname && pathname !== '/' && props.fieldNames && props.fieldNames.length > 0) {
268
+ const fieldHash = hashStableFingerprint(props.fieldNames);
269
+ if (fieldHash) {
270
+ // Preserve path structure with slashes
271
+ const normalized = normalizeKey(pathname, true);
272
+ baseKey = `form:${normalized}:${fieldHash}`;
273
+ }
274
+ }
275
+ // Priority 3: Route pathname + component type
276
+ else if (pathname && pathname !== '/') {
277
+ // Preserve path structure with slashes
278
+ const normalized = normalizeKey(pathname, true);
279
+ baseKey = `form:${normalized}`;
280
+ }
281
+ }
282
+
283
+ // No stable key can be determined
284
+ if (!baseKey) {
285
+ return null;
286
+ }
287
+
288
+ // Scope by user ID if provided (prevents data leakage between users)
289
+ if (userId) {
290
+ return `${baseKey}:user:${userId}`;
291
+ }
292
+
293
+ return baseKey;
294
+ }
295
+
296
+ /**
297
+ * Create a stable hash from an array of values
298
+ * Exported for use in components that need custom fingerprinting
299
+ *
300
+ * @param values - Array of string values to hash
301
+ * @returns Stable hash string
302
+ */
303
+ export { hashStableFingerprint };
304
+