@jmruthers/pace-core 0.6.4 → 0.6.6

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 (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -10,6 +10,10 @@ import { renderHook, act } from '@testing-library/react';
10
10
  import { useDataTableState, dataTableReducer } from '../useDataTableState';
11
11
  import type { DataRecord, SortingState, ColumnFiltersState, VisibilityState, GroupingState, ExpandedState, PaginationState } from '../../types';
12
12
 
13
+ // Following testing standards: use timeout parameter to prevent hanging
14
+ // See: packages/core/docs/standards/04-testing-standards.md
15
+ const TEST_TIMEOUT = 10000; // 10 seconds per test
16
+
13
17
  interface TestData extends DataRecord {
14
18
  id: string;
15
19
  name: string;
@@ -51,7 +55,7 @@ describe('[unit] dataTableReducer', () => {
51
55
 
52
56
  expect(result.sorting).toEqual(newSorting);
53
57
  expect(result).toEqual({ ...initialState, sorting: newSorting });
54
- });
58
+ }, TEST_TIMEOUT);
55
59
 
56
60
  it('handles SET_COLUMN_FILTERS action', () => {
57
61
  const newFilters: ColumnFiltersState = [{ id: 'name', value: 'John' }];
@@ -60,7 +64,7 @@ describe('[unit] dataTableReducer', () => {
60
64
  const result = dataTableReducer(initialState, action);
61
65
 
62
66
  expect(result.columnFilters).toEqual(newFilters);
63
- });
67
+ }, TEST_TIMEOUT);
64
68
 
65
69
  it('handles SET_COLUMN_VISIBILITY action', () => {
66
70
  const newVisibility: VisibilityState = { name: false, email: true };
@@ -69,7 +73,7 @@ describe('[unit] dataTableReducer', () => {
69
73
  const result = dataTableReducer(initialState, action);
70
74
 
71
75
  expect(result.columnVisibility).toEqual(newVisibility);
72
- });
76
+ }, TEST_TIMEOUT);
73
77
 
74
78
  it('handles SET_GROUPING action', () => {
75
79
  const newGrouping: GroupingState = ['name'];
@@ -78,7 +82,7 @@ describe('[unit] dataTableReducer', () => {
78
82
  const result = dataTableReducer(initialState, action);
79
83
 
80
84
  expect(result.grouping).toEqual(newGrouping);
81
- });
85
+ }, TEST_TIMEOUT);
82
86
 
83
87
  it('handles SET_EXPANDED action', () => {
84
88
  const newExpanded: ExpandedState = { '1': true };
@@ -87,7 +91,7 @@ describe('[unit] dataTableReducer', () => {
87
91
  const result = dataTableReducer(initialState, action);
88
92
 
89
93
  expect(result.expanded).toEqual(newExpanded);
90
- });
94
+ }, TEST_TIMEOUT);
91
95
 
92
96
  it('handles SET_PAGINATION action', () => {
93
97
  const newPagination: PaginationState = { pageIndex: 2, pageSize: 25 };
@@ -96,7 +100,7 @@ describe('[unit] dataTableReducer', () => {
96
100
  const result = dataTableReducer(initialState, action);
97
101
 
98
102
  expect(result.pagination).toEqual(newPagination);
99
- });
103
+ }, TEST_TIMEOUT);
100
104
 
101
105
  it('handles SET_COLUMN_ORDER action', () => {
102
106
  const newOrder = ['name', 'id', 'email'];
@@ -105,7 +109,7 @@ describe('[unit] dataTableReducer', () => {
105
109
  const result = dataTableReducer(initialState, action);
106
110
 
107
111
  expect(result.columnOrder).toEqual(newOrder);
108
- });
112
+ }, TEST_TIMEOUT);
109
113
 
110
114
  it('handles SET_ROW_SELECTION action', () => {
111
115
  const newSelection = { '1': true, '2': false };
@@ -114,7 +118,7 @@ describe('[unit] dataTableReducer', () => {
114
118
  const result = dataTableReducer(initialState, action);
115
119
 
116
120
  expect(result.rowSelection).toEqual(newSelection);
117
- });
121
+ }, TEST_TIMEOUT);
118
122
 
