@jmruthers/pace-core 0.6.1 → 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 (549) hide show
  1. package/CHANGELOG.md +88 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +46 -87
  3. package/cursor-rules/01-standards-compliance.mdc +16 -47
  4. package/cursor-rules/02-project-structure.mdc +4 -4
  5. package/cursor-rules/03-solid-principles.mdc +45 -164
  6. package/cursor-rules/04-testing-standards.mdc +22 -69
  7. package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
  8. package/cursor-rules/06-code-quality.mdc +42 -125
  9. package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
  10. package/cursor-rules/08-markup-quality.mdc +452 -0
  11. package/cursor-rules/CHANGELOG.md +18 -0
  12. package/cursor-rules/README.md +2 -1
  13. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
  17. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
  18. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  19. package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
  20. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  21. package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
  22. package/dist/chunk-2T2IG7T7.js.map +1 -0
  23. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  24. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  25. package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
  26. package/dist/chunk-6Z7LTB3D.js.map +1 -0
  27. package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
  28. package/dist/chunk-CNCQDFLN.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
  31. package/dist/chunk-DWUBLJJM.js.map +1 -0
  32. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  33. package/dist/chunk-FFQEQTNW.js.map +1 -0
  34. package/dist/chunk-FMUCXFII.js +76 -0
  35. package/dist/chunk-FMUCXFII.js.map +1 -0
  36. package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
  37. package/dist/chunk-HFZBI76P.js.map +1 -0
  38. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  39. package/dist/chunk-L4OXEN46.js.map +1 -0
  40. package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
  41. package/dist/chunk-M43Y4SSO.js.map +1 -0
  42. package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
  43. package/dist/chunk-M7MPQISP.js.map +1 -0
  44. package/dist/chunk-PQBSKX33.js +7793 -0
  45. package/dist/chunk-PQBSKX33.js.map +1 -0
  46. package/dist/chunk-QRPVRXYT.js +226 -0
  47. package/dist/chunk-QRPVRXYT.js.map +1 -0
  48. package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
  49. package/dist/chunk-RWEBCB47.js.map +1 -0
  50. package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
  51. package/dist/chunk-YDQHOZNA.js.map +1 -0
  52. package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
  53. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  54. package/dist/components.d.ts +5 -5
  55. package/dist/components.js +18 -16
  56. package/dist/components.js.map +1 -1
  57. package/dist/contextValidator-3JNZKUTX.js +9 -0
  58. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  59. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  60. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  61. package/dist/hooks.d.ts +55 -122
  62. package/dist/hooks.js +10 -13
  63. package/dist/hooks.js.map +1 -1
  64. package/dist/index.d.ts +60 -13
  65. package/dist/index.js +30 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/providers.d.ts +21 -3
  68. package/dist/providers.js +4 -3
  69. package/dist/rbac/index.d.ts +210 -139
  70. package/dist/rbac/index.js +17 -13
  71. package/dist/styles/index.js +1 -1
  72. package/dist/theming/runtime.d.ts +1 -13
  73. package/dist/theming/runtime.js +2 -2
  74. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  75. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  76. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  77. package/dist/types.d.ts +2 -2
  78. package/dist/types.js +1 -1
  79. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
  80. package/dist/utils.d.ts +4 -5
  81. package/dist/utils.js +17 -19
  82. package/dist/utils.js.map +1 -1
  83. package/docs/api/README.md +21 -17
  84. package/docs/api/modules.md +4191 -2967
  85. package/docs/architecture/database-schema-requirements.md +161 -0
  86. package/docs/components/context-selector.md +126 -0
  87. package/docs/core-concepts/rbac-system.md +3 -3
  88. package/docs/documentation-index.md +2 -4
  89. package/docs/getting-started/cursor-rules.md +2 -1
  90. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  91. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  92. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  93. package/docs/migration/README.md +52 -6
  94. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  95. package/docs/migration/database-changes-december-2025.md +3 -3
  96. package/docs/pace-mint-fix-auto-selection.md +218 -0
  97. package/docs/pace-mint-rbac-setup.md +391 -0
  98. package/docs/rbac/event-based-apps.md +1 -1
  99. package/docs/rbac/getting-started.md +1 -1
  100. package/docs/rbac/quick-start.md +1 -1
  101. package/docs/rbac/secure-client-protection.md +330 -0
  102. package/docs/standards/README.md +1 -0
  103. package/package.json +4 -3
  104. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  105. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  106. package/scripts/audit/core/checks/bundle.cjs +142 -0
  107. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
  108. package/scripts/audit/core/checks/config.cjs +54 -0
  109. package/scripts/audit/core/checks/coverage.cjs +84 -0
  110. package/scripts/audit/core/checks/dependencies.cjs +985 -0
  111. package/scripts/audit/core/checks/documentation.cjs +268 -0
  112. package/scripts/audit/core/checks/environment.cjs +116 -0
  113. package/scripts/audit/core/checks/error-handling.cjs +340 -0
  114. package/scripts/audit/core/checks/forms.cjs +172 -0
  115. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  116. package/scripts/audit/core/checks/hooks.cjs +334 -0
  117. package/scripts/audit/core/checks/imports.cjs +244 -0
  118. package/scripts/audit/core/checks/performance.cjs +325 -0
  119. package/scripts/audit/core/checks/routes.cjs +117 -0
  120. package/scripts/audit/core/checks/state.cjs +130 -0
  121. package/scripts/audit/core/checks/structure.cjs +65 -0
  122. package/scripts/audit/core/checks/style.cjs +584 -0
  123. package/scripts/audit/core/checks/testing.cjs +122 -0
  124. package/scripts/audit/core/checks/typescript.cjs +61 -0
  125. package/scripts/audit/core/scanner.cjs +199 -0
  126. package/scripts/audit/core/utils.cjs +137 -0
  127. package/scripts/audit/index.cjs +223 -0
  128. package/scripts/audit/reporters/console.cjs +151 -0
  129. package/scripts/audit/reporters/json.cjs +54 -0
  130. package/scripts/audit/reporters/markdown.cjs +124 -0
  131. package/scripts/audit-consuming-app.cjs +61 -936
  132. package/scripts/build-docs/build-decision.js +240 -0
  133. package/scripts/build-docs/cache-utils.js +105 -0
  134. package/scripts/build-docs/content-normalization.js +150 -0
  135. package/scripts/build-docs/file-utils.js +105 -0
  136. package/scripts/build-docs/git-utils.js +86 -0
  137. package/scripts/build-docs/hash-utils.js +116 -0
  138. package/scripts/build-docs/typedoc-runner.js +220 -0
  139. package/scripts/build-docs-incremental.js +77 -913
  140. package/scripts/utils/command-runner.js +16 -11
  141. package/scripts/validate-formats.js +61 -56
  142. package/scripts/validate-master.js +74 -69
  143. package/scripts/validate-pre-publish.js +70 -65
  144. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  145. package/src/components/Alert/Alert.test.tsx +12 -18
  146. package/src/components/Alert/Alert.tsx +5 -7
  147. package/src/components/Avatar/Avatar.test.tsx +4 -4
  148. package/src/components/Badge/Badge.tsx +14 -0
  149. package/src/components/Button/Button.tsx +22 -0
  150. package/src/components/Calendar/Calendar.tsx +8 -2
  151. package/src/components/Card/Card.tsx +4 -0
  152. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  153. package/src/components/Checkbox/Checkbox.tsx +2 -2
  154. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  155. package/src/components/ContextSelector/index.ts +3 -0
  156. package/src/components/DataTable/DataTable.tsx +38 -4
  157. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  158. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  159. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  160. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  161. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  162. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  163. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  164. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  165. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  166. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  167. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  168. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  169. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  170. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  171. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  172. package/src/components/DataTable/components/EditFields.tsx +307 -0
  173. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  174. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  176. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  177. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  178. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  179. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  180. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  181. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  182. package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
  183. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  184. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  185. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  186. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  187. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  188. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  189. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  190. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  191. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  192. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  193. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  194. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  195. package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
  196. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  197. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  198. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  199. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  200. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  201. package/src/components/DataTable/styles.ts +6 -6
  202. package/src/components/DataTable/types.ts +6 -10
  203. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  204. package/src/components/DataTable/utils/debugTools.ts +18 -113
  205. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  206. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  207. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  208. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  209. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  210. package/src/components/Dialog/Dialog.tsx +31 -3
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  212. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  213. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  214. package/src/components/ErrorBoundary/index.ts +27 -2
  215. package/src/components/FileDisplay/FileDisplay.tsx +74 -28
  216. package/src/components/FileUpload/FileUpload.tsx +22 -2
  217. package/src/components/Footer/Footer.test.tsx +16 -16
  218. package/src/components/Footer/Footer.tsx +14 -11
  219. package/src/components/Form/Form.tsx +1 -0
  220. package/src/components/Header/Header.test.tsx +43 -73
  221. package/src/components/Header/Header.tsx +59 -49
  222. package/src/components/Input/Input.test.tsx +2 -2
  223. package/src/components/Input/Input.tsx +8 -4
  224. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  225. package/src/components/LoginForm/LoginForm.tsx +4 -0
  226. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  227. package/src/components/NavigationMenu/types.ts +56 -0
  228. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  229. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  230. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  231. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  232. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
  233. package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
  234. package/src/components/PaceAppLayout/README.md +14 -17
  235. package/src/components/PaceAppLayout/test-setup.tsx +3 -4
  236. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  237. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  238. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  239. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  240. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  241. package/src/components/Select/Select.tsx +80 -434
  242. package/src/components/Select/context.ts +23 -0
  243. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  244. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  245. package/src/components/Select/hooks/useSelectState.ts +104 -0
  246. package/src/components/Select/index.ts +9 -1
  247. package/src/components/Select/types.ts +123 -0
  248. package/src/components/Select/utils/text.ts +26 -0
  249. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  250. package/src/components/Switch/Switch.tsx +4 -4
  251. package/src/components/Tabs/Tabs.tsx +1 -1
  252. package/src/components/Toast/Toast.tsx +4 -0
  253. package/src/components/Tooltip/Tooltip.tsx +2 -2
  254. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  255. package/src/components/UserMenu/UserMenu.tsx +21 -18
  256. package/src/components/index.ts +7 -7
  257. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  258. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  259. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  260. package/src/hooks/index.ts +1 -2
  261. package/src/hooks/public/usePublicEvent.ts +4 -0
  262. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  263. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  264. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  265. package/src/hooks/services/useAuth.ts +32 -0
  266. package/src/hooks/services/useCurrentEvent.ts +6 -0
  267. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  268. package/src/hooks/useAppConfig.ts +15 -30
  269. package/src/hooks/useDebounce.ts +9 -0
  270. package/src/hooks/useEventTheme.ts +6 -0
  271. package/src/hooks/useFileDisplay.ts +81 -50
  272. package/src/hooks/useFileReference.ts +25 -7
  273. package/src/hooks/useFileUrl.ts +11 -1
  274. package/src/hooks/useFocusManagement.ts +14 -0
  275. package/src/hooks/useFocusTrap.ts +3 -0
  276. package/src/hooks/useInactivityTracker.ts +3 -0
  277. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  278. package/src/hooks/useOrganisationPermissions.ts +4 -0
  279. package/src/hooks/useOrganisationSecurity.ts +4 -0
  280. package/src/hooks/usePerformanceMonitor.ts +4 -0
  281. package/src/hooks/usePermissionCache.ts +7 -0
  282. package/src/hooks/useQueryCache.ts +12 -1
  283. package/src/hooks/useSessionRestoration.ts +4 -0
  284. package/src/hooks/useStorage.ts +4 -0
  285. package/src/hooks/useToast.ts +1 -1
  286. package/src/index.ts +6 -6
  287. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  288. package/src/providers/services/AuthServiceProvider.tsx +35 -7
  289. package/src/providers/services/EventServiceProvider.tsx +51 -5
  290. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  291. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  294. package/src/rbac/README.md +1 -1
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  296. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  297. package/src/rbac/adapters.tsx +12 -3
  298. package/src/rbac/api.test.ts +59 -51
  299. package/src/rbac/api.ts +246 -167
  300. package/src/rbac/components/NavigationProvider.tsx +4 -1
  301. package/src/rbac/components/PagePermissionGuard.tsx +185 -17
  302. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  303. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  304. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  305. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  306. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  307. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  308. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  309. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  310. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  311. package/src/rbac/engine.ts +38 -14
  312. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  313. package/src/rbac/hooks/permissions/index.ts +7 -0
  314. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  315. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  316. package/src/rbac/hooks/permissions/useCan.ts +377 -0
  317. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  318. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  319. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  320. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  321. package/src/rbac/hooks/useCan.test.ts +64 -66
  322. package/src/rbac/hooks/usePermissions.ts +14 -995
  323. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  324. package/src/rbac/hooks/useRBAC.ts +36 -37
  325. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  326. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  327. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  328. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  329. package/src/rbac/hooks/useSecureSupabase.ts +27 -7
  330. package/src/rbac/index.ts +7 -0
  331. package/src/rbac/permissions.ts +0 -30
  332. package/src/rbac/secureClient.test.ts +22 -18
  333. package/src/rbac/secureClient.ts +294 -68
  334. package/src/rbac/security.ts +0 -17
  335. package/src/rbac/types.ts +9 -0
  336. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  337. package/src/rbac/utils/clientSecurity.ts +93 -0
  338. package/src/rbac/utils/contextValidator.ts +77 -168
  339. package/src/services/AuthService.ts +39 -7
  340. package/src/services/EventService.ts +186 -54
  341. package/src/services/OrganisationService.ts +81 -14
  342. package/src/services/__tests__/EventService.test.ts +1 -2
  343. package/src/services/base/BaseService.ts +3 -0
  344. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  345. package/src/theming/parseEventColours.ts +5 -19
  346. package/src/types/vitest-globals.d.ts +51 -26
  347. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  348. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  349. package/src/utils/__tests__/index.unit.test.ts +2 -2
  350. package/src/utils/audit/audit.ts +0 -3
  351. package/src/utils/core/cn.ts +1 -1
  352. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  353. package/src/utils/file-reference/index.ts +53 -1
  354. package/src/utils/formatting/formatting.ts +8 -18
  355. package/src/utils/index.ts +0 -1
  356. package/dist/chunk-3QRJFVBR.js.map +0 -1
  357. package/dist/chunk-3XTALGJF.js.map +0 -1
  358. package/dist/chunk-4N5C5XZU.js.map +0 -1
  359. package/dist/chunk-4ZC4GX36.js.map +0 -1
  360. package/dist/chunk-7D4SUZUM.js +0 -38
  361. package/dist/chunk-BYFSK72L.js.map +0 -1
  362. package/dist/chunk-EXUD6RNJ.js +0 -451
  363. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  364. package/dist/chunk-GLK6VM3F.js.map +0 -1
  365. package/dist/chunk-I7PSE6JW.js.map +0 -1
  366. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  367. package/dist/chunk-KNC55RTG.js.map +0 -1
  368. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  369. package/dist/chunk-R77UEZ4E.js.map +0 -1
  370. package/dist/chunk-SQGMNID3.js.map +0 -1
  371. package/dist/chunk-T33XF5ZC.js +0 -12922
  372. package/dist/chunk-T33XF5ZC.js.map +0 -1
  373. package/dist/chunk-XM25TVIE.js.map +0 -1
  374. package/docs/api/classes/ColumnFactory.md +0 -243
  375. package/docs/api/classes/ErrorBoundary.md +0 -144
  376. package/docs/api/classes/InvalidScopeError.md +0 -73
  377. package/docs/api/classes/Logger.md +0 -178
  378. package/docs/api/classes/MissingUserContextError.md +0 -66
  379. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  380. package/docs/api/classes/PermissionDeniedError.md +0 -73
  381. package/docs/api/classes/RBACAuditManager.md +0 -297
  382. package/docs/api/classes/RBACCache.md +0 -322
  383. package/docs/api/classes/RBACEngine.md +0 -171
  384. package/docs/api/classes/RBACError.md +0 -76
  385. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  386. package/docs/api/classes/SecureSupabaseClient.md +0 -160
  387. package/docs/api/classes/StorageUtils.md +0 -328
  388. package/docs/api/enums/FileCategory.md +0 -184
  389. package/docs/api/enums/LogLevel.md +0 -54
  390. package/docs/api/enums/RBACErrorCode.md +0 -228
  391. package/docs/api/enums/RPCFunction.md +0 -118
  392. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  393. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  394. package/docs/api/interfaces/AggregateConfig.md +0 -43
  395. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  396. package/docs/api/interfaces/AvatarProps.md +0 -128
  397. package/docs/api/interfaces/BadgeProps.md +0 -27
  398. package/docs/api/interfaces/ButtonProps.md +0 -53
  399. package/docs/api/interfaces/CalendarProps.md +0 -70
  400. package/docs/api/interfaces/CardProps.md +0 -66
  401. package/docs/api/interfaces/ColorPalette.md +0 -7
  402. package/docs/api/interfaces/ColorShade.md +0 -66
  403. package/docs/api/interfaces/ComplianceResult.md +0 -30
  404. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  405. package/docs/api/interfaces/DataRecord.md +0 -11
  406. package/docs/api/interfaces/DataTableAction.md +0 -249
  407. package/docs/api/interfaces/DataTableColumn.md +0 -504
  408. package/docs/api/interfaces/DataTableProps.md +0 -625
  409. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  410. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  411. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  412. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  413. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  414. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  415. package/docs/api/interfaces/ExportColumn.md +0 -90
  416. package/docs/api/interfaces/ExportOptions.md +0 -126
  417. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  418. package/docs/api/interfaces/FileMetadata.md +0 -129
  419. package/docs/api/interfaces/FileReference.md +0 -118
  420. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  421. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  422. package/docs/api/interfaces/FileUploadProps.md +0 -293
  423. package/docs/api/interfaces/FooterProps.md +0 -105
  424. package/docs/api/interfaces/FormFieldProps.md +0 -166
  425. package/docs/api/interfaces/FormProps.md +0 -113
  426. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  427. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  428. package/docs/api/interfaces/InputProps.md +0 -53
  429. package/docs/api/interfaces/LabelProps.md +0 -107
  430. package/docs/api/interfaces/LoggerConfig.md +0 -62
  431. package/docs/api/interfaces/LoginFormProps.md +0 -184
  432. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  433. package/docs/api/interfaces/NavigationContextType.md +0 -164
  434. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  435. package/docs/api/interfaces/NavigationItem.md +0 -120
  436. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  437. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  438. package/docs/api/interfaces/Organisation.md +0 -140
  439. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  440. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  441. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  442. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  443. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
  444. package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
  445. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  446. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  447. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  448. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  449. package/docs/api/interfaces/PaletteData.md +0 -41
  450. package/docs/api/interfaces/ParsedAddress.md +0 -120
  451. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  452. package/docs/api/interfaces/ProgressProps.md +0 -42
  453. package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
  454. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  455. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  456. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
  457. package/docs/api/interfaces/QuickFix.md +0 -52
  458. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  459. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  460. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  461. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  462. package/docs/api/interfaces/RBACConfig.md +0 -133
  463. package/docs/api/interfaces/RBACContext.md +0 -52
  464. package/docs/api/interfaces/RBACLogger.md +0 -112
  465. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  466. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  467. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  468. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  469. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  470. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  471. package/docs/api/interfaces/RBACResult.md +0 -58
  472. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  473. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  474. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  475. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  476. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  477. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  478. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  479. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  480. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  481. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  482. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  483. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  484. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  485. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  486. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  487. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  488. package/docs/api/interfaces/RouteConfig.md +0 -134
  489. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  490. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  491. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  492. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  493. package/docs/api/interfaces/SetupIssue.md +0 -41
  494. package/docs/api/interfaces/StorageConfig.md +0 -41
  495. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  496. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  497. package/docs/api/interfaces/StorageListOptions.md +0 -99
  498. package/docs/api/interfaces/StorageListResult.md +0 -41
  499. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  500. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  501. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  502. package/docs/api/interfaces/StyleImport.md +0 -19
  503. package/docs/api/interfaces/SwitchProps.md +0 -34
  504. package/docs/api/interfaces/TabsContentProps.md +0 -9
  505. package/docs/api/interfaces/TabsListProps.md +0 -9
  506. package/docs/api/interfaces/TabsProps.md +0 -9
  507. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  508. package/docs/api/interfaces/TextareaProps.md +0 -53
  509. package/docs/api/interfaces/ToastActionElement.md +0 -9
  510. package/docs/api/interfaces/ToastProps.md +0 -9
  511. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
  512. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
  513. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  514. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  515. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
  516. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  517. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  518. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  519. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  520. package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
  521. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  522. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
  523. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
  524. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  525. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  526. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  527. package/docs/api/interfaces/UserEventAccess.md +0 -118
  528. package/docs/api/interfaces/UserMenuProps.md +0 -86
  529. package/docs/api/interfaces/UserProfile.md +0 -63
  530. package/docs/migration/quick-migration-guide.md +0 -356
  531. package/docs/migration/service-architecture.md +0 -281
  532. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  533. package/src/components/EventSelector/EventSelector.tsx +0 -420
  534. package/src/components/EventSelector/index.ts +0 -3
  535. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  536. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
  537. package/src/components/OrganisationSelector/index.ts +0 -9
  538. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  539. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  540. package/src/hooks/useSecureDataAccess.ts +0 -681
  541. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
  542. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  543. /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
  544. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  545. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  546. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  547. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  548. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  549. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Performance Check Module
