@jmruthers/pace-core 0.5.109 → 0.5.111

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 (240) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/{AuthService-DrHrvXNZ.d.ts → AuthService-CVgsgtaZ.d.ts} +8 -0
  3. package/dist/{DataTable-5HITILXS.js → DataTable-5W2HVLLV.js} +8 -8
  4. package/dist/{UnifiedAuthProvider-A7I23UCN.js → UnifiedAuthProvider-LUM3QLS5.js} +3 -3
  5. package/dist/{api-5I3E47G2.js → api-SIZPFBFX.js} +5 -3
  6. package/dist/{audit-65VNHEV2.js → audit-5JI5T3SL.js} +2 -2
  7. package/dist/{chunk-P72NKAT5.js → chunk-2BIDKXQU.js} +157 -120
  8. package/dist/chunk-2BIDKXQU.js.map +1 -0
  9. package/dist/{chunk-S4D3Z723.js → chunk-ACYQNYHB.js} +7 -7
  10. package/dist/{chunk-D6MEKC27.js → chunk-EFVQBYFN.js} +2 -2
  11. package/dist/{chunk-EZ64QG2I.js → chunk-I5YM5BGS.js} +2 -2
  12. package/dist/{chunk-Q7APDV6H.js → chunk-IWJYNWXN.js} +13 -5
  13. package/dist/chunk-IWJYNWXN.js.map +1 -0
  14. package/dist/{chunk-YFMENCR4.js → chunk-JE2GFA3O.js} +3 -3
  15. package/dist/{chunk-AUXS7XSO.js → chunk-MW73E7SP.js} +35 -11
  16. package/dist/chunk-MW73E7SP.js.map +1 -0
  17. package/dist/{chunk-F6TSYCKP.js → chunk-PXXS26G5.js} +68 -29
  18. package/dist/chunk-PXXS26G5.js.map +1 -0
  19. package/dist/{chunk-UW2DE6JX.js → chunk-TD4BXGPE.js} +4 -4
  20. package/dist/{chunk-EYSXQ756.js → chunk-TDFBX7KJ.js} +2 -2
  21. package/dist/{chunk-WWNOVFDC.js → chunk-UGVU7L7N.js} +52 -90
  22. package/dist/chunk-UGVU7L7N.js.map +1 -0
  23. package/dist/{chunk-2W4WKJVF.js → chunk-X7SPKHYZ.js} +290 -255
  24. package/dist/chunk-X7SPKHYZ.js.map +1 -0
  25. package/dist/{chunk-3TKTL5AZ.js → chunk-ZL45MG76.js} +60 -60
  26. package/dist/chunk-ZL45MG76.js.map +1 -0
  27. package/dist/components.js +10 -10
  28. package/dist/hooks.d.ts +11 -1
  29. package/dist/hooks.js +9 -7
  30. package/dist/hooks.js.map +1 -1
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +13 -13
  33. package/dist/providers.d.ts +2 -2
  34. package/dist/providers.js +2 -2
  35. package/dist/rbac/index.d.ts +46 -29
  36. package/dist/rbac/index.js +9 -9
  37. package/dist/utils.js +1 -1
  38. package/docs/api/classes/ColumnFactory.md +1 -1
  39. package/docs/api/classes/ErrorBoundary.md +1 -1
  40. package/docs/api/classes/InvalidScopeError.md +4 -4
  41. package/docs/api/classes/MissingUserContextError.md +4 -4
  42. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  43. package/docs/api/classes/PermissionDeniedError.md +4 -4
  44. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  45. package/docs/api/classes/RBACAuditManager.md +8 -8
  46. package/docs/api/classes/RBACCache.md +8 -8
  47. package/docs/api/classes/RBACEngine.md +9 -8
  48. package/docs/api/classes/RBACError.md +4 -4
  49. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  50. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  51. package/docs/api/classes/StorageUtils.md +1 -1
  52. package/docs/api/enums/FileCategory.md +1 -1
  53. package/docs/api/interfaces/AggregateConfig.md +1 -1
  54. package/docs/api/interfaces/ButtonProps.md +1 -1
  55. package/docs/api/interfaces/CardProps.md +1 -1
  56. package/docs/api/interfaces/ColorPalette.md +1 -1
  57. package/docs/api/interfaces/ColorShade.md +1 -1
  58. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  59. package/docs/api/interfaces/DataRecord.md +1 -1
  60. package/docs/api/interfaces/DataTableAction.md +1 -1
  61. package/docs/api/interfaces/DataTableColumn.md +1 -1
  62. package/docs/api/interfaces/DataTableProps.md +1 -1
  63. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  64. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  65. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  66. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  67. package/docs/api/interfaces/FileMetadata.md +1 -1
  68. package/docs/api/interfaces/FileReference.md +1 -1
  69. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  70. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  71. package/docs/api/interfaces/FileUploadProps.md +1 -1
  72. package/docs/api/interfaces/FooterProps.md +1 -1
  73. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  74. package/docs/api/interfaces/InputProps.md +1 -1
  75. package/docs/api/interfaces/LabelProps.md +1 -1
  76. package/docs/api/interfaces/LoginFormProps.md +1 -1
  77. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  78. package/docs/api/interfaces/NavigationContextType.md +1 -1
  79. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  80. package/docs/api/interfaces/NavigationItem.md +1 -1
  81. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  82. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  83. package/docs/api/interfaces/Organisation.md +1 -1
  84. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  85. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  86. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  87. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  88. package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
  89. package/docs/api/interfaces/PaceLoginPageProps.md +4 -4
  90. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  91. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  92. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  93. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  94. package/docs/api/interfaces/PaletteData.md +1 -1
  95. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  96. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  97. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  98. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  99. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  100. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  101. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  102. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  103. package/docs/api/interfaces/RBACConfig.md +19 -8
  104. package/docs/api/interfaces/RBACLogger.md +5 -5
  105. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  106. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  107. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  108. package/docs/api/interfaces/RouteConfig.md +19 -6
  109. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  110. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  111. package/docs/api/interfaces/StorageConfig.md +1 -1
  112. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  113. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  114. package/docs/api/interfaces/StorageListOptions.md +1 -1
  115. package/docs/api/interfaces/StorageListResult.md +1 -1
  116. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  117. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  118. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  119. package/docs/api/interfaces/StyleImport.md +1 -1
  120. package/docs/api/interfaces/SwitchProps.md +1 -1
  121. package/docs/api/interfaces/ToastActionElement.md +1 -1
  122. package/docs/api/interfaces/ToastProps.md +1 -1
  123. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  124. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  125. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  126. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  127. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  128. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  129. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  130. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  131. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  132. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  133. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  134. package/docs/api/interfaces/UserEventAccess.md +1 -1
  135. package/docs/api/interfaces/UserMenuProps.md +1 -1
  136. package/docs/api/interfaces/UserProfile.md +1 -1
  137. package/docs/api/modules.md +44 -43
  138. package/docs/api-reference/hooks.md +8 -4
  139. package/docs/architecture/rpc-function-standards.md +3 -1
  140. package/docs/best-practices/common-patterns.md +3 -3
  141. package/docs/best-practices/deployment.md +10 -4
  142. package/docs/best-practices/performance.md +11 -3
  143. package/docs/core-concepts/organisations.md +8 -8
  144. package/docs/core-concepts/permissions.md +133 -72
  145. package/docs/documentation-index.md +0 -2
  146. package/docs/migration/rbac-migration.md +65 -66
  147. package/docs/rbac/README.md +114 -38
  148. package/docs/rbac/advanced-patterns.md +15 -22
  149. package/docs/rbac/api-reference.md +63 -16
  150. package/docs/rbac/examples.md +12 -12
  151. package/docs/rbac/getting-started.md +19 -19
  152. package/docs/rbac/quick-start.md +110 -35
  153. package/docs/rbac/troubleshooting.md +127 -3
  154. package/package.json +1 -1
  155. package/src/components/DataTable/components/__tests__/ActionButtons.test.tsx +913 -0
  156. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +609 -0
  157. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +434 -0
  158. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +120 -0
  159. package/src/components/DataTable/components/__tests__/PaginationControls.test.tsx +519 -0
  160. package/src/components/DataTable/examples/__tests__/HierarchicalActionsExample.test.tsx +316 -0
  161. package/src/components/DataTable/examples/__tests__/InitialPageSizeExample.test.tsx +211 -0
  162. package/src/components/FileUpload/FileUpload.tsx +2 -8
  163. package/src/components/NavigationMenu/NavigationMenu.test.tsx +38 -4
  164. package/src/components/NavigationMenu/NavigationMenu.tsx +71 -6
  165. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +193 -63
  166. package/src/components/PaceAppLayout/PaceAppLayout.tsx +102 -135
  167. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +41 -2
  168. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +61 -6
  169. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +71 -21
  170. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +113 -41
  171. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +155 -45
  172. package/src/components/PaceLoginPage/PaceLoginPage.tsx +30 -1
  173. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +63 -5
  174. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +156 -72
  175. package/src/hooks/__tests__/useRBAC.unit.test.ts +4 -38
  176. package/src/hooks/index.ts +1 -1
  177. package/src/hooks/useFileDisplay.ts +51 -0
  178. package/src/hooks/usePermissionCache.test.ts +112 -68
  179. package/src/hooks/usePermissionCache.ts +55 -15
  180. package/src/rbac/README.md +81 -39
  181. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +3 -3
  182. package/src/rbac/__tests__/engine.comprehensive.test.ts +15 -6
  183. package/src/rbac/__tests__/rbac-core.test.tsx +1 -1
  184. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +57 -4
  185. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +3 -2
  186. package/src/rbac/adapters.tsx +4 -4
  187. package/src/rbac/api.test.ts +39 -15
  188. package/src/rbac/api.ts +27 -9
  189. package/src/rbac/audit.test.ts +2 -2
  190. package/src/rbac/audit.ts +14 -5
  191. package/src/rbac/cache.test.ts +12 -0
  192. package/src/rbac/cache.ts +29 -9
  193. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -1
  194. package/src/rbac/components/NavigationGuard.tsx +14 -14
  195. package/src/rbac/components/NavigationProvider.test.tsx +1 -1
  196. package/src/rbac/components/PagePermissionGuard.tsx +22 -38
  197. package/src/rbac/components/PagePermissionProvider.test.tsx +1 -1
  198. package/src/rbac/components/PermissionEnforcer.tsx +19 -15
  199. package/src/rbac/components/RoleBasedRouter.tsx +16 -9
  200. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +123 -107
  201. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +2 -2
  202. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +121 -103
  203. package/src/rbac/config.ts +2 -0
  204. package/src/rbac/docs/event-based-apps.md +6 -6
  205. package/src/rbac/engine.ts +27 -7
  206. package/src/rbac/hooks/useCan.test.ts +29 -2
  207. package/src/rbac/hooks/usePermissions.test.ts +25 -25
  208. package/src/rbac/hooks/usePermissions.ts +47 -23
  209. package/src/rbac/hooks/useRBAC.simple.test.ts +1 -8
  210. package/src/rbac/hooks/useRBAC.test.ts +3 -40
  211. package/src/rbac/hooks/useRBAC.ts +0 -55
  212. package/src/rbac/hooks/useResolvedScope.ts +23 -31
  213. package/src/rbac/permissions.test.ts +11 -7
  214. package/src/rbac/security.test.ts +2 -2
  215. package/src/rbac/security.ts +23 -8
  216. package/src/rbac/types.test.ts +2 -2
  217. package/src/rbac/types.ts +1 -2
  218. package/src/services/EventService.ts +41 -13
  219. package/src/services/__tests__/EventService.test.ts +25 -4
  220. package/src/services/interfaces/IEventService.ts +1 -0
  221. package/src/utils/file-reference.ts +9 -0
  222. package/dist/chunk-2W4WKJVF.js.map +0 -1
  223. package/dist/chunk-3TKTL5AZ.js.map +0 -1
  224. package/dist/chunk-AUXS7XSO.js.map +0 -1
  225. package/dist/chunk-F6TSYCKP.js.map +0 -1
  226. package/dist/chunk-P72NKAT5.js.map +0 -1
  227. package/dist/chunk-Q7APDV6H.js.map +0 -1
  228. package/dist/chunk-WWNOVFDC.js.map +0 -1
  229. package/docs/rbac/breaking-changes-v3.md +0 -222
  230. package/docs/rbac/migration-guide.md +0 -260
  231. /package/dist/{DataTable-5HITILXS.js.map → DataTable-5W2HVLLV.js.map} +0 -0
  232. /package/dist/{UnifiedAuthProvider-A7I23UCN.js.map → UnifiedAuthProvider-LUM3QLS5.js.map} +0 -0
  233. /package/dist/{api-5I3E47G2.js.map → api-SIZPFBFX.js.map} +0 -0
  234. /package/dist/{audit-65VNHEV2.js.map → audit-5JI5T3SL.js.map} +0 -0
  235. /package/dist/{chunk-S4D3Z723.js.map → chunk-ACYQNYHB.js.map} +0 -0
  236. /package/dist/{chunk-D6MEKC27.js.map → chunk-EFVQBYFN.js.map} +0 -0
  237. /package/dist/{chunk-EZ64QG2I.js.map → chunk-I5YM5BGS.js.map} +0 -0
  238. /package/dist/{chunk-YFMENCR4.js.map → chunk-JE2GFA3O.js.map} +0 -0
  239. /package/dist/{chunk-UW2DE6JX.js.map → chunk-TD4BXGPE.js.map} +0 -0
  240. /package/dist/{chunk-EYSXQ756.js.map → chunk-TDFBX7KJ.js.map} +0 -0
