@jmruthers/pace-core 0.5.191 → 0.5.193

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 (293) hide show
  1. package/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
  2. package/dist/{DataTable-WKRZD47S.js → DataTable-5FU7IESH.js} +7 -6
  3. package/dist/{PublicPageProvider-ULXC_u6U.d.ts → PublicPageProvider-C0Sm_e5k.d.ts} +3 -1
  4. package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
  5. package/dist/{UnifiedAuthProvider-FTSG5XH7.js → UnifiedAuthProvider-RGJTDE2C.js} +3 -3
  6. package/dist/{api-IHKALJZD.js → api-N774RPUA.js} +2 -2
  7. package/dist/chunk-6C4YBBJM 5.js +628 -0
  8. package/dist/chunk-7D4SUZUM.js 2.map +1 -0
  9. package/dist/{chunk-LOMZXPSN.js → chunk-7EQTDTTJ.js} +47 -74
  10. package/dist/chunk-7EQTDTTJ.js 2.map +1 -0
  11. package/dist/chunk-7EQTDTTJ.js.map +1 -0
  12. package/dist/{chunk-6LTQQAT6.js → chunk-7FLMSG37.js} +336 -137
  13. package/dist/chunk-7FLMSG37.js 2.map +1 -0
  14. package/dist/chunk-7FLMSG37.js.map +1 -0
  15. package/dist/{chunk-XNYQOL3Z.js → chunk-BC4IJKSL.js} +9 -18
  16. package/dist/chunk-BC4IJKSL.js.map +1 -0
  17. package/dist/{chunk-ULHIJK66.js → chunk-E3SPN4VZ 5.js } +146 -36
  18. package/dist/chunk-E3SPN4VZ.js +12917 -0
  19. package/dist/{chunk-ULHIJK66.js.map → chunk-E3SPN4VZ.js.map} +1 -1
  20. package/dist/chunk-E66EQZE6 5.js +37 -0
  21. package/dist/chunk-E66EQZE6.js 2.map +1 -0
  22. package/dist/{chunk-6TQDD426.js → chunk-HWIIPPNI.js} +40 -221
  23. package/dist/chunk-HWIIPPNI.js.map +1 -0
  24. package/dist/chunk-I7PSE6JW 5.js +191 -0
  25. package/dist/chunk-I7PSE6JW.js 2.map +1 -0
  26. package/dist/{chunk-OETXORNB.js → chunk-IIELH4DL.js} +211 -136
  27. package/dist/chunk-IIELH4DL.js.map +1 -0
  28. package/dist/{chunk-ROXMHMY2.js → chunk-KNC55RTG.js} +13 -3
  29. package/dist/{chunk-ROXMHMY2.js.map → chunk-KNC55RTG.js 5.map } +1 -1
  30. package/dist/chunk-KNC55RTG.js.map +1 -0
  31. package/dist/chunk-KQCRWDSA.js 5.map +1 -0
  32. package/dist/{chunk-XYXSXPUK.js → chunk-LFNCN2SP.js} +7 -6
  33. package/dist/chunk-LFNCN2SP.js 2.map +1 -0
  34. package/dist/chunk-LFNCN2SP.js.map +1 -0
  35. package/dist/chunk-LMC26NLJ 2.js +84 -0
  36. package/dist/{chunk-VKB2CO4Z.js → chunk-NOAYCWCX 5.js } +84 -87
  37. package/dist/chunk-NOAYCWCX.js +4993 -0
  38. package/dist/chunk-NOAYCWCX.js.map +1 -0
  39. package/dist/chunk-QWWZ5CAQ.js 3.map +1 -0
  40. package/dist/chunk-QXHPKYJV 3.js +113 -0
  41. package/dist/chunk-R77UEZ4E 3.js +68 -0
  42. package/dist/chunk-VBXEHIUJ.js 6.map +1 -0
  43. package/dist/{chunk-VRGWKHDB.js → chunk-XNXXZ43G.js} +77 -33
  44. package/dist/chunk-XNXXZ43G.js.map +1 -0
  45. package/dist/chunk-ZSAAAMVR 6.js +25 -0
  46. package/dist/components.d.ts +2 -2
  47. package/dist/components.js +7 -7
  48. package/dist/components.js 5.map +1 -0
  49. package/dist/hooks.js +8 -8
  50. package/dist/index.d.ts +5 -5
  51. package/dist/index.js +12 -14
  52. package/dist/index.js.map +1 -1
  53. package/dist/providers.d.ts +3 -3
  54. package/dist/providers.js +2 -2
  55. package/dist/rbac/index.d.ts +1 -19
  56. package/dist/rbac/index.js +7 -9
  57. package/dist/styles/index 2.js +12 -0
  58. package/dist/styles/index.js 5.map +1 -0
  59. package/dist/theming/runtime 5.js +19 -0
  60. package/dist/theming/runtime.js 5.map +1 -0
  61. package/dist/utils.js +1 -1
  62. package/docs/api/classes/ColumnFactory.md +1 -1
  63. package/docs/api/classes/ErrorBoundary.md +1 -1
  64. package/docs/api/classes/InvalidScopeError.md +1 -1
  65. package/docs/api/classes/Logger.md +1 -1
  66. package/docs/api/classes/MissingUserContextError.md +1 -1
  67. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  68. package/docs/api/classes/PermissionDeniedError.md +2 -2
  69. package/docs/api/classes/RBACAuditManager.md +2 -2
  70. package/docs/api/classes/RBACCache.md +1 -1
  71. package/docs/api/classes/RBACEngine.md +2 -2
  72. package/docs/api/classes/RBACError.md +1 -1
  73. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  74. package/docs/api/classes/SecureSupabaseClient.md +10 -10
  75. package/docs/api/classes/StorageUtils.md +1 -1
  76. package/docs/api/enums/FileCategory.md +1 -1
  77. package/docs/api/enums/LogLevel.md +1 -1
  78. package/docs/api/enums/RBACErrorCode.md +1 -1
  79. package/docs/api/enums/RPCFunction.md +1 -1
  80. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  81. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  82. package/docs/api/interfaces/AggregateConfig.md +1 -1
  83. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  84. package/docs/api/interfaces/AvatarProps.md +1 -1
  85. package/docs/api/interfaces/BadgeProps.md +1 -1
  86. package/docs/api/interfaces/ButtonProps.md +1 -1
  87. package/docs/api/interfaces/CalendarProps.md +1 -1
  88. package/docs/api/interfaces/CardProps.md +1 -1
  89. package/docs/api/interfaces/ColorPalette.md +1 -1
  90. package/docs/api/interfaces/ColorShade.md +1 -1
  91. package/docs/api/interfaces/ComplianceResult.md +1 -1
  92. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  93. package/docs/api/interfaces/DataRecord.md +1 -1
  94. package/docs/api/interfaces/DataTableAction.md +1 -1
  95. package/docs/api/interfaces/DataTableColumn.md +1 -1
  96. package/docs/api/interfaces/DataTableProps.md +1 -1
  97. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  98. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  99. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  100. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  101. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  102. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  103. package/docs/api/interfaces/ExportColumn.md +1 -1
  104. package/docs/api/interfaces/ExportOptions.md +1 -1
  105. package/docs/api/interfaces/FileDisplayProps.md +24 -11
  106. package/docs/api/interfaces/FileMetadata.md +1 -1
  107. package/docs/api/interfaces/FileReference.md +1 -1
  108. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  109. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  110. package/docs/api/interfaces/FileUploadProps.md +1 -1
  111. package/docs/api/interfaces/FooterProps.md +1 -1
  112. package/docs/api/interfaces/FormFieldProps.md +1 -1
  113. package/docs/api/interfaces/FormProps.md +1 -1
  114. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  115. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  116. package/docs/api/interfaces/InputProps.md +1 -1
  117. package/docs/api/interfaces/LabelProps.md +1 -1
  118. package/docs/api/interfaces/LoggerConfig.md +1 -1
  119. package/docs/api/interfaces/LoginFormProps.md +1 -1
  120. package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
  121. package/docs/api/interfaces/NavigationContextType.md +1 -1
  122. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  123. package/docs/api/interfaces/NavigationItem.md +1 -1
  124. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  125. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  126. package/docs/api/interfaces/Organisation.md +1 -1
  127. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  128. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  129. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  130. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  131. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  132. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  133. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  134. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  135. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  136. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  137. package/docs/api/interfaces/PaletteData.md +1 -1
  138. package/docs/api/interfaces/ParsedAddress.md +1 -1
  139. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  140. package/docs/api/interfaces/ProgressProps.md +1 -1
  141. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  142. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  143. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  144. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  145. package/docs/api/interfaces/QuickFix.md +1 -1
  146. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  147. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  148. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  149. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  150. package/docs/api/interfaces/RBACConfig.md +2 -2
  151. package/docs/api/interfaces/RBACContext.md +1 -1
  152. package/docs/api/interfaces/RBACLogger.md +1 -1
  153. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  154. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  155. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  156. package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
  157. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  158. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  159. package/docs/api/interfaces/RBACResult.md +1 -1
  160. package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
  161. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  162. package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
  163. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  164. package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
  165. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  166. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  167. package/docs/api/interfaces/RBACRolesListResult.md +2 -2
  168. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  169. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  170. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  171. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  172. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  173. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  174. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  175. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  176. package/docs/api/interfaces/RouteConfig.md +2 -2
  177. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  178. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  179. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  180. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  181. package/docs/api/interfaces/SetupIssue.md +1 -1
  182. package/docs/api/interfaces/StorageConfig.md +1 -1
  183. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  184. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  185. package/docs/api/interfaces/StorageListOptions.md +1 -1
  186. package/docs/api/interfaces/StorageListResult.md +1 -1
  187. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  188. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  189. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  190. package/docs/api/interfaces/StyleImport.md +1 -1
  191. package/docs/api/interfaces/SwitchProps.md +1 -1
  192. package/docs/api/interfaces/TabsContentProps.md +1 -1
  193. package/docs/api/interfaces/TabsListProps.md +1 -1
  194. package/docs/api/interfaces/TabsProps.md +1 -1
  195. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  196. package/docs/api/interfaces/TextareaProps.md +1 -1
  197. package/docs/api/interfaces/ToastActionElement.md +1 -1
  198. package/docs/api/interfaces/ToastProps.md +1 -1
  199. package/docs/api/interfaces/UnifiedAuthContextType.md +60 -38
  200. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  201. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  202. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  203. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  204. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  205. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  206. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  207. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  208. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  209. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  210. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  211. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  212. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  213. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  214. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  215. package/docs/api/interfaces/UserEventAccess.md +1 -1
  216. package/docs/api/interfaces/UserMenuProps.md +1 -1
  217. package/docs/api/interfaces/UserProfile.md +1 -1
  218. package/docs/api/modules.md +194 -209
  219. package/docs/migration/database-changes-december-2025.md +2 -1
  220. package/docs/rbac/event-based-apps.md +124 -6
  221. package/package.json +1 -1
  222. package/scripts/check-pace-core-compliance.cjs +292 -57
  223. package/src/__tests__/rls-policies.test.ts +3 -1
  224. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
  225. package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
  226. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
  227. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
  228. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
  229. package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
  230. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +75 -11
  231. package/src/components/DataTable/components/UnifiedTableBody.tsx +85 -14
  232. package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
  233. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -1
  234. package/src/components/FileDisplay/FileDisplay.tsx +16 -4
  235. package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
  236. package/src/components/NavigationMenu/NavigationMenu.tsx +1 -10
  237. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -1
  238. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +25 -2
  239. package/src/components/PaceAppLayout/PaceAppLayout.tsx +97 -68
  240. package/src/components/PaceLoginPage/PaceLoginPage.tsx +0 -7
  241. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
  242. package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
  243. package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
  244. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +14 -7
  245. package/src/hooks/services/useAuthService.ts +21 -3
  246. package/src/hooks/services/useEventService.ts +21 -3
  247. package/src/hooks/services/useInactivityService.ts +21 -3
  248. package/src/hooks/services/useOrganisationService.ts +21 -3
  249. package/src/hooks/useFileDisplay.ts +10 -17
  250. package/src/hooks/useSecureDataAccess.test.ts +16 -9
  251. package/src/hooks/useSecureDataAccess.ts +3 -2
  252. package/src/providers/services/EventServiceProvider.tsx +0 -8
  253. package/src/providers/services/UnifiedAuthProvider.tsx +174 -24
  254. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +10 -16
  255. package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
  256. package/src/rbac/adapters.tsx +3 -22
  257. package/src/rbac/api.test.ts +2 -2
  258. package/src/rbac/api.ts +7 -1
  259. package/src/rbac/components/EnhancedNavigationMenu.tsx +2 -15
  260. package/src/rbac/components/NavigationGuard.tsx +1 -10
  261. package/src/rbac/components/NavigationProvider.tsx +0 -1
  262. package/src/rbac/components/PermissionEnforcer.tsx +45 -12
  263. package/src/rbac/components/SecureDataProvider.tsx +0 -1
  264. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
  265. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
  266. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
  267. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
  268. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
  269. package/src/rbac/engine.ts +14 -2
  270. package/src/rbac/hooks/index.ts +0 -3
  271. package/src/rbac/hooks/usePermissions.ts +51 -11
  272. package/src/rbac/hooks/useRBAC.ts +3 -13
  273. package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
  274. package/src/rbac/hooks/useResolvedScope.ts +58 -33
  275. package/src/rbac/hooks/useSecureSupabase.ts +4 -9
  276. package/src/rbac/secureClient.ts +31 -0
  277. package/src/services/EventService.ts +4 -57
  278. package/src/services/InactivityService.ts +127 -34
  279. package/src/services/OrganisationService.ts +68 -10
  280. package/dist/chunk-6LTQQAT6.js.map +0 -1
  281. package/dist/chunk-6TQDD426.js.map +0 -1
  282. package/dist/chunk-LOMZXPSN.js.map +0 -1
  283. package/dist/chunk-OETXORNB.js.map +0 -1
  284. package/dist/chunk-VKB2CO4Z.js.map +0 -1
  285. package/dist/chunk-VRGWKHDB.js.map +0 -1
  286. package/dist/chunk-XNYQOL3Z.js.map +0 -1
  287. package/dist/chunk-XYXSXPUK.js.map +0 -1
  288. package/scripts/check-pace-core-compliance.js +0 -512
  289. package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
  290. package/src/utils/context/superAdminOverride.ts +0 -58
  291. /package/dist/{DataTable-WKRZD47S.js.map → DataTable-5FU7IESH.js.map} +0 -0
  292. /package/dist/{UnifiedAuthProvider-FTSG5XH7.js.map → UnifiedAuthProvider-RGJTDE2C.js.map} +0 -0
  293. /package/dist/{api-IHKALJZD.js.map → api-N774RPUA.js.map} +0 -0
