@jmruthers/pace-core 0.5.191 → 0.6.1

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 (380) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +372 -0
  4. package/cursor-rules/01-standards-compliance.mdc +275 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +341 -0
  7. package/cursor-rules/04-testing-standards.mdc +315 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +392 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
  11. package/cursor-rules/CHANGELOG.md +101 -0
  12. package/cursor-rules/README.md +191 -0
  13. package/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
  14. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
  15. package/dist/{DataTable-WKRZD47S.js → DataTable-DQ7RSOHE.js} +8 -7
  16. package/dist/{PublicPageProvider-ULXC_u6U.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +37 -156
  17. package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
  18. package/dist/{UnifiedAuthProvider-FTSG5XH7.js → UnifiedAuthProvider-ATAP5UTR.js} +3 -3
  19. package/dist/{api-IHKALJZD.js → api-N774RPUA.js} +2 -2
  20. package/dist/{chunk-6C4YBBJM.js → chunk-3QRJFVBR.js} +1 -1
  21. package/dist/chunk-3QRJFVBR.js.map +1 -0
  22. package/dist/{chunk-OETXORNB.js → chunk-3XTALGJF.js} +211 -136
  23. package/dist/chunk-3XTALGJF.js.map +1 -0
  24. package/dist/{chunk-6TQDD426.js → chunk-4N5C5XZU.js} +47 -228
  25. package/dist/chunk-4N5C5XZU.js.map +1 -0
  26. package/dist/{chunk-LOMZXPSN.js → chunk-4ZC4GX36.js} +47 -74
  27. package/dist/chunk-4ZC4GX36.js.map +1 -0
  28. package/dist/{chunk-6LTQQAT6.js → chunk-BYFSK72L.js} +357 -158
  29. package/dist/chunk-BYFSK72L.js.map +1 -0
  30. package/dist/{chunk-XYXSXPUK.js → chunk-EXUD6RNJ.js} +50 -10
  31. package/dist/chunk-EXUD6RNJ.js.map +1 -0
  32. package/dist/{chunk-VKB2CO4Z.js → chunk-GLK6VM3F.js} +244 -249
  33. package/dist/chunk-GLK6VM3F.js.map +1 -0
  34. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  35. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  36. package/dist/{chunk-XNYQOL3Z.js → chunk-JBKQ3SAO.js} +9 -18
  37. package/dist/chunk-JBKQ3SAO.js.map +1 -0
  38. package/dist/{chunk-ROXMHMY2.js → chunk-KNC55RTG.js} +13 -3
  39. package/dist/{chunk-ROXMHMY2.js.map → chunk-KNC55RTG.js.map} +1 -1
  40. package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
  41. package/dist/{chunk-ULHIJK66.js → chunk-T33XF5ZC.js} +255 -140
  42. package/dist/chunk-T33XF5ZC.js.map +1 -0
  43. package/dist/{chunk-VRGWKHDB.js → chunk-XM25TVIE.js} +100 -33
  44. package/dist/chunk-XM25TVIE.js.map +1 -0
  45. package/dist/components.d.ts +4 -4
  46. package/dist/components.js +9 -9
  47. package/dist/hooks.d.ts +6 -6
  48. package/dist/hooks.js +20 -25
  49. package/dist/hooks.js.map +1 -1
  50. package/dist/index.d.ts +11 -11
  51. package/dist/index.js +18 -21
  52. package/dist/index.js.map +1 -1
  53. package/dist/providers.d.ts +3 -3
  54. package/dist/providers.js +2 -2
  55. package/dist/rbac/index.d.ts +2 -20
  56. package/dist/rbac/index.js +7 -9
  57. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
  58. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  59. package/dist/utils.d.ts +1 -1
  60. package/dist/utils.js +3 -3
  61. package/docs/api/classes/ColumnFactory.md +1 -1
  62. package/docs/api/classes/ErrorBoundary.md +1 -1
  63. package/docs/api/classes/InvalidScopeError.md +1 -1
  64. package/docs/api/classes/Logger.md +1 -1
  65. package/docs/api/classes/MissingUserContextError.md +1 -1
  66. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  67. package/docs/api/classes/PermissionDeniedError.md +2 -2
  68. package/docs/api/classes/RBACAuditManager.md +2 -2
  69. package/docs/api/classes/RBACCache.md +1 -1
  70. package/docs/api/classes/RBACEngine.md +2 -2
  71. package/docs/api/classes/RBACError.md +1 -1
  72. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  73. package/docs/api/classes/SecureSupabaseClient.md +10 -10
  74. package/docs/api/classes/StorageUtils.md +1 -1
  75. package/docs/api/enums/FileCategory.md +1 -1
  76. package/docs/api/enums/LogLevel.md +1 -1
  77. package/docs/api/enums/RBACErrorCode.md +1 -1
  78. package/docs/api/enums/RPCFunction.md +1 -1
  79. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  80. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +1 -1
  82. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  83. package/docs/api/interfaces/AvatarProps.md +1 -1
  84. package/docs/api/interfaces/BadgeProps.md +1 -1
  85. package/docs/api/interfaces/ButtonProps.md +1 -1
  86. package/docs/api/interfaces/CalendarProps.md +1 -1
  87. package/docs/api/interfaces/CardProps.md +1 -1
  88. package/docs/api/interfaces/ColorPalette.md +1 -1
  89. package/docs/api/interfaces/ColorShade.md +1 -1
  90. package/docs/api/interfaces/ComplianceResult.md +1 -1
  91. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  92. package/docs/api/interfaces/DataRecord.md +1 -1
  93. package/docs/api/interfaces/DataTableAction.md +1 -1
  94. package/docs/api/interfaces/DataTableColumn.md +1 -1
  95. package/docs/api/interfaces/DataTableProps.md +1 -1
  96. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  97. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  98. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  99. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  100. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  101. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  102. package/docs/api/interfaces/ExportColumn.md +1 -1
  103. package/docs/api/interfaces/ExportOptions.md +1 -1
  104. package/docs/api/interfaces/FileDisplayProps.md +24 -11
  105. package/docs/api/interfaces/FileMetadata.md +1 -1
  106. package/docs/api/interfaces/FileReference.md +1 -1
  107. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  108. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  109. package/docs/api/interfaces/FileUploadProps.md +1 -1
  110. package/docs/api/interfaces/FooterProps.md +1 -1
  111. package/docs/api/interfaces/FormFieldProps.md +1 -1
  112. package/docs/api/interfaces/FormProps.md +1 -1
  113. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  114. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  115. package/docs/api/interfaces/InputProps.md +1 -1
  116. package/docs/api/interfaces/LabelProps.md +1 -1
  117. package/docs/api/interfaces/LoggerConfig.md +1 -1
  118. package/docs/api/interfaces/LoginFormProps.md +1 -1
  119. package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
  120. package/docs/api/interfaces/NavigationContextType.md +1 -1
  121. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  122. package/docs/api/interfaces/NavigationItem.md +1 -1
  123. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  124. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  125. package/docs/api/interfaces/Organisation.md +1 -1
  126. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  127. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  128. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  129. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  130. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  131. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  132. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  133. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  134. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  135. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  136. package/docs/api/interfaces/PaletteData.md +1 -1
  137. package/docs/api/interfaces/ParsedAddress.md +1 -1
  138. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  139. package/docs/api/interfaces/ProgressProps.md +1 -1
  140. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  141. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  142. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  143. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  144. package/docs/api/interfaces/QuickFix.md +1 -1
  145. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  146. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  147. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  148. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  149. package/docs/api/interfaces/RBACConfig.md +2 -2
  150. package/docs/api/interfaces/RBACContext.md +1 -1
  151. package/docs/api/interfaces/RBACLogger.md +1 -1
  152. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  153. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  154. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  155. package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
  156. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  157. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  158. package/docs/api/interfaces/RBACResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
  160. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  161. package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
  162. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  163. package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
  164. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  165. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  166. package/docs/api/interfaces/RBACRolesListResult.md +2 -2
  167. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  168. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  169. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  170. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  171. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  172. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  173. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  174. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  175. package/docs/api/interfaces/RouteConfig.md +2 -2
  176. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  177. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  178. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  179. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  180. package/docs/api/interfaces/SetupIssue.md +1 -1
  181. package/docs/api/interfaces/StorageConfig.md +1 -1
  182. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  183. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  184. package/docs/api/interfaces/StorageListOptions.md +1 -1
  185. package/docs/api/interfaces/StorageListResult.md +1 -1
  186. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  187. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  188. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  189. package/docs/api/interfaces/StyleImport.md +1 -1
  190. package/docs/api/interfaces/SwitchProps.md +1 -1
  191. package/docs/api/interfaces/TabsContentProps.md +1 -1
  192. package/docs/api/interfaces/TabsListProps.md +1 -1
  193. package/docs/api/interfaces/TabsProps.md +1 -1
  194. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  195. package/docs/api/interfaces/TextareaProps.md +1 -1
  196. package/docs/api/interfaces/ToastActionElement.md +1 -1
  197. package/docs/api/interfaces/ToastProps.md +1 -1
  198. package/docs/api/interfaces/UnifiedAuthContextType.md +60 -38
  199. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  200. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  201. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  202. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  203. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  205. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  207. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  208. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  209. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  210. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  211. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  212. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  213. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  214. package/docs/api/interfaces/UserEventAccess.md +1 -1
  215. package/docs/api/interfaces/UserMenuProps.md +1 -1
  216. package/docs/api/interfaces/UserProfile.md +1 -1
  217. package/docs/api/modules.md +194 -209
  218. package/docs/getting-started/cursor-rules.md +262 -0
  219. package/docs/getting-started/installation-guide.md +6 -1
  220. package/docs/getting-started/quick-start.md +6 -1
  221. package/docs/migration/MIGRATION_GUIDE.md +4 -4
  222. package/docs/migration/REACT_19_MIGRATION.md +227 -0
  223. package/docs/migration/database-changes-december-2025.md +2 -1
  224. package/docs/rbac/event-based-apps.md +124 -6
  225. package/docs/standards/README.md +39 -0
  226. package/docs/troubleshooting/migration.md +4 -4
  227. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  228. package/package.json +11 -6
  229. package/scripts/audit-consuming-app.cjs +961 -0
  230. package/scripts/check-pace-core-compliance.cjs +315 -61
  231. package/scripts/install-cursor-rules.cjs +236 -0
  232. package/src/__tests__/helpers/test-providers.tsx +1 -1
  233. package/src/__tests__/helpers/test-utils.tsx +1 -1
  234. package/src/__tests__/rls-policies.test.ts +3 -1
  235. package/src/components/Badge/Badge.tsx +2 -4
  236. package/src/components/Button/Button.tsx +5 -4
  237. package/src/components/Calendar/Calendar.tsx +1 -1
  238. package/src/components/DataTable/DataTable.test.tsx +57 -93
  239. package/src/components/DataTable/DataTable.tsx +2 -2
  240. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
  241. package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
  242. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
  243. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
  244. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
  245. package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
  246. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +88 -16
  247. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  248. package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
  249. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  250. package/src/components/DataTable/components/DataTableCore.tsx +4 -7
  251. package/src/components/DataTable/components/DataTableModals.tsx +1 -1
  252. package/src/components/DataTable/components/EditableRow.tsx +1 -1
  253. package/src/components/DataTable/components/UnifiedTableBody.tsx +86 -17
  254. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  255. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  256. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  257. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  258. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  259. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  260. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  261. package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
  262. package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
  263. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  264. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  265. package/src/components/Dialog/Dialog.tsx +6 -5
  266. package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
  267. package/src/components/EventSelector/EventSelector.tsx +1 -1
  268. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -3
  269. package/src/components/FileDisplay/FileDisplay.tsx +16 -4
  270. package/src/components/Footer/Footer.tsx +1 -1
  271. package/src/components/Form/Form.test.tsx +36 -15
  272. package/src/components/Form/Form.tsx +30 -26
  273. package/src/components/Header/Header.tsx +1 -1
  274. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  275. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  276. package/src/components/Input/Input.tsx +28 -30
  277. package/src/components/Label/Label.tsx +1 -1
  278. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  279. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  280. package/src/components/LoginForm/LoginForm.tsx +8 -8
  281. package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
  282. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -11
  283. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -1
  284. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  285. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +75 -52
  286. package/src/components/PaceAppLayout/PaceAppLayout.tsx +98 -69
  287. package/src/components/PaceAppLayout/README.md +1 -1
  288. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -8
  289. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  290. package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
  291. package/src/components/Progress/Progress.tsx +1 -1
  292. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
  293. package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
  294. package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
  295. package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
  296. package/src/components/Select/Select.tsx +33 -22
  297. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
  298. package/src/components/Table/Table.tsx +1 -1
  299. package/src/components/Textarea/Textarea.tsx +27 -29
  300. package/src/components/Toast/Toast.tsx +1 -1
  301. package/src/components/Tooltip/Tooltip.tsx +1 -1
  302. package/src/components/UserMenu/UserMenu.tsx +1 -1
  303. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  304. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +14 -7
  305. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  306. package/src/hooks/public/usePublicEvent.ts +1 -1
  307. package/src/hooks/public/usePublicEventLogo.ts +1 -1
  308. package/src/hooks/public/usePublicRouteParams.ts +1 -1
  309. package/src/hooks/services/useAuthService.ts +21 -3
  310. package/src/hooks/services/useEventService.ts +21 -3
  311. package/src/hooks/services/useInactivityService.ts +21 -3
  312. package/src/hooks/services/useOrganisationService.ts +21 -3
  313. package/src/hooks/useDataTableState.ts +8 -18
  314. package/src/hooks/useFileDisplay.ts +10 -17
  315. package/src/hooks/useFocusManagement.ts +2 -2
  316. package/src/hooks/useFocusTrap.ts +4 -4
  317. package/src/hooks/useFormDialog.ts +8 -7
  318. package/src/hooks/useInactivityTracker.ts +1 -1
  319. package/src/hooks/usePermissionCache.ts +1 -1
  320. package/src/hooks/useSecureDataAccess.test.ts +16 -9
  321. package/src/hooks/useSecureDataAccess.ts +22 -6
  322. package/src/hooks/useToast.ts +2 -2
  323. package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
  324. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  325. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  326. package/src/providers/services/EventServiceProvider.tsx +0 -8
  327. package/src/providers/services/UnifiedAuthProvider.tsx +196 -46
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
  329. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +34 -40
  330. package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
  331. package/src/rbac/adapters.tsx +3 -22
  332. package/src/rbac/api.test.ts +2 -2
  333. package/src/rbac/api.ts +7 -1
  334. package/src/rbac/components/EnhancedNavigationMenu.tsx +3 -16
  335. package/src/rbac/components/NavigationGuard.tsx +2 -11
  336. package/src/rbac/components/NavigationProvider.tsx +1 -2
  337. package/src/rbac/components/PagePermissionGuard.tsx +1 -1
  338. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  339. package/src/rbac/components/PermissionEnforcer.tsx +46 -13
  340. package/src/rbac/components/RoleBasedRouter.tsx +1 -1
  341. package/src/rbac/components/SecureDataProvider.tsx +1 -2
  342. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
  343. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
  344. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
  345. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
  346. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
  347. package/src/rbac/engine.ts +14 -2
  348. package/src/rbac/hooks/index.ts +0 -3
  349. package/src/rbac/hooks/usePermissions.ts +51 -11
  350. package/src/rbac/hooks/useRBAC.ts +3 -13
  351. package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
  352. package/src/rbac/hooks/useResolvedScope.ts +58 -33
  353. package/src/rbac/hooks/useSecureSupabase.ts +4 -9
  354. package/src/rbac/secureClient.ts +43 -0
  355. package/src/services/EventService.ts +4 -57
  356. package/src/services/InactivityService.ts +127 -34
  357. package/src/services/OrganisationService.ts +68 -10
  358. package/src/utils/security/secureDataAccess.test.ts +31 -20
  359. package/src/utils/security/secureDataAccess.ts +4 -3
  360. package/dist/chunk-6C4YBBJM.js.map +0 -1
  361. package/dist/chunk-6LTQQAT6.js.map +0 -1
  362. package/dist/chunk-6TQDD426.js.map +0 -1
  363. package/dist/chunk-LOMZXPSN.js.map +0 -1
  364. package/dist/chunk-OETXORNB.js.map +0 -1
  365. package/dist/chunk-ULHIJK66.js.map +0 -1
  366. package/dist/chunk-VKB2CO4Z.js.map +0 -1
  367. package/dist/chunk-VRGWKHDB.js.map +0 -1
  368. package/dist/chunk-XNYQOL3Z.js.map +0 -1
  369. package/dist/chunk-XYXSXPUK.js.map +0 -1
  370. package/scripts/check-pace-core-compliance.js +0 -512
  371. package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
  372. package/src/utils/context/superAdminOverride.ts +0 -58
  373. /package/dist/{DataTable-WKRZD47S.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
  374. /package/dist/{UnifiedAuthProvider-FTSG5XH7.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
  375. /package/dist/{api-IHKALJZD.js.map → api-N774RPUA.js.map} +0 -0
  376. /package/dist/{chunk-QWWZ5CAQ.js.map → chunk-LXQLPRQ2.js.map} +0 -0
  377. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  378. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  379. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  380. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -378,12 +378,20 @@ function scanFile(filePath, manifest) {
378
378
  providerSetupIssues: [],
379
379
  viteConfigIssues: [],
380
380
  routerSetupIssues: [],
381
- unnecessaryWrappers: []
381
+ unnecessaryWrappers: [],
382
+ appDiscoveryIssues: []
382
383
  };
383
384
 
384
385
  const content = fs.readFileSync(filePath, 'utf8');
385
386
  const relativePath = path.relative(process.cwd(), filePath);
386
387
 
388
+ // Normalize path for cross-platform compatibility (handle both forward and backslash paths)
389
+ const normalizedPath = relativePath.replace(/\\/g, '/');
390
+
391
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
392
+ // Direct Supabase auth calls are the correct approach in Edge Functions
393
+ const isEdgeFunction = normalizedPath.includes('supabase/functions/');
394
+
387
395
  // Check for restricted imports
388
396
  manifest.restrictedImports.forEach(({ module, reason }) => {
389
397
  const importPattern = new RegExp(`from\\s+['"]${module.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]`, 'g');
@@ -513,9 +521,11 @@ function scanFile(filePath, manifest) {
513
521
  /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
514
522
 
515
523
  authRbacPatterns.forEach(({ pattern, name, type, replacement }) => {
516
- if (pattern.test(content)) {
517
- // For custom RBAC hooks, always flag them even if they import from pace-core
518
- // because they're duplicating functionality that should come from pace-core
524
+ // Create a new regex instance to avoid state issues
525
+ const testPattern = new RegExp(pattern.source, pattern.flags);
526
+ if (testPattern.test(content)) {
527
+ // For custom RBAC hooks, check if they're actually using pace-core APIs
528
+ // If they use pace-core RBAC APIs (useRoleManagement, useSecureSupabase, etc.), they're compliant
519
529
  const isCustomRBACHook = [
520
530
  'useUserRoles',
521
531
  'useUserOrganisationRoles',
@@ -526,6 +536,20 @@ function scanFile(filePath, manifest) {
526
536
  'useRole'
527
537
  ].includes(name);
528
538
 
539
+ // Check if the hook uses pace-core RBAC APIs (use a fresh regex to avoid state issues)
540
+ const rbacApiPattern = /useRoleManagement|useSecureSupabase|useSecureDataAccess|useRBAC|usePermissions|useCan|rbac_role_grant|rbac_role_revoke|rbac_roles_list|data_rbac_apps_list/;
541
+ const usesPaceCoreRBAC = isCustomRBACHook && rbacApiPattern.test(content);
542
+
543
+ // Check for @pace-core-compliant comment (use a fresh regex)
544
+ const compliancePattern = /@pace-core-compliant|pace-core-compliant/i;
545
+ const hasComplianceComment = compliancePattern.test(content);
546
+
547
+ // If it's a custom RBAC hook but uses pace-core APIs or has compliance comment, skip it
548
+ if (isCustomRBACHook && (usesPaceCoreRBAC || hasComplianceComment)) {
549
+ // This hook is compliant - it uses pace-core APIs
550
+ return; // Skip to next pattern
551
+ }
552
+
529
553
  // Flag custom RBAC hooks even if they import from pace-core (they're still duplicating functionality)
530
554
  // For other hooks, only flag if they don't import from pace-core
531
555
  if (isCustomRBACHook || !hasPaceCoreImport) {
@@ -640,7 +664,26 @@ function scanFile(filePath, manifest) {
640
664
  // This includes all variations: supabase.auth.getUser(), client.auth.getUser(), etc.
641
665
  // Priority patterns - these are the most common violations
642
666
  // Using multiple patterns to catch all variations
643
- const priorityAuthPatterns = [
667
+
668
+ // Skip Edge Functions - they run in Deno, not React, so React hooks are not available
669
+ // Edge Functions MUST use direct supabase.auth.getUser() calls - this is correct and required
670
+ // isEdgeFunction is already defined above
671
+
672
+ // Other auth patterns - defined here so it's accessible in all scopes
673
+ const otherAuthPatterns = [
674
+ { pattern: /\.auth\.signIn\s*\(/g, method: 'signIn' },
675
+ { pattern: /\.auth\.signUp\s*\(/g, method: 'signUp' },
676
+ { pattern: /\.auth\.signOut\s*\(/g, method: 'signOut' },
677
+ { pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
678
+ ];
679
+
680
+ // Skip all auth checks for Edge Functions - they cannot use React hooks
681
+ if (isEdgeFunction) {
682
+ // Edge Functions use service role client or direct auth calls - correct pattern
683
+ // Don't flag these as violations
684
+ } else {
685
+ // Only check for direct auth usage in client-side code (React components, hooks, etc.)
686
+ const priorityAuthPatterns = [
644
687
  // Most specific patterns first
645
688
  { pattern: /supabase\.auth\.getUser\s*\(/g, method: 'getUser', specific: true },
646
689
  { pattern: /supabase\.auth\.getSession\s*\(/g, method: 'getSession', specific: true },
@@ -654,59 +697,51 @@ function scanFile(filePath, manifest) {
654
697
  { pattern: /\w+\.auth\.getSession\s*\(/g, method: 'getSession', specific: false }
655
698
  ];
656
699
 
657
- // Other auth patterns
658
- const otherAuthPatterns = [
659
- { pattern: /\.auth\.signIn\s*\(/g, method: 'signIn' },
660
- { pattern: /\.auth\.signUp\s*\(/g, method: 'signUp' },
661
- { pattern: /\.auth\.signOut\s*\(/g, method: 'signOut' },
662
- { pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
663
- ];
664
-
665
- // Check if file actually uses useUnifiedAuth hook (not just imports it)
666
- const usesUnifiedAuthHook = /useUnifiedAuth\s*\(/.test(content);
667
- const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
668
- /useUnifiedAuth/.test(content) ||
669
- /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
670
-
671
- // Check for usage of useCurrentUser hook (even if imported from local file)
672
- // This catches both local imports and direct usage
673
- const useCurrentUserImportPattern = /import\s+.*useCurrentUser.*from\s+['"][^'"]*['"]/g;
674
- const useCurrentUserUsagePattern = /useCurrentUser\s*\(/g;
675
-
676
- // Check for local import (not from pace-core)
677
- const useCurrentUserImportMatch = content.match(useCurrentUserImportPattern);
678
- if (useCurrentUserImportMatch) {
679
- const isFromPaceCore = useCurrentUserImportMatch.some(match => match.includes('@jmruthers/pace-core'));
680
- if (!isFromPaceCore) {
681
- useCurrentUserImportMatch.forEach(match => {
700
+ // Check if file actually uses useUnifiedAuth hook (not just imports it)
701
+ const usesUnifiedAuthHook = /useUnifiedAuth\s*\(/.test(content);
702
+ const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
703
+ /useUnifiedAuth/.test(content) ||
704
+ /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
705
+
706
+ // Check for usage of useCurrentUser hook (even if imported from local file)
707
+ // This catches both local imports and direct usage
708
+ const useCurrentUserImportPattern = /import\s+.*useCurrentUser.*from\s+['"][^'"]*['"]/g;
709
+ const useCurrentUserUsagePattern = /useCurrentUser\s*\(/g;
710
+
711
+ // Check for local import (not from pace-core)
712
+ const useCurrentUserImportMatch = content.match(useCurrentUserImportPattern);
713
+ if (useCurrentUserImportMatch) {
714
+ const isFromPaceCore = useCurrentUserImportMatch.some(match => match.includes('@jmruthers/pace-core'));
715
+ if (!isFromPaceCore) {
716
+ useCurrentUserImportMatch.forEach(match => {
717
+ violations.customAuthCode.push({
718
+ name: 'useCurrentUser import',
719
+ type: 'hook import',
720
+ file: relativePath,
721
+ line: getLineNumber(content, match),
722
+ reason: 'useCurrentUser imported from local file. Replace with useUnifiedAuth from pace-core.',
723
+ replacement: 'useUnifiedAuth from @jmruthers/pace-core'
724
+ });
725
+ });
726
+ }
727
+ }
728
+
729
+ // Check for usage (even if imported)
730
+ const useCurrentUserUsageMatches = content.match(useCurrentUserUsagePattern);
731
+ if (useCurrentUserUsageMatches && !usesUnifiedAuthHook) {
732
+ useCurrentUserUsageMatches.forEach(match => {
682
733
  violations.customAuthCode.push({
683
- name: 'useCurrentUser import',
684
- type: 'hook import',
734
+ name: 'useCurrentUser',
735
+ type: 'hook usage',
685
736
  file: relativePath,
686
737
  line: getLineNumber(content, match),
687
- reason: 'useCurrentUser imported from local file. Replace with useUnifiedAuth from pace-core.',
688
- replacement: 'useUnifiedAuth from @jmruthers/pace-core'
738
+ reason: 'useCurrentUser hook usage detected. Replace with useUnifiedAuth from pace-core.',
739
+ replacement: 'useUnifiedAuth'
689
740
  });
690
741
  });
691
742
  }
692
- }
693
-
694
- // Check for usage (even if imported)
695
- const useCurrentUserUsageMatches = content.match(useCurrentUserUsagePattern);
696
- if (useCurrentUserUsageMatches && !usesUnifiedAuthHook) {
697
- useCurrentUserUsageMatches.forEach(match => {
698
- violations.customAuthCode.push({
699
- name: 'useCurrentUser',
700
- type: 'hook usage',
701
- file: relativePath,
702
- line: getLineNumber(content, match),
703
- reason: 'useCurrentUser hook usage detected. Replace with useUnifiedAuth from pace-core.',
704
- replacement: 'useUnifiedAuth'
705
- });
706
- });
707
- }
708
-
709
- // Check priority patterns first (getUser, getSession) - these should always be flagged
743
+
744
+ // Check priority patterns first (getUser, getSession) - these should always be flagged
710
745
  // Use exec in a loop to get all matches with their correct positions
711
746
  priorityAuthPatterns.forEach(({ pattern, method, specific }) => {
712
747
  // Reset regex for each pattern
@@ -730,7 +765,8 @@ function scanFile(filePath, manifest) {
730
765
  const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
731
766
 
732
767
  // Only skip if clearly in a comment - be conservative
733
- if (!isInLineComment) {
768
+ // Also skip Edge Functions - they run in Deno, not React, so React hooks aren't available
769
+ if (!isInLineComment && !isEdgeFunction) {
734
770
  violations.directSupabaseAuth.push({
735
771
  file: relativePath,
736
772
  line: getLineNumber(content, matchText),
@@ -745,8 +781,11 @@ function scanFile(filePath, manifest) {
745
781
  }
746
782
  }
747
783
  });
784
+ } // End of else block for non-Edge Functions
748
785
 
749
786
  // Additional simple pattern check as fallback - look for literal strings
787
+ // Skip for Edge Functions
788
+ if (!isEdgeFunction) {
750
789
  // This catches cases where the regex might miss due to formatting
751
790
  const simpleAuthPatterns = [
752
791
  { search: 'supabase.auth.getUser(', method: 'getUser' },
@@ -761,7 +800,8 @@ function scanFile(filePath, manifest) {
761
800
  const lineUpToMatch = content.substring(lineStart, searchIndex);
762
801
  const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
763
802
 
764
- if (!isInLineComment) {
803
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
804
+ if (!isInLineComment && !isEdgeFunction) {
765
805
  // Calculate line number from index
766
806
  const lineNum = content.substring(0, searchIndex).split('\n').length;
767
807
 
@@ -787,8 +827,11 @@ function scanFile(filePath, manifest) {
787
827
  searchIndex += search.length; // Move past this match
788
828
  }
789
829
  });
830
+ } // End of Edge Function check for simple patterns
790
831
 
791
832
  // Check other auth patterns - flag if not using useUnifiedAuth
833
+ // Skip for Edge Functions
834
+ if (!isEdgeFunction) {
792
835
  otherAuthPatterns.forEach(({ pattern, method }) => {
793
836
  let match;
794
837
  const regex = new RegExp(pattern.source, pattern.flags);
@@ -819,7 +862,8 @@ function scanFile(filePath, manifest) {
819
862
  (doubleQuotes % 2 === 1 && beforeMatch.endsWith('"')) ||
820
863
  (backticks % 2 === 1 && beforeMatch.endsWith('`'));
821
864
 
822
- if (!isInLineComment && !isInBlockComment && !isInString && !usesUnifiedAuthHook) {
865
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
866
+ if (!isInLineComment && !isInBlockComment && !isInString && !usesUnifiedAuthHook && !isEdgeFunction) {
823
867
  violations.directSupabaseAuth.push({
824
868
  file: relativePath,
825
869
  line: getLineNumber(content, matchText),
@@ -829,6 +873,7 @@ function scanFile(filePath, manifest) {
829
873
  }
830
874
  }
831
875
  });
876
+ } // End of Edge Function check for other auth patterns
832
877
 
833
878
  // Check for direct RBAC table queries (should use pace-core RBAC APIs/RPC functions)
834
879
  // List of all RBAC tables with specific recommendations
@@ -1143,6 +1188,16 @@ function scanFile(filePath, manifest) {
1143
1188
  const matchIndex = match.index;
1144
1189
  const matchText = match[0];
1145
1190
 
1191
+ // Check if this is an edge function (supabase/functions directory) - check early to skip
1192
+ // Handle both forward and backslash paths (Windows vs Unix)
1193
+ const normalizedPath = relativePath.replace(/\\/g, '/');
1194
+ const isEdgeFunction = normalizedPath.includes('supabase/functions/');
1195
+
1196
+ // Skip edge functions - they use service role client which is correct for server-side operations
1197
+ if (isEdgeFunction) {
1198
+ continue; // Edge functions use service role client - correct pattern
1199
+ }
1200
+
1146
1201
  // Extract table name
1147
1202
  const afterMatch = content.substring(matchIndex, Math.min(content.length, matchIndex + 100));
1148
1203
  const tableMatch = afterMatch.match(/['"]rbac_([^'"]+)['"]/);
@@ -1155,7 +1210,8 @@ function scanFile(filePath, manifest) {
1155
1210
 
1156
1211
  // Extract variable name if pattern matches variable.from() (handle newlines)
1157
1212
  let variableName = null;
1158
- const beforeMatch = content.substring(Math.max(0, matchIndex - 200), matchIndex);
1213
+ // Use wider context to find function signatures (up to 500 chars before)
1214
+ const beforeMatch = content.substring(Math.max(0, matchIndex - 500), matchIndex);
1159
1215
  // Find the last word/identifier before .from (same logic as main pattern)
1160
1216
  const parts = beforeMatch.split('.from');
1161
1217
  if (parts.length > 0) {
@@ -1169,9 +1225,28 @@ function scanFile(filePath, manifest) {
1169
1225
  // Check if using secure variable (check both set and direct pattern match)
1170
1226
  // Escape special regex characters in variable name and use multiline flag to handle newlines
1171
1227
  const escapedVarName = variableName ? variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
1172
- const isUsingSecureVariable = (variableName && secureVariables.has(variableName)) ||
1228
+ // Check if variable is declared with useSecureSupabase or useSecureDataAccess
1229
+ const isDeclaredSecure = (variableName && secureVariables.has(variableName)) ||
1173
1230
  (variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content)) ||
1174
1231
  (variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureDataAccess\\s*\\(`, 'm').test(content));
1232
+ // Check if variable is passed as parameter with useSecureSupabase type annotation
1233
+ // Look for the parameter in function signatures (check both beforeMatch and full content)
1234
+ const isParameterSecure = variableName && (
1235
+ // Check in beforeMatch (500 chars before)
1236
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*useSecureSupabase`, 'm').test(beforeMatch) ||
1237
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*ReturnType`, 'm').test(beforeMatch) ||
1238
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*secureSupabase`, 'i').test(beforeMatch) ||
1239
+ // Also check the full function signature area (wider context in full content)
1240
+ new RegExp(`(function|=>|async\\s+function)[^(]*\\([^)]*\\b${escapedVarName}\\s*:\\s*.*(useSecureSupabase|ReturnType|secureSupabase)`, 'm').test(content) ||
1241
+ // Check for ReturnType<typeof import pattern (common in TypeScript)
1242
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*ReturnType.*useSecureSupabase`, 'm').test(content)
1243
+ );
1244
+ // Check for comments indicating secureSupabase usage
1245
+ const hasSecureComment = variableName && (
1246
+ new RegExp(`secureSupabase|useSecureSupabase`, 'i').test(beforeMatch) ||
1247
+ new RegExp(`COMPLIANCE.*secureSupabase|pace-core.*secureSupabase`, 'i').test(beforeMatch)
1248
+ );
1249
+ const isUsingSecureVariable = isDeclaredSecure || isParameterSecure || hasSecureComment;
1175
1250
 
1176
1251
  // Skip if we already reported this specific table
1177
1252
  const alreadyReported = violations.customAuthCode.some(v =>
@@ -1776,6 +1851,119 @@ function scanFile(filePath, manifest) {
1776
1851
  violations.unnecessaryWrappers.push(...wrapperIssues);
1777
1852
  }
1778
1853
 
1854
+ // ============================================
1855
+ // App Discovery Compliance Checks
1856
+ // ============================================
1857
+ // Check for direct queries to rbac_apps table or hardcoded app names
1858
+ // Should use data_rbac_apps_list RPC function instead
1859
+
1860
+ // Check for direct queries to rbac_apps table
1861
+ const rbacAppsQueryPatterns = [
1862
+ // Supabase client queries
1863
+ /\.from\s*\(\s*['"]rbac_apps['"]\s*\)/g,
1864
+ /\.from\s*\(\s*['"]rbac_apps['"]\s*\)/g,
1865
+ // SQL queries (less common but possible)
1866
+ /FROM\s+rbac_apps\b/gi,
1867
+ /SELECT\s+.*\s+FROM\s+rbac_apps\b/gi
1868
+ ];
1869
+
1870
+ // Check if file uses data_rbac_apps_list RPC function
1871
+ const usesRpcFunction = /data_rbac_apps_list|rpc\s*\(\s*['"]data_rbac_apps_list['"]/gi.test(content);
1872
+
1873
+ rbacAppsQueryPatterns.forEach(pattern => {
1874
+ let match;
1875
+ while ((match = pattern.exec(content)) !== null) {
1876
+ // Skip if in a line comment
1877
+ const lineStart = content.lastIndexOf('\n', match.index) + 1;
1878
+ const lineUpToMatch = content.substring(lineStart, match.index);
1879
+ const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
1880
+
1881
+ if (isInLineComment) {
1882
+ continue;
1883
+ }
1884
+
1885
+ // Check what comes after .from('rbac_apps') to determine if it's a SELECT query or CRUD operation
1886
+ const afterMatch = content.substring(match.index, Math.min(content.length, match.index + 200));
1887
+ const isSelectQuery = /\.(select|selectAll)\s*\(/i.test(afterMatch);
1888
+ const isCrudOperation = /\.(update|insert|delete|upsert)\s*\(/i.test(afterMatch);
1889
+
1890
+ // Only flag SELECT queries - UPDATE/INSERT/DELETE operations are acceptable with secureSupabase
1891
+ if (isSelectQuery && !isCrudOperation) {
1892
+ violations.appDiscoveryIssues.push({
1893
+ type: 'direct_table_query',
1894
+ file: relativePath,
1895
+ line: getLineNumber(content, match[0]),
1896
+ reason: 'Direct query to rbac_apps table detected. Use data_rbac_apps_list RPC function for dynamic app discovery.',
1897
+ recommendation: 'Replace with: const { data } = await supabase.rpc(\'data_rbac_apps_list\');',
1898
+ severity: 'warning'
1899
+ });
1900
+ }
1901
+ // Skip CRUD operations (UPDATE/INSERT/DELETE) - these are acceptable with secureSupabase
1902
+ }
1903
+ });
1904
+
1905
+ // Check for hardcoded app names in arrays or string literals
1906
+ // Known app names: BASE, CAKE, PACE, MINT, TRAC, PORTAL, MEDI
1907
+ // Only flag if they appear to be used for app discovery (arrays, comparisons, etc.)
1908
+ const hardcodedAppNamePatterns = [
1909
+ // Array of app names (likely used for iteration/discovery)
1910
+ /\[['"]\s*(BASE|CAKE|PACE|MINT|TRAC|PORTAL|MEDI)\s*['"]/gi,
1911
+ // String literals in comparisons or includes checks (app discovery patterns)
1912
+ /(app|apps|appName|app_name)\s*[=!]==?\s*['"]\s*(BASE|CAKE|PACE|MINT|TRAC|PORTAL|MEDI)\s*['"]/gi,
1913
+ /(app|apps|appName|app_name)\s*\.(includes|indexOf|find|filter)\s*\([^)]*['"]\s*(BASE|CAKE|PACE|MINT|TRAC|PORTAL|MEDI)\s*['"]/gi,
1914
+ /['"]\s*(BASE|CAKE|PACE|MINT|TRAC|PORTAL|MEDI)\s*['"]\s*\.(includes|indexOf)/gi
1915
+ ];
1916
+
1917
+ hardcodedAppNamePatterns.forEach(pattern => {
1918
+ let match;
1919
+ while ((match = pattern.exec(content)) !== null) {
1920
+ // Skip if it's in a get_app_id call (acceptable usage)
1921
+ const beforeMatch = content.substring(Math.max(0, match.index - 50), match.index);
1922
+ const isInGetAppId = /get_app_id\s*\(/i.test(beforeMatch);
1923
+
1924
+ // Skip if in a line comment
1925
+ const lineStart = content.lastIndexOf('\n', match.index) + 1;
1926
+ const lineUpToMatch = content.substring(lineStart, match.index);
1927
+ const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
1928
+
1929
+ // Skip if it's a comment about app names
1930
+ const isInComment = /\/\*[\s\S]*?\*\//.test(beforeMatch + match[0]);
1931
+
1932
+ // Skip if it's in a data_rbac_apps_list call (already using the function)
1933
+ const isInRpcCall = /data_rbac_apps_list/i.test(beforeMatch);
1934
+
1935
+ // Extract app name (could be in different capture groups depending on pattern)
1936
+ const appName = match[1] || match[2] || match[3];
1937
+
1938
+ if (!isInGetAppId && !isInLineComment && !isInComment && !isInRpcCall && appName) {
1939
+ violations.appDiscoveryIssues.push({
1940
+ type: 'hardcoded_app_name',
1941
+ file: relativePath,
1942
+ line: getLineNumber(content, match[0]),
1943
+ appName: appName,
1944
+ reason: `Hardcoded app name '${appName}' detected. Use data_rbac_apps_list RPC function for dynamic app discovery.`,
1945
+ recommendation: 'Use data_rbac_apps_list() to discover apps dynamically instead of hardcoding app names.',
1946
+ severity: 'warning'
1947
+ });
1948
+ }
1949
+ }
1950
+ });
1951
+
1952
+ // If file has app discovery code but doesn't use the RPC function, suggest it
1953
+ if (violations.appDiscoveryIssues.length > 0 && !usesRpcFunction) {
1954
+ // Add a general suggestion if there are multiple issues
1955
+ if (violations.appDiscoveryIssues.length > 1) {
1956
+ violations.appDiscoveryIssues.push({
1957
+ type: 'suggestion',
1958
+ file: relativePath,
1959
+ line: 1,
1960
+ reason: 'Multiple app discovery issues found. Consider using data_rbac_apps_list RPC function for all app discovery needs.',
1961
+ recommendation: 'Replace all hardcoded app names and direct table queries with: const { data: apps } = await supabase.rpc(\'data_rbac_apps_list\');',
1962
+ severity: 'info'
1963
+ });
1964
+ }
1965
+ }
1966
+
1779
1967
  return violations;
1780
1968
  }
1781
1969
 
@@ -1985,7 +2173,8 @@ function generateReport(allViolations, manifest) {
1985
2173
  allViolations.viteConfigIssues.length +
1986
2174
  allViolations.routerSetupIssues.length;
1987
2175
  const totalUnnecessaryWrappers = allViolations.unnecessaryWrappers.length;
1988
- const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth + totalSetupIssues + totalUnnecessaryWrappers;
2176
+ const totalAppDiscovery = allViolations.appDiscoveryIssues.length;
2177
+ const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth + totalSetupIssues + totalUnnecessaryWrappers + totalAppDiscovery;
1989
2178
 
1990
2179
  console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
1991
2180
  console.log(`${colors.bold}${colors.cyan} pace-core Compliance Report${colors.reset}`);
@@ -2065,6 +2254,52 @@ function generateReport(allViolations, manifest) {
2065
2254
  });
2066
2255
  }
2067
2256
 
2257
+ // App Discovery Issues
2258
+ if (totalAppDiscovery > 0) {
2259
+ console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
2260
+ console.log(`${colors.bold}${colors.cyan} App Discovery Compliance${colors.reset}`);
2261
+ console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
2262
+
2263
+ // Separate by type
2264
+ const directQueries = allViolations.appDiscoveryIssues.filter(v => v.type === 'direct_table_query');
2265
+ const hardcodedNames = allViolations.appDiscoveryIssues.filter(v => v.type === 'hardcoded_app_name');
2266
+ const suggestions = allViolations.appDiscoveryIssues.filter(v => v.type === 'suggestion');
2267
+
2268
+ if (directQueries.length > 0) {
2269
+ console.log(`${colors.yellow}${colors.bold}⚠️ Direct rbac_apps Queries: ${directQueries.length}${colors.reset}\n`);
2270
+ directQueries.forEach(({ file, line, reason, recommendation }) => {
2271
+ console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2272
+ console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
2273
+ console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
2274
+ });
2275
+ }
2276
+
2277
+ if (hardcodedNames.length > 0) {
2278
+ console.log(`${colors.yellow}${colors.bold}⚠️ Hardcoded App Names: ${hardcodedNames.length}${colors.reset}\n`);
2279
+ hardcodedNames.forEach(({ file, line, appName, reason, recommendation }) => {
2280
+ console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2281
+ console.log(` App Name: ${colors.cyan}${appName}${colors.reset}`);
2282
+ console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
2283
+ console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
2284
+ });
2285
+ }
2286
+
2287
+ if (suggestions.length > 0) {
2288
+ console.log(`${colors.cyan}${colors.bold}💡 Suggestions: ${suggestions.length}${colors.reset}\n`);
2289
+ suggestions.forEach(({ file, reason, recommendation }) => {
2290
+ console.log(` ${colors.cyan}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
2291
+ console.log(` ${reason}`);
2292
+ console.log(` ${colors.green}Recommendation:${colors.reset} ${recommendation}\n`);
2293
+ });
2294
+ }
2295
+
2296
+ console.log(`${colors.cyan}Example Usage:${colors.reset}`);
2297
+ console.log(` ${colors.green}const { data: apps } = await supabase.rpc('data_rbac_apps_list');${colors.reset}`);
2298
+ console.log(` ${colors.green}const appNames = apps?.map(app => app.name) || [];${colors.reset}\n`);
2299
+ } else {
2300
+ console.log(`${colors.green}✅ App discovery compliance: Using data_rbac_apps_list RPC function${colors.reset}\n`);
2301
+ }
2302
+
2068
2303
  // RBAC/Auth Compliance Section
2069
2304
  if (totalRbacAuth > 0) {
2070
2305
  console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
@@ -2115,7 +2350,11 @@ function generateReport(allViolations, manifest) {
2115
2350
  console.log(` ${colors.green} p_user_id: userId,${colors.reset}`);
2116
2351
  console.log(` ${colors.green} p_event_id: eventId${colors.reset}`);
2117
2352
  console.log(` ${colors.green}});${colors.reset}`);
2118
- } else if (type === 'rbac query' && (name.includes('rbac_apps') || name.includes('rbac_app_pages') || name.includes('rbac_page_permissions'))) {
2353
+ } else if (type === 'rbac query' && name.includes('rbac_apps')) {
2354
+ console.log(` ${colors.cyan}Example (app discovery):${colors.reset}`);
2355
+ console.log(` ${colors.green}const { data: apps } = await supabase.rpc('data_rbac_apps_list');${colors.reset}`);
2356
+ console.log(` ${colors.yellow}Note:${colors.reset} Use data_rbac_apps_list RPC function for dynamic app discovery instead of querying rbac_apps directly.`);
2357
+ } else if (type === 'rbac query' && (name.includes('rbac_app_pages') || name.includes('rbac_page_permissions'))) {
2119
2358
  console.log(` ${colors.cyan}Example (admin operations):${colors.reset}`);
2120
2359
  console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
2121
2360
  console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
@@ -2243,6 +2482,8 @@ function generateReport(allViolations, manifest) {
2243
2482
  console.log(` - Suggestions: ${colors.yellow}${totalSuggestions}${colors.reset}`);
2244
2483
  console.log(` - RBAC/Auth Issues: ${totalRbacAuth > 0 ? colors.red : colors.green}${totalRbacAuth}${colors.reset}`);
2245
2484
  console.log(` - Setup/Configuration Issues: ${totalSetupIssues > 0 ? colors.red : colors.green}${totalSetupIssues}${colors.reset}`);
2485
+ console.log(` - Unnecessary Wrappers: ${totalUnnecessaryWrappers > 0 ? colors.yellow : colors.green}${totalUnnecessaryWrappers}${colors.reset}`);
2486
+ console.log(` - App Discovery Issues: ${totalAppDiscovery > 0 ? colors.yellow : colors.green}${totalAppDiscovery}${colors.reset}`);
2246
2487
 
2247
2488
  if (totalIssues === 0) {
2248
2489
  console.log(`\n${colors.green}${colors.bold}✅ Excellent! Your codebase is fully compliant with pace-core standards.${colors.reset}\n`);
@@ -2267,6 +2508,10 @@ function findSourceFiles(dir, fileList = []) {
2267
2508
  const stat = fs.statSync(fullPath);
2268
2509
 
2269
2510
  if (stat.isDirectory()) {
2511
+ // Skip Edge Functions directory - they run in Deno, not React, so React hooks aren't available
2512
+ if (item === 'functions' && dir.includes('supabase')) {
2513
+ return; // Skip supabase/functions directory
2514
+ }
2270
2515
  if (!ignoreDirs.includes(item) && !item.startsWith('.')) {
2271
2516
  findSourceFiles(fullPath, fileList);
2272
2517
  }
@@ -2309,7 +2554,8 @@ function main() {
2309
2554
  providerSetupIssues: [],
2310
2555
  viteConfigIssues: [],
2311
2556
  routerSetupIssues: [],
2312
- unnecessaryWrappers: []
2557
+ unnecessaryWrappers: [],
2558
+ appDiscoveryIssues: []
2313
2559
  };
2314
2560
 
2315
2561
  files.forEach(file => {
@@ -2328,6 +2574,7 @@ function main() {
2328
2574
  allViolations.viteConfigIssues.push(...violations.viteConfigIssues);
2329
2575
  allViolations.routerSetupIssues.push(...violations.routerSetupIssues);
2330
2576
  allViolations.unnecessaryWrappers.push(...violations.unnecessaryWrappers);
2577
+ allViolations.appDiscoveryIssues.push(...violations.appDiscoveryIssues);
2331
2578
  } catch (error) {
2332
2579
  console.error(`${colors.red}Error scanning ${file}: ${error.message}${colors.reset}`);
2333
2580
  }
@@ -2349,5 +2596,12 @@ if (require.main === module) {
2349
2596
  }
2350
2597
  }
2351
2598
 
2352
- module.exports = { main, scanFile, generateReport };
2599
+ module.exports = {
2600
+ main,
2601
+ scanFile,
2602
+ generateReport,
2603
+ loadManifest,
2604
+ findProjectRoot,
2605
+ findSourceFiles
2606
+ };
2353
2607