@jmruthers/pace-core 0.6.4 → 0.6.6

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 (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -0,0 +1,462 @@
1
+ ---
2
+ description: Enforce RBAC contract compliance - strict rules for permission checking, page protection, and RBAC usage patterns
3
+ globs: ["src/**/*.{ts,tsx,js,jsx}"]
4
+ alwaysApply: false
5
+ paceCoreVersion: "0.6.x"
6
+ rulesVersion: "2025-01-28"
7
+ ---
8
+ # RBAC Compliance Guide
9
+
10
+ **📚 Human-Readable Standard**: See [09-rbac-compliance.md](../../packages/core/docs/standards/09-rbac-compliance.md) for complete documentation including RLS policy patterns, helper functions, and security requirements.
11
+
12
+ This guide enforces the **mandatory RBAC contract** between pace-core and consuming apps. These rules are **enforced by ESLint** and violations will result in build errors.
13
+
14
+ **⚠️ CRITICAL**: This contract is **non-negotiable**. See [RBAC Contract](../../packages/core/docs/rbac/RBAC_CONTRACT.md) for complete documentation.
15
+
16
+ ## MUST: Use PagePermissionGuard for All Protected Pages
17
+
18
+ **MUST wrap all protected pages with `PagePermissionGuard`:**
19
+
20
+ ```tsx
21
+ // ✅ CORRECT
22
+ import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';
23
+
24
+ function DashboardPage() {
25
+ return (
26
+ <PagePermissionGuard pageName="dashboard" operation="read">
27
+ <DashboardContent />
28
+ </PagePermissionGuard>
29
+ );
30
+ }
31
+ ```
32
+
33
+ **MUST NOT:**
34
+ - Render protected content without `PagePermissionGuard`
35
+ - Create wrapper components around `PagePermissionGuard`
36
+ - Bypass page-level permission checks
37
+
38
+ ```tsx
39
+ // ❌ FORBIDDEN - No guard
40
+ function DashboardPage() {
41
+ return <DashboardContent />; // ERROR: Unprotected page
42
+ }
43
+
44
+ // ❌ FORBIDDEN - Wrapper component
45
+ function EventPageGuard({ pageName, children }) {
46
+ return <PagePermissionGuard pageName={pageName}>{children}</PagePermissionGuard>;
47
+ }
48
+ ```
49
+
50
+ ## MUST: Use pace-core Permission Hooks Directly
51
+
52
+ **MUST use pace-core hooks directly without wrappers:**
53
+
54
+ ```tsx
55
+ // ✅ CORRECT
56
+ import { useCan, useResourcePermissions } from '@jmruthers/pace-core/rbac';
57
+ import { RESOURCE_NAMES } from '@/config/resource-names';
58
+
59
+ function MyComponent() {
60
+ const { canUpdate, canDelete } = useResourcePermissions(RESOURCE_NAMES.JOURNAL);
61
+ const canEdit = useCan(userId, scope, 'update:users');
62
+ // Use canUpdate, canDelete, canEdit directly
63
+ }
64
+ ```
65
+
66
+ **MUST NOT:**
67
+ - Create wrapper functions around permission hooks
68
+ - Create custom permission utility functions
69
+ - Use string literals in `useResourcePermissions` calls
70
+
71
+ ```tsx
72
+ // ❌ FORBIDDEN - Wrapper function
73
+ const canEdit = (postId: string) => {
74
+ const hasPermission = canUpdate('journal');
75
+ const post = posts.find(p => p.id === postId);
76
+ return hasPermission && !!post;
77
+ };
78
+
79
+ // ❌ FORBIDDEN - Custom utility
80
+ function checkPermission(permission: string) {
81
+ // Custom logic...
82
+ }
83
+
84
+ // ❌ FORBIDDEN - String literal
85
+ const { canUpdate } = useResourcePermissions('journal');
86
+ ```
87
+
88
+ ## MUST: Use RESOURCE_NAMES Constants
89
+
90
+ **MUST use `RESOURCE_NAMES` constant object for all resource permission checks:**
91
+
92
+ ```tsx
93
+ // ✅ CORRECT
94
+ import { RESOURCE_NAMES } from '@/config/resource-names';
95
+ const { canCreate, canUpdate, canDelete } = useResourcePermissions(RESOURCE_NAMES.JOURNAL);
96
+ ```
97
+
98
+ **MUST NOT:**
99
+ - Use string literals in `useResourcePermissions` calls
100
+ - Hardcode resource names
101
+
102
+ ```tsx
103
+ // ❌ FORBIDDEN - String literal
104
+ const { canUpdate } = useResourcePermissions('journal');
105
+ ```
106
+
107
+ ## MUST: Use Standard AccessDenied Component
108
+
109
+ **MUST use `AccessDenied` from pace-core for all access denied scenarios:**
110
+
111
+ ```tsx
112
+ // ✅ CORRECT
113
+ import { AccessDenied } from '@jmruthers/pace-core/rbac';
114
+
115
+ <PagePermissionGuard
116
+ pageName="dashboard"
117
+ operation="read"
118
+ fallback={<AccessDenied />}
119
+ >
120
+ <DashboardContent />
121
+ </PagePermissionGuard>
122
+ ```
123
+
124
+ **MUST NOT:**
125
+ - Create custom access denied components
126
+ - Use custom permission denied components
127
+
128
+ ```tsx
129
+ // ❌ FORBIDDEN - Custom component
130
+ function CustomAccessDenied() {
131
+ return <div>Access Denied</div>;
132
+ }
133
+ ```
134
+
135
+ ## MUST: Use pace-core API for Permission Checks
136
+
137
+ **MUST use pace-core API functions for programmatic permission checks:**
138
+
139
+ ```tsx
140
+ // ✅ CORRECT
141
+ import { isPermitted, isPermittedCached } from '@jmruthers/pace-core/rbac';
142
+
143
+ const hasAccess = await isPermitted({
144
+ userId: 'user-123',
145
+ scope: { organisationId: 'org-456' },
146
+ permission: 'read:dashboard',
147
+ pageId: 'dashboard'
148
+ });
149
+ ```
150
+
151
+ **MUST NOT:**
152
+ - Call RBAC RPC functions directly
153
+ - Query RBAC tables directly (without `useSecureSupabase`)
154
+ - Create custom RBAC helper functions
155
+
156
+ ```tsx
157
+ // ❌ FORBIDDEN - Direct RPC call
158
+ const { data } = await supabase.rpc('rbac_check_permission_simplified', {
159
+ p_user_id: userId,
160
+ p_permission: 'read:dashboard'
161
+ });
162
+
163
+ // ❌ FORBIDDEN - Direct table query
164
+ const { data } = await supabase
165
+ .from('rbac_user_profiles')
166
+ .select('*');
167
+
168
+ // ❌ FORBIDDEN - Custom RBAC helper
169
+ function checkPermission(userId: string, permission: string) {
170
+ // Custom logic that bypasses pace-core
171
+ }
172
+ ```
173
+
174
+ ## MUST: Use pace-core API in Edge Functions (No Exceptions)
175
+
176
+ **Edge Functions (Deno serverless functions) MUST use pace-core's `isPermitted()` API function.** Edge Functions cannot use React hooks, but pace-core provides programmatic APIs that work outside React.
177
+
178
+ **✅ CORRECT - Edge Function Pattern:**
179
+
180
+ ```typescript
181
+ // supabase/functions/my-function/index.ts
182
+ import { createClient } from 'jsr:@supabase/supabase-js@2';
183
+ import { setupRBAC, isPermitted } from 'npm:@jmruthers/pace-core@^0.6.0/rbac';
184
+
185
+ Deno.serve(async (req: Request) => {
186
+ // 1. Create Supabase client from request headers
187
+ const authHeader = req.headers.get('Authorization');
188
+ if (!authHeader) {
189
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
190
+ }
191
+
192
+ const supabase = createClient(
193
+ Deno.env.get('SUPABASE_URL') ?? '',
194
+ Deno.env.get('SUPABASE_ANON_KEY') ?? '',
195
+ {
196
+ global: {
197
+ headers: { Authorization: authHeader },
198
+ },
199
+ }
200
+ );
201
+
202
+ // 2. Get user from session
203
+ const { data: { user } } = await supabase.auth.getUser();
204
+ if (!user) {
205
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
206
+ }
207
+
208
+ // 3. Setup RBAC (required before using isPermitted)
209
+ setupRBAC(supabase);
210
+
211
+ // 4. Extract organisation context from request (headers, body, or query params)
212
+ const organisationId = req.headers.get('x-organisation-id') ||
213
+ (await req.json()).organisationId;
214
+
215
+ if (!organisationId) {
216
+ return new Response(JSON.stringify({ error: 'Organisation context required' }), { status: 400 });
217
+ }
218
+
219
+ // 5. Check permission using pace-core API
220
+ const hasPermission = await isPermitted({
221
+ userId: user.id,
222
+ scope: { organisationId },
223
+ permission: 'read:dashboard',
224
+ pageId: 'dashboard'
225
+ });
226
+
227
+ if (!hasPermission) {
228
+ return new Response(JSON.stringify({ error: 'Permission denied' }), { status: 403 });
229
+ }
230
+
231
+ // 6. Proceed with function logic
232
+ return new Response(JSON.stringify({ success: true }));
233
+ });
234
+ ```
235
+
236
+ **❌ FORBIDDEN - Custom RBAC Helper in Edge Functions:**
237
+
238
+ ```typescript
239
+ // ❌ FORBIDDEN - Creating custom RBAC helper
240
+ // supabase/functions/_shared/rbac.ts
241
+ export async function checkPermission(userId: string, permission: string) {
242
+ // Custom logic that bypasses pace-core
243
+ const { data } = await supabase.rpc('rbac_check_permission_simplified', {
244
+ p_user_id: userId,
245
+ p_permission: permission
246
+ });
247
+ return data;
248
+ }
249
+ ```
250
+
251
+ **Why No Exceptions:**
252
+ - pace-core provides `isPermitted()` API that works outside React
253
+ - `setupRBAC()` initializes the engine with a Supabase client
254
+ - No custom helpers needed - use pace-core APIs directly
255
+ - Custom helpers bypass security validation, caching, and audit logging
256
+
257
+ **Edge Function Requirements:**
258
+ 1. **MUST** call `setupRBAC(supabase)` before using `isPermitted()`
259
+ 2. **MUST** extract `userId` from Supabase auth session
260
+ 3. **MUST** extract `organisationId` from request (headers, body, or query params)
261
+ 4. **MUST** use `isPermitted()` with complete `PermissionCheck` input
262
+ 5. **MUST NOT** create custom RBAC helper functions
263
+ 6. **MUST NOT** call `rbac_check_permission_simplified` RPC directly
264
+
265
+ ## MUST: Use useSecureSupabase for RBAC Table Queries
266
+
267
+ **If you must query RBAC tables (rare), MUST use `useSecureSupabase`:**
268
+
269
+ ```tsx
270
+ // ✅ CORRECT
271
+ import { useSecureSupabase } from '@jmruthers/pace-core/rbac';
272
+
273
+ function MyComponent() {
274
+ const secureSupabase = useSecureSupabase();
275
+ // If your hook signature requires a client argument, pass ONLY the pace-core-provided client (never a local createClient()).
276
+ const { data } = await secureSupabase
277
+ .from('rbac_user_profiles') // Allowed through secure client
278
+ .select('*');
279
+ }
280
+ ```
281
+
282
+ **MUST NOT query these tables directly:**
283
+ - `rbac_organisation_roles`
284
+ - `rbac_event_app_roles`
285
+ - `rbac_global_roles`
286
+ - `rbac_apps`
287
+ - `rbac_app_pages`
288
+ - `rbac_page_permissions`
289
+ - `rbac_user_profiles`
290
+
291
+ ## MUST NOT: Use Hardcoded Role Checks
292
+
293
+ **MUST NOT compare roles directly:**
294
+
295
+ ```tsx
296
+ // ❌ FORBIDDEN - Hardcoded role check
297
+ if (user.role === 'admin') {
298
+ // ...
299
+ }
300
+ ```
301
+
302
+ **MUST use pace-core APIs:**
303
+
304
+ ```tsx
305
+ // ✅ CORRECT
306
+ import { useAccessLevel, getRoleContext } from '@jmruthers/pace-core/rbac';
307
+
308
+ const { accessLevel } = useAccessLevel(userId, scope);
309
+ const roleContext = await getRoleContext({ userId, scope });
310
+ ```
311
+
312
+ ## MUST: Configure enforcePermissions Correctly
313
+
314
+ **For event-based apps (where pages handle their own checks):**
315
+
316
+ ```tsx
317
+ // ✅ CORRECT - Event-based app pattern
318
+ <PaceAppLayout
319
+ appName="MyApp"
320
+ enforcePermissions={false} // Pages handle checks via PagePermissionGuard
321
+ // ...
322
+ >
323
+ ```
324
+
325
+ **For organisation-based apps (where layout handles checks):**
326
+
327
+ ```tsx
328
+ // ✅ CORRECT - Organisation-based app pattern
329
+ <PaceAppLayout
330
+ appName="MyApp"
331
+ enforcePermissions={true} // Layout handles checks
332
+ defaultPermission="read"
333
+ // ...
334
+ >
335
+ ```
336
+
337
+ ## MUST: Setup RBAC Before Use
338
+
339
+ **MUST call `setupRBAC()` before any RBAC usage:**
340
+
341
+ ```tsx
342
+ // main.tsx - MUST be first
343
+ import { setupRBAC } from '@jmruthers/pace-core/rbac';
344
+ setupRBAC(supabase);
345
+ // Then render app
346
+ ```
347
+
348
+ ## Enforcement
349
+
350
+ These rules are enforced by ESLint rules (all ERROR severity):
351
+
352
+ 1. **`no-direct-rbac-rpc`** - Detects direct calls to `rbac_check_permission_simplified`
353
+ 2. **`no-direct-rbac-tables`** - Detects direct queries to RBAC tables
354
+ 3. **`no-bypass-page-guard`** - Detects routes without `PagePermissionGuard`
355
+ 4. **`no-custom-access-denied`** - Detects custom access denied components
356
+ 5. **`no-hardcoded-role-checks`** - Detects hardcoded role comparisons
357
+ 6. **`no-custom-permission-utilities`** - Detects custom permission utility functions
358
+ 7. **`no-resource-permission-string-literals`** - Detects string literals in `useResourcePermissions` calls
359
+ 8. **`no-permission-wrapper-functions`** - Detects wrapper functions around pace-core permission hooks
360
+
361
+ ## Compliance Checklist
362
+
363
+ Before committing RBAC-related code, verify:
364
+
365
+ - [ ] All protected pages use `PagePermissionGuard`
366
+ - [ ] No wrapper components around `PagePermissionGuard`
367
+ - [ ] No wrapper functions around permission hooks
368
+ - [ ] All `useResourcePermissions` calls use `RESOURCE_NAMES` constants
369
+ - [ ] No direct RPC calls to `rbac_check_permission_simplified`
370
+ - [ ] No direct queries to RBAC tables (use `useSecureSupabase` if needed)
371
+ - [ ] No hardcoded role checks (use `useAccessLevel` or `getRoleContext`)
372
+ - [ ] No custom permission utility functions
373
+ - [ ] All access denied scenarios use `AccessDenied` from pace-core
374
+ - [ ] `enforcePermissions` configured correctly for app type
375
+ - [ ] `setupRBAC()` called before any RBAC usage
376
+ - [ ] Edge Functions use `isPermitted()` API (no custom RBAC helpers)
377
+ - [ ] All RLS policies for authenticated users include super-admin checks
378
+ - [ ] All `is_super_admin()` calls use explicit parameter (`safe_get_user_id_for_rls()`)
379
+ - [ ] No security-critical functions use fallback strategies
380
+ - [ ] ESLint rules pass without errors
381
+
382
+ ## MUST: Include Super-Admin Checks in RLS Policies
383
+
384
+ **MUST include super-admin bypass in all RLS policies for authenticated users:**
385
+
386
+ ```sql
387
+ -- ✅ CORRECT: Super-admin check included
388
+ CREATE POLICY "rbac_select_table_name" ON table_name
389
+ FOR SELECT TO authenticated
390
+ USING (
391
+ organisation_id IS NOT NULL
392
+ AND (
393
+ is_super_admin(safe_get_user_id_for_rls())
394
+ OR check_user_organisation_access(organisation_id)
395
+ )
396
+ );
397
+ ```
398
+
399
+ **MUST NOT:**
400
+ - Create RLS policies without super-admin checks (except for public/anonymous policies)
401
+ - Use `is_super_admin()` without a parameter (relies on fallback strategies)
402
+ - Create security-critical functions with fallback strategies
403
+
404
+ ```sql
405
+ -- ❌ FORBIDDEN: Missing super-admin check
406
+ CREATE POLICY "rbac_select_table_name" ON table_name
407
+ FOR SELECT TO authenticated
408
+ USING (
409
+ check_user_organisation_access(organisation_id)
410
+ );
411
+
412
+ -- ❌ FORBIDDEN: is_super_admin() without parameter
413
+ CREATE POLICY "rbac_select_table_name" ON table_name
414
+ FOR SELECT TO authenticated
415
+ USING (
416
+ is_super_admin() -- Missing parameter, uses fallback strategies
417
+ OR check_user_organisation_access(organisation_id)
418
+ );
419
+ ```
420
+
421
+ **MUST use explicit parameter passing:**
422
+
423
+ ```sql
424
+ -- ✅ CORRECT: Explicit user ID parameter
425
+ is_super_admin(safe_get_user_id_for_rls())
426
+ ```
427
+
428
+ **MUST NOT create security-critical functions with fallback strategies:**
429
+
430
+ ```sql
431
+ -- ❌ FORBIDDEN: Multiple fallback strategies
432
+ CREATE FUNCTION is_super_admin(p_user_id UUID DEFAULT NULL)
433
+ RETURNS boolean AS $$
434
+ BEGIN
435
+ IF p_user_id IS NOT NULL THEN
436
+ v_user_id := p_user_id;
437
+ ELSE
438
+ v_user_id := auth.uid(); -- Fallback 1
439
+ END IF;
440
+ -- More fallbacks...
441
+ END;
442
+ $$;
443
+
444
+ -- ✅ CORRECT: Required parameter, no fallbacks
445
+ CREATE FUNCTION is_super_admin(p_user_id UUID)
446
+ RETURNS boolean AS $$
447
+ BEGIN
448
+ IF p_user_id IS NULL THEN
449
+ RETURN false; -- Fail secure
450
+ END IF;
451
+ -- Check super-admin status
452
+ END;
453
+ $$;
454
+ ```
455
+
456
+ ## Reference
457
+
458
+ - **RBAC Contract**: `packages/core/docs/rbac/RBAC_CONTRACT.md` - Complete contract documentation
459
+ - **Migration Guide**: `packages/core/docs/rbac/MIGRATION_GUIDE.md` - Migrating to RBAC Contract v2.0.0
460
+ - **Quick Start**: `packages/core/docs/rbac/quick-start.md` - Getting started guide
461
+ - **API Reference**: `packages/core/docs/rbac/api-reference.md` - Complete API documentation
462
+ - **RLS Standards**: `packages/core/docs/standards/09-rbac-compliance.md` - RLS policy patterns and super-admin requirements
@@ -0,0 +1,179 @@
1
+ ---
2
+ description: Enforce consistent error handling patterns including type-safe errors, user-friendly messages, and proper logging
3
+ globs: ["src/**/*.{ts,tsx,js,jsx}"]
4
+ alwaysApply: false
5
+ paceCoreVersion: "0.6.x"
6
+ rulesVersion: "2025-01-28"
7
+ ---
8
+ # Error Handling Patterns Guide
9
+
10
+ **📚 Human-Readable Standard**: See [10-error-handling-patterns.md](../../packages/core/docs/standards/10-error-handling-patterns.md) for complete documentation.
11
+
12
+ This guide enforces consistent error handling patterns to ensure user-friendly errors, type-safe handling, and proper logging.
13
+
14
+ **AI Agent Instructions**: When writing error handling code, ALWAYS follow these patterns. Never expose internal details to users. Always use type-safe error handling.
15
+
16
+ ## MUST: Use Type-Safe Error Handling
17
+
18
+ **MUST use type guards or Result types, NEVER use `any`:**
19
+
20
+ ```tsx
21
+ // ❌ WRONG: Using any
22
+ catch (error: any) {
23
+ console.log(error.message);
24
+ }
25
+
26
+ // ✅ CORRECT: Type guard
27
+ function isApiError(error: unknown): error is ApiError {
28
+ return typeof error === 'object' && error !== null && 'ok' in error && (error as ApiError).ok === false;
29
+ }
30
+ catch (error) {
31
+ if (isApiError(error)) {
32
+ handleApiError(error);
33
+ } else {
34
+ handleUnknownError(error);
35
+ }
36
+ }
37
+
38
+ // ✅ CORRECT: Result type
39
+ type Result<T> = { ok: true; data: T } | { ok: false; error: ApiError };
40
+ const result = await fetchData();
41
+ if (result.ok) {
42
+ useData(result.data);
43
+ } else {
44
+ showError(result.error.message);
45
+ }
46
+ ```
47
+
48
+ ## MUST: Never Expose Internal Details
49
+
50
+ **MUST NOT expose SQL, stack traces, file paths, or internal errors to users:**
51
+
52
+ ```tsx
53
+ // ❌ WRONG: Exposing internal details
54
+ toast.error(error.message); // May contain SQL, stack traces
55
+
56
+ // ✅ CORRECT: User-friendly message
57
+ toast.error('Unable to save changes. Please try again.');
58
+ logger.error('Save failed', { error: error.message, context: 'saveUser' });
59
+ ```
60
+
61
+ ## MUST: Use Consistent Error Shapes
62
+
63
+ **MUST use ApiResult shape for API errors:**
64
+
65
+ ```tsx
66
+ // ✅ CORRECT: Consistent error shape
67
+ type ApiError = {
68
+ ok: false;
69
+ error: {
70
+ code: string;
71
+ message: string; // User-friendly
72
+ details?: object; // Non-sensitive context
73
+ };
74
+ };
75
+ ```
76
+
77
+ ## MUST: Log Errors with Context
78
+
79
+ **MUST log errors with context, but NEVER log sensitive data:**
80
+
81
+ ```tsx
82
+ // ✅ CORRECT: Log with context, no sensitive data
83
+ logger.error('Failed to save user', {
84
+ userId: user.id,
85
+ operation: 'updateUser',
86
+ errorCode: error.code,
87
+ });
88
+
89
+ // ❌ WRONG: Logging sensitive data
90
+ logger.error('Failed to save user', {
91
+ password: user.password, // NEVER
92
+ token: authToken, // NEVER
93
+ ssn: user.ssn, // NEVER
94
+ });
95
+ ```
96
+
97
+ ## SHOULD: Provide Recovery Paths
98
+
99
+ **SHOULD provide retry logic or fallback values when appropriate:**
100
+
101
+ ```tsx
102
+ // ✅ CORRECT: Retry with exponential backoff
103
+ async function fetchWithRetry<T>(fn: () => Promise<Result<T>>, maxRetries = 3): Promise<Result<T>> {
104
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
105
+ const result = await fn();
106
+ if (result.ok) return result;
107
+ if (result.error.code?.startsWith('4')) return result; // Don't retry 4xx
108
+ await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
109
+ }
110
+ return { ok: false, error: { code: 'MAX_RETRIES', message: 'Operation failed after retries' } };
111
+ }
112
+
113
+ // ✅ CORRECT: Fallback values
114
+ const preferences = await fetchPreferences().catch(() => ({ theme: 'light', language: 'en' }));
115
+ ```
116
+
117
+ ## SHOULD: Use Error Boundaries for React
118
+
119
+ **SHOULD use ErrorBoundary for React component errors:**
120
+
121
+ ```tsx
122
+ // ✅ CORRECT: Error boundary
123
+ import { ErrorBoundary } from '@jmruthers/pace-core';
124
+ <ErrorBoundary fallback={<ErrorFallback />} onError={(error, errorInfo) => logger.error('React error', { error, errorInfo })}>
125
+ <YourApp />
126
+ </ErrorBoundary>
127
+ ```
128
+
129
+ ## Decision Tree: Error Handling
130
+
131
+ **ALWAYS follow this decision tree:**
132
+
133
+ ```
134
+ 1. What type of error is this?
135
+ ├─ API Error → Use ApiResult shape, user-friendly message
136
+ ├─ Validation Error → Use Zod errors, field-specific messages
137
+ ├─ Network Error → Retry logic, connection message
138
+ └─ Unknown Error → Generic user message, detailed log
139
+
140
+ 2. Should user see this error?
141
+ ├─ YES → User-friendly message (no internals)
142
+ └─ NO → Log only, show generic message
143
+
144
+ 3. Can we recover?
145
+ ├─ YES → Retry logic or fallback value
146
+ └─ NO → Show error, allow user to retry
147
+
148
+ 4. Is this sensitive data?
149
+ ├─ YES → Never log (passwords, tokens, PII)
150
+ └─ NO → Log with context
151
+ ```
152
+
153
+ ## Error Handling Checklist
154
+
155
+ Before committing error handling code, verify:
156
+
157
+ - [ ] Type-safe error handling (no `any`)
158
+ - [ ] User-friendly error messages (no internals)
159
+ - [ ] Errors logged with context (no sensitive data)
160
+ - [ ] Recovery paths provided when possible
161
+ - [ ] Consistent error shapes used
162
+ - [ ] Error boundaries for React components
163
+ - [ ] Async operations have proper error handling
164
+ - [ ] Validation errors use Zod
165
+ - [ ] Network errors handled gracefully
166
+
167
+ ## Common Mistakes to Avoid
168
+
169
+ 1. **Exposing internal details** - Never show SQL, stack traces, file paths
170
+ 2. **Using `any` for errors** - Always use type guards or Result types
171
+ 3. **Logging sensitive data** - Never log passwords, tokens, PII
172
+ 4. **Ignoring errors** - Always handle errors, even if just logging
173
+ 5. **Generic error messages** - Provide specific, actionable messages
174
+
175
+ ## Reference
176
+
177
+ - **Standard**: `packages/core/docs/standards/10-error-handling-patterns.md`
178
+ - **Code Quality**: See `06-code-quality.mdc` for TypeScript standards
179
+ - **Security**: See `01-standards-compliance.mdc` for security requirements