@jmruthers/pace-core 0.6.5 → 0.6.7

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 (473) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/audit-tool/00-dependencies.cjs +394 -0
  4. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  5. package/audit-tool/audits/02-project-structure.cjs +255 -0
  6. package/audit-tool/audits/03-architecture.cjs +196 -0
  7. package/audit-tool/audits/04-code-quality.cjs +149 -0
  8. package/audit-tool/audits/05-styling.cjs +224 -0
  9. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  10. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  11. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  12. package/audit-tool/audits/09-operations.cjs +208 -0
  13. package/audit-tool/index.cjs +291 -0
  14. package/audit-tool/utils/code-utils.cjs +218 -0
  15. package/audit-tool/utils/file-utils.cjs +230 -0
  16. package/audit-tool/utils/report-utils.cjs +241 -0
  17. package/core-usage-manifest.json +93 -0
  18. package/cursor-rules/00-standards-overview.mdc +156 -0
  19. package/cursor-rules/01-pace-core-compliance.mdc +586 -0
  20. package/cursor-rules/02-project-structure.mdc +42 -4
  21. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
  22. package/cursor-rules/04-code-quality.mdc +419 -0
  23. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
  24. package/cursor-rules/06-security-rbac.mdc +518 -0
  25. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  26. package/cursor-rules/08-testing-documentation.mdc +324 -0
  27. package/cursor-rules/09-operations.mdc +365 -0
  28. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  29. package/dist/DataTable-7PMH7XN7.js +15 -0
  30. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  31. package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
  32. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  33. package/dist/api-Y4MQWOFW.js +4 -0
  34. package/dist/audit-MYQXYZFU.js +3 -0
  35. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  36. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  37. package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
  38. package/dist/chunk-6F3IILHI.js +62 -0
  39. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  40. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  41. package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
  42. package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
  43. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  44. package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
  45. package/dist/chunk-C7NSAPTL.js +1 -0
  46. package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
  47. package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
  48. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  49. package/dist/chunk-GHYHJTYV.js +994 -0
  50. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  51. package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
  52. package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
  53. package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
  54. package/dist/chunk-MBADTM7L.js +64 -0
  55. package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
  56. package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
  57. package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
  58. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  59. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  60. package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
  61. package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
  62. package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
  63. package/dist/components.d.ts +7 -5
  64. package/dist/components.js +46 -257
  65. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  66. package/dist/eslint-rules/index.cjs +35 -0
  67. package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
  68. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  69. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  70. package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
  71. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  72. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  73. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  74. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  75. package/dist/hooks.d.ts +6 -6
  76. package/dist/hooks.js +62 -172
  77. package/dist/icons/index.d.ts +1 -0
  78. package/dist/icons/index.js +1 -0
  79. package/dist/index.d.ts +12 -11
  80. package/dist/index.js +67 -660
  81. package/dist/providers.d.ts +2 -2
  82. package/dist/providers.js +8 -35
  83. package/dist/rbac/eslint-rules.d.ts +46 -44
  84. package/dist/rbac/eslint-rules.js +7 -4
  85. package/dist/rbac/index.d.ts +109 -586
  86. package/dist/rbac/index.js +14 -207
  87. package/dist/styles/index.js +2 -12
  88. package/dist/theming/runtime.d.ts +14 -1
  89. package/dist/theming/runtime.js +3 -19
  90. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  91. package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
  92. package/dist/types-t9H8qKRw.d.ts +55 -0
  93. package/dist/types.d.ts +1 -1
  94. package/dist/types.js +7 -94
  95. package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
  96. package/dist/utils.d.ts +24 -117
  97. package/dist/utils.js +54 -392
  98. package/docs/README.md +17 -7
  99. package/docs/api/README.md +4 -402
  100. package/docs/api/modules.md +301 -871
  101. package/docs/api-reference/components.md +21 -21
  102. package/docs/api-reference/deprecated.md +31 -6
  103. package/docs/api-reference/hooks.md +80 -80
  104. package/docs/api-reference/rpc-functions.md +78 -3
  105. package/docs/api-reference/types.md +1 -1
  106. package/docs/api-reference/utilities.md +1 -1
  107. package/docs/architecture/README.md +1 -1
  108. package/docs/core-concepts/events.md +3 -3
  109. package/docs/core-concepts/organisations.md +6 -6
  110. package/docs/core-concepts/permissions.md +6 -6
  111. package/docs/documentation-index.md +12 -18
  112. package/docs/getting-started/cursor-rules.md +3 -23
  113. package/docs/getting-started/dependencies.md +650 -0
  114. package/docs/getting-started/documentation-index.md +1 -1
  115. package/docs/getting-started/examples/README.md +4 -4
  116. package/docs/getting-started/examples/full-featured-app.md +1 -1
  117. package/docs/getting-started/faq.md +2 -2
  118. package/docs/getting-started/installation-guide.md +20 -7
  119. package/docs/getting-started/quick-reference.md +4 -4
  120. package/docs/getting-started/quick-start.md +23 -12
  121. package/docs/implementation-guides/authentication.md +15 -15
  122. package/docs/implementation-guides/component-styling.md +1 -1
  123. package/docs/implementation-guides/data-tables.md +126 -33
  124. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  125. package/docs/implementation-guides/dynamic-colors.md +3 -3
  126. package/docs/implementation-guides/file-upload-storage.md +2 -2
  127. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  128. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  129. package/docs/implementation-guides/large-datasets.md +3 -2
  130. package/docs/implementation-guides/organisation-security.md +2 -2
  131. package/docs/implementation-guides/performance.md +2 -2
  132. package/docs/implementation-guides/permission-enforcement.md +5 -1
  133. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  134. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  135. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  136. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  137. package/docs/rbac/README.md +17 -8
  138. package/docs/rbac/advanced-patterns.md +6 -6
  139. package/docs/rbac/api-reference.md +20 -20
  140. package/docs/rbac/edge-functions-guide.md +376 -0
  141. package/docs/rbac/event-based-apps.md +3 -3
  142. package/docs/rbac/examples.md +41 -41
  143. package/docs/rbac/getting-started.md +37 -37
  144. package/docs/rbac/performance.md +1 -1
  145. package/docs/rbac/quick-start.md +52 -52
  146. package/docs/rbac/secure-client-protection.md +1 -35
  147. package/docs/rbac/troubleshooting.md +1 -1
  148. package/docs/security/README.md +5 -5
  149. package/docs/standards/0-standards-overview.md +220 -0
  150. package/docs/standards/1-pace-core-compliance-standards.md +986 -0
  151. package/docs/standards/2-project-structure-standards.md +949 -0
  152. package/docs/standards/3-architecture-standards.md +606 -0
  153. package/docs/standards/4-code-quality-standards.md +728 -0
  154. package/docs/standards/5-styling-standards.md +348 -0
  155. package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
  156. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  157. package/docs/standards/8-testing-documentation-standards.md +401 -0
  158. package/docs/standards/9-operations-standards.md +1102 -0
  159. package/docs/standards/README.md +185 -57
  160. package/docs/troubleshooting/README.md +4 -4
  161. package/docs/troubleshooting/common-issues.md +2 -2
  162. package/docs/troubleshooting/debugging.md +9 -9
  163. package/docs/troubleshooting/migration.md +4 -4
  164. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  165. package/eslint-config-pace-core.cjs +33 -6
  166. package/package.json +35 -23
  167. package/scripts/install-cursor-rules.cjs +25 -6
  168. package/scripts/install-eslint-config.cjs +284 -0
  169. package/src/__tests__/fixtures/supabase.ts +1 -1
  170. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
  171. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  172. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  173. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  174. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
  175. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  176. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  177. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  178. package/src/__tests__/public-recipe-view.test.ts +38 -9
  179. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  180. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  181. package/src/__tests__/templates/component.test.template.tsx +18 -15
  182. package/src/components/Button/Button.tsx +5 -1
  183. package/src/components/Calendar/Calendar.tsx +201 -47
  184. package/src/components/ContextSelector/ContextSelector.tsx +106 -119
  185. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  186. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  187. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  188. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  189. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  190. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  191. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  192. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  193. package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
  194. package/src/components/DataTable/components/EditFields.tsx +23 -3
  195. package/src/components/DataTable/components/EditableRow.tsx +12 -9
  196. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  197. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  198. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  199. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  200. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  201. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  202. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  203. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  204. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  205. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  206. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  207. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  208. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  209. package/src/components/DataTable/components/index.ts +2 -1
  210. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  211. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  212. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  213. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  214. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  215. package/src/components/DataTable/types.ts +5 -18
  216. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  217. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  218. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  219. package/src/components/DateTimeField/DateTimeField.tsx +10 -9
  220. package/src/components/Dialog/Dialog.test.tsx +128 -104
  221. package/src/components/Dialog/Dialog.tsx +742 -24
  222. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  223. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
  224. package/src/components/FileDisplay/FileDisplay.tsx +23 -17
  225. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  226. package/src/components/FileUpload/FileUpload.tsx +112 -130
  227. package/src/components/Form/Form.test.tsx +6 -8
  228. package/src/components/Form/Form.tsx +365 -4
  229. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  230. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  231. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  232. package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
  233. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  234. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  235. package/src/components/Progress/Progress.tsx +2 -4
  236. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  237. package/src/components/Select/Select.tsx +109 -98
  238. package/src/components/Select/types.ts +4 -1
  239. package/src/components/UserMenu/UserMenu.tsx +9 -6
  240. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  241. package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
  242. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  243. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  244. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  245. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  246. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  247. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  248. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  249. package/src/hooks/public/usePublicEvent.ts +67 -195
  250. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  251. package/src/hooks/public/usePublicEventLogo.ts +24 -14
  252. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  253. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  254. package/src/hooks/useAppConfig.ts +28 -26
  255. package/src/hooks/useEventTheme.test.ts +217 -239
  256. package/src/hooks/useEventTheme.ts +16 -28
  257. package/src/hooks/useFileDisplay.ts +2 -2
  258. package/src/hooks/useOrganisationPermissions.ts +5 -7
  259. package/src/hooks/useQueryCache.ts +0 -1
  260. package/src/hooks/useSessionDraft.ts +380 -0
  261. package/src/hooks/useSessionRestoration.ts +3 -1
  262. package/src/icons/index.ts +27 -0
  263. package/src/index.ts +5 -0
  264. package/src/providers/OrganisationProvider.tsx +23 -14
  265. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  266. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  267. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  268. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  269. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  270. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  271. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  272. package/src/providers/services/EventServiceProvider.tsx +1 -24
  273. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  274. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  275. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
  276. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  277. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  278. package/src/rbac/adapters.tsx +7 -295
  279. package/src/rbac/api.test.ts +44 -56
  280. package/src/rbac/api.ts +10 -17
  281. package/src/rbac/cache-invalidation.ts +0 -1
  282. package/src/rbac/compliance/index.ts +10 -0
  283. package/src/rbac/compliance/pattern-detector.ts +553 -0
  284. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  285. package/src/rbac/components/AccessDenied.tsx +150 -0
  286. package/src/rbac/components/NavigationGuard.tsx +12 -20
  287. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  288. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  289. package/src/rbac/components/index.ts +3 -41
  290. package/src/rbac/eslint-rules.js +1 -1
  291. package/src/rbac/hooks/index.ts +0 -3
  292. package/src/rbac/hooks/permissions/index.ts +0 -3
  293. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  294. package/src/rbac/hooks/usePermissions.ts +0 -3
  295. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  296. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  297. package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
  298. package/src/rbac/hooks/useResourcePermissions.ts +139 -48
  299. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  300. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  301. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  302. package/src/rbac/index.ts +7 -9
  303. package/src/rbac/utils/contextValidator.ts +9 -7
  304. package/src/services/AuthService.ts +130 -18
  305. package/src/services/EventService.ts +4 -97
  306. package/src/services/InactivityService.ts +16 -0
  307. package/src/services/OrganisationService.ts +7 -44
  308. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  309. package/src/services/base/BaseService.ts +0 -3
  310. package/src/styles/core.css +7 -0
  311. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  312. package/src/theming/parseEventColours.ts +22 -10
  313. package/src/types/database.generated.ts +4733 -3809
  314. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  315. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  316. package/src/utils/context/organisationContext.test.ts +13 -28
  317. package/src/utils/context/organisationContext.ts +21 -52
  318. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  319. package/src/utils/file-reference/index.ts +39 -15
  320. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  321. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  322. package/src/utils/index.ts +4 -1
  323. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  324. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  325. package/src/utils/persistence/keyDerivation.ts +304 -0
  326. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  327. package/src/utils/security/secureStorage.ts +5 -5
  328. package/src/utils/storage/README.md +1 -1
  329. package/src/utils/storage/helpers.ts +3 -3
  330. package/src/utils/supabase/createBaseClient.ts +147 -0
  331. package/src/utils/timezone/timezone.test.ts +1 -2
  332. package/src/utils/timezone/timezone.ts +1 -1
  333. package/src/utils/validation/csrf.ts +4 -4
  334. package/cursor-rules/00-pace-core-compliance.mdc +0 -331
  335. package/cursor-rules/01-standards-compliance.mdc +0 -244
  336. package/cursor-rules/04-testing-standards.mdc +0 -268
  337. package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
  338. package/cursor-rules/06-code-quality.mdc +0 -309
  339. package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
  340. package/cursor-rules/CHANGELOG.md +0 -119
  341. package/cursor-rules/README.md +0 -192
  342. package/dist/DataTable-AOVNCPTX.js +0 -175
  343. package/dist/DataTable-AOVNCPTX.js.map +0 -1
  344. package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
  345. package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
  346. package/dist/api-O6HTBX5Y.js +0 -52
  347. package/dist/api-O6HTBX5Y.js.map +0 -1
  348. package/dist/audit-V53FV5AG.js +0 -17
  349. package/dist/audit-V53FV5AG.js.map +0 -1
  350. package/dist/chunk-5DRSZLL2.js.map +0 -1
  351. package/dist/chunk-63FOKYGO.js.map +0 -1
  352. package/dist/chunk-6COVEUS7.js.map +0 -1
  353. package/dist/chunk-AFVQODI2.js +0 -263
  354. package/dist/chunk-AFVQODI2.js.map +0 -1
  355. package/dist/chunk-DGUM43GV.js.map +0 -1
  356. package/dist/chunk-E66EQZE6.js.map +0 -1
  357. package/dist/chunk-EFN2EIMK.js.map +0 -1
  358. package/dist/chunk-FFQEQTNW.js.map +0 -1
  359. package/dist/chunk-FMUCXFII.js.map +0 -1
  360. package/dist/chunk-G37KK66H.js.map +0 -1
  361. package/dist/chunk-G7QEZTYQ.js +0 -2053
  362. package/dist/chunk-G7QEZTYQ.js.map +0 -1
  363. package/dist/chunk-HU2C6SSC.js.map +0 -1
  364. package/dist/chunk-IHB5DR3H.js.map +0 -1
  365. package/dist/chunk-IVOFDYWT.js.map +0 -1
  366. package/dist/chunk-J36DSWQK.js.map +0 -1
  367. package/dist/chunk-JGRYX5UX.js.map +0 -1
  368. package/dist/chunk-KQCRWDSA.js +0 -1
  369. package/dist/chunk-KQCRWDSA.js.map +0 -1
  370. package/dist/chunk-L4OXEN46.js.map +0 -1
  371. package/dist/chunk-LMC26NLJ.js +0 -84
  372. package/dist/chunk-LMC26NLJ.js.map +0 -1
  373. package/dist/chunk-M43Y4SSO.js.map +0 -1
  374. package/dist/chunk-M7MPQISP.js.map +0 -1
  375. package/dist/chunk-NTM7ZSB6.js.map +0 -1
  376. package/dist/chunk-PWLANIRT.js.map +0 -1
  377. package/dist/chunk-QXHPKYJV.js.map +0 -1
  378. package/dist/chunk-RGAWHO7N.js.map +0 -1
  379. package/dist/chunk-UPPMRMYG.js.map +0 -1
  380. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  381. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  382. package/dist/components.js.map +0 -1
  383. package/dist/contextValidator-5OGXSPKS.js +0 -9
  384. package/dist/contextValidator-5OGXSPKS.js.map +0 -1
  385. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  386. package/dist/hooks.js.map +0 -1
  387. package/dist/index.js.map +0 -1
  388. package/dist/providers.js.map +0 -1
  389. package/dist/rbac/eslint-rules.js.map +0 -1
  390. package/dist/rbac/index.js.map +0 -1
  391. package/dist/styles/index.js.map +0 -1
  392. package/dist/theming/runtime.js.map +0 -1
  393. package/dist/types.js.map +0 -1
  394. package/dist/utils.js.map +0 -1
  395. package/docs/best-practices/README.md +0 -472
  396. package/docs/best-practices/accessibility.md +0 -601
  397. package/docs/best-practices/common-patterns.md +0 -516
  398. package/docs/best-practices/deployment.md +0 -1103
  399. package/docs/best-practices/performance.md +0 -1328
  400. package/docs/best-practices/security.md +0 -940
  401. package/docs/best-practices/testing.md +0 -1034
  402. package/docs/rbac/compliance/compliance-guide.md +0 -544
  403. package/docs/standards/01-architecture-standard.md +0 -44
  404. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  405. package/docs/standards/03-component-standard.md +0 -32
  406. package/docs/standards/04-code-style-standard.md +0 -32
  407. package/docs/standards/05-security-standard.md +0 -44
  408. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  409. package/docs/standards/pace-core-compliance.md +0 -432
  410. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  411. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  412. package/scripts/audit/core/checks/bundle.cjs +0 -142
  413. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  414. package/scripts/audit/core/checks/config.cjs +0 -54
  415. package/scripts/audit/core/checks/coverage.cjs +0 -84
  416. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  417. package/scripts/audit/core/checks/documentation.cjs +0 -268
  418. package/scripts/audit/core/checks/environment.cjs +0 -116
  419. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  420. package/scripts/audit/core/checks/forms.cjs +0 -172
  421. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  422. package/scripts/audit/core/checks/hooks.cjs +0 -334
  423. package/scripts/audit/core/checks/imports.cjs +0 -244
  424. package/scripts/audit/core/checks/performance.cjs +0 -325
  425. package/scripts/audit/core/checks/routes.cjs +0 -117
  426. package/scripts/audit/core/checks/state.cjs +0 -130
  427. package/scripts/audit/core/checks/structure.cjs +0 -65
  428. package/scripts/audit/core/checks/style.cjs +0 -584
  429. package/scripts/audit/core/checks/testing.cjs +0 -122
  430. package/scripts/audit/core/checks/typescript.cjs +0 -61
  431. package/scripts/audit/core/scanner.cjs +0 -199
  432. package/scripts/audit/core/utils.cjs +0 -137
  433. package/scripts/audit/index.cjs +0 -223
  434. package/scripts/audit/reporters/console.cjs +0 -151
  435. package/scripts/audit/reporters/json.cjs +0 -54
  436. package/scripts/audit/reporters/markdown.cjs +0 -124
  437. package/scripts/audit-consuming-app.cjs +0 -86
  438. package/src/components/DataTable/components/DataTableBody.tsx +0 -454
  439. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  440. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  441. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  442. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  443. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  444. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  445. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  446. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  447. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  448. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  449. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  450. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  451. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  452. package/src/components/DataTable/utils/debugTools.ts +0 -514
  453. package/src/eslint-rules/pace-core-compliance.js +0 -638
  454. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  455. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  456. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  457. package/src/rbac/components/NavigationProvider.tsx +0 -345
  458. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  459. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  460. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  461. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  462. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  463. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  464. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  465. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  466. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  467. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  468. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  469. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  470. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  471. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  472. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  473. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -14,42 +14,23 @@ vi.mock('../../components/PublicLayout/PublicPageProvider', () => ({
14
14
  }))