@@ -144,49 +144,74 @@ export function useResolvedScope({
144
144
  let appConfig: AppConfig | null = null;
145
145
 
146
146
  // Try to resolve app config from database (with caching)
147
+ // Only query if user is authenticated (RLS policies require authentication)
147
148
  if (supabase && appName) {
148
149
  try {
149
- // Check cache first
150
- const cached = appConfigCache.get(appName);
151
- const now = Date.now();
152
- if (cached && (now - cached.timestamp) < CACHE_TTL) {
153
- appId = cached.appId;
154
- appConfig = cached.appConfig;
150
+ // Check if user is authenticated before querying (RLS requires auth)
151
+ // HTTP 406 errors are expected when not authenticated, so we skip the query
152
+ const { data: session } = await supabase.auth.getSession();
153
+ if (!session?.session) {
154
+ // User not authenticated - skip app resolution, will retry after login
155
+ // This is expected on login pages, so don't log as error
156
+ log.debug(`Skipping app resolution for "${appName}" - user not authenticated`);
155
157
  } else {
156
- // Cache miss or expired - fetch from database
157
- const { data: app, error } = await supabase
158
- .from('rbac_apps')
159
- .select('id, name, requires_event, is_active')
160
- .eq('name', appName)
161
- .eq('is_active', true)
162
- .single() as { data: { id: string; name: string; requires_event: boolean; is_active: boolean } | null; error: any };
163
-
164
- if (error) {
165
- // Check if app exists but is inactive
166
- const { data: inactiveApp } = await supabase
158
+ // Check cache first
159
+ const cached = appConfigCache.get(appName);
160
+ const now = Date.now();
161
+ if (cached && (now - cached.timestamp) < CACHE_TTL) {
162
+ appId = cached.appId;
163
+ appConfig = cached.appConfig;
164
+ } else {
165
+ // Cache miss or expired - fetch from database
166
+ const { data: app, error } = await supabase
167
167
  .from('rbac_apps')
168
- .select('id, name, is_active')
168
+ .select('id, name, requires_event, is_active')
169
169
  .eq('name', appName)
170
- .single() as { data: { id: string; name: string; is_active: boolean } | null };
170
+ .eq('is_active', true)
171
+ .single() as { data: { id: string; name: string; requires_event: boolean; is_active: boolean } | null; error: any };
171
172
 
172
- if (inactiveApp) {
173
- log.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
174
- // Don't cache inactive apps - set appId to undefined
175
- appId = undefined;
176
- } else {
177
- log.error(`App "${appName}" not found in rbac_apps table`);
178
- // Don't cache missing apps - set appId to undefined
179
- appId = undefined;
173
+ if (error) {
174
+ // HTTP 406 is expected when not authenticated (RLS blocks query)
175
+ // Don't log as error if it's a 406 - this is expected behavior
176
+ if (error.code === '406' || error.code === 'PGRST116' || error.message?.includes('406')) {
177
+ log.debug(`App resolution blocked by RLS for "${appName}" - user may not be authenticated`);
178
+ // Don't cache - will retry after authentication
179
+ appId = undefined;
180
+ } else {
181
+ // Check if app exists but is inactive
182
+ const { data: inactiveApp } = await supabase
183
+ .from('rbac_apps')
184
+ .select('id, name, is_active')
185
+ .eq('name', appName)
186
+ .single() as { data: { id: string; name: string; is_active: boolean } | null };
187
+
188
+ if (inactiveApp) {
189
+ log.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
190
+ // Don't cache inactive apps - set appId to undefined
191
+ appId = undefined;
192
+ } else {
193
+ log.error(`App "${appName}" not found in rbac_apps table`, { error });
194
+ // Don't cache missing apps - set appId to undefined
195
+ appId = undefined;
196
+ }
197
+ }
198
+ } else if (app) {
199
+ appId = app.id;
200
+ appConfig = { requires_event: app.requires_event ?? false };
201
+ // Only cache successful lookups of active apps
202
+ appConfigCache.set(appName, { appId, appConfig, timestamp: now });
180
203
  }
181
- } else if (app) {
182
- appId = app.id;
183
- appConfig = { requires_event: app.requires_event ?? false };
184
- // Only cache successful lookups of active apps
185
- appConfigCache.set(appName, { appId, appConfig, timestamp: now });
186
204
  }
187
205
  }
188
206
  } catch (error) {
189
- log.error('Unexpected error resolving app config:', error);
207
+ // Handle network errors or other unexpected errors gracefully
208
+ // Don't log 406 errors as they're expected when not authenticated
209
+ const errorMessage = error instanceof Error ? error.message : String(error);
210
+ if (!errorMessage.includes('406') && !errorMessage.includes('PGRST116')) {
211
+ log.error('Unexpected error resolving app config:', error);
212
+ } else {
213
+ log.debug('App resolution skipped - authentication required');
214
+ }
190
215
  }
191
216
  }
192
217
 
@@ -120,7 +120,7 @@ import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
120
120
  import { useOrganisations } from '../../hooks/useOrganisations';
121
121
  import { useEvents } from '../../hooks/useEvents';
122
122
  import { useResolvedScope } from './useResolvedScope';
123
- import { useSuperAdminBypass } from './useSuperAdminBypass';
123
+ import { useOrganisationSecurity } from '../../hooks/useOrganisationSecurity';
124
124
  import { createSecureClient, SecureSupabaseClient } from '../secureClient';
125
125
  import type { Database } from '../../types/database';
126
126
  import type { SupabaseClient } from '@supabase/supabase-js';
@@ -215,14 +215,9 @@ export function useSecureSupabase(
215
215
  const eventLoading = 'eventLoading' in eventsContext ? eventsContext.eventLoading : false;
216
216
 
217
217
  // Check super admin status for conditional filtering
218
- // Use both verified status and metadata hint to avoid race conditions
219
- // Strategy: Use verified status if available, otherwise use metadata hint during verification
220
- // This prevents creating clients with wrong super admin status before verification completes
221
- const { isSuperAdmin: verifiedIsSuperAdmin, isLoading: isVerifyingSuperAdmin } = useSuperAdminBypass();
222
- const metadataHint = Boolean(user?.app_metadata?.is_super_admin) || Boolean(user?.user_metadata?.is_super_admin);
223
- // If verified as super admin, use that. If verification in progress, use metadata hint optimistically.
224
- // Once verification completes and user is not super admin, verifiedIsSuperAdmin will be false.
225
- const isSuperAdmin = verifiedIsSuperAdmin || (isVerifyingSuperAdmin && metadataHint);
218
+ // Use verified status from useOrganisationSecurity which checks the database
219
+ const { superAdminContext } = useOrganisationSecurity();
220
+ const isSuperAdmin = superAdminContext.isSuperAdmin;
226
221
 
227
222
  // Resolve scope to get appId
228
223
  const { resolvedScope } = useResolvedScope({
@@ -152,6 +152,19 @@ export class SecureSupabaseClient {
152
152
 
153
153
  // Override insert to add organisation context
154
154
  query.insert = (values: any) => {
155
+ // Tables that don't have organisation_id column
156
+ const tablesWithoutOrganisationId = [
157
+ 'core_organisations', // Organisation table itself - uses 'id' as primary key
158
+ 'rbac_apps', // App configuration table - no organisation scope
159
+ 'rbac_app_pages', // Page configuration table - scoped by app_id, not organisation_id
160
+ 'rbac_global_roles', // Global roles - no organisation scope
161
+ ];
162
+
163
+ // Skip adding organisation_id for tables that don't have it
164
+ if (tablesWithoutOrganisationId.includes(tableName)) {
165
+ return originalInsert(values);
166
+ }
167
+
155
168
  // For rbac_user_profiles, only add organisation_id if not super admin
156
169
  // Super admins can create users in any org, non-super-admins are restricted
157
170
  if (tableName === 'rbac_user_profiles') {
@@ -204,6 +217,24 @@ export class SecureSupabaseClient {
204
217
  * - Always apply org filter unless super admin bypasses it
205
218
  */
206
219
  private addOrganisationFilter(query: any, tableName: string) {
220
+ // Tables that don't have organisation_id column - RLS policies handle access control
221
+ const tablesWithoutOrganisationId = [
222
+ 'core_organisations', // Organisation table itself - uses 'id' as primary key
223
+ 'rbac_apps', // App configuration table - no organisation scope
224
+ 'rbac_app_pages', // Page configuration table - scoped by app_id, not organisation_id
225
+ 'rbac_global_roles', // Global roles - no organisation scope
226
+ ];
227
+
228
+ // Skip organisation filter for tables that don't have organisation_id column
229
+ if (tablesWithoutOrganisationId.includes(tableName)) {
230
+ return query; // RLS policies handle access control for these tables
231
+ }
232
+
233
+ // If organisation context is not set, don't add a filter (e.g., super admin without selected org)
234
+ if (!this.organisationId) {
235
+ return query;
236
+ }
237
+
207
238
  // For rbac_user_profiles, use conditional filtering based on super admin status
208
239
  if (tableName === 'rbac_user_profiles') {
209
240
  // Super admins: No org filter (see all users via RLS)
@@ -116,10 +116,6 @@ export class EventService extends BaseService implements IEventService {
116
116
  if (user?.id) {
117
117
  try {
118
118
  this.isSuperAdmin = await isSuperAdmin(user.id as UUID);
119
- logger.debug('EventService', 'Updated super admin status', {
120
- userId: user.id,
121
- isSuperAdmin: this.isSuperAdmin
122
- });
123
119
  } catch (error) {
124
120
  logger.warn('EventService', 'Failed to check super admin status', { error });
125
121
  this.isSuperAdmin = false; // Default to false on error
@@ -151,9 +147,10 @@ export class EventService extends BaseService implements IEventService {
151
147
 
152
148
  // Event state getters
153
149
  getEvents(): Event[] {
154
- // Return a new array reference so React can detect changes
155
- // This ensures useMemo dependencies work correctly
156
- return [...this.events];
150
+ // Return stable array reference - only create new array when events actually change
151
+ // This prevents unnecessary re-renders when getEvents() is called on every render
152
+ // The service's notify() mechanism handles change detection
153
+ return this.events;
157
154
  }
158
155
 
159
156
  getSelectedEvent(): Event | null {
@@ -308,12 +305,6 @@ export class EventService extends BaseService implements IEventService {
308
305
  async initialize(): Promise<void> {
309
306
  // Only call super.initialize() which will call doInitialize() and fetchEvents()
310
307
  // Don't call fetchEvents() again here to avoid double-fetching
311
- logger.debug('EventService', 'initialize() called', {
312
- isInitializedRef: this.isInitializedRef,
313
- hasUser: !!this.user,
314
- hasSession: !!this.session,
315
- appName: this.appName
316
- });
317
308
  await super.initialize();
318
309
  }
319
310
 
@@ -322,23 +313,13 @@ export class EventService extends BaseService implements IEventService {
322
313
  }
323
314
 
324
315
  protected async doInitialize(): Promise<void> {
325
- logger.debug('EventService', 'doInitialize() called', {
326
- isInitializedRef: this.isInitializedRef,
327
- isFetchingRef: this.isFetchingRef,
328
- hasUser: !!this.user,
329
- hasSession: !!this.session,
330
- appName: this.appName
331
- });
332
-
333
316
  // Skip if already initialized
334
317
  if (this.isInitializedRef) {
335
- logger.debug('EventService', 'Skipping initialization - already initialized');
336
318
  return;
337
319
  }
338
320
 
339
321
  // Skip if already fetching
340
322
  if (this.isFetchingRef) {
341
- logger.debug('EventService', 'Skipping initialization - already fetching');
342
323
  return;
343
324
  }
344
325
 
@@ -355,16 +336,9 @@ export class EventService extends BaseService implements IEventService {
355
336
  // For event-required apps, selectedOrganisation may be null (org derived from event)
356
337
  // For org-required apps, selectedOrganisation is required
357
338
  if (!this.user) {
358
- logger.debug('EventService', 'Skipping initialization - missing user');
359
339
  return;
360
340
  }
361
341
 
362
- logger.debug('EventService', 'Initializing - fetching events', {
363
- userId: this.user.id,
364
- organisationId: this.selectedOrganisation?.id || 'derived-from-event',
365
- appName: this.appName
366
- });
367
-
368
342
  // Initial setup - fetch events on initialization
369
343
  await this.fetchEvents(false);
370
344
 
@@ -380,12 +354,6 @@ export class EventService extends BaseService implements IEventService {
380
354
  // For event-required apps, selectedOrganisation may be null (org derived from event)
381
355
  // For org-required apps, selectedOrganisation is required
382
356
  if (!this.user || !this.session || !this.supabaseClient || !this.appName) {
383
- logger.debug('EventService', 'Skipping fetchEvents - missing dependencies', {
384
- hasUser: !!this.user,
385
- hasSession: !!this.session,
386
- hasSupabaseClient: !!this.supabaseClient,
387
- appName: this.appName
388
- });
389
357
  // Already false from initialization, just notify
390
358
  this.notify();
391
359
  return;
@@ -397,7 +365,6 @@ export class EventService extends BaseService implements IEventService {
397
365
 
398
366
  // Prevent multiple simultaneous fetches
399
367
  if (this.isFetchingRef) {
400
- logger.debug('EventService', 'Skipping fetchEvents - already fetching');
401
368
  return;
402
369
  }
403
370
 
@@ -431,9 +398,6 @@ export class EventService extends BaseService implements IEventService {
431
398
  if (userIsSuperAdmin) {
432
399
  // Super admin: Pass null to see all events across all organisations
433
400
  organisationIdForRpc = null;
434
- logger.debug('EventService', 'Super admin detected - fetching all events', {
435
- userId: this.user.id
436
- });
437
401
  } else {
438
402
  // Not super admin: determine org from context based on app type
439
403
  if (this.selectedEvent) {
@@ -443,10 +407,6 @@ export class EventService extends BaseService implements IEventService {
443
407
  // Event-required app with no selected event yet: pass null to get all accessible events
444
408
  // The RPC will filter by event app roles, returning all events the user has access to
445
409
  organisationIdForRpc = null;
446
- logger.debug('EventService', 'Event-required app: fetching all accessible events (no event selected yet)', {
447
- userId: this.user.id,
448
- appName: this.appName
449
- });
450
410
  } else if (this.selectedOrganisation) {
451
411
  // Org-required app: use selected organisation
452
412
  organisationIdForRpc = this.selectedOrganisation.id;
@@ -476,12 +436,6 @@ export class EventService extends BaseService implements IEventService {
476
436
  }
477
437
  }
478
438
 
479
- logger.debug('EventService', 'Fetching events via RPC', {
480
- userId: this.user.id,
481
- organisationId: organisationIdForRpc,
482
- appName: this.appName
483
- });
484
-
485
439
  // Call the RPC function following the established pattern
486
440
  // For super admins, pass null for p_organisation_id to see all events
487
441
  let { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
@@ -490,13 +444,6 @@ export class EventService extends BaseService implements IEventService {
490
444
  p_app_name: this.appName
491
445
  });
492
446
 
493
- logger.debug('EventService', 'RPC response received', {
494
- hasData: !!data,
495
- dataLength: Array.isArray(data) ? data.length : 'not array',
496
- hasError: !!rpcError,
497
- error: rpcError
498
- });
499
-
500
447
  if (rpcError) {
501
448
  logger.error('EventService', 'RPC error fetching events:', rpcError);
502
449
  throw new Error(rpcError.message || 'Failed to fetch events');
@@ -145,12 +145,31 @@ export class InactivityService extends BaseService implements IInactivityService
145
145
  if (this.inactivityTracker) {
146
146
  this.inactivityTracker.resetActivity();
147
147
  }
148
+
149
+ // Store previous values
150
+ const prevIsIdle = this._isIdle;
151
+ const prevShowWarning = this._showWarning;
152
+ const prevShowInactivityWarning = this._showInactivityWarning;
153
+ const prevInactivityTimeRemaining = this._inactivityTimeRemaining;
154
+ const prevTimeRemaining = this._timeRemaining;
155
+
156
+ // Update state
148
157
  this._isIdle = false;
149
158
  this._showWarning = false;
150
159
  this._showInactivityWarning = false;
151
160
  this._inactivityTimeRemaining = 0;
152
161
  this._timeRemaining = this.idleTimeoutMs;
153
- this.notify();
162
+
163
+ // Only notify if values actually changed
164
+ if (
165
+ prevIsIdle !== this._isIdle ||
166
+ prevShowWarning !== this._showWarning ||
167
+ prevShowInactivityWarning !== this._showInactivityWarning ||
168
+ prevInactivityTimeRemaining !== this._inactivityTimeRemaining ||
169
+ prevTimeRemaining !== this._timeRemaining
170
+ ) {
171
+ this.notify();
172
+ }
154
173
  }
155
174
 
156
175
  startTracking(): void {
@@ -275,10 +294,63 @@ export class InactivityService extends BaseService implements IInactivityService
275
294
  let idleTimer: NodeJS.Timeout | null = null;
276
295
  let warningTimer: NodeJS.Timeout | null = null;
277
296
  let lastActivity = Date.now();
278
-
279
- const resetTimers = () => {
280
- lastActivity = Date.now();
297
+ let pollInterval: NodeJS.Timeout | null = null;
298
+
299
+ // Store previous state for comparison
300
+ let prevIsIdle = false;
301
+ let prevShowWarning = false;
302
+ let prevShowInactivityWarning = false;
303
+ let prevInactivityTimeRemaining = 0;
304
+ let prevTimeRemaining = this.idleTimeoutMs;
305
+
306
+ // Poll every 10 seconds to check for state changes
307
+ const pollInactivityState = () => {
308
+ const now = Date.now();
309
+ const timeSinceActivity = now - lastActivity;
310
+ const timeUntilIdle = this.idleTimeoutMs - timeSinceActivity;
311
+ const timeUntilWarning = (this.idleTimeoutMs - this.warnBeforeMs) - timeSinceActivity;
312
+
313
+ // Calculate new state values based on time since last activity
314
+ const newIsIdle = timeSinceActivity >= (this.idleTimeoutMs - this.warnBeforeMs);
315
+ const newShowWarning = newIsIdle && timeSinceActivity < this.idleTimeoutMs;
316
+ const newShowInactivityWarning = newShowWarning;
317
+ const newInactivityTimeRemaining = newShowWarning
318
+ ? Math.ceil((this.idleTimeoutMs - timeSinceActivity) / 1000)
319
+ : 0;
320
+ const newTimeRemaining = Math.max(0, timeUntilIdle);
321
+
322
+ // Check if state actually changed
323
+ const stateChanged =
324
+ prevIsIdle !== newIsIdle ||
325
+ prevShowWarning !== newShowWarning ||
326
+ prevShowInactivityWarning !== newShowInactivityWarning ||
327
+ prevInactivityTimeRemaining !== newInactivityTimeRemaining ||
328
+ prevTimeRemaining !== newTimeRemaining;
329
+
330
+ // Only update and notify if state changed
331
+ if (stateChanged) {
332
+ this._isIdle = newIsIdle;
333
+ this._showWarning = newShowWarning;
334
+ this._showInactivityWarning = newShowInactivityWarning;
335
+ this._inactivityTimeRemaining = newInactivityTimeRemaining;
336
+ this._timeRemaining = newTimeRemaining;
337
+
338
+ // Update previous state
339
+ prevIsIdle = newIsIdle;
340
+ prevShowWarning = newShowWarning;
341
+ prevShowInactivityWarning = newShowInactivityWarning;
342
+ prevInactivityTimeRemaining = newInactivityTimeRemaining;
343
+ prevTimeRemaining = newTimeRemaining;
344
+
345
+ this.notify();
346
+
347
+ // Handle idle logout if needed
348
+ if (newIsIdle && timeSinceActivity >= this.idleTimeoutMs) {
349
+ this.handleIdleLogout();
350
+ }
351
+ }
281
352
 
353
+ // Update timers based on current state
282
354
  if (idleTimer) {
283
355
  clearTimeout(idleTimer);
284
356
  idleTimer = null;
@@ -288,47 +360,58 @@ export class InactivityService extends BaseService implements IInactivityService
288
360
  clearTimeout(warningTimer);
289
361
  warningTimer = null;
290
362
  }
291
-
292
- this._showInactivityWarning = false;
293
- this._inactivityTimeRemaining = 0;
294
- this._isIdle = false;
295
- this._showWarning = false;
296
- this.notify();
297
- };
298
-
299
- const startIdleTimer = () => {
300
- if (idleTimer) {
301
- clearTimeout(idleTimer);
363
+
364
+ // Set up timers for next state transitions
365
+ if (!newIsIdle && timeUntilIdle > 0) {
366
+ idleTimer = setTimeout(() => {
367
+ pollInactivityState();
368
+ }, Math.min(10000, timeUntilIdle));
302
369
  }
303
-
304
- idleTimer = setTimeout(() => {
305
- this._isIdle = true;
306
- this._showWarning = true;
307
- this.notify();
308
-
309
- // Start warning timer
370
+
371
+ if (newShowWarning && timeUntilIdle > 0) {
310
372
  warningTimer = setTimeout(() => {
311
373
  this.handleIdleLogout();
312
- }, this.warnBeforeMs);
313
- }, this.idleTimeoutMs - this.warnBeforeMs);
374
+ }, timeUntilIdle);
375
+ }
314
376
  };
315
377
 
316
- const startWarningTimer = () => {
378
+ const resetTimers = () => {
379
+ // Only update lastActivity - don't notify yet
380
+ lastActivity = Date.now();
381
+
382
+ // Clear timers
383
+ if (idleTimer) {
384
+ clearTimeout(idleTimer);
385
+ idleTimer = null;
386
+ }
387
+
317
388
  if (warningTimer) {
318
389
  clearTimeout(warningTimer);
390
+ warningTimer = null;
319
391
  }
320
-
321
- warningTimer = setTimeout(() => {
322
- this.handleIdleLogout();
323
- }, this.warnBeforeMs);
392
+
393
+ // Reset state values (will be checked on next poll)
394
+ this._showInactivityWarning = false;
395
+ this._inactivityTimeRemaining = 0;
396
+ this._isIdle = false;
397
+ this._showWarning = false;
398
+
399
+ // Update previous state to match
400
+ prevIsIdle = false;
401
+ prevShowWarning = false;
402
+ prevShowInactivityWarning = false;
403
+ prevInactivityTimeRemaining = 0;
404
+ prevTimeRemaining = this.idleTimeoutMs;
405
+
406
+ // Poll will check and notify if needed
324
407
  };
325
408
 
326
- // Activity detection
409
+ // Activity detection - only updates lastActivity, doesn't notify
327
410
  const activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'];
328
411
 
329
412
  const handleActivity = () => {
330
413
  resetTimers();
331
- startIdleTimer();
414
+ // Don't call notify - polling will handle state updates
332
415
  };
333
416
 
334
417
  // Add event listeners
@@ -336,8 +419,13 @@ export class InactivityService extends BaseService implements IInactivityService
336
419
  document.addEventListener(event, handleActivity, true);
337
420
  });
338
421
 
339
- // Start initial timer
340
- startIdleTimer();
422
+ // Start polling every 10 seconds
423
+ pollInterval = setInterval(() => {
424
+ pollInactivityState();
425
+ }, 10000); // 10 seconds
426
+
427
+ // Initial poll
428
+ pollInactivityState();
341
429
 
342
430
  // Store cleanup function
343
431
  this.cleanupHandlers = () => {
@@ -350,6 +438,11 @@ export class InactivityService extends BaseService implements IInactivityService
350
438
  clearTimeout(warningTimer);
351
439
  warningTimer = null;
352
440
  }
441
+
442
+ if (pollInterval) {
443
+ clearInterval(pollInterval);
444
+ pollInterval = null;
445
+ }
353
446
 
354
447
  activityEvents.forEach(event => {
355
448
  document.removeEventListener(event, handleActivity, true);
@@ -364,6 +457,6 @@ export class InactivityService extends BaseService implements IInactivityService
364
457
  };
365
458
 
366
459
  this._isTracking = true;
367
- this.notify();
460
+ this.notify(); // Initial notification only
368
461
  }
369
462
  }