119
123
  it('handles TOGGLE_ROW_SELECTION action', () => {
120
124
  const initialStateWithSelection = { ...initialState, rowSelection: { '1': true } };
@@ -123,7 +127,7 @@ describe('[unit] dataTableReducer', () => {
123
127
  const result = dataTableReducer(initialStateWithSelection, action);
124
128
 
125
129
  expect(result.rowSelection).toEqual({ '1': true, '2': true });
126
- });
130
+ }, TEST_TIMEOUT);
127
131
 
128
132
  it('handles CLEAR_ROW_SELECTION action', () => {
129
133
  const initialStateWithSelection = { ...initialState, rowSelection: { '1': true, '2': true } };
@@ -132,7 +136,7 @@ describe('[unit] dataTableReducer', () => {
132
136
  const result = dataTableReducer(initialStateWithSelection, action);
133
137
 
134
138
  expect(result.rowSelection).toEqual({});
135
- });
139
+ }, TEST_TIMEOUT);
136
140
 
137
141
  it('handles SET_CREATING action', () => {
138
142
  const action = { type: 'SET_CREATING' as const, payload: true };
@@ -140,7 +144,7 @@ describe('[unit] dataTableReducer', () => {
140
144
  const result = dataTableReducer(initialState, action);
141
145
 
142
146
  expect(result.isCreating).toBe(true);
143
- });
147
+ }, TEST_TIMEOUT);
144
148
 
145
149
  it('handles SET_CREATION_DATA action', () => {
146
150
  const creationData = { name: 'New User', email: 'new@example.com' };
@@ -149,7 +153,7 @@ describe('[unit] dataTableReducer', () => {
149
153
  const result = dataTableReducer(initialState, action);
150
154
 
151
155
  expect(result.creationData).toEqual(creationData);
152
- });
156
+ }, TEST_TIMEOUT);
153
157
 
154
158
  it('handles CLEAR_CREATION_DATA action', () => {
155
159
  const initialStateWithCreationData = { ...initialState, creationData: { name: 'Test' } };
@@ -158,7 +162,7 @@ describe('[unit] dataTableReducer', () => {
158
162
  const result = dataTableReducer(initialStateWithCreationData, action);
159
163
 
160
164
  expect(result.creationData).toEqual({});
161
- });
165
+ }, TEST_TIMEOUT);
162
166
 
163
167
  it('handles SET_EDITING_ROW action', () => {
164
168
  const editingData = { rowId: '1', data: { name: 'Updated Name' } };
@@ -168,7 +172,7 @@ describe('[unit] dataTableReducer', () => {
168
172
 
169
173
  expect(result.editingRowId).toBe('1');
170
174
  expect(result.editingData).toEqual({ name: 'Updated Name' });
171
- });
175
+ }, TEST_TIMEOUT);
172
176
 
