@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.
- package/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
# Code Quality Standards
|
|
2
|
+
|
|
3
|
+
**🤖 Cursor Rule**: See [04-code-quality.mdc](../../cursor-rules/04-code-quality.mdc) for AI-optimized directives that automatically enforce code quality standards.
|
|
4
|
+
|
|
5
|
+
**🔧 ESLint Rules**: See [04-code-quality.cjs](../../eslint-rules/rules/04-code-quality.cjs) for mechanically checkable code quality rules.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
This standard defines TypeScript rules, naming conventions, and code style patterns to ensure consistent, maintainable, and type-safe code across pace-core and consuming apps.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## TypeScript Rules
|
|
14
|
+
|
|
15
|
+
### No `any` Type
|
|
16
|
+
|
|
17
|
+
**Never use `any`. Use `unknown` for truly unknown types, then narrow with type guards.**
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// ❌ WRONG - Using any
|
|
21
|
+
function processData(data: any) {
|
|
22
|
+
return data.value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ✅ CORRECT - Using unknown with type guard
|
|
26
|
+
function isDataObject(value: unknown): value is { value: string } {
|
|
27
|
+
return (
|
|
28
|
+
typeof value === 'object' &&
|
|
29
|
+
value !== null &&
|
|
30
|
+
'value' in value &&
|
|
31
|
+
typeof (value as { value: unknown }).value === 'string'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function processData(data: unknown) {
|
|
36
|
+
if (isDataObject(data)) {
|
|
37
|
+
return data.value; // TypeScript knows data.value is string
|
|
38
|
+
}
|
|
39
|
+
throw new Error('Invalid data');
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Prefer Discriminated Unions
|
|
44
|
+
|
|
45
|
+
**Use discriminated unions instead of boolean mode flags.**
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// ❌ WRONG - Boolean flag
|
|
49
|
+
interface ComponentProps {
|
|
50
|
+
mode: 'create' | 'edit';
|
|
51
|
+
isEditing: boolean; // Redundant with mode
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ✅ CORRECT - Discriminated union
|
|
55
|
+
type ComponentProps =
|
|
56
|
+
| { mode: 'create'; initialData?: never }
|
|
57
|
+
| { mode: 'edit'; initialData: Event };
|
|
58
|
+
|
|
59
|
+
function Component(props: ComponentProps) {
|
|
60
|
+
if (props.mode === 'create') {
|
|
61
|
+
// TypeScript knows initialData doesn't exist
|
|
62
|
+
return <CreateForm />;
|
|
63
|
+
}
|
|
64
|
+
// TypeScript knows initialData exists
|
|
65
|
+
return <EditForm data={props.initialData} />;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Avoid Type Assertions
|
|
70
|
+
|
|
71
|
+
**Avoid type assertions unless in escape hatches. Prefer type guards.**
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// ❌ WRONG - Type assertion
|
|
75
|
+
function getValue(data: unknown): string {
|
|
76
|
+
return (data as { value: string }).value; // Unsafe
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ✅ CORRECT - Type guard
|
|
80
|
+
function isValueObject(data: unknown): data is { value: string } {
|
|
81
|
+
return (
|
|
82
|
+
typeof data === 'object' &&
|
|
83
|
+
data !== null &&
|
|
84
|
+
'value' in data &&
|
|
85
|
+
typeof (data as { value: unknown }).value === 'string'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getValue(data: unknown): string {
|
|
90
|
+
if (isValueObject(data)) {
|
|
91
|
+
return data.value; // Type-safe
|
|
92
|
+
}
|
|
93
|
+
throw new Error('Invalid data');
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Use ReadonlyArray Where Possible
|
|
98
|
+
|
|
99
|
+
**Prefer `ReadonlyArray` for arrays that shouldn't be mutated.**
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// ❌ WRONG - Mutable array
|
|
103
|
+
function processItems(items: string[]) {
|
|
104
|
+
items.push('new'); // Mutates input
|
|
105
|
+
return items;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ✅ CORRECT - Readonly array
|
|
109
|
+
function processItems(items: ReadonlyArray<string>): string[] {
|
|
110
|
+
return [...items, 'new']; // Creates new array
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Avoid Implicit `any`
|
|
115
|
+
|
|
116
|
+
**Always provide explicit types. Enable `strict` mode in TypeScript.**
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
// tsconfig.json
|
|
120
|
+
{
|
|
121
|
+
"compilerOptions": {
|
|
122
|
+
"strict": true,
|
|
123
|
+
"noImplicitAny": true,
|
|
124
|
+
"strictNullChecks": true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Naming Conventions
|
|
132
|
+
|
|
133
|
+
### Hooks
|
|
134
|
+
|
|
135
|
+
**Hooks must start with `use` prefix and use camelCase.**
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// ✅ CORRECT
|
|
139
|
+
export function useEventData() { }
|
|
140
|
+
export function useUserProfile() { }
|
|
141
|
+
export function useDebounce() { }
|
|
142
|
+
|
|
143
|
+
// ❌ WRONG
|
|
144
|
+
export function getEventData() { } // Not a hook
|
|
145
|
+
export function UseEventData() { } // Wrong case
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Providers
|
|
149
|
+
|
|
150
|
+
**Providers must end with `Provider` suffix and use PascalCase.**
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// ✅ CORRECT
|
|
154
|
+
export function UnifiedAuthProvider() { }
|
|
155
|
+
export function QueryProvider() { }
|
|
156
|
+
export function ThemeProvider() { }
|
|
157
|
+
|
|
158
|
+
// ❌ WRONG
|
|
159
|
+
export function unifiedAuthProvider() { } // Wrong case
|
|
160
|
+
export function UnifiedAuth() { } // Missing Provider suffix
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Utilities
|
|
164
|
+
|
|
165
|
+
**Utilities use camelCase and should be pure functions when possible.**
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// ✅ CORRECT
|
|
169
|
+
export function formatDate(date: Date): string { }
|
|
170
|
+
export function validateEmail(email: string): boolean { }
|
|
171
|
+
export function calculateTotal(items: Item[]): number { }
|
|
172
|
+
|
|
173
|
+
// ❌ WRONG
|
|
174
|
+
export function FormatDate() { } // Wrong case
|
|
175
|
+
export function format_date() { } // Wrong case
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Components
|
|
179
|
+
|
|
180
|
+
**Components use PascalCase.**
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// ✅ CORRECT
|
|
184
|
+
export function EventCard() { }
|
|
185
|
+
export function UserProfile() { }
|
|
186
|
+
export const Button = () => { };
|
|
187
|
+
|
|
188
|
+
// ❌ WRONG
|
|
189
|
+
export function eventCard() { } // Wrong case
|
|
190
|
+
export function event_card() { } // Wrong case
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Types and Interfaces
|
|
194
|
+
|
|
195
|
+
**Types and interfaces use PascalCase. Prefer `type` for unions/intersections, `interface` for objects.**
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// ✅ CORRECT
|
|
199
|
+
export type EventStatus = 'draft' | 'published' | 'archived';
|
|
200
|
+
export interface Event {
|
|
201
|
+
id: string;
|
|
202
|
+
title: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ❌ WRONG
|
|
206
|
+
export type eventStatus = 'draft' | 'published'; // Wrong case
|
|
207
|
+
export interface event { } // Wrong case
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Preferred Patterns
|
|
213
|
+
|
|
214
|
+
### Pure Functions
|
|
215
|
+
|
|
216
|
+
**Prefer pure functions that don't have side effects.**
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// ✅ CORRECT - Pure function
|
|
220
|
+
function calculateTotal(items: ReadonlyArray<Item>): number {
|
|
221
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ❌ WRONG - Side effect
|
|
225
|
+
function calculateTotal(items: Item[]): number {
|
|
226
|
+
let total = 0;
|
|
227
|
+
items.forEach(item => {
|
|
228
|
+
total += item.price;
|
|
229
|
+
console.log(item); // Side effect
|
|
230
|
+
});
|
|
231
|
+
return total;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Composition Over Inheritance
|
|
236
|
+
|
|
237
|
+
**Prefer composition and functional patterns over class inheritance.**
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// ❌ WRONG - Class inheritance
|
|
241
|
+
class BaseService {
|
|
242
|
+
protected baseUrl: string;
|
|
243
|
+
constructor(baseUrl: string) {
|
|
244
|
+
this.baseUrl = baseUrl;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
class EventService extends BaseService {
|
|
249
|
+
getEvents() {
|
|
250
|
+
return fetch(`${this.baseUrl}/events`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ✅ CORRECT - Composition
|
|
255
|
+
function createApiClient(baseUrl: string) {
|
|
256
|
+
return {
|
|
257
|
+
get: (path: string) => fetch(`${baseUrl}${path}`),
|
|
258
|
+
post: (path: string, data: unknown) => fetch(`${baseUrl}${path}`, {
|
|
259
|
+
method: 'POST',
|
|
260
|
+
body: JSON.stringify(data),
|
|
261
|
+
}),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function createEventService(client: ReturnType<typeof createApiClient>) {
|
|
266
|
+
return {
|
|
267
|
+
getEvents: () => client.get('/events'),
|
|
268
|
+
createEvent: (data: Event) => client.post('/events', data),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Early Returns
|
|
274
|
+
|
|
275
|
+
**Use early returns to reduce nesting and improve readability.**
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// ❌ WRONG - Deep nesting
|
|
279
|
+
function processEvent(event: Event | null): string {
|
|
280
|
+
if (event !== null) {
|
|
281
|
+
if (event.status === 'published') {
|
|
282
|
+
if (event.date > new Date()) {
|
|
283
|
+
return 'Upcoming event';
|
|
284
|
+
} else {
|
|
285
|
+
return 'Past event';
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
return 'Draft event';
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
return 'No event';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ✅ CORRECT - Early returns
|
|
296
|
+
function processEvent(event: Event | null): string {
|
|
297
|
+
if (event === null) {
|
|
298
|
+
return 'No event';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (event.status !== 'published') {
|
|
302
|
+
return 'Draft event';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (event.date > new Date()) {
|
|
306
|
+
return 'Upcoming event';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return 'Past event';
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Real-World Example: Permission Check with Early Returns**
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// ✅ CORRECT - Real-world permission check with early returns
|
|
317
|
+
async function canUserEditEvent(
|
|
318
|
+
userId: string,
|
|
319
|
+
eventId: string,
|
|
320
|
+
secureSupabase: SupabaseClient
|
|
321
|
+
): Promise<boolean> {
|
|
322
|
+
// Early return: No user ID
|
|
323
|
+
if (!userId) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Early return: No event ID
|
|
328
|
+
if (!eventId) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Get event
|
|
333
|
+
const { data: event, error } = await secureSupabase
|
|
334
|
+
.from('events')
|
|
335
|
+
.select('organisation_id, status, created_by')
|
|
336
|
+
.eq('id', eventId)
|
|
337
|
+
.single();
|
|
338
|
+
|
|
339
|
+
// Early return: Event not found
|
|
340
|
+
if (error || !event) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Early return: Event is archived
|
|
345
|
+
if (event.status === 'archived') {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check permissions
|
|
350
|
+
const { canUpdate, isLoading } = useResourcePermissions(RESOURCE_NAMES.EVENTS);
|
|
351
|
+
|
|
352
|
+
// Early return: Permissions still loading
|
|
353
|
+
if (isLoading) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Check if user created the event (always allow edit for creator)
|
|
358
|
+
if (event.created_by === userId) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Check RBAC permissions
|
|
363
|
+
return canUpdate({ organisationId: event.organisation_id });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Usage
|
|
367
|
+
async function handleEditClick(eventId: string) {
|
|
368
|
+
const { user } = await secureSupabase.auth.getUser();
|
|
369
|
+
|
|
370
|
+
if (!user) {
|
|
371
|
+
toast.error('Please log in to edit events');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const canEdit = await canUserEditEvent(user.id, eventId, secureSupabase);
|
|
376
|
+
|
|
377
|
+
if (!canEdit) {
|
|
378
|
+
toast.error('You do not have permission to edit this event');
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
navigate(`/events/${eventId}/edit`);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Small Private Helpers
|
|
387
|
+
|
|
388
|
+
**Extract complex logic into small, focused helper functions.**
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// ❌ WRONG - Complex inline logic
|
|
392
|
+
function formatEventDisplay(event: Event): string {
|
|
393
|
+
const date = new Date(event.date);
|
|
394
|
+
const month = date.toLocaleString('en-US', { month: 'short' });
|
|
395
|
+
const day = date.getDate();
|
|
396
|
+
const year = date.getFullYear();
|
|
397
|
+
const time = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
398
|
+
return `${event.title} - ${month} ${day}, ${year} at ${time}`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ✅ CORRECT - Extracted helpers
|
|
402
|
+
function formatDate(date: Date): string {
|
|
403
|
+
return date.toLocaleString('en-US', {
|
|
404
|
+
month: 'short',
|
|
405
|
+
day: 'numeric',
|
|
406
|
+
year: 'numeric'
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function formatTime(date: Date): string {
|
|
411
|
+
return date.toLocaleTimeString('en-US', {
|
|
412
|
+
hour: '2-digit',
|
|
413
|
+
minute: '2-digit'
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function formatEventDisplay(event: Event): string {
|
|
418
|
+
const date = new Date(event.date);
|
|
419
|
+
return `${event.title} - ${formatDate(date)} at ${formatTime(date)}`;
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Forbidden Patterns
|
|
426
|
+
|
|
427
|
+
### Implicit `any`
|
|
428
|
+
|
|
429
|
+
**Never use implicit `any`. Always provide explicit types.**
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// ❌ WRONG
|
|
433
|
+
function process(data) { // Implicit any
|
|
434
|
+
return data.value;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ✅ CORRECT
|
|
438
|
+
function process(data: { value: string }): string {
|
|
439
|
+
return data.value;
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Bloated Components
|
|
444
|
+
|
|
445
|
+
**Components should be small and focused. Extract logic to hooks/services.**
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
// ❌ WRONG - Bloated component
|
|
449
|
+
function EventPage({ eventId }: { eventId: string }) {
|
|
450
|
+
const [event, setEvent] = useState(null);
|
|
451
|
+
const [loading, setLoading] = useState(true);
|
|
452
|
+
const [error, setError] = useState(null);
|
|
453
|
+
const [permissions, setPermissions] = useState(null);
|
|
454
|
+
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
// 50+ lines of logic
|
|
457
|
+
}, [eventId]);
|
|
458
|
+
|
|
459
|
+
// 200+ lines of JSX
|
|
460
|
+
return <div>...</div>;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ✅ CORRECT - Focused component
|
|
464
|
+
function EventPage({ eventId }: { eventId: string }) {
|
|
465
|
+
const { event, isLoading, error } = useEventData(eventId);
|
|
466
|
+
const { permissions } = useResourcePermissions(RESOURCE_NAMES.EVENTS);
|
|
467
|
+
|
|
468
|
+
if (isLoading) return <Loading />;
|
|
469
|
+
if (error) return <Error error={error} />;
|
|
470
|
+
|
|
471
|
+
return <EventDetails event={event} permissions={permissions} />;
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Domain-Specific Types in pace-core
|
|
476
|
+
|
|
477
|
+
**pace-core must not contain domain-specific types. These belong in consuming apps.**
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
// ❌ WRONG - Domain type in pace-core
|
|
481
|
+
// packages/core/src/types/event.ts
|
|
482
|
+
export interface Event {
|
|
483
|
+
id: string;
|
|
484
|
+
name: string;
|
|
485
|
+
date: Date;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ✅ CORRECT - Generic type in pace-core
|
|
489
|
+
// packages/core/src/types/common.ts
|
|
490
|
+
export interface BaseEntity {
|
|
491
|
+
id: string;
|
|
492
|
+
created_at: string;
|
|
493
|
+
updated_at: string;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ✅ CORRECT - Domain type in consuming app
|
|
497
|
+
// src/types/event.ts
|
|
498
|
+
import type { BaseEntity } from '@jmruthers/pace-core';
|
|
499
|
+
|
|
500
|
+
export interface Event extends BaseEntity {
|
|
501
|
+
name: string;
|
|
502
|
+
date: Date;
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## React Patterns
|
|
509
|
+
|
|
510
|
+
### Functional Components Only
|
|
511
|
+
|
|
512
|
+
**Use functional components with hooks. No class components.**
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
// ❌ WRONG - Class component
|
|
516
|
+
class EventCard extends React.Component<{ event: Event }> {
|
|
517
|
+
render() {
|
|
518
|
+
return <div>{this.props.event.name}</div>;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ✅ CORRECT - Functional component
|
|
523
|
+
function EventCard({ event }: { event: Event }) {
|
|
524
|
+
return <div>{event.name}</div>;
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Proper Hook Dependencies
|
|
529
|
+
|
|
530
|
+
**Always include all dependencies in hook dependency arrays.**
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
// ❌ WRONG - Missing dependencies
|
|
534
|
+
function Component({ id }: { id: string }) {
|
|
535
|
+
const [data, setData] = useState(null);
|
|
536
|
+
|
|
537
|
+
useEffect(() => {
|
|
538
|
+
fetchData(id).then(setData);
|
|
539
|
+
}, []); // Missing id dependency
|
|
540
|
+
|
|
541
|
+
return <div>{data?.name}</div>;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ✅ CORRECT - All dependencies included
|
|
545
|
+
function Component({ id }: { id: string }) {
|
|
546
|
+
const [data, setData] = useState(null);
|
|
547
|
+
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
fetchData(id).then(setData);
|
|
550
|
+
}, [id]); // All dependencies included
|
|
551
|
+
|
|
552
|
+
return <div>{data?.name}</div>;
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Memoization When Appropriate
|
|
557
|
+
|
|
558
|
+
**Use `useMemo` and `useCallback` when values/functions are expensive or passed to memoized children.**
|
|
559
|
+
|
|
560
|
+
```tsx
|
|
561
|
+
// ✅ CORRECT - Memoized expensive computation
|
|
562
|
+
function Component({ items }: { items: ReadonlyArray<Item> }) {
|
|
563
|
+
const total = useMemo(() => {
|
|
564
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
565
|
+
}, [items]);
|
|
566
|
+
|
|
567
|
+
return <div>Total: {total}</div>;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ✅ CORRECT - Memoized callback for memoized child
|
|
571
|
+
const MemoizedChild = React.memo(({ onClick }: { onClick: () => void }) => {
|
|
572
|
+
return <button onClick={onClick}>Click</button>;
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
function Component() {
|
|
576
|
+
const handleClick = useCallback(() => {
|
|
577
|
+
console.log('clicked');
|
|
578
|
+
}, []);
|
|
579
|
+
|
|
580
|
+
return <MemoizedChild onClick={handleClick} />;
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
## Accessibility Requirements
|
|
587
|
+
|
|
588
|
+
**MUST** ensure all components and pages meet WCAG 2.1 Level AA standards.
|
|
589
|
+
|
|
590
|
+
### Core Principles
|
|
591
|
+
|
|
592
|
+
1. **Semantic HTML** - Use semantic elements (`<main>`, `<section>`, `<article>`, `<nav>`, etc.) instead of `<div>` wrappers
|
|
593
|
+
2. **Keyboard Navigation** - All interactive elements must be accessible via keyboard
|
|
594
|
+
3. **ARIA Labels** - Provide clear labels for screen readers when visible text isn't sufficient
|
|
595
|
+
4. **Focus Management** - Visible focus indicators on all interactive elements
|
|
596
|
+
5. **Color Contrast** - Text must meet 4.5:1 contrast ratio (WCAG AA)
|
|
597
|
+
|
|
598
|
+
### Implementation Requirements
|
|
599
|
+
|
|
600
|
+
```tsx
|
|
601
|
+
// ✅ CORRECT - Semantic HTML with ARIA
|
|
602
|
+
<main>
|
|
603
|
+
<h1>Page Title</h1>
|
|
604
|
+
<section aria-labelledby="section-title">
|
|
605
|
+
<h2 id="section-title">Section Title</h2>
|
|
606
|
+
<Button
|
|
607
|
+
aria-label="Delete user"
|
|
608
|
+
aria-describedby="delete-help"
|
|
609
|
+
>
|
|
610
|
+
Delete
|
|
611
|
+
</Button>
|
|
612
|
+
<span id="delete-help" className="sr-only">
|
|
613
|
+
Permanently removes the user account
|
|
614
|
+
</span>
|
|
615
|
+
</section>
|
|
616
|
+
</main>
|
|
617
|
+
|
|
618
|
+
// ❌ WRONG - Non-semantic structure
|
|
619
|
+
<div>
|
|
620
|
+
<div className="title">Page Title</div>
|
|
621
|
+
<div>
|
|
622
|
+
<button>Delete</button>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
### Keyboard Navigation
|
|
628
|
+
|
|
629
|
+
**MUST** ensure all interactive elements are keyboard accessible:
|
|
630
|
+
|
|
631
|
+
```tsx
|
|
632
|
+
// ✅ CORRECT - pace-core components handle keyboard navigation automatically
|
|
633
|
+
import { Button, DataTable } from '@jmruthers/pace-core';
|
|
634
|
+
|
|
635
|
+
<Button onClick={handleClick}>Click me</Button>
|
|
636
|
+
<DataTable data={data} columns={columns} />
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Screen Reader Support
|
|
640
|
+
|
|
641
|
+
**MUST** provide appropriate ARIA attributes:
|
|
642
|
+
|
|
643
|
+
```tsx
|
|
644
|
+
// ✅ CORRECT - ARIA labels for icons
|
|
645
|
+
<Button aria-label="Close dialog">
|
|
646
|
+
<Icon name="x" aria-hidden="true" />
|
|
647
|
+
</Button>
|
|
648
|
+
|
|
649
|
+
// ✅ CORRECT - Error messages with role="alert"
|
|
650
|
+
{error && (
|
|
651
|
+
<div role="alert" aria-live="polite">
|
|
652
|
+
{error.message}
|
|
653
|
+
</div>
|
|
654
|
+
)}
|
|
655
|
+
|
|
656
|
+
// ✅ CORRECT - Loading states
|
|
657
|
+
<div role="status" aria-live="polite" aria-busy={loading}>
|
|
658
|
+
{loading && <span className="sr-only">Loading data...</span>}
|
|
659
|
+
{loading ? <Spinner /> : <Content />}
|
|
660
|
+
</div>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Testing Accessibility
|
|
664
|
+
|
|
665
|
+
**MUST** test with:
|
|
666
|
+
- Keyboard navigation (Tab, Enter, Space, Arrow keys)
|
|
667
|
+
- Screen readers (NVDA, JAWS, VoiceOver)
|
|
668
|
+
- Automated tools (axe DevTools, WAVE, Lighthouse)
|
|
669
|
+
|
|
670
|
+
**Accessibility Checklist:**
|
|
671
|
+
- [ ] All interactive elements keyboard accessible
|
|
672
|
+
- [ ] Visible focus indicators on all interactive elements
|
|
673
|
+
- [ ] ARIA labels provided for icons and images
|
|
674
|
+
- [ ] Semantic HTML used appropriately
|
|
675
|
+
- [ ] Error messages properly associated with form fields
|
|
676
|
+
- [ ] Color contrast meets WCAG AA (4.5:1 for text)
|
|
677
|
+
- [ ] Screen reader announcements work correctly
|
|
678
|
+
- [ ] Focus management in modals and dynamic content
|
|
679
|
+
|
|
680
|
+
## Code Quality Checklist
|
|
681
|
+
|
|
682
|
+
Before committing code, verify:
|
|
683
|
+
|
|
684
|
+
- [ ] No `any` types (use `unknown` with type guards)
|
|
685
|
+
- [ ] No implicit `any` (TypeScript `strict` mode enabled)
|
|
686
|
+
- [ ] Discriminated unions used instead of boolean flags
|
|
687
|
+
- [ ] Type assertions avoided (use type guards)
|
|
688
|
+
- [ ] `ReadonlyArray` used for immutable arrays
|
|
689
|
+
- [ ] Naming conventions followed (hooks: `use*`, providers: `*Provider`, etc.)
|
|
690
|
+
- [ ] Pure functions preferred (no side effects)
|
|
691
|
+
- [ ] Early returns used to reduce nesting
|
|
692
|
+
- [ ] Complex logic extracted to helpers
|
|
693
|
+
- [ ] Components are small and focused
|
|
694
|
+
- [ ] No domain-specific types in pace-core
|
|
695
|
+
- [ ] Functional components only (no class components)
|
|
696
|
+
- [ ] Hook dependencies are complete
|
|
697
|
+
- [ ] Memoization used when appropriate
|
|
698
|
+
- [ ] Accessibility requirements met (WCAG 2.1 AA)
|
|
699
|
+
- [ ] Keyboard navigation supported
|
|
700
|
+
- [ ] ARIA labels provided where needed
|
|
701
|
+
- [ ] Semantic HTML used appropriately
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## ESLint Rules
|
|
706
|
+
|
|
707
|
+
The following ESLint rules enforce code quality standards:
|
|
708
|
+
|
|
709
|
+
### Naming Conventions
|
|
710
|
+
|
|
711
|
+
- **`naming-convention`** - Enforces hook naming (`use*`) and provider naming (`*Provider`)
|
|
712
|
+
|
|
713
|
+
These rules are part of the `pace-core-compliance` plugin and are automatically enabled when you extend `@jmruthers/pace-core/eslint-config`.
|
|
714
|
+
|
|
715
|
+
**Setup**: Run `node node_modules/@jmruthers/pace-core/scripts/install-eslint-config.cjs` to configure ESLint in your consuming app.
|
|
716
|
+
|
|
717
|
+
## Related Documentation
|
|
718
|
+
|
|
719
|
+
- [Standards Overview](./0-standards-overview.md) - Standards system overview
|
|
720
|
+
- [Architecture](./3-architecture-standards.md) - SOLID principles and architecture
|
|
721
|
+
- [API & Tech Stack](./7-api-tech-stack-standards.md) - TypeScript configuration
|
|
722
|
+
- [Testing & Documentation](./8-testing-documentation-standards.md) - Testing patterns
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
**Last Updated:** 2025-01-28
|
|
727
|
+
**Version:** 2.0.0
|
|
728
|
+
**Applies to:** All pace-core and consuming apps
|