15
15
  }));
16
16
 
17
- // Mock Supabase client
18
- const mockSupabaseClient = {
19
- rpc: vi.fn(),
20
- from: vi.fn(() => ({
21
- select: vi.fn(() => ({
22
- eq: vi.fn(() => ({
23
- eq: vi.fn(() => ({
24
- not: vi.fn(() => ({
25
- limit: vi.fn(() => ({
26
- single: vi.fn()
27
- }))
28
- }))
29
- }))
30
- }))
31
- }))
32
- }))
33
- };
34
-
35
- // Helper to create table query mocks
36
- const createTableQueryMock = () => ({
37
- data: null,
38
- error: null,
39
- mockResolvedValueOnce: vi.fn(),
40
- mockResolvedValue: vi.fn()
41
- });
42
-
43
- // Helper to create a complete table query chain
44
- const createTableQueryChain = (finalResult: any) => {
45
- const mockChain = {
17
+ // Mock Supabase client with proper query builder chain
18
+ // usePublicEvent uses: .from('core_events').select(...).eq(...).eq(...).not(...).limit(1).single()
19
+ const createMockQueryBuilder = (mockData: any = null, mockError: any = null) => {
20
+ const builder: any = {
46
21
  select: vi.fn().mockReturnThis(),
47
22
  eq: vi.fn().mockReturnThis(),
48
23
  not: vi.fn().mockReturnThis(),
49
24
  limit: vi.fn().mockReturnThis(),
50
- single: vi.fn().mockResolvedValue(finalResult)
25
+ single: vi.fn().mockResolvedValue({ data: mockData, error: mockError }),
26
+ maybeSingle: vi.fn().mockResolvedValue({ data: mockData, error: mockError })
51
27
  };
52
- return mockChain;
28
+ return builder;
29
+ };
30
+
31
+ const mockSupabaseClient = {
32
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
33
+ from: vi.fn(() => createMockQueryBuilder())
53
34
  };
54
35
 
55
36
  // Mock createClient
@@ -110,7 +91,7 @@ describe('usePublicEvent', () => {
110
91
  expect(result.current.error).toBe(null);
111
92
  });
112
93
 
113
- it('should fetch event data successfully via RPC', async () => {
94
+ it('should fetch event data successfully via table query', async () => {
114
95
  const mockEventData = {
115
96
  event_id: '123',
116
97
  event_name: 'Test Event',
@@ -131,16 +112,24 @@ describe('usePublicEvent', () => {
131
112
  event_logo: null
132
113
  };
133
114
 
134
- mockSupabaseClient.rpc.mockResolvedValueOnce({
135
- data: [mockEventData],
136
- error: null
115
+ // usePublicEvent makes TWO queries: core_events + core_file_references (logo)
116
+ const mockEventQueryBuilder = createMockQueryBuilder({
117
+ ...mockEventData,
118
+ event_code: 'test-event',
119
+ is_visible: true,
120
+ created_at: '2024-01-01T00:00:00Z',
121
+ updated_at: '2024-01-01T00:00:00Z'
137
122
  });
123
+ const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
124
+ mockSupabaseClient.from
125
+ .mockReturnValueOnce(mockEventQueryBuilder)
126
+ .mockReturnValueOnce(mockLogoQueryBuilder);
138
127
 
139
128
  const { result } = renderHook(() => usePublicEvent('test-event'));
140
129
 
141
130
  await waitFor(() => {
142
131
  expect(result.current.isLoading).toBe(false);
143
- }, { interval: 10 });
132
+ }, { interval: 10, timeout: 2000 });
144
133
 
145
134
  expect(result.current.event).toEqual({
146
135
  id: '123',
@@ -210,22 +199,30 @@ describe('usePublicEvent', () => {
210
199
  event_email: 'event@example.com'
211
200
  };
212
201
 
213
- mockSupabaseClient.rpc.mockResolvedValueOnce({
214
- data: [mockEventData],
215
- error: null
202
+ // usePublicEvent makes TWO queries: core_events + core_file_references (logo)
203
+ const mockEventQueryBuilder = createMockQueryBuilder({
204
+ ...mockEventData,
205
+ event_code: 'test-event',
206
+ is_visible: true,
207
+ created_at: '2024-01-01T00:00:00Z',
208
+ updated_at: '2024-01-01T00:00:00Z'
216
209
  });
210
+ const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
211
+ mockSupabaseClient.from
212
+ .mockReturnValueOnce(mockEventQueryBuilder)
213
+ .mockReturnValueOnce(mockLogoQueryBuilder);
217
214
 
218
215
  const { result, rerender } = renderHook(() => usePublicEvent('test-event'));
219
216
 
220
217
  await waitFor(() => {
221
218
  expect(result.current.isLoading).toBe(false);
222
- }, { interval: 10 });
219
+ }, { interval: 10, timeout: 2000 });
223
220
 
224
- // Rerender with same event code - should use cache
221
+ // Rerender with same event code - should use cache (no new queries)
225
222
  rerender();
226
223
 
227
- // Should not call RPC again
228
- expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(1);
224
+ // Should not call query again (cached) - only initial fetch: 2 calls (event + logo)
225
+ expect(mockSupabaseClient.from).toHaveBeenCalledTimes(2);
229
226
  });
230
227
 
231
228
  it('should respect cache TTL', async () => {
@@ -248,28 +245,40 @@ describe('usePublicEvent', () => {
248
245
  event_email: 'event@example.com'
249
246
  };
250
247
 
251
- mockSupabaseClient.rpc.mockResolvedValueOnce({
252
- data: [mockEventData],
253
- error: null
248
+ // usePublicEvent makes TWO queries: core_events + core_file_references (logo)
249
+ const mockEventQueryBuilder = createMockQueryBuilder({
250
+ ...mockEventData,
251
+ event_code: 'test-event',
252
+ is_visible: true,
253
+ created_at: '2024-01-01T00:00:00Z',
254
+ updated_at: '2024-01-01T00:00:00Z'
254
255
  });
256
+ const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
257
+ // Need mocks for both hook instances: initial (2 calls) + after cache expires (2 calls) = 4 total
258
+ mockSupabaseClient.from
259
+ .mockReturnValueOnce(mockEventQueryBuilder) // First hook: event
260
+ .mockReturnValueOnce(mockLogoQueryBuilder) // First hook: logo
261
+ .mockReturnValueOnce(mockEventQueryBuilder) // Second hook (after cache expires): event
262
+ .mockReturnValueOnce(mockLogoQueryBuilder); // Second hook (after cache expires): logo
255
263
 
256
264
  const { result } = renderHook(() => usePublicEvent('test-event', { cacheTtl: 100 }));
257
265
 
258
266
  await waitFor(() => {
259
267
  expect(result.current.isLoading).toBe(false);
260
- }, { interval: 10 });
268
+ }, { interval: 10, timeout: 2000 });
261
269
 
262
270
  // Wait for cache to expire
263
271
  await new Promise(resolve => setTimeout(resolve, 150));
264
272
 
265
- // Rerender should fetch again
273
+ // New hook instance should fetch again (cache expired)
266
274
  const { result: result2 } = renderHook(() => usePublicEvent('test-event', { cacheTtl: 100 }));
267
275
 
268
276
  await waitFor(() => {
269
277
  expect(result2.current.isLoading).toBe(false);
270
- }, { interval: 10 });
278
+ }, { interval: 10, timeout: 2000 });
271
279
 
272
- expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(2);
280
+ // Each fetch makes 2 calls (event + logo), so 2 fetches = 4 calls
281
+ expect(mockSupabaseClient.from).toHaveBeenCalledTimes(4);
273
282
  });
274
283
 
275
284
  it('should disable caching when requested', async () => {
@@ -297,14 +306,33 @@ describe('usePublicEvent', () => {
297
306
  const { clearPublicEventCache } = await import('../public/usePublicEvent');
298
307
  clearPublicEventCache();
299
308
 
300
- // Use mockImplementation to handle multiple calls
309
+ // usePublicEvent makes TWO queries per fetch: core_events + core_file_references (logo)
301
310
  let callCount = 0;
302
- mockSupabaseClient.rpc.mockImplementation(() => {
311
+ const mockEventQueryBuilder1 = createMockQueryBuilder({
312
+ ...mockEventData,
313
+ event_code: 'test-event',
314
+ is_visible: true,
315
+ created_at: '2024-01-01T00:00:00Z',
316
+ updated_at: '2024-01-01T00:00:00Z'
317
+ });
318
+ const mockLogoQueryBuilder1 = createMockQueryBuilder(null, null);
319
+ const mockEventQueryBuilder2 = createMockQueryBuilder({
320
+ ...mockEventData,
321
+ event_code: 'test-event-2',
322
+ is_visible: true,
323
+ created_at: '2024-01-01T00:00:00Z',
324
+ updated_at: '2024-01-01T00:00:00Z'
325
+ });
326
+ const mockLogoQueryBuilder2 = createMockQueryBuilder(null, null);
327
+
328
+ mockSupabaseClient.from.mockImplementation(() => {
303
329
  callCount++;
304
- return Promise.resolve({
305
- data: [mockEventData],
306
- error: null
307
- });
330
+ // First fetch: event1 + logo1, Second fetch: event2 + logo2
331
+ if (callCount <= 2) {
332
+ return callCount === 1 ? mockEventQueryBuilder1 : mockLogoQueryBuilder1;
333
+ } else {
334
+ return callCount === 3 ? mockEventQueryBuilder2 : mockLogoQueryBuilder2;
335
+ }
308
336
  });
309
337
 
310
338
  const { result, rerender } = renderHook(
@@ -314,44 +342,36 @@ describe('usePublicEvent', () => {
314
342
 
315
343
  await waitFor(() => {
316
344
  expect(result.current.isLoading).toBe(false);
317
- }, { interval: 10 });
345
+ }, { interval: 10, timeout: 2000 });
318
346
 
319
347
  // Change the event code to force a new fetch
320
348
  rerender({ eventCode: 'test-event-2' });
321
349
 
322
350
  await waitFor(() => {
323
351
  expect(result.current.isLoading).toBe(false);
324
- }, { interval: 10 });
352
+ }, { interval: 10, timeout: 2000 });
325
353
 
326
- expect(callCount).toBe(2);
354
+ // Each fetch makes 2 calls (event + logo), so 2 fetches = 4 calls
355
+ expect(callCount).toBe(4);
327
356
  });
328
357
  });
329
358
 
330
359
  describe('Error Handling', () => {
331
- it('should handle RPC errors', async () => {
332
- // Mock the RPC call to return an error object
360
+ it('should handle query errors', async () => {
361
+ // usePublicEvent uses table query, not RPC
333
362
  const testError = { message: 'Database error', details: 'Test error details', hint: null, code: 'TEST_ERROR' };
334
- mockSupabaseClient.rpc.mockImplementation(() =>
335
- Promise.resolve({
336
- data: null,
337
- error: testError
338
- })
339
- );
363
+ const mockQueryBuilder = createMockQueryBuilder(null, testError);
364
+ mockSupabaseClient.from.mockReturnValue(mockQueryBuilder);
340
365
 
341
366
  const { result } = renderHook(() => usePublicEvent('test-event'));
342
367
 
343
368
  await waitFor(() => {
344
369
  expect(result.current.isLoading).toBe(false);
345
- }, { interval: 10 });
370
+ }, { interval: 10, timeout: 2000 });
346
371
 
347
372
  expect(result.current.error).toBeInstanceOf(Error);
348
373
  expect(result.current.error?.message).toBe('Database error');
349
374
  expect(result.current.event).toBe(null);
350
-
351
- // Verify the mock was called
352
- expect(mockSupabaseClient.rpc).toHaveBeenCalledWith('get_public_event_by_code', {
353
- event_code_param: 'test-event'
354
- });
355
375
  });
356
376
 
357
377
  it('should handle missing Supabase client', async () => {
@@ -411,16 +431,24 @@ describe('usePublicEvent', () => {
411
431
  event_logo: null
412
432
  };
413
433
 
414
- mockSupabaseClient.rpc.mockResolvedValueOnce({
415
- data: [mockEventData],
416
- error: null
434
+ // usePublicEvent makes TWO queries: core_events + core_file_references (logo)
435
+ const mockEventQueryBuilder = createMockQueryBuilder({
436
+ ...mockEventData,
437
+ event_code: 'test-event',
438
+ is_visible: true,
439
+ created_at: '2024-01-01T00:00:00Z',
440
+ updated_at: '2024-01-01T00:00:00Z'
417
441
  });
442
+ const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
443
+ mockSupabaseClient.from
444
+ .mockReturnValueOnce(mockEventQueryBuilder)
445
+ .mockReturnValueOnce(mockLogoQueryBuilder);
418
446
 
419
447
  const { result } = renderHook(() => usePublicEvent('test-event'));
420
448
 
421
449
  await waitFor(() => {
422
450
  expect(result.current.isLoading).toBe(false);
423
- }, { interval: 10 });
451
+ }, { interval: 10, timeout: 2000 });
424
452
 
425
453
  expect(result.current.event).toBeTruthy();
426
454
  });
@@ -447,23 +475,35 @@ describe('usePublicEvent', () => {
447
475
  event_email: 'event@example.com'
448
476
  };
449
477
 
450
- mockSupabaseClient.rpc.mockResolvedValue({
451
- data: [mockEventData],
452
- error: null
478
+ // usePublicEvent makes TWO queries: core_events + core_file_references (logo)
479
+ const mockEventQueryBuilder = createMockQueryBuilder({
480
+ ...mockEventData,
481
+ event_code: 'test-event',
482
+ is_visible: true,
483
+ created_at: '2024-01-01T00:00:00Z',
484
+ updated_at: '2024-01-01T00:00:00Z'
453
485
  });
486
+ const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
487
+ // Need mocks for initial fetch (2 calls) + refetch (2 calls) = 4 total
488
+ mockSupabaseClient.from
489
+ .mockReturnValueOnce(mockEventQueryBuilder) // Initial: event
490
+ .mockReturnValueOnce(mockLogoQueryBuilder) // Initial: logo
491
+ .mockReturnValueOnce(mockEventQueryBuilder) // Refetch: event
492
+ .mockReturnValueOnce(mockLogoQueryBuilder); // Refetch: logo
454
493
 
455
494
  const { result } = renderHook(() => usePublicEvent('test-event'));
456
495
 
457
496
  await waitFor(() => {
458
497
  expect(result.current.isLoading).toBe(false);
459
- }, { interval: 10 });
498
+ }, { interval: 10, timeout: 2000 });
460
499
 
461
500
  // Call refetch
462
501
  await act(async () => {
463
502
  await result.current.refetch();
464
503
  });
465
504
 
466
- expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(2);
505
+ // Each fetch makes 2 calls (event + logo), so 2 fetches = 4 calls
506
+ expect(mockSupabaseClient.from).toHaveBeenCalledTimes(4);
467
507
  });
468
508
 
469
509
  it('should clear cache when refetch is called', async () => {
@@ -486,24 +526,36 @@ describe('usePublicEvent', () => {
486
526
  event_email: 'event@example.com'
487
527
  };
488
528
 
489
- mockSupabaseClient.rpc.mockResolvedValue({
490
- data: [mockEventData],
491
- error: null
529
+ // usePublicEvent makes TWO queries: core_events + core_file_references (logo)
530
+ const mockEventQueryBuilder = createMockQueryBuilder({
531
+ ...mockEventData,
532
+ event_code: 'test-event',
533
+ is_visible: true,
534
+ created_at: '2024-01-01T00:00:00Z',
535
+ updated_at: '2024-01-01T00:00:00Z'
492
536
  });
537
+ const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
538
+ // Need mocks for initial fetch (2 calls) + refetch (2 calls) = 4 total
539
+ mockSupabaseClient.from
540
+ .mockReturnValueOnce(mockEventQueryBuilder) // Initial: event
541
+ .mockReturnValueOnce(mockLogoQueryBuilder) // Initial: logo
542
+ .mockReturnValueOnce(mockEventQueryBuilder) // Refetch: event
543
+ .mockReturnValueOnce(mockLogoQueryBuilder); // Refetch: logo
493
544
 
494
545
  const { result } = renderHook(() => usePublicEvent('test-event'));
495
546
 
496
547
  await waitFor(() => {
497
548
  expect(result.current.isLoading).toBe(false);
498
- }, { interval: 10 });
549
+ }, { interval: 10, timeout: 2000 });
499
550
 
500
551
  // Call refetch
501
552
  await act(async () => {
502
553
  await result.current.refetch();
503
554
  });
504
555
 
505
- // Should call RPC again (cache was cleared)
506
- expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(2);
556
+ // Should call query again (cache was cleared)
557
+ // Each fetch makes 2 calls (event + logo), so 2 fetches = 4 calls
558
+ expect(mockSupabaseClient.from).toHaveBeenCalledTimes(4);
507
559
  });
508
560
  });
509
561
 
@@ -530,52 +582,49 @@ describe('usePublicEvent', () => {
530
582
  });
531
583
 
532
584
  describe('Edge Cases', () => {
533
- it('should handle null event data from RPC', async () => {
534
- mockSupabaseClient.rpc.mockResolvedValueOnce({
535
- data: null,
536
- error: null
537
- });
585
+ it('should handle null event data from query', async () => {
586
+ // usePublicEvent uses table query, not RPC
587
+ const mockQueryBuilder = createMockQueryBuilder(null, null);
588
+ mockSupabaseClient.from.mockReturnValue(mockQueryBuilder);
538
589
 
539
590
  const { result } = renderHook(() => usePublicEvent('test-event'));
540
591
 
541
592
  await waitFor(() => {
542
593
  expect(result.current.isLoading).toBe(false);
543
- }, { interval: 10 });
594
+ }, { interval: 10, timeout: 2000 });
544
595
 
545
596
  expect(result.current.event).toBe(null);
546
- expect(result.current.error).toEqual(new Error('Event not found'));
597
+ expect(result.current.error?.message).toBe('Event not found');
547
598
  });
548
599
 
549
- it('should handle empty event data array from RPC', async () => {
550
- mockSupabaseClient.rpc.mockResolvedValueOnce({
551
- data: [],
552
- error: null
553
- });
600
+ it('should handle query returning null (event not found)', async () => {
601
+ // usePublicEvent uses table query, not RPC
602
+ const mockQueryBuilder = createMockQueryBuilder(null, { code: 'PGRST116', message: 'No rows found' });
603
+ mockSupabaseClient.from.mockReturnValue(mockQueryBuilder);
554
604
 
555
605
  const { result } = renderHook(() => usePublicEvent('test-event'));
556
606
 
557
607
  await waitFor(() => {
558
608
  expect(result.current.isLoading).toBe(false);
559
- }, { interval: 10 });
609
+ }, { interval: 10, timeout: 2000 });
560
610
 
561
611
  expect(result.current.event).toBe(null);
562
- expect(result.current.error).toEqual(new Error('Event not found'));
612
+ expect(result.current.error?.message).toBe('Event not found');
563
613
  });
564
614
 
565
- it('should handle undefined event data from RPC', async () => {
566
- mockSupabaseClient.rpc.mockResolvedValueOnce({
567
- data: [undefined],
568
- error: null
569
- });
615
+ it('should handle undefined event data from query', async () => {
616
+ // usePublicEvent uses table query, not RPC
617
+ const mockQueryBuilder = createMockQueryBuilder(null, null);
618
+ mockSupabaseClient.from.mockReturnValue(mockQueryBuilder);
570
619
 
571
620
  const { result } = renderHook(() => usePublicEvent('test-event'));
572
621
 
573
622
  await waitFor(() => {
574
623
  expect(result.current.isLoading).toBe(false);
575
- }, { interval: 10 });
624
+ }, { interval: 10, timeout: 2000 });
576
625
 
577
626
  expect(result.current.event).toBe(null);
578
- expect(result.current.error).toEqual(new Error('Event not found'));
627
+ expect(result.current.error?.message).toBe('Event not found');
579
628
  });
580
629
  });
581
630
  });
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @file useSessionDraft Hook Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/__tests__
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
8
+ import { renderHook, act } from '@testing-library/react';
9
+ import { useSessionDraft } from '../useSessionDraft';
10
+
11
+ describe('useSessionDraft', () => {
12
+ beforeEach(() => {
13
+ // Clear sessionStorage before each test
14
+ sessionStorage.clear();
15
+ });
16
+
17
+ afterEach(() => {
18
+ // Clear sessionStorage after each test
19
+ sessionStorage.clear();
20
+ });
21
+
22
+ it('should initialize with initialState when no draft exists', () => {
23
+ const { result } = renderHook(() =>
24
+ useSessionDraft('test-key', { value: 'initial' })
25
+ );
26
+
27
+ expect(result.current.state).toEqual({ value: 'initial' });
28
+ expect(result.current.wasRestored).toBe(false);
29
+ });
30
+
31
+ it('should restore state from sessionStorage on mount', () => {
32
+ // Set up initial draft
33
+ const draft = {
34
+ version: 1,
35
+ data: { value: 'restored' },
36
+ timestamp: Date.now(),
37
+ };
38
+ sessionStorage.setItem('pace-core:draft:test-key', JSON.stringify(draft));
39
+
40
+ const { result } = renderHook(() =>
41
+ useSessionDraft('test-key', { value: 'initial' })
42
+ );
43
+
44
+ expect(result.current.state).toEqual({ value: 'restored' });
45
+ expect(result.current.wasRestored).toBe(true);
46
+ });
47
+
48
+ it('should persist state changes with debouncing', async () => {
49
+ vi.useFakeTimers();
50
+
51
+ const { result } = renderHook(() =>
52
+ useSessionDraft('test-key', { value: 'initial' })
53
+ );
54
+
55
+ act(() => {
56
+ result.current.setState({ value: 'updated' });
57
+ });
58
+
59
+ // State should update immediately
60
+ expect(result.current.state).toEqual({ value: 'updated' });
61
+
62
+ // But storage should not be updated yet (debounced)
63
+ expect(sessionStorage.getItem('pace-core:draft:test-key')).toBeNull();
64
+
65
+ // Fast-forward past debounce delay
66
+ await act(async () => {
67
+ vi.advanceTimersByTime(300);
68
+ });
69
+
70
+ // Now storage should be updated
71
+ const stored = sessionStorage.getItem('pace-core:draft:test-key');
72
+ expect(stored).not.toBeNull();
73
+ if (stored) {
74
+ const parsed = JSON.parse(stored);
75
+ expect(parsed.data).toEqual({ value: 'updated' });
76
+ }
77
+
78
+ vi.useRealTimers();
79
+ });
80
+
81
+ it('should clear draft when clearDraft is called', () => {
82
+ const { result } = renderHook(() =>
83
+ useSessionDraft('test-key', { value: 'initial' })
84
+ );
85
+
86
+ act(() => {
87
+ result.current.setState({ value: 'updated' });
88
+ result.current.saveImmediately();
89
+ });
90
+
91
+ expect(sessionStorage.getItem('pace-core:draft:test-key')).not.toBeNull();
92
+
93
+ act(() => {
94
+ result.current.clearDraft();
95
+ });
96
+
97
+ expect(sessionStorage.getItem('pace-core:draft:test-key')).toBeNull();
98
+ expect(result.current.state).toEqual({ value: 'initial' });
99
+ });
100
+
101
+ it('should handle storage quota errors gracefully', () => {
102
+ // Mock sessionStorage.setItem to throw quota error
103
+ const originalSetItem = sessionStorage.setItem;
104
+ sessionStorage.setItem = vi.fn(() => {
105
+ const error = new DOMException('QuotaExceededError');
106
+ error.name = 'QuotaExceededError';
107
+ throw error;
108
+ });
109
+
110
+ const { result } = renderHook(() =>
111
+ useSessionDraft('test-key', { value: 'initial' })
112
+ );
113
+
114
+ // Should not throw, just fail silently
115
+ expect(() => {
116
+ act(() => {
117
+ result.current.setState({ value: 'updated' });
118
+ result.current.saveImmediately();
119
+ });
120
+ }).not.toThrow();
121
+
122
+ // Restore original
123
+ sessionStorage.setItem = originalSetItem;
124
+ });
125
+
126
+ it('should handle corrupted stored data gracefully', () => {
127
+ // Set corrupted data
128
+ sessionStorage.setItem('pace-core:draft:test-key', 'invalid json');
129
+
130
+ const { result } = renderHook(() =>
131
+ useSessionDraft('test-key', { value: 'initial' })
132
+ );
133
+
134
+ // Should fall back to initial state
135
+ expect(result.current.state).toEqual({ value: 'initial' });
136
+ expect(result.current.wasRestored).toBe(false);
137
+
138
+ // Corrupted data should be cleared
139
+ expect(sessionStorage.getItem('pace-core:draft:test-key')).toBeNull();
140
+ });
141
+
142
+ it('should reject drafts with mismatched schema version', () => {
143
+ // Set draft with wrong version
144
+ const draft = {
145
+ version: 999, // Wrong version
146
+ data: { value: 'old' },
147
+ timestamp: Date.now(),
148
+ };
149
+ sessionStorage.setItem('pace-core:draft:test-key', JSON.stringify(draft));
150
+
151
+ const { result } = renderHook(() =>
152
+ useSessionDraft('test-key', { value: 'initial' })
153
+ );
154
+
155
+ // Should fall back to initial state
156
+ expect(result.current.state).toEqual({ value: 'initial' });
157
+ expect(result.current.wasRestored).toBe(false);
158
+
159
+ // Old draft should be cleared
160
+ expect(sessionStorage.getItem('pace-core:draft:test-key')).toBeNull();
161
+ });
162
+ });
163
+
@@ -28,7 +28,8 @@ vi.mock('../../utils/core/logger', () => {
28
28
  import { createLogger } from '../../utils/core/logger';
29
29
  const getMockLogger = () => createLogger('test');
30
30
 
31
- const SESSION_RESTORATION_TIMEOUT_MS = 5000;
31
+ // Match the actual timeout value from useSessionRestoration.ts
32
+ const SESSION_RESTORATION_TIMEOUT_MS = 10000; // 10 seconds (matches implementation)
32
33
 
33
34
  describe('useSessionRestoration', () => {
34
35
  let mockContext: { sessionRestoration: SessionRestorationState };
@@ -136,9 +137,11 @@ describe('useSessionRestoration', () => {
136
137
  // Advance time past timeout
137
138
  await act(async () => {
138
139
  vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS + 100);
140
+ // Wait for state update after timeout
141
+ await vi.runAllTimersAsync();
139
142
  });
140
143
 
141
- // With fake timers, the timeout should fire immediately
144
+ // With fake timers, the timeout should fire after advancing
142
145
  expect(result.current.hasTimedOut).toBe(true);
143
146
 
144
147
  const logger = getMockLogger();
@@ -310,7 +313,7 @@ describe('useSessionRestoration', () => {
310
313
  expect(result.current.isRestoring).toBe(true);
311
314
  });
312
315
 
313
- it('resets hasTimedOut when restoration state changes', () => {
316
+ it('resets hasTimedOut when restoration state changes', async () => {
314
317
  mockContext.sessionRestoration = {
315
318
  isRestoring: true,
316
319
  restorationComplete: false,
@@ -321,11 +324,13 @@ describe('useSessionRestoration', () => {
321
324
  const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
322
325
 
323
326
  // Trigger timeout - advance timers
324
- act(() => {
327
+ await act(async () => {
325
328
  vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS + 100);
329
+ // Wait for state update after timeout
330
+ await vi.runAllTimersAsync();
326
331
  });
327
332
 
328
- // Verify timeout occurred (with fake timers, setTimeout fires immediately)
333
+ // Verify timeout occurred (with fake timers, setTimeout fires after advancing)
329
334
  expect(result.current.hasTimedOut).toBe(true);
330
335
 
331
336
  // Reset restoration state