@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,606 @@
|
|
|
1
|
+
# Architecture Standards
|
|
2
|
+
|
|
3
|
+
**🤖 Cursor Rule**: See [03-architecture.mdc](../../cursor-rules/03-architecture.mdc) for AI-optimized directives that automatically enforce SOLID principles and architectural patterns.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Define the core architectural principles and SOLID expectations for pace-core and consuming apps so components, APIs, and utilities evolve consistently and sustainably.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Architectural Principles
|
|
12
|
+
|
|
13
|
+
1. **Composition over complexity** - Build complex features from simple, composable pieces
|
|
14
|
+
2. **Separation of concerns** - Each module has a single, well-defined responsibility
|
|
15
|
+
3. **Domain-agnostic core** - pace-core provides generic primitives, not business logic
|
|
16
|
+
4. **Extensible, stable APIs** - APIs should be easy to extend without breaking existing code
|
|
17
|
+
5. **Secure by default** - Security is built-in, not bolted on
|
|
18
|
+
6. **Performance-conscious** - Consider performance implications in design decisions
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## SOLID Principles
|
|
23
|
+
|
|
24
|
+
### Single Responsibility Principle
|
|
25
|
+
|
|
26
|
+
**Each module has one reason to change; extract complex logic to hooks/services.**
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// ❌ WRONG - Component doing too much
|
|
30
|
+
function EventCard({ eventId }: { eventId: string }) {
|
|
31
|
+
const [event, setEvent] = useState(null);
|
|
32
|
+
const [loading, setLoading] = useState(true);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
// Data fetching logic
|
|
36
|
+
fetchEvent(eventId).then(setEvent).finally(() => setLoading(false));
|
|
37
|
+
}, [eventId]);
|
|
38
|
+
|
|
39
|
+
// Formatting logic
|
|
40
|
+
const formattedDate = new Intl.DateTimeFormat('en-US').format(new Date(event.date));
|
|
41
|
+
|
|
42
|
+
// Permission checking logic
|
|
43
|
+
const canEdit = checkPermission('edit', event);
|
|
44
|
+
|
|
45
|
+
// Rendering
|
|
46
|
+
return <div>...</div>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ✅ CORRECT - Separated concerns
|
|
50
|
+
function EventCard({ eventId }: { eventId: string }) {
|
|
51
|
+
const { event, isLoading } = useEventData(eventId); // Hook handles data fetching
|
|
52
|
+
const formattedDate = formatDate(event?.date); // Utility handles formatting
|
|
53
|
+
const { canEdit } = useResourcePermissions(RESOURCE_NAMES.EVENTS); // Hook handles permissions
|
|
54
|
+
|
|
55
|
+
if (isLoading) return <Loading />;
|
|
56
|
+
return <div>...</div>;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Open/Closed Principle
|
|
61
|
+
|
|
62
|
+
**Extend via composition/configuration, avoid modifying shared primitives.**
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// ❌ WRONG - Modifying base component
|
|
66
|
+
function CustomButton({ children, ...props }) {
|
|
67
|
+
return (
|
|
68
|
+
<Button {...props} className="custom-style">
|
|
69
|
+
{children}
|
|
70
|
+
</Button>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ✅ CORRECT - Extending via composition
|
|
75
|
+
function CustomButton({ children, variant = 'default', ...props }) {
|
|
76
|
+
return (
|
|
77
|
+
<Button variant={variant} {...props}>
|
|
78
|
+
{children}
|
|
79
|
+
</Button>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ✅ CORRECT - Using configuration/props
|
|
84
|
+
<Button variant="custom" className="additional-styles">
|
|
85
|
+
Click me
|
|
86
|
+
</Button>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Liskov Substitution Principle
|
|
90
|
+
|
|
91
|
+
**Derived types/components must satisfy the base contract.**
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// ✅ CORRECT - Derived component satisfies base contract
|
|
95
|
+
interface BaseButtonProps {
|
|
96
|
+
onClick?: () => void;
|
|
97
|
+
disabled?: boolean;
|
|
98
|
+
children: React.ReactNode;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function PrimaryButton({ onClick, disabled, children }: BaseButtonProps) {
|
|
102
|
+
return (
|
|
103
|
+
<Button variant="primary" onClick={onClick} disabled={disabled}>
|
|
104
|
+
{children}
|
|
105
|
+
</Button>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Can be used anywhere BaseButtonProps is expected
|
|
110
|
+
function ButtonGroup({ buttons }: { buttons: BaseButtonProps[] }) {
|
|
111
|
+
return buttons.map((props, i) => <PrimaryButton key={i} {...props} />);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Interface Segregation Principle
|
|
116
|
+
|
|
117
|
+
**Prefer focused interfaces/props over catch-all configs.**
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// ❌ WRONG - Catch-all config object
|
|
121
|
+
interface ComponentProps {
|
|
122
|
+
config: {
|
|
123
|
+
variant?: string;
|
|
124
|
+
size?: string;
|
|
125
|
+
color?: string;
|
|
126
|
+
disabled?: boolean;
|
|
127
|
+
onClick?: () => void;
|
|
128
|
+
// ... many more
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ✅ CORRECT - Focused, specific props
|
|
133
|
+
interface ComponentProps {
|
|
134
|
+
variant?: 'default' | 'primary' | 'secondary';
|
|
135
|
+
size?: 'sm' | 'md' | 'lg';
|
|
136
|
+
disabled?: boolean;
|
|
137
|
+
onClick?: () => void;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Dependency Inversion Principle
|
|
142
|
+
|
|
143
|
+
**Depend on abstractions (types/interfaces); inject implementations.**
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
// ✅ CORRECT - Depend on interface, not implementation
|
|
147
|
+
interface DataService {
|
|
148
|
+
fetchEvent(id: string): Promise<Event>;
|
|
149
|
+
updateEvent(id: string, data: Partial<Event>): Promise<Event>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function useEventData(eventId: string, service: DataService) {
|
|
153
|
+
const [event, setEvent] = useState<Event | null>(null);
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
service.fetchEvent(eventId).then(setEvent);
|
|
157
|
+
}, [eventId, service]);
|
|
158
|
+
|
|
159
|
+
return { event };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Can inject different implementations
|
|
163
|
+
const supabaseService: DataService = { /* ... */ };
|
|
164
|
+
const mockService: DataService = { /* ... */ };
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Component Design Principles
|
|
170
|
+
|
|
171
|
+
### Stateless When Possible
|
|
172
|
+
|
|
173
|
+
**Keep components stateless and composable. Move state to hooks.**
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
// ❌ WRONG - Component manages state
|
|
177
|
+
function Counter() {
|
|
178
|
+
const [count, setCount] = useState(0);
|
|
179
|
+
return <button onClick={() => setCount(count + 1)}>{count}</button>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ✅ CORRECT - State in hook, component is presentational
|
|
183
|
+
function useCounter(initial = 0) {
|
|
184
|
+
const [count, setCount] = useState(initial);
|
|
185
|
+
const increment = () => setCount(c => c + 1);
|
|
186
|
+
return { count, increment };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function Counter() {
|
|
190
|
+
const { count, increment } = useCounter();
|
|
191
|
+
return <Button onClick={increment}>{count}</Button>;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Accessible by Default
|
|
196
|
+
|
|
197
|
+
**Components must be accessible with correct roles, keyboard support, and visible focus.**
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
// ✅ CORRECT - Accessible component
|
|
201
|
+
function AccessibleButton({ children, onClick, disabled }: ButtonProps) {
|
|
202
|
+
return (
|
|
203
|
+
<button
|
|
204
|
+
onClick={onClick}
|
|
205
|
+
disabled={disabled}
|
|
206
|
+
role="button"
|
|
207
|
+
aria-disabled={disabled}
|
|
208
|
+
tabIndex={disabled ? -1 : 0}
|
|
209
|
+
className="focus:outline focus:outline-2 focus:outline-main-500"
|
|
210
|
+
>
|
|
211
|
+
{children}
|
|
212
|
+
</button>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### UI Primitives Only
|
|
218
|
+
|
|
219
|
+
**Never add domain logic or data fetching inside components. Use hooks/services instead.**
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
// ❌ WRONG - Domain logic in component
|
|
223
|
+
function EventCard({ eventId }: { eventId: string }) {
|
|
224
|
+
const [event, setEvent] = useState(null);
|
|
225
|
+
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
// Domain-specific logic
|
|
228
|
+
if (eventId.startsWith('EVT-')) {
|
|
229
|
+
fetchEvent(eventId).then(setEvent);
|
|
230
|
+
}
|
|
231
|
+
}, [eventId]);
|
|
232
|
+
|
|
233
|
+
return <div>{event?.name}</div>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ✅ CORRECT - Logic in hook
|
|
237
|
+
function useEventData(eventId: string) {
|
|
238
|
+
const [event, setEvent] = useState(null);
|
|
239
|
+
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
if (eventId.startsWith('EVT-')) {
|
|
242
|
+
fetchEvent(eventId).then(setEvent);
|
|
243
|
+
}
|
|
244
|
+
}, [eventId]);
|
|
245
|
+
|
|
246
|
+
return { event };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function EventCard({ eventId }: { eventId: string }) {
|
|
250
|
+
const { event } = useEventData(eventId);
|
|
251
|
+
return <div>{event?.name}</div>;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Support Controlled + Uncontrolled Usage
|
|
256
|
+
|
|
257
|
+
**Components should work in both controlled and uncontrolled modes.**
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
// ✅ CORRECT - Supports both modes
|
|
261
|
+
interface InputProps {
|
|
262
|
+
value?: string; // Controlled
|
|
263
|
+
defaultValue?: string; // Uncontrolled
|
|
264
|
+
onChange?: (value: string) => void;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function Input({ value, defaultValue, onChange }: InputProps) {
|
|
268
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
|
|
269
|
+
const isControlled = value !== undefined;
|
|
270
|
+
const currentValue = isControlled ? value : internalValue;
|
|
271
|
+
|
|
272
|
+
const handleChange = (newValue: string) => {
|
|
273
|
+
if (!isControlled) {
|
|
274
|
+
setInternalValue(newValue);
|
|
275
|
+
}
|
|
276
|
+
onChange?.(newValue);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return <input value={currentValue} onChange={e => handleChange(e.target.value)} />;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Real-World Example: Search Input Component**
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
// ✅ CORRECT - Real-world search input with debouncing
|
|
287
|
+
import { useDebounce } from '@jmruthers/pace-core';
|
|
288
|
+
import { Input } from '@jmruthers/pace-core';
|
|
289
|
+
|
|
290
|
+
interface SearchInputProps {
|
|
291
|
+
value?: string;
|
|
292
|
+
defaultValue?: string;
|
|
293
|
+
onSearch?: (query: string) => void;
|
|
294
|
+
placeholder?: string;
|
|
295
|
+
debounceMs?: number;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function SearchInput({
|
|
299
|
+
value,
|
|
300
|
+
defaultValue,
|
|
301
|
+
onSearch,
|
|
302
|
+
placeholder = 'Search...',
|
|
303
|
+
debounceMs = 300,
|
|
304
|
+
}: SearchInputProps) {
|
|
305
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
|
|
306
|
+
const isControlled = value !== undefined;
|
|
307
|
+
const currentValue = isControlled ? value : internalValue;
|
|
308
|
+
|
|
309
|
+
// Debounce search calls
|
|
310
|
+
const debouncedValue = useDebounce(currentValue, debounceMs);
|
|
311
|
+
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
if (debouncedValue && onSearch) {
|
|
314
|
+
onSearch(debouncedValue);
|
|
315
|
+
}
|
|
316
|
+
}, [debouncedValue, onSearch]);
|
|
317
|
+
|
|
318
|
+
const handleChange = (newValue: string) => {
|
|
319
|
+
if (!isControlled) {
|
|
320
|
+
setInternalValue(newValue);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<Input
|
|
326
|
+
value={currentValue}
|
|
327
|
+
onChange={handleChange}
|
|
328
|
+
placeholder={placeholder}
|
|
329
|
+
type="search"
|
|
330
|
+
aria-label="Search"
|
|
331
|
+
/>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Usage examples:
|
|
336
|
+
// Controlled mode (parent manages state)
|
|
337
|
+
function ControlledSearch() {
|
|
338
|
+
const [query, setQuery] = useState('');
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<SearchInput
|
|
342
|
+
value={query}
|
|
343
|
+
onSearch={(q) => {
|
|
344
|
+
setQuery(q);
|
|
345
|
+
// Trigger search API call
|
|
346
|
+
}}
|
|
347
|
+
/>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Uncontrolled mode (component manages its own state)
|
|
352
|
+
function UncontrolledSearch() {
|
|
353
|
+
return (
|
|
354
|
+
<SearchInput
|
|
355
|
+
defaultValue=""
|
|
356
|
+
onSearch={(q) => {
|
|
357
|
+
// Trigger search API call
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Small Surface Area
|
|
365
|
+
|
|
366
|
+
**Keep component APIs small and focused. Prefer composition over configuration.**
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
// ❌ WRONG - Too many props, complex API
|
|
370
|
+
interface ComplexComponentProps {
|
|
371
|
+
variant: 'a' | 'b' | 'c' | 'd' | 'e';
|
|
372
|
+
size: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
373
|
+
color: 'red' | 'blue' | 'green' | 'yellow';
|
|
374
|
+
position: 'top' | 'bottom' | 'left' | 'right';
|
|
375
|
+
animation: 'fade' | 'slide' | 'zoom';
|
|
376
|
+
// ... 20 more props
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ✅ CORRECT - Small, focused API
|
|
380
|
+
interface SimpleComponentProps {
|
|
381
|
+
variant?: 'default' | 'primary';
|
|
382
|
+
children: React.ReactNode;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Complex behavior via composition
|
|
386
|
+
function ComplexLayout() {
|
|
387
|
+
return (
|
|
388
|
+
<Container>
|
|
389
|
+
<Header variant="primary" />
|
|
390
|
+
<Content />
|
|
391
|
+
<Footer />
|
|
392
|
+
</Container>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## API Design Principles
|
|
400
|
+
|
|
401
|
+
### Consistent Naming Conventions
|
|
402
|
+
|
|
403
|
+
**Follow standard naming patterns for RPCs and APIs.**
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// ✅ CORRECT - Consistent naming
|
|
407
|
+
// Format: <family>_<domain>_<verb>
|
|
408
|
+
data_events_list
|
|
409
|
+
data_events_get
|
|
410
|
+
app_events_create
|
|
411
|
+
app_events_update
|
|
412
|
+
app_events_delete
|
|
413
|
+
app_events_bulk_create // Bulk operations use _bulk_ prefix
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Type-Safe Results
|
|
417
|
+
|
|
418
|
+
**Always use type-safe result types for APIs.**
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
// ✅ CORRECT - Type-safe result type
|
|
422
|
+
type ApiResult<T> =
|
|
423
|
+
| { ok: true; data: T }
|
|
424
|
+
| { ok: false; error: ApiError };
|
|
425
|
+
|
|
426
|
+
async function fetchEvent(id: string): Promise<ApiResult<Event>> {
|
|
427
|
+
try {
|
|
428
|
+
const { data, error } = await supabase.from('events').select('*').eq('id', id).single();
|
|
429
|
+
if (error) {
|
|
430
|
+
return { ok: false, error: { code: 'NOT_FOUND', message: 'Event not found' } };
|
|
431
|
+
}
|
|
432
|
+
return { ok: true, data };
|
|
433
|
+
} catch (error) {
|
|
434
|
+
return { ok: false, error: { code: 'UNKNOWN', message: 'An error occurred' } };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Idempotent Writes
|
|
440
|
+
|
|
441
|
+
**Write operations should be idempotent when possible.**
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
// ✅ CORRECT - Idempotent update
|
|
445
|
+
async function updateEvent(id: string, data: Partial<Event>): Promise<ApiResult<Event>> {
|
|
446
|
+
// Using upsert makes this idempotent
|
|
447
|
+
const { data: event, error } = await supabase
|
|
448
|
+
.from('events')
|
|
449
|
+
.upsert({ id, ...data }, { onConflict: 'id' })
|
|
450
|
+
.select()
|
|
451
|
+
.single();
|
|
452
|
+
|
|
453
|
+
if (error) {
|
|
454
|
+
return { ok: false, error: { code: 'UPDATE_FAILED', message: error.message } };
|
|
455
|
+
}
|
|
456
|
+
return { ok: true, data: event };
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Read RPCs Never Mutate
|
|
461
|
+
|
|
462
|
+
**Read operations must never have side effects.**
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// ❌ WRONG - Read operation with side effects
|
|
466
|
+
async function getEvent(id: string): Promise<Event> {
|
|
467
|
+
// Side effect: updates last_accessed
|
|
468
|
+
await supabase.from('events').update({ last_accessed: new Date() }).eq('id', id);
|
|
469
|
+
const { data } = await supabase.from('events').select('*').eq('id', id).single();
|
|
470
|
+
return data;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ✅ CORRECT - Pure read operation
|
|
474
|
+
async function getEvent(id: string): Promise<ApiResult<Event>> {
|
|
475
|
+
const { data, error } = await supabase.from('events').select('*').eq('id', id).single();
|
|
476
|
+
if (error) {
|
|
477
|
+
return { ok: false, error: { code: 'NOT_FOUND', message: 'Event not found' } };
|
|
478
|
+
}
|
|
479
|
+
return { ok: true, data };
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Never Accept Dynamic SQL
|
|
484
|
+
|
|
485
|
+
**Never accept SQL strings as parameters. Use parameterized queries or RPCs.**
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
// ❌ WRONG - Dynamic SQL injection risk
|
|
489
|
+
async function executeQuery(sql: string): Promise<any> {
|
|
490
|
+
return supabase.rpc('execute_sql', { sql });
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ✅ CORRECT - Parameterized query
|
|
494
|
+
async function getEvents(filters: EventFilters): Promise<ApiResult<Event[]>> {
|
|
495
|
+
let query = supabase.from('events').select('*');
|
|
496
|
+
|
|
497
|
+
if (filters.organisationId) {
|
|
498
|
+
query = query.eq('organisation_id', filters.organisationId);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (filters.status) {
|
|
502
|
+
query = query.eq('status', filters.status);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const { data, error } = await query;
|
|
506
|
+
// ...
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Performance & RLS Boundaries
|
|
513
|
+
|
|
514
|
+
**Note:** Detailed RLS performance requirements are covered in [Security & RBAC Standards](./6-security-rbac-standards.md). This section provides a brief overview.
|
|
515
|
+
|
|
516
|
+
### RLS Helper Functions
|
|
517
|
+
|
|
518
|
+
**Policies must rely on helper functions (no subqueries) to avoid N+1/per-row overhead.**
|
|
519
|
+
|
|
520
|
+
See [Security & RBAC Standards](./6-security-rbac-standards.md) for detailed RLS policy patterns and helper function requirements.
|
|
521
|
+
|
|
522
|
+
### Test Migrations
|
|
523
|
+
|
|
524
|
+
**Verify DB migrations for performance regressions and timeouts.**
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
# Run migrations with timeout
|
|
528
|
+
timeout 120 npm run test:db
|
|
529
|
+
|
|
530
|
+
# Check for performance issues
|
|
531
|
+
supabase advisors performance
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Monitor Queries
|
|
535
|
+
|
|
536
|
+
**Use EXPLAIN/Advisors to ensure policies don't introduce InitPlan nodes.**
|
|
537
|
+
|
|
538
|
+
See [Security & RBAC Standards](./6-security-rbac-standards.md) for detailed RLS performance monitoring requirements.
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## In/Out of Scope
|
|
543
|
+
|
|
544
|
+
### In Scope
|
|
545
|
+
|
|
546
|
+
pace-core provides:
|
|
547
|
+
|
|
548
|
+
- **UI primitives** - Buttons, inputs, cards, dialogs, etc.
|
|
549
|
+
- **Generic hooks** - `useDebounce`, `useToast`, `useUnifiedAuth`, etc.
|
|
550
|
+
- **Shared API patterns** - Result types, error handling, RPC conventions
|
|
551
|
+
- **Error-handling conventions** - Consistent error shapes and recovery
|
|
552
|
+
- **RPC shape conventions** - Naming, parameterization, idempotency
|
|
553
|
+
|
|
554
|
+
### Out of Scope
|
|
555
|
+
|
|
556
|
+
pace-core does NOT provide:
|
|
557
|
+
|
|
558
|
+
- **App/domain-specific logic** - Business rules, workflows, domain models
|
|
559
|
+
- **App-specific styling** - Custom themes, brand colors (apps define these)
|
|
560
|
+
- **Business workflows** - Order processing, user onboarding, etc.
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Precedence
|
|
565
|
+
|
|
566
|
+
When architectural decisions conflict, apply this precedence:
|
|
567
|
+
|
|
568
|
+
1. **Security** - Security requirements override all others
|
|
569
|
+
2. **API/RPC** - API contracts must be stable and consistent
|
|
570
|
+
3. **Components** - Component APIs should be simple and composable
|
|
571
|
+
4. **Code Style** - Code style and patterns
|
|
572
|
+
5. **Testing** - Testing requirements
|
|
573
|
+
6. **Documentation** - Documentation requirements
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## Cursor Checklist
|
|
578
|
+
|
|
579
|
+
When making architectural changes, verify:
|
|
580
|
+
|
|
581
|
+
- [ ] Changes fit boundaries (no domain logic in core primitives)
|
|
582
|
+
- [ ] Follow SOLID guidance above
|
|
583
|
+
- [ ] Prefer additive changes; avoid breaking contracts
|
|
584
|
+
- [ ] Keep helpers small, pure, and typed
|
|
585
|
+
- [ ] Components are stateless when possible
|
|
586
|
+
- [ ] Components are accessible by default
|
|
587
|
+
- [ ] APIs use type-safe result types
|
|
588
|
+
- [ ] RPCs follow naming conventions
|
|
589
|
+
- [ ] Write operations are idempotent when possible
|
|
590
|
+
- [ ] No dynamic SQL in APIs
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## Related Documentation
|
|
595
|
+
|
|
596
|
+
- [Standards Overview](./0-standards-overview.md) - Standards system overview
|
|
597
|
+
- [Code Quality](./4-code-quality-standards.md) - TypeScript and code patterns
|
|
598
|
+
- [API & Tech Stack](./7-api-tech-stack-standards.md) - API and RPC standards
|
|
599
|
+
- [Security & RBAC](./6-security-rbac-standards.md) - Security and RLS patterns
|
|
600
|
+
- [Styling](./5-styling-standards.md) - Component styling patterns
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
**Last Updated:** 2025-01-28
|
|
605
|
+
**Version:** 2.0.0
|
|
606
|
+
**Applies to:** All pace-core and consuming apps
|