@jmruthers/pace-core 0.6.1 → 0.6.3

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 (549) hide show
  1. package/CHANGELOG.md +88 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +46 -87
  3. package/cursor-rules/01-standards-compliance.mdc +16 -47
  4. package/cursor-rules/02-project-structure.mdc +4 -4
  5. package/cursor-rules/03-solid-principles.mdc +45 -164
  6. package/cursor-rules/04-testing-standards.mdc +22 -69
  7. package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
  8. package/cursor-rules/06-code-quality.mdc +42 -125
  9. package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
  10. package/cursor-rules/08-markup-quality.mdc +452 -0
  11. package/cursor-rules/CHANGELOG.md +18 -0
  12. package/cursor-rules/README.md +2 -1
  13. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
  17. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
  18. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  19. package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
  20. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  21. package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
  22. package/dist/chunk-2T2IG7T7.js.map +1 -0
  23. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  24. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  25. package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
  26. package/dist/chunk-6Z7LTB3D.js.map +1 -0
  27. package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
  28. package/dist/chunk-CNCQDFLN.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
  31. package/dist/chunk-DWUBLJJM.js.map +1 -0
  32. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  33. package/dist/chunk-FFQEQTNW.js.map +1 -0
  34. package/dist/chunk-FMUCXFII.js +76 -0
  35. package/dist/chunk-FMUCXFII.js.map +1 -0
  36. package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
  37. package/dist/chunk-HFZBI76P.js.map +1 -0
  38. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  39. package/dist/chunk-L4OXEN46.js.map +1 -0
  40. package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
  41. package/dist/chunk-M43Y4SSO.js.map +1 -0
  42. package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
  43. package/dist/chunk-M7MPQISP.js.map +1 -0
  44. package/dist/chunk-PQBSKX33.js +7793 -0
  45. package/dist/chunk-PQBSKX33.js.map +1 -0
  46. package/dist/chunk-QRPVRXYT.js +226 -0
  47. package/dist/chunk-QRPVRXYT.js.map +1 -0
  48. package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
  49. package/dist/chunk-RWEBCB47.js.map +1 -0
  50. package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
  51. package/dist/chunk-YDQHOZNA.js.map +1 -0
  52. package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
  53. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  54. package/dist/components.d.ts +5 -5
  55. package/dist/components.js +18 -16
  56. package/dist/components.js.map +1 -1
  57. package/dist/contextValidator-3JNZKUTX.js +9 -0
  58. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  59. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  60. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  61. package/dist/hooks.d.ts +55 -122
  62. package/dist/hooks.js +10 -13
  63. package/dist/hooks.js.map +1 -1
  64. package/dist/index.d.ts +60 -13
  65. package/dist/index.js +30 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/providers.d.ts +21 -3
  68. package/dist/providers.js +4 -3
  69. package/dist/rbac/index.d.ts +210 -139
  70. package/dist/rbac/index.js +17 -13
  71. package/dist/styles/index.js +1 -1
  72. package/dist/theming/runtime.d.ts +1 -13
  73. package/dist/theming/runtime.js +2 -2
  74. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  75. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  76. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  77. package/dist/types.d.ts +2 -2
  78. package/dist/types.js +1 -1
  79. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
  80. package/dist/utils.d.ts +4 -5
  81. package/dist/utils.js +17 -19
  82. package/dist/utils.js.map +1 -1
  83. package/docs/api/README.md +21 -17
  84. package/docs/api/modules.md +4191 -2967
  85. package/docs/architecture/database-schema-requirements.md +161 -0
  86. package/docs/components/context-selector.md +126 -0
  87. package/docs/core-concepts/rbac-system.md +3 -3
  88. package/docs/documentation-index.md +2 -4
  89. package/docs/getting-started/cursor-rules.md +2 -1
  90. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  91. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  92. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  93. package/docs/migration/README.md +52 -6
  94. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  95. package/docs/migration/database-changes-december-2025.md +3 -3
  96. package/docs/pace-mint-fix-auto-selection.md +218 -0
  97. package/docs/pace-mint-rbac-setup.md +391 -0
  98. package/docs/rbac/event-based-apps.md +1 -1
  99. package/docs/rbac/getting-started.md +1 -1
  100. package/docs/rbac/quick-start.md +1 -1
  101. package/docs/rbac/secure-client-protection.md +330 -0
  102. package/docs/standards/README.md +1 -0
  103. package/package.json +4 -3
  104. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  105. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  106. package/scripts/audit/core/checks/bundle.cjs +142 -0
  107. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
  108. package/scripts/audit/core/checks/config.cjs +54 -0
  109. package/scripts/audit/core/checks/coverage.cjs +84 -0
  110. package/scripts/audit/core/checks/dependencies.cjs +985 -0
  111. package/scripts/audit/core/checks/documentation.cjs +268 -0
  112. package/scripts/audit/core/checks/environment.cjs +116 -0
  113. package/scripts/audit/core/checks/error-handling.cjs +340 -0
  114. package/scripts/audit/core/checks/forms.cjs +172 -0
  115. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  116. package/scripts/audit/core/checks/hooks.cjs +334 -0
  117. package/scripts/audit/core/checks/imports.cjs +244 -0
  118. package/scripts/audit/core/checks/performance.cjs +325 -0
  119. package/scripts/audit/core/checks/routes.cjs +117 -0
  120. package/scripts/audit/core/checks/state.cjs +130 -0
  121. package/scripts/audit/core/checks/structure.cjs +65 -0
  122. package/scripts/audit/core/checks/style.cjs +584 -0
  123. package/scripts/audit/core/checks/testing.cjs +122 -0
  124. package/scripts/audit/core/checks/typescript.cjs +61 -0
  125. package/scripts/audit/core/scanner.cjs +199 -0
  126. package/scripts/audit/core/utils.cjs +137 -0
  127. package/scripts/audit/index.cjs +223 -0
  128. package/scripts/audit/reporters/console.cjs +151 -0
  129. package/scripts/audit/reporters/json.cjs +54 -0
  130. package/scripts/audit/reporters/markdown.cjs +124 -0
  131. package/scripts/audit-consuming-app.cjs +61 -936
  132. package/scripts/build-docs/build-decision.js +240 -0
  133. package/scripts/build-docs/cache-utils.js +105 -0
  134. package/scripts/build-docs/content-normalization.js +150 -0
  135. package/scripts/build-docs/file-utils.js +105 -0
  136. package/scripts/build-docs/git-utils.js +86 -0
  137. package/scripts/build-docs/hash-utils.js +116 -0
  138. package/scripts/build-docs/typedoc-runner.js +220 -0
  139. package/scripts/build-docs-incremental.js +77 -913
  140. package/scripts/utils/command-runner.js +16 -11
  141. package/scripts/validate-formats.js +61 -56
  142. package/scripts/validate-master.js +74 -69
  143. package/scripts/validate-pre-publish.js +70 -65
  144. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  145. package/src/components/Alert/Alert.test.tsx +12 -18
  146. package/src/components/Alert/Alert.tsx +5 -7
  147. package/src/components/Avatar/Avatar.test.tsx +4 -4
  148. package/src/components/Badge/Badge.tsx +14 -0
  149. package/src/components/Button/Button.tsx +22 -0
  150. package/src/components/Calendar/Calendar.tsx +8 -2
  151. package/src/components/Card/Card.tsx +4 -0
  152. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  153. package/src/components/Checkbox/Checkbox.tsx +2 -2
  154. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  155. package/src/components/ContextSelector/index.ts +3 -0
  156. package/src/components/DataTable/DataTable.tsx +38 -4
  157. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  158. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  159. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  160. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  161. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  162. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  163. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  164. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  165. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  166. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  167. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  168. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  169. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  170. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  171. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  172. package/src/components/DataTable/components/EditFields.tsx +307 -0
  173. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  174. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  176. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  177. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  178. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  179. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  180. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  181. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  182. package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
  183. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  184. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  185. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  186. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  187. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  188. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  189. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  190. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  191. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  192. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  193. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  194. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  195. package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
  196. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  197. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  198. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  199. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  200. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  201. package/src/components/DataTable/styles.ts +6 -6
  202. package/src/components/DataTable/types.ts +6 -10
  203. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  204. package/src/components/DataTable/utils/debugTools.ts +18 -113
  205. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  206. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  207. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  208. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  209. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  210. package/src/components/Dialog/Dialog.tsx +31 -3
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  212. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  213. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  214. package/src/components/ErrorBoundary/index.ts +27 -2
  215. package/src/components/FileDisplay/FileDisplay.tsx +74 -28
  216. package/src/components/FileUpload/FileUpload.tsx +22 -2
  217. package/src/components/Footer/Footer.test.tsx +16 -16
  218. package/src/components/Footer/Footer.tsx +14 -11
  219. package/src/components/Form/Form.tsx +1 -0
  220. package/src/components/Header/Header.test.tsx +43 -73
  221. package/src/components/Header/Header.tsx +59 -49
  222. package/src/components/Input/Input.test.tsx +2 -2
  223. package/src/components/Input/Input.tsx +8 -4
  224. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  225. package/src/components/LoginForm/LoginForm.tsx +4 -0
  226. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  227. package/src/components/NavigationMenu/types.ts +56 -0
  228. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  229. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  230. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  231. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  232. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
  233. package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
  234. package/src/components/PaceAppLayout/README.md +14 -17
  235. package/src/components/PaceAppLayout/test-setup.tsx +3 -4
  236. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  237. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  238. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  239. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  240. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  241. package/src/components/Select/Select.tsx +80 -434
  242. package/src/components/Select/context.ts +23 -0
  243. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  244. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  245. package/src/components/Select/hooks/useSelectState.ts +104 -0
  246. package/src/components/Select/index.ts +9 -1
  247. package/src/components/Select/types.ts +123 -0
  248. package/src/components/Select/utils/text.ts +26 -0
  249. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  250. package/src/components/Switch/Switch.tsx +4 -4
  251. package/src/components/Tabs/Tabs.tsx +1 -1
  252. package/src/components/Toast/Toast.tsx +4 -0
  253. package/src/components/Tooltip/Tooltip.tsx +2 -2
  254. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  255. package/src/components/UserMenu/UserMenu.tsx +21 -18
  256. package/src/components/index.ts +7 -7
  257. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  258. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  259. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  260. package/src/hooks/index.ts +1 -2
  261. package/src/hooks/public/usePublicEvent.ts +4 -0
  262. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  263. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  264. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  265. package/src/hooks/services/useAuth.ts +32 -0
  266. package/src/hooks/services/useCurrentEvent.ts +6 -0
  267. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  268. package/src/hooks/useAppConfig.ts +15 -30
  269. package/src/hooks/useDebounce.ts +9 -0
  270. package/src/hooks/useEventTheme.ts +6 -0
  271. package/src/hooks/useFileDisplay.ts +81 -50
  272. package/src/hooks/useFileReference.ts +25 -7
  273. package/src/hooks/useFileUrl.ts +11 -1
  274. package/src/hooks/useFocusManagement.ts +14 -0
  275. package/src/hooks/useFocusTrap.ts +3 -0
  276. package/src/hooks/useInactivityTracker.ts +3 -0
  277. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  278. package/src/hooks/useOrganisationPermissions.ts +4 -0
  279. package/src/hooks/useOrganisationSecurity.ts +4 -0
  280. package/src/hooks/usePerformanceMonitor.ts +4 -0
  281. package/src/hooks/usePermissionCache.ts +7 -0
  282. package/src/hooks/useQueryCache.ts +12 -1
  283. package/src/hooks/useSessionRestoration.ts +4 -0
  284. package/src/hooks/useStorage.ts +4 -0
  285. package/src/hooks/useToast.ts +1 -1
  286. package/src/index.ts +6 -6
  287. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  288. package/src/providers/services/AuthServiceProvider.tsx +35 -7
  289. package/src/providers/services/EventServiceProvider.tsx +51 -5
  290. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  291. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  294. package/src/rbac/README.md +1 -1
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  296. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  297. package/src/rbac/adapters.tsx +12 -3
  298. package/src/rbac/api.test.ts +59 -51
  299. package/src/rbac/api.ts +246 -167
  300. package/src/rbac/components/NavigationProvider.tsx +4 -1
  301. package/src/rbac/components/PagePermissionGuard.tsx +185 -17
  302. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  303. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  304. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  305. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  306. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  307. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  308. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  309. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  310. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  311. package/src/rbac/engine.ts +38 -14
  312. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  313. package/src/rbac/hooks/permissions/index.ts +7 -0
  314. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  315. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  316. package/src/rbac/hooks/permissions/useCan.ts +377 -0
  317. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  318. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  319. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  320. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  321. package/src/rbac/hooks/useCan.test.ts +64 -66
  322. package/src/rbac/hooks/usePermissions.ts +14 -995
  323. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  324. package/src/rbac/hooks/useRBAC.ts +36 -37
  325. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  326. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  327. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  328. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  329. package/src/rbac/hooks/useSecureSupabase.ts +27 -7
  330. package/src/rbac/index.ts +7 -0
  331. package/src/rbac/permissions.ts +0 -30
  332. package/src/rbac/secureClient.test.ts +22 -18
  333. package/src/rbac/secureClient.ts +294 -68
  334. package/src/rbac/security.ts +0 -17
  335. package/src/rbac/types.ts +9 -0
  336. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  337. package/src/rbac/utils/clientSecurity.ts +93 -0
  338. package/src/rbac/utils/contextValidator.ts +77 -168
  339. package/src/services/AuthService.ts +39 -7
  340. package/src/services/EventService.ts +186 -54
  341. package/src/services/OrganisationService.ts +81 -14
  342. package/src/services/__tests__/EventService.test.ts +1 -2
  343. package/src/services/base/BaseService.ts +3 -0
  344. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  345. package/src/theming/parseEventColours.ts +5 -19
  346. package/src/types/vitest-globals.d.ts +51 -26
  347. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  348. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  349. package/src/utils/__tests__/index.unit.test.ts +2 -2
  350. package/src/utils/audit/audit.ts +0 -3
  351. package/src/utils/core/cn.ts +1 -1
  352. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  353. package/src/utils/file-reference/index.ts +53 -1
  354. package/src/utils/formatting/formatting.ts +8 -18
  355. package/src/utils/index.ts +0 -1
  356. package/dist/chunk-3QRJFVBR.js.map +0 -1
  357. package/dist/chunk-3XTALGJF.js.map +0 -1
  358. package/dist/chunk-4N5C5XZU.js.map +0 -1
  359. package/dist/chunk-4ZC4GX36.js.map +0 -1
  360. package/dist/chunk-7D4SUZUM.js +0 -38
  361. package/dist/chunk-BYFSK72L.js.map +0 -1
  362. package/dist/chunk-EXUD6RNJ.js +0 -451
  363. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  364. package/dist/chunk-GLK6VM3F.js.map +0 -1
  365. package/dist/chunk-I7PSE6JW.js.map +0 -1
  366. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  367. package/dist/chunk-KNC55RTG.js.map +0 -1
  368. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  369. package/dist/chunk-R77UEZ4E.js.map +0 -1
  370. package/dist/chunk-SQGMNID3.js.map +0 -1
  371. package/dist/chunk-T33XF5ZC.js +0 -12922
  372. package/dist/chunk-T33XF5ZC.js.map +0 -1
  373. package/dist/chunk-XM25TVIE.js.map +0 -1
  374. package/docs/api/classes/ColumnFactory.md +0 -243
  375. package/docs/api/classes/ErrorBoundary.md +0 -144
  376. package/docs/api/classes/InvalidScopeError.md +0 -73
  377. package/docs/api/classes/Logger.md +0 -178
  378. package/docs/api/classes/MissingUserContextError.md +0 -66
  379. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  380. package/docs/api/classes/PermissionDeniedError.md +0 -73
  381. package/docs/api/classes/RBACAuditManager.md +0 -297
  382. package/docs/api/classes/RBACCache.md +0 -322
  383. package/docs/api/classes/RBACEngine.md +0 -171
  384. package/docs/api/classes/RBACError.md +0 -76
  385. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  386. package/docs/api/classes/SecureSupabaseClient.md +0 -160
  387. package/docs/api/classes/StorageUtils.md +0 -328
  388. package/docs/api/enums/FileCategory.md +0 -184
  389. package/docs/api/enums/LogLevel.md +0 -54
  390. package/docs/api/enums/RBACErrorCode.md +0 -228
  391. package/docs/api/enums/RPCFunction.md +0 -118
  392. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  393. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  394. package/docs/api/interfaces/AggregateConfig.md +0 -43
  395. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  396. package/docs/api/interfaces/AvatarProps.md +0 -128
  397. package/docs/api/interfaces/BadgeProps.md +0 -27
  398. package/docs/api/interfaces/ButtonProps.md +0 -53
  399. package/docs/api/interfaces/CalendarProps.md +0 -70
  400. package/docs/api/interfaces/CardProps.md +0 -66
  401. package/docs/api/interfaces/ColorPalette.md +0 -7
  402. package/docs/api/interfaces/ColorShade.md +0 -66
  403. package/docs/api/interfaces/ComplianceResult.md +0 -30
  404. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  405. package/docs/api/interfaces/DataRecord.md +0 -11
  406. package/docs/api/interfaces/DataTableAction.md +0 -249
  407. package/docs/api/interfaces/DataTableColumn.md +0 -504
  408. package/docs/api/interfaces/DataTableProps.md +0 -625
  409. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  410. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  411. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  412. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  413. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  414. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  415. package/docs/api/interfaces/ExportColumn.md +0 -90
  416. package/docs/api/interfaces/ExportOptions.md +0 -126
  417. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  418. package/docs/api/interfaces/FileMetadata.md +0 -129
  419. package/docs/api/interfaces/FileReference.md +0 -118
  420. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  421. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  422. package/docs/api/interfaces/FileUploadProps.md +0 -293
  423. package/docs/api/interfaces/FooterProps.md +0 -105
  424. package/docs/api/interfaces/FormFieldProps.md +0 -166
  425. package/docs/api/interfaces/FormProps.md +0 -113
  426. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  427. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  428. package/docs/api/interfaces/InputProps.md +0 -53
  429. package/docs/api/interfaces/LabelProps.md +0 -107
  430. package/docs/api/interfaces/LoggerConfig.md +0 -62
  431. package/docs/api/interfaces/LoginFormProps.md +0 -184
  432. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  433. package/docs/api/interfaces/NavigationContextType.md +0 -164
  434. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  435. package/docs/api/interfaces/NavigationItem.md +0 -120
  436. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  437. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  438. package/docs/api/interfaces/Organisation.md +0 -140
  439. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  440. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  441. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  442. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  443. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
  444. package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
  445. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  446. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  447. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  448. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  449. package/docs/api/interfaces/PaletteData.md +0 -41
  450. package/docs/api/interfaces/ParsedAddress.md +0 -120
  451. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  452. package/docs/api/interfaces/ProgressProps.md +0 -42
  453. package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
  454. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  455. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  456. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
  457. package/docs/api/interfaces/QuickFix.md +0 -52
  458. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  459. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  460. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  461. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  462. package/docs/api/interfaces/RBACConfig.md +0 -133
  463. package/docs/api/interfaces/RBACContext.md +0 -52
  464. package/docs/api/interfaces/RBACLogger.md +0 -112
  465. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  466. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  467. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  468. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  469. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  470. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  471. package/docs/api/interfaces/RBACResult.md +0 -58
  472. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  473. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  474. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  475. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  476. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  477. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  478. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  479. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  480. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  481. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  482. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  483. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  484. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  485. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  486. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  487. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  488. package/docs/api/interfaces/RouteConfig.md +0 -134
  489. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  490. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  491. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  492. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  493. package/docs/api/interfaces/SetupIssue.md +0 -41
  494. package/docs/api/interfaces/StorageConfig.md +0 -41
  495. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  496. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  497. package/docs/api/interfaces/StorageListOptions.md +0 -99
  498. package/docs/api/interfaces/StorageListResult.md +0 -41
  499. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  500. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  501. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  502. package/docs/api/interfaces/StyleImport.md +0 -19
  503. package/docs/api/interfaces/SwitchProps.md +0 -34
  504. package/docs/api/interfaces/TabsContentProps.md +0 -9
  505. package/docs/api/interfaces/TabsListProps.md +0 -9
  506. package/docs/api/interfaces/TabsProps.md +0 -9
  507. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  508. package/docs/api/interfaces/TextareaProps.md +0 -53
  509. package/docs/api/interfaces/ToastActionElement.md +0 -9
  510. package/docs/api/interfaces/ToastProps.md +0 -9
  511. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
  512. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
  513. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  514. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  515. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
  516. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  517. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  518. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  519. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  520. package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
  521. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  522. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
  523. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
  524. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  525. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  526. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  527. package/docs/api/interfaces/UserEventAccess.md +0 -118
  528. package/docs/api/interfaces/UserMenuProps.md +0 -86
  529. package/docs/api/interfaces/UserProfile.md +0 -63
  530. package/docs/migration/quick-migration-guide.md +0 -356
  531. package/docs/migration/service-architecture.md +0 -281
  532. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  533. package/src/components/EventSelector/EventSelector.tsx +0 -420
  534. package/src/components/EventSelector/index.ts +0 -3
  535. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  536. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
  537. package/src/components/OrganisationSelector/index.ts +0 -9
  538. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  539. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  540. package/src/hooks/useSecureDataAccess.ts +0 -681
  541. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
  542. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  543. /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
  544. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  545. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  546. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  547. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  548. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  549. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
