@jmruthers/pace-core 0.6.1 → 0.6.2
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 +43 -10
- package/cursor-rules/00-pace-core-compliance.mdc +18 -91
- package/cursor-rules/01-standards-compliance.mdc +16 -47
- package/cursor-rules/02-project-structure.mdc +4 -4
- package/cursor-rules/03-solid-principles.mdc +45 -164
- package/cursor-rules/04-testing-standards.mdc +22 -69
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
- package/cursor-rules/06-code-quality.mdc +42 -125
- package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +18 -0
- package/cursor-rules/README.md +2 -1
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
- package/dist/{DataTable-DQ7RSOHE.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +356 -111
- package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
- package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
- package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
- package/dist/chunk-24UVZUZG.js.map +1 -0
- package/dist/{chunk-4N5C5XZU.js → chunk-2UOI2FG5.js} +4 -4
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-T33XF5ZC.js → chunk-3XC4CPTD.js} +4317 -3963
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-4ZC4GX36.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-BYFSK72L.js → chunk-EHMR7VYL.js} +4 -4
- package/dist/chunk-EHMR7VYL.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-3XTALGJF.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-GLK6VM3F.js → chunk-NECFR5MM.js} +254 -170
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-JBKQ3SAO.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XM25TVIE.js → chunk-XWQCNGTQ.js} +724 -363
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +5 -5
- package/dist/components.js +14 -11
- package/dist/components.js.map +1 -1
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +55 -122
- package/dist/hooks.js +8 -12
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +60 -13
- package/dist/index.js +19 -19
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +145 -114
- package/dist/rbac/index.js +8 -11
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +31 -1
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +14 -14
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +7 -1
- package/docs/api/classes/ColumnFactory.md +8 -8
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +18 -15
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +9 -2
- package/docs/api/interfaces/ButtonProps.md +7 -4
- package/docs/api/interfaces/CalendarProps.md +8 -5
- package/docs/api/interfaces/CardProps.md +8 -5
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +24 -21
- package/docs/api/interfaces/DataTableColumn.md +31 -31
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +26 -23
- package/docs/api/interfaces/FooterProps.md +10 -8
- package/docs/api/interfaces/FormFieldProps.md +10 -10
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +7 -4
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +14 -11
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +11 -11
- package/docs/api/interfaces/NavigationMenuProps.md +15 -15
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +3 -3
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +4 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +14 -11
- package/docs/api/interfaces/UserMenuProps.md +8 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +575 -634
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +2 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +2 -24
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/standards/README.md +1 -0
- package/package.json +2 -1
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +714 -687
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +454 -0
- package/scripts/audit/core/checks/documentation.cjs +203 -0
- package/scripts/audit/core/checks/environment.cjs +128 -0
- package/scripts/audit/core/checks/error-handling.cjs +299 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +61 -936
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +14 -0
- package/src/components/Button/Button.tsx +22 -0
- package/src/components/Calendar/Calendar.tsx +8 -2
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/DataTable.tsx +38 -4
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +196 -554
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +8 -0
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +8 -0
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +61 -849
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +3 -0
- package/src/components/FileDisplay/FileDisplay.tsx +32 -18
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +14 -11
- package/src/components/Form/Form.tsx +1 -0
- package/src/components/Header/Header.tsx +21 -10
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +8 -4
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoginForm/LoginForm.tsx +4 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +32 -11
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +80 -434
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.tsx +4 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +21 -18
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +4 -0
- package/src/hooks/public/usePublicEventLogo.ts +4 -0
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +4 -0
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +4 -0
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +14 -0
- package/src/hooks/useFocusTrap.ts +3 -0
- package/src/hooks/useInactivityTracker.ts +3 -0
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +7 -0
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +1 -1
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
- package/src/providers/services/AuthServiceProvider.tsx +18 -0
- package/src/providers/services/EventServiceProvider.tsx +18 -0
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +36 -0
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +2 -2
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +14 -5
- package/src/rbac/api.ts +100 -67
- package/src/rbac/components/NavigationProvider.tsx +4 -1
- package/src/rbac/components/PagePermissionGuard.tsx +157 -17
- package/src/rbac/components/RoleBasedRouter.tsx +5 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +20 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +347 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +71 -64
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +33 -13
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.ts +200 -61
- package/src/rbac/types.ts +8 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/dist/chunk-3QRJFVBR.js.map +0 -1
- package/dist/chunk-3XTALGJF.js.map +0 -1
- package/dist/chunk-4N5C5XZU.js.map +0 -1
- package/dist/chunk-4ZC4GX36.js.map +0 -1
- package/dist/chunk-BYFSK72L.js.map +0 -1
- package/dist/chunk-EXUD6RNJ.js +0 -451
- package/dist/chunk-EXUD6RNJ.js.map +0 -1
- package/dist/chunk-GLK6VM3F.js.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-JBKQ3SAO.js.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-LXQLPRQ2.js.map +0 -1
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-T33XF5ZC.js.map +0 -1
- package/dist/chunk-XM25TVIE.js.map +0 -1
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -681
- /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Enforce testing framework consistency and standards for consuming apps
|
|
3
3
|
globs: ["**/*.{test,spec}.{ts,tsx}"]
|
|
4
|
-
alwaysApply:
|
|
5
|
-
paceCoreVersion: "0.
|
|
6
|
-
rulesVersion: "2025-01-
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
paceCoreVersion: "0.6.x"
|
|
6
|
+
rulesVersion: "2025-01-28"
|
|
7
7
|
---
|
|
8
8
|
# Testing Standards Guide
|
|
9
9
|
|
|
@@ -25,24 +25,18 @@ npm run test:coverage
|
|
|
25
25
|
**MUST use React Testing Library + userEvent for all component tests.**
|
|
26
26
|
|
|
27
27
|
```tsx
|
|
28
|
-
// ✅ CORRECT
|
|
28
|
+
// ✅ CORRECT: React Testing Library + userEvent
|
|
29
29
|
import { render, screen } from '@testing-library/react';
|
|
30
30
|
import userEvent from '@testing-library/user-event';
|
|
31
|
-
import { Button } from '@jmruthers/pace-core';
|
|
32
|
-
|
|
33
31
|
test('button clicks work', async () => {
|
|
34
32
|
const user = userEvent.setup();
|
|
35
33
|
const handleClick = vi.fn();
|
|
36
|
-
|
|
37
34
|
render(<Button onClick={handleClick}>Click me</Button>);
|
|
38
|
-
|
|
39
35
|
await user.click(screen.getByRole('button', { name: /click me/i }));
|
|
40
|
-
|
|
41
36
|
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
42
37
|
});
|
|
43
38
|
|
|
44
|
-
// ❌ WRONG
|
|
45
|
-
import { shallow } from 'enzyme';
|
|
39
|
+
// ❌ WRONG: Enzyme or other testing libraries
|
|
46
40
|
```
|
|
47
41
|
|
|
48
42
|
## MUST: Colocate Tests
|
|
@@ -94,21 +88,15 @@ export default defineConfig({
|
|
|
94
88
|
**Tests MUST focus on what users see and do:**
|
|
95
89
|
|
|
96
90
|
```tsx
|
|
97
|
-
// ❌ WRONG
|
|
98
|
-
test('calls setState', () => {
|
|
99
|
-
const component = render(<Counter />);
|
|
100
|
-
expect(component.state.count).toBe(0);
|
|
101
|
-
});
|
|
91
|
+
// ❌ WRONG: Testing implementation (component.state.count)
|
|
92
|
+
test('calls setState', () => { const component = render(<Counter />); expect(component.state.count).toBe(0); });
|
|
102
93
|
|
|
103
|
-
// ✅ CORRECT
|
|
94
|
+
// ✅ CORRECT: Testing user behavior (what user sees and does)
|
|
104
95
|
test('displays count and increments on button click', async () => {
|
|
105
96
|
const user = userEvent.setup();
|
|
106
97
|
render(<Counter />);
|
|
107
|
-
|
|
108
98
|
expect(screen.getByText('Count: 0')).toBeInTheDocument();
|
|
109
|
-
|
|
110
99
|
await user.click(screen.getByRole('button', { name: /increment/i }));
|
|
111
|
-
|
|
112
100
|
expect(screen.getByText('Count: 1')).toBeInTheDocument();
|
|
113
101
|
});
|
|
114
102
|
```
|
|
@@ -118,14 +106,11 @@ test('displays count and increments on button click', async () => {
|
|
|
118
106
|
**MUST prefer accessible queries (byRole, byLabelText, etc.):**
|
|
119
107
|
|
|
120
108
|
```tsx
|
|
121
|
-
// ✅ CORRECT
|
|
109
|
+
// ✅ CORRECT: Accessible queries (byRole, byLabelText, byText)
|
|
122
110
|
screen.getByRole('button', { name: /submit/i });
|
|
123
111
|
screen.getByLabelText(/email address/i);
|
|
124
|
-
screen.getByText(/welcome/i);
|
|
125
112
|
|
|
126
|
-
// ❌ AVOID
|
|
127
|
-
screen.getByTestId('submit-button');
|
|
128
|
-
screen.getByClassName('btn-primary');
|
|
113
|
+
// ❌ AVOID: Non-accessible queries (getByTestId, getByClassName - use as last resort)
|
|
129
114
|
```
|
|
130
115
|
|
|
131
116
|
## SHOULD: Test Critical Paths
|
|
@@ -174,19 +159,10 @@ describe('EventCard', () => {
|
|
|
174
159
|
**MUST NOT mock unless necessary:**
|
|
175
160
|
|
|
176
161
|
```tsx
|
|
177
|
-
// ❌ WRONG
|
|
178
|
-
|
|
179
|
-
global.fetch = mockFetch;
|
|
180
|
-
|
|
181
|
-
// ✅ CORRECT - Use real implementation or MSW
|
|
162
|
+
// ❌ WRONG: Unnecessary mock (global.fetch = mockFetch)
|
|
163
|
+
// ✅ CORRECT: Use real implementation or MSW
|
|
182
164
|
import { server } from './mocks/server';
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
server.use(
|
|
186
|
-
rest.get('/api/events', (req, res, ctx) => {
|
|
187
|
-
return res(ctx.json([{ id: '1', name: 'Event' }]));
|
|
188
|
-
})
|
|
189
|
-
);
|
|
165
|
+
server.use(rest.get('/api/events', (req, res, ctx) => res(ctx.json([{ id: '1', name: 'Event' }]))));
|
|
190
166
|
```
|
|
191
167
|
|
|
192
168
|
## MUST: Test Async Code Properly
|
|
@@ -194,16 +170,11 @@ server.use(
|
|
|
194
170
|
**MUST handle async operations correctly:**
|
|
195
171
|
|
|
196
172
|
```tsx
|
|
197
|
-
// ✅ CORRECT
|
|
173
|
+
// ✅ CORRECT: Async testing with waitFor
|
|
198
174
|
test('loads and displays events', async () => {
|
|
199
175
|
render(<EventList />);
|
|
200
|
-
|
|
201
176
|
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
|
202
|
-
|
|
203
|
-
await waitFor(() => {
|
|
204
|
-
expect(screen.getByText('Event 1')).toBeInTheDocument();
|
|
205
|
-
});
|
|
206
|
-
|
|
177
|
+
await waitFor(() => expect(screen.getByText('Event 1')).toBeInTheDocument());
|
|
207
178
|
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
|
|
208
179
|
});
|
|
209
180
|
```
|
|
@@ -213,11 +184,8 @@ test('loads and displays events', async () => {
|
|
|
213
184
|
**MUST clean up resources:**
|
|
214
185
|
|
|
215
186
|
```tsx
|
|
216
|
-
// ✅ CORRECT
|
|
217
|
-
afterEach(() => {
|
|
218
|
-
cleanup();
|
|
219
|
-
vi.clearAllMocks();
|
|
220
|
-
});
|
|
187
|
+
// ✅ CORRECT: Cleanup after tests
|
|
188
|
+
afterEach(() => { cleanup(); vi.clearAllMocks(); });
|
|
221
189
|
```
|
|
222
190
|
|
|
223
191
|
## SHOULD: Use Test Utilities
|
|
@@ -225,23 +193,11 @@ afterEach(() => {
|
|
|
225
193
|
**SHOULD create reusable test utilities:**
|
|
226
194
|
|
|
227
195
|
```tsx
|
|
228
|
-
// test
|
|
229
|
-
import { render } from '@testing-library/react';
|
|
230
|
-
import { UnifiedAuthProvider } from '@jmruthers/pace-core';
|
|
231
|
-
|
|
196
|
+
// ✅ CORRECT: Reusable test utilities
|
|
232
197
|
export function renderWithProviders(ui: React.ReactElement) {
|
|
233
|
-
return render(
|
|
234
|
-
<UnifiedAuthProvider supabaseClient={mockSupabase} appName="Test App">
|
|
235
|
-
{ui}
|
|
236
|
-
</UnifiedAuthProvider>
|
|
237
|
-
);
|
|
198
|
+
return render(<UnifiedAuthProvider supabaseClient={mockSupabase} appName="Test App">{ui}</UnifiedAuthProvider>);
|
|
238
199
|
}
|
|
239
|
-
|
|
240
|
-
// Usage
|
|
241
|
-
test('component works with providers', () => {
|
|
242
|
-
renderWithProviders(<MyComponent />);
|
|
243
|
-
// ...
|
|
244
|
-
});
|
|
200
|
+
// Usage: renderWithProviders(<MyComponent />);
|
|
245
201
|
```
|
|
246
202
|
|
|
247
203
|
## MUST: Include Timeout Parameters
|
|
@@ -249,12 +205,9 @@ test('component works with providers', () => {
|
|
|
249
205
|
**Tests MUST include timeout parameters to prevent hanging:**
|
|
250
206
|
|
|
251
207
|
```tsx
|
|
252
|
-
// ✅ CORRECT
|
|
208
|
+
// ✅ CORRECT: Include timeout parameters to prevent hanging
|
|
253
209
|
test('async operation completes', async () => {
|
|
254
|
-
await waitFor(
|
|
255
|
-
() => expect(screen.getByText('Loaded')).toBeInTheDocument(),
|
|
256
|
-
{ timeout: 5000 }
|
|
257
|
-
);
|
|
210
|
+
await waitFor(() => expect(screen.getByText('Loaded')).toBeInTheDocument(), { timeout: 5000 });
|
|
258
211
|
}, { timeout: 10000 });
|
|
259
212
|
```
|
|
260
213
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
description: Standardized templates for bug reports and feature requests for pace-core
|
|
3
3
|
globs: []
|
|
4
4
|
alwaysApply: false
|
|
5
|
-
paceCoreVersion: "0.
|
|
6
|
-
rulesVersion: "2025-01-
|
|
5
|
+
paceCoreVersion: "0.6.x"
|
|
6
|
+
rulesVersion: "2025-01-28"
|
|
7
7
|
---
|
|
8
8
|
# Bug Reports and Feature Requests Guide
|
|
9
9
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Enforce code quality standards including TypeScript, ESLint, formatting, performance, and accessibility
|
|
3
3
|
globs: ["src/**/*.{ts,tsx,js,jsx}"]
|
|
4
|
-
alwaysApply:
|
|
5
|
-
paceCoreVersion: "0.
|
|
6
|
-
rulesVersion: "2025-01-
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
paceCoreVersion: "0.6.x"
|
|
6
|
+
rulesVersion: "2025-01-28"
|
|
7
7
|
---
|
|
8
8
|
# Code Quality Guide
|
|
9
9
|
|
|
@@ -36,27 +36,17 @@ This guide enforces code quality standards to ensure maintainable, performant, a
|
|
|
36
36
|
**MUST NOT use `any` type. Use `unknown` if type is truly unknown:**
|
|
37
37
|
|
|
38
38
|
```tsx
|
|
39
|
-
// ❌ WRONG
|
|
40
|
-
function processData(data: any) {
|
|
41
|
-
return data.value;
|
|
42
|
-
}
|
|
39
|
+
// ❌ WRONG: Using any
|
|
40
|
+
function processData(data: any) { return data.value; }
|
|
43
41
|
|
|
44
|
-
// ✅ CORRECT
|
|
42
|
+
// ✅ CORRECT: Using unknown with type guard or proper types
|
|
45
43
|
function processData(data: unknown) {
|
|
46
44
|
if (typeof data === 'object' && data !== null && 'value' in data) {
|
|
47
45
|
return (data as { value: string }).value;
|
|
48
46
|
}
|
|
49
47
|
throw new Error('Invalid data');
|
|
50
48
|
}
|
|
51
|
-
|
|
52
|
-
// ✅ CORRECT - Using proper types
|
|
53
|
-
interface Data {
|
|
54
|
-
value: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function processData(data: Data) {
|
|
58
|
-
return data.value;
|
|
59
|
-
}
|
|
49
|
+
// Or: interface Data { value: string; } function processData(data: Data) { return data.value; }
|
|
60
50
|
```
|
|
61
51
|
|
|
62
52
|
### MUST: Use Discriminated Unions
|
|
@@ -64,18 +54,11 @@ function processData(data: Data) {
|
|
|
64
54
|
**MUST use discriminated unions instead of boolean flags:**
|
|
65
55
|
|
|
66
56
|
```tsx
|
|
67
|
-
// ❌ WRONG
|
|
68
|
-
interface User {
|
|
69
|
-
isAdmin: boolean;
|
|
70
|
-
isGuest: boolean;
|
|
71
|
-
// Confusing: what if both are true?
|
|
72
|
-
}
|
|
57
|
+
// ❌ WRONG: Boolean flags (confusing: what if both are true?)
|
|
58
|
+
interface User { isAdmin: boolean; isGuest: boolean; }
|
|
73
59
|
|
|
74
|
-
// ✅ CORRECT
|
|
75
|
-
type User =
|
|
76
|
-
| { type: 'admin'; permissions: Permission[] }
|
|
77
|
-
| { type: 'guest'; limitedAccess: boolean }
|
|
78
|
-
| { type: 'user'; role: UserRole };
|
|
60
|
+
// ✅ CORRECT: Discriminated union
|
|
61
|
+
type User = { type: 'admin'; permissions: Permission[] } | { type: 'guest'; limitedAccess: boolean } | { type: 'user'; role: UserRole };
|
|
79
62
|
```
|
|
80
63
|
|
|
81
64
|
### SHOULD: Use ReadonlyArray
|
|
@@ -148,20 +131,10 @@ npm run format
|
|
|
148
131
|
**SHOULD use React.memo, useMemo, useCallback appropriately:**
|
|
149
132
|
|
|
150
133
|
```tsx
|
|
151
|
-
// ✅ CORRECT
|
|
152
|
-
const expensiveValue = useMemo(() =>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// ✅ CORRECT - Memoize callbacks
|
|
157
|
-
const handleClick = useCallback(() => {
|
|
158
|
-
doSomething(id);
|
|
159
|
-
}, [id]);
|
|
160
|
-
|
|
161
|
-
// ✅ CORRECT - Memoize components
|
|
162
|
-
const ExpensiveComponent = React.memo(({ data }) => {
|
|
163
|
-
return <div>{/* ... */}</div>;
|
|
164
|
-
});
|
|
134
|
+
// ✅ CORRECT: Memoize expensive computations, callbacks, and components
|
|
135
|
+
const expensiveValue = useMemo(() => computeExpensiveValue(data), [data]);
|
|
136
|
+
const handleClick = useCallback(() => doSomething(id), [id]);
|
|
137
|
+
const ExpensiveComponent = React.memo(({ data }) => <div>{/* ... */}</div>);
|
|
165
138
|
```
|
|
166
139
|
|
|
167
140
|
### SHOULD: Avoid Unnecessary Re-renders
|
|
@@ -169,18 +142,12 @@ const ExpensiveComponent = React.memo(({ data }) => {
|
|
|
169
142
|
**SHOULD avoid causing unnecessary re-renders:**
|
|
170
143
|
|
|
171
144
|
```tsx
|
|
172
|
-
// ❌ WRONG
|
|
173
|
-
function Component({ items }) {
|
|
174
|
-
const config = { items, enabled: true }; // New object each render
|
|
175
|
-
return <Child config={config} />;
|
|
176
|
-
}
|
|
145
|
+
// ❌ WRONG: New object on every render (causes unnecessary re-renders)
|
|
146
|
+
function Component({ items }) { const config = { items, enabled: true }; return <Child config={config} />; }
|
|
177
147
|
|
|
178
|
-
// ✅ CORRECT
|
|
148
|
+
// ✅ CORRECT: Memoize object
|
|
179
149
|
function Component({ items }) {
|
|
180
|
-
const config = useMemo(
|
|
181
|
-
() => ({ items, enabled: true }),
|
|
182
|
-
[items]
|
|
183
|
-
);
|
|
150
|
+
const config = useMemo(() => ({ items, enabled: true }), [items]);
|
|
184
151
|
return <Child config={config} />;
|
|
185
152
|
}
|
|
186
153
|
```
|
|
@@ -190,17 +157,11 @@ function Component({ items }) {
|
|
|
190
157
|
**SHOULD lazy load heavy components:**
|
|
191
158
|
|
|
192
159
|
```tsx
|
|
193
|
-
// ✅ CORRECT
|
|
160
|
+
// ✅ CORRECT: Lazy load heavy components
|
|
194
161
|
import { lazy, Suspense } from 'react';
|
|
195
|
-
|
|
196
162
|
const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
|
197
|
-
|
|
198
163
|
function App() {
|
|
199
|
-
return
|
|
200
|
-
<Suspense fallback={<Loading />}>
|
|
201
|
-
<HeavyComponent />
|
|
202
|
-
</Suspense>
|
|
203
|
-
);
|
|
164
|
+
return <Suspense fallback={<Loading />}><HeavyComponent /></Suspense>;
|
|
204
165
|
}
|
|
205
166
|
```
|
|
206
167
|
|
|
@@ -211,11 +172,8 @@ function App() {
|
|
|
211
172
|
**MUST use semantic HTML elements:**
|
|
212
173
|
|
|
213
174
|
```tsx
|
|
214
|
-
// ❌ WRONG
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
// ✅ CORRECT - Semantic
|
|
218
|
-
<button onClick={handleClick}>Click me</button>
|
|
175
|
+
// ❌ WRONG: Non-semantic (<div onClick={...}>)
|
|
176
|
+
// ✅ CORRECT: Semantic HTML (<button onClick={...}>)
|
|
219
177
|
```
|
|
220
178
|
|
|
221
179
|
### MUST: Provide ARIA Labels
|
|
@@ -223,7 +181,7 @@ function App() {
|
|
|
223
181
|
**MUST provide ARIA labels for interactive elements:**
|
|
224
182
|
|
|
225
183
|
```tsx
|
|
226
|
-
// ✅ CORRECT
|
|
184
|
+
// ✅ CORRECT: Provide ARIA labels for interactive elements
|
|
227
185
|
<button aria-label="Close dialog">×</button>
|
|
228
186
|
<input aria-label="Search" type="search" />
|
|
229
187
|
```
|
|
@@ -233,17 +191,8 @@ function App() {
|
|
|
233
191
|
**MUST ensure all interactive elements are keyboard accessible:**
|
|
234
192
|
|
|
235
193
|
```tsx
|
|
236
|
-
// ✅ CORRECT
|
|
237
|
-
<button
|
|
238
|
-
onClick={handleClick}
|
|
239
|
-
onKeyDown={(e) => {
|
|
240
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
241
|
-
handleClick();
|
|
242
|
-
}
|
|
243
|
-
}}
|
|
244
|
-
>
|
|
245
|
-
Click me
|
|
246
|
-
</button>
|
|
194
|
+
// ✅ CORRECT: Ensure keyboard navigation (Enter or Space key)
|
|
195
|
+
<button onClick={handleClick} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleClick(); }}>Click me</button>
|
|
247
196
|
```
|
|
248
197
|
|
|
249
198
|
### MUST: Provide Focus Indicators
|
|
@@ -251,10 +200,8 @@ function App() {
|
|
|
251
200
|
**MUST provide visible focus indicators:**
|
|
252
201
|
|
|
253
202
|
```tsx
|
|
254
|
-
// ✅ CORRECT
|
|
255
|
-
<button className="focus:outline-none focus:ring-2 focus:ring-main-500">
|
|
256
|
-
Click me
|
|
257
|
-
</button>
|
|
203
|
+
// ✅ CORRECT: Provide visible focus indicators
|
|
204
|
+
<button className="focus:outline-none focus:ring-2 focus:ring-main-500">Click me</button>
|
|
258
205
|
```
|
|
259
206
|
|
|
260
207
|
## Code Review Checklist
|
|
@@ -281,20 +228,12 @@ function App() {
|
|
|
281
228
|
**Functions SHOULD be small and focused:**
|
|
282
229
|
|
|
283
230
|
```tsx
|
|
284
|
-
// ❌ WRONG
|
|
285
|
-
function processUserData(user) {
|
|
286
|
-
// 100+ lines of logic
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ✅ CORRECT - Small, focused functions
|
|
290
|
-
function validateUser(user: User): ValidationResult {
|
|
291
|
-
// Validation logic
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function transformUser(user: User): TransformedUser {
|
|
295
|
-
// Transformation logic
|
|
296
|
-
}
|
|
231
|
+
// ❌ WRONG: Large function (100+ lines, multiple responsibilities)
|
|
232
|
+
function processUserData(user) { /* 100+ lines of logic */ }
|
|
297
233
|
|
|
234
|
+
// ✅ CORRECT: Small, focused functions
|
|
235
|
+
function validateUser(user: User): ValidationResult { /* Validation logic */ }
|
|
236
|
+
function transformUser(user: User): TransformedUser { /* Transformation logic */ }
|
|
298
237
|
function processUserData(user: User) {
|
|
299
238
|
const validation = validateUser(user);
|
|
300
239
|
if (!validation.valid) return validation;
|
|
@@ -307,24 +246,11 @@ function processUserData(user: User) {
|
|
|
307
246
|
**Functions SHOULD have low cyclomatic complexity (< 10):**
|
|
308
247
|
|
|
309
248
|
```tsx
|
|
310
|
-
// ❌ WRONG
|
|
311
|
-
function processData(data) {
|
|
312
|
-
if (condition1) {
|
|
313
|
-
if (condition2) {
|
|
314
|
-
if (condition3) {
|
|
315
|
-
// ... many nested conditions
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
249
|
+
// ❌ WRONG: High complexity (many nested conditions)
|
|
250
|
+
function processData(data) { if (condition1) { if (condition2) { if (condition3) { /* ... */ } } } }
|
|
320
251
|
|
|
321
|
-
// ✅ CORRECT
|
|
322
|
-
function processData(data) {
|
|
323
|
-
if (!condition1) return;
|
|
324
|
-
if (!condition2) return;
|
|
325
|
-
if (!condition3) return;
|
|
326
|
-
// Process
|
|
327
|
-
}
|
|
252
|
+
// ✅ CORRECT: Lower complexity (early returns)
|
|
253
|
+
function processData(data) { if (!condition1) return; if (!condition2) return; if (!condition3) return; /* Process */ }
|
|
328
254
|
```
|
|
329
255
|
|
|
330
256
|
## Error Handling
|
|
@@ -334,7 +260,7 @@ function processData(data) {
|
|
|
334
260
|
**MUST handle errors and provide user feedback:**
|
|
335
261
|
|
|
336
262
|
```tsx
|
|
337
|
-
// ✅ CORRECT
|
|
263
|
+
// ✅ CORRECT: Handle errors gracefully with user feedback
|
|
338
264
|
try {
|
|
339
265
|
const data = await fetchData();
|
|
340
266
|
setData(data);
|
|
@@ -349,24 +275,15 @@ try {
|
|
|
349
275
|
**MUST use type-safe error handling:**
|
|
350
276
|
|
|
351
277
|
```tsx
|
|
352
|
-
// ✅ CORRECT
|
|
278
|
+
// ✅ CORRECT: Type-safe error handling
|
|
353
279
|
function isApiError(error: unknown): error is ApiError {
|
|
354
|
-
return
|
|
355
|
-
typeof error === 'object' &&
|
|
356
|
-
error !== null &&
|
|
357
|
-
'code' in error &&
|
|
358
|
-
'message' in error
|
|
359
|
-
);
|
|
280
|
+
return typeof error === 'object' && error !== null && 'code' in error && 'message' in error;
|
|
360
281
|
}
|
|
361
|
-
|
|
362
282
|
try {
|
|
363
283
|
await apiCall();
|
|
364
284
|
} catch (error) {
|
|
365
|
-
if (isApiError(error))
|
|
366
|
-
|
|
367
|
-
} else {
|
|
368
|
-
handleUnknownError(error);
|
|
369
|
-
}
|
|
285
|
+
if (isApiError(error)) handleApiError(error);
|
|
286
|
+
else handleUnknownError(error);
|
|
370
287
|
}
|
|
371
288
|
```
|
|
372
289
|
|