173
177
  it('handles CLEAR_EDITING action', () => {
174
178
  const initialStateWithEditing = {
@@ -182,7 +186,7 @@ describe('[unit] dataTableReducer', () => {
182
186
 
183
187
  expect(result.editingRowId).toBe(null);
184
188
  expect(result.editingData).toEqual({});
185
- });
189
+ }, TEST_TIMEOUT);
186
190
 
187
191
  it('handles SET_IMPORT_MODAL action', () => {
188
192
  const action = { type: 'SET_IMPORT_MODAL' as const, payload: true };
@@ -190,7 +194,7 @@ describe('[unit] dataTableReducer', () => {
190
194
  const result = dataTableReducer(initialState, action);
191
195
 
192
196
  expect(result.showImportModal).toBe(true);
193
- });
197
+ }, TEST_TIMEOUT);
194
198
 
195
199
  it('handles SET_FILTER_ROW action', () => {
196
200
  const action = { type: 'SET_FILTER_ROW' as const, payload: true };
@@ -198,7 +202,7 @@ describe('[unit] dataTableReducer', () => {
198
202
  const result = dataTableReducer(initialState, action);
199
203
 
200
204
  expect(result.showFilterRow).toBe(true);
201
- });
205
+ }, TEST_TIMEOUT);
202
206
 
203
207
  it('handles SET_SEARCH_QUERY action', () => {
204
208
  const action = { type: 'SET_SEARCH_QUERY' as const, payload: 'search term' };
@@ -206,7 +210,7 @@ describe('[unit] dataTableReducer', () => {
206
210
  const result = dataTableReducer(initialState, action);
207
211
 
208
212
  expect(result.searchQuery).toBe('search term');
209
- });
213
+ }, TEST_TIMEOUT);
210
214
 
211
215
  it('handles RESET_STATE action', () => {
212
216
  const modifiedState = {
@@ -229,7 +233,7 @@ describe('[unit] dataTableReducer', () => {
229
233
  expect(result.rowSelection).toEqual({});
230
234
  expect(result.isCreating).toBe(false);
231
235
  expect(result.searchQuery).toBe('');
232
- });
236
+ }, TEST_TIMEOUT);
233
237
 
234
238
  it('handles INITIALIZE_STATE action', () => {
235
239
  const partialState = {
@@ -244,7 +248,7 @@ describe('[unit] dataTableReducer', () => {
244
248
  expect(result.sorting).toEqual(partialState.sorting);
245
249
  expect(result.searchQuery).toBe(partialState.searchQuery);
246
250
  expect(result.columnFilters).toEqual(initialState.columnFilters); // Should remain unchanged
247
- });
251
+ }, TEST_TIMEOUT);
248
252
 
249
253
  it('returns same state for unknown action', () => {
250
254
  const unknownAction = { type: 'UNKNOWN_ACTION' as any, payload: null };
@@ -252,7 +256,7 @@ describe('[unit] dataTableReducer', () => {
252
256
  const result = dataTableReducer(initialState, unknownAction);
253
257
 
254
258
  expect(result).toBe(initialState);
255
- });
259
+ }, TEST_TIMEOUT);
256
260
  });
257
261
 
258
262
  describe('[unit] useDataTableState', () => {
@@ -274,7 +278,7 @@ describe('[unit] useDataTableState', () => {
274
278
  expect(result.current.state.showImportModal).toBe(false);
275
279
  expect(result.current.state.showFilterRow).toBe(false);
276
280
  expect(result.current.state.searchQuery).toBe('');
277
- });
281
+ }, TEST_TIMEOUT);
278
282
 
279
283
  it('initializes with custom options', () => {
280
284
  const options = {
@@ -288,7 +292,7 @@ describe('[unit] useDataTableState', () => {
288
292
  expect(result.current.state.pagination.pageSize).toBe(25);
289
293
  expect(result.current.state.columnOrder).toEqual(['id', 'name', 'email']);
290
294
  expect(result.current.state.rowSelection).toEqual({ '1': true });
291
- });
295
+ }, TEST_TIMEOUT);
292
296
 
293
297
  it('provides action creators', () => {
294
298
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -312,7 +316,7 @@ describe('[unit] useDataTableState', () => {
312
316
  expect(typeof result.current.actions.setFilterRow).toBe('function');
313
317
  expect(typeof result.current.actions.setSearchQuery).toBe('function');
314
318
  expect(typeof result.current.actions.resetState).toBe('function');
315
- });
319
+ }, TEST_TIMEOUT);
316
320
 
317
321
  it('updates state when actions are called', () => {
318
322
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -322,7 +326,7 @@ describe('[unit] useDataTableState', () => {
322
326
  });
323
327
 
324
328
  expect(result.current.state.sorting).toEqual([{ id: 'name', desc: false }]);
325
- });
329
+ }, TEST_TIMEOUT);
326
330
 
327
331
  it('updates column filters', () => {
328
332
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -332,7 +336,7 @@ describe('[unit] useDataTableState', () => {
332
336
  });
333
337
 
334
338
  expect(result.current.state.columnFilters).toEqual([{ id: 'name', value: 'John' }]);
335
- });
339
+ }, TEST_TIMEOUT);
336
340
 
337
341
  it('updates column visibility', () => {
338
342
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -342,7 +346,7 @@ describe('[unit] useDataTableState', () => {
342
346
  });
343
347
 
344
348
  expect(result.current.state.columnVisibility).toEqual({ name: false, email: true });
345
- });
349
+ }, TEST_TIMEOUT);
346
350
 
347
351
  it('updates grouping', () => {
348
352
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -352,7 +356,7 @@ describe('[unit] useDataTableState', () => {
352
356
  });
353
357
 
354
358
  expect(result.current.state.grouping).toEqual(['name']);
355
- });
359
+ }, TEST_TIMEOUT);
356
360
 
357
361
  it('updates expanded state', () => {
358
362
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -362,7 +366,7 @@ describe('[unit] useDataTableState', () => {
362
366
  });
363
367
 
364
368
  expect(result.current.state.expanded).toEqual({ '1': true });
365
- });
369
+ }, TEST_TIMEOUT);
366
370
 
