@jmruthers/pace-core 0.5.191 → 0.6.1

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 (380) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +372 -0
  4. package/cursor-rules/01-standards-compliance.mdc +275 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +341 -0
  7. package/cursor-rules/04-testing-standards.mdc +315 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +392 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
  11. package/cursor-rules/CHANGELOG.md +101 -0
  12. package/cursor-rules/README.md +191 -0
  13. package/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
  14. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
  15. package/dist/{DataTable-WKRZD47S.js → DataTable-DQ7RSOHE.js} +8 -7
  16. package/dist/{PublicPageProvider-ULXC_u6U.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +37 -156
  17. package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
  18. package/dist/{UnifiedAuthProvider-FTSG5XH7.js → UnifiedAuthProvider-ATAP5UTR.js} +3 -3
  19. package/dist/{api-IHKALJZD.js → api-N774RPUA.js} +2 -2
  20. package/dist/{chunk-6C4YBBJM.js → chunk-3QRJFVBR.js} +1 -1
  21. package/dist/chunk-3QRJFVBR.js.map +1 -0
  22. package/dist/{chunk-OETXORNB.js → chunk-3XTALGJF.js} +211 -136
  23. package/dist/chunk-3XTALGJF.js.map +1 -0
  24. package/dist/{chunk-6TQDD426.js → chunk-4N5C5XZU.js} +47 -228
  25. package/dist/chunk-4N5C5XZU.js.map +1 -0
  26. package/dist/{chunk-LOMZXPSN.js → chunk-4ZC4GX36.js} +47 -74
  27. package/dist/chunk-4ZC4GX36.js.map +1 -0
  28. package/dist/{chunk-6LTQQAT6.js → chunk-BYFSK72L.js} +357 -158
  29. package/dist/chunk-BYFSK72L.js.map +1 -0
  30. package/dist/{chunk-XYXSXPUK.js → chunk-EXUD6RNJ.js} +50 -10
  31. package/dist/chunk-EXUD6RNJ.js.map +1 -0
  32. package/dist/{chunk-VKB2CO4Z.js → chunk-GLK6VM3F.js} +244 -249
  33. package/dist/chunk-GLK6VM3F.js.map +1 -0
  34. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  35. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  36. package/dist/{chunk-XNYQOL3Z.js → chunk-JBKQ3SAO.js} +9 -18
  37. package/dist/chunk-JBKQ3SAO.js.map +1 -0
  38. package/dist/{chunk-ROXMHMY2.js → chunk-KNC55RTG.js} +13 -3
  39. package/dist/{chunk-ROXMHMY2.js.map → chunk-KNC55RTG.js.map} +1 -1
  40. package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
  41. package/dist/{chunk-ULHIJK66.js → chunk-T33XF5ZC.js} +255 -140
  42. package/dist/chunk-T33XF5ZC.js.map +1 -0
  43. package/dist/{chunk-VRGWKHDB.js → chunk-XM25TVIE.js} +100 -33
  44. package/dist/chunk-XM25TVIE.js.map +1 -0
  45. package/dist/components.d.ts +4 -4
  46. package/dist/components.js +9 -9
  47. package/dist/hooks.d.ts +6 -6
  48. package/dist/hooks.js +20 -25
  49. package/dist/hooks.js.map +1 -1
  50. package/dist/index.d.ts +11 -11
  51. package/dist/index.js +18 -21
  52. package/dist/index.js.map +1 -1
  53. package/dist/providers.d.ts +3 -3
  54. package/dist/providers.js +2 -2
  55. package/dist/rbac/index.d.ts +2 -20
  56. package/dist/rbac/index.js +7 -9
  57. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
  58. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  59. package/dist/utils.d.ts +1 -1
  60. package/dist/utils.js +3 -3
  61. package/docs/api/classes/ColumnFactory.md +1 -1
  62. package/docs/api/classes/ErrorBoundary.md +1 -1
  63. package/docs/api/classes/InvalidScopeError.md +1 -1
  64. package/docs/api/classes/Logger.md +1 -1
  65. package/docs/api/classes/MissingUserContextError.md +1 -1
  66. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  67. package/docs/api/classes/PermissionDeniedError.md +2 -2
  68. package/docs/api/classes/RBACAuditManager.md +2 -2
  69. package/docs/api/classes/RBACCache.md +1 -1
  70. package/docs/api/classes/RBACEngine.md +2 -2
  71. package/docs/api/classes/RBACError.md +1 -1
  72. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  73. package/docs/api/classes/SecureSupabaseClient.md +10 -10
  74. package/docs/api/classes/StorageUtils.md +1 -1
  75. package/docs/api/enums/FileCategory.md +1 -1
  76. package/docs/api/enums/LogLevel.md +1 -1
  77. package/docs/api/enums/RBACErrorCode.md +1 -1
  78. package/docs/api/enums/RPCFunction.md +1 -1
  79. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  80. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +1 -1
  82. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  83. package/docs/api/interfaces/AvatarProps.md +1 -1
  84. package/docs/api/interfaces/BadgeProps.md +1 -1
  85. package/docs/api/interfaces/ButtonProps.md +1 -1
  86. package/docs/api/interfaces/CalendarProps.md +1 -1
  87. package/docs/api/interfaces/CardProps.md +1 -1
  88. package/docs/api/interfaces/ColorPalette.md +1 -1
  89. package/docs/api/interfaces/ColorShade.md +1 -1
  90. package/docs/api/interfaces/ComplianceResult.md +1 -1
  91. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  92. package/docs/api/interfaces/DataRecord.md +1 -1
  93. package/docs/api/interfaces/DataTableAction.md +1 -1
  94. package/docs/api/interfaces/DataTableColumn.md +1 -1
  95. package/docs/api/interfaces/DataTableProps.md +1 -1
  96. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  97. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  98. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  99. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  100. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  101. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  102. package/docs/api/interfaces/ExportColumn.md +1 -1
  103. package/docs/api/interfaces/ExportOptions.md +1 -1
  104. package/docs/api/interfaces/FileDisplayProps.md +24 -11
  105. package/docs/api/interfaces/FileMetadata.md +1 -1
  106. package/docs/api/interfaces/FileReference.md +1 -1
  107. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  108. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  109. package/docs/api/interfaces/FileUploadProps.md +1 -1
  110. package/docs/api/interfaces/FooterProps.md +1 -1
  111. package/docs/api/interfaces/FormFieldProps.md +1 -1
  112. package/docs/api/interfaces/FormProps.md +1 -1
  113. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  114. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  115. package/docs/api/interfaces/InputProps.md +1 -1
  116. package/docs/api/interfaces/LabelProps.md +1 -1
  117. package/docs/api/interfaces/LoggerConfig.md +1 -1
  118. package/docs/api/interfaces/LoginFormProps.md +1 -1
  119. package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
  120. package/docs/api/interfaces/NavigationContextType.md +1 -1
  121. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  122. package/docs/api/interfaces/NavigationItem.md +1 -1
  123. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  124. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  125. package/docs/api/interfaces/Organisation.md +1 -1
  126. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  127. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  128. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  129. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  130. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  131. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  132. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  133. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  134. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  135. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  136. package/docs/api/interfaces/PaletteData.md +1 -1
  137. package/docs/api/interfaces/ParsedAddress.md +1 -1
  138. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  139. package/docs/api/interfaces/ProgressProps.md +1 -1
  140. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  141. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  142. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  143. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  144. package/docs/api/interfaces/QuickFix.md +1 -1
  145. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  146. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  147. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  148. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  149. package/docs/api/interfaces/RBACConfig.md +2 -2
  150. package/docs/api/interfaces/RBACContext.md +1 -1
  151. package/docs/api/interfaces/RBACLogger.md +1 -1
  152. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  153. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  154. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  155. package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
  156. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  157. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  158. package/docs/api/interfaces/RBACResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
  160. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  161. package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
  162. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  163. package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
  164. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  165. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  166. package/docs/api/interfaces/RBACRolesListResult.md +2 -2
  167. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  168. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  169. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  170. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  171. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  172. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  173. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  174. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  175. package/docs/api/interfaces/RouteConfig.md +2 -2
  176. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  177. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  178. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  179. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  180. package/docs/api/interfaces/SetupIssue.md +1 -1
  181. package/docs/api/interfaces/StorageConfig.md +1 -1
  182. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  183. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  184. package/docs/api/interfaces/StorageListOptions.md +1 -1
  185. package/docs/api/interfaces/StorageListResult.md +1 -1
  186. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  187. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  188. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  189. package/docs/api/interfaces/StyleImport.md +1 -1
  190. package/docs/api/interfaces/SwitchProps.md +1 -1
  191. package/docs/api/interfaces/TabsContentProps.md +1 -1
  192. package/docs/api/interfaces/TabsListProps.md +1 -1
  193. package/docs/api/interfaces/TabsProps.md +1 -1
  194. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  195. package/docs/api/interfaces/TextareaProps.md +1 -1
  196. package/docs/api/interfaces/ToastActionElement.md +1 -1
  197. package/docs/api/interfaces/ToastProps.md +1 -1
  198. package/docs/api/interfaces/UnifiedAuthContextType.md +60 -38
  199. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  200. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  201. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  202. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  203. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  205. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  207. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  208. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  209. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  210. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  211. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  212. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  213. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  214. package/docs/api/interfaces/UserEventAccess.md +1 -1
  215. package/docs/api/interfaces/UserMenuProps.md +1 -1
  216. package/docs/api/interfaces/UserProfile.md +1 -1
  217. package/docs/api/modules.md +194 -209
  218. package/docs/getting-started/cursor-rules.md +262 -0
  219. package/docs/getting-started/installation-guide.md +6 -1
  220. package/docs/getting-started/quick-start.md +6 -1
  221. package/docs/migration/MIGRATION_GUIDE.md +4 -4
  222. package/docs/migration/REACT_19_MIGRATION.md +227 -0
  223. package/docs/migration/database-changes-december-2025.md +2 -1
  224. package/docs/rbac/event-based-apps.md +124 -6
  225. package/docs/standards/README.md +39 -0
  226. package/docs/troubleshooting/migration.md +4 -4
  227. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  228. package/package.json +11 -6
  229. package/scripts/audit-consuming-app.cjs +961 -0
  230. package/scripts/check-pace-core-compliance.cjs +315 -61
  231. package/scripts/install-cursor-rules.cjs +236 -0
  232. package/src/__tests__/helpers/test-providers.tsx +1 -1
  233. package/src/__tests__/helpers/test-utils.tsx +1 -1
  234. package/src/__tests__/rls-policies.test.ts +3 -1
  235. package/src/components/Badge/Badge.tsx +2 -4
  236. package/src/components/Button/Button.tsx +5 -4
  237. package/src/components/Calendar/Calendar.tsx +1 -1
  238. package/src/components/DataTable/DataTable.test.tsx +57 -93
  239. package/src/components/DataTable/DataTable.tsx +2 -2
  240. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
  241. package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
  242. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
  243. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
  244. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
  245. package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
  246. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +88 -16
  247. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  248. package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
  249. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  250. package/src/components/DataTable/components/DataTableCore.tsx +4 -7
  251. package/src/components/DataTable/components/DataTableModals.tsx +1 -1
  252. package/src/components/DataTable/components/EditableRow.tsx +1 -1
  253. package/src/components/DataTable/components/UnifiedTableBody.tsx +86 -17
  254. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  255. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  256. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  257. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  258. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  259. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  260. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  261. package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
  262. package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
  263. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  264. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  265. package/src/components/Dialog/Dialog.tsx +6 -5
  266. package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
  267. package/src/components/EventSelector/EventSelector.tsx +1 -1
  268. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -3
  269. package/src/components/FileDisplay/FileDisplay.tsx +16 -4
  270. package/src/components/Footer/Footer.tsx +1 -1
  271. package/src/components/Form/Form.test.tsx +36 -15
  272. package/src/components/Form/Form.tsx +30 -26
  273. package/src/components/Header/Header.tsx +1 -1
  274. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  275. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  276. package/src/components/Input/Input.tsx +28 -30
  277. package/src/components/Label/Label.tsx +1 -1
  278. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  279. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  280. package/src/components/LoginForm/LoginForm.tsx +8 -8
  281. package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
  282. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -11
  283. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -1
  284. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  285. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +75 -52
  286. package/src/components/PaceAppLayout/PaceAppLayout.tsx +98 -69
  287. package/src/components/PaceAppLayout/README.md +1 -1
  288. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -8
  289. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  290. package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
  291. package/src/components/Progress/Progress.tsx +1 -1
  292. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
  293. package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
  294. package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
  295. package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
  296. package/src/components/Select/Select.tsx +33 -22
  297. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
  298. package/src/components/Table/Table.tsx +1 -1
  299. package/src/components/Textarea/Textarea.tsx +27 -29
  300. package/src/components/Toast/Toast.tsx +1 -1
  301. package/src/components/Tooltip/Tooltip.tsx +1 -1
  302. package/src/components/UserMenu/UserMenu.tsx +1 -1
  303. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  304. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +14 -7
  305. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  306. package/src/hooks/public/usePublicEvent.ts +1 -1
  307. package/src/hooks/public/usePublicEventLogo.ts +1 -1
  308. package/src/hooks/public/usePublicRouteParams.ts +1 -1
  309. package/src/hooks/services/useAuthService.ts +21 -3
  310. package/src/hooks/services/useEventService.ts +21 -3
  311. package/src/hooks/services/useInactivityService.ts +21 -3
  312. package/src/hooks/services/useOrganisationService.ts +21 -3
  313. package/src/hooks/useDataTableState.ts +8 -18
  314. package/src/hooks/useFileDisplay.ts +10 -17
  315. package/src/hooks/useFocusManagement.ts +2 -2
  316. package/src/hooks/useFocusTrap.ts +4 -4
  317. package/src/hooks/useFormDialog.ts +8 -7
  318. package/src/hooks/useInactivityTracker.ts +1 -1
  319. package/src/hooks/usePermissionCache.ts +1 -1
  320. package/src/hooks/useSecureDataAccess.test.ts +16 -9
  321. package/src/hooks/useSecureDataAccess.ts +22 -6
  322. package/src/hooks/useToast.ts +2 -2
  323. package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
  324. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  325. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  326. package/src/providers/services/EventServiceProvider.tsx +0 -8
  327. package/src/providers/services/UnifiedAuthProvider.tsx +196 -46
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
  329. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +34 -40
  330. package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
  331. package/src/rbac/adapters.tsx +3 -22
  332. package/src/rbac/api.test.ts +2 -2
  333. package/src/rbac/api.ts +7 -1
  334. package/src/rbac/components/EnhancedNavigationMenu.tsx +3 -16
  335. package/src/rbac/components/NavigationGuard.tsx +2 -11
  336. package/src/rbac/components/NavigationProvider.tsx +1 -2
  337. package/src/rbac/components/PagePermissionGuard.tsx +1 -1
  338. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  339. package/src/rbac/components/PermissionEnforcer.tsx +46 -13
  340. package/src/rbac/components/RoleBasedRouter.tsx +1 -1
  341. package/src/rbac/components/SecureDataProvider.tsx +1 -2
  342. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
  343. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
  344. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
  345. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
  346. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
  347. package/src/rbac/engine.ts +14 -2
  348. package/src/rbac/hooks/index.ts +0 -3
  349. package/src/rbac/hooks/usePermissions.ts +51 -11
  350. package/src/rbac/hooks/useRBAC.ts +3 -13
  351. package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
  352. package/src/rbac/hooks/useResolvedScope.ts +58 -33
  353. package/src/rbac/hooks/useSecureSupabase.ts +4 -9
  354. package/src/rbac/secureClient.ts +43 -0
  355. package/src/services/EventService.ts +4 -57
  356. package/src/services/InactivityService.ts +127 -34
  357. package/src/services/OrganisationService.ts +68 -10
  358. package/src/utils/security/secureDataAccess.test.ts +31 -20
  359. package/src/utils/security/secureDataAccess.ts +4 -3
  360. package/dist/chunk-6C4YBBJM.js.map +0 -1
  361. package/dist/chunk-6LTQQAT6.js.map +0 -1
  362. package/dist/chunk-6TQDD426.js.map +0 -1
  363. package/dist/chunk-LOMZXPSN.js.map +0 -1
  364. package/dist/chunk-OETXORNB.js.map +0 -1
  365. package/dist/chunk-ULHIJK66.js.map +0 -1
  366. package/dist/chunk-VKB2CO4Z.js.map +0 -1
  367. package/dist/chunk-VRGWKHDB.js.map +0 -1
  368. package/dist/chunk-XNYQOL3Z.js.map +0 -1
  369. package/dist/chunk-XYXSXPUK.js.map +0 -1
  370. package/scripts/check-pace-core-compliance.js +0 -512
  371. package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
  372. package/src/utils/context/superAdminOverride.ts +0 -58
  373. /package/dist/{DataTable-WKRZD47S.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
  374. /package/dist/{UnifiedAuthProvider-FTSG5XH7.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
  375. /package/dist/{api-IHKALJZD.js.map → api-N774RPUA.js.map} +0 -0
  376. /package/dist/{chunk-QWWZ5CAQ.js.map → chunk-LXQLPRQ2.js.map} +0 -0
  377. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  378. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  379. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  380. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -116,10 +116,6 @@ export class EventService extends BaseService implements IEventService {
116
116
  if (user?.id) {
117
117
  try {
118
118
  this.isSuperAdmin = await isSuperAdmin(user.id as UUID);
119
- logger.debug('EventService', 'Updated super admin status', {
120
- userId: user.id,
121
- isSuperAdmin: this.isSuperAdmin
122
- });
123
119
  } catch (error) {
124
120
  logger.warn('EventService', 'Failed to check super admin status', { error });
125
121
  this.isSuperAdmin = false; // Default to false on error
@@ -151,9 +147,10 @@ export class EventService extends BaseService implements IEventService {
151
147
 
152
148
  // Event state getters
153
149
  getEvents(): Event[] {
154
- // Return a new array reference so React can detect changes
155
- // This ensures useMemo dependencies work correctly
156
- return [...this.events];
150
+ // Return stable array reference - only create new array when events actually change
151
+ // This prevents unnecessary re-renders when getEvents() is called on every render
152
+ // The service's notify() mechanism handles change detection
153
+ return this.events;
157
154
  }
158
155
 
159
156
  getSelectedEvent(): Event | null {
@@ -308,12 +305,6 @@ export class EventService extends BaseService implements IEventService {
308
305
  async initialize(): Promise<void> {
309
306
  // Only call super.initialize() which will call doInitialize() and fetchEvents()
310
307
  // Don't call fetchEvents() again here to avoid double-fetching
311
- logger.debug('EventService', 'initialize() called', {
312
- isInitializedRef: this.isInitializedRef,
313
- hasUser: !!this.user,
314
- hasSession: !!this.session,
315
- appName: this.appName
316
- });
317
308
  await super.initialize();
318
309
  }
319
310
 
@@ -322,23 +313,13 @@ export class EventService extends BaseService implements IEventService {
322
313
  }
323
314
 
324
315
  protected async doInitialize(): Promise<void> {
325
- logger.debug('EventService', 'doInitialize() called', {
326
- isInitializedRef: this.isInitializedRef,
327
- isFetchingRef: this.isFetchingRef,
328
- hasUser: !!this.user,
329
- hasSession: !!this.session,
330
- appName: this.appName
331
- });
332
-
333
316
  // Skip if already initialized
334
317
  if (this.isInitializedRef) {
335
- logger.debug('EventService', 'Skipping initialization - already initialized');
336
318
  return;
337
319
  }
338
320
 
339
321
  // Skip if already fetching
340
322
  if (this.isFetchingRef) {
341
- logger.debug('EventService', 'Skipping initialization - already fetching');
342
323
  return;
343
324
  }
344
325
 
@@ -355,16 +336,9 @@ export class EventService extends BaseService implements IEventService {
355
336
  // For event-required apps, selectedOrganisation may be null (org derived from event)
356
337
  // For org-required apps, selectedOrganisation is required
357
338
  if (!this.user) {
358
- logger.debug('EventService', 'Skipping initialization - missing user');
359
339
  return;
360
340
  }
361
341
 
362
- logger.debug('EventService', 'Initializing - fetching events', {
363
- userId: this.user.id,
364
- organisationId: this.selectedOrganisation?.id || 'derived-from-event',
365
- appName: this.appName
366
- });
367
-
368
342
  // Initial setup - fetch events on initialization
369
343
  await this.fetchEvents(false);
370
344
 
@@ -380,12 +354,6 @@ export class EventService extends BaseService implements IEventService {
380
354
  // For event-required apps, selectedOrganisation may be null (org derived from event)
381
355
  // For org-required apps, selectedOrganisation is required
382
356
  if (!this.user || !this.session || !this.supabaseClient || !this.appName) {
383
- logger.debug('EventService', 'Skipping fetchEvents - missing dependencies', {
384
- hasUser: !!this.user,
385
- hasSession: !!this.session,
386
- hasSupabaseClient: !!this.supabaseClient,
387
- appName: this.appName
388
- });
389
357
  // Already false from initialization, just notify
390
358
  this.notify();
391
359
  return;
@@ -397,7 +365,6 @@ export class EventService extends BaseService implements IEventService {
397
365
 
398
366
  // Prevent multiple simultaneous fetches
399
367
  if (this.isFetchingRef) {
400
- logger.debug('EventService', 'Skipping fetchEvents - already fetching');
401
368
  return;
402
369
  }
403
370
 
@@ -431,9 +398,6 @@ export class EventService extends BaseService implements IEventService {
431
398
  if (userIsSuperAdmin) {
432
399
  // Super admin: Pass null to see all events across all organisations
433
400
  organisationIdForRpc = null;
434
- logger.debug('EventService', 'Super admin detected - fetching all events', {
435
- userId: this.user.id
436
- });
437
401
  } else {
438
402
  // Not super admin: determine org from context based on app type
439
403
  if (this.selectedEvent) {
@@ -443,10 +407,6 @@ export class EventService extends BaseService implements IEventService {
443
407
  // Event-required app with no selected event yet: pass null to get all accessible events
444
408
  // The RPC will filter by event app roles, returning all events the user has access to
445
409
  organisationIdForRpc = null;
446
- logger.debug('EventService', 'Event-required app: fetching all accessible events (no event selected yet)', {
447
- userId: this.user.id,
448
- appName: this.appName
449
- });
450
410
  } else if (this.selectedOrganisation) {
451
411
  // Org-required app: use selected organisation
452
412
  organisationIdForRpc = this.selectedOrganisation.id;
@@ -476,12 +436,6 @@ export class EventService extends BaseService implements IEventService {
476
436
  }
477
437
  }
478
438
 
479
- logger.debug('EventService', 'Fetching events via RPC', {
480
- userId: this.user.id,
481
- organisationId: organisationIdForRpc,
482
- appName: this.appName
483
- });
484
-
485
439
  // Call the RPC function following the established pattern
486
440
  // For super admins, pass null for p_organisation_id to see all events
487
441
  let { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
@@ -490,13 +444,6 @@ export class EventService extends BaseService implements IEventService {
490
444
  p_app_name: this.appName
491
445
  });
492
446
 
493
- logger.debug('EventService', 'RPC response received', {
494
- hasData: !!data,
495
- dataLength: Array.isArray(data) ? data.length : 'not array',
496
- hasError: !!rpcError,
497
- error: rpcError
498
- });
499
-
500
447
  if (rpcError) {
501
448
  logger.error('EventService', 'RPC error fetching events:', rpcError);
502
449
  throw new Error(rpcError.message || 'Failed to fetch events');
@@ -145,12 +145,31 @@ export class InactivityService extends BaseService implements IInactivityService
145
145
  if (this.inactivityTracker) {
146
146
  this.inactivityTracker.resetActivity();
147
147
  }
148
+
149
+ // Store previous values
150
+ const prevIsIdle = this._isIdle;
151
+ const prevShowWarning = this._showWarning;
152
+ const prevShowInactivityWarning = this._showInactivityWarning;
153
+ const prevInactivityTimeRemaining = this._inactivityTimeRemaining;
154
+ const prevTimeRemaining = this._timeRemaining;
155
+
156
+ // Update state
148
157
  this._isIdle = false;
149
158
  this._showWarning = false;
150
159
  this._showInactivityWarning = false;
151
160
  this._inactivityTimeRemaining = 0;
152
161
  this._timeRemaining = this.idleTimeoutMs;
153
- this.notify();
162
+
163
+ // Only notify if values actually changed
164
+ if (
165
+ prevIsIdle !== this._isIdle ||
166
+ prevShowWarning !== this._showWarning ||
167
+ prevShowInactivityWarning !== this._showInactivityWarning ||
168
+ prevInactivityTimeRemaining !== this._inactivityTimeRemaining ||
169
+ prevTimeRemaining !== this._timeRemaining
170
+ ) {
171
+ this.notify();
172
+ }
154
173
  }
155
174
 
156
175
  startTracking(): void {
@@ -275,10 +294,63 @@ export class InactivityService extends BaseService implements IInactivityService
275
294
  let idleTimer: NodeJS.Timeout | null = null;
276
295
  let warningTimer: NodeJS.Timeout | null = null;
277
296
  let lastActivity = Date.now();
278
-
279
- const resetTimers = () => {
280
- lastActivity = Date.now();
297
+ let pollInterval: NodeJS.Timeout | null = null;
298
+
299
+ // Store previous state for comparison
300
+ let prevIsIdle = false;
301
+ let prevShowWarning = false;
302
+ let prevShowInactivityWarning = false;
303
+ let prevInactivityTimeRemaining = 0;
304
+ let prevTimeRemaining = this.idleTimeoutMs;
305
+
306
+ // Poll every 10 seconds to check for state changes
307
+ const pollInactivityState = () => {
308
+ const now = Date.now();
309
+ const timeSinceActivity = now - lastActivity;
310
+ const timeUntilIdle = this.idleTimeoutMs - timeSinceActivity;
311
+ const timeUntilWarning = (this.idleTimeoutMs - this.warnBeforeMs) - timeSinceActivity;
312
+
313
+ // Calculate new state values based on time since last activity
314
+ const newIsIdle = timeSinceActivity >= (this.idleTimeoutMs - this.warnBeforeMs);
315
+ const newShowWarning = newIsIdle && timeSinceActivity < this.idleTimeoutMs;
316
+ const newShowInactivityWarning = newShowWarning;
317
+ const newInactivityTimeRemaining = newShowWarning
318
+ ? Math.ceil((this.idleTimeoutMs - timeSinceActivity) / 1000)
319
+ : 0;
320
+ const newTimeRemaining = Math.max(0, timeUntilIdle);
321
+
322
+ // Check if state actually changed
323
+ const stateChanged =
324
+ prevIsIdle !== newIsIdle ||
325
+ prevShowWarning !== newShowWarning ||
326
+ prevShowInactivityWarning !== newShowInactivityWarning ||
327
+ prevInactivityTimeRemaining !== newInactivityTimeRemaining ||
328
+ prevTimeRemaining !== newTimeRemaining;
329
+
330
+ // Only update and notify if state changed
331
+ if (stateChanged) {
332
+ this._isIdle = newIsIdle;
333
+ this._showWarning = newShowWarning;
334
+ this._showInactivityWarning = newShowInactivityWarning;
335
+ this._inactivityTimeRemaining = newInactivityTimeRemaining;
336
+ this._timeRemaining = newTimeRemaining;
337
+
338
+ // Update previous state
339
+ prevIsIdle = newIsIdle;
340
+ prevShowWarning = newShowWarning;
341
+ prevShowInactivityWarning = newShowInactivityWarning;
342
+ prevInactivityTimeRemaining = newInactivityTimeRemaining;
343
+ prevTimeRemaining = newTimeRemaining;
344
+
345
+ this.notify();
346
+
347
+ // Handle idle logout if needed
348
+ if (newIsIdle && timeSinceActivity >= this.idleTimeoutMs) {
349
+ this.handleIdleLogout();
350
+ }
351
+ }
281
352
 
353
+ // Update timers based on current state
282
354
  if (idleTimer) {
283
355
  clearTimeout(idleTimer);
284
356
  idleTimer = null;
@@ -288,47 +360,58 @@ export class InactivityService extends BaseService implements IInactivityService
288
360
  clearTimeout(warningTimer);
289
361
  warningTimer = null;
290
362
  }
291
-
292
- this._showInactivityWarning = false;
293
- this._inactivityTimeRemaining = 0;
294
- this._isIdle = false;
295
- this._showWarning = false;
296
- this.notify();
297
- };
298
-
299
- const startIdleTimer = () => {
300
- if (idleTimer) {
301
- clearTimeout(idleTimer);
363
+
364
+ // Set up timers for next state transitions
365
+ if (!newIsIdle && timeUntilIdle > 0) {
366
+ idleTimer = setTimeout(() => {
367
+ pollInactivityState();
368
+ }, Math.min(10000, timeUntilIdle));
302
369
  }
303
-
304
- idleTimer = setTimeout(() => {
305
- this._isIdle = true;
306
- this._showWarning = true;
307
- this.notify();
308
-
309
- // Start warning timer
370
+
371
+ if (newShowWarning && timeUntilIdle > 0) {
310
372
  warningTimer = setTimeout(() => {
311
373
  this.handleIdleLogout();
312
- }, this.warnBeforeMs);
313
- }, this.idleTimeoutMs - this.warnBeforeMs);
374
+ }, timeUntilIdle);
375
+ }
314
376
  };
315
377
 
316
- const startWarningTimer = () => {
378
+ const resetTimers = () => {
379
+ // Only update lastActivity - don't notify yet
380
+ lastActivity = Date.now();
381
+
382
+ // Clear timers
383
+ if (idleTimer) {
384
+ clearTimeout(idleTimer);
385
+ idleTimer = null;
386
+ }
387
+
317
388
  if (warningTimer) {
318
389
  clearTimeout(warningTimer);
390
+ warningTimer = null;
319
391
  }
320
-
321
- warningTimer = setTimeout(() => {
322
- this.handleIdleLogout();
323
- }, this.warnBeforeMs);
392
+
393
+ // Reset state values (will be checked on next poll)
394
+ this._showInactivityWarning = false;
395
+ this._inactivityTimeRemaining = 0;
396
+ this._isIdle = false;
397
+ this._showWarning = false;
398
+
399
+ // Update previous state to match
400
+ prevIsIdle = false;
401
+ prevShowWarning = false;
402
+ prevShowInactivityWarning = false;
403
+ prevInactivityTimeRemaining = 0;
404
+ prevTimeRemaining = this.idleTimeoutMs;
405
+
406
+ // Poll will check and notify if needed
324
407
  };
325
408
 
326
- // Activity detection
409
+ // Activity detection - only updates lastActivity, doesn't notify
327
410
  const activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'];
328
411
 
329
412
  const handleActivity = () => {
330
413
  resetTimers();
331
- startIdleTimer();
414
+ // Don't call notify - polling will handle state updates
332
415
  };
333
416
 
334
417
  // Add event listeners
@@ -336,8 +419,13 @@ export class InactivityService extends BaseService implements IInactivityService
336
419
  document.addEventListener(event, handleActivity, true);
337
420
  });
338
421
 
339
- // Start initial timer
340
- startIdleTimer();
422
+ // Start polling every 10 seconds
423
+ pollInterval = setInterval(() => {
424
+ pollInactivityState();
425
+ }, 10000); // 10 seconds
426
+
427
+ // Initial poll
428
+ pollInactivityState();
341
429
 
342
430
  // Store cleanup function
343
431
  this.cleanupHandlers = () => {
@@ -350,6 +438,11 @@ export class InactivityService extends BaseService implements IInactivityService
350
438
  clearTimeout(warningTimer);
351
439
  warningTimer = null;
352
440
  }
441
+
442
+ if (pollInterval) {
443
+ clearInterval(pollInterval);
444
+ pollInterval = null;
445
+ }
353
446
 
354
447
  activityEvents.forEach(event => {
355
448
  document.removeEventListener(event, handleActivity, true);
@@ -364,6 +457,6 @@ export class InactivityService extends BaseService implements IInactivityService
364
457
  };
365
458
 
366
459
  this._isTracking = true;
367
- this.notify();
460
+ this.notify(); // Initial notification only
368
461
  }
369
462
  }
@@ -20,6 +20,8 @@ import type {
20
20
  import { setOrganisationContext } from '../utils/context/organisationContext';
21
21
  import { logger } from '../utils/core/logger';
22
22
  import { assertUserId, assertOrganisationId } from '../types/core';
23
+ import { isSuperAdmin } from '../rbac/api';
24
+ import type { UUID } from '../rbac/types';
23
25
 
24
26
  // Type for RPC response from data_user_organisation_roles_get
25
27
  interface OrganisationRoleRpcResponse {
@@ -46,6 +48,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
46
48
  private _roleMapState: Map<string, string> = new Map();
47
49
  private _isLoading = false;
48
50
  private _error: Error | null = null;
51
+ private _isSuperAdmin: boolean = false; // Cache super admin status
49
52
  private _isContextReady = false;
50
53
  private retryCount = 0;
51
54
 
@@ -81,17 +84,21 @@ export class OrganisationService extends BaseService implements IOrganisationSer
81
84
  // Additional methods for testing
82
85
  setSelectedOrganisation(organisation: Organisation | null): void {
83
86
  // SECURITY: Validate organisation is in user's accessible organisations (only if orgs are loaded)
87
+ // Exception: Super admins can set any organisation (they have global access)
84
88
  if (organisation && this._organisations.length > 0) {
85
- const isValidOrg = this._organisations.some(org => org.id === organisation.id);
86
- if (!isValidOrg) {
87
- logger.warn('OrganisationService', 'Attempted to set invalid organisation - not in user\'s accessible organisations', {
88
- organisationId: organisation.id,
89
- organisationName: organisation.name,
90
- accessibleOrgIds: this._organisations.map(o => o.id)
91
- });
92
- // Don't set invalid organisation - this prevents security issues
93
- // If organisations haven't loaded yet, validation will happen in loadUserOrganisations()
94
- return;
89
+ // Only validate if user is not a super admin (use cached status)
90
+ if (!this._isSuperAdmin) {
91
+ const isValidOrg = this._organisations.some(org => org.id === organisation.id);
92
+ if (!isValidOrg) {
93
+ logger.warn('OrganisationService', 'Attempted to set invalid organisation - not in user\'s accessible organisations', {
94
+ organisationId: organisation.id,
95
+ organisationName: organisation.name,
96
+ accessibleOrgIds: this._organisations.map(o => o.id)
97
+ });
98
+ // Don't set invalid organisation - this prevents security issues
99
+ // If organisations haven't loaded yet, validation will happen in loadUserOrganisations()
100
+ return;
101
+ }
95
102
  }
96
103
  }
97
104
 
@@ -140,6 +147,11 @@ export class OrganisationService extends BaseService implements IOrganisationSer
140
147
  const wasAuthenticated = !!(this.user && this.session);
141
148
  const isAuthenticated = !!(user && session);
142
149
 
150
+ // Reset super admin cache when user changes
151
+ if (this.user?.id !== user?.id) {
152
+ this._isSuperAdmin = false;
153
+ }
154
+
143
155
  this.user = user;
144
156
  this.session = session;
145
157
 
@@ -460,11 +472,47 @@ export class OrganisationService extends BaseService implements IOrganisationSer
460
472
  throw membershipError;
461
473
  }
462
474
 
475
+ // Check if user is super admin - super admins don't need organisation memberships
476
+ let userIsSuperAdmin = false;
477
+ if (this.user?.id) {
478
+ try {
479
+ userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
480
+ this._isSuperAdmin = userIsSuperAdmin; // Cache the result
481
+ } catch (error) {
482
+ logger.warn('OrganisationService', 'Failed to check super admin status', { error });
483
+ // Continue with normal flow if check fails
484
+ this._isSuperAdmin = false;
485
+ }
486
+ } else {
487
+ this._isSuperAdmin = false;
488
+ }
489
+
490
+ // Super admins can proceed without organisation memberships
463
491
  if (!memberships || memberships.length === 0) {
492
+ if (userIsSuperAdmin) {
493
+ // Super admin without org memberships - allow empty state
494
+ this._organisations = [];
495
+ this._userMemberships = [];
496
+ this._isLoading = false;
497
+ this._error = null;
498
+ this._isContextReady = true;
499
+ this.notify();
500
+ return;
501
+ }
464
502
  throw new Error('User has no active organisation memberships') as OrganisationSecurityError;
465
503
  }
466
504
 
467
505
  if (!organisations || organisations.length === 0) {
506
+ if (userIsSuperAdmin) {
507
+ // Super admin without orgs - allow empty state
508
+ this._organisations = [];
509
+ this._userMemberships = [];
510
+ this._isLoading = false;
511
+ this._error = null;
512
+ this._isContextReady = true;
513
+ this.notify();
514
+ return;
515
+ }
468
516
  throw new Error('No organisations found in role data') as OrganisationSecurityError;
469
517
  }
470
518
 
@@ -481,6 +529,16 @@ export class OrganisationService extends BaseService implements IOrganisationSer
481
529
  // Filter to active organisations only
482
530
 
483
531
  if (activeOrgs.length === 0) {
532
+ if (userIsSuperAdmin) {
533
+ // Super admin without active orgs - allow empty state
534
+ this._organisations = [];
535
+ this._userMemberships = [];
536
+ this._isLoading = false;
537
+ this._error = null;
538
+ this._isContextReady = true;
539
+ this.notify();
540
+ return;
541
+ }
484
542
  throw new Error('User has no access to active organisations') as OrganisationSecurityError;
485
543
  }
486
544
 
@@ -21,15 +21,25 @@ const mockSupabaseClient = {
21
21
  let mockQueryBuilder: any;
22
22
 
23
23
  const createMockQueryBuilder = (customData?: any, customError?: any) => {
24
+ // Create spy functions that return this for chaining
25
+ const selectSpy = vi.fn(function(this: any) { return this; });
26
+ const eqSpy = vi.fn(function(this: any) { return this; });
27
+ const orderSpy = vi.fn(function(this: any) { return this; });
28
+ const limitSpy = vi.fn(function(this: any) { return this; });
29
+ const rangeSpy = vi.fn(function(this: any) { return this; });
30
+ const insertSpy = vi.fn(function(this: any) { return this; });
31
+ const updateSpy = vi.fn(function(this: any) { return this; });
32
+ const deleteSpy = vi.fn(function(this: any) { return this; });
33
+
24
34
  const builder: any = {
25
- select: vi.fn(function(this: any) { return this; }),
26
- eq: vi.fn(function(this: any) { return this; }),
27
- order: vi.fn(function(this: any) { return this; }),
28
- limit: vi.fn(function(this: any) { return this; }),
29
- range: vi.fn(function(this: any) { return this; }),
30
- insert: vi.fn(function(this: any) { return this; }),
31
- update: vi.fn(function(this: any) { return this; }),
32
- delete: vi.fn(function(this: any) { return this; }),
35
+ select: selectSpy,
36
+ eq: eqSpy,
37
+ order: orderSpy,
38
+ limit: limitSpy,
39
+ range: rangeSpy,
40
+ insert: insertSpy,
41
+ update: updateSpy,
42
+ delete: deleteSpy,
33
43
  single: vi.fn().mockImplementation(() => {
34
44
  const data = customData !== undefined ? customData : { id: 'test-123' };
35
45
  const error = customError !== undefined ? customError : null;
@@ -56,7 +66,7 @@ const createMockQueryBuilder = (customData?: any, customError?: any) => {
56
66
  describe('secureDataAccess', () => {
57
67
  let secureDataAccess: ReturnType<typeof createSecureDataAccess>;
58
68
  const mockOrganisationId = 'org-123';
59
- const mockTable = 'pace_person'; // Use a table that has organisation_id column
69
+ const mockTable = 'core_events'; // Use a table that has organisation_id column (pace_person doesn't have it)
60
70
  const mockSelect = 'id, name, organisation_id';
61
71
 
62
72
  beforeEach(() => {
@@ -332,10 +342,10 @@ describe('secureDataAccess', () => {
332
342
  const mockFilters = { id: '1' };
333
343
  const mockResult = { id: '1', name: 'Updated Item', organisation_id: mockOrganisationId };
334
344
 
335
- mockQueryBuilder.update = vi.fn().mockReturnThis();
336
- mockQueryBuilder.eq = vi.fn().mockReturnThis();
337
- mockQueryBuilder.select = vi.fn().mockReturnThis();
345
+ // Create a fresh mock query builder with proper chaining
346
+ mockQueryBuilder = createMockQueryBuilder(mockResult);
338
347
  mockQueryBuilder.single = vi.fn().mockResolvedValue({ data: mockResult, error: null });
348
+ mockSupabaseClient.from = vi.fn().mockReturnValue(mockQueryBuilder);
339
349
 
340
350
  const result = await secureDataAccess.secureUpdate(mockTable, mockData, mockFilters, mockOrganisationId);
341
351
 
@@ -366,9 +376,13 @@ describe('secureDataAccess', () => {
366
376
  it('should delete data with organisation filter', async () => {
367
377
  const mockFilters = { id: '1' };
368
378
 
369
- mockQueryBuilder.delete = vi.fn().mockReturnThis();
370
- mockQueryBuilder.eq = vi.fn().mockReturnThis();
371
- mockQueryBuilder.single = vi.fn().mockResolvedValue({ data: null, error: null });
379
+ // Create a fresh mock query builder with proper chaining
380
+ mockQueryBuilder = createMockQueryBuilder();
381
+ // Make the query builder thenable and resolve successfully
382
+ mockQueryBuilder.then = function(resolve: any) {
383
+ return Promise.resolve({ data: null, error: null }).then(resolve);
384
+ };
385
+ mockSupabaseClient.from = vi.fn().mockReturnValue(mockQueryBuilder);
372
386
 
373
387
  const result = await secureDataAccess.secureDelete(mockTable, mockFilters, mockOrganisationId);
374
388
 
@@ -687,12 +701,9 @@ describe('secureDataAccess', () => {
687
701
  };
688
702
 
689
703
  const mockData = [{ id: '1', name: 'Test', organisation_id: mockOrganisationId }];
690
- // Create a new mock with the expected data
691
- mockQueryBuilder = createMockQueryBuilder(mockData);
692
- mockSupabaseClient.from = vi.fn().mockReturnValue(mockQueryBuilder);
693
704
  // Create a new mock with the expected data
694
- mockQueryBuilder = createMockQueryBuilder(mockData);
695
- mockSupabaseClient.from = vi.fn().mockReturnValue(mockQueryBuilder);
705
+ mockQueryBuilder = createMockQueryBuilder(mockData);
706
+ mockSupabaseClient.from = vi.fn().mockReturnValue(mockQueryBuilder);
696
707
 
697
708
  const options: SecureQueryOptions = {
698
709
  table: mockTable,
@@ -99,9 +99,10 @@ export const createSecureDataAccess = (
99
99
  // SECURITY: Phase 2 additions - complete organisation table mapping
100
100
  'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
101
101
  // SECURITY: Emergency additions for Phase 1 fixes
102
- 'cake_meal', 'cake_mealtype', 'core_person', 'pace_person',
103
- // NOTE: core_member, medi_profile, core_contact, core_consent, core_identification, core_qualification
104
- // are now person-scoped (not organisation-scoped) - removed from this list
102
+ 'cake_meal', 'cake_mealtype',
103
+ // NOTE: core_person, pace_person, core_member, medi_profile, core_contact, core_consent,
104
+ // core_identification, core_qualification are now person-scoped (not organisation-scoped)
105
+ // and do NOT have organisation_id columns - removed from this list
105
106
  // SECURITY: Phase 3A additions - medical and personal data
106
107
  // NOTE: medi_condition, medi_diet, medi_action_plan, medi_profile_versions are now person-scoped
107
108
  // (via medi_profile) - removed from this list