@jmruthers/pace-core 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -117,7 +117,11 @@ describe('useResolvedScope Hook', () => {
117
117
  );
118
118
 
119
119
  expect(result.current.isLoading).toBe(true);
120
- expect(result.current.resolvedScope).toBeNull();
120
+ // Scope is now returned immediately if org/event IDs are available (for performance)
121
+ // Only appId resolution happens asynchronously
122
+ expect(result.current.resolvedScope).not.toBeNull();
123
+ expect(result.current.resolvedScope?.organisationId).toBe('org-123');
124
+ expect(result.current.resolvedScope?.eventId).toBe('event-123');
121
125
 
122
126
  // Wait for async app ID resolution to complete
123
127
  await waitFor(
@@ -127,18 +131,9 @@ describe('useResolvedScope Hook', () => {
127
131
  { timeout: 2000 }
128
132
  );
129
133
 
130
- // The stable scope ref is updated in a useEffect that depends on resolvedScope state
131
- // The return value checks stableScope.organisationId, so we need to wait for the ref update
132
- // Force a re-render to pick up the ref change (refs don't trigger re-renders)
133
- rerender();
134
-
135
- await waitFor(
136
- () => {
137
- expect(result.current.resolvedScope).not.toBeNull();
138
- expect(result.current.resolvedScope?.organisationId).toBe('org-123');
139
- },
140
- { timeout: 2000, interval: 10 }
141
- );
134
+ // Scope should still be available after loading completes
135
+ expect(result.current.resolvedScope).not.toBeNull();
136
+ expect(result.current.resolvedScope?.organisationId).toBe('org-123');
142
137
 
143
138
  // Verify the mock was called (it should be called to fetch app ID)
144
139
  // Note: With caching, this might not be called on every test run
@@ -310,11 +305,19 @@ describe('useResolvedScope Hook', () => {
310
305
  { timeout: 2000 }
311
306
  );
312
307
 
313
- expect(result.current.resolvedScope).toBeNull();
314
- expect(result.current.error).toBeInstanceOf(Error);
315
- expect(result.current.error?.message).toBe(
316
- 'Organisation context is required for this operation'
317
- );
308
+ // Hook can return scope with just appId if appId is resolved (for non-PORTAL/ADMIN apps)
309
+ // Or null if appId is not resolved and no org/event context is available
310
+ // The hook no longer throws errors - it just returns null scope
311
+ if (result.current.resolvedScope) {
312
+ // If scope is returned, it should only have appId (no org/event context)
313
+ expect(result.current.resolvedScope.organisationId).toBeUndefined();
314
+ expect(result.current.resolvedScope.eventId).toBeUndefined();
315
+ expect(result.current.resolvedScope.appId).toBeDefined();
316
+ } else {
317
+ // If no scope is returned, there should be no error (hook doesn't throw errors)
318
+ expect(result.current.resolvedScope).toBeNull();
319
+ }
320
+ expect(result.current.error).toBeNull();
318
321
  });
319
322
  });
320
323
 
@@ -942,8 +945,19 @@ describe('useResolvedScope Hook', () => {
942
945
  { timeout: 2000 }
943
946
  );
944
947
 
945
- expect(result.current.resolvedScope).toBeNull();
946
- expect(result.current.error).toBeInstanceOf(Error);
948
+ // Hook can return scope with just appId if appId is resolved (for non-PORTAL/ADMIN apps)
949
+ // Empty string org ID is treated as no org context, but appId can still be in scope
950
+ if (result.current.resolvedScope) {
951
+ // If scope is returned, it should only have appId (no org/event context)
952
+ expect(result.current.resolvedScope.organisationId).toBeUndefined();
953
+ expect(result.current.resolvedScope.eventId).toBeUndefined();
954
+ expect(result.current.resolvedScope.appId).toBeDefined();
955
+ } else {
956
+ // If no scope is returned, there should be no error (hook doesn't throw errors)
957
+ expect(result.current.resolvedScope).toBeNull();
958
+ }
959
+ // Hook no longer throws errors - it just returns null scope or scope with appId
960
+ expect(result.current.error).toBeNull();
947
961
  });
948
962
 
