@jmruthers/pace-core 0.6.2 → 0.6.4

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 (299) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/cursor-rules/00-pace-core-compliance.mdc +34 -2
  3. package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
  4. package/dist/{DataTable-TPTKCX4D.js → DataTable-E7YQZD7D.js} +9 -8
  5. package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
  6. package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
  7. package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-QPXO24B4.js} +5 -4
  8. package/dist/{api-MVVQZLJI.js → api-6LVZTHDS.js} +10 -10
  9. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  10. package/dist/chunk-36LVWXB2.js +227 -0
  11. package/dist/chunk-36LVWXB2.js.map +1 -0
  12. package/dist/{chunk-24UVZUZG.js → chunk-3LPHPB62.js} +129 -387
  13. package/dist/chunk-3LPHPB62.js.map +1 -0
  14. package/dist/{chunk-2UOI2FG5.js → chunk-5EC5MEWX.js} +4 -4
  15. package/dist/{chunk-3XC4CPTD.js → chunk-7JPAB3T5.js} +244 -5727
  16. package/dist/chunk-7JPAB3T5.js.map +1 -0
  17. package/dist/{chunk-6J4GEEJR.js → chunk-ATKZM7RX.js} +53 -27
  18. package/dist/chunk-ATKZM7RX.js.map +1 -0
  19. package/dist/{chunk-EHMR7VYL.js → chunk-AVMLPIM7.js} +443 -189
  20. package/dist/chunk-AVMLPIM7.js.map +1 -0
  21. package/dist/chunk-DGUM43GV.js +11 -0
  22. package/dist/{chunk-NECFR5MM.js → chunk-I6DAQMWX.js} +575 -647
  23. package/dist/chunk-I6DAQMWX.js.map +1 -0
  24. package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
  25. package/dist/{chunk-XWQCNGTQ.js → chunk-NN6WWZ5U.js} +173 -79
  26. package/dist/chunk-NN6WWZ5U.js.map +1 -0
  27. package/dist/{chunk-MMZ7JXPU.js → chunk-OEWDTMG7.js} +13 -21
  28. package/dist/{chunk-MMZ7JXPU.js.map → chunk-OEWDTMG7.js.map} +1 -1
  29. package/dist/{chunk-SFZUDBL5.js → chunk-YKRAFF5K.js} +70 -56
  30. package/dist/chunk-YKRAFF5K.js.map +1 -0
  31. package/dist/components.d.ts +2 -2
  32. package/dist/components.js +12 -13
  33. package/dist/contextValidator-OOPCLPZW.js +9 -0
  34. package/dist/contextValidator-OOPCLPZW.js.map +1 -0
  35. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  36. package/dist/hooks.d.ts +2 -2
  37. package/dist/hooks.js +7 -6
  38. package/dist/hooks.js.map +1 -1
  39. package/dist/index.d.ts +7 -7
  40. package/dist/index.js +21 -16
  41. package/dist/index.js.map +1 -1
  42. package/dist/providers.d.ts +3 -3
  43. package/dist/providers.js +4 -3
  44. package/dist/rbac/index.d.ts +67 -27
  45. package/dist/rbac/index.js +15 -8
  46. package/dist/styles/index.js +1 -1
  47. package/dist/theming/runtime.js +1 -1
  48. package/dist/types.js +1 -1
  49. package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
  50. package/dist/utils.js +5 -7
  51. package/dist/utils.js.map +1 -1
  52. package/docs/api/README.md +14 -16
  53. package/docs/api/modules.md +3796 -2513
  54. package/docs/components/context-selector.md +126 -0
  55. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  56. package/docs/pace-mint-fix-auto-selection.md +218 -0
  57. package/docs/pace-mint-rbac-setup.md +391 -0
  58. package/docs/rbac/secure-client-protection.md +330 -0
  59. package/package.json +10 -5
  60. package/scripts/audit/core/checks/compliance.cjs +72 -0
  61. package/scripts/audit/core/checks/dependencies.cjs +568 -28
  62. package/scripts/audit/core/checks/documentation.cjs +68 -3
  63. package/scripts/audit/core/checks/environment.cjs +2 -14
  64. package/scripts/audit/core/checks/error-handling.cjs +47 -6
  65. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  66. package/src/components/ContextSelector/index.ts +3 -0
  67. package/src/components/DataTable/components/RowComponent.tsx +19 -19
  68. package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
  69. package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
  70. package/src/components/Dialog/Dialog.tsx +29 -1
  71. package/src/components/FileDisplay/FileDisplay.tsx +42 -10
  72. package/src/components/Header/Header.test.tsx +43 -73
  73. package/src/components/Header/Header.tsx +44 -45
  74. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  75. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  76. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  77. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
  78. package/src/components/PaceAppLayout/PaceAppLayout.tsx +157 -36
  79. package/src/components/PaceAppLayout/README.md +14 -17
  80. package/src/components/PaceAppLayout/test-setup.tsx +2 -2
  81. package/src/components/index.ts +5 -5
  82. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  83. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  84. package/src/hooks/useAppConfig.ts +15 -30
  85. package/src/hooks/useFileDisplay.ts +77 -50
  86. package/src/index.ts +4 -5
  87. package/src/providers/services/AuthServiceProvider.tsx +17 -7
  88. package/src/providers/services/EventServiceProvider.tsx +33 -5
  89. package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
  90. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  91. package/src/rbac/adapters.tsx +2 -2
  92. package/src/rbac/api.test.ts +59 -51
  93. package/src/rbac/api.ts +178 -132
  94. package/src/rbac/components/PagePermissionGuard.tsx +38 -10
  95. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  96. package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
  97. package/src/rbac/hooks/permissions/useCan.ts +41 -11
  98. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
  99. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
  100. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
  101. package/src/rbac/hooks/useCan.test.ts +0 -9
  102. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  103. package/src/rbac/hooks/useRBAC.ts +36 -37
  104. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  105. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  106. package/src/rbac/hooks/useSecureSupabase.ts +7 -7
  107. package/src/rbac/index.ts +7 -0
  108. package/src/rbac/secureClient.test.ts +22 -18
  109. package/src/rbac/secureClient.ts +103 -16
  110. package/src/rbac/security.ts +0 -17
  111. package/src/rbac/types.ts +1 -0
  112. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  113. package/src/rbac/utils/clientSecurity.ts +93 -0
  114. package/src/rbac/utils/contextValidator.ts +77 -168
  115. package/src/services/AuthService.ts +39 -7
  116. package/src/services/EventService.ts +285 -56
  117. package/src/services/OrganisationService.ts +81 -14
  118. package/src/services/__tests__/EventService.test.ts +1 -2
  119. package/src/services/base/BaseService.ts +3 -0
  120. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  121. package/dist/chunk-24UVZUZG.js.map +0 -1
  122. package/dist/chunk-3XC4CPTD.js.map +0 -1
  123. package/dist/chunk-6J4GEEJR.js.map +0 -1
  124. package/dist/chunk-7D4SUZUM.js +0 -38
  125. package/dist/chunk-EHMR7VYL.js.map +0 -1
  126. package/dist/chunk-NECFR5MM.js.map +0 -1
  127. package/dist/chunk-SFZUDBL5.js.map +0 -1
  128. package/dist/chunk-XWQCNGTQ.js.map +0 -1
  129. package/docs/api/classes/ColumnFactory.md +0 -243
  130. package/docs/api/classes/InvalidScopeError.md +0 -73
  131. package/docs/api/classes/Logger.md +0 -178
  132. package/docs/api/classes/MissingUserContextError.md +0 -66
  133. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  134. package/docs/api/classes/PermissionDeniedError.md +0 -73
  135. package/docs/api/classes/RBACAuditManager.md +0 -297
  136. package/docs/api/classes/RBACCache.md +0 -322
  137. package/docs/api/classes/RBACEngine.md +0 -171
  138. package/docs/api/classes/RBACError.md +0 -76
  139. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  140. package/docs/api/classes/SecureSupabaseClient.md +0 -163
  141. package/docs/api/classes/StorageUtils.md +0 -328
  142. package/docs/api/enums/FileCategory.md +0 -184
  143. package/docs/api/enums/LogLevel.md +0 -54
  144. package/docs/api/enums/RBACErrorCode.md +0 -228
  145. package/docs/api/enums/RPCFunction.md +0 -118
  146. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  147. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  148. package/docs/api/interfaces/AggregateConfig.md +0 -43
  149. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  150. package/docs/api/interfaces/AvatarProps.md +0 -128
  151. package/docs/api/interfaces/BadgeProps.md +0 -34
  152. package/docs/api/interfaces/ButtonProps.md +0 -56
  153. package/docs/api/interfaces/CalendarProps.md +0 -73
  154. package/docs/api/interfaces/CardProps.md +0 -69
  155. package/docs/api/interfaces/ColorPalette.md +0 -7
  156. package/docs/api/interfaces/ColorShade.md +0 -66
  157. package/docs/api/interfaces/ComplianceResult.md +0 -30
  158. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  159. package/docs/api/interfaces/DataRecord.md +0 -11
  160. package/docs/api/interfaces/DataTableAction.md +0 -252
  161. package/docs/api/interfaces/DataTableColumn.md +0 -504
  162. package/docs/api/interfaces/DataTableProps.md +0 -625
  163. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  164. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  165. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  166. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  167. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  168. package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
  169. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
  170. package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
  171. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  172. package/docs/api/interfaces/ExportColumn.md +0 -90
  173. package/docs/api/interfaces/ExportOptions.md +0 -126
  174. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  175. package/docs/api/interfaces/FileMetadata.md +0 -129
  176. package/docs/api/interfaces/FileReference.md +0 -118
  177. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  178. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  179. package/docs/api/interfaces/FileUploadProps.md +0 -296
  180. package/docs/api/interfaces/FooterProps.md +0 -107
  181. package/docs/api/interfaces/FormFieldProps.md +0 -166
  182. package/docs/api/interfaces/FormProps.md +0 -113
  183. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  184. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  185. package/docs/api/interfaces/InputProps.md +0 -56
  186. package/docs/api/interfaces/LabelProps.md +0 -107
  187. package/docs/api/interfaces/LoggerConfig.md +0 -62
  188. package/docs/api/interfaces/LoginFormProps.md +0 -187
  189. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  190. package/docs/api/interfaces/NavigationContextType.md +0 -164
  191. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  192. package/docs/api/interfaces/NavigationItem.md +0 -120
  193. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  194. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  195. package/docs/api/interfaces/Organisation.md +0 -140
  196. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  197. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  198. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  199. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  200. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
  201. package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
  202. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  203. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  204. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  205. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  206. package/docs/api/interfaces/PaletteData.md +0 -41
  207. package/docs/api/interfaces/ParsedAddress.md +0 -120
  208. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  209. package/docs/api/interfaces/ProgressProps.md +0 -42
  210. package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
  211. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  212. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  213. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
  214. package/docs/api/interfaces/QuickFix.md +0 -52
  215. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  216. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  217. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  218. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  219. package/docs/api/interfaces/RBACConfig.md +0 -133
  220. package/docs/api/interfaces/RBACContext.md +0 -52
  221. package/docs/api/interfaces/RBACLogger.md +0 -112
  222. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  223. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  224. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  225. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  226. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  227. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  228. package/docs/api/interfaces/RBACResult.md +0 -58
  229. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  230. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  231. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  232. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  233. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  234. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  235. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  236. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  237. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  238. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  239. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  240. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  241. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  242. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  243. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  244. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  245. package/docs/api/interfaces/RouteConfig.md +0 -134
  246. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  247. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  248. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  249. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  250. package/docs/api/interfaces/SetupIssue.md +0 -41
  251. package/docs/api/interfaces/StorageConfig.md +0 -41
  252. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  253. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  254. package/docs/api/interfaces/StorageListOptions.md +0 -99
  255. package/docs/api/interfaces/StorageListResult.md +0 -41
  256. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  257. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  258. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  259. package/docs/api/interfaces/StyleImport.md +0 -19
  260. package/docs/api/interfaces/SwitchProps.md +0 -34
  261. package/docs/api/interfaces/TabsContentProps.md +0 -9
  262. package/docs/api/interfaces/TabsListProps.md +0 -9
  263. package/docs/api/interfaces/TabsProps.md +0 -9
  264. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  265. package/docs/api/interfaces/TextareaProps.md +0 -53
  266. package/docs/api/interfaces/ToastActionElement.md +0 -12
  267. package/docs/api/interfaces/ToastProps.md +0 -9
  268. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
  269. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
  270. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  271. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  272. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
  273. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  274. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  275. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
  276. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  277. package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
  278. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  279. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
  280. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
  281. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  282. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  283. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  284. package/docs/api/interfaces/UserEventAccess.md +0 -121
  285. package/docs/api/interfaces/UserMenuProps.md +0 -88
  286. package/docs/api/interfaces/UserProfile.md +0 -63
  287. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  288. package/src/components/EventSelector/EventSelector.tsx +0 -423
  289. package/src/components/EventSelector/index.ts +0 -3
  290. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  291. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
  292. package/src/components/OrganisationSelector/index.ts +0 -9
  293. /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-E7YQZD7D.js.map} +0 -0
  294. /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-QPXO24B4.js.map} +0 -0
  295. /package/dist/{api-MVVQZLJI.js.map → api-6LVZTHDS.js.map} +0 -0
  296. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  297. /package/dist/{chunk-2UOI2FG5.js.map → chunk-5EC5MEWX.js.map} +0 -0
  298. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  299. /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