@@ -6,15 +6,73 @@ import { useRBAC } from '../../rbac/hooks/useRBAC';
6
6
  // Mock the useRBAC hook
7
7
  vi.mock('../../rbac/hooks/useRBAC');
8
8
 
9
+ // Mock isPermittedCached from RBAC API (used by usePermissionCache)
10
+ vi.mock('../../rbac/api', async () => {
11
+ const actual = await vi.importActual('../../rbac/api');
12
+ return {
13
+ ...actual,
14
+ isPermittedCached: vi.fn().mockResolvedValue(true),
15
+ };
16
+ });
17
+
18
+ // Mock useOrganisations hook (required by usePermissionCache)
19
+ const mockOrganisationContext = {
20
+ selectedOrganisation: {
21
+ id: 'test-org-id',
22
+ name: 'Test Organisation',
23
+ display_name: 'Test Organisation',
24
+ slug: 'test-org',
25
+ description: 'Test organisation',
26
+ subscription_tier: 'basic',
27
+ settings: {},
28
+ is_active: true,
29
+ created_at: '2023-01-01T00:00:00Z',
30
+ updated_at: '2023-01-01T00:00:00Z',
31
+ },
32
+ organisations: [],
33
+ userMemberships: [],
34
+ isLoading: false,
35
+ error: null,
36
+ hasValidOrganisationContext: true,
37
+ setSelectedOrganisation: vi.fn(),
38
+ switchOrganisation: vi.fn().mockResolvedValue(undefined),
39
+ getUserRole: vi.fn().mockReturnValue('member'),
40
+ validateOrganisationAccess: vi.fn().mockReturnValue(true),
41
+ ensureOrganisationContext: vi.fn().mockReturnValue(null),
42
+ refreshOrganisations: vi.fn().mockResolvedValue(undefined),
43
+ getPrimaryOrganisation: vi.fn().mockReturnValue(null),
44
+ isOrganisationSecure: vi.fn().mockReturnValue(true),
45
+ };
46
+
47
+ vi.mock('../useOrganisations', () => ({
48
+ useOrganisations: () => mockOrganisationContext,
49
+ }));
50
+
51
+ // Mock useEvents hook (optional - wrapped in try/catch in usePermissionCache)
52
+ vi.mock('../useEvents', () => ({
53
+ useEvents: vi.fn(() => ({
54
+ selectedEvent: { event_id: 'event-123' },
55
+ events: [],
56
+ isLoading: false,
57
+ error: null,
58
+ })),
59
+ }));
60
+
9
61
  const mockUseRBAC = vi.mocked(useRBAC);
