@jmruthers/pace-core 0.5.183 → 0.5.185

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 (307) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +60 -1
  3. package/core-usage-manifest.json +312 -0
  4. package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
  5. package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
  6. package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-BABf6JCh.d.ts} +21 -10
  7. package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
  8. package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
  9. package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
  10. package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
  11. package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
  12. package/dist/{chunk-CSOFYHAG.js → chunk-AISXLWGZ.js} +374 -60
  13. package/dist/chunk-AISXLWGZ.js.map +1 -0
  14. package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
  15. package/dist/chunk-FXFJRTKI.js.map +1 -0
  16. package/dist/{chunk-QETLRQI6.js → chunk-HC67NW5K.js} +380 -360
  17. package/dist/chunk-HC67NW5K.js.map +1 -0
  18. package/dist/chunk-HESYZWZW.js +388 -0
  19. package/dist/chunk-HESYZWZW.js.map +1 -0
  20. package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
  21. package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
  22. package/dist/{chunk-UHNYIBXL.js → chunk-IXSNYUCT.js} +1 -1
  23. package/dist/chunk-IXSNYUCT.js.map +1 -0
  24. package/dist/{chunk-MI7HBHN3.js → chunk-MX3EIJGQ.js} +4 -3
  25. package/dist/{chunk-MI7HBHN3.js.map → chunk-MX3EIJGQ.js.map} +1 -1
  26. package/dist/{chunk-PWAHJW4G.js → chunk-OKI34GZD.js} +86 -33
  27. package/dist/chunk-OKI34GZD.js.map +1 -0
  28. package/dist/{chunk-W22JP75J.js → chunk-STTZQK2I.js} +3 -3
  29. package/dist/chunk-THRPYOFK.js +215 -0
  30. package/dist/chunk-THRPYOFK.js.map +1 -0
  31. package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
  32. package/dist/chunk-U6WNSFX5.js.map +1 -0
  33. package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
  34. package/dist/components.d.ts +182 -6
  35. package/dist/components.js +157 -11
  36. package/dist/components.js.map +1 -1
  37. package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
  38. package/dist/{file-reference-D06mEEWW.d.ts → file-reference-BjR39ktt.d.ts} +7 -1
  39. package/dist/hooks.d.ts +7 -14
  40. package/dist/hooks.js +10 -22
  41. package/dist/hooks.js.map +1 -1
  42. package/dist/index.d.ts +11 -11
  43. package/dist/index.js +79 -16
  44. package/dist/index.js.map +1 -1
  45. package/dist/providers.d.ts +1 -1
  46. package/dist/providers.js +3 -1
  47. package/dist/rbac/index.d.ts +205 -14
  48. package/dist/rbac/index.js +28 -6
  49. package/dist/timezone-_pgH8qrY.d.ts +530 -0
  50. package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
  51. package/dist/types.d.ts +1 -1
  52. package/dist/types.js +1 -1
  53. package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-CvnC3d-e.d.ts} +113 -2
  54. package/dist/utils.d.ts +109 -151
  55. package/dist/utils.js +128 -138
  56. package/dist/utils.js.map +1 -1
  57. package/docs/api/README.md +60 -1
  58. package/docs/api/classes/ColumnFactory.md +1 -1
  59. package/docs/api/classes/ErrorBoundary.md +1 -1
  60. package/docs/api/classes/InvalidScopeError.md +1 -1
  61. package/docs/api/classes/Logger.md +178 -0
  62. package/docs/api/classes/MissingUserContextError.md +1 -1
  63. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  64. package/docs/api/classes/PermissionDeniedError.md +1 -1
  65. package/docs/api/classes/RBACAuditManager.md +2 -2
  66. package/docs/api/classes/RBACCache.md +1 -1
  67. package/docs/api/classes/RBACEngine.md +2 -2
  68. package/docs/api/classes/RBACError.md +1 -1
  69. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  70. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  71. package/docs/api/classes/StorageUtils.md +1 -1
  72. package/docs/api/enums/FileCategory.md +1 -1
  73. package/docs/api/enums/LogLevel.md +54 -0
  74. package/docs/api/enums/RBACErrorCode.md +1 -1
  75. package/docs/api/enums/RPCFunction.md +1 -1
  76. package/docs/api/interfaces/AggregateConfig.md +1 -1
  77. package/docs/api/interfaces/BadgeProps.md +1 -1
  78. package/docs/api/interfaces/ButtonProps.md +1 -1
  79. package/docs/api/interfaces/CalendarProps.md +18 -2
  80. package/docs/api/interfaces/CardProps.md +1 -1
  81. package/docs/api/interfaces/ColorPalette.md +1 -1
  82. package/docs/api/interfaces/ColorShade.md +1 -1
  83. package/docs/api/interfaces/ComplianceResult.md +30 -0
  84. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  85. package/docs/api/interfaces/DataRecord.md +1 -1
  86. package/docs/api/interfaces/DataTableAction.md +1 -1
  87. package/docs/api/interfaces/DataTableColumn.md +1 -1
  88. package/docs/api/interfaces/DataTableProps.md +1 -1
  89. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  90. package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
  91. package/docs/api/interfaces/DatabaseIssue.md +41 -0
  92. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventAppRoleData.md +6 -6
  95. package/docs/api/interfaces/ExportColumn.md +1 -1
  96. package/docs/api/interfaces/ExportOptions.md +1 -1
  97. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  98. package/docs/api/interfaces/FileMetadata.md +1 -1
  99. package/docs/api/interfaces/FileReference.md +1 -1
  100. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  101. package/docs/api/interfaces/FileUploadOptions.md +24 -8
  102. package/docs/api/interfaces/FileUploadProps.md +24 -13
  103. package/docs/api/interfaces/FooterProps.md +1 -1
  104. package/docs/api/interfaces/FormFieldProps.md +1 -1
  105. package/docs/api/interfaces/FormProps.md +1 -1
  106. package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
  107. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  108. package/docs/api/interfaces/InputProps.md +1 -1
  109. package/docs/api/interfaces/LabelProps.md +1 -1
  110. package/docs/api/interfaces/LoggerConfig.md +62 -0
  111. package/docs/api/interfaces/LoginFormProps.md +1 -1
  112. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  113. package/docs/api/interfaces/NavigationContextType.md +1 -1
  114. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  115. package/docs/api/interfaces/NavigationItem.md +1 -1
  116. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  117. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  118. package/docs/api/interfaces/Organisation.md +1 -1
  119. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  120. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  121. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  122. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  123. package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
  124. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  125. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  126. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  127. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  128. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  129. package/docs/api/interfaces/PaletteData.md +1 -1
  130. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  131. package/docs/api/interfaces/ProgressProps.md +1 -1
  132. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  133. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  134. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  135. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  136. package/docs/api/interfaces/QuickFix.md +52 -0
  137. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  138. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  139. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  140. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  141. package/docs/api/interfaces/RBACConfig.md +4 -4
  142. package/docs/api/interfaces/RBACContext.md +1 -1
  143. package/docs/api/interfaces/RBACLogger.md +1 -1
  144. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  146. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  148. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  149. package/docs/api/interfaces/RBACResult.md +1 -1
  150. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  151. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  152. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  153. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  154. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  155. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  156. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  157. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  158. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  159. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  160. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  161. package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
  162. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  163. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  164. package/docs/api/interfaces/RoleManagementResult.md +5 -5
  165. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  166. package/docs/api/interfaces/RouteConfig.md +1 -1
  167. package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
  168. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  169. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  170. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  171. package/docs/api/interfaces/SetupIssue.md +41 -0
  172. package/docs/api/interfaces/StorageConfig.md +1 -1
  173. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  174. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  175. package/docs/api/interfaces/StorageListOptions.md +1 -1
  176. package/docs/api/interfaces/StorageListResult.md +1 -1
  177. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  178. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  179. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  180. package/docs/api/interfaces/StyleImport.md +1 -1
  181. package/docs/api/interfaces/SwitchProps.md +1 -1
  182. package/docs/api/interfaces/TabsContentProps.md +1 -1
  183. package/docs/api/interfaces/TabsListProps.md +1 -1
  184. package/docs/api/interfaces/TabsProps.md +1 -1
  185. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  186. package/docs/api/interfaces/TextareaProps.md +1 -1
  187. package/docs/api/interfaces/ToastActionElement.md +1 -1
  188. package/docs/api/interfaces/ToastProps.md +1 -1
  189. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  190. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  191. package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
  192. package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
  193. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  194. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  195. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  196. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  198. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  199. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  200. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  201. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  202. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  203. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  204. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  205. package/docs/api/interfaces/UserEventAccess.md +1 -1
  206. package/docs/api/interfaces/UserMenuProps.md +1 -1
  207. package/docs/api/interfaces/UserProfile.md +1 -1
  208. package/docs/api/modules.md +738 -42
  209. package/docs/api-reference/hooks.md +111 -0
  210. package/docs/api-reference/rpc-functions.md +1 -1
  211. package/docs/api-reference/utilities.md +184 -0
  212. package/docs/getting-started/installation-guide.md +75 -16
  213. package/docs/getting-started/quick-start.md +61 -11
  214. package/docs/implementation-guides/authentication.md +88 -12
  215. package/docs/implementation-guides/file-reference-system.md +2 -1
  216. package/docs/implementation-guides/file-upload-storage.md +21 -0
  217. package/docs/rbac/README.md +1 -0
  218. package/docs/rbac/compliance/compliance-guide.md +544 -0
  219. package/docs/rbac/getting-started.md +158 -33
  220. package/docs/standards/pace-core-compliance.md +432 -0
  221. package/eslint-config-pace-core.cjs +93 -0
  222. package/package.json +15 -3
  223. package/scripts/analyze-bundle.js +232 -0
  224. package/scripts/build-css.js +56 -0
  225. package/scripts/build-docs-incremental.js +1015 -0
  226. package/scripts/check-pace-core-compliance.cjs +2353 -0
  227. package/scripts/generate-docs.js +157 -0
  228. package/scripts/setup-build-cache.js +73 -0
  229. package/scripts/utils/command-runner.js +131 -0
  230. package/scripts/utils/env.js +33 -0
  231. package/scripts/utils/index.js +10 -0
  232. package/scripts/utils/logger.js +88 -0
  233. package/scripts/utils/path-helpers.js +37 -0
  234. package/scripts/validate-formats.js +133 -0
  235. package/scripts/validate-master.js +155 -0
  236. package/scripts/validate-pre-publish.js +140 -0
  237. package/scripts/validate-theme.js +142 -0
  238. package/src/components/Calendar/Calendar.tsx +8 -1
  239. package/src/components/Card/Card.tsx +47 -8
  240. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
  241. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
  242. package/src/components/DatePickerWithTimezone/README.md +135 -0
  243. package/src/components/DatePickerWithTimezone/index.ts +10 -0
  244. package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
  245. package/src/components/DateTimeField/DateTimeField.tsx +232 -0
  246. package/src/components/DateTimeField/README.md +148 -0
  247. package/src/components/DateTimeField/index.ts +10 -0
  248. package/src/components/FileUpload/FileUpload.tsx +3 -0
  249. package/src/components/Header/Header.test.tsx +47 -18
  250. package/src/components/Header/Header.tsx +24 -6
  251. package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
  252. package/src/components/PaceAppLayout/README.md +9 -0
  253. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  254. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
  255. package/src/components/ProtectedRoute/ProtectedRoute.tsx +12 -4
  256. package/src/components/index.ts +8 -0
  257. package/src/eslint-rules/pace-core-compliance.cjs +406 -0
  258. package/src/eslint-rules/pace-core-compliance.js +640 -0
  259. package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
  260. package/src/hooks/index.ts +2 -0
  261. package/src/hooks/useFileReference.test.ts +1 -0
  262. package/src/hooks/useFormDialog.ts +147 -0
  263. package/src/index.ts +27 -0
  264. package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
  265. package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
  266. package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
  267. package/src/rbac/compliance/database-validator.ts +165 -0
  268. package/src/rbac/compliance/index.ts +38 -0
  269. package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
  270. package/src/rbac/compliance/runtime-compliance.ts +77 -0
  271. package/src/rbac/compliance/setup-validator.ts +131 -0
  272. package/src/rbac/components/PagePermissionGuard.tsx +8 -64
  273. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
  274. package/src/rbac/docs/event-based-apps.md +285 -0
  275. package/src/rbac/errors.ts +11 -0
  276. package/src/rbac/hooks/useRoleManagement.ts +292 -12
  277. package/src/rbac/index.ts +30 -0
  278. package/src/services/OrganisationService.ts +4 -0
  279. package/src/types/file-reference.ts +6 -0
  280. package/src/utils/__tests__/timezone.test.ts +345 -0
  281. package/src/utils/file-reference/__tests__/file-reference.test.ts +2 -0
  282. package/src/utils/file-reference/index.ts +1 -0
  283. package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
  284. package/src/utils/formatting/formatting.ts +179 -0
  285. package/src/utils/index.ts +27 -1
  286. package/src/utils/location/index.ts +16 -0
  287. package/src/utils/location/location.test.ts +286 -0
  288. package/src/utils/location/location.ts +175 -0
  289. package/src/utils/timezone/index.ts +17 -0
  290. package/src/utils/timezone/timezone.test.ts +349 -0
  291. package/src/utils/timezone/timezone.ts +281 -0
  292. package/dist/chunk-CSOFYHAG.js.map +0 -1
  293. package/dist/chunk-FUEYYMX5.js.map +0 -1
  294. package/dist/chunk-HKIT6O7W.js +0 -198
  295. package/dist/chunk-HKIT6O7W.js.map +0 -1
  296. package/dist/chunk-KUEN3HFB.js +0 -94
  297. package/dist/chunk-KUEN3HFB.js.map +0 -1
  298. package/dist/chunk-M7W4CP3M.js.map +0 -1
  299. package/dist/chunk-PWAHJW4G.js.map +0 -1
  300. package/dist/chunk-QETLRQI6.js.map +0 -1
  301. package/dist/chunk-UHNYIBXL.js.map +0 -1
  302. package/dist/formatting-5wETwiGF.d.ts +0 -162
  303. /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
  304. /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
  305. /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
  306. /package/dist/{chunk-W22JP75J.js.map → chunk-STTZQK2I.js.map} +0 -0
  307. /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