@@ -23,6 +23,8 @@ type AuthStateSubscription = {
23
23
  };
24
24
 
25
25
  export class AuthService extends BaseService implements IAuthService {
26
+ private static instanceCount = 0;
27
+ private instanceId: number;
26
28
  private user: User | null = null;
27
29
  private session: Session | null = null;
28
30
  private authLoading = false;
@@ -43,12 +45,23 @@ export class AuthService extends BaseService implements IAuthService {
43
45
 
44
46
  constructor(supabaseClient: SupabaseClient, appName?: string) {
45
47
  super();
48
+ this.instanceId = ++AuthService.instanceCount;
46
49
  this.supabaseClient = supabaseClient;
47
50
  this.appName = appName;
51
+ logger.debug('AuthService', `Instance created [ID:${this.instanceId}]`, { appName });
52
+ }
53
+
54
+ getInstanceId(): number {
55
+ return this.instanceId;
48
56
  }
49
57
 
50
58
  // Auth state getters
51
59
  getUser(): User | null {
60
+ if (this.user) {
61
+ logger.debug('AuthService', `getUser() [ID:${this.instanceId}] returning user: ${this.user.id}`);
62
+ } else {
63
+ logger.debug('AuthService', `getUser() [ID:${this.instanceId}] returning null`);
64
+ }
52
65
  return this.user;
53
66
  }
54
67
 
@@ -398,6 +411,11 @@ export class AuthService extends BaseService implements IAuthService {
398
411
  const subscription = this.supabaseClient.auth.onAuthStateChange(
399
412
  (event: AuthChangeEvent, session: SupabaseSession | null) => {
400
413
  try {
414
+ logger.debug('AuthService', `Auth state change [ID:${this.instanceId}]`, {
415
+ event,
416
+ hasSession: !!session,
417
+ userId: session?.user?.id
418
+ });
401
419
  // Handle different auth events
402
420
  if (event === 'SIGNED_OUT') {
403
421
  this.session = null;
@@ -407,15 +425,15 @@ export class AuthService extends BaseService implements IAuthService {
407
425
  // Automatic session tracking (non-blocking)
408
426
  if (session?.user) {
409
427
  this.trackSession('logout', session).catch(err => {
410
- logger.warn('AuthService', 'Failed to track logout session:', err);
428
+ logger.warn('AuthService', `Failed to track logout session [ID:${this.instanceId}]:`, err);
411
429
  });
412
430
  }
413
431
  } else if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
414
432
  this.session = session;
415
433
  this.user = session?.user ?? null;
416
434
 
417
- // Only clear auth error if we have a valid session
418
- if (session) {
435
+ // Ensure state is set before non-blocking tracking calls
436
+ if (session?.user) {
419
437
  this.authError = null;
420
438
  }
421
439
 
@@ -423,7 +441,7 @@ export class AuthService extends BaseService implements IAuthService {
423
441
  // Only track on SIGNED_IN, not TOKEN_REFRESHED (to avoid duplicate login records)
424
442
  if (event === 'SIGNED_IN' && session?.user) {
425
443
  this.trackSession('login', session).catch(err => {
426
- logger.warn('AuthService', 'Failed to track login session:', err);
444
+ logger.warn('AuthService', `Failed to track login session [ID:${this.instanceId}]:`, err);
427
445
  });
428
446
  }
429
447
  } else if (event === 'INITIAL_SESSION') {
@@ -452,16 +470,30 @@ export class AuthService extends BaseService implements IAuthService {
452
470
  // This ensures ProtectedRoute waits for session restoration to complete
453
471
  // before checking authentication state
454
472
  this.authLoading = false;
473
+
474
+ // Final check: Ensure state matches what we just logged
475
+ logger.debug('AuthService', `State synchronized after INITIAL_SESSION [ID:${this.instanceId}]`, {
476
+ hasUser: !!this.user,
477
+ userId: this.user?.id
478
+ });
479
+
455
480
  this.notify();
456
481
  return; // Return early to avoid setting loading to false again below
457
482
  }
458
483
 
459
- // For other events (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED), set loading to false
460
- // INITIAL_SESSION is handled above and returns early
484
+ // For other events (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED), set loading to false and notify
461
485
  this.authLoading = false;
486
+
487
+ // Final check: Ensure state matches what we just logged
488
+ logger.debug('AuthService', `State synchronized after event [ID:${this.instanceId}]`, {
489
+ event,
490
+ hasUser: !!this.user,
491
+ userId: this.user?.id
492
+ });
493
+
462
494
  this.notify();
463
495
  } catch (error) {
464
- logger.warn('AuthService', 'Error in auth state change handler:', error);
496
+ logger.warn('AuthService', `Error in auth state change handler [ID:${this.instanceId}]:`, error);
465
497
  this.authLoading = false;
466
498
  this.notify();
467
499
  }
@@ -16,11 +16,12 @@ import { Organisation } from '../types/organisation';
16
16
  import { assertOrganisationId } from '../types/core';
17
17
  import { logger } from '../utils/core/logger';
18
18
  import { secureStorage } from '../utils/security/secureStorage';
19
- import { isSuperAdmin, getAppConfigByName } from '../rbac/api';
19
+ import { isSuperAdmin } from '../rbac/api';
20
20
  import type { UUID } from '../rbac/types';
21
- import type { AppConfig } from '../rbac/utils/contextValidator';
22
21
 
23
22
  export class EventService extends BaseService implements IEventService {
23
+ private static instanceCount = 0;
24
+ private instanceId: number;
24
25
  private events: Event[] = [];
25
26
  private selectedEvent: Event | null = null;
26
27
  private _isLoading = false; // Start as false to avoid blocking UI
@@ -34,7 +35,7 @@ export class EventService extends BaseService implements IEventService {
34
35
  private selectedOrganisation: Organisation | null = null;
35
36
  private setSelectedEventId: ((eventId: string | null) => void) | null = null;
36
37
  private isSuperAdmin: boolean = false; // Track super admin status for conditional validation
37
- private appConfig: AppConfig | null = null; // Cache app config to avoid repeated lookups
38
+ // App config removed - scope is now page-level only (rbac_app_pages.scope_type)
38
39
 
39
40
  // Internal state management
40
41
  private isInitializedRef = false;
@@ -51,12 +52,22 @@ export class EventService extends BaseService implements IEventService {
51
52
  setSelectedEventId: (eventId: string | null) => void
52
53
  ) {
53
54
  super();
55
+ this.instanceId = ++EventService.instanceCount;
54
56
  this.supabaseClient = supabaseClient;
55
57
  this.user = user;
56
58
  this.session = session;
57
59
  this.appName = appName;
58
60
  this.selectedOrganisation = selectedOrganisation;
59
61
  this.setSelectedEventId = setSelectedEventId;
62
+ logger.debug('EventService', `Instance created [ID:${this.instanceId}]`, {
63
+ appName,
64
+ hasUser: !!user,
65
+ userId: user?.id
66
+ });
67
+ }
68
+
69
+ getInstanceId(): number {
70
+ return this.instanceId;
60
71
  }
61
72
 
62
73
  // Helper method to get user-scoped storage key
@@ -97,6 +108,15 @@ export class EventService extends BaseService implements IEventService {
97
108
  this.resetInitialization();
98
109
  this.isInitializedRef = false;
99
110
  this.isFetchingRef = false;
111
+ // Reset user cleared flag when new user logs in - allows auto-selection for new user
112
+ this.userClearedEventRef = false;
113
+ this.hasAutoSelectedRef = false;
114
+
115
+ logger.debug('EventService', `User changed [ID:${this.instanceId}]`, {
116
+ previousUserId,
117
+ newUserId,
118
+ willInitialize: newUserId !== null
119
+ });
100
120
  }
101
121
 
102
122
  this.supabaseClient = supabaseClient;
@@ -106,19 +126,43 @@ export class EventService extends BaseService implements IEventService {
106
126
  this.selectedOrganisation = selectedOrganisation;
107
127
  this.setSelectedEventId = setSelectedEventId;
108
128
 
109
- // Clear app config cache when app name changes
110
- if (previousAppName !== appName) {
111
- this.appConfig = null;
112
- }
129
+ // App name changed - state will be reset by updateDependencies
113
130
 
114
131
  // Update super admin status when user changes
115
132
  // This allows super admins to select events from any organisation
133
+ // RBAC should be initialized synchronously by UnifiedAuthProvider, so this should always work
116
134
  if (user?.id) {
117
135
  try {
118
- this.isSuperAdmin = await isSuperAdmin(user.id as UUID);
136
+ const { isRBACInitialized, isSuperAdmin: checkSuperAdmin, setupRBAC } = await import('../rbac/api');
137
+
138
+ // Ensure RBAC is initialized if possible (defensive check for edge cases)
139
+ if (!isRBACInitialized() && this.supabaseClient) {
140
+ setupRBAC(this.supabaseClient);
141
+ }
142
+
143
+ if (isRBACInitialized()) {
144
+ this.isSuperAdmin = await checkSuperAdmin(user.id as UUID);
145
+ logger.debug('EventService', 'Super admin status updated in updateDependencies', {
146
+ userId: user.id,
147
+ isSuperAdmin: this.isSuperAdmin
148
+ });
149
+ } else {
150
+ // RBAC not initialized - this should be rare since UnifiedAuthProvider initializes it synchronously
151
+ // Keep existing value (don't reset to false) to avoid clearing a previously determined super admin status
152
+ logger.warn('EventService', 'RBAC not initialized in updateDependencies, keeping existing super admin status', {
153
+ userId: user.id,
154
+ existingIsSuperAdmin: this.isSuperAdmin,
155
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
156
+ });
157
+ }
119
158
  } catch (error) {
120
- logger.warn('EventService', 'Failed to check super admin status', { error });
121
- this.isSuperAdmin = false; // Default to false on error
159
+ logger.warn('EventService', 'Failed to check super admin status in updateDependencies', {
160
+ error,
161
+ userId: user.id,
162
+ existingIsSuperAdmin: this.isSuperAdmin
163
+ });
164
+ // Don't reset to false on error - keep existing value to avoid blocking super admins
165
+ // The error might be transient, and we don't want to clear a valid super admin status
122
166
  }
123
167
  } else {
124
168
  this.isSuperAdmin = false;
@@ -131,14 +175,51 @@ export class EventService extends BaseService implements IEventService {
131
175
  this.resetInitialization(); // Reset BaseService's isInitialized flag
132
176
  this.isInitializedRef = false;
133
177
  this.isFetchingRef = false;
178
+
179
+ // SECURITY: Super admins can see events regardless of organisation context
180
+ // Do not clear events for super admins when organisation context is removed
181
+ const shouldClearEvents = !this.isSuperAdmin;
182
+
183
+ // Determine if this is the first time an org is being set (from null/undefined to a value)
184
+ const isFirstOrgSet = (previousOrgId === null || previousOrgId === undefined) && newOrgId !== null && newOrgId !== undefined;
185
+
134
186
  // Clear events ONLY when switching between different organisations (not when org first becomes available)
135
- if (previousOrgId !== null && newOrgId !== null && previousOrgId !== newOrgId) {
136
- this.events = [];
137
- this.selectedEvent = null;
138
- } else if (previousOrgId !== null && newOrgId === null) {
139
- // Organisation was removed - clear events
140
- this.events = [];
141
- this.selectedEvent = null;
187
+ // IMPORTANT: Check isFirstOrgSet FIRST to prevent clearing when org is first set
188
+ if (isFirstOrgSet) {
189
+ // Organisation first becomes available - DO NOT clear the event, preserve it
190
+ // The event will be validated when events are re-fetched with org context
191
+ // If it's no longer valid, it will be cleared without setting userClearedEventRef = true
192
+ const hadAutoSelectedEvent = this.hasAutoSelectedRef && !!this.selectedEvent;
193
+ this.userClearedEventRef = false;
194
+ // Don't reset hasAutoSelectedRef if we had an auto-selected event - preserve it
195
+ // This ensures the event remains selected when org is first set
196
+ if (!hadAutoSelectedEvent) {
197
+ this.hasAutoSelectedRef = false;
198
+ }
199
+ logger.debug('EventService', 'Organisation first set - preserving event and resetting auto-selection flags', {
200
+ organisationId: newOrgId,
201
+ hasSelectedEvent: !!this.selectedEvent,
202
+ selectedEventId: this.selectedEvent?.event_id,
203
+ hadAutoSelectedEvent,
204
+ preservingEvent: hadAutoSelectedEvent,
205
+ previousOrgId,
206
+ newOrgId
207
+ });
208
+ } else if (previousOrgId !== null && previousOrgId !== undefined && newOrgId !== null && newOrgId !== undefined && previousOrgId !== newOrgId) {
209
+ // Switching between different organisations - clear events
210
+ if (shouldClearEvents) {
211
+ this.events = [];
212
+ // Use setSelectedEvent(null) to preserve userClearedEventRef flag if user explicitly cleared
213
+ // This prevents auto-selection from re-selecting the event after org switch
214
+ this.setSelectedEvent(null);
215
+ }
216
+ } else if (previousOrgId !== null && previousOrgId !== undefined && newOrgId === null) {
217
+ // Organisation was removed - clear events if not super admin
218
+ if (shouldClearEvents) {
219
+ this.events = [];
220
+ // Use setSelectedEvent(null) to preserve userClearedEventRef flag if user explicitly cleared
221
+ this.setSelectedEvent(null);
222
+ }
142
223
  }
143
224
  }
144
225
 
@@ -179,7 +260,13 @@ export class EventService extends BaseService implements IEventService {
179
260
  });
180
261
  // Reset the user cleared flag when selecting an event
181
262
  this.userClearedEventRef = false;
263
+ logger.debug('EventService', 'Event selected', {
264
+ eventId: event.event_id,
265
+ eventName: event.event_name,
266
+ userClearedEventRef: this.userClearedEventRef
267
+ });
182
268
  } else {
269
+ const previousEventId = this.selectedEvent?.event_id;
183
270
  this.selectedEvent = null;
184
271
  this.setSelectedEventId?.(null);
185
272
  // Clear from secure storage (don't await to avoid blocking)
@@ -190,6 +277,11 @@ export class EventService extends BaseService implements IEventService {
190
277
  this.hasAutoSelectedRef = false;
191
278
  // Mark that user explicitly cleared the event to prevent auto-selection
192
279
  this.userClearedEventRef = true;
280
+ logger.debug('EventService', 'Event cleared via setSelectedEvent(null)', {
281
+ previousEventId,
282
+ userClearedEventRef: this.userClearedEventRef,
283
+ stackTrace: new Error().stack
284
+ });
193
285
  }
194
286
  this.notify();
195
287
  }
@@ -220,7 +312,7 @@ export class EventService extends BaseService implements IEventService {
220
312
  const persistedEvent = events.find(event => event.event_id === persistedEventId);
221
313
 
222
314
  if (persistedEvent) {
223
- // Use setSelectedEvent() to go through same path as EventSelector
315
+ // Use setSelectedEvent() to ensure consistent behavior
224
316
  // This ensures consistent behavior and proper notification
225
317
  // Theme will be applied by useEventTheme hook once user navigates away from login
226
318
  this.setSelectedEvent(persistedEvent);
@@ -315,11 +407,13 @@ export class EventService extends BaseService implements IEventService {
315
407
  protected async doInitialize(): Promise<void> {
316
408
  // Skip if already initialized
317
409
  if (this.isInitializedRef) {
410
+ logger.debug('EventService', 'Skipping initialization - already initialized');
318
411
  return;
319
412
  }
320
413
 
321
414
  // Skip if already fetching
322
415
  if (this.isFetchingRef) {
416
+ logger.debug('EventService', 'Skipping initialization - already fetching');
323
417
  return;
324
418
  }
325
419
 
@@ -336,14 +430,27 @@ export class EventService extends BaseService implements IEventService {
336
430
  // For event-required apps, selectedOrganisation may be null (org derived from event)
337
431
  // For org-required apps, selectedOrganisation is required
338
432
  if (!this.user) {
433
+ logger.debug('EventService', 'Skipping initialization - no user');
339
434
  return;
340
435
  }
341
436
 
437
+ logger.debug('EventService', 'Initializing', {
438
+ userId: this.user.id,
439
+ appName: this.appName,
440
+ hasSelectedOrganisation: !!this.selectedOrganisation,
441
+ hasSupabaseClient: !!this.supabaseClient,
442
+ hasSession: !!this.session
443
+ });
444
+
342
445
  // Initial setup - fetch events on initialization
343
446
  await this.fetchEvents(false);
344
447
 
345
448
  // Mark as initialized after successful fetch
346
449
  this.isInitializedRef = true;
450
+ logger.debug('EventService', 'Initialization complete', {
451
+ eventsCount: this.events.length,
452
+ hasError: !!this.error
453
+ });
347
454
  }
348
455
 
349
456
  protected doCleanup(): void {
@@ -372,18 +479,7 @@ export class EventService extends BaseService implements IEventService {
372
479
  let isMounted = true;
373
480
 
374
481
  try {
375
- // Load app config if not already cached (only once per app)
376
- if (!this.appConfig && this.appName) {
377
- try {
378
- this.appConfig = await getAppConfigByName(this.appName);
379
- } catch (configError) {
380
- logger.warn('EventService', 'Failed to load app config, defaulting to event-required', {
381
- error: configError
382
- });
383
- // Default to event-required for safety
384
- this.appConfig = { requires_event: true };
385
- }
386
- }
482
+ // Scope is now page-level only - no app-level config needed
387
483
 
388
484
  // Determine organisationId for RPC call
389
485
  // For event-required apps: org is derived from selectedEvent (if available), or null to get all accessible events
@@ -392,58 +488,119 @@ export class EventService extends BaseService implements IEventService {
392
488
  let organisationIdForRpc: string | null = null;
393
489
 
394
490
  // Check if user is super admin first
395
- let userIsSuperAdmin = false;
491
+ // RBAC should be initialized synchronously by UnifiedAuthProvider before EventService is used
492
+ // Use cached value as fallback for edge cases (e.g., EventService used outside UnifiedAuthProvider)
493
+ let userIsSuperAdmin = this.isSuperAdmin; // Start with cached value from updateDependencies
396
494
  try {
397
- userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
495
+ const { isRBACInitialized, isSuperAdmin: checkSuperAdmin, setupRBAC } = await import('../rbac/api');
496
+
497
+ // Ensure RBAC is initialized if possible (defensive check for edge cases)
498
+ if (!isRBACInitialized() && this.supabaseClient) {
499
+ setupRBAC(this.supabaseClient);
500
+ }
501
+
502
+ // Check super admin status if RBAC is ready
503
+ // RBAC should always be initialized by UnifiedAuthProvider, but we check defensively
504
+ if (isRBACInitialized()) {
505
+ userIsSuperAdmin = await checkSuperAdmin(this.user.id as UUID);
506
+ // Update cached value for future use
507
+ this.isSuperAdmin = userIsSuperAdmin;
508
+ logger.debug('EventService', 'Super admin check completed', {
509
+ userId: this.user.id,
510
+ isSuperAdmin: userIsSuperAdmin
511
+ });
512
+ } else {
513
+ // RBAC not initialized - this should be rare since UnifiedAuthProvider initializes it synchronously
514
+ // Use cached value from updateDependencies as fallback
515
+ // If cached value is true, trust it to avoid blocking super admins
516
+ if (this.isSuperAdmin) {
517
+ userIsSuperAdmin = true;
518
+ logger.warn('EventService', 'RBAC not initialized, using cached super admin status', {
519
+ userId: this.user.id,
520
+ cachedIsSuperAdmin: this.isSuperAdmin,
521
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
522
+ });
523
+ } else {
524
+ logger.warn('EventService', 'RBAC not initialized, using cached non-super-admin status', {
525
+ userId: this.user.id,
526
+ cachedIsSuperAdmin: this.isSuperAdmin,
527
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
528
+ });
529
+ }
530
+ }
531
+
398
532
  if (userIsSuperAdmin) {
399
533
  // Super admin: Pass null to see all events across all organisations
400
534
  organisationIdForRpc = null;
401
535
  } else {
402
- // Not super admin: determine org from context based on app type
536
+ // Not super admin: determine org from available context
537
+ // Scope is now page-level only - use whatever context is available
403
538
  if (this.selectedEvent) {
404
539
  // If event is already selected, use its organisation
405
540
  organisationIdForRpc = this.selectedEvent.organisation_id;
406
- } else if (this.appConfig?.requires_event === true) {
407
- // Event-required app with no selected event yet: pass null to get all accessible events
408
- // The RPC will filter by event app roles, returning all events the user has access to
409
- organisationIdForRpc = null;
410
541
  } else if (this.selectedOrganisation) {
411
- // Org-required app: use selected organisation
542
+ // Use selected organisation
412
543
  organisationIdForRpc = this.selectedOrganisation.id;
413
544
  } else {
414
- // No context available - this shouldn't happen for authenticated users
415
- logger.warn('EventService', 'No organisation context available for event fetch', {
545
+ // No context available - pass null to get all accessible events via event-app roles
546
+ // This allows users with event-app roles to see their events even without org context
547
+ logger.debug('EventService', 'No organisation context available, fetching all accessible events', {
416
548
  hasSelectedEvent: !!this.selectedEvent,
417
- hasSelectedOrganisation: !!this.selectedOrganisation,
418
- appRequiresEvent: this.appConfig?.requires_event
549
+ hasSelectedOrganisation: !!this.selectedOrganisation
419
550
  });
420
- organisationIdForRpc = null; // Will return empty list
551
+ organisationIdForRpc = null; // Will return events user has access to via event-app roles
421
552
  }
422
553
  }
423
554
  } catch (superAdminCheckError) {
424
- // If super admin check fails, fall back to organisation-scoped query
425
- logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
426
- error: superAdminCheckError
427
- });
428
- // Fallback: use available context
429
- if (this.selectedEvent) {
430
- organisationIdForRpc = this.selectedEvent.organisation_id;
431
- } else if (this.appConfig?.requires_event === true) {
432
- // Event-required app: pass null to get all accessible events
433
- organisationIdForRpc = null;
434
- } else if (this.selectedOrganisation) {
435
- organisationIdForRpc = this.selectedOrganisation.id;
555
+ // If super admin check fails, use cached value as fallback
556
+ // If cached value is true, trust it to avoid blocking super admins
557
+ if (this.isSuperAdmin) {
558
+ userIsSuperAdmin = true;
559
+ organisationIdForRpc = null; // Super admin gets all events
560
+ logger.warn('EventService', 'Super admin check failed, using cached super admin status', {
561
+ error: superAdminCheckError,
562
+ cachedIsSuperAdmin: this.isSuperAdmin
563
+ });
564
+ } else {
565
+ // Fallback: use available context
566
+ logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
567
+ error: superAdminCheckError,
568
+ cachedIsSuperAdmin: this.isSuperAdmin
569
+ });
570
+ if (this.selectedEvent) {
571
+ organisationIdForRpc = this.selectedEvent.organisation_id;
572
+ } else if (this.selectedOrganisation) {
573
+ organisationIdForRpc = this.selectedOrganisation.id;
574
+ } else {
575
+ // No context - pass null to get all accessible events via event-app roles
576
+ organisationIdForRpc = null;
577
+ }
436
578
  }
437
579
  }
438
580
 
439
581
  // Call the RPC function following the established pattern
440
582
  // For super admins, pass null for p_organisation_id to see all events
583
+ logger.debug('EventService', 'Fetching events', {
584
+ userId: this.user.id,
585
+ organisationIdForRpc,
586
+ appName: this.appName,
587
+ hasSelectedEvent: !!this.selectedEvent,
588
+ hasSelectedOrganisation: !!this.selectedOrganisation,
589
+ isSuperAdmin: userIsSuperAdmin
590
+ });
591
+
441
592
  let { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
442
593
  p_user_id: this.user.id,
443
594
  p_organisation_id: organisationIdForRpc,
444
595
  p_app_name: this.appName
445
596
  });
446
597
 
598
+ logger.debug('EventService', 'RPC response', {
599
+ dataLength: data?.length || 0,
600
+ hasError: !!rpcError,
601
+ error: rpcError
602
+ });
603
+
447
604
  if (rpcError) {
448
605
  logger.error('EventService', 'RPC error fetching events:', rpcError);
449
606
  throw new Error(rpcError.message || 'Failed to fetch events');
@@ -482,9 +639,45 @@ export class EventService extends BaseService implements IEventService {
482
639
  updated_at: new Date().toISOString()
483
640
  }));
484
641
 
485
- this.events = transformedEvents;
642
+ // Sort events by event_date descending (newest first)
643
+ // Handle null dates by putting them at the end
644
+ const sortedEvents = [...transformedEvents].sort((a, b) => {
645
+ // If both have dates, sort descending (newest first)
646
+ if (a.event_date && b.event_date) {
647
+ return new Date(b.event_date).getTime() - new Date(a.event_date).getTime();
648
+ }
649
+ // If only one has a date, prioritize the one with a date
650
+ if (a.event_date && !b.event_date) return -1;
651
+ if (!a.event_date && b.event_date) return 1;
652
+ // If neither has a date, maintain original order
653
+ return 0;
654
+ });
655
+
656
+ this.events = sortedEvents;
486
657
  this.error = null;
487
658
 
659
+ // Validate selected event - if it's no longer in the events list, clear it
660
+ // This can happen when org context changes or events are refreshed
661
+ // Don't set userClearedEventRef to true in this case - it's an automatic clear, not user-initiated
662
+ if (this.selectedEvent) {
663
+ const selectedEventId = this.selectedEvent.event_id;
664
+ const eventStillExists = transformedEvents.some(
665
+ e => e.event_id === selectedEventId
666
+ );
667
+ if (!eventStillExists) {
668
+ // Event no longer available - clear it but don't mark as user-cleared
669
+ const previousUserClearedRef = this.userClearedEventRef;
670
+ this.selectedEvent = null;
671
+ this.setSelectedEventId?.(null);
672
+ // Restore the previous userClearedEventRef value - this was an automatic clear, not user-initiated
673
+ this.userClearedEventRef = previousUserClearedRef;
674
+ logger.debug('EventService', 'Cleared selected event - no longer in events list', {
675
+ previousEventId: selectedEventId,
676
+ eventsCount: transformedEvents.length
677
+ });
678
+ }
679
+ }
680
+
488
681
  // Reset auto-selection ref for new events
489
682
  this.hasAutoSelectedRef = false;
490
683
 
@@ -492,26 +685,62 @@ export class EventService extends BaseService implements IEventService {
492
685
  if (!skipLoadPersisted) {
493
686
  const persistedEventLoaded = await this.loadPersistedEvent(transformedEvents);
494
687
 
688
+ logger.debug('EventService', 'Event selection check', {
689
+ persistedEventLoaded,
690
+ userClearedEventRef: this.userClearedEventRef,
691
+ eventsCount: transformedEvents.length,
692
+ hasSelectedEvent: !!this.selectedEvent
693
+ });
694
+
495
695
  // If no persisted event was loaded and user hasn't explicitly cleared an event, auto-select the next event
496
696
  if (!persistedEventLoaded && !this.userClearedEventRef) {
497
697
  const nextEvent = this.getNextEventByDate(transformedEvents);
698
+ logger.debug('EventService', 'Auto-selection attempt', {
699
+ nextEventFound: !!nextEvent,
700
+ nextEventId: nextEvent?.event_id,
701
+ nextEventDate: nextEvent?.event_date
702
+ });
498
703
  if (nextEvent) {
499
704
  this.hasAutoSelectedRef = true;
500
705
  // Use setSelectedEvent() to ensure consistent behavior
501
706
  // Theme will be applied by useEventTheme() hook
502
707
  this.setSelectedEvent(nextEvent);
708
+ logger.debug('EventService', 'Auto-selected next event', {
709
+ eventId: nextEvent.event_id,
710
+ eventName: nextEvent.event_name,
711
+ eventDate: nextEvent.event_date
712
+ });
713
+ } else {
714
+ logger.debug('EventService', 'No next event found for auto-selection', {
715
+ eventsCount: transformedEvents.length,
716
+ eventsWithDates: transformedEvents.filter(e => e.event_date).length
717
+ });
503
718
  }
719
+ } else if (persistedEventLoaded) {
720
+ logger.debug('EventService', 'Skipped auto-selection - persisted event loaded');
721
+ } else if (this.userClearedEventRef) {
722
+ logger.debug('EventService', 'Skipped auto-selection - user explicitly cleared event');
504
723
  }
505
724
  } else {
506
725
  // If skipping persisted event load, still do auto-selection for new users
507
726
  if (!this.userClearedEventRef) {
508
727
  const nextEvent = this.getNextEventByDate(transformedEvents);
728
+ logger.debug('EventService', 'Auto-selection attempt (skip persisted)', {
729
+ nextEventFound: !!nextEvent,
730
+ nextEventId: nextEvent?.event_id
731
+ });
509
732
  if (nextEvent) {
510
733
  this.hasAutoSelectedRef = true;
511
734
  // Use setSelectedEvent() to ensure consistent behavior
512
735
  // Theme will be applied by useEventTheme() hook
513
736
  this.setSelectedEvent(nextEvent);
737
+ logger.debug('EventService', 'Auto-selected next event (skip persisted)', {
738
+ eventId: nextEvent.event_id,
739
+ eventName: nextEvent.event_name
740
+ });
514
741
  }
742
+ } else {
743
+ logger.debug('EventService', 'Skipped auto-selection (skip persisted) - user explicitly cleared event');
515
744
  }
516
745
  }
517
746
  }