10
62
 
11
63
  // Import after mocking
12
64
  import { usePermissionCache } from '../usePermissionCache';
65
+ import { isPermittedCached } from '../../rbac/api';
66
+ const mockIsPermittedCached = vi.mocked(isPermittedCached);
13
67
 
14
68
  describe('usePermissionCache - Simple Tests', () => {
15
69
  beforeEach(() => {
16
70
  vi.clearAllMocks();
17
71
 
72
+ // Reset isPermittedCached mock
73
+ mockIsPermittedCached.mockClear();
74
+ mockIsPermittedCached.mockResolvedValue(true);
75
+
18
76
  // Mock the useRBAC hook return value
19
77
  mockUseRBAC.mockReturnValue({
20
78
  hasPermission: vi.fn().mockResolvedValue(true),
@@ -40,9 +98,9 @@ describe('usePermissionCache - Simple Tests', () => {
40
98
  });
41
99
 
42
100
  it('should check permission and return result', async () => {
43
- const mockHasPermission = vi.fn().mockResolvedValue(true);
101
+ mockIsPermittedCached.mockResolvedValueOnce(true);
44
102
  mockUseRBAC.mockReturnValue({
45
- hasPermission: mockHasPermission,
103
+ hasPermission: vi.fn().mockResolvedValue(true),
46
104
  user: { id: 'test-user-id' }
47
105
  });
48
106
 
@@ -53,15 +111,15 @@ describe('usePermissionCache - Simple Tests', () => {
53
111
  const permission = await result.current.checkPermission('read', 'dashboard');
54
112
 
55
113
  expect(permission).toBe(true);
56
- expect(mockHasPermission).toHaveBeenCalledWith('read', 'dashboard');
114
+ expect(mockIsPermittedCached).toHaveBeenCalled();
57
115
  });
58
116
 
59
117
  it('should handle permission check errors gracefully', async () => {
60
118
  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
61
- const mockHasPermission = vi.fn().mockRejectedValue(new Error('Database error'));
119
+ mockIsPermittedCached.mockRejectedValueOnce(new Error('Database error'));
62
120
 
63
121
  mockUseRBAC.mockReturnValue({
64
- hasPermission: mockHasPermission,
122
+ hasPermission: vi.fn().mockResolvedValue(true),
65
123
  user: { id: 'test-user-id' }
66
124
  });
67
125
 
@@ -6,15 +6,86 @@ import { useRBAC } from '../../rbac/hooks/useRBAC';
6
6
  // Mock the useRBAC hook
7
7
  vi.mock('../../rbac/hooks/useRBAC');
8
8
 
9
+ // Mock useUnifiedAuth (required by usePermissionCache for user context)
10
+ vi.mock('../../providers/UnifiedAuthProvider', () => ({
11
+ useUnifiedAuth: vi.fn(() => ({
12
+ user: { id: 'test-user-id' },
13
+ session: null,
14
+ appName: 'test-app',
15
+ selectedOrganisationId: undefined,
16
+ selectedEventId: undefined
17
+ }))
18
+ }));
19
+
20
+ // Mock isPermittedCached from RBAC API (used by usePermissionCache)
21
+ vi.mock('../../rbac/api', async () => {
22
+ const actual = await vi.importActual('../../rbac/api');
23
+ return {
24
+ ...actual,
25
+ isPermittedCached: vi.fn().mockResolvedValue(true),
26
+ };
27
+ });
28
+
29
+ // Mock useOrganisations hook (required by usePermissionCache)
30
+ const mockOrganisationContext = {
31
+ selectedOrganisation: {
32
+ id: 'test-org-id',
33
+ name: 'Test Organisation',
34
+ display_name: 'Test Organisation',
35
+ slug: 'test-org',
36
+ description: 'Test organisation',
37
+ subscription_tier: 'basic',
38
+ settings: {},
39
+ is_active: true,
40
+ created_at: '2023-01-01T00:00:00Z',
41
+ updated_at: '2023-01-01T00:00:00Z',
42
+ },
43
+ organisations: [],
44
+ userMemberships: [],
45
+ isLoading: false,
46
+ error: null,
47
+ hasValidOrganisationContext: true,
48
+ setSelectedOrganisation: vi.fn(),
49
+ switchOrganisation: vi.fn().mockResolvedValue(undefined),
50
+ getUserRole: vi.fn().mockReturnValue('member'),
51
+ validateOrganisationAccess: vi.fn().mockReturnValue(true),
52
+ ensureOrganisationContext: vi.fn().mockReturnValue(null),
53
+ refreshOrganisations: vi.fn().mockResolvedValue(undefined),
54
+ getPrimaryOrganisation: vi.fn().mockReturnValue(null),
55
+ isOrganisationSecure: vi.fn().mockReturnValue(true),
56
+ };
57
+
58
+ vi.mock('../useOrganisations', () => ({
59
+ useOrganisations: () => mockOrganisationContext,
60
+ }));
61
+
62
+ // Mock useEvents hook (optional - wrapped in try/catch in usePermissionCache)
63
+ vi.mock('../useEvents', () => ({
64
+ useEvents: vi.fn(() => ({
65
+ selectedEvent: { event_id: 'event-123' },
66
+ events: [],
67
+ isLoading: false,
68
+ error: null,
69
+ })),
70
+ }));
71
+
9
72
  const mockUseRBAC = vi.mocked(useRBAC);
10
73
 
11
74
  // Import after mocking
12
75
  import { usePermissionCache } from '../usePermissionCache';
76
+ import { isPermittedCached } from '../../rbac/api';
77
+ import { useUnifiedAuth } from '../../providers/UnifiedAuthProvider';
78
+ const mockIsPermittedCached = vi.mocked(isPermittedCached);
79
+ const mockUseUnifiedAuth = vi.mocked(useUnifiedAuth);
13
80
 
14
81
  describe('usePermissionCache', () => {
15
82
  beforeEach(() => {
16
83
  vi.clearAllMocks();
17
84
 
85
+ // Reset isPermittedCached mock
86
+ mockIsPermittedCached.mockClear();
87
+ mockIsPermittedCached.mockResolvedValue(true);
88
+
18
89
  // Default mock implementation
19
90
  mockUseRBAC.mockReturnValue({
20
91
  hasPermission: vi.fn().mockResolvedValue(true),
@@ -59,9 +130,9 @@ describe('usePermissionCache', () => {
59
130
 
60
131
  describe('Single Permission Checking', () => {
61
132
  it('checks permission and caches result', async () => {
62
- const mockHasPermission = vi.fn().mockResolvedValue(true);
133
+ mockIsPermittedCached.mockResolvedValueOnce(true);
63
134
  mockUseRBAC.mockReturnValue({
64
- hasPermission: mockHasPermission,
135
+ hasPermission: vi.fn().mockResolvedValue(true),
65
136
  user: { id: 'test-user-id' }
66
137
  });
67
138
 
@@ -72,13 +143,13 @@ describe('usePermissionCache', () => {
72
143
  const permission = await result.current.checkPermission('read', 'dashboard');
73
144
 
74
145
  expect(permission).toBe(true);
75
- expect(mockHasPermission).toHaveBeenCalledWith('read', 'dashboard');
146
+ expect(mockIsPermittedCached).toHaveBeenCalled();
76
147
  });
77
148
 
78
149
  it('returns cached result for subsequent calls', async () => {
79
- const mockHasPermission = vi.fn().mockResolvedValue(true);
150
+ mockIsPermittedCached.mockResolvedValueOnce(true);
80
151
  mockUseRBAC.mockReturnValue({
81
- hasPermission: mockHasPermission,
152
+ hasPermission: vi.fn().mockResolvedValue(true),
82
153
  user: { id: 'test-user-id' }
83
154
  });
84
155
 
@@ -92,13 +163,14 @@ describe('usePermissionCache', () => {
92
163
  // Second call should use cache
93
164
  await result.current.checkPermission('read', 'dashboard');
94
165
 
95
- expect(mockHasPermission).toHaveBeenCalledTimes(1);
166
+ // Should only call isPermittedCached once (second call uses cache)
167
+ expect(mockIsPermittedCached).toHaveBeenCalledTimes(1);
96
168
  });
97
169
 
98
170
  it('respects custom TTL', async () => {
99
- const mockHasPermission = vi.fn().mockResolvedValue(true);
171
+ mockIsPermittedCached.mockResolvedValueOnce(true);
100
172
  mockUseRBAC.mockReturnValue({
101
- hasPermission: mockHasPermission,
173
+ hasPermission: vi.fn().mockResolvedValue(true),
102
174
  user: { id: 'test-user-id' }
103
175
  });
104
176
 
@@ -108,15 +180,15 @@ describe('usePermissionCache', () => {
108
180
 
109
181
  await result.current.checkPermission('read', 'dashboard', 2000);
110
182
 
111
- expect(mockHasPermission).toHaveBeenCalledWith('read', 'dashboard');
183
+ expect(mockIsPermittedCached).toHaveBeenCalled();
112
184
  });
113
185
 
114
186
  it('handles permission check errors gracefully', async () => {
115
187
  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
116
- const mockHasPermission = vi.fn().mockRejectedValue(new Error('Database error'));
188
+ mockIsPermittedCached.mockRejectedValueOnce(new Error('Database error'));
117
189
 
118
190
  mockUseRBAC.mockReturnValue({
119
- hasPermission: mockHasPermission,
191
+ hasPermission: vi.fn().mockResolvedValue(true),
120
192
  user: { id: 'test-user-id' }
121
193
  });
122
194
 
@@ -135,14 +207,14 @@ describe('usePermissionCache', () => {
135
207
 
136
208
  describe('Multiple Permission Checking', () => {
137
209
  it('checks multiple permissions efficiently', async () => {
138
- const mockHasPermission = vi.fn()
210
+ mockIsPermittedCached
139
211
  .mockResolvedValueOnce(true)
140
212
  .mockResolvedValueOnce(false)
141
213
  .mockResolvedValueOnce(true)
142
214
  .mockResolvedValueOnce(false);
143
215
 
144
216
  mockUseRBAC.mockReturnValue({
145
- hasPermission: mockHasPermission,
217
+ hasPermission: vi.fn().mockResolvedValue(true),
146
218
  user: { id: 'test-user-id' }
147
219
  });
148
220
 
@@ -176,12 +248,12 @@ describe('usePermissionCache', () => {
176
248
  });
177
249
 
178
250
  it('uses cached results for multiple permission checks', async () => {
179
- const mockHasPermission = vi.fn()
251
+ mockIsPermittedCached
180
252
  .mockResolvedValueOnce(true)
181
253
  .mockResolvedValueOnce(false);
182
254
 
183
255
  mockUseRBAC.mockReturnValue({
184
- hasPermission: mockHasPermission,
256
+ hasPermission: vi.fn().mockResolvedValue(true),
185
257
  user: { id: 'test-user-id' }
186
258
  });
187
259
 
@@ -202,17 +274,19 @@ describe('usePermissionCache', () => {
202
274
  ['create', 'dashboard']
203
275
  ]);
204
276
 
205
- expect(mockHasPermission).toHaveBeenCalledTimes(2);
277
+ // Should only call isPermittedCached twice (once per permission, second call uses cache)
278
+ expect(mockIsPermittedCached).toHaveBeenCalledTimes(2);
206
279
  });
207
280
 
208
281
  it('handles mixed cached and uncached permissions', async () => {
209
- const mockHasPermission = vi.fn()
282
+ mockIsPermittedCached
210
283
  .mockResolvedValueOnce(true)
211
284
  .mockResolvedValueOnce(false)
212
- .mockResolvedValueOnce(true);
285
+ .mockResolvedValueOnce(true)
286
+ .mockResolvedValueOnce(false);
213
287
 
214
288
  mockUseRBAC.mockReturnValue({
215
- hasPermission: mockHasPermission,
289
+ hasPermission: vi.fn().mockResolvedValue(true),
216
290
  user: { id: 'test-user-id' }
217
291
  });
218
292
 
@@ -245,9 +319,9 @@ describe('usePermissionCache', () => {
245
319
 
246
320
  describe('Cache Management', () => {
247
321
  it('enforces maximum cache size', async () => {
248
- const mockHasPermission = vi.fn().mockResolvedValue(true);
322
+ mockIsPermittedCached.mockResolvedValue(true);
249
323
  mockUseRBAC.mockReturnValue({
250
- hasPermission: mockHasPermission,
324
+ hasPermission: vi.fn().mockResolvedValue(true),
251
325
  user: { id: 'test-user-id' }
252
326
  });
253
327
 
@@ -268,9 +342,9 @@ describe('usePermissionCache', () => {
268
342
  });
269
343
 
270
344
  it('invalidates cache entries', async () => {
271
- const mockHasPermission = vi.fn().mockResolvedValue(true);
345
+ mockIsPermittedCached.mockResolvedValue(true);
272
346
  mockUseRBAC.mockReturnValue({
273
- hasPermission: mockHasPermission,
347
+ hasPermission: vi.fn().mockResolvedValue(true),
274
348
  user: { id: 'test-user-id' }
275
349
  });
276
350
 
@@ -289,13 +363,14 @@ describe('usePermissionCache', () => {
289
363
  await result.current.checkPermission('read', 'dashboard');
290
364
  await result.current.checkPermission('create', 'dashboard');
291
365
 
292
- expect(mockHasPermission).toHaveBeenCalledTimes(4);
366
+ // Should be called 4 times (2 initial + 2 after invalidation)
367
+ expect(mockIsPermittedCached).toHaveBeenCalledTimes(4);
293
368
  });
294
369
 
295
370
  it('invalidates cache entries by pattern', async () => {
296
- const mockHasPermission = vi.fn().mockResolvedValue(true);
371
+ mockIsPermittedCached.mockResolvedValue(true);
297
372
  mockUseRBAC.mockReturnValue({
298
- hasPermission: mockHasPermission,
373
+ hasPermission: vi.fn().mockResolvedValue(true),
299
374
  user: { id: 'test-user-id' }
300
375
  });
301
376
 
@@ -316,15 +391,16 @@ describe('usePermissionCache', () => {
316
391
  await result.current.checkPermission('read', 'admin');
317
392
 
318
393
  // dashboard should be called again, admin should use cache
319
- expect(mockHasPermission).toHaveBeenCalledTimes(4);
394
+ // 3 initial calls + 1 for dashboard after invalidation = 4 total
395
+ expect(mockIsPermittedCached).toHaveBeenCalledTimes(4);
320
396
  });
321
397
 
322
398
  it('cleans up expired cache entries', async () => {
323
399
  vi.useFakeTimers();
324
400
 
325
- const mockHasPermission = vi.fn().mockResolvedValue(true);
401
+ mockIsPermittedCached.mockResolvedValue(true);
326
402
  mockUseRBAC.mockReturnValue({
327
- hasPermission: mockHasPermission,
403
+ hasPermission: vi.fn().mockResolvedValue(true),
328
404
  user: { id: 'test-user-id' }
329
405
  });
330
406
 
@@ -348,12 +424,12 @@ describe('usePermissionCache', () => {
348
424
 
349
425
  describe('Debug Information', () => {
350
426
  it('provides accurate debug information', async () => {
351
- const mockHasPermission = vi.fn()
427
+ mockIsPermittedCached
352
428
  .mockResolvedValueOnce(true)
353
429
  .mockResolvedValueOnce(false);
354
430
 
355
431
  mockUseRBAC.mockReturnValue({
356
- hasPermission: mockHasPermission,
432
+ hasPermission: vi.fn().mockResolvedValue(true),
357
433
  user: { id: 'test-user-id' }
358
434
  });
359
435
 
@@ -378,9 +454,9 @@ describe('usePermissionCache', () => {
378
454
  });
379
455
 
380
456
  it('tracks response times accurately', async () => {
381
- const mockHasPermission = vi.fn().mockResolvedValue(true);
457
+ mockIsPermittedCached.mockResolvedValue(true);
382
458
  mockUseRBAC.mockReturnValue({
383
- hasPermission: mockHasPermission,
459
+ hasPermission: vi.fn().mockResolvedValue(true),
384
460
  user: { id: 'test-user-id' }
385
461
  });
386
462
 
@@ -400,9 +476,17 @@ describe('usePermissionCache', () => {
400
476
 
401
477
  describe('Audit Trail', () => {
402
478
  it('records audit trail when enabled', async () => {
403
- const mockHasPermission = vi.fn().mockResolvedValue(true);
479
+ mockIsPermittedCached.mockResolvedValue(true);
480
+ // The hook uses useUnifiedAuth for user, not useRBAC
481
+ mockUseUnifiedAuth.mockReturnValue({
482
+ user: { id: 'test-user-id' },
483
+ session: null,
484
+ appName: 'test-app',
485
+ selectedOrganisationId: undefined,
486
+ selectedEventId: undefined
487
+ } as any);
404
488
  mockUseRBAC.mockReturnValue({
405
- hasPermission: mockHasPermission,
489
+ hasPermission: vi.fn().mockResolvedValue(true),
406
490
  user: { id: 'test-user-id' }
407
491
  });
408
492
 
@@ -416,28 +500,28 @@ describe('usePermissionCache', () => {
416
500
  const auditTrail = result.current.getAuditTrail();
417
501
 
418
502
  expect(auditTrail).toHaveLength(2);
419
- expect(auditTrail[0]).toEqual({
420
- timestamp: expect.any(Number),
421
- operation: 'read',
422
- pageId: 'dashboard',
423
- result: true,
424
- cached: false,
425
- userId: 'test-user-id'
426
- });
427
- expect(auditTrail[1]).toEqual({
428
- timestamp: expect.any(Number),
429
- operation: 'create',
430
- pageId: 'admin',
431
- result: true,
432
- cached: false,
433
- userId: 'test-user-id'
434
- });
503
+ // Check all properties including timestamp
504
+ expect(auditTrail[0]).toHaveProperty('timestamp');
505
+ expect(auditTrail[0]).toHaveProperty('operation', 'read');
506
+ expect(auditTrail[0]).toHaveProperty('pageId', 'dashboard');
507
+ expect(auditTrail[0]).toHaveProperty('result', true);
508
+ expect(auditTrail[0]).toHaveProperty('cached', false);
509
+ expect(auditTrail[0]).toHaveProperty('userId', 'test-user-id');
510
+ expect(typeof auditTrail[0].timestamp).toBe('number');
511
+
512
+ expect(auditTrail[1]).toHaveProperty('timestamp');
513
+ expect(auditTrail[1]).toHaveProperty('operation', 'create');
514
+ expect(auditTrail[1]).toHaveProperty('pageId', 'admin');
515
+ expect(auditTrail[1]).toHaveProperty('result', true);
516
+ expect(auditTrail[1]).toHaveProperty('cached', false);
517
+ expect(auditTrail[1]).toHaveProperty('userId', 'test-user-id');
518
+ expect(typeof auditTrail[1].timestamp).toBe('number');
435
519
  });
436
520
 
437
521
  it('does not record audit trail when disabled', async () => {
438
- const mockHasPermission = vi.fn().mockResolvedValue(true);
522
+ mockIsPermittedCached.mockResolvedValue(true);
439
523
  mockUseRBAC.mockReturnValue({
440
- hasPermission: mockHasPermission,
524
+ hasPermission: vi.fn().mockResolvedValue(true),
441
525
  user: { id: 'test-user-id' }
442
526
  });
443
527
 
@@ -452,9 +536,9 @@ describe('usePermissionCache', () => {
452
536
  });
453
537
 
454
538
  it('limits audit trail size', async () => {
455
- const mockHasPermission = vi.fn().mockResolvedValue(true);
539
+ mockIsPermittedCached.mockResolvedValue(true);
456
540
  mockUseRBAC.mockReturnValue({
457
- hasPermission: mockHasPermission,
541
+ hasPermission: vi.fn().mockResolvedValue(true),
458
542
  user: { id: 'test-user-id' }
459
543
  });
460
544
 
@@ -476,9 +560,9 @@ describe('usePermissionCache', () => {
476
560
  it('logs permission checks when enabled', async () => {
477
561
  const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
478
562
 
479
- const mockHasPermission = vi.fn().mockResolvedValue(true);
563
+ mockIsPermittedCached.mockResolvedValue(true);
480
564
  mockUseRBAC.mockReturnValue({
481
- hasPermission: mockHasPermission,
565
+ hasPermission: vi.fn().mockResolvedValue(true),
482
566
  user: { id: 'test-user-id' }
483
567
  });
484
568
 
@@ -498,9 +582,9 @@ describe('usePermissionCache', () => {
498
582
  it('does not log when disabled', async () => {
499
583
  const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
500
584
 
501
- const mockHasPermission = vi.fn().mockResolvedValue(true);
585
+ mockIsPermittedCached.mockResolvedValue(true);
502
586
  mockUseRBAC.mockReturnValue({
503
- hasPermission: mockHasPermission,
587
+ hasPermission: vi.fn().mockResolvedValue(true),
504
588
  user: { id: 'test-user-id' }
505
589
  });
506
590
 
@@ -520,14 +604,14 @@ describe('usePermissionCache', () => {
520
604
 
521
605
  describe('Cached Permissions', () => {
522
606
  it('returns cached permissions for a page', async () => {
523
- const mockHasPermission = vi.fn()
607
+ mockIsPermittedCached
524
608
  .mockResolvedValueOnce(true)
525
609
  .mockResolvedValueOnce(false)
526
610
  .mockResolvedValueOnce(true)
527
611
  .mockResolvedValueOnce(false);
528
612
 
529
613
  mockUseRBAC.mockReturnValue({
530
- hasPermission: mockHasPermission,
614
+ hasPermission: vi.fn().mockResolvedValue(true),
531
615
  user: { id: 'test-user-id' }
532
616
  });
533
617
 
@@ -554,9 +638,9 @@ describe('usePermissionCache', () => {
554
638
  });
555
639
 
556
640
  it('returns empty array for uncached page', async () => {
557
- const mockHasPermission = vi.fn().mockResolvedValue(true);
641
+ mockIsPermittedCached.mockResolvedValue(true);
558
642
  mockUseRBAC.mockReturnValue({
559
- hasPermission: mockHasPermission,
643
+ hasPermission: vi.fn().mockResolvedValue(true),
560
644
  user: { id: 'test-user-id' }
561
645
  });
562
646
 
@@ -572,9 +656,9 @@ describe('usePermissionCache', () => {
572
656
 
573
657
  describe('Edge Cases', () => {
574
658
  it('handles concurrent permission checks', async () => {
575
- const mockHasPermission = vi.fn().mockResolvedValue(true);
659
+ mockIsPermittedCached.mockResolvedValue(true);
576
660
  mockUseRBAC.mockReturnValue({
577
- hasPermission: mockHasPermission,
661
+ hasPermission: vi.fn().mockResolvedValue(true),
578
662
  user: { id: 'test-user-id' }
579
663
  });
580
664
 
@@ -592,13 +676,13 @@ describe('usePermissionCache', () => {
592
676
  const results = await Promise.all(promises);
593
677
 
594
678
  expect(results).toEqual([true, true, true]);
595
- expect(mockHasPermission).toHaveBeenCalledTimes(1); // Should only call once due to caching
679
+ expect(mockIsPermittedCached).toHaveBeenCalledTimes(1); // Should only call once due to caching
596
680
  });
597
681
 
598
682
  it('handles rapid cache invalidation', async () => {
599
- const mockHasPermission = vi.fn().mockResolvedValue(true);
683
+ mockIsPermittedCached.mockResolvedValue(true);
600
684
  mockUseRBAC.mockReturnValue({
601
- hasPermission: mockHasPermission,
685
+ hasPermission: vi.fn().mockResolvedValue(true),
602
686
  user: { id: 'test-user-id' }
603
687
  });
604
688
 
@@ -610,13 +694,13 @@ describe('usePermissionCache', () => {
610
694
  result.current.invalidateCache();
611
695
  await result.current.checkPermission('read', 'dashboard');
612
696
 
613
- expect(mockHasPermission).toHaveBeenCalledTimes(2);
697
+ expect(mockIsPermittedCached).toHaveBeenCalledTimes(2);
614
698
  });
615
699
 
616
700
  it('handles empty permission arrays', async () => {
617
- const mockHasPermission = vi.fn().mockResolvedValue(true);
701
+ mockIsPermittedCached.mockResolvedValue(true);
618
702
  mockUseRBAC.mockReturnValue({
619
- hasPermission: mockHasPermission,
703
+ hasPermission: vi.fn().mockResolvedValue(true),
620
704
  user: { id: 'test-user-id' }
621
705
  });
622
706
 
@@ -627,7 +711,7 @@ describe('usePermissionCache', () => {
627
711
  const permissions = await result.current.checkMultiplePermissions([]);
628
712
 
629
713
  expect(permissions).toHaveLength(0);
630
- expect(mockHasPermission).not.toHaveBeenCalled();
714
+ expect(mockIsPermittedCached).not.toHaveBeenCalled();
631
715
  });
632
716
  });
633
717
  });
@@ -139,45 +139,11 @@ describe('useRBAC (unit)', () => {
139
139
  await waitFor(() => expect(result.current.isLoading).toBe(false));
140
140
 
141
141
  expect(result.current.isSuperAdmin).toBe(true);
142
- expect(await result.current.hasPermission('delete', 'users')).toBe(true);
142
+ // Note: Permission checking tests moved to useCan.test.ts
143
143
  });
144
144
 
145
- it('uses cached permission map when checking permissions', async () => {
146
- mockUseUnifiedAuth.mockReturnValue({
147
- user: { id: 'user-1' },
148
- session: { access_token: 'token' },
149
- appName: 'console',
150
- });
151
- mockUseOrganisations.mockReturnValue({ selectedOrganisation: { id: 'org-1' } });
152
- mockGetPermissionMap.mockResolvedValue({ 'read:users': true, 'write:users': false });
153
-
154
- const { result } = renderHook(() => useRBAC());
155
-
156
- await waitFor(() => expect(result.current.isLoading).toBe(false));
157
-
158
- mockIsPermittedCached.mockClear();
159
- expect(await result.current.hasPermission('read:users')).toBe(true);
160
- expect(await result.current.hasPermission('write:users')).toBe(false);
161
- expect(mockIsPermittedCached).not.toHaveBeenCalled();
162
- });
163
-
164
- it('falls back to engine when permission not cached', async () => {
165
- mockUseUnifiedAuth.mockReturnValue({
166
- user: { id: 'user-1' },
167
- session: { access_token: 'token' },
168
- appName: 'console',
169
- });
170
- mockUseOrganisations.mockReturnValue({ selectedOrganisation: { id: 'org-1' } });
171
- mockGetPermissionMap.mockResolvedValue({});
172
- mockIsPermittedCached.mockResolvedValue(true);
173
-
174
- const { result } = renderHook(() => useRBAC());
175
-
176
- await waitFor(() => expect(result.current.isLoading).toBe(false));
177
-
178
- expect(await result.current.hasPermission('read', 'users')).toBe(true);
179
- expect(mockIsPermittedCached).toHaveBeenCalled();
180
- });
145
+ // Note: Permission checking tests have been moved to useCan.test.ts
146
+ // useRBAC now focuses on role information and context, not permission checks
181
147
 
182
148
  it('returns true for hasGlobalPermission when organisation admin', async () => {
183
149
  mockUseUnifiedAuth.mockReturnValue({
@@ -213,6 +179,6 @@ describe('useRBAC (unit)', () => {
213
179
 
214
180
  expect(result.current.error).toBeInstanceOf(Error);
215
181
  expect(result.current.globalRole).toBeNull();
216
- expect(await result.current.hasPermission('read', 'users')).toBe(false);
182
+ // Note: Permission checking moved to useCan hook
217
183
  });
218
184
  });
@@ -52,7 +52,7 @@ export { useDataTablePerformance } from './useDataTablePerformance';
52
52
  export type { UseDataTablePerformanceOptions, UseDataTablePerformanceReturn } from './useDataTablePerformance';
53
53
 
54
54
  // === FILE DISPLAY HOOKS ===
55
- export { useFileDisplay, clearFileDisplayCache, getFileDisplayCacheStats } from './useFileDisplay';
55
+ export { useFileDisplay, clearFileDisplayCache, getFileDisplayCacheStats, invalidateFileDisplayCache } from './useFileDisplay';
56
56
  export type { UseFileDisplayReturn, UseFileDisplayOptions } from './useFileDisplay';
57
57
 
58
58
  // === PUBLIC DATA ACCESS HOOKS ===