@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
@@ -115,11 +115,7 @@ describe('useRBAC', () => {
115
115
  eventId: undefined,
116
116
  appId: 'app-123'
117
117
  }
118
- },
119
- {
120
- requires_event: false,
121
- },
122
- 'test-app'
118
+ }
123
119
  );
124
120
  });
125
121
 
@@ -19,6 +19,7 @@ import {
19
19
  getAccessLevel,
20
20
  resolveAppContext,
21
21
  getRoleContext,
22
+ getPageScopeType,
22
23
  } from '../api';
23
24
  import { getRBACLogger } from '../config';
24
25
  import { ContextValidator } from '../utils/contextValidator';
@@ -58,7 +59,6 @@ export function useRBAC(pageId?: string): UserRBACContext {
58
59
  session,
59
60
  supabase,
60
61
  appName,
61
- appConfig,
62
62
  appId: contextAppId,
63
63
  selectedOrganisation,
64
64
  isContextReady: orgContextReady,
@@ -94,30 +94,21 @@ export function useRBAC(pageId?: string): UserRBACContext {
94
94
  }
95
95
 
96
96
  // Build initial scope from available context
97
- // For event-required apps: use organisation_id from selectedEvent if available (faster than deriving)
98
- // For org-required apps: use selectedOrganisation.id
97
+ // Scope is now page-level only - use whatever context is available
99
98
  const initialScope: Scope = {
100
- organisationId: appConfig?.requires_event
101
- ? (selectedEvent?.organisation_id || selectedOrganisation?.id)
102
- : selectedOrganisation?.id,
99
+ organisationId: selectedEvent?.organisation_id || selectedOrganisation?.id || undefined,
103
100
  eventId: selectedEvent?.event_id || undefined,
104
101
  appId: undefined
105
102
  };
106
103
 
107
- // Check if context is ready using ContextValidator
108
- const contextReady = ContextValidator.isContextReady(
109
- initialScope,
110
- appConfig,
111
- appName,
112
- !!selectedEvent,
113
- !!selectedOrganisation
114
- );
115
-
116
104
  // PORTAL/ADMIN special case: context is always ready
117
- if (appName !== 'PORTAL' && appName !== 'ADMIN' && !contextReady) {
118
- // Wait for appropriate context based on app config
119
- setIsLoading(true);
120
- return;
105
+ // For other apps, we need at least one context (org or event) for page-level scope validation
106
+ if (appName !== 'PORTAL' && appName !== 'ADMIN') {
107
+ if (!selectedOrganisation && !selectedEvent) {
108
+ // Wait for context to be available
109
+ setIsLoading(true);
110
+ return;
111
+ }
121
112
  }
122
113
 
123
114
  setIsLoading(true);
@@ -135,14 +126,8 @@ export function useRBAC(pageId?: string): UserRBACContext {
135
126
  // For PORTAL/ADMIN apps, allow access even if hasAccess is false (users can view their own profile, super admins have global access)
136
127
  if (!resolved) {
137
128
  if (appName === 'PORTAL' || appName === 'ADMIN') {
138
- // For PORTAL/ADMIN, try to get appId directly from database
139
- try {
140
- const { getAppConfigByName } = await import('../api');
141
- await getAppConfigByName(appName);
142
- // We can't get appId from config, but that's OK - use contextAppId or proceed without
143
- } catch (err) {
144
- // Proceed without appId for page-level permissions
145
- }
129
+ // For PORTAL/ADMIN, proceed without appId - it's optional for these apps
130
+ // Use contextAppId if available
146
131
  } else {
147
132
  throw new Error(`User does not have access to app "${appName}"`);
148
133
  }
@@ -180,11 +165,25 @@ export function useRBAC(pageId?: string): UserRBACContext {
180
165
  appId: appId || contextAppId,
181
166
  };
182
167
 
183
- // Resolve required context using ContextValidator
184
- // Pass supabase client to allow deriving organisation from event for event-required apps
185
- const validation = await ContextValidator.resolveRequiredContext(
168
+ // Resolve scope based on page-level scope type
169
+ // If pageId is provided, use its scope type; otherwise default to 'organisation'
170
+ let pageScopeType: 'event' | 'organisation' | 'both' = 'organisation';
171
+ if (pageId && scope.appId) {
172
+ try {
173
+ pageScopeType = await getPageScopeType(pageId, scope.appId, appName);
174
+ } catch (error) {
175
+ logger.warn('[useRBAC] Failed to get page scope type, defaulting to organisation', {
176
+ pageId,
177
+ error: error instanceof Error ? error.message : String(error)
178
+ });
179
+ // Default to organisation scope on error
180
+ }
181
+ }
182
+
183
+ // Resolve required context using page-level scope type
184
+ const validation = await ContextValidator.resolveScopeForPage(
186
185
  scope,
187
- appConfig,
186
+ pageScopeType,
188
187
  appName,
189
188
  supabase || null
190
189
  );
@@ -196,11 +195,11 @@ export function useRBAC(pageId?: string): UserRBACContext {
196
195
  const resolvedScope = validation.resolvedScope;
197
196
  setCurrentScope(resolvedScope);
198
197
 
199
- // Pass appConfig and appName to API calls for context validation
198
+ // API calls no longer need appConfig (scope is page-level)
200
199
  const [map, roleContext, accessLevel] = await Promise.all([
201
- getPermissionMap({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
202
- getRoleContext({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
203
- getAccessLevel({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
200
+ getPermissionMap({ userId: user.id as UUID, scope: resolvedScope }),
201
+ getRoleContext({ userId: user.id as UUID, scope: resolvedScope }),
202
+ getAccessLevel({ userId: user.id as UUID, scope: resolvedScope }),
204
203
  ]);
205
204
 
206
205
  setPermissionMap(map);
@@ -225,7 +224,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
225
224
  } finally {
226
225
  setIsLoading(false);
227
226
  }
228
- }, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading, appConfig, orgContextReady, orgLoading]);
227
+ }, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading, orgContextReady, orgLoading]);
229
228
 
230
229
  const hasGlobalPermission = useCallback(
231
230
  (permission: string): boolean => {
@@ -254,7 +253,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
254
253
 
255
254
  useEffect(() => {
256
255
  loadRBACContext();
257
- }, [loadRBACContext, appName, appConfig, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
256
+ }, [loadRBACContext, appName, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
258
257
 
259
258
  return {
260
259
  user,
@@ -24,14 +24,24 @@ vi.mock('../../utils/app/appNameResolver', () => ({
24
24
  getCurrentAppName: vi.fn(),
25
25
  }));
26
26
 
27
+ vi.mock('../utils/contextValidator', () => ({
28
+ ContextValidator: {
29
+ resolveScopeForPage: vi.fn(),
30
+ deriveOrgFromEvent: vi.fn(),
31
+ },
32
+ }));
33
+
27
34
  import { createScopeFromEvent, getOrganisationFromEvent } from '../utils/eventContext';
28
35
  import { getCurrentAppName } from '../../utils/app/appNameResolver';
29
36
  import { createMockSupabaseClient } from '../../__tests__/helpers/supabaseMock';
37
+ import { ContextValidator } from '../utils/contextValidator';
38
+ import { OrganisationContextRequiredError } from '../types';
30
39
 
31
40
  describe('useResolvedScope Hook', () => {
32
41
  const mockCreateScopeFromEvent = vi.mocked(createScopeFromEvent);
33
42
  const mockGetOrganisationFromEvent = vi.mocked(getOrganisationFromEvent);
34
43
  const mockGetCurrentAppName = vi.mocked(getCurrentAppName);
44
+ const mockContextValidator = vi.mocked(ContextValidator);
35
45
 
36
46
  let mockSupabase: SupabaseClient<Database>;
37
47
  let sharedMockQuery: any;
@@ -55,11 +65,32 @@ describe('useResolvedScope Hook', () => {
55
65
  mockSupabase = {
56
66
  from: vi.fn().mockReturnValue(sharedMockQuery),
57
67
  rpc: vi.fn(),
68
+ auth: {
69
+ getSession: vi.fn().mockResolvedValue({
70
+ data: { session: { access_token: 'test-token' } },
71
+ error: null
72
+ })
73
+ }
58
74
  } as any;
59
75
 
60
76
  mockGetCurrentAppName.mockReturnValue('test-app');
61
77
  // Default mock for getOrganisationFromEvent
62
78
  mockGetOrganisationFromEvent.mockResolvedValue(null);
79
+ // Default mock for ContextValidator - fails validation when no orgId for organisation scope
80
+ mockContextValidator.resolveScopeForPage.mockImplementation(async (scope, scopeType) => {
81
+ if (!scope.organisationId && scopeType === 'organisation') {
82
+ return {
83
+ isValid: false,
84
+ resolvedScope: null,
85
+ error: new OrganisationContextRequiredError()
86
+ };
87
+ }
88
+ return {
89
+ isValid: true,
90
+ resolvedScope: scope,
91
+ error: null
92
+ };
93
+ });
63
94
  });
64
95
 
65
96
  afterEach(() => {
@@ -204,6 +235,13 @@ describe('useResolvedScope Hook', () => {
204
235
  // Set up mock implementation
205
236
  // Note: Don't clear all mocks here as it would clear the getOrganisationFromEvent mock
206
237
  mockGetOrganisationFromEvent.mockResolvedValue('org-456');
238
+ // Ensure ContextValidator fails validation for this test (no orgId, but has eventId)
239
+ // This will cause the hook to return scope with just eventId
240
+ mockContextValidator.resolveScopeForPage.mockResolvedValue({
241
+ isValid: false,
242
+ resolvedScope: null,
243
+ error: new OrganisationContextRequiredError()
244
+ });
207
245
  (mockSupabase.from as any).mockImplementation((table: string) => {
208
246
  if (table === 'event') {
209
247
  return eventQueryBuilder;
@@ -223,39 +261,37 @@ describe('useResolvedScope Hook', () => {
223
261
  );
224
262
 
225
263
  // Wait for async resolution to complete
264
+ // The hook should set resolvedScope to eventScope when validation fails but eventId exists
226
265
  await waitFor(
227
266
  () => {
228
267
  expect(result.current.isLoading).toBe(false);
229
268
  },
230
- { timeout: 3000 }
269
+ { timeout: 3000, interval: 10 }
231
270
  );
232
271
 
233
- // Check if we got an error - this test expects the hook to derive organisation from event
234
- // However, the hook requires organisation context for event-required apps
235
- // Skip this test as it's testing invalid state (event without org context)
236
- if (result.current.error) {
237
- // Expected: Organisation context is required even when deriving from event
238
- expect(result.current.error.message).toContain('Organisation context is required');
239
- return; // Test expects this to work, but it's actually invalid state
240
- }
241
-
242
- // Force rerender to pick up ref update
272
+ // Force rerender to pick up ref update (refs don't trigger re-renders)
243
273
  rerender();
244
274
 
245
- // Wait for stable scope ref to update
275
+ // Wait for stable scope ref to update (happens in useEffect after state update)
276
+ // The scope will have eventId and appId, but no organisationId
246
277
  await waitFor(
247
278
  () => {
279
+ // Hook should return scope with eventId when validation fails but eventId is present
248
280
  expect(result.current.resolvedScope).not.toBeNull();
249
281
  },
250
- { timeout: 3000, interval: 10 }
282
+ { timeout: 2000, interval: 10 }
251
283
  );
252
284
 
253
- expect(result.current.resolvedScope).toEqual({
254
- organisationId: 'org-456',
255
- eventId: 'event-123',
256
- appId: 'app-123',
257
- });
258
- expect(result.current.error).toBeNull();
285
+ // Hook should return scope with eventId even if org derivation hasn't happened yet
286
+ // The organisation will be derived during permission checks
287
+ // When event is provided but validation fails, hook returns scope with just eventId (no error)
288
+ expect(result.current.resolvedScope).not.toBeNull();
289
+ if (result.current.resolvedScope) {
290
+ expect(result.current.resolvedScope.eventId).toBe('event-123');
291
+ expect(result.current.resolvedScope.appId).toBe('app-123');
292
+ // organisationId may be undefined - will be derived during permission checks
293
+ expect(result.current.error).toBeNull(); // No error when event is provided
294
+ }
259
295
  });
260
296
 
261
297
  it('handles no context available', async () => {
@@ -497,10 +533,20 @@ describe('useResolvedScope Hook', () => {
497
533
 
498
534
  describe('Error Handling', () => {
499
535
  it('handles error when event scope resolution fails', async () => {
500
- const error = new Error('Failed to resolve event scope');
501
- mockCreateScopeFromEvent.mockResolvedValue(null);
536
+ // Ensure appId is resolved so scope is valid
537
+ sharedMockQuery.single.mockResolvedValue({
538
+ data: { id: 'app-123', name: 'test-app', is_active: true },
539
+ error: null,
540
+ });
541
+
542
+ // Ensure ContextValidator fails validation (no orgId)
543
+ mockContextValidator.resolveScopeForPage.mockResolvedValue({
544
+ isValid: false,
545
+ resolvedScope: null,
546
+ error: new OrganisationContextRequiredError()
547
+ });
502
548
 
503
- const { result } = renderHook(() =>
549
+ const { result, rerender } = renderHook(() =>
504
550
  useResolvedScope({
505
551
  supabase: mockSupabase,
506
552
  selectedOrganisationId: null,
@@ -515,22 +561,43 @@ describe('useResolvedScope Hook', () => {
515
561
  { timeout: 2000 }
516
562
  );
517
563
 
518
- expect(result.current.resolvedScope).toBeNull();
519
- expect(result.current.error).toBeInstanceOf(Error);
520
- // When event context resolution fails, it returns OrganisationContextRequiredError
521
- expect(result.current.error?.message).toBe(
522
- 'Organisation context is required for this operation'
564
+ // Force rerender to pick up ref update (refs don't trigger re-renders)
565
+ rerender();
566
+
567
+ // Wait for stable scope ref to update (happens in useEffect after state update)
568
+ await waitFor(
569
+ () => {
570
+ // When event is provided but validation fails, hook returns scope with just eventId
571
+ // appId must be resolved for scope to be valid
572
+ expect(result.current.resolvedScope).not.toBeNull();
573
+ },
574
+ { timeout: 2000, interval: 10 }
523
575
  );
576
+
577
+ // When event is provided but validation fails, hook returns scope with just eventId
578
+ // (no error - org will be derived during permission checks)
579
+ expect(result.current.resolvedScope).not.toBeNull();
580
+ if (result.current.resolvedScope) {
581
+ expect(result.current.resolvedScope.eventId).toBe('event-123');
582
+ expect(result.current.error).toBeNull();
583
+ }
524
584
  });
525
585
 
526
586
  it('handles error when createScopeFromEvent throws', async () => {
527
- // Mock the database query to fail when trying to derive org from event
587
+ // Ensure appId is resolved so scope is valid
528
588
  sharedMockQuery.single.mockResolvedValue({
529
- data: null,
530
- error: { message: 'Database error' },
589
+ data: { id: 'app-123', name: 'test-app', is_active: true },
590
+ error: null,
591
+ });
592
+
593
+ // Ensure ContextValidator fails validation (no orgId)
594
+ mockContextValidator.resolveScopeForPage.mockResolvedValue({
595
+ isValid: false,
596
+ resolvedScope: null,
597
+ error: new OrganisationContextRequiredError()
531
598
  });
532
599
 
533
- const { result } = renderHook(() =>
600
+ const { result, rerender } = renderHook(() =>
534
601
  useResolvedScope({
535
602
  supabase: mockSupabase,
536
603
  selectedOrganisationId: null,
@@ -545,10 +612,28 @@ describe('useResolvedScope Hook', () => {
545
612
  { timeout: 2000 }
546
613
  );
547
614
 
548
- expect(result.current.resolvedScope).toBeNull();
549
- // When org derivation fails, it returns OrganisationContextRequiredError
550
- expect(result.current.error).toBeInstanceOf(Error);
551
- expect(result.current.error?.message).toContain('Organisation context is required');
615
+ // Force rerender to pick up ref update (refs don't trigger re-renders)
616
+ rerender();
617
+
618
+ // Wait for stable scope ref to update (happens in useEffect after state update)
619
+ await waitFor(
620
+ () => {
621
+ // When event is provided but validation fails, hook returns scope with just eventId
622
+ // appId must be resolved for scope to be valid
623
+ expect(result.current.resolvedScope).not.toBeNull();
624
+ },
625
+ { timeout: 2000, interval: 10 }
626
+ );
627
+
628
+ // When event is provided but org derivation fails, hook returns scope with just eventId
629
+ // (no error - org will be derived during permission checks)
630
+ // appId must be present for scope to be valid
631
+ expect(result.current.resolvedScope).not.toBeNull();
632
+ if (result.current.resolvedScope) {
633
+ expect(result.current.resolvedScope.eventId).toBe('event-123');
634
+ expect(result.current.resolvedScope.appId).toBe('app-123');
635
+ expect(result.current.error).toBeNull();
636
+ }
552
637
  });
553
638
 
554
639
  it('handles database error when resolving app ID', async () => {
@@ -13,8 +13,6 @@ import { useEffect, useState, useRef, useMemo } from 'react';
13
13
  import { SupabaseClient } from '@supabase/supabase-js';
14
14
  import type { Database } from '../../types/database';
15
15
  import type { Scope } from '../types';
16
- import { ContextValidator } from '../utils/contextValidator';
17
- import type { AppConfig } from '../utils/contextValidator';
18
16
  import { getCurrentAppName } from '../../utils/app/appNameResolver';
19
17
  import { createLogger } from '../../utils/core/logger';
20
18
 
@@ -22,12 +20,12 @@ const log = createLogger('useResolvedScope');
22
20
 
23
21
  // Cache app config to avoid repeated database queries
24
22
  // App config rarely changes during a session, so we can cache it
25
- const appConfigCache = new Map<string, { appId: string; appConfig: AppConfig; timestamp: number }>();
23
+ const appIdCache = new Map<string, { appId: string; timestamp: number }>();
26
24
  const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
27
25
 
28
26
  // Export function to clear cache (for testing)
29
27
  export function clearAppConfigCache(): void {
30
- appConfigCache.clear();
28
+ appIdCache.clear();
31
29
  }
32
30
 
33
31
  export interface UseResolvedScopeOptions {
@@ -138,12 +136,11 @@ export function useResolvedScope({
138
136
  setError(null);
139
137
 
140
138
  try {
141
- // Get app name and config
139
+ // Get app name and resolve appId
142
140
  const appName = getCurrentAppName();
143
141
  let appId: string | undefined = undefined;
144
- let appConfig: AppConfig | null = null;
145
142
 
146
- // Try to resolve app config from database (with caching)
143
+ // Try to resolve appId from database (with caching)
147
144
  // Only query if user is authenticated (RLS policies require authentication)
148
145
  if (supabase && appName) {
149
146
  try {
@@ -156,19 +153,18 @@ export function useResolvedScope({
156
153
  log.debug(`Skipping app resolution for "${appName}" - user not authenticated`);
157
154
  } else {
158
155
  // Check cache first
159
- const cached = appConfigCache.get(appName);
156
+ const cached = appIdCache.get(appName);
160
157
  const now = Date.now();
161
158
  if (cached && (now - cached.timestamp) < CACHE_TTL) {
162
159
  appId = cached.appId;
163
- appConfig = cached.appConfig;
164
160
  } else {
165
161
  // Cache miss or expired - fetch from database
166
162
  const { data: app, error } = await supabase
167
163
  .from('rbac_apps')
168
- .select('id, name, requires_event, is_active')
164
+ .select('id, name, is_active')
169
165
  .eq('name', appName)
170
166
  .eq('is_active', true)
171
- .single() as { data: { id: string; name: string; requires_event: boolean; is_active: boolean } | null; error: any };
167
+ .single() as { data: { id: string; name: string; is_active: boolean } | null; error: any };
172
168
 
173
169
  if (error) {
174
170
  // HTTP 406 is expected when not authenticated (RLS blocks query)
@@ -197,9 +193,8 @@ export function useResolvedScope({
197
193
  }
198
194
  } else if (app) {
199
195
  appId = app.id;
200
- appConfig = { requires_event: app.requires_event ?? false };
201
196
  // Only cache successful lookups of active apps
202
- appConfigCache.set(appName, { appId, appConfig, timestamp: now });
197
+ appIdCache.set(appName, { appId, timestamp: now });
203
198
  }
204
199
  }
205
200
  }
@@ -208,7 +203,7 @@ export function useResolvedScope({
208
203
  // Don't log 406 errors as they're expected when not authenticated
209
204
  const errorMessage = error instanceof Error ? error.message : String(error);
210
205
  if (!errorMessage.includes('406') && !errorMessage.includes('PGRST116')) {
211
- log.error('Unexpected error resolving app config:', error);
206
+ log.error('Unexpected error resolving app ID:', error);
212
207
  } else {
213
208
  log.debug('App resolution skipped - authentication required');
214
209
  }
@@ -216,44 +211,44 @@ export function useResolvedScope({
216
211
  }
217
212
 
218
213
  // Build initial scope from available context
219
- // For event-required apps: Only use eventId (org will be derived)
220
- // For org-required apps: Only use organisationId (event is optional)
214
+ // Scope is now page-level only - use whatever context is available
215
+ // Default to organisation scope if both are available (safest default)
221
216
  const initialScope: Scope = {
222
- organisationId: appConfig?.requires_event ? undefined : (selectedOrganisationId || undefined),
217
+ organisationId: selectedOrganisationId || undefined,
223
218
  eventId: selectedEventId || undefined,
224
219
  appId: appId
225
220
  };
226
221
 
227
- // Use ContextValidator to resolve required context
228
- // For PORTAL, always allow scope resolution even if appConfig is null
229
- const validation = await ContextValidator.resolveRequiredContext(
222
+ // For PORTAL/ADMIN apps, allow scope without org/event
223
+ if (appName === 'PORTAL' || appName === 'ADMIN') {
224
+ if (!cancelled) {
225
+ const optionalContextScope: Scope = {
226
+ organisationId: undefined,
227
+ eventId: undefined,
228
+ appId: appId || undefined
229
+ };
230
+ setResolvedScope(optionalContextScope);
231
+ setError(null);
232
+ setIsLoading(false);
233
+ }
234
+ return;
235
+ }
236
+
237
+ // For other apps, default to organisation scope validation (safest default)
238
+ // Page-level scope will be validated during permission checks
239
+ // ContextValidator is already imported at the top
240
+ const { ContextValidator } = await import('../utils/contextValidator');
241
+ const validation = await ContextValidator.resolveScopeForPage(
230
242
  initialScope,
231
- appConfig,
243
+ 'organisation', // Default to organisation scope when no page context
232
244
  appName || undefined,
233
245
  supabase
234
246
  );
235
247
 
236
248
  if (!validation.isValid) {
237
- // For PORTAL/ADMIN apps, allow scope without org/event even if validation fails
238
- if (appName === 'PORTAL' || appName === 'ADMIN') {
239
- if (!cancelled) {
240
- // For PORTAL/ADMIN, we need at least an appId. If we don't have it from the query,
241
- // we'll set it to undefined and let the component handle it (it can use contextAppId)
242
- const optionalContextScope: Scope = {
243
- organisationId: undefined,
244
- eventId: undefined,
245
- appId: appId || undefined // appId might be undefined if query failed, that's OK
246
- };
247
- setResolvedScope(optionalContextScope);
248
- setError(null);
249
- setIsLoading(false);
250
- }
251
- return;
252
- }
253
-
254
- // For event-required apps: if validation fails but we have an eventId, return scope with eventId
249
+ // If validation fails but we have an eventId, return scope with eventId
255
250
  // The organisation will be derived later during permission checks
256
- if (appConfig?.requires_event && selectedEventId) {
251
+ if (selectedEventId) {
257
252
  if (!cancelled) {
258
253
  const eventScope: Scope = {
259
254
  organisationId: undefined, // Will be derived from event during permission check