package/src/rbac/api.ts CHANGED
@@ -30,7 +30,6 @@ import { createLogger } from '../utils/core/logger';
30
30
  import { enablePerformanceMonitoring } from './performance';
31
31
  import { getOrCreateRequest } from './request-deduplication';
32
32
  import { ContextValidator } from './utils/contextValidator';
33
- import type { AppConfig } from './utils/contextValidator';
34
33
 
35
34
  const log = createLogger('RBACAPI');
36
35
 
@@ -92,6 +91,15 @@ export function setupRBAC(supabase: SupabaseClient<Database>, config?: Partial<R
92
91
 
93
92
  }
94
93
 
94
+ /**
95
+ * Check if RBAC system is initialized
96
+ *
97
+ * @returns True if RBAC is initialized
98
+ */
99
+ export function isRBACInitialized(): boolean {
100
+ return globalEngine !== null;
101
+ }
102
+
95
103
  /**
96
104
  * Get the global RBAC engine
97
105
  *
@@ -126,42 +134,39 @@ export async function getAccessLevel(
126
134
  userId: UUID;
127
135
  scope: Scope;
128
136
  },
129
- appConfig?: AppConfig | null,
130
137
  appName?: string
131
138
  ): Promise<AccessLevel> {
132
- const engine = getEngine();
133
-
134
- // Check super admin status first - super admins bypass context requirements
135
- const isSuperAdminUser = await engine['checkSuperAdmin'](input.userId);
136
- if (isSuperAdminUser) {
137
- return 'super';
138
- }
139
-
140
- // Fetch app config if not provided
141
- let resolvedAppConfig: AppConfig | null = appConfig ?? null;
142
- let resolvedAppName = appName;
143
-
144
- if (!resolvedAppConfig && input.scope.appId) {
145
- resolvedAppConfig = await getAppConfig(input.scope.appId);
146
- }
147
-
148
- // Validate context using ContextValidator
149
- const validation = await ContextValidator.resolveRequiredContext(
150
- input.scope,
151
- resolvedAppConfig,
152
- resolvedAppName,
153
- engine['supabase']
154
- );
155
-
156
- if (!validation.isValid || !validation.resolvedScope) {
157
- throw validation.error || new OrganisationContextRequiredError();
139
+ try {
140
+ const engine = getEngine();
141
+
142
+ // Check super admin status first - super admins bypass context requirements
143
+ const isSuperAdminUser = await engine['checkSuperAdmin'](input.userId);
144
+ if (isSuperAdminUser) {
145
+ return 'super';
146
+ }
147
+
148
+ // For functions without pageId, default to organisation scope validation
149
+ // This is a safe default - most operations require organisation context
150
+ const validation = await ContextValidator.resolveScopeForPage(
151
+ input.scope,
152
+ 'organisation', // Default to organisation scope when no page context
153
+ appName,
154
+ engine['supabase']
155
+ );
156
+
157
+ if (!validation.isValid || !validation.resolvedScope) {
158
+ throw validation.error || new OrganisationContextRequiredError();
159
+ }
160
+
161
+ // Use resolved scope
162
+ return engine.getAccessLevel({
163
+ ...input,
164
+ scope: validation.resolvedScope
165
+ });
166
+ } catch (error) {
167
+ // Re-throw the error - this is an API function that should propagate errors
168
+ throw error;
158
169
  }
159
-
160
- // Use resolved scope
161
- return engine.getAccessLevel({
162
- ...input,
163
- scope: validation.resolvedScope
164
- });
165
170
  }
166
171
 
167
172
  /**
@@ -189,44 +194,46 @@ export async function getPermissionMap(
189
194
  userId: UUID;
190
195
  scope: Scope;
191
196
  },
192
- appConfig?: AppConfig | null,
193
197
  appName?: string
194
198
  ): Promise<PermissionMap> {
195
- const engine = getEngine();
196
-
197
- // Fetch app config if not provided
198
- let resolvedAppConfig: AppConfig | null = appConfig ?? null;
199
- let resolvedAppName = appName;
200
-
201
- if (!resolvedAppConfig && input.scope.appId) {
202
- resolvedAppConfig = await getAppConfig(input.scope.appId);
203
- }
204
-
205
- // Validate context using ContextValidator
206
- const validation = await ContextValidator.resolveRequiredContext(
207
- input.scope,
208
- resolvedAppConfig,
209
- resolvedAppName,
210
- engine['supabase']
211
- );
212
-
213
- if (!validation.isValid || !validation.resolvedScope) {
214
- throw validation.error || new OrganisationContextRequiredError();
199
+ try {
200
+ const engine = getEngine();
201
+
202
+ // For functions without pageId, default to organisation scope validation
203
+ // This is a safe default - most operations require organisation context
204
+ const validation = await ContextValidator.resolveScopeForPage(
205
+ input.scope,
206
+ 'organisation', // Default to organisation scope when no page context
207
+ appName,
208
+ engine['supabase']
209
+ );
210
+
211
+ if (!validation.isValid || !validation.resolvedScope) {
212
+ throw validation.error || new OrganisationContextRequiredError();
213
+ }
214
+
215
+ // Use resolved scope
216
+ return engine.getPermissionMap({
217
+ ...input,
218
+ scope: validation.resolvedScope
219
+ });
220
+ } catch (error) {
221
+ // Re-throw the error - this is an API function that should propagate errors
222
+ throw error;
215
223
  }
216
-
217
- // Use resolved scope
218
- return engine.getPermissionMap({
219
- ...input,
220
- scope: validation.resolvedScope
221
- });
222
224
  }
223
225
 
224
226
  export async function resolveAppContext(input: {
225
227
  userId: UUID;
226
228
  appName: string;
227
229
  }): Promise<RBACAppContext | null> {
228
- const engine = getEngine();
229
- return engine.resolveAppContext(input);
230
+ try {
231
+ const engine = getEngine();
232
+ return await engine.resolveAppContext(input);
233
+ } catch (error) {
234
+ // Re-throw the error - this is an API function that should propagate errors
235
+ throw error;
236
+ }
230
237
  }
231
238
 
232
239
  export async function getRoleContext(
@@ -234,24 +241,15 @@ export async function getRoleContext(
234
241
  userId: UUID;
235
242
  scope: Scope;
236
243
  },
237
- appConfig?: AppConfig | null,
238
244
  appName?: string
239
245
  ): Promise<RBACRoleContext> {
240
246
  const engine = getEngine();
241
247
 
242
- // Fetch app config if not provided
243
- let resolvedAppConfig: AppConfig | null = appConfig ?? null;
244
- let resolvedAppName = appName;
245
-
246
- if (!resolvedAppConfig && input.scope.appId) {
247
- resolvedAppConfig = await getAppConfig(input.scope.appId);
248
- }
249
-
250
- // Validate context using ContextValidator
251
- const validation = await ContextValidator.resolveRequiredContext(
248
+ // For functions without pageId, default to organisation scope validation
249
+ const validation = await ContextValidator.resolveScopeForPage(
252
250
  input.scope,
253
- resolvedAppConfig,
254
- resolvedAppName,
251
+ 'organisation', // Default to organisation scope when no page context
252
+ appName,
255
253
  engine['supabase']
256
254
  );
257
255
 
@@ -286,27 +284,35 @@ export async function getRoleContext(
286
284
  */
