@jmruthers/pace-core 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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-THFPBKTP.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-KAGUYQ4J.js} +5 -4
  8. package/dist/{api-MVVQZLJI.js → api-IAGWF3ZG.js} +10 -10
  9. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  10. package/dist/{chunk-SFZUDBL5.js → chunk-2T2IG7T7.js} +70 -56
  11. package/dist/chunk-2T2IG7T7.js.map +1 -0
  12. package/dist/{chunk-MMZ7JXPU.js → chunk-6Z7LTB3D.js} +13 -21
  13. package/dist/{chunk-MMZ7JXPU.js.map → chunk-6Z7LTB3D.js.map} +1 -1
  14. package/dist/{chunk-6J4GEEJR.js → chunk-CNCQDFLN.js} +53 -27
  15. package/dist/chunk-CNCQDFLN.js.map +1 -0
  16. package/dist/chunk-DGUM43GV.js +11 -0
  17. package/dist/{chunk-EHMR7VYL.js → chunk-DWUBLJJM.js} +361 -187
  18. package/dist/chunk-DWUBLJJM.js.map +1 -0
  19. package/dist/{chunk-2UOI2FG5.js → chunk-HFZBI76P.js} +4 -4
  20. package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
  21. package/dist/{chunk-3XC4CPTD.js → chunk-PQBSKX33.js} +244 -5727
  22. package/dist/chunk-PQBSKX33.js.map +1 -0
  23. package/dist/chunk-QRPVRXYT.js +226 -0
  24. package/dist/chunk-QRPVRXYT.js.map +1 -0
  25. package/dist/{chunk-24UVZUZG.js → chunk-RWEBCB47.js} +129 -387
  26. package/dist/chunk-RWEBCB47.js.map +1 -0
  27. package/dist/{chunk-XWQCNGTQ.js → chunk-YDQHOZNA.js} +173 -79
  28. package/dist/chunk-YDQHOZNA.js.map +1 -0
  29. package/dist/{chunk-NECFR5MM.js → chunk-ZNIWI3UC.js} +562 -644
  30. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  31. package/dist/components.d.ts +2 -2
  32. package/dist/components.js +12 -13
  33. package/dist/contextValidator-3JNZKUTX.js +9 -0
  34. package/dist/contextValidator-3JNZKUTX.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 +3 -3
  60. package/scripts/audit/core/checks/compliance.cjs +72 -0
  61. package/scripts/audit/core/checks/dependencies.cjs +559 -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 +135 -33
  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 +186 -54
  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-THFPBKTP.js.map} +0 -0
  294. /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  295. /package/dist/{api-MVVQZLJI.js.map → api-IAGWF3ZG.js.map} +0 -0
  296. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  297. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  298. /package/dist/{chunk-2UOI2FG5.js.map → chunk-HFZBI76P.js.map} +0 -0
  299. /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
@@ -97,6 +97,14 @@ const documentationCheck = {
97
97
  }
98
98
 
99
99
  // Find exported functions/components
100
+ // Match various export patterns:
101
+ // - export function Name
102
+ // - export const Name = ...
103
+ // - export class Name
104
+ // - export interface Name
105
+ // - export type Name
106
+ // - export default function Name
107
+ // - export default const Name = ...
100
108
  const exportPattern = /export\s+(?:default\s+)?(?:async\s+)?(function|const|class|interface|type)\s+(\w+)/g;
101
109
  let match;