367
371
  it('updates pagination', () => {
368
372
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -372,7 +376,7 @@ describe('[unit] useDataTableState', () => {
372
376
  });
373
377
 
374
378
  expect(result.current.state.pagination).toEqual({ pageIndex: 2, pageSize: 25 });
375
- });
379
+ }, TEST_TIMEOUT);
376
380
 
377
381
  it('updates column order', () => {
378
382
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -382,7 +386,7 @@ describe('[unit] useDataTableState', () => {
382
386
  });
383
387
 
384
388
  expect(result.current.state.columnOrder).toEqual(['name', 'id', 'email']);
385
- });
389
+ }, TEST_TIMEOUT);
386
390
 
387
391
  it('updates row selection', () => {
388
392
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -392,7 +396,7 @@ describe('[unit] useDataTableState', () => {
392
396
  });
393
397
 
394
398
  expect(result.current.state.rowSelection).toEqual({ '1': true, '2': false });
395
- });
399
+ }, TEST_TIMEOUT);
396
400
 
397
401
  it('toggles row selection', () => {
398
402
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -408,7 +412,7 @@ describe('[unit] useDataTableState', () => {
408
412
  });
409
413
 
410
414
  expect(result.current.state.rowSelection).toEqual({ '1': false });
411
- });
415
+ }, TEST_TIMEOUT);
412
416
 
413
417
  it('clears row selection', () => {
414
418
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -424,7 +428,7 @@ describe('[unit] useDataTableState', () => {
424
428
  });
425
429
 
426
430
  expect(result.current.state.rowSelection).toEqual({});
427
- });
431
+ }, TEST_TIMEOUT);
428
432
 
429
433
  it('updates creating state', () => {
430
434
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -434,7 +438,7 @@ describe('[unit] useDataTableState', () => {
434
438
  });
435
439
 
436
440
  expect(result.current.state.isCreating).toBe(true);
437
- });
441
+ }, TEST_TIMEOUT);
438
442
 
439
443
  it('updates creation data', () => {
440
444
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -445,7 +449,7 @@ describe('[unit] useDataTableState', () => {
445
449
  });
446
450
 
447
451
  expect(result.current.state.creationData).toEqual(creationData);
448
- });
452
+ }, TEST_TIMEOUT);
449
453
 
450
454
  it('clears creation data', () => {
451
455
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -461,7 +465,7 @@ describe('[unit] useDataTableState', () => {
461
465
  });
462
466
 
463
467
  expect(result.current.state.creationData).toEqual({});
464
- });
468
+ }, TEST_TIMEOUT);
465
469
 
466
470
  it('updates editing row', () => {
467
471
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -472,7 +476,7 @@ describe('[unit] useDataTableState', () => {
472
476
 
473
477
  expect(result.current.state.editingRowId).toBe('1');
474
478
  expect(result.current.state.editingData).toEqual({ name: 'Updated Name' });
475
- });
479
+ }, TEST_TIMEOUT);
476
480
 
477
481
  it('clears editing state', () => {
478
482
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -489,7 +493,7 @@ describe('[unit] useDataTableState', () => {
489
493
 
490
494
  expect(result.current.state.editingRowId).toBe(null);
491
495
  expect(result.current.state.editingData).toEqual({});
492
- });
496
+ }, TEST_TIMEOUT);
493
497
 
494
498
  it('updates import modal state', () => {
495
499
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -499,7 +503,7 @@ describe('[unit] useDataTableState', () => {
499
503
  });
500
504
 
501
505
  expect(result.current.state.showImportModal).toBe(true);
502
- });
506
+ }, TEST_TIMEOUT);
503
507
 
504
508
  it('updates filter row state', () => {
505
509
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -509,7 +513,7 @@ describe('[unit] useDataTableState', () => {
509
513
  });
510
514
 
511
515
  expect(result.current.state.showFilterRow).toBe(true);
512
- });
516
+ }, TEST_TIMEOUT);
513
517
 
514
518
  it('updates search query', () => {
515
519
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -519,7 +523,7 @@ describe('[unit] useDataTableState', () => {
519
523
  });
520
524
 
521
525
  expect(result.current.state.searchQuery).toBe('search term');
522
- });
526
+ }, TEST_TIMEOUT);
523
527
 