949
963
  it('handles empty string event ID', async () => {
@@ -1030,6 +1044,7 @@ describe('useResolvedScope Hook', () => {
1030
1044
  supabase: mockSupabase,
1031
1045
  selectedOrganisationId: null,
1032
1046
  selectedEventId: 'event-123',
1047
+ selectedEventOrganisationId: 'org-456', // Pass org ID from event explicitly
1033
1048
  })
1034
1049
  );
1035
1050
 
@@ -1040,25 +1055,21 @@ describe('useResolvedScope Hook', () => {
1040
1055
  { timeout: 3000 }
1041
1056
  );
1042
1057
 
1043
- // Check for errors - organisation context is required even when deriving from event
1044
- // The hook requires organisation context for event-required apps
1045
- if (result.current.error) {
1046
- // Expected: Organisation context is required
1047
- expect(result.current.error.message).toContain('Organisation context is required');
1048
- // Test expects this to work, but it's actually the correct behavior to require org context
1049
- return;
1050
- }
1051
-
1052
- // If no error (shouldn't happen with current implementation), verify scope
1058
+ // Hook now requires selectedEventOrganisationId to be passed explicitly
1059
+ // It no longer derives org from event automatically
1053
1060
  if (result.current.resolvedScope) {
1054
- // Should use resolved app ID (app-123) over event scope app ID
1055
- expect(result.current.resolvedScope.organisationId).toBeDefined();
1061
+ // Should use org ID from selectedEventOrganisationId
1062
+ expect(result.current.resolvedScope.organisationId).toBe('org-456');
1056
1063
  expect(result.current.resolvedScope.eventId).toBe('event-123');
1057
1064
  // AppId will be set when app lookup succeeds (needed for appConfig)
1058
1065
  if (result.current.resolvedScope.appId) {
1059
1066
  expect(result.current.resolvedScope.appId).toBeDefined();
1060
1067
  }
1068
+ } else {
1069
+ // If no scope, it means no valid context (shouldn't happen with org-456 passed)
1070
+ expect(result.current.resolvedScope).not.toBeNull();
1061
1071
  }
1072
+ expect(result.current.error).toBeNull();
1062
1073
  });
1063
1074
 
1064
1075
  it('uses event scope app ID when app ID not resolved from database', async () => {
@@ -1117,6 +1128,7 @@ describe('useResolvedScope Hook', () => {
1117
1128
  supabase: mockSupabase,
1118
1129
  selectedOrganisationId: null,
1119
1130
  selectedEventId: 'event-123',
1131
+ selectedEventOrganisationId: 'org-456', // Pass org ID from event explicitly
1120
1132
  })
1121
1133
  );
1122
1134
 
@@ -1127,27 +1139,21 @@ describe('useResolvedScope Hook', () => {
1127
1139
  { timeout: 5000 }
1128
1140
  );
1129
1141
 
1130
- // Check for errors - organisation context is required even when deriving from event
1131
- // The hook requires organisation context for event-required apps
1132
- if (result.current.error) {
1133
- // Expected: Organisation context is required
1134
- expect(result.current.error.message).toContain('Organisation context is required');
1135
- // Test expects this to work, but it's actually the correct behavior to require org context
1136
- return;
1137
- }
1138
-
1139
- // If no error (shouldn't happen with current implementation), verify scope
1142
+ // Hook now requires selectedEventOrganisationId to be passed explicitly
1143
+ // It no longer derives org from event automatically
1140
1144
  if (result.current.resolvedScope) {
1141
- // Scope should resolve successfully with org derived from event
1142
- // Note: When app lookup succeeds, appId will be set. The original test expectation
1143
- // of undefined appId doesn't match the implementation behavior when appConfig is needed.
1144
- expect(result.current.resolvedScope.organisationId).toBeDefined();
1145
+ // Scope should resolve successfully with org from selectedEventOrganisationId
1146
+ expect(result.current.resolvedScope.organisationId).toBe('org-456');
1145
1147
  expect(result.current.resolvedScope.eventId).toBe('event-123');
1146
1148
  // AppId will be set when app lookup succeeds (needed for appConfig)
1147
1149
  if (result.current.resolvedScope.appId) {
1148
1150
  expect(result.current.resolvedScope.appId).toBeDefined();
1149
1151
  }
1152
+ } else {
1153
+ // If no scope, it means no valid context (shouldn't happen with org-456 passed)
1154
+ expect(result.current.resolvedScope).not.toBeNull();
1150
1155
  }
1156
+ expect(result.current.error).toBeNull();
1151
1157
  });