102
110
  while ((match = exportPattern.exec(content)) !== null) {
@@ -105,9 +113,66 @@ const documentationCheck = {
105
113
  const exportIndex = match.index;
106
114
 
107
115
  // Check if there's JSDoc before this export
108
- const beforeExport = content.substring(Math.max(0, exportIndex - 500), exportIndex);
109
- const hasJSDoc = /\/\*\*[\s\S]*?\*\//.test(beforeExport) ||
110
- /\/\/\/\s*</.test(beforeExport);
116
+ // Search backwards up to 2000 characters, but stop at:
117
+ // - Previous export statement
118
+ // - Previous function/class/interface/type declaration (non-exported)
119
+ // - File start
120
+ const searchStart = Math.max(0, exportIndex - 2000);
121
+ const beforeExport = content.substring(searchStart, exportIndex);
122
+
123
+ // Find the last export/declaration before this one to avoid false positives
124
+ // from JSDoc comments that belong to previous exports
125
+ const previousExportMatch = beforeExport.match(/export\s+(?:default\s+)?(?:async\s+)?(function|const|class|interface|type)\s+\w+/);
126
+ const previousDeclMatch = beforeExport.match(/(?:^|\n)\s*(?:async\s+)?(function|const|class|interface|type)\s+\w+/);
127
+
128
+ let searchWindow = beforeExport;
129
+ if (previousExportMatch && previousExportMatch.index !== undefined) {
130
+ // Start search after the previous export
131
+ const prevExportEnd = previousExportMatch.index + previousExportMatch[0].length;
132
+ searchWindow = beforeExport.substring(prevExportEnd);
133
+ } else if (previousDeclMatch && previousDeclMatch.index !== undefined) {
134
+ // Start search after the previous declaration (but be more lenient)
135
+ const prevDeclEnd = previousDeclMatch.index + previousDeclMatch[0].length;
136
+ // Only use this if it's close to our export (within 500 chars)
137
+ if (prevDeclEnd > beforeExport.length - 500) {
138
+ searchWindow = beforeExport.substring(prevDeclEnd);
139
+ }
140
+ }
141
+
142
+ // Check for JSDoc in the search window
143
+ // Look for standard JSDoc block comments /** ... */
144
+ // Also check for TypeScript triple-slash reference comments /// <
145
+ // Check if JSDoc ends close to our export (within last 100 chars of search window)
146
+ const jsdocBlockMatch = searchWindow.match(/\/\*\*[\s\S]*?\*\//g);
147
+ const jsdocRefMatch = searchWindow.match(/\/\/\/\s*</g);
148
+
149
+ let hasJSDoc = false;
150
+
151
+ // Check if JSDoc block comment ends close to our export
152
+ if (jsdocBlockMatch && jsdocBlockMatch.length > 0) {
153
+ const lastJSDoc = jsdocBlockMatch[jsdocBlockMatch.length - 1];
154
+ const lastJSDocEnd = searchWindow.lastIndexOf(lastJSDoc) + lastJSDoc.length;
155
+ // JSDoc is considered associated if it ends within 200 characters of the export
156
+ // or if there's only whitespace/comments between JSDoc and export
157
+ const distanceToExport = searchWindow.length - lastJSDocEnd;
158
+ const betweenJSDocAndExport = searchWindow.substring(lastJSDocEnd);
159
+ // Check if there's only whitespace, comments, or newlines between JSDoc and export
160
+ // Remove all whitespace and single-line comments, then check if anything remains
161
+ const cleanedBetween = betweenJSDocAndExport
162
+ .replace(/\/\/.*$/gm, '') // Remove single-line comments
163
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments (shouldn't be here, but just in case)
164
+ .replace(/[\s\n\r]/g, ''); // Remove all whitespace
165
+ const onlyWhitespaceBetween = cleanedBetween.length === 0;
166
+ hasJSDoc = distanceToExport <= 200 || onlyWhitespaceBetween;
167
+ }
168
+
169
+ // Also check for TypeScript reference comments
170
+ if (!hasJSDoc && jsdocRefMatch && jsdocRefMatch.length > 0) {
171
+ const lastRef = jsdocRefMatch[jsdocRefMatch.length - 1];
172
+ const lastRefEnd = searchWindow.lastIndexOf(lastRef) + lastRef.length;
173
+ const distanceToExport = searchWindow.length - lastRefEnd;
174
+ hasJSDoc = distanceToExport <= 200;
175
+ }
111
176
 
112
177
  // Only check public exports (not internal utilities)
113
178
  const isPublic = match[0].includes('export') &&
@@ -9,7 +9,6 @@
9
9
  * - Missing required environment variables
10
10
  * - Hardcoded secrets/API keys
11
11
  * - Incorrect environment variable usage
12
- * - Missing .env.example file
13
12
  */
14
13
 
15
14
  const fs = require('fs');
@@ -17,7 +16,7 @@ const path = require('path');
17
16
 
18
17
  const environmentCheck = {
19
18
  name: 'environment',
20
- description: 'Environment variable usage (secrets, required vars, .env.example)',
19
+ description: 'Environment variable usage (secrets, required vars)',
21
20
  severity: 'warning',
22
21
 
23
22
  async run(context) {
@@ -26,24 +25,13 @@ const environmentCheck = {
26
25
  const warnings = [];
27
26
  const suggestions = [];
28
27
 
29
- // Check for .env.example
30
- const envExamplePath = path.join(projectRoot, '.env.example');
31
- if (!fs.existsSync(envExamplePath)) {
32
- suggestions.push({
33
- type: 'missing-env-example',
34
- file: '.env.example',
35
- message: '.env.example file is missing',
36
- recommendation: 'Create .env.example with all required environment variables (without actual values)'
37
- });
38
- }
39
-
40
28
  // Required environment variables for pace-core apps
41
29
  const requiredEnvVars = [
42
30
  'VITE_SUPABASE_URL',
43
31
  'VITE_SUPABASE_PUBLISHABLE_KEY'
44
32
  ];
45
33
 
46
- // Check .env.example for required vars
34
+ // Check .env.example for required vars (if file exists)
47
35
  if (fs.existsSync(envExamplePath)) {
48
36
  try {
49
37
  const envExampleContent = fs.readFileSync(envExamplePath, 'utf8');
@@ -37,10 +37,56 @@ const errorHandlingCheck = {
37
37
  const packagesCorePath = path.join(projectRoot, 'packages', 'core');
38
38
  const isPaceCoreRepository = fs.existsSync(packagesCorePath);
39
39
 
40
- // Check for ErrorBoundary usage
40
+ // Check for ErrorBoundary usage in main app file
41
41
  let hasErrorBoundary = false;
42
42
  const mainFiles = ['main.tsx', 'main.ts', 'App.tsx', 'App.ts', 'index.tsx', 'index.ts'];
43
43
 
44
+ // First, specifically check the main app file for ErrorBoundary usage
45
+ // This must be done before the general file loop to ensure we check the actual main file
46
+ // Previously, the check would skip src/ files (thinking they were demo apps) and only
47
+ // check if ErrorBoundary appeared anywhere in the codebase. Now we specifically check
48
+ // the main.tsx file for proper ErrorBoundary import and JSX usage patterns.
49
+ if (!isPaceCoreRepository) {
50
+ const mainFile = mainFiles.find(file => {
51
+ const filePath = path.join(projectRoot, 'src', file);
52
+ return fs.existsSync(filePath);
53
+ });
54
+
55
+ if (mainFile) {
56
+ const mainFilePath = path.join(projectRoot, 'src', mainFile);
57
+ try {
58
+ const mainContent = fs.readFileSync(mainFilePath, 'utf8');
59
+
60
+ // Check for ErrorBoundary import from pace-core
61
+ // Matches: import { ErrorBoundary } from '@jmruthers/pace-core'
62
+ // Matches: import { ..., ErrorBoundary, ... } from '@jmruthers/pace-core'
63
+ // Matches: import { ErrorBoundary } from '@jmruthers/pace-core/components'
64
+ const hasErrorBoundaryImport = /import\s+[^'"]*ErrorBoundary[^'"]*from\s+['"]@jmruthers\/pace-core/.test(mainContent);
65
+
66
+ // Check for ErrorBoundary JSX usage
67
+ // Matches: <ErrorBoundary ...> or <ErrorBoundary>
68
+ const hasErrorBoundaryJSX = /<ErrorBoundary[\s>]/.test(mainContent);
69
+
70
+ // Check if ErrorBoundary wraps the render call or app component
71
+ // Pattern 1: createRoot(...).render(<ErrorBoundary ...)
72
+ // Pattern 2: render(<ErrorBoundary ...)
73
+ // Pattern 3: <ErrorBoundary ...> wrapping App component or createRoot call
74
+ const hasErrorBoundaryInRender = /createRoot\s*\([^)]*\)\s*\.\s*render\s*\(\s*<ErrorBoundary/.test(mainContent) ||
75
+ /\.render\s*\(\s*<ErrorBoundary/.test(mainContent) ||
76
+ /<ErrorBoundary[\s\S]{0,500}?<App[\s>]/.test(mainContent) ||
77
+ /<ErrorBoundary[\s\S]{0,500}?createRoot/.test(mainContent);
78
+
79
+ // ErrorBoundary is present if it's imported AND used in JSX
80
+ // We check both JSX usage and render wrapping to catch all valid patterns
81
+ if (hasErrorBoundaryImport && (hasErrorBoundaryJSX || hasErrorBoundaryInRender)) {
82
+ hasErrorBoundary = true;
83
+ }
84
+ } catch (error) {
85
+ // If we can't read the main file, continue with other checks
86
+ }
87
+ }
88
+ }
89
+
44
90
  for (const filePath of files) {
45
91
  try {
46
92
  const content = fs.readFileSync(filePath, 'utf8');
@@ -61,11 +107,6 @@ const errorHandlingCheck = {
61
107
  continue; // Skip script files
62
108
  }
63
109
 
64
- // Check for ErrorBoundary
65
- if (content.includes('ErrorBoundary') || content.includes('error-boundary')) {
66
- hasErrorBoundary = true;
67
- }
68
-
69
110
  // Check for unhandled promise rejections
70
111
  const asyncFunctionPattern = /(async\s+function|const\s+\w+\s*=\s*async|export\s+async\s+function)/g;
71
112
  let asyncMatch;
@@ -0,0 +1,384 @@
1
+ /**
2
+ * @file Context Selector Component
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/ContextSelector
5
+ * @since 0.6.4
6
+ *
7
+ * A unified intelligent selector component that shows all organisations and events
8
+ * a user can access based on their roles and permissions. This is the single
9
+ * selector for all apps - it intelligently determines what to show based on:
10
+ * - User's organisation roles
11
+ * - User's event-app roles
12
+ * - Super admin status (shows all)
13
+ *
14
+ * Features:
15
+ * - Intelligent display of accessible organisations and events
16
+ * - Grouped display (Organisations / Events sections)
17
+ * - Visual distinction between orgs and events
18
+ * - Super admin support (shows all orgs and events)
19
+ * - Loading states and error handling
20
+ * - Accessible interface design
21
+ * - Integration with OrganisationProvider and EventService
22
+ * - Automatically shows superset of what user can access
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <ContextSelector
27
+ * onOrganisationSelect={(org) => {
28
+ * switchOrganisation(org.id);
29
+ * }}
30
+ * onEventSelect={(event) => {
31
+ * setSelectedEvent(event);
32
+ * }}
33
+ * />
34
+ * ```
35
+ *
36
+ * @accessibility
37
+ * - WCAG 2.1 AA compliant
38
+ * - Keyboard navigation support
39
+ * - Screen reader friendly
40
+ * - Focus management
41
+ * - ARIA labels and descriptions
42
+ * - High contrast support
43
+ *
44
+ * @security
45
+ * - Only shows organisations/events user has access to
46
+ * - Super admins see all items
47
+ * - Validates access before selection
48
+ */
49
+
50
+ import React, { useMemo } from 'react';
51
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectGroup, SelectLabel, SelectSeparator } from '../Select';
52
+ import { Alert, AlertDescription } from '../Alert/Alert';
53
+ import { Button } from '../Button/Button';
54
+ import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner';
55
+ import { RefreshCw, AlertCircle, Building2, Calendar } from 'lucide-react';
56
+ import { useOrganisations } from '../../hooks/useOrganisations';
57
+ import { useEvents } from '../../hooks/useEvents';
58
+ import { useRBAC } from '../../rbac/hooks/useRBAC';
59
+ import type { Organisation } from '../../types/organisation';
60
+ import type { Event } from '../../types/event';
61
+ import { logger } from '../../utils/core/logger';
62
+
63
+ /**
64
+ * Props for the ContextSelector component.
65
+ */
66
+ export interface ContextSelectorProps {
67
+ /** Placeholder text for the dropdown */
68
+ placeholder?: string;
69
+ /** Additional CSS classes */
70
+ className?: string;
71
+ /** Callback fired when an organisation is selected */
72
+ onOrganisationSelect?: (org: Organisation) => void;
73
+ /** Callback fired when an event is selected */
74
+ onEventSelect?: (event: Event) => void;
75
+ /** Show friendly message when no items available */
76
+ showNoItemsMessage?: boolean;
77
+ /** Show retry button on errors */
78
+ showRetryButton?: boolean;
79
+ /** Compact display mode */
80
+ compact?: boolean;
81
+ /** Disabled state */
82
+ disabled?: boolean;
83
+ /** Show organisations section (default: true) */
84
+ showOrganisations?: boolean;
85
+ /** Show events section (default: true) */
86
+ showEvents?: boolean;
87
+ }
88
+
89
+ /**
90
+ * ContextSelector component for selecting organisations or events
91
+ *
92
+ * This is the unified intelligent selector that shows all organisations and events
93
+ * a user can access. It automatically determines what to show based on the user's
94
+ * roles and permissions - no need to configure separate org/event selectors.
95
+ *
96
+ * The selector intelligently shows:
97
+ * - All organisations the user has access to (via organisation roles)
98
+ * - All events the user has access to (via event-app roles or organisation membership)
99
+ * - Everything for super admins
100
+ *
101
+ * @component
102
+ * @example
103
+ * <ContextSelector
104
+ * onOrganisationSelect={(org) => switchOrganisation(org)}
105
+ * onEventSelect={(event) => setSelectedEvent(event)}
106
+ * />
107
+ */
108
+ export function ContextSelector({
109
+ placeholder = "Select organisation or event",
110
+ className,
111
+ onOrganisationSelect,
112
+ onEventSelect,
113
+ showNoItemsMessage = true,
114
+ showRetryButton = true,
115
+ compact = false,
116
+ disabled = false,
117
+ showOrganisations = true,
118
+ showEvents = true
119
+ }: ContextSelectorProps) {
120
+ const {
121
+ organisations,
122
+ selectedOrganisation,
123
+ isLoading: orgLoading,
124
+ error: orgError,
125
+ refreshOrganisations
126
+ } = useOrganisations();
127
+
128
+ const {
129
+ events,
130
+ selectedEvent,
131
+ isLoading: eventLoading,
132
+ error: eventError,
133
+ refreshEvents
134
+ } = useEvents();
135
+
136
+ const { isSuperAdmin } = useRBAC();
137
+
138
+ const isLoading = (showOrganisations && orgLoading) || (showEvents && eventLoading);
139
+ const hasError = (showOrganisations && orgError) || (showEvents && eventError);
140
+ const hasItems =
141
+ (showOrganisations && (organisations?.length || 0) > 0) ||
142
+ (showEvents && (events?.length || 0) > 0);
143
+
144
+ // Determine current selection value
145
+ // Priority: Event selection takes precedence over organisation selection
146
+ // When an event is selected, show the event (even if an org is also selected)
147
+ // The organisation remains in context (derived from event) but selector shows the active selection
148
+ const currentValue = useMemo(() => {
149
+ if (showEvents && selectedEvent) {
150
+ return `event:${selectedEvent.event_id || selectedEvent.id}`;
151
+ }
152
+ if (showOrganisations && selectedOrganisation) {
153
+ return `org:${selectedOrganisation.id}`;
154
+ }
155
+ return '';
156
+ }, [showOrganisations, showEvents, selectedOrganisation?.id, selectedEvent]);
157
+
158
+ const handleValueChange = (value: string) => {
159
+ if (disabled || isLoading) return;
160
+
161
+ if (value.startsWith('org:') && showOrganisations) {
162
+ const orgId = value.replace('org:', '');
163
+ const org = organisations?.find(o => o.id === orgId);
164
+ if (org && onOrganisationSelect) {
165
+ onOrganisationSelect(org);
166
+ }
167
+ } else if (value.startsWith('event:') && showEvents) {
168
+ const eventId = value.replace('event:', '');
169
+ const event = events?.find(e => (e.event_id || e.id) === eventId);
170
+ if (event && onEventSelect) {
171
+ onEventSelect(event);
172
+ }
173
+ }
174
+ };
175
+
176
+ const handleRetry = async () => {
177
+ try {
178
+ if (showOrganisations && orgError) {
179
+ await refreshOrganisations();
180
+ }
181
+ if (showEvents && eventError) {
182
+ await refreshEvents();
183
+ }
184
+ } catch (error) {
185
+ logger.error('ContextSelector', 'Failed to refresh:', error);
186
+ }
187
+ };
188
+
189
+ // Loading state - only show loading if we don't have any items yet
190
+ // This prevents the selector from being stuck in loading when data exists
191
+ if (isLoading && !hasItems) {
192
+ const loadingText = compact ? "Loading..." :
193
+ showOrganisations && showEvents ? "Loading organisations and events..." :
194
+ showOrganisations ? "Loading organisations..." :
195
+ "Loading events...";
196
+ return (
197
+ <div className={`flex items-center gap-2 ${className}`}>
198
+ <LoadingSpinner size="sm" />
199
+ <span className="text-sm text-muted-foreground">
200
+ {loadingText}
201
+ </span>
202
+ </div>
203
+ );
204
+ }
205
+
206
+ // Error state
207
+ if (hasError) {
208
+ const errorMessages = [];
209
+ if (showOrganisations && orgError) {
210
+ errorMessages.push(`Failed to load organisations: ${orgError.message}`);
211
+ }
212
+ if (showEvents && eventError) {
213
+ errorMessages.push(`Failed to load events: ${eventError.message}`);
214
+ }
215
+ return (
216
+ <div className={`space-y-2 ${className}`}>
217
+ <Alert variant="destructive">
218
+ <AlertCircle className="size-4" />
219
+ <AlertDescription>
220
+ {errorMessages.join(' ')}
221
+ </AlertDescription>
222
+ </Alert>
223
+ {showRetryButton && (
224
+ <Button
225
+ variant="outline"
226
+ size="sm"
227
+ onClick={handleRetry}
228
+ disabled={isLoading}
229
+ className="w-full"
230
+ >
231
+ <RefreshCw className={`size-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
232
+ Retry
233
+ </Button>
234
+ )}
235
+ </div>
236
+ );
237
+ }
238
+
239
+ // No items available
240
+ if (!hasItems) {
241
+ if (showNoItemsMessage) {
242
+ const noItemsText =
243
+ showOrganisations && showEvents ? "No organisations or events available. Please contact your administrator." :
244
+ showOrganisations ? "No organisations available. Please contact your administrator." :
245
+ "No events available. Please contact your administrator.";
246
+ return (
247
+ <div className={`space-y-2 ${className}`}>
248
+ <Alert>
249
+ <AlertCircle className="size-4" />
250
+ <AlertDescription>
251
+ {noItemsText}
252
+ </AlertDescription>
253
+ </Alert>
254
+ {showRetryButton && (
255
+ <Button
256
+ variant="outline"
257
+ size="sm"
258
+ onClick={handleRetry}
259
+ disabled={isLoading}
260
+ className="w-full"
261
+ >
262
+ <RefreshCw className={`size-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
263
+ Check Again
264
+ </Button>
265
+ )}
266
+ </div>
267
+ );
268
+ }
269
+ return null;
270
+ }
271
+
272
+ // Format display value
273
+ // Priority: Event selection takes precedence over organisation selection (matches currentValue)
274
+ const displayValue = useMemo(() => {
275
+ if (showEvents && selectedEvent) {
276
+ return (
277
+ <div className="flex items-center gap-2">
278
+ <Calendar className="size-4 flex-shrink-0" />
279
+ <span className="truncate">{selectedEvent.event_name}</span>
280
+ </div>
281
+ );
282
+ }
283
+ if (showOrganisations && selectedOrganisation) {
284
+ return (
285
+ <div className="flex items-center gap-2">
286
+ <Building2 className="size-4 flex-shrink-0" />
287
+ <span className="truncate">{selectedOrganisation.display_name}</span>
288
+ </div>
289
+ );
290
+ }
291
+ return null;
292
+ }, [showOrganisations, showEvents, selectedOrganisation, selectedEvent]);
293
+
294
+ // Determine placeholder text based on what's shown
295
+ const effectivePlaceholder = useMemo(() => {
296
+ if (placeholder !== "Select organisation or event") {
297
+ return placeholder;
298
+ }
299
+ if (showOrganisations && showEvents) {
300
+ return "Select organisation or event";
301
+ }
302
+ if (showOrganisations) {
303
+ return "Select organisation";
304
+ }
305
+ if (showEvents) {
306
+ return "Select event";
307
+ }
308
+ return placeholder;
309
+ }, [placeholder, showOrganisations, showEvents]);
310
+
311
+ return (
312
+ <div className={className} data-testid="context-selector">
313
+ <Select
314
+ value={currentValue}
315
+ onValueChange={handleValueChange}
316
+ disabled={disabled || isLoading}
317
+ >
318
+ <SelectTrigger
319
+ className="text-left"
320
+ variant="outline"
321
+ >
322
+ <SelectValue placeholder={effectivePlaceholder}>
323
+ {displayValue}
324
+ </SelectValue>
325
+ </SelectTrigger>
326
+ <SelectContent>
327
+ {/* Organisations Section */}
328
+ {showOrganisations && organisations && organisations.length > 0 && (
329
+ <>
330
+ <SelectGroup>
331
+ <SelectLabel>Organisations</SelectLabel>
332
+ {organisations.map((org) => (
333
+ <SelectItem
334
+ key={org.id}
335
+ value={`org:${org.id}`}
336
+ >
337
+ <div className="flex items-center gap-2">
338
+ <Building2 className="size-4" />
339
+ <div className="flex flex-col">
340
+ <span className="font-medium">{org.display_name}</span>
341
+ {!compact && org.description && (
342
+ <span className="text-xs text-muted-foreground truncate max-w-40">
343
+ {org.description}
344
+ </span>
345
+ )}
346
+ </div>
347
+ </div>
348
+ </SelectItem>
349
+ ))}
350
+ </SelectGroup>
351
+ {showEvents && events && events.length > 0 && <SelectSeparator />}
352
+ </>
353
+ )}
354
+
355
+ {/* Events Section */}
356
+ {showEvents && events && events.length > 0 && (
357
+ <SelectGroup>
358
+ <SelectLabel>Events</SelectLabel>
359
+ {events.map((event) => (
360
+ <SelectItem
361
+ key={event.event_id || event.id}
362
+ value={`event:${event.event_id || event.id}`}
363
+ >
364
+ <div className="flex items-center gap-2">
365
+ <Calendar className="size-4" />
366
+ <div className="flex flex-col">
367
+ <span className="font-medium">{event.event_name}</span>
368
+ {!compact && event.event_date && (
369
+ <span className="text-xs text-muted-foreground">
370
+ {new Date(event.event_date).toLocaleDateString()}
371
+ </span>
372
+ )}
373
+ </div>
374
+ </div>
375
+ </SelectItem>
376
+ ))}
377
+ </SelectGroup>
378
+ )}
379
+ </SelectContent>
380
+ </Select>
381
+ </div>
382
+ );
383
+ }
384
+
@@ -0,0 +1,3 @@
1
+ export { ContextSelector } from './ContextSelector';
2
+ export type { ContextSelectorProps } from './ContextSelector';
3
+
@@ -75,8 +75,7 @@ export interface RowProps {
75
75
  };
76
76
  }
77
77
 
78
- const RowComponent = React.memo(
79
- ({
78
+ const RowComponent = ({
80
79
  row,
81
80
  style,
82
81
  isEditing,
@@ -138,6 +137,23 @@ const RowComponent = React.memo(
138
137
  }
139
138
  }, [isEditing, onSaveEditing, onCancelEditing]);
140
139
 
140
+ // CRITICAL: All hooks must be called before any early returns to avoid React hooks violations
141
+ // Move useMemo hooks here so they're always called, regardless of early returns below
142
+ const indentSize = hierarchical?.indentSize || 24;
143
+ const indentation = useMemo(() => {
144
+ return isChild && hierarchical?.state ? calculateIndentation(hierarchicalRow, [], indentSize) : 0;
145
+ }, [isChild, hierarchical?.state, hierarchicalRow, indentSize]);
146
+
147
+ const rowClassName = useMemo(() => {
148
+ if (isHierarchical) {
149
+ const hierarchicalClass = isParent
150
+ ? hierarchical?.parentRowClassName || 'bg-main-50 hover:bg-main-100 font-medium'
151
+ : hierarchical?.childRowClassName || 'bg-sec-25 hover:bg-sec-50';
152
+ return cn(getTableRowClasses({ isSelected, isVirtualized: false }), hierarchicalClass);
153
+ }
154
+ return getTableRowClasses({ isSelected, isVirtualized: false });
155
+ }, [isHierarchical, isParent, isSelected, hierarchical]);
156
+
141
157
  if (row.getIsGrouped && row.getIsGrouped()) {
142
158
  const groupValue = row.getValue(grouping[0]);
143
159
  const subRowsCount = row.subRows?.length || 0;
@@ -250,21 +266,6 @@ const RowComponent = React.memo(
250
266
  );
251
267
  }
252
268
 
253
- const indentSize = hierarchical?.indentSize || 24;
254
- const indentation = useMemo(() => {
255
- return isChild && hierarchical?.state ? calculateIndentation(hierarchicalRow, [], indentSize) : 0;
256
- }, [isChild, hierarchical?.state, hierarchicalRow, indentSize]);
257
-
258
- const rowClassName = useMemo(() => {
259
- if (isHierarchical) {
260
- const hierarchicalClass = isParent
261
- ? hierarchical?.parentRowClassName || 'bg-main-50 hover:bg-main-100 font-medium'
262
- : hierarchical?.childRowClassName || 'bg-sec-25 hover:bg-sec-50';
263
- return cn(getTableRowClasses({ isSelected, isVirtualized: false }), hierarchicalClass);
264
- }
265
- return getTableRowClasses({ isSelected, isVirtualized: false });
266
- }, [isHierarchical, isParent, isSelected, hierarchical]);
267
-
268
269
  return (
269
270
  <tr
270
271
  ref={rowRef}
@@ -332,8 +333,7 @@ const RowComponent = React.memo(
332
333
  })}
333
334
  </tr>
334
335
  );
335
- }
336
- );
336
+ };
337
337
 
338
338
  RowComponent.displayName = 'RowComponent';
339
339
 
@@ -11,7 +11,7 @@
11
11
 
12
12
  import React, { useEffect, useRef } from 'react';
13
13
  import { type Table } from '@tanstack/react-table';
14
- import { useVirtualizer } from '@tanstack/react-virtual';
14
+ import { useVirtualizer, type VirtualItem } from '@tanstack/react-virtual';
15
15
  import { EmptyState } from './EmptyState';
16
16
  import { FilterRow } from './FilterRow';
17
17
  import { MemoizedRow } from './RowComponent';
@@ -194,7 +194,7 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
194
194
  }
195
195
 
196
196
  if (effectiveShouldVirtualize && virtualRows.length > 0) {
197
- return virtualRows.map((virtualRow) => {
197
+ return virtualRows.map((virtualRow: VirtualItem) => {
198
198
  const row = rows[virtualRow.index];
199
199
  if (!row) return null;
200
200