524
528
  it('resets state to initial values', () => {
525
529
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -546,7 +550,7 @@ describe('[unit] useDataTableState', () => {
546
550
  expect(result.current.state.columnFilters).toEqual([]);
547
551
  expect(result.current.state.rowSelection).toEqual({});
548
552
  expect(result.current.state.searchQuery).toBe('');
549
- });
553
+ }, TEST_TIMEOUT);
550
554
 
551
555
  it('calls onRowSelectionChange callback when provided', () => {
552
556
  const onRowSelectionChange = vi.fn();
@@ -559,20 +563,20 @@ describe('[unit] useDataTableState', () => {
559
563
  });
560
564
 
561
565
  expect(onRowSelectionChange).toHaveBeenCalledWith({ '1': true });
562
- });
566
+ }, TEST_TIMEOUT);
563
567
 
564
568
  it('provides computed values', () => {
565
569
  const { result } = renderHook(() => useDataTableState<TestData>());
566
570
 
567
571
  expect(result.current.computed).toBeDefined();
568
572
  expect(typeof result.current.computed).toBe('object');
569
- });
573
+ }, TEST_TIMEOUT);
570
574
 
571
575
  it('provides clearFilters function', () => {
572
576
  const { result } = renderHook(() => useDataTableState<TestData>());
573
577
 
574
578
  expect(typeof result.current.clearFilters).toBe('function');
575
- });
579
+ }, TEST_TIMEOUT);
576
580
 
577
581
  it('maintains state consistency across multiple actions', () => {
578
582
  const { result } = renderHook(() => useDataTableState<TestData>());
@@ -588,5 +592,5 @@ describe('[unit] useDataTableState', () => {
588
592
  expect(result.current.state.columnFilters).toEqual([{ id: 'name', value: 'John' }]);
589
593
  expect(result.current.state.pagination).toEqual({ pageIndex: 1, pageSize: 25 });
590
594
  expect(result.current.state.rowSelection).toEqual({ '1': true, '2': false });
591
- });
595
+ }, TEST_TIMEOUT);
592
596
  });
@@ -100,14 +100,7 @@ export function useDataTablePermissions<TData extends DataRecord>(
100
100
  }, 5000);
101
101
 