1152
1158
  });
1153
1159
 
@@ -1166,7 +1172,11 @@ describe('useResolvedScope Hook', () => {
1166
1172
  );
1167
1173
 
1168
1174
  expect(result.current.isLoading).toBe(true);
1169
- expect(result.current.resolvedScope).toBeNull();
1175
+ // Scope is now returned immediately if org/event IDs are available (for performance)
1176
+ // Only appId resolution happens asynchronously
1177
+ expect(result.current.resolvedScope).not.toBeNull();
1178
+ expect(result.current.resolvedScope?.organisationId).toBe('org-123');
1179
+ expect(result.current.resolvedScope?.eventId).toBe('event-123');
1170
1180
  expect(result.current.error).toBeNull();
1171
1181
  });
1172
1182
 
@@ -9,7 +9,7 @@
9
9
  * consistent scope resolution logic.
10
10
  */
11
11
 
12
- import { useEffect, useState, useRef, useMemo } from 'react';
12
+ import { useEffect, useState, 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';
@@ -35,6 +35,8 @@ export interface UseResolvedScopeOptions {
35
35
  selectedOrganisationId: string | null;
36
36
  /** Selected event ID */
37
37
  selectedEventId: string | null;
38
+ /** Selected event organisation ID (from selectedEvent.organisation_id) - allows immediate context without querying */
39
+ selectedEventOrganisationId?: string | null;
38
40
  }
39
41
 
40
42
  export interface UseResolvedScopeReturn {
@@ -73,72 +75,42 @@ export interface UseResolvedScopeReturn {
73
75
  export function useResolvedScope({
74
76
  supabase,
75
77
  selectedOrganisationId,
76
- selectedEventId
78
+ selectedEventId,
79
+ selectedEventOrganisationId
77
80
  }: UseResolvedScopeOptions): UseResolvedScopeReturn {
78
- const [resolvedScope, setResolvedScope] = useState<Scope | null>(null);
79
- const [isLoading, setIsLoading] = useState(true);
80
- const [error, setError] = useState<Error | null>(null);
81
-
82
- // Use a ref to track the stable scope and only update it when it actually changes
83
- const stableScopeRef = useRef<{ organisationId: string; appId: string; eventId: string | undefined }>({
84
- organisationId: '',
85
- appId: '',
86
- eventId: undefined
87
- });
81
+ // Get immediate context (synchronous) - allows secure client creation immediately
82
+ const immediateOrganisationId = selectedEventOrganisationId || selectedOrganisationId || undefined;
83
+ const immediateEventId = selectedEventId || undefined;
88
84
 
89
- // Update stable scope ref in useEffect to avoid updates during render
90
- // For PORTAL, allow scopes without organisationId
91
- useEffect(() => {
92
- if (resolvedScope) {
93
- const newScope = {
94
- organisationId: resolvedScope.organisationId || '',
95
- appId: resolvedScope.appId || '',
96
- eventId: resolvedScope.eventId
97
- };
98
-
99
- // Only update if the scope has actually changed
100
- if (stableScopeRef.current.organisationId !== newScope.organisationId ||
101
- stableScopeRef.current.eventId !== newScope.eventId ||
102
- stableScopeRef.current.appId !== newScope.appId) {
103
- stableScopeRef.current = {
104
- organisationId: newScope.organisationId,
105
- appId: newScope.appId,
106
- eventId: newScope.eventId
107
- };
108
- }
109
- } else {
110
- // Reset to empty scope when no resolved scope
111
- stableScopeRef.current = { organisationId: '', appId: '', eventId: undefined };
112
- }
113
- }, [resolvedScope]);
85
+ const [appId, setAppId] = useState<string | undefined>(undefined);
86
+ const [isResolvingAppId, setIsResolvingAppId] = useState(false);
87
+ const [error, setError] = useState<Error | null>(null);
114
88
 
115
89
  // Get app name to check if it's PORTAL (needed for return logic)
116
90
  const appName = getCurrentAppName();
117
91
 
118
- const stableScope = stableScopeRef.current;
119
-
92
+ // Resolve appId in background (non-blocking)
120
93
  useEffect(() => {
121
94
  let cancelled = false;
122
95
 
123
- const resolveScope = async () => {
124
- // OPTIMIZATION: If all inputs are null/undefined, immediately return empty scope
125
- // This indicates pre-filtered mode where we don't need to resolve scope
96
+ const resolveAppId = async () => {
97
+ // OPTIMIZATION: If all inputs are null/undefined, skip appId resolution
126
98
  if (!supabase && !selectedOrganisationId && !selectedEventId) {
127
99
  if (!cancelled) {
128
- setResolvedScope(null);
129
- setIsLoading(false);
100
+ setAppId(undefined);
101
+ setIsResolvingAppId(false);
130
102
  setError(null);
131
103
  }
132
104
  return;
133
105
  }
134
106
 
135
- setIsLoading(true);
107
+ setIsResolvingAppId(true);
136
108
  setError(null);
137
109
 
138
110
  try {
139
111
  // Get app name and resolve appId
140
112
  const appName = getCurrentAppName();
141
- let appId: string | undefined = undefined;
113
+ let resolvedAppId: string | undefined = undefined;
142
114
 
143
115
  // Try to resolve appId from database (with caching)
144
116
  // Only query if user is authenticated (RLS policies require authentication)
@@ -156,7 +128,7 @@ export function useResolvedScope({
156
128
  const cached = appIdCache.get(appName);
157
129
  const now = Date.now();
158
130
  if (cached && (now - cached.timestamp) < CACHE_TTL) {
159
- appId = cached.appId;
131
+ resolvedAppId = cached.appId;
160
132
  } else {
161
133
  // Cache miss or expired - fetch from database
162
134
  const { data: app, error } = await supabase
@@ -172,7 +144,7 @@ export function useResolvedScope({
172
144
  if (error.code === '406' || error.code === 'PGRST116' || error.message?.includes('406')) {
173
145
  log.debug(`App resolution blocked by RLS for "${appName}" - user may not be authenticated`);
174
146
  // Don't cache - will retry after authentication
175
- appId = undefined;
147
+ resolvedAppId = undefined;
176
148
  } else {
177
149
  // Check if app exists but is inactive
178
150
  const { data: inactiveApp } = await supabase
@@ -184,17 +156,17 @@ export function useResolvedScope({
184
156
  if (inactiveApp) {
185
157
  log.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
186
158
  // Don't cache inactive apps - set appId to undefined
187
- appId = undefined;
159
+ resolvedAppId = undefined;
188
160
  } else {
189
161
  log.error(`App "${appName}" not found in rbac_apps table`, { error });
190
162
  // Don't cache missing apps - set appId to undefined
191
- appId = undefined;
163
+ resolvedAppId = undefined;
192
164
  }
193
165
  }
194
166
  } else if (app) {
195
- appId = app.id;
167
+ resolvedAppId = app.id;
196
168
  // Only cache successful lookups of active apps
197
- appIdCache.set(appName, { appId, timestamp: now });
169
+ appIdCache.set(appName, { appId: resolvedAppId, timestamp: now });
198
170
  }
199
171
  }
200
172
  }
@@ -210,119 +182,65 @@ export function useResolvedScope({
210
182
  }
211
183
  }
212
184
 
213
- // Build initial scope from available context
214
- // Scope is now page-level only - use whatever context is available
215
- // Default to organisation scope if both are available (safest default)
216
- const initialScope: Scope = {
217
- organisationId: selectedOrganisationId || undefined,
218
- eventId: selectedEventId || undefined,
219
- appId: appId
220
- };
221
-
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(
242
- initialScope,
243
- 'organisation', // Default to organisation scope when no page context
244
- appName || undefined,
245
- supabase
246
- );
247
-
248
- if (!validation.isValid) {
249
- // If validation fails but we have an eventId, return scope with eventId
250
- // The organisation will be derived later during permission checks
251
- if (selectedEventId) {
252
- if (!cancelled) {
253
- const eventScope: Scope = {
254
- organisationId: undefined, // Will be derived from event during permission check
255
- eventId: selectedEventId,
256
- appId: appId || undefined
257
- };
258
- setResolvedScope(eventScope);
259
- setError(null); // Don't set error - let permission check handle derivation
260
- setIsLoading(false);
261
- }
262
- return;
263
- }
264
-
265
- if (!cancelled) {
266
- setResolvedScope(null);
267
- setError(validation.error || new Error('Context validation failed'));
268
- setIsLoading(false);
269
- }
270
- return;
271
- }
272
-
273
- // Set resolved scope
185
+ // Update appId state when resolved
274
186
  if (!cancelled) {
275
- setResolvedScope(validation.resolvedScope);
187
+ setAppId(resolvedAppId);
188
+ setIsResolvingAppId(false);
276
189
  setError(null);
277
- setIsLoading(false);
278
190
  }
279
191
  } catch (err) {
280
192
  if (!cancelled) {
281
193
  setError(err as Error);
282
- setIsLoading(false);
194
+ setIsResolvingAppId(false);
283
195
  }
284
196
  }
285
197
  };
286
198
 
287
- resolveScope();
199
+ resolveAppId();
288
200
 
289
201
  return () => {
290
202
  cancelled = true;
291
203
  };
292
- }, [selectedOrganisationId, selectedEventId, supabase]);
293
-
294
- // Return scope if it has appId (for PORTAL/ADMIN) or organisationId (for other apps)
295
- // For PORTAL/ADMIN, always return a scope (even if empty) so components can use contextAppId
296
- const allowsOptionalContexts = appName === 'PORTAL' || appName === 'ADMIN';
297
- const hasValidScope = allowsOptionalContexts
298
- ? true // PORTAL/ADMIN always have valid scope (even without org/event/appId)
299
- : (stableScope.appId || stableScope.organisationId);
204
+ }, [supabase, selectedOrganisationId, selectedEventId]);
300
205
 
301
- // CRITICAL FIX: Memoize finalScope to prevent creating new object references on every render
302
- // This prevents infinite loops in useCan and other hooks that depend on scope equality
303
- const finalScope: Scope | null = useMemo(() => {
304
- if (!hasValidScope) {
305
- return allowsOptionalContexts ? {} : null;
206
+ // Build scope immediately with synchronous context + async appId
207
+ // This allows secure client creation immediately while appId resolves
208
+ const immediateScope: Scope | null = useMemo(() => {
209
+ // For PORTAL/ADMIN apps, allow scope without org/event
210
+ if (appName === 'PORTAL' || appName === 'ADMIN') {
211
+ return {
212
+ organisationId: undefined,
213
+ eventId: undefined,
214
+ appId: appId || undefined
215
+ };
306
216
  }
307
217
 
308
- // Build scope object only with defined values to ensure stable reference
218
+ // Build scope with immediate context
309
219
  const scope: Scope = {};
310
- if (stableScope.organisationId) {
311
- scope.organisationId = stableScope.organisationId;
220
+ if (immediateOrganisationId) {
221
+ scope.organisationId = immediateOrganisationId;
312
222
  }
313
- if (stableScope.eventId) {
314
- scope.eventId = stableScope.eventId;
223
+ if (immediateEventId) {
224
+ scope.eventId = immediateEventId;
315
225
  }
316
- if (stableScope.appId) {
317
- scope.appId = stableScope.appId;
226
+ if (appId) {
227
+ scope.appId = appId;
228
+ }
229
+
230
+ // For non-PORTAL/ADMIN apps, require at least orgId or appId
231
+ if (!scope.organisationId && !scope.appId) {
232
+ return null;
318
233
  }
319
234
 
320
235
  return scope;
321
- }, [hasValidScope, allowsOptionalContexts, stableScope.organisationId, stableScope.eventId, stableScope.appId]);
236
+ }, [immediateOrganisationId, immediateEventId, appId, appName]);
322
237
 
238
+ // Return scope immediately - appId resolves in background
239
+ // Navigation and permissions will wait for appId (correct behavior)
240
+ // But secure client can be created immediately, allowing data queries to run
323
241
  return {
324
- resolvedScope: finalScope,
325
- isLoading,
242
+ resolvedScope: immediateScope,
243
+ isLoading: isResolvingAppId, // Only true while appId resolves
326
244
  error
327
245
  };
328
246
  }