5
+ * @package @jmruthers/pace-core
6
+ * @module Audit/Checks/Performance
7
+ *
8
+ * Checks for:
9
+ * - Missing React.memo on expensive components
10
+ * - Missing useMemo/useCallback for expensive computations
11
+ * - Unnecessary re-renders
12
+ * - Large inline objects/functions in JSX props
13
+ * - Missing key props in lists
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const { getRelativePath, getLineNumber } = require('../utils.cjs');
18
+
19
+ const performanceCheck = {
20
+ name: 'performance',
21
+ description: 'Performance anti-patterns (missing memoization, unnecessary re-renders)',
22
+ severity: 'warning',
23
+
24
+ async run(context) {
25
+ const { projectRoot, files } = context;
26
+ const issues = [];
27
+ const warnings = [];
28
+ const suggestions = [];
29
+
30
+ if (!files || files.length === 0) {
31
+ return { issues, warnings, suggestions };
32
+ }
33
+
34
+ for (const filePath of files) {
35
+ try {
36
+ // Only check React component files
37
+ if (!filePath.match(/\.(tsx|jsx)$/)) {
38
+ continue;
39
+ }
40
+
41
+ const content = fs.readFileSync(filePath, 'utf8');
42
+ const relativePath = getRelativePath(filePath, projectRoot);
43
+ const normalizedPath = relativePath.replace(/\\/g, '/');
44
+
45
+ // Skip root-level src directory - in pace-core repository, this is a demo/showcase app
46
+ // Note: We DO check packages/core/ files because performance issues (like missing key props) are real issues that should be fixed
47
+ const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
48
+ if (isRootSrc) {
49
+ continue; // Skip demo app files
50
+ }
51
+
52
+ // Skip scripts directory - utility scripts don't need performance validation
53
+ const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
54
+ if (isScript) {
55
+ continue; // Skip script files
56
+ }
57
+
58
+ // Skip examples directory - these are demo/example files, not production code
59
+ // Examples are meant to show usage patterns, not be optimized for performance
60
+ const isExample = normalizedPath.includes('/examples/') || normalizedPath.startsWith('examples/');
61
+ if (isExample) {
62
+ continue; // Skip example files
63
+ }
64
+
65
+ // Skip test files - test code doesn't need performance optimization
66
+ const isTestFile = normalizedPath.includes('.test.') ||
67
+ normalizedPath.includes('.spec.') ||
68
+ normalizedPath.includes('test-setup') ||
69
+ normalizedPath.includes('__tests__');
70
+ if (isTestFile) {
71
+ continue; // Skip test files
72
+ }
73
+
74
+ // For library code (packages/core/src), be more lenient with performance suggestions
75
+ // Library components often use inline functions which are acceptable patterns
76
+ const isLibraryCode = normalizedPath.includes('packages/core/src/');
77
+
78
+ // Check for components that could benefit from React.memo
79
+ // Note: For library components, React.memo should be used judiciously
80
+ // We only suggest it for components that are likely to render frequently
81
+ const componentPattern = /export\s+(default\s+)?(function|const)\s+(\w+)\s*[=\(]/g;
82
+ let match;
83
+ while ((match = componentPattern.exec(content)) !== null) {
84
+ const componentName = match[3];
85
+ const isMemoized = content.includes(`React.memo(${componentName})`) ||
86
+ content.includes(`memo(${componentName})`) ||
87
+ content.includes(`export default memo(${componentName})`);
88
+
89
+ // Check if component receives props and is not memoized
90
+ if (match[0].includes('props') || match[0].includes('{') || content.includes(`${componentName}({`)) {
91
+ // Check if component has significant JSX (heuristic: more than 5 lines of JSX)
92
+ const componentStart = match.index;
93
+ const afterMatch = content.substring(componentStart);
94
+ const jsxLines = (afterMatch.match(/<[A-Z]/g) || []).length;
95
+
96
+ // Skip if component uses forwardRef (memoization handled differently)
97
+ // Skip if component is a hook (starts with 'use')
98
+ // Skip if component is very simple (less than 5 JSX elements)
99
+ // Skip if component is already memoized
100
+ if (jsxLines > 5 && !isMemoized && !content.includes('forwardRef') && !componentName.startsWith('use')) {
101
+ // For library components, be more conservative - only suggest for components
102
+ // that are likely to be in lists or render frequently
103
+ // Skip if it's a page-level component (like *Page, *Layout, *Route)
104
+ const isPageComponent = /Page|Layout|Route|Modal|Dialog/.test(componentName);
105
+
106
+ // Only suggest memo for components that are likely to render frequently
107
+ // (not page-level components, which typically render once per route)
108
+ // For library code, skip React.memo suggestions entirely - memoization decisions
109
+ // should be made by the consuming application based on their specific use case
110
+ if (!isPageComponent && !isLibraryCode) {
111
+ suggestions.push({
112
+ type: 'missing-memo',
113
+ file: relativePath,
114
+ line: getLineNumber(content, match.index),
115
+ message: `Component '${componentName}' receives props but is not memoized`,
116
+ recommendation: `Consider wrapping with React.memo if the component renders frequently: export default React.memo(${componentName})`
117
+ });
118
+ }
119
+ // For library code, skip React.memo suggestions - library components are often
120
+ // controlled by parent components, and memoization should be decided by consumers
121
+ }
122
+ }
123
+ }
124
+
125
+ // Check for large inline objects/functions in JSX props
126
+ // But exclude JSX children (which is standard React pattern)
127
+ const jsxPropPattern = /<[A-Z]\w+\s+[^>]*\{[^}]{100,}[^>]*>/g;
128
+ let jsxMatch;
129
+ while ((jsxMatch = jsxPropPattern.exec(content)) !== null) {
130
+ const matchText = jsxMatch[0];
131
+
132
+ // Check if this is JSX children (element prop) or guard component props
133
+ // Patterns to exclude (these are valid React patterns):
134
+ // 1. <Route element={<Component />} /> - React Router pattern
135
+ // 2. <GuardComponent fallback={<Component />} /> - Guard component pattern
136
+ // 3. <Component>{children}</Component> - JSX children (but this won't match our pattern)
137
+ // 4. Any prop that contains JSX elements (starts with <)
138
+
139
+ const isRouteElement = /element\s*=\s*\{/.test(matchText);
140
+ const isGuardProp = /(fallback|loading|error|children)\s*=\s*\{/.test(matchText);
141
+ const hasJSXElement = /<[A-Z]\w+/.test(matchText);
142
+
143
+ // Also check if it's a className utility call (like cn()) - not a performance issue
144
+ const isUtilityCall = /cn\s*\(|clsx\s*\(|classnames\s*\(/.test(matchText);
145
+
146
+ // Only flag if it's an actual inline object/function, not JSX children
147
+ if (!isRouteElement && !isGuardProp && !hasJSXElement && !isUtilityCall) {
148
+ warnings.push({
149
+ type: 'inline-object-in-jsx',
150
+ file: relativePath,
151
+ line: getLineNumber(content, jsxMatch.index),
152
+ message: 'Large inline object or function in JSX props detected',
153
+ recommendation: 'Extract inline objects/functions to variables or useMemo/useCallback to prevent unnecessary re-renders. JSX children in props (like React Router element prop or guard component fallback prop) are standard React patterns and not performance issues.'
154
+ });
155
+ }
156
+ }
157
+
158
+ // Check for missing key props in lists
159
+ // Only flag if JSX is returned, not data arrays
160
+ const mapPattern = /\.map\s*\(\s*\([^)]+\)\s*=>/g;
161
+ while ((match = mapPattern.exec(content)) !== null) {
162
+ const afterMap = content.substring(match.index, match.index + 500);
163
+ // Check if JSX is returned without key
164
+ // Also check if it's actually returning JSX (not just data objects)
165
+ const returnsJSX = afterMap.includes('return') && afterMap.includes('<');
166
+
167
+ // Check for key in various formats: key=, key:, or inside React.cloneElement
168
+ const hasKey = afterMap.includes('key=') ||
169
+ /key\s*:/.test(afterMap) ||
170
+ /React\.cloneElement\s*\([^,]+,\s*\{[^}]*key\s*:/.test(afterMap);
171
+
172
+ // Check if it's a data transformation (returns object/array/primitive, not JSX)
173
+ // Pattern 1: Returns a simple value like .map(([key, _]) => key)
174
+ const returnsSimpleValue = /=>\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*;/.test(afterMap) && !afterMap.includes('<');
175
+ // Pattern 2: Returns a data object without JSX
176
+ const returnsDataObject = /return\s*\{[^<]*\}/.test(afterMap) && !afterMap.match(/return\s*\{[^<]*<[A-Z]/);
177
+ // Pattern 3: Returns array element access like .map((subRow) => subRow.original)
178
+ const returnsDataProperty = /=>\s*[a-zA-Z_$][a-zA-Z0-9_$]*\.[a-zA-Z_$][a-zA-Z0-9_$]*\s*[;}]/.test(afterMap) && !afterMap.includes('<');
179
+
180
+ // Skip pace-core files for this check - library code has complex patterns that are hard to detect accurately
181
+ const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
182
+
183
+ if (returnsJSX && !hasKey && !returnsDataObject && !returnsSimpleValue && !returnsDataProperty && !isPaceCorePackage) {
184
+ warnings.push({
185
+ type: 'missing-key',
186
+ file: relativePath,
187
+ line: getLineNumber(content, match.index),
188
+ message: 'Array map returns JSX without key prop',
189
+ recommendation: 'Add a unique key prop to list items: {items.map(item => <Item key={item.id} ... />)}. Data array transformations (not JSX) do not need keys.'
190
+ });
191
+ }
192
+ }
193
+
194
+ // Check for expensive computations that could use useMemo
195
+ const expensivePatterns = [
196
+ /\.filter\([^)]*\)\.map\(/g, // filter().map() chains
197
+ /\.sort\([^)]*\)\.map\(/g, // sort().map() chains
198
+ /\.reduce\([^)]*\)/g // reduce operations
199
+ ];
200
+
201
+ expensivePatterns.forEach(pattern => {
202
+ let expMatch;
203
+ while ((expMatch = pattern.exec(content)) !== null) {
204
+ // Check a larger context to see if it's already in useMemo or useCallback
205
+ const beforeMatch = content.substring(Math.max(0, expMatch.index - 500), expMatch.index);
206
+ const afterMatch = content.substring(expMatch.index, Math.min(content.length, expMatch.index + 200));
207
+
208
+ // Check if it's already in useMemo or useCallback
209
+ if (beforeMatch.includes('useMemo') || beforeMatch.includes('useCallback')) {
210
+ continue;
211
+ }
212
+
213
+ // Check if it's inside a function definition that's static (like aggregateFn, cell renderer, etc.)
214
+ // These are function definitions that are called by the library, not recalculated on every render
215
+ const isStaticFunctionDefinition = /(aggregateFn|cell|header|footer|render|format|transform)\s*[:=]\s*\(/.test(beforeMatch);
216
+ if (isStaticFunctionDefinition) {
217
+ continue; // Skip static function definitions
218
+ }
219
+
220
+ // Check if it's inside a const/let declaration that's likely already memoized
221
+ // Pattern: const x = useMemo(() => ... or const x = useCallback(() => ...
222
+ const isInMemoizedConst = /const\s+\w+\s*=\s*(useMemo|useCallback)\s*\(/.test(beforeMatch);
223
+ if (isInMemoizedConst) {
224
+ continue;
225
+ }
226
+
227
+ // Check if it's part of a column definition (static, not recalculated)
228
+ const isColumnDefinition = /(accessorKey|header|cell|aggregateFn|aggregateCell)\s*:/.test(beforeMatch);
229
+ if (isColumnDefinition) {
230
+ continue; // Skip column definitions - these are static
231
+ }
232
+
233
+ suggestions.push({
234
+ type: 'expensive-computation',
235
+ file: relativePath,
236
+ line: getLineNumber(content, expMatch.index),
237
+ message: 'Expensive computation detected that could benefit from useMemo',
238
+ recommendation: 'Wrap expensive computations in useMemo to prevent recalculation on every render'
239
+ });
240
+ }
241
+ });
242
+
243
+ // Check for functions passed as props that could use useCallback
244
+ // Only flag complex inline functions, not simple event handlers
245
+ // For library code, be very lenient - inline functions are often acceptable patterns
246
+ if (!isLibraryCode) {
247
+ // Only check for consuming applications, not library code
248
+ const functionPropPattern = /(onClick|onChange|onSubmit|onFocus|onBlur|onMouseEnter|onMouseLeave|onValueChange)\s*=\s*\{[^}]*=>/g;
249
+ let funcMatch;
250
+ while ((funcMatch = functionPropPattern.exec(content)) !== null) {
251
+ const beforeMatch = content.substring(Math.max(0, funcMatch.index - 150), funcMatch.index);
252
+ const afterMatch = content.substring(funcMatch.index, Math.min(content.length, funcMatch.index + 300));
253
+
254
+ // Skip if already using useCallback or defined as const
255
+ if (beforeMatch.includes('useCallback') || beforeMatch.includes('const ')) {
256
+ continue;
257
+ }
258
+
259
+ // Extract the function body to check complexity
260
+ // Handle both arrow functions with and without braces
261
+ let funcBody = '';
262
+ const arrowMatch = afterMatch.match(/=>\s*(\{?)([^}]*?)(\}?)/);
263
+ if (arrowMatch) {
264
+ if (arrowMatch[1] === '{') {
265
+ // Multi-line function with braces - extract content between braces
266
+ const braceMatch = afterMatch.match(/=>\s*\{([^}]+)\}/);
267
+ if (braceMatch) {
268
+ funcBody = braceMatch[1];
269
+ }
270
+ } else {
271
+ // Single expression arrow function
272
+ funcBody = arrowMatch[2];
273
+ }
274
+ }
275
+
276
+ if (funcBody) {
277
+ const trimmedBody = funcBody.trim();
278
+
279
+ // Very short handlers are usually simple
280
+ if (trimmedBody.length < 50) {
281
+ continue;
282
+ }
283
+
284
+ // Check for simple setState patterns (even with object spread)
285
+ const isSimpleSetState = /^\s*set\w+\s*\([^)]*\)\s*;?\s*$/.test(trimmedBody) ||
286
+ /^\s*set\w+\s*\([^)]*prev\s*=>\s*\(\{[^}]*\}\)[^}]*\)\s*;?\s*$/.test(trimmedBody);
287
+
288
+ // Check for simple function calls
289
+ const isSimpleFunctionCall = /^\s*(handle\w+|on\w+|navigate|window\.|document\.)\s*\([^)]*\)\s*;?\s*$/.test(trimmedBody);
290
+
291
+ // Check for simple conditional (single if/ternary)
292
+ const isSimpleConditional = /^\s*(if\s*\([^)]+\)\s*\{[^}]{0,50}\}|[^?]+\?[^:]+:[^;]+;?)\s*$/.test(trimmedBody);
293
+
294
+ // Check for simple value transformations (common in library components)
295
+ // Pattern: (e) => handleFunc(e.target.value || undefined)
296
+ // Pattern: (e) => handleFunc(e.target.value ? transform(e.target.value) : undefined)
297
+ const isSimpleValueTransform = /^\s*\w+\s*\([^)]*\.(target|value|checked|selected)[^)]*\)\s*;?\s*$/.test(trimmedBody) ||
298
+ /^\s*\w+\s*\([^)]*\?[^:]+:[^;]+\)\s*;?\s*$/.test(trimmedBody);
299
+
300
+ // Skip simple handlers - they're fine as inline functions
301
+ if (isSimpleSetState || isSimpleFunctionCall || isSimpleConditional || isSimpleValueTransform) {
302
+ continue;
303
+ }
304
+ }
305
+
306
+ suggestions.push({
307
+ type: 'inline-function-prop',
308
+ file: relativePath,
309
+ line: getLineNumber(content, funcMatch.index),
310
+ message: 'Complex inline function passed as prop',
311
+ recommendation: 'Use useCallback to memoize complex functions passed as props to prevent child re-renders. Simple event handlers (single setState call or function invocation) are fine as inline functions.'
312
+ });
313
+ }
314
+ }
315
+ // For library code, skip this check entirely - inline functions are acceptable patterns
316
+ } catch (error) {
317
+ // Skip files with errors
318
+ }
319
+ }
320
+
321
+ return { issues, warnings, suggestions };
322
+ }
323
+ };
324
+
325
+ module.exports = performanceCheck;
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Route Protection Check Module
5
+ * @package @jmruthers/pace-core
6
+ * @module Audit/Checks/Routes
7
+ *
8
+ * Checks for:
9
+ * - Routes without PagePermissionGuard
10
+ * - Incorrect route nesting
11
+ * - Missing route error boundaries
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const { getRelativePath, getLineNumber } = require('../utils.cjs');
16
+
17
+ const routesCheck = {
18
+ name: 'routes',
19
+ description: 'Route protection (PagePermissionGuard, route nesting)',
20
+ severity: 'error',
21
+
22
+ async run(context) {
23
+ const { projectRoot, files } = context;
24
+ const issues = [];
25
+ const warnings = [];
26
+ const suggestions = [];
27
+
28
+ if (!files || files.length === 0) {
29
+ return { issues, warnings, suggestions };
30
+ }
31
+
32
+ for (const filePath of files) {
33
+ try {
34
+ const content = fs.readFileSync(filePath, 'utf8');
35
+ const relativePath = getRelativePath(filePath, projectRoot);
36
+ const normalizedPath = relativePath.replace(/\\/g, '/');
37
+
38
+ // Skip pace-core package files - routes check is for consuming applications, not the library itself
39
+ // Note: Library components (like ProtectedRoute, PaceAppLayout) ARE the route protection components
40
+ // They don't need to use PagePermissionGuard - they provide the protection functionality
41
+ const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
42
+ if (isPaceCorePackage) {
43
+ continue; // Skip library files (including examples)
44
+ }
45
+
46
+ // Skip root-level src directory - in pace-core repository, this is a demo/showcase app
47
+ const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
48
+ if (isRootSrc) {
49
+ continue; // Skip demo app files
50
+ }
51
+
52
+ // Skip scripts directory - utility scripts don't need route protection validation
53
+ const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
54
+ if (isScript) {
55
+ continue; // Skip script files
56
+ }
57
+
58
+ // Check for route definitions
59
+ const routePatterns = [
60
+ /<Route\s+path=["'][^"']+["']/g,
61
+ /<Route\s+element\s*=/g,
62
+ /createBrowserRouter\s*\(/g,
63
+ /createRoutesFromElements/g
64
+ ];
65
+
66
+ const hasRoutes = routePatterns.some(pattern => pattern.test(content));
67
+
68
+ if (hasRoutes) {
69
+ // Check for PagePermissionGuard
70
+ const hasPagePermissionGuard = content.includes('PagePermissionGuard') ||
71
+ content.includes('from \'@jmruthers/pace-core/rbac\'');
72
+
73
+ if (!hasPagePermissionGuard && !relativePath.includes('test') && !relativePath.includes('spec')) {
74
+ issues.push({
75
+ type: 'unprotected-route',
76
+ file: relativePath,
77
+ message: 'Route file found without PagePermissionGuard',
78
+ recommendation: 'Wrap routes with PagePermissionGuard from @jmruthers/pace-core/rbac to enforce permissions'
79
+ });
80
+ }
81
+
82
+ // Check for route nesting issues
83
+ const routeCount = (content.match(/<Route/g) || []).length;
84
+ if (routeCount > 0) {
85
+ // Check if routes are properly nested
86
+ const hasRoutesWrapper = content.includes('<Routes') || content.includes('<BrowserRouter');
87
+ if (!hasRoutesWrapper) {
88
+ warnings.push({
89
+ type: 'route-nesting',
90
+ file: relativePath,
91
+ message: 'Route elements found but may not be wrapped in <Routes>',
92
+ recommendation: 'Ensure all Route elements are wrapped in <Routes> component'
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ // Check for error boundaries around routes
99
+ if (hasRoutes && !content.includes('ErrorBoundary') && !content.includes('error-boundary')) {
100
+ suggestions.push({
101
+ type: 'missing-route-error-boundary',
102
+ file: relativePath,
103
+ message: 'Routes without error boundary',
104
+ recommendation: 'Wrap routes with ErrorBoundary to catch and handle route errors gracefully'
105
+ });
106
+ }
107
+
108
+ } catch (error) {
109
+ // Skip files with errors
110
+ }
111
+ }
112
+
113
+ return { issues, warnings, suggestions };
114
+ }
115
+ };
116
+
117
+ module.exports = routesCheck;
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * State Management Check Module
5
+ * @package @jmruthers/pace-core
6
+ * @module Audit/Checks/State
7
+ *
8
+ * Checks for:
9
+ * - Improper state lifting
10
+ * - Prop drilling (too many levels)
11
+ * - Missing state normalization
12
+ * - Unnecessary global state
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const { getRelativePath, getLineNumber } = require('../utils.cjs');
17
+
18
+ const stateCheck = {
19
+ name: 'state',
20
+ description: 'State management patterns (prop drilling, state lifting)',
21
+ severity: 'suggestion',
22
+
23
+ async run(context) {
24
+ const { projectRoot, files } = context;
25
+ const issues = [];
26
+ const warnings = [];
27
+ const suggestions = [];
28
+
29
+ if (!files || files.length === 0) {
30
+ return { issues, warnings, suggestions };
31
+ }
32
+
33
+ for (const filePath of files) {
34
+ try {
35
+ // Only check React component files
36
+ if (!filePath.match(/\.(tsx|jsx)$/)) {
37
+ continue;
38
+ }
39
+
40
+ const content = fs.readFileSync(filePath, 'utf8');
41
+ const relativePath = getRelativePath(filePath, projectRoot);
42
+ const normalizedPath = relativePath.replace(/\\/g, '/');
43
+
44
+ // Skip pace-core package files - state check is for consuming applications, not the library itself
45
+ // Note: Library components are designed to have many props for configurability - this is not prop drilling
46
+ const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
47
+ if (isPaceCorePackage) {
48
+ continue; // Skip library files
49
+ }
50
+
51
+ // Skip root-level src directory - in pace-core repository, this is a demo/showcase app
52
+ const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
53
+ if (isRootSrc) {
54
+ continue; // Skip demo app files
55
+ }
56
+
57
+ // Skip scripts directory - utility scripts don't need state management validation
58
+ const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
59
+ if (isScript) {
60
+ continue; // Skip script files
61
+ }
62
+
63
+ // Check for prop drilling (component with many props that are just passed through)
64
+ const componentPattern = /(?:function|const)\s+(\w+)\s*[=\(]\s*\([^)]*\)/g;
65
+ let match;
66
+ while ((match = componentPattern.exec(content)) !== null) {
67
+ const propsMatch = match[0].match(/\(([^)]+)\)/);
68
+ if (propsMatch) {
69
+ const props = propsMatch[1].split(',').map(p => p.trim()).filter(p => p);
70
+
71
+ // Check if component has many props (potential prop drilling)
72
+ if (props.length > 8) {
73
+ suggestions.push({
74
+ type: 'many-props',
75
+ file: relativePath,
76
+ line: getLineNumber(content, match.index),
77
+ message: `Component has ${props.length} props - may indicate prop drilling`,
78
+ recommendation: 'Consider using context or state management library if props are passed through multiple levels'
79
+ });
80
+ }
81
+
82
+ // Check if props are just passed through to children
83
+ const componentBody = content.substring(match.index, Math.min(content.length, match.index + 1000));
84
+ const passThroughProps = props.filter(prop => {
85
+ const propName = prop.split(':')[0].trim();
86
+ return new RegExp(`<\\w+\\s+[^>]*${propName}=`, 'g').test(componentBody);
87
+ });
88
+
89
+ if (passThroughProps.length > props.length * 0.7 && props.length > 3) {
90
+ suggestions.push({
91
+ type: 'prop-drilling',
92
+ file: relativePath,
93
+ line: getLineNumber(content, match.index),
94
+ message: 'Component appears to be passing most props through to children',
95
+ recommendation: 'Consider lifting state up or using React Context to avoid prop drilling'
96
+ });
97
+ }
98
+ }
99
+ }
100
+
101
+ // Check for unnecessary useState when data comes from props
102
+ const useStatePattern = /const\s+\[(\w+),\s*set\w+\]\s*=\s*useState/g;
103
+ let stateMatch;
104
+ while ((stateMatch = useStatePattern.exec(content)) !== null) {
105
+ const stateVar = stateMatch[1];
106
+ const beforeState = content.substring(Math.max(0, stateMatch.index - 200), stateMatch.index);
107
+
108
+ // Check if there's a prop with the same name
109
+ const propPattern = new RegExp(`(?:props\\.|\\{\\s*${stateVar}\\s*\\})`, 'g');
110
+ if (propPattern.test(beforeState)) {
111
+ suggestions.push({
112
+ type: 'unnecessary-state',
113
+ file: relativePath,
114
+ line: getLineNumber(content, stateMatch.index),
115
+ message: `State variable '${stateVar}' may duplicate a prop`,
116
+ recommendation: 'If state comes from props, consider using the prop directly or use derived state pattern'
117
+ });
118
+ }
119
+ }
120
+
121
+ } catch (error) {
122
+ // Skip files with errors
123
+ }
124
+ }
125
+
126
+ return { issues, warnings, suggestions };
127
+ }
128
+ };
129
+
130
+ module.exports = stateCheck;
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * File Structure Check Module
5
+ * @package @jmruthers/pace-core
6
+ * @module Audit/Checks/Structure
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ const structureCheck = {
13
+ name: 'structure',
14
+ description: 'File structure checks (required/recommended directories)',
15
+ severity: 'error',
16
+
17
+ async run(context) {
18
+ const { projectRoot } = context;
19
+ const issues = [];
20
+ const warnings = [];
21
+
22
+ // Skip structure checks if this is the pace-core repository itself
23
+ // Structure checks are for consuming applications, not the library repository
24
+ // Detect pace-core repository by checking if packages/core exists
25
+ const packagesCorePath = path.join(projectRoot, 'packages', 'core');
26
+ const isPaceCoreRepository = fs.existsSync(packagesCorePath);
27
+
28
+ if (isPaceCoreRepository) {
29
+ // This is the pace-core library repository, not a consuming app
30
+ // Skip structure checks - the library has its own structure
31
+ return { issues: [], warnings: [], suggestions: [] };
32
+ }
33
+
34
+ const requiredDirs = ['src'];
35
+ const recommendedDirs = ['src/components', 'src/hooks', 'src/utils', 'src/pages'];
36
+
37
+ requiredDirs.forEach(dir => {
38
+ const dirPath = path.join(projectRoot, dir);
39
+ if (!fs.existsSync(dirPath)) {
40
+ issues.push({
41
+ type: 'missing-directory',
42
+ file: dir,
43
+ message: `Required directory '${dir}' is missing`,
44
+ recommendation: `Create the '${dir}' directory`
45
+ });
46
+ }
47
+ });
48
+
49
+ recommendedDirs.forEach(dir => {
50
+ const dirPath = path.join(projectRoot, dir);
51
+ if (!fs.existsSync(dirPath)) {
52
+ warnings.push({
53
+ type: 'missing-directory',
54
+ file: dir,
55
+ message: `Recommended directory '${dir}' is missing`,
56
+ recommendation: `Consider creating the '${dir}' directory for better organization`
57
+ });
58
+ }
59
+ });
60
+
61
+ return { issues, warnings, suggestions: [] };
62
+ }
63
+ };
64
+
65
+ module.exports = structureCheck;