102
102
  try {
103
- logger.debug('useDataTablePermissions', 'Starting super admin check', { userId: user?.id });
104
103
  const superAdminStatus = await isSuperAdmin(user.id);
105
- const elapsed = Date.now() - startTime;
106
- logger.debug('useDataTablePermissions', 'Super admin check completed', {
107
- userId: user?.id,
108
- isSuperAdmin: superAdminStatus,
109
- elapsedMs: elapsed,
110
- });
111
104
  setIsSuperAdminUser(superAdminStatus);
112
105
  } catch (error) {
113
106
  const elapsed = Date.now() - startTime;
@@ -159,7 +152,8 @@ export function useDataTablePermissions<TData extends DataRecord>(
159
152
  const { resolvedScope: rawResolvedScope, isLoading: scopeLoading } = useResolvedScope({
160
153
  supabase,
161
154
  selectedOrganisationId: selectedOrganisation?.id || null,
162
- selectedEventId: selectedEvent?.event_id || null
155
+ selectedEventId: selectedEvent?.event_id || null,
156
+ selectedEventOrganisationId: selectedEvent?.organisation_id || null
163
157
  });
164
158
 
165
159
  /**
@@ -277,24 +271,33 @@ export function useDataTablePermissions<TData extends DataRecord>(
277
271
  // to maintain consistent API with useCan return type
278
272
  // React 19 fix: Read isLoading directly from result objects to ensure we get the latest state
279
273
  // instead of relying on memoized object references which may not update properly in React 19
274
+ // CRITICAL FIX: When isSuperAdminUser is true, immediately grant all permissions without waiting
275
+ // for useCan results. This ensures super admins can use DataTable features immediately.
280
276
  const permissions = useMemo(() => {
281
277
  // Helper to create a permission result that bypasses for super admins
282
278
  // If super admin check is still loading, we show loading state to prevent premature "Access Denied"
283
279
  // Once super admin check completes, if user is super admin, all permissions are true
284
280
  // Otherwise, use the normal permission check results
285
- const createSuperAdminAwarePermission = (result: ReturnType<typeof useCan>) => ({
286
- // If super admin check completed and user is super admin, grant all permissions
287
- // and mark loading as complete for DataTable gating purposes.
288
- // This is not a "hack": super admins *semantically* bypass permission checks, so the
289
- // table must not remain blocked behind background permission queries.
290
- can: isSuperAdminUser === true ? true : result.can,
291
- isLoading:
292
- isSuperAdminUser === true
293
- ? false
294
- : (isSuperAdminUser === null && isCheckingSuperAdmin) || result.isLoading,
295
- error: isSuperAdminUser === true ? null : result.error,
296
- refetch: result.refetch,
297
- });
281
+ const createSuperAdminAwarePermission = (result: ReturnType<typeof useCan>) => {
282
+ // CRITICAL: If super admin is confirmed, immediately grant permission
283
+ // Don't wait for useCan results - super admins bypass all checks
284
+ if (isSuperAdminUser === true) {
285
+ return {
286
+ can: true,
287
+ isLoading: false,
288
+ error: null,
289
+ refetch: result.refetch,
290
+ };
291
+ }
292
+
293
+ // For non-super-admins or while checking, use normal permission results
294
+ return {
295
+ can: result.can,
296
+ isLoading: (isSuperAdminUser === null && isCheckingSuperAdmin) || result.isLoading,
297
+ error: result.error,
298
+ refetch: result.refetch,
299
+ };
300
+ };
298
301
 
299
302
  return {
300
303
  canRead: createSuperAdminAwarePermission(canReadResult),
@@ -8,7 +8,7 @@
8
8
  * This replaces the scattered useState calls with a single, predictable state management system.
9
9
  */
10
10
 
11
- import { useReducer, useCallback, useMemo } from 'react';
11
+ import { useReducer, useCallback, useMemo, useEffect } from 'react';
12
12
  import type {
13
13
  SortingState,
14
14
  ColumnFiltersState,
@@ -18,6 +18,10 @@ import type {
18
18
  PaginationState
19
19
  } from '@tanstack/react-table';
20
20
  import type { DataRecord, CellValue, RowId } from '../types';
21
+ import { useSessionDraft } from '../../../hooks/useSessionDraft';
22
+ import { deriveDataTableKey } from '../../../utils/persistence/keyDerivation';
23
+ import { filterSensitiveFields } from '../../../utils/persistence/sensitiveFieldDetection';
24
+ import { useUnifiedAuth } from '../../../providers/services/UnifiedAuthProvider';
21
25
 
22
26
  // Re-export types for external use
23
27
  export type { DataRecord, CellValue, RowId };
@@ -265,6 +269,11 @@ export interface UseDataTableStateOptions<TData extends DataRecord> {
265
269
  onRowSelectionChange?: (selection: Record<string, boolean>) => void;
266
270
  defaultSorting?: SortingState;
267
271
  defaultGrouping?: GroupingState;
272
+ // Persistence options
273
+ rbacPageId?: string;
274
+ title?: string;
275
+ location?: { pathname: string } | null;
276
+ columnFieldNames?: string[]; // Field names for sensitive field filtering
268
277
  }
269
278
 
270
279
  export function useDataTableState<TData extends DataRecord>({
@@ -273,18 +282,78 @@ export function useDataTableState<TData extends DataRecord>({
273
282
  initialRowSelection = {},
274
283
  onRowSelectionChange,
275
284
  defaultSorting,
276
- defaultGrouping
285
+ defaultGrouping,
286
+ rbacPageId,
287
+ title,
288
+ location,
289
+ columnFieldNames = [],
277
290
  }: UseDataTableStateOptions<TData> = {}) {
278
291
 
279
- const initialState = useMemo(() =>
292
+ // Call all hooks unconditionally at the top level
293
+ // Hooks must be called in the same order on every render
294
+ // Get user ID for scoping persistence (prevents data leakage between users)
295
+ // If provider is missing, it will throw - errors should be handled by error boundaries
296
+ const auth = useUnifiedAuth();
297
+ const userId = auth.user?.id || null;
298
+
299
+ const persistenceKey = useMemo(() => {
300
+ return deriveDataTableKey(
301
+ {
302
+ rbacPageId,
303
+ title,
304
+ columnIds,
305
+ },
306
+ location,
307
+ userId
308
+ );
309
+ }, [rbacPageId, title, columnIds, location, userId]);
310
+
311
+ // Create initial state
312
+ const baseInitialState = useMemo(() =>
280
313
  createInitialState<TData>(initialPageSize, columnIds, defaultSorting, defaultGrouping),
281
314
  [initialPageSize, columnIds, defaultSorting, defaultGrouping]
282
315
  );
283
316
 
284
- const [state, dispatch] = useReducer(dataTableReducer<TData>, {
285
- ...initialState,
286
- rowSelection: initialRowSelection
287
- });
317
+ // Define state subset to persist (excluding sensitive fields from editing/creation data)
318
+ type PersistedState = Omit<DataTableState<TData>, 'editingData' | 'creationData'> & {
319
+ editingData: Partial<Record<string, CellValue>>;
320
+ creationData: Partial<Record<string, CellValue>>;
321
+ };
322
+
323
+ // Use session draft for persistence
324
+ const { state: persistedState, setState: setPersistedState, clearDraft } = useSessionDraft<PersistedState>(
325
+ persistenceKey || 'datatable:no-key',
326
+ {
327
+ ...baseInitialState,
328
+ rowSelection: initialRowSelection,
329
+ editingData: {},
330
+ creationData: {},
331
+ } as PersistedState,
332
+ {
333
+ enabled: Boolean(persistenceKey),
334
+ debounceMs: 300,
335
+ }
336
+ );
337
+
338
+ // Merge persisted state with initial state (persisted takes precedence)
339
+ const initialState = useMemo(() => {
340
+ if (!persistenceKey || !persistedState) {
341
+ return {
342
+ ...baseInitialState,
343
+ rowSelection: initialRowSelection,
344
+ };
345
+ }
346
+
347
+ // Merge persisted state with initial state
348
+ return {
349
+ ...baseInitialState,
350
+ ...persistedState,
351
+ // Ensure rowSelection is merged properly
352
+ rowSelection: persistedState.rowSelection || initialRowSelection,
353
+ };
354
+ }, [baseInitialState, persistedState, initialRowSelection, persistenceKey]);
355
+
356
+ const [state, dispatch] = useReducer(dataTableReducer<TData>, initialState);
288
357
 
289
358
  // ============================================================================
290
359
  // ACTION CREATORS
@@ -384,10 +453,57 @@ export function useDataTableState<TData extends DataRecord>({
384
453
  dispatch({ type: 'SET_SEARCH_QUERY', payload: '' });
385
454
  }, []);
386
455
 
456
+ // Persist state changes (debounced)
457
+ useEffect(() => {
458
+ if (!persistenceKey) {
459
+ return;
460
+ }
461
+
462
+ // Filter sensitive fields from editingData and creationData
463
+ const filteredEditingData = columnFieldNames.length > 0
464
+ ? filterSensitiveFields(state.editingData, columnFieldNames)
465
+ : state.editingData;
466
+
467
+ const filteredCreationData = columnFieldNames.length > 0
468
+ ? filterSensitiveFields(state.creationData, columnFieldNames)
469
+ : state.creationData;
470
+
471
+ // Persist state subset
472
+ const stateToPersist: PersistedState = {
473
+ sorting: state.sorting,
474
+ columnFilters: state.columnFilters,
475
+ columnVisibility: state.columnVisibility,
476
+ grouping: state.grouping,
477
+ expanded: state.expanded,
478
+ pagination: state.pagination,
479
+ columnOrder: state.columnOrder,
480
+ rowSelection: state.rowSelection,
481
+ isCreating: state.isCreating,
482
+ creationData: filteredCreationData,
483
+ editingRowId: state.editingRowId,
484
+ editingData: filteredEditingData,
485
+ showImportModal: state.showImportModal,
486
+ showFilterRow: state.showFilterRow,
487
+ searchQuery: state.searchQuery,
488
+ };
489
+
490
+ setPersistedState(stateToPersist);
491
+ }, [
492
+ state,
493
+ persistenceKey,
494
+ columnFieldNames,
495
+ setPersistedState,
496
+ ]);
497
+
498
+ // Enhanced actions - no need to modify, persistence happens automatically via useEffect
499
+ const enhancedActions = actions;
500
+
387
501
  return {
388
502
  state,
389
- actions,
503
+ actions: enhancedActions,
390
504
  computed,
391
- clearFilters
505
+ clearFilters,
506
+ // Expose clearDraft for external use (e.g., on successful submit)
507
+ clearDraft: persistenceKey ? clearDraft : undefined,
392
508
  };
393
509
  }