@@ -37,37 +37,112 @@ const supabase = createClient(
37
37
  setupRBAC(supabase);
38
38
  ```
39
39
 
40
- ### 3. Wrap Your App with Required Providers
40
+ ### 3. Configure Vite (CRITICAL)
41
+
42
+ **⚠️ CRITICAL**: Without this configuration, you'll get React context and Router context errors.
43
+
44
+ Add to your `vite.config.ts`:
45
+
46
+ ```typescript
47
+ import { defineConfig } from 'vite';
48
+ import react from '@vitejs/plugin-react';
49
+ import tailwindcss from '@tailwindcss/vite';
50
+ import path from 'path';
51
+
52
+ export default defineConfig({
53
+ plugins: [
54
+ react(),
55
+ tailwindcss({
56
+ content: [
57
+ './src/**/*.{js,ts,jsx,tsx}',
58
+ './node_modules/@jmruthers/pace-core/src/**/*.{js,ts,jsx,tsx}'
59
+ ]
60
+ })
61
+ ],
62
+ resolve: {
63
+ alias: {
64
+ "@": path.resolve(__dirname, "./src"),
65
+ },
66
+ // CRITICAL: Dedupe React and React Router to ensure single instances
67
+ dedupe: ['react', 'react-dom', 'react-router-dom']
68
+ },
69
+ // CRITICAL: Exclude pace-core from pre-bundling to prevent React context mismatches
70
+ optimizeDeps: {
71
+ include: [
72
+ 'react',
73
+ 'react-dom',
74
+ 'react/jsx-runtime'
75
+ ],
76
+ exclude: ['@jmruthers/pace-core', 'react-router-dom']
77
+ },
78
+ });
79
+ ```
80
+
81
+ ### 4. Wrap Your App with Required Providers
82
+
83
+ **⚠️ CRITICAL**: Provider nesting order matters! Follow this exact structure.
41
84
 
42
85
  ```tsx
43
- // App.tsx - COMPLETE setup (copy this exactly)
86
+ // src/main.tsx - COMPLETE setup (copy this exactly)
44
87
  import React from 'react';
88
+ import ReactDOM from 'react-dom/client';
89
+ import { BrowserRouter } from 'react-router-dom';
90
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
45
91
  import { setupRBAC } from '@jmruthers/pace-core/rbac';
46
92
  import { UnifiedAuthProvider, OrganisationProvider } from '@jmruthers/pace-core';
47
93
  import { createClient } from '@supabase/supabase-js';
94
+ import App from './App';
95
+
96
+ // Import CSS
97
+ import '@jmruthers/pace-core/src/styles/core.css';
48
98
 
49
99
  // Initialize Supabase
50
100
  const supabase = createClient(
51
- process.env.REACT_APP_SUPABASE_URL!,
52
- process.env.REACT_APP_SUPABASE_ANON_KEY!
101
+ import.meta.env.VITE_SUPABASE_URL!,
102
+ import.meta.env.VITE_SUPABASE_ANON_KEY!
53
103
  );
54
104
 
55
- // ⚠️ REQUIRED: Setup RBAC first
105
+ // ⚠️ REQUIRED: Setup RBAC before rendering
56
106
  setupRBAC(supabase);
57
107
 
58
- function App() {
59
- return (
60
- <UnifiedAuthProvider supabaseClient={supabase} appName="My App">
61
- <OrganisationProvider>
62
- <YourAppContent />
63
- </OrganisationProvider>
64
- </UnifiedAuthProvider>
65
- );
66
- }
67
-
68
- export default App;
108
+ const queryClient = new QueryClient();
109
+
110
+ ReactDOM.createRoot(document.getElementById('root')!).render(
111
+ <React.StrictMode>
112
+ {/* CRITICAL: Correct nesting order */}
113
+ <QueryClientProvider client={queryClient}>
114
+ <BrowserRouter>
115
+ <UnifiedAuthProvider
116
+ supabaseClient={supabase}
117
+ appName="My App"
118
+ idleTimeoutMs={30 * 60 * 1000}
119
+ warnBeforeMs={5 * 60 * 1000}
120
+ onIdleLogout={() => window.location.href = '/login'}
121
+ >
122
+ <OrganisationProvider>
123
+ <App />
124
+ </OrganisationProvider>
125
+ </UnifiedAuthProvider>
126
+ </BrowserRouter>
127
+ </QueryClientProvider>
128
+ </React.StrictMode>
129
+ );
69
130
  ```
70
131
 
132
+ **Provider Nesting Order (CRITICAL):**
133
+ 1. `React.StrictMode` (outermost)
134
+ 2. `QueryClientProvider`
135
+ 3. `BrowserRouter`
136
+ 4. `UnifiedAuthProvider`
137
+ 5. `OrganisationProvider`
138
+ 6. `App` (innermost)
139
+
140
+ **Why this order matters:**
141
+ - `BrowserRouter` must wrap `UnifiedAuthProvider` to provide Router context
142
+ - `UnifiedAuthProvider` must be inside `BrowserRouter` to use Router hooks
143
+ - Wrong nesting causes "useNavigate() may be used only in the context of a <Router>" errors
144
+ - Missing Vite config causes "useUnifiedAuth must be used within a UnifiedAuthProvider" errors
145
+
71
146
  ## 🎯 **When to Use What Pattern**
72
147
 
73
148
  ### Pattern 1: PagePermissionGuard (REQUIRED for Pages)
@@ -170,33 +245,46 @@ function UserActions() {
170
245
  Here's a complete, working example that demonstrates all patterns:
171
246
 
172
247
  ```tsx
173
- // App.tsx - COMPLETE setup
248
+ // src/main.tsx - COMPLETE setup
174
249
  import React from 'react';
250
+ import ReactDOM from 'react-dom/client';
251
+ import { BrowserRouter } from 'react-router-dom';
252
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
175
253
  import { setupRBAC } from '@jmruthers/pace-core/rbac';
176
254
  import { UnifiedAuthProvider, OrganisationProvider } from '@jmruthers/pace-core';
177
255
  import { createClient } from '@supabase/supabase-js';
178
- import { Dashboard } from './components/Dashboard';
256
+ import App from './App';
179
257
 
180
258
  // Initialize Supabase
181
259
  const supabase = createClient(
182
- process.env.REACT_APP_SUPABASE_URL!,
183
- process.env.REACT_APP_SUPABASE_ANON_KEY!
260
+ import.meta.env.VITE_SUPABASE_URL!,
261
+ import.meta.env.VITE_SUPABASE_ANON_KEY!
184
262
  );
185
263
 
186
- // ⚠️ REQUIRED: Setup RBAC first
264
+ // ⚠️ REQUIRED: Setup RBAC before rendering
187
265
  setupRBAC(supabase);
188
266
 
189
- function App() {
190
- return (
191
- <UnifiedAuthProvider supabaseClient={supabase} appName="My App">
192
- <OrganisationProvider>
193
- <Dashboard />
194
- </OrganisationProvider>
195
- </UnifiedAuthProvider>
196
- );
197
- }
198
-
199
- export default App;
267
+ const queryClient = new QueryClient();
268
+
269
+ ReactDOM.createRoot(document.getElementById('root')!).render(
270
+ <React.StrictMode>
271
+ <QueryClientProvider client={queryClient}>
272
+ <BrowserRouter>
273
+ <UnifiedAuthProvider
274
+ supabaseClient={supabase}
275
+ appName="My App"
276
+ idleTimeoutMs={30 * 60 * 1000}
277
+ warnBeforeMs={5 * 60 * 1000}
278
+ onIdleLogout={() => window.location.href = '/login'}
279
+ >
280
+ <OrganisationProvider>
281
+ <App />
282
+ </OrganisationProvider>
283
+ </UnifiedAuthProvider>
284
+ </BrowserRouter>
285
+ </QueryClientProvider>
286
+ </React.StrictMode>
287
+ );
200
288
  ```
201
289
 
202
290
  ```tsx
@@ -471,10 +559,47 @@ setupRBAC(supabase);
471
559
  **Cause**: OrganisationProvider not wrapping your app or user not in any organisation
472
560
 
473
561
  **Solution**:
474
- 1. Ensure OrganisationProvider wraps your app
562
+ 1. Ensure OrganisationProvider wraps your app (inside UnifiedAuthProvider)
475
563
  2. Check that user has organisation memberships in the database
476
564
  3. Verify user has selected an organisation
477
565
 
566
+ ### Error: "useUnifiedAuth must be used within a UnifiedAuthProvider"
567
+
568
+ **Cause**: Vite is pre-bundling `@jmruthers/pace-core`, creating separate React instances
569
+
570
+ **Solution**:
571
+ 1. Add to `vite.config.ts`:
572
+ ```typescript
573
+ optimizeDeps: {
574
+ exclude: ['@jmruthers/pace-core', 'react-router-dom']
575
+ }
576
+ ```
577
+ 2. Clear Vite cache: `rm -rf node_modules/.vite`
578
+ 3. Restart dev server
579
+
580
+ ### Error: "useNavigate() may be used only in the context of a <Router> component"
581
+
582
+ **Cause**: Incorrect provider nesting - BrowserRouter should wrap UnifiedAuthProvider
583
+
584
+ **Solution**:
585
+ 1. Ensure correct nesting order:
586
+ ```tsx
587
+ <BrowserRouter>
588
+ <UnifiedAuthProvider>
589
+ <OrganisationProvider>
590
+ <App />
591
+ </OrganisationProvider>
592
+ </UnifiedAuthProvider>
593
+ </BrowserRouter>
594
+ ```
595
+ 2. Do NOT put BrowserRouter inside UnifiedAuthProvider
596
+ 3. Add to `vite.config.ts`:
597
+ ```typescript
598
+ resolve: {
599
+ dedupe: ['react', 'react-dom', 'react-router-dom']
600
+ }
601
+ ```
602
+
478
603
  ### Error: Permission checks always return false
479
604
 
480
605
  **Cause**: User doesn't have proper roles or permissions assigned
@@ -0,0 +1,432 @@
1
+ # pace-core Compliance Enforcement
2
+
3
+ This guide explains how to enforce pace-core usage patterns in consuming apps to ensure consistent design, reduce duplication, and maintain high code quality.
4
+
5
+ ## Overview
6
+
7
+ pace-core provides a comprehensive enforcement system that includes:
8
+
9
+ 1. **ESLint Rules** - Real-time linting during development
10
+ 2. **Static Analysis Script** - Comprehensive codebase scanning
11
+ 3. **ESLint Config Preset** - Easy setup for consuming apps
12
+
13
+ ## Quick Start
14
+
15
+ ### Option 1: ESLint Config Preset (Recommended)
16
+
17
+ The easiest way to enable compliance checking is to use the shareable ESLint config. The config is CommonJS but works with both ES module and CommonJS ESLint configs:
18
+
19
+ **For ES Module ESLint Config (eslint.config.js):**
20
+ ```javascript
21
+ // eslint.config.js (ES modules)
22
+ import paceCoreConfig from '@jmruthers/pace-core/eslint-config';
23
+
24
+ export default [
25
+ ...paceCoreConfig,
26
+ // your other config
27
+ ];
28
+ ```
29
+
30
+ **For CommonJS ESLint Config (.eslintrc.js or eslint.config.cjs):**
31
+ ```javascript
32
+ // eslint.config.cjs (CommonJS)
33
+ const paceCoreConfig = require('@jmruthers/pace-core/eslint-config');
34
+
35
+ module.exports = [
36
+ ...paceCoreConfig,
37
+ // your other config
38
+ ];
39
+ ```
40
+
41
+ ### Option 2: Manual ESLint Rules Setup
42
+
43
+ If you prefer more control, you can import the rules directly. Note: The rules are CommonJS, so use `createRequire` in ES modules:
44
+
45
+ **For ES Module ESLint Config:**
46
+ ```javascript
47
+ // eslint.config.js
48
+ import { createRequire } from 'module';
49
+ const require = createRequire(import.meta.url);
50
+ const paceCoreRules = require('@jmruthers/pace-core/eslint-rules').rules;
51
+
52
+ export default [
53
+ {
54
+ plugins: {
55
+ 'pace-core-compliance': {
56
+ rules: paceCoreRules
57
+ }
58
+ },
59
+ rules: {
60
+ 'pace-core-compliance/no-restricted-imports': 'error',
61
+ 'pace-core-compliance/prefer-pace-core-components': 'warn',
62
+ 'pace-core-compliance/prefer-pace-core-hooks': 'warn',
63
+ 'pace-core-compliance/prefer-pace-core-utils': 'warn',
64
+ 'pace-core-compliance/no-local-component-duplication': 'error'
65
+ }
66
+ }
67
+ ];
68
+ ```
69
+
70
+ **For CommonJS ESLint Config:**
71
+ ```javascript
72
+ // eslint.config.cjs
73
+ const paceCoreRules = require('@jmruthers/pace-core/eslint-rules').rules;
74
+
75
+ module.exports = [
76
+ {
77
+ plugins: {
78
+ 'pace-core-compliance': {
79
+ rules: paceCoreRules
80
+ }
81
+ },
82
+ rules: {
83
+ 'pace-core-compliance/no-restricted-imports': 'error',
84
+ 'pace-core-compliance/prefer-pace-core-components': 'warn',
85
+ 'pace-core-compliance/prefer-pace-core-hooks': 'warn',
86
+ 'pace-core-compliance/prefer-pace-core-utils': 'warn',
87
+ 'pace-core-compliance/no-local-component-duplication': 'error'
88
+ }
89
+ }
90
+ ];
91
+ ```
92
+
93
+ ## ESLint Rules
94
+
95
+ ### no-restricted-imports
96
+
97
+ **Severity**: Error
98
+
99
+ Blocks direct imports of libraries that pace-core wraps and standardizes.
100
+
101
+ **Restricted Libraries**:
102
+ - `@radix-ui/*` - All Radix UI packages (use pace-core components instead)
103
+ - `lucide-react` - Icons (use pace-core components that include icons)
104
+ - `react-day-picker` - Use `Calendar` from pace-core
105
+ - `@tanstack/react-table` - Use `DataTable` from pace-core
106
+ - `react-hook-form` - Use `Form` and `useZodForm` from pace-core
107
+ - `zod` - Use validation utilities from pace-core
108
+
109
+ **Example Violation**:
110
+ ```typescript
111
+ // ❌ Bad
112
+ import { Dialog } from '@radix-ui/react-dialog';
113
+ import { Button } from 'lucide-react';
114
+
115
+ // ✅ Good
116
+ import { Dialog } from '@jmruthers/pace-core';
117
+ import { Button } from '@jmruthers/pace-core';
118
+ ```
119
+
120
+ ### prefer-pace-core-components
121
+
122
+ **Severity**: Warning
123
+
124
+ Suggests using pace-core components instead of native HTML elements.
125
+
126
+ **Detected Patterns**:
127
+ - `<button>` → Use `Button` from pace-core
128
+ - `<input>` → Use `Input` from pace-core
129
+ - `<textarea>` → Use `Textarea` from pace-core
130
+ - `<label>` → Use `Label` from pace-core
131
+
132
+ **Example**:
133
+ ```tsx
134
+ // ⚠️ Warning
135
+ <button onClick={handleClick}>Click me</button>
136
+
137
+ // ✅ Recommended
138
+ import { Button } from '@jmruthers/pace-core';
139
+ <Button onClick={handleClick}>Click me</Button>
140
+ ```
141
+
142
+ ### prefer-pace-core-hooks
143
+
144
+ **Severity**: Warning
145
+
146
+ Detects custom hooks that duplicate pace-core functionality.
147
+
148
+ **Common Patterns Detected**:
149
+ - `useToast`, `useNotification` → Use `useToast` from pace-core
150
+ - `useDebounce`, `useDebounced` → Use `useDebounce` from pace-core
151
+ - `useAuth`, `useAuthentication` → Use `useUnifiedAuth` from pace-core
152
+ - `useForm`, `useZodForm` → Use `useZodForm` from pace-core
153
+
154
+ **Example**:
155
+ ```typescript
156
+ // ⚠️ Warning
157
+ function useToast() {
158
+ // custom implementation
159
+ }
160
+
161
+ // ✅ Recommended
162
+ import { useToast } from '@jmruthers/pace-core';
163
+ ```
164
+
165
+ ### prefer-pace-core-utils
166
+
167
+ **Severity**: Warning
168
+
169
+ Detects utility functions that duplicate pace-core functionality.
170
+
171
+ **Common Patterns Detected**:
172
+ - `formatDate`, `dateFormat` → Use `formatDate` from pace-core
173
+ - `formatCurrency`, `formatMoney` → Use `formatCurrency` from pace-core
174
+ - `cn`, `classNames`, `clsx` → Use `cn` from pace-core
175
+ - `validate`, `validateInput` → Use `validateUserInput` from pace-core
176
+
177
+ **Example**:
178
+ ```typescript
179
+ // ⚠️ Warning
180
+ function formatDate(date: Date): string {
181
+ // custom implementation
182
+ }
183
+
184
+ // ✅ Recommended
185
+ import { formatDate } from '@jmruthers/pace-core';
186
+ ```
187
+
188
+ ### no-local-component-duplication
189
+
190
+ **Severity**: Error
191
+
192
+ Prevents creating local components with names matching pace-core components.
193
+
194
+ **Example Violation**:
195
+ ```
196
+ // ❌ Error: components/Button.tsx
197
+ export function Button() { ... }
198
+
199
+ // pace-core already provides Button component
200
+ ```
201
+
202
+ **Fix**: Remove the local component and import from pace-core:
203
+ ```typescript
204
+ import { Button } from '@jmruthers/pace-core';
205
+ ```
206
+
207
+ ## Static Analysis Script
208
+
209
+ The static analysis script provides a comprehensive scan of your codebase and generates a detailed compliance report.
210
+
211
+ ### Running the Script
212
+
213
+ ```bash
214
+ # From your consuming app root
215
+ node node_modules/@jmruthers/pace-core/scripts/check-pace-core-compliance.cjs
216
+ ```
217
+
218
+ Or add it to your `package.json`:
219
+
220
+ ```json
221
+ {
222
+ "scripts": {
223
+ "check:pace-core": "node node_modules/@jmruthers/pace-core/scripts/check-pace-core-compliance.cjs"
224
+ }
225
+ }
226
+ ```
227
+
228
+ ### What It Checks
229
+
230
+ 1. **Restricted Imports** - Scans for direct imports of wrapped libraries
231
+ 2. **Duplicate Components** - Finds local components matching pace-core names
232
+ 3. **Duplicate Hooks** - Finds local hooks matching pace-core hooks
233
+ 4. **Duplicate Utils** - Finds local utils matching pace-core utils
234
+ 5. **Suggestions** - Recommends pace-core alternatives for native HTML elements
235
+
236
+ ### Report Output
237
+
238
+ The script generates a color-coded terminal report showing:
239
+
240
+ - ❌ **Errors** - Critical violations that must be fixed
241
+ - ⚠️ **Warnings** - Issues that should be addressed
242
+ - 💡 **Suggestions** - Recommendations for improvement
243
+ - ✅ **Summary** - Overall compliance status
244
+
245
+ Example output:
246
+ ```
247
+ ═══════════════════════════════════════════════════════════
248
+ pace-core Compliance Report
249
+ ═══════════════════════════════════════════════════════════
250
+
251
+ ❌ Restricted Imports Found: 3
252
+
253
+ • src/components/CustomDialog.tsx:5
254
+ Import: @radix-ui/react-dialog
255
+ Reason: Use Dialog component from pace-core instead
256
+
257
+ Summary:
258
+ Total Issues: 3
259
+ - Restricted Imports: 3
260
+ - Duplicate Components/Hooks/Utils: 0
261
+ - Suggestions: 0
262
+ ```
263
+
264
+ ## Best Practices
265
+
266
+ ### 1. Always Import from pace-core
267
+
268
+ ```typescript
269
+ // ✅ Good
270
+ import { Button, Card, Dialog } from '@jmruthers/pace-core';
271
+ import { useToast, useDebounce } from '@jmruthers/pace-core';
272
+ import { formatDate, formatCurrency } from '@jmruthers/pace-core';
273
+ ```
274
+
275
+ ### 2. Use pace-core Components for UI
276
+
277
+ Avoid native HTML elements when pace-core provides a component:
278
+
279
+ ```tsx
280
+ // ❌ Avoid
281
+ <button className="btn">Click</button>
282
+
283
+ // ✅ Use pace-core
284
+ <Button>Click</Button>
285
+ ```
286
+
287
+ ### 3. Leverage pace-core Hooks
288
+
289
+ Don't recreate hooks that pace-core already provides:
290
+
291
+ ```typescript
292
+ // ❌ Avoid
293
+ const [debouncedValue, setDebouncedValue] = useState(value);
294
+ useEffect(() => {
295
+ const timer = setTimeout(() => setDebouncedValue(value), 500);
296
+ return () => clearTimeout(timer);
297
+ }, [value]);
298
+
299
+ // ✅ Use pace-core
300
+ import { useDebounce } from '@jmruthers/pace-core';
301
+ const debouncedValue = useDebounce(value, 500);
302
+ ```
303
+
304
+ ### 4. Use pace-core Utilities
305
+
306
+ Leverage formatting, validation, and other utilities from pace-core:
307
+
308
+ ```typescript
309
+ // ❌ Avoid
310
+ const formatted = new Intl.DateTimeFormat('en-US').format(date);
311
+
312
+ // ✅ Use pace-core
313
+ import { formatDate } from '@jmruthers/pace-core';
314
+ const formatted = formatDate(date);
315
+ ```
316
+
317
+ ### 5. Check Before Creating New Components
318
+
319
+ Before creating a new component, check if pace-core already provides it:
320
+
321
+ 1. Review the [pace-core documentation](https://github.com/your-org/pace-core)
322
+ 2. Check `core-usage-manifest.json` for available components
323
+ 3. Search pace-core exports
324
+
325
+ ## Troubleshooting
326
+
327
+ ### ESLint Rules Not Working
328
+
329
+ 1. **Verify plugin is loaded**: Check that the config is imported correctly:
330
+ ```javascript
331
+ // In your eslint.config.js, add temporarily:
332
+ import paceCoreConfig from '@jmruthers/pace-core/eslint-config';
333
+ console.log('Config:', paceCoreConfig);
334
+ console.log('Has plugins:', paceCoreConfig[0]?.plugins);
335
+ ```
336
+
337
+ 2. **Check rule names**: Rules must be prefixed with `pace-core-compliance/`
338
+
339
+ 3. **Verify manifest exists**: Rules load from `core-usage-manifest.json` in the pace-core package
340
+
341
+ 4. **CommonJS/ES Module issues**: If you're using ES modules and rules aren't loading:
342
+ - Ensure you're using the config preset (Option 1) which handles this automatically
343
+ - Or use `createRequire` when importing rules directly (Option 2)
344
+
345
+ 5. **Verify rules are available**: Check that the rules file exists:
346
+ ```bash
347
+ ls node_modules/@jmruthers/pace-core/dist/eslint-rules/pace-core-compliance.cjs
348
+ ```
349
+
350
+ ### Static Analysis Script Errors
351
+
352
+ 1. **Manifest not found**: Ensure `core-usage-manifest.json` exists in pace-core package
353
+ 2. **No files scanned**: Check that you're running from the project root
354
+ 3. **Permission errors**: Ensure script has read access to source files
355
+
356
+ ### False Positives
357
+
358
+ If you encounter false positives:
359
+
360
+ 1. **Component name conflicts**: If you have a legitimate reason to create a local component with a pace-core name, consider:
361
+ - Renaming your component
362
+ - Using a namespace/prefix
363
+ - Documenting why pace-core doesn't meet your needs
364
+
365
+ 2. **Hook/Util patterns**: The pattern matching may flag similar names. Review the suggestion and decide if migration makes sense.
366
+
367
+ ## Integration with CI/CD
368
+
369
+ While CI/CD integration is not included in the initial release, you can easily add it:
370
+
371
+ ```yaml
372
+ # .github/workflows/pace-core-compliance.yml
373
+ name: pace-core Compliance
374
+
375
+ on: [push, pull_request]
376
+
377
+ jobs:
378
+ compliance:
379
+ runs-on: ubuntu-latest
380
+ steps:
381
+ - uses: actions/checkout@v3
382
+ - uses: actions/setup-node@v3
383
+ - run: npm ci
384
+ - run: npm run check:pace-core
385
+ - run: npm run lint
386
+ ```
387
+
388
+ ## Verification
389
+
390
+ After setting up the ESLint config, verify it's working:
391
+
392
+ 1. **Check ESLint can load the config**:
393
+ ```bash
394
+ npx eslint --print-config src/App.tsx
395
+ ```
396
+ Look for `pace-core-compliance` in the plugins section.
397
+
398
+ 2. **Test with a violation**: Create a test file that imports a restricted library:
399
+ ```typescript
400
+ // test-violation.ts
401
+ import { Dialog } from '@radix-ui/react-dialog'; // Should trigger error
402
+ ```
403
+ Run ESLint and verify it reports the violation.
404
+
405
+ 3. **Run static analysis**: Use the compliance script to get a full report:
406
+ ```bash
407
+ npm run check:pace-core
408
+ ```
409
+
410
+ ## Getting Help
411
+
412
+ - **Documentation**: See [pace-core docs](../README.md)
413
+ - **Issues**: Report false positives or rule issues
414
+ - **Manifest**: Check `core-usage-manifest.json` for available exports
415
+ - **ESLint Config**: The config file is at `@jmruthers/pace-core/eslint-config` (CommonJS)
416
+ - **ESLint Rules**: The rules are at `@jmruthers/pace-core/eslint-rules` (CommonJS)
417
+
418
+ ## File Locations
419
+
420
+ When installed in a consuming app, the compliance tools are available at:
421
+
422
+ - **ESLint Config**: `node_modules/@jmruthers/pace-core/eslint-config-pace-core.cjs`
423
+ - **ESLint Rules**: `node_modules/@jmruthers/pace-core/dist/eslint-rules/pace-core-compliance.cjs`
424
+ - **Static Analysis Script**: `node_modules/@jmruthers/pace-core/scripts/check-pace-core-compliance.cjs`
425
+ - **Manifest**: `node_modules/@jmruthers/pace-core/core-usage-manifest.json`
426
+
427
+ ## Related Documentation
428
+
429
+ - [Component Standards](./03-component-standard.md)
430
+ - [API & RPC Standards](./02-api-and-rpc-standard.md)
431
+ - [Getting Started Guide](../getting-started/README.md)
432
+