287
285
  export async function isPermitted(
288
286
  input: PermissionCheck,
289
- appConfig?: AppConfig | null,
290
- appName?: string
287
+ appName?: string,
288
+ /**
289
+ * Pre-computed super admin status to avoid duplicate checks.
290
+ * Pass null if not checked yet (will check), true if already checked and is super admin,
291
+ * or false if already checked and is not super admin.
292
+ * @default null
293
+ */
294
+ precomputedSuperAdmin: boolean | null = null
291
295
  ): Promise<boolean> {
292
296
  const engine = getEngine();
293
297
 
294
298
  // Check super admin status first - super admins bypass context requirements
295
299
  // Super admins have access to all permissions regardless of organisation context
296
- const isSuperAdminUser = await engine['checkSuperAdmin'](input.userId);
297
- if (isSuperAdminUser) {
300
+ // PERFORMANCE: Use precomputed value if provided to avoid duplicate checks
301
+ if (precomputedSuperAdmin === true) {
298
302
  return true;
299
303
  }
300
304
 
301
- // Fetch app config if not provided and we have appId
302
- let resolvedAppConfig: AppConfig | null = appConfig ?? null;
303
- let resolvedAppName = appName;
304
-
305
- if (!resolvedAppConfig && input.scope.appId) {
306
- resolvedAppConfig = await getAppConfig(input.scope.appId);
305
+ // If null, check super admin status
306
+ if (precomputedSuperAdmin === null) {
307
+ const isSuperAdminUser = await engine['checkSuperAdmin'](input.userId);
308
+ if (isSuperAdminUser) {
309
+ return true;
310
+ }
307
311
  }
312
+ // If precomputedSuperAdmin === false, skip check and proceed with permission check
308
313
 
309
- // If we have appId but no appName, try to get it from the database
314
+ // Get app name if not provided (for PORTAL/ADMIN special case)
315
+ let resolvedAppName = appName;
310
316
  if (!resolvedAppName && input.scope.appId) {
311
317
  try {
312
318
  const { data } = await engine['supabase']
@@ -323,10 +329,34 @@ export async function isPermitted(
323
329
  }
324
330
  }
325
331
 
326
- // Validate context using ContextValidator
327
- const validation = await ContextValidator.resolveRequiredContext(
332
+ // Get page scope type (required for all permission checks)
333
+ // All pages must have scope_type set - this is the single source of truth
334
+ let pageScopeType: 'event' | 'organisation' | 'both';
335
+ if (input.pageId) {
336
+ try {
337
+ const scopeType = await getPageScopeType(
338
+ input.pageId,
339
+ input.scope.appId,
340
+ resolvedAppName
341
+ );
342
+ if (!scopeType) {
343
+ throw new Error(`Page ${input.pageId} does not have scope_type set`);
344
+ }
345
+ pageScopeType = scopeType;
346
+ } catch (err) {
347
+ log.error('Failed to get page scope type:', err);
348
+ throw new Error(`Failed to determine page scope type: ${err instanceof Error ? err.message : String(err)}`);
349
+ }
350
+ } else {
351
+ // No pageId provided - default to organisation scope
352
+ // This should rarely happen, but provides a safe fallback
353
+ pageScopeType = 'organisation';
354
+ }
355
+
356
+ // Validate context using page-level scope (single source of truth)
357
+ const validation = await ContextValidator.resolveScopeForPage(
328
358
  input.scope,
329
- resolvedAppConfig,
359
+ pageScopeType,
330
360
  resolvedAppName,
331
361
  engine['supabase']
332
362
  );
@@ -338,7 +368,64 @@ export async function isPermitted(
338
368
  // Use resolved scope for permission check
339
369
  const validatedScope = validation.resolvedScope;
340
370
 
341
- // Create security context from validated scope
371
+ // Handle 'both' scope pages - check both scopes and return union
372
+ if (pageScopeType === 'both' && input.pageId) {
373
+ // Check permission with event scope
374
+ const eventScope: Scope = {
375
+ organisationId: validatedScope.organisationId, // Org derived from event
376
+ eventId: validatedScope.eventId,
377
+ appId: validatedScope.appId
378
+ };
379
+
380
+ // Check permission with organisation scope (if org is available separately)
381
+ // For 'both' pages, we check the permission in both contexts and return the union
382
+ // Higher permission wins (true > false, admin > user, etc.)
383
+
384
+ const eventSecurityContext: SecurityContext = {
385
+ userId: input.userId,
386
+ organisationId: eventScope.organisationId || null,
387
+ timestamp: new Date(),
388
+ };
389
+
390
+ const eventInput: PermissionCheck = {
391
+ ...input,
392
+ scope: eventScope
393
+ };
394
+
395
+ const hasEventPermission = await engine.isPermitted(eventInput, eventSecurityContext);
396
+
397
+ // Also check with organisation scope if we have a separate org context
398
+ // (This handles cases where user has org permissions separate from event)
399
+ if (validatedScope.organisationId && validatedScope.eventId) {
400
+ const orgScope: Scope = {
401
+ organisationId: validatedScope.organisationId,
402
+ eventId: undefined, // Clear event for org-only check
403
+ appId: validatedScope.appId
404
+ };
405
+
406
+ const orgSecurityContext: SecurityContext = {
407
+ userId: input.userId,
408
+ organisationId: orgScope.organisationId || null,
409
+ timestamp: new Date(),
410
+ };
411
+
412
+ const orgInput: PermissionCheck = {
413
+ ...input,
414
+ scope: orgScope
415
+ };
416
+
417
+ const hasOrgPermission = await engine.isPermitted(orgInput, orgSecurityContext);
418
+
419
+ // Return union (true if either scope grants permission)
420
+ // For access levels, the database function handles the "higher wins" logic
421
+ return hasEventPermission || hasOrgPermission;
422
+ }
423
+
424
+ // If only event scope available, return event permission
425
+ return hasEventPermission;
426
+ }
427
+
428
+ // Standard permission check for single-scope pages
342
429
  const securityContext: SecurityContext = {
343
430
  userId: input.userId,
344
431
  organisationId: validatedScope.organisationId || null,
@@ -362,13 +449,11 @@ export async function isPermitted(
362
449
  * and checks cache before making new requests. Uses session cache for page-level checks.
363
450
  *
364
451
  * @param input - Permission check input
365
- * @param appConfig - Optional app configuration
366
- * @param appName - Optional app name
452
+ * @param appName - Optional app name (for PORTAL/ADMIN special case)
367
453
  * @returns Promise resolving to permission result
368
454
  */
369
455
  export async function isPermittedCached(
370
456
  input: PermissionCheck,
371
- appConfig?: AppConfig | null,
372
457
  appName?: string
373
458
  ): Promise<boolean> {
374
459
  const { userId, scope, permission, pageId } = input;
@@ -391,7 +476,9 @@ export async function isPermittedCached(
391
476
  // Use request deduplication - if same request is in-flight, share the promise
392
477
  return getOrCreateRequest(input, async (checkInput) => {
393
478
  // Check permission with context validation
394
- const result = await isPermitted(checkInput, appConfig, appName);
479
+ // Note: We can't pass precomputedSuperAdmin here because getOrCreateRequest doesn't support it
480
+ // The super admin check in isPermitted will be cached, so it's not a huge performance hit
481
+ const result = await isPermitted(checkInput, appName, null);
395
482
 
396
483
  // Determine if this is a page-level check (has pageId or permission contains 'page.')
397
484
  const isPageLevelCheck = !!pageId || permission.includes('page.');
@@ -480,89 +567,81 @@ export async function isSuperAdmin(userId: UUID): Promise<boolean> {
480
567
  return engine['checkSuperAdmin'](userId);
481
568
  }
482
569
 
570
+
483
571
  /**
484
- * Get app configuration including requires_event setting
572
+ * Get page scope type (effective scope for a page)
485
573
  *
486
- * @param appId - App ID
487
- * @returns Promise resolving to app configuration
574
+ * Returns the scope type for a page. After migration, all pages have explicit scope_type set.
575
+ * This is the single source of truth for page scoping.
576
+ *
577
+ * @param pageId - Page ID (UUID) or page name
578
+ * @param appId - App ID (required if pageId is a page name)
579
+ * @param appName - App name (alternative to appId, used to resolve appId)
580
+ * @returns Promise resolving to page scope type: 'event', 'organisation', or 'both'
488
581
  */
489
- export async function getAppConfig(appId: UUID): Promise<AppConfig | null> {
490
- try {
491
- const engine = getEngine();
492
- return getAppConfigWithClient(engine['supabase'], appId);
493
- } catch (err) {
494
- // RBAC not initialized - return null gracefully
495
- if (err instanceof RBACNotInitializedError) {
496
- return null;
497
- }
498
- throw err;
499
- }
500
- }
501
-
502
- export async function getAppConfigWithClient(client: SupabaseClient | null | undefined, appId: UUID): Promise<AppConfig | null> {
503
- // Return null if client is not available
504
- if (!client) {
505
- return null;
506
- }
507
-
508
- // Cache key for app config - cache for 5 minutes (app config rarely changes)
509
- const cacheKey = `app_config:${appId}`;
510
-
511
- // Check cache first
512
- const cached = rbacCache.get<AppConfig>(cacheKey, true);
513
- if (cached !== null) {
514
- return cached;
515
- }
582
+ export async function getPageScopeType(
583
+ pageId: UUID | string,
584
+ appId?: UUID,
585
+ appName?: string
586
+ ): Promise<'event' | 'organisation' | 'both'> {
587
+ const engine = getEngine();
516
588
 
517
589
  try {
518
- const { data, error } = await client
519
- .from('rbac_apps')
520
- .select('requires_event, name')
521
- .eq('id', appId)
522
- .eq('is_active', true)
523
- .single() as { data: { requires_event: boolean; name: string } | null; error: any };
524
-
525
- if (error || !data) {
526
- return null;
590
+ // Resolve appId if not provided
591
+ let resolvedAppId = appId;
592
+ if (!resolvedAppId && appName) {
593
+ // Get appId directly from app name
594
+ const { data: app } = await engine['supabase']
595
+ .from('rbac_apps')
596
+ .select('id')
597
+ .eq('name', appName)
598
+ .eq('is_active', true)
599
+ .single() as { data: { id: UUID } | null; error: any };
600
+ resolvedAppId = app?.id;
527
601
  }
528
-
529
- const appConfig: AppConfig = { requires_event: data.requires_event ?? false };
530
602
 
531
- // Cache the result for 5 minutes (300000ms) with session cache enabled
532
- // App config rarely changes, so we can cache it longer
533
- rbacCache.set(cacheKey, appConfig, 5 * 60 * 1000, true);
603
+ if (!resolvedAppId) {
604
+ throw new Error(`Could not resolve appId for page ${pageId}`);
605
+ }
534
606
 
535
- return appConfig;
536
- } catch (err) {
537
- log.error('Error fetching app config:', err);
538
- return null;
539
- }
540
- }
541
-
542
- /**
543
- * Get app configuration by app name
544
- *
545
- * @param appName - App name
546
- * @returns Promise resolving to app configuration
547
- */
548
- export async function getAppConfigByName(appName: string): Promise<AppConfig | null> {
549
- const engine = getEngine();
550
- try {
607
+ // Resolve pageId if it's a page name
608
+ let resolvedPageId: UUID | string = pageId;
609
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
610
+ if (!uuidRegex.test(pageId)) {
611
+ // It's a page name, resolve to UUID
612
+ const { data: page } = await engine['supabase']
613
+ .from('rbac_app_pages')
614
+ .select('id')
615
+ .eq('app_id', resolvedAppId)
616
+ .eq('page_name', pageId)
617
+ .maybeSingle() as { data: { id: UUID } | null; error: any };
618
+ resolvedPageId = page?.id || pageId;
619
+ }
620
+
621
+ // If still not a UUID, can't proceed
622
+ if (!uuidRegex.test(resolvedPageId)) {
623
+ throw new Error(`Could not resolve pageId ${pageId} to a valid UUID`);
624
+ }
625
+
626
+ // Call the database function to get scope type (always returns a value)
551
627
  const { data, error } = await engine['supabase']
552
- .from('rbac_apps')
553
- .select('requires_event, name')
554
- .eq('name', appName)
555
- .eq('is_active', true)
556
- .single() as { data: { requires_event: boolean; name: string } | null; error: any };
557
-
558
- if (error || !data) {
559
- return null;
628
+ .rpc('get_page_scope_type' as any, {
629
+ p_page_id: resolvedPageId
630
+ }) as { data: 'event' | 'organisation' | 'both' | null; error: any };
631
+
632
+ if (error) {
633
+ log.error('Error fetching page scope type:', { pageId, appId, error });
634
+ throw new Error(`Failed to get page scope type: ${error.message}`);
560
635
  }
561
-
562
- return { requires_event: data.requires_event ?? false };
636
+
637
+ if (!data) {
638
+ throw new Error(`Page ${resolvedPageId} does not have scope_type set`);
639
+ }
640
+
641
+ return data;
563
642
  } catch (err) {
564
- log.error('Error fetching app config by name:', err);
565
- return null;
643
+ log.error('Error fetching page scope type:', err);
644
+ throw err instanceof Error ? err : new Error(`Failed to get page scope type: ${String(err)}`);
566
645
  }
567
646
  }
568
647
 
@@ -211,12 +211,15 @@ export function NavigationProvider({
211
211
  const permission = item.permissions[0];
212
212
 
213
213
  // Call useCan hook for actual permission checking
214
+ // Pass null for super admin status (not checked yet - hook will check if needed)
214
215
  const { can, error } = useCan(
215
216
  user.id,
216
217
  currentScope,
217
218
  permission,
218
219
  item.pageId,
219
- true // useCache
220
+ true, // useCache
221
+ null, // precomputedSuperAdmin - not checked yet
222
+ undefined // appName
220
223
  );
221
224
 
222
225
  // Handle errors gracefully - allow access when there are permission check errors (graceful degradation)