@jmruthers/pace-core 0.6.6 → 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/{scripts/audit/audit-dependencies.cjs → audit-tool/00-dependencies.cjs} +12 -13
- 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/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/{00-pace-core-compliance.mdc → 01-pace-core-compliance.mdc} +187 -34
- package/cursor-rules/02-project-structure.mdc +37 -5
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +125 -11
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +55 -10
- package/cursor-rules/{09-rbac-compliance.mdc → 06-security-rbac.mdc} +62 -6
- 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/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-2N_tqbfq.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-BBH6Vqg7.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +26 -16
- package/dist/{chunk-FENMYN2U.js → chunk-5X4QLXRG.js} +1 -3
- package/dist/{chunk-4T7OBVTU.js → chunk-6F3IILHI.js} +1 -1
- package/dist/{chunk-SD6WQY43.js → chunk-7ILTDCL2.js} +9 -1
- package/dist/{chunk-3QC3KRHK.js → chunk-A3W6LW53.js} +16 -1
- package/dist/{chunk-7TYHROIV.js → chunk-BM4CQ5P3.js} +50 -8
- package/dist/{chunk-2HGJFNAH.js → chunk-FEJLJNWA.js} +1 -15
- package/dist/{chunk-OHIK3MIO.js → chunk-GHYHJTYV.js} +2 -2
- package/dist/{chunk-UIYSCEV7.js → chunk-IUBRCBSY.js} +1 -1
- package/dist/{chunk-LAZMKTTF.js → chunk-JGWDVX64.js} +281 -347
- package/dist/{chunk-MAGBIDNS.js → chunk-L4XMVJKY.js} +2 -2
- package/dist/{chunk-A55DK444.js → chunk-OJ4SKRSV.js} +1 -7
- package/dist/{chunk-ZS5VO5JB.js → chunk-Q7Q7V5NV.js} +406 -451
- package/dist/{chunk-3O3WHILE.js → chunk-VBCS3DUA.js} +236 -60
- package/dist/{chunk-BVP2BCJF.js → chunk-ZKAWKYT4.js} +8 -8
- package/dist/components.d.ts +5 -4
- package/dist/components.js +27 -32
- package/dist/eslint-rules/index.cjs +22 -9
- package/{src/eslint-rules/rules/compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +184 -23
- 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/{rbac.cjs → 06-security-rbac.cjs} +26 -10
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +18 -17
- package/dist/rbac/index.js +6 -6
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/{types-B-K_5VnO.d.ts → types-DXstZpNI.d.ts} +0 -17
- package/dist/{usePublicRouteParams-COZ28Mvq.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +19 -19
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +8 -8
- package/docs/README.md +1 -1
- package/docs/api/modules.md +47 -31
- package/docs/api-reference/components.md +18 -20
- package/docs/api-reference/hooks.md +80 -80
- 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/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/quick-reference.md +4 -4
- 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 +1 -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/README.md +5 -5
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- 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 -1
- 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/{00-pace-core-compliance.md → 1-pace-core-compliance-standards.md} +204 -185
- package/docs/standards/{02-project-structure.md → 2-project-structure-standards.md} +11 -47
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/{08-markup-quality.md → 5-styling-standards.md} +12 -9
- package/docs/standards/{09-rbac-compliance.md → 6-security-rbac-standards.md} +126 -18
- 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 +203 -104
- 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/eslint-config-pace-core.cjs +21 -10
- package/package.json +6 -5
- package/scripts/install-cursor-rules.cjs +11 -243
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +10 -10
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- 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/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +137 -153
- 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/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +5 -16
- package/src/components/DataTable/components/EditableRow.tsx +5 -7
- 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/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/index.ts +2 -1
- package/src/components/DataTable/types.ts +0 -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 +7 -8
- package/src/components/Dialog/Dialog.test.tsx +1 -0
- package/src/components/Dialog/Dialog.tsx +25 -8
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +86 -77
- package/src/components/Select/types.ts +3 -0
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +49 -49
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/public/usePublicEvent.ts +5 -5
- package/src/hooks/public/usePublicEventLogo.ts +5 -5
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +2 -2
- package/src/hooks/useEventTheme.test.ts +7 -7
- package/src/hooks/useEventTheme.ts +1 -4
- package/src/hooks/useFileDisplay.ts +2 -2
- 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/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +10 -10
- 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/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/storage/README.md +1 -1
- package/cursor-rules/01-standards-compliance.mdc +0 -285
- package/cursor-rules/04-testing-standards.mdc +0 -270
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -248
- package/cursor-rules/06-code-quality.mdc +0 -311
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -216
- package/cursor-rules/10-error-handling-patterns.mdc +0 -179
- package/cursor-rules/11-performance-optimization.mdc +0 -169
- package/cursor-rules/12-ci-cd-integration.mdc +0 -150
- package/dist/DataTable-LRJL4IRV.js +0 -15
- package/dist/eslint-rules/rules/compliance.cjs +0 -348
- package/dist/eslint-rules/rules/components.cjs +0 -113
- package/dist/eslint-rules/rules/imports.cjs +0 -102
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -604
- 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-standards-compliance.md +0 -188
- package/docs/standards/03-solid-principles.md +0 -39
- package/docs/standards/04-testing-standards.md +0 -36
- package/docs/standards/05-bug-reports-and-features.md +0 -27
- package/docs/standards/06-code-quality.md +0 -34
- package/docs/standards/07-tech-stack-compliance.md +0 -30
- package/docs/standards/10-error-handling-patterns.md +0 -401
- package/docs/standards/11-performance-optimization.md +0 -348
- package/docs/standards/12-ci-cd-integration.md +0 -370
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +0 -192
- package/scripts/audit/audit-compliance.cjs +0 -1295
- package/scripts/audit/audit-components.cjs +0 -260
- package/scripts/audit/audit-rbac.cjs +0 -954
- package/scripts/audit/audit-standards.cjs +0 -1268
- package/scripts/audit/index.cjs +0 -1927
- package/src/components/DataTable/components/DataTableBody.tsx +0 -478
- 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/index.cjs +0 -22
- package/src/eslint-rules/rules/components.cjs +0 -113
- package/src/eslint-rules/rules/imports.cjs +0 -102
- package/src/eslint-rules/rules/rbac.cjs +0 -790
- package/src/eslint-rules/utils/helpers.cjs +0 -42
- package/src/eslint-rules/utils/manifest-loader.cjs +0 -75
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Enforce code quality standards including TypeScript, ESLint, formatting, performance, and accessibility
|
|
3
|
-
globs: ["src/**/*.{ts,tsx,js,jsx}"]
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
paceCoreVersion: "0.6.x"
|
|
6
|
-
rulesVersion: "2025-01-28"
|
|
7
|
-
---
|
|
8
|
-
# Code Quality Guide
|
|
9
|
-
|
|
10
|
-
**📚 Human-Readable Standard**: See [06-code-quality.md](../../packages/core/docs/standards/06-code-quality.md) for complete documentation.
|
|
11
|
-
|
|
12
|
-
This guide enforces code quality standards to ensure maintainable, performant, and accessible code.
|
|
13
|
-
|
|
14
|
-
## TypeScript Standards
|
|
15
|
-
|
|
16
|
-
### MUST: Use Strict Mode
|
|
17
|
-
|
|
18
|
-
**TypeScript MUST use strict mode:**
|
|
19
|
-
|
|
20
|
-
```json
|
|
21
|
-
// tsconfig.json
|
|
22
|
-
{
|
|
23
|
-
"compilerOptions": {
|
|
24
|
-
"strict": true,
|
|
25
|
-
"noImplicitAny": true,
|
|
26
|
-
"strictNullChecks": true,
|
|
27
|
-
"strictFunctionTypes": true,
|
|
28
|
-
"strictBindCallApply": true,
|
|
29
|
-
"strictPropertyInitialization": true,
|
|
30
|
-
"noImplicitThis": true,
|
|
31
|
-
"alwaysStrict": true
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### MUST NOT: Use `any`
|
|
37
|
-
|
|
38
|
-
**MUST NOT use `any` type. Use `unknown` if type is truly unknown:**
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
// ❌ WRONG: Using any
|
|
42
|
-
function processData(data: any) { return data.value; }
|
|
43
|
-
|
|
44
|
-
// ✅ CORRECT: Using unknown with type guard or proper types
|
|
45
|
-
function processData(data: unknown) {
|
|
46
|
-
if (typeof data === 'object' && data !== null && 'value' in data) {
|
|
47
|
-
return (data as { value: string }).value;
|
|
48
|
-
}
|
|
49
|
-
throw new Error('Invalid data');
|
|
50
|
-
}
|
|
51
|
-
// Or: interface Data { value: string; } function processData(data: Data) { return data.value; }
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### MUST: Use Discriminated Unions
|
|
55
|
-
|
|
56
|
-
**MUST use discriminated unions instead of boolean flags:**
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
// ❌ WRONG: Boolean flags (confusing: what if both are true?)
|
|
60
|
-
interface User { isAdmin: boolean; isGuest: boolean; }
|
|
61
|
-
|
|
62
|
-
// ✅ CORRECT: Discriminated union
|
|
63
|
-
type User = { type: 'admin'; permissions: Permission[] } | { type: 'guest'; limitedAccess: boolean } | { type: 'user'; role: UserRole };
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### SHOULD: Use ReadonlyArray
|
|
67
|
-
|
|
68
|
-
**SHOULD use ReadonlyArray for immutable arrays:**
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
// ✅ CORRECT - ReadonlyArray
|
|
72
|
-
function processItems(items: ReadonlyArray<Item>) {
|
|
73
|
-
// Can't mutate items
|
|
74
|
-
return items.map(item => transform(item));
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## ESLint Configuration
|
|
79
|
-
|
|
80
|
-
### MUST: Use pace-core ESLint Config
|
|
81
|
-
|
|
82
|
-
**MUST extend pace-core ESLint configuration:**
|
|
83
|
-
|
|
84
|
-
```javascript
|
|
85
|
-
// eslint.config.js
|
|
86
|
-
import paceCoreConfig from '@jmruthers/pace-core/eslint-config';
|
|
87
|
-
|
|
88
|
-
export default [
|
|
89
|
-
...paceCoreConfig,
|
|
90
|
-
// Your additional config
|
|
91
|
-
];
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### MUST: Fix ESLint Errors
|
|
95
|
-
|
|
96
|
-
**MUST fix all ESLint errors before committing:**
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
npm run lint
|
|
100
|
-
npm run lint:fix # Auto-fix where possible
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Code Formatting
|
|
104
|
-
|
|
105
|
-
### MUST: Use Consistent Formatting
|
|
106
|
-
|
|
107
|
-
**MUST use Prettier or equivalent for consistent formatting:**
|
|
108
|
-
|
|
109
|
-
```json
|
|
110
|
-
// .prettierrc
|
|
111
|
-
{
|
|
112
|
-
"semi": true,
|
|
113
|
-
"singleQuote": true,
|
|
114
|
-
"tabWidth": 2,
|
|
115
|
-
"trailingComma": "es5",
|
|
116
|
-
"printWidth": 100
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### MUST: Format Before Committing
|
|
121
|
-
|
|
122
|
-
**MUST format code before committing:**
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
npm run format
|
|
126
|
-
# Or use pre-commit hook
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Performance Considerations
|
|
130
|
-
|
|
131
|
-
### SHOULD: Optimize Re-renders
|
|
132
|
-
|
|
133
|
-
**SHOULD use React.memo, useMemo, useCallback appropriately:**
|
|
134
|
-
|
|
135
|
-
```tsx
|
|
136
|
-
// ✅ CORRECT: Memoize expensive computations, callbacks, and components
|
|
137
|
-
const expensiveValue = useMemo(() => computeExpensiveValue(data), [data]);
|
|
138
|
-
const handleClick = useCallback(() => doSomething(id), [id]);
|
|
139
|
-
const ExpensiveComponent = React.memo(({ data }) => <div>{/* ... */}</div>);
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### SHOULD: Avoid Unnecessary Re-renders
|
|
143
|
-
|
|
144
|
-
**SHOULD avoid causing unnecessary re-renders:**
|
|
145
|
-
|
|
146
|
-
```tsx
|
|
147
|
-
// ❌ WRONG: New object on every render (causes unnecessary re-renders)
|
|
148
|
-
function Component({ items }) { const config = { items, enabled: true }; return <Child config={config} />; }
|
|
149
|
-
|
|
150
|
-
// ✅ CORRECT: Memoize object
|
|
151
|
-
function Component({ items }) {
|
|
152
|
-
const config = useMemo(() => ({ items, enabled: true }), [items]);
|
|
153
|
-
return <Child config={config} />;
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### SHOULD: Lazy Load Heavy Components
|
|
158
|
-
|
|
159
|
-
**SHOULD lazy load heavy components:**
|
|
160
|
-
|
|
161
|
-
```tsx
|
|
162
|
-
// ✅ CORRECT: Lazy load heavy components
|
|
163
|
-
import { lazy, Suspense } from 'react';
|
|
164
|
-
const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
|
165
|
-
function App() {
|
|
166
|
-
return <Suspense fallback={<Loading />}><HeavyComponent /></Suspense>;
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Accessibility Requirements
|
|
171
|
-
|
|
172
|
-
### MUST: Use Semantic HTML
|
|
173
|
-
|
|
174
|
-
**MUST use semantic HTML elements:**
|
|
175
|
-
|
|
176
|
-
```tsx
|
|
177
|
-
// ❌ WRONG: Non-semantic (<div onClick={...}>)
|
|
178
|
-
// ✅ CORRECT: Semantic HTML (<button onClick={...}>)
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### MUST: Provide ARIA Labels
|
|
182
|
-
|
|
183
|
-
**MUST provide ARIA labels for interactive elements:**
|
|
184
|
-
|
|
185
|
-
```tsx
|
|
186
|
-
// ✅ CORRECT: Provide ARIA labels for interactive elements
|
|
187
|
-
<button aria-label="Close dialog">×</button>
|
|
188
|
-
<input aria-label="Search" type="search" />
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### MUST: Ensure Keyboard Navigation
|
|
192
|
-
|
|
193
|
-
**MUST ensure all interactive elements are keyboard accessible:**
|
|
194
|
-
|
|
195
|
-
```tsx
|
|
196
|
-
// ✅ CORRECT: Ensure keyboard navigation (Enter or Space key)
|
|
197
|
-
<button onClick={handleClick} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleClick(); }}>Click me</button>
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### MUST: Provide Focus Indicators
|
|
201
|
-
|
|
202
|
-
**MUST provide visible focus indicators:**
|
|
203
|
-
|
|
204
|
-
```tsx
|
|
205
|
-
// ✅ CORRECT: Provide visible focus indicators
|
|
206
|
-
<button className="focus:outline-none focus:ring-2 focus:ring-main-500">Click me</button>
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## Code Review Checklist
|
|
210
|
-
|
|
211
|
-
**Before submitting code for review, verify:**
|
|
212
|
-
|
|
213
|
-
- [ ] TypeScript strict mode enabled
|
|
214
|
-
- [ ] No `any` types used
|
|
215
|
-
- [ ] ESLint passes with no errors
|
|
216
|
-
- [ ] Code is formatted consistently
|
|
217
|
-
- [ ] Performance optimizations applied where needed
|
|
218
|
-
- [ ] Accessibility requirements met
|
|
219
|
-
- [ ] Semantic HTML used
|
|
220
|
-
- [ ] ARIA labels provided
|
|
221
|
-
- [ ] Keyboard navigation works
|
|
222
|
-
- [ ] Focus indicators visible
|
|
223
|
-
- [ ] Code is readable and maintainable
|
|
224
|
-
- [ ] Comments explain "why", not "what"
|
|
225
|
-
|
|
226
|
-
## Code Complexity
|
|
227
|
-
|
|
228
|
-
### SHOULD: Keep Functions Small
|
|
229
|
-
|
|
230
|
-
**Functions SHOULD be small and focused:**
|
|
231
|
-
|
|
232
|
-
```tsx
|
|
233
|
-
// ❌ WRONG: Large function (100+ lines, multiple responsibilities)
|
|
234
|
-
function processUserData(user) { /* 100+ lines of logic */ }
|
|
235
|
-
|
|
236
|
-
// ✅ CORRECT: Small, focused functions
|
|
237
|
-
function validateUser(user: User): ValidationResult { /* Validation logic */ }
|
|
238
|
-
function transformUser(user: User): TransformedUser { /* Transformation logic */ }
|
|
239
|
-
function processUserData(user: User) {
|
|
240
|
-
const validation = validateUser(user);
|
|
241
|
-
if (!validation.valid) return validation;
|
|
242
|
-
return transformUser(user);
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### SHOULD: Limit Cyclomatic Complexity
|
|
247
|
-
|
|
248
|
-
**Functions SHOULD have low cyclomatic complexity (< 10):**
|
|
249
|
-
|
|
250
|
-
```tsx
|
|
251
|
-
// ❌ WRONG: High complexity (many nested conditions)
|
|
252
|
-
function processData(data) { if (condition1) { if (condition2) { if (condition3) { /* ... */ } } } }
|
|
253
|
-
|
|
254
|
-
// ✅ CORRECT: Lower complexity (early returns)
|
|
255
|
-
function processData(data) { if (!condition1) return; if (!condition2) return; if (!condition3) return; /* Process */ }
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
## Error Handling
|
|
259
|
-
|
|
260
|
-
### MUST: Handle Errors Gracefully
|
|
261
|
-
|
|
262
|
-
**MUST handle errors and provide user feedback:**
|
|
263
|
-
|
|
264
|
-
```tsx
|
|
265
|
-
// ✅ CORRECT: Handle errors gracefully with user feedback
|
|
266
|
-
try {
|
|
267
|
-
const data = await fetchData();
|
|
268
|
-
setData(data);
|
|
269
|
-
} catch (error) {
|
|
270
|
-
logger.error('Failed to fetch data', error);
|
|
271
|
-
toast.error('Failed to load data. Please try again.');
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### MUST: Use Type-Safe Error Handling
|
|
276
|
-
|
|
277
|
-
**MUST use type-safe error handling:**
|
|
278
|
-
|
|
279
|
-
```tsx
|
|
280
|
-
// ✅ CORRECT: Type-safe error handling
|
|
281
|
-
function isApiError(error: unknown): error is ApiError {
|
|
282
|
-
return typeof error === 'object' && error !== null && 'code' in error && 'message' in error;
|
|
283
|
-
}
|
|
284
|
-
try {
|
|
285
|
-
await apiCall();
|
|
286
|
-
} catch (error) {
|
|
287
|
-
if (isApiError(error)) handleApiError(error);
|
|
288
|
-
else handleUnknownError(error);
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
## Code Quality Checklist
|
|
293
|
-
|
|
294
|
-
Before committing, verify:
|
|
295
|
-
- [ ] TypeScript strict mode enabled
|
|
296
|
-
- [ ] No `any` types
|
|
297
|
-
- [ ] ESLint passes
|
|
298
|
-
- [ ] Code formatted
|
|
299
|
-
- [ ] Performance optimized
|
|
300
|
-
- [ ] Accessible (semantic HTML, ARIA, keyboard)
|
|
301
|
-
- [ ] Functions are small and focused
|
|
302
|
-
- [ ] Error handling in place
|
|
303
|
-
- [ ] Code is readable
|
|
304
|
-
- [ ] Comments explain "why"
|
|
305
|
-
|
|
306
|
-
## Reference
|
|
307
|
-
|
|
308
|
-
- TypeScript Handbook: https://www.typescriptlang.org/docs/
|
|
309
|
-
- ESLint Rules: pace-core eslint-config
|
|
310
|
-
- React Performance: https://react.dev/learn/render-and-commit
|
|
311
|
-
- Web Accessibility: https://www.w3.org/WAI/
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Enforce tech stack compliance including Tailwind v4, React 19+, Supabase, and other required technologies
|
|
3
|
-
globs: ["src/**/*.{ts,tsx,js,jsx,css}", "*.config.{ts,js}"]
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
paceCoreVersion: "0.6.x"
|
|
6
|
-
rulesVersion: "2025-01-28"
|
|
7
|
-
---
|
|
8
|
-
# Tech Stack Compliance Guide
|
|
9
|
-
|
|
10
|
-
**📚 Human-Readable Standard**: See [07-tech-stack-compliance.md](../../packages/core/docs/standards/07-tech-stack-compliance.md) for complete documentation.
|
|
11
|
-
|
|
12
|
-
This guide ensures consuming apps use the correct versions and patterns for all technologies in the PACE stack.
|
|
13
|
-
|
|
14
|
-
## Tailwind CSS v4
|
|
15
|
-
|
|
16
|
-
### MUST: Use Tailwind v4 CSS-First Approach
|
|
17
|
-
|
|
18
|
-
**MUST use Tailwind v4 CSS-first syntax, NOT v3 patterns:**
|
|
19
|
-
|
|
20
|
-
```css
|
|
21
|
-
/* ✅ CORRECT: Tailwind v4 CSS-first (@import 'tailwindcss'; @theme { ... }) */
|
|
22
|
-
/* ❌ WRONG: Tailwind v3 (@tailwind base/components/utilities directives) */
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### MUST NOT: Use Bracket Syntax
|
|
26
|
-
|
|
27
|
-
**MUST NOT use bracket syntax like `bg-[oklch(...)]`. Map everything through theme variables:**
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
// ❌ WRONG: Bracket syntax (bg-[oklch(...)])
|
|
31
|
-
// ✅ CORRECT: Theme variable (bg-main-500)
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### MUST: Use Custom Color Namespaces
|
|
35
|
-
|
|
36
|
-
**MUST use custom color namespaces:**
|
|
37
|
-
- `main-*` for primary colors (green, emerald, teal, cyan, sky, blue)
|
|
38
|
-
- `sec-*` for secondary colors (indigo, violet, purple, fuchsia, slate, gray, zinc, neutral, stone)
|
|
39
|
-
- `acc-*` for accent colors (red, orange, amber, yellow, lime, pink, rose)
|
|
40
|
-
|
|
41
|
-
```tsx
|
|
42
|
-
// ✅ CORRECT: Custom namespaces (main-*, sec-*, acc-*)
|
|
43
|
-
// ❌ WRONG: Standard Tailwind colors (blue-*, gray-*, red-*)
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### MUST: Use Semantic Classes
|
|
47
|
-
|
|
48
|
-
**MUST use semantic classes mapped in index.css:**
|
|
49
|
-
|
|
50
|
-
```tsx
|
|
51
|
-
// ✅ CORRECT - Semantic classes
|
|
52
|
-
<div className="bg-background text-foreground">
|
|
53
|
-
|
|
54
|
-
// ❌ WRONG - Direct color classes (unless necessary)
|
|
55
|
-
<div className="bg-main-50 text-main-950">
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## React 19+
|
|
59
|
-
|
|
60
|
-
### MUST: Use React 19+ Features
|
|
61
|
-
|
|
62
|
-
**MUST use React 19+ patterns:**
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
// ✅ CORRECT: React 19+ features (Suspense, useTransition, etc.)
|
|
66
|
-
import { Suspense, useTransition } from 'react';
|
|
67
|
-
function App() {
|
|
68
|
-
const [isPending, startTransition] = useTransition();
|
|
69
|
-
return <Suspense fallback={<Loading />}><Component /></Suspense>;
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### MUST: Use Functional Components
|
|
74
|
-
|
|
75
|
-
**MUST use functional components with hooks exclusively:**
|
|
76
|
-
|
|
77
|
-
```tsx
|
|
78
|
-
// ✅ CORRECT: Functional components with hooks
|
|
79
|
-
function MyComponent() { const [state, setState] = useState(); return <div>...</div>; }
|
|
80
|
-
|
|
81
|
-
// ❌ WRONG: Class components
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Supabase
|
|
85
|
-
|
|
86
|
-
### MUST: Use Secure Supabase Client
|
|
87
|
-
|
|
88
|
-
**MUST use `useSecureSupabase()` for all database operations:**
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
// ✅ CORRECT: useSecureSupabase() from '@jmruthers/pace-core/rbac'
|
|
92
|
-
// ❌ WRONG: createClient() from '@supabase/supabase-js' (base client)
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### MUST: Enforce RLS
|
|
96
|
-
|
|
97
|
-
**MUST ensure RLS is enabled on all tables. Never bypass RLS.**
|
|
98
|
-
|
|
99
|
-
### SHOULD: Use RPC Functions
|
|
100
|
-
|
|
101
|
-
**SHOULD use RPC functions for complex queries:**
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
// ✅ CORRECT: RPC functions for complex queries
|
|
105
|
-
const { data } = await secureSupabase.rpc('data_events_list', { organisation_id: orgId });
|
|
106
|
-
|
|
107
|
-
// ❌ AVOID: Complex client-side queries with many joins
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## TanStack Query
|
|
111
|
-
|
|
112
|
-
### MUST: Use TanStack Query for Server State
|
|
113
|
-
|
|
114
|
-
**MUST use TanStack Query for all server state management:**
|
|
115
|
-
|
|
116
|
-
```tsx
|
|
117
|
-
// ✅ CORRECT: TanStack Query for server state
|
|
118
|
-
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
119
|
-
const { data, isLoading } = useQuery({ queryKey: ['events', orgId], queryFn: () => fetchEvents(orgId) });
|
|
120
|
-
|
|
121
|
-
// ❌ WRONG: useState + useEffect for server state
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### SHOULD: Configure Query Client
|
|
125
|
-
|
|
126
|
-
**SHOULD configure QueryClient with appropriate defaults:**
|
|
127
|
-
|
|
128
|
-
```tsx
|
|
129
|
-
// ✅ CORRECT: Configure QueryClient with appropriate defaults (staleTime, cacheTime, retry)
|
|
130
|
-
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, cacheTime: 10 * 60 * 1000, retry: 1 } } });
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## React Hook Form + Zod
|
|
134
|
-
|
|
135
|
-
### MUST: Use React Hook Form with Zod
|
|
136
|
-
|
|
137
|
-
**MUST use React Hook Form with Zod for all forms:**
|
|
138
|
-
|
|
139
|
-
```tsx
|
|
140
|
-
// ✅ CORRECT: React Hook Form + Zod (useZodForm from pace-core)
|
|
141
|
-
import { useZodForm } from '@jmruthers/pace-core';
|
|
142
|
-
const schema = z.object({ email: z.string().email(), name: z.string().min(1) });
|
|
143
|
-
const form = useZodForm(schema);
|
|
144
|
-
|
|
145
|
-
// ❌ WRONG: Manual form handling (useState for form state)
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Vite
|
|
149
|
-
|
|
150
|
-
### MUST: Use Vite for Build Tooling
|
|
151
|
-
|
|
152
|
-
**MUST use Vite for all build tooling:**
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
// ✅ CORRECT: Vite for build tooling
|
|
156
|
-
import { defineConfig } from 'vite';
|
|
157
|
-
import react from '@vitejs/plugin-react';
|
|
158
|
-
export default defineConfig({ plugins: [react()] });
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### MUST: Use Environment Variables Correctly
|
|
162
|
-
|
|
163
|
-
**MUST use `import.meta.env` for environment variables:**
|
|
164
|
-
|
|
165
|
-
```tsx
|
|
166
|
-
// ✅ CORRECT: import.meta.env.VITE_API_URL (Vite env vars)
|
|
167
|
-
// ❌ WRONG: process.env.VITE_API_URL (Node.js env vars)
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## TypeScript
|
|
171
|
-
|
|
172
|
-
### MUST: Use TypeScript 5+
|
|
173
|
-
|
|
174
|
-
**MUST use TypeScript 5+ with strict mode:**
|
|
175
|
-
|
|
176
|
-
```json
|
|
177
|
-
// ✅ CORRECT: TypeScript 5+ with strict mode
|
|
178
|
-
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "strict": true } }
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## Version Requirements
|
|
182
|
-
|
|
183
|
-
### MUST: Use Required Versions
|
|
184
|
-
|
|
185
|
-
**MUST use compatible versions:**
|
|
186
|
-
|
|
187
|
-
- React: `^19.0.0`
|
|
188
|
-
- TypeScript: `^5.0.0`
|
|
189
|
-
- Tailwind CSS: `^4.0.0`
|
|
190
|
-
- Vite: `^6.0.0`
|
|
191
|
-
- Supabase: `^2.49.0+`
|
|
192
|
-
|
|
193
|
-
**Check `package.json` for exact versions required by pace-core.**
|
|
194
|
-
|
|
195
|
-
## Tech Stack Checklist
|
|
196
|
-
|
|
197
|
-
Before committing, verify:
|
|
198
|
-
- [ ] Tailwind v4 CSS-first syntax (no @tailwind directives)
|
|
199
|
-
- [ ] No bracket syntax for colors (use theme variables)
|
|
200
|
-
- [ ] Custom color namespaces (main-*, sec-*, acc-*)
|
|
201
|
-
- [ ] React 19+ functional components
|
|
202
|
-
- [ ] Secure Supabase client (useSecureSupabase)
|
|
203
|
-
- [ ] TanStack Query for server state
|
|
204
|
-
- [ ] React Hook Form + Zod for forms
|
|
205
|
-
- [ ] Vite for build tooling
|
|
206
|
-
- [ ] TypeScript 5+ with strict mode
|
|
207
|
-
- [ ] All versions compatible with pace-core
|
|
208
|
-
|
|
209
|
-
## Reference
|
|
210
|
-
|
|
211
|
-
- Tailwind v4: https://tailwindcss.com/blog/tailwindcss-v4
|
|
212
|
-
- React 19: https://react.dev/blog/2024/04/25/react-19-upgrade-guide
|
|
213
|
-
- Supabase: https://supabase.com/docs
|
|
214
|
-
- TanStack Query: https://tanstack.com/query
|
|
215
|
-
- React Hook Form: https://react-hook-form.com
|
|
216
|
-
- Vite: https://vitejs.dev
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Enforce consistent error handling patterns including type-safe errors, user-friendly messages, and proper logging
|
|
3
|
-
globs: ["src/**/*.{ts,tsx,js,jsx}"]
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
paceCoreVersion: "0.6.x"
|
|
6
|
-
rulesVersion: "2025-01-28"
|
|
7
|
-
---
|
|
8
|
-
# Error Handling Patterns Guide
|
|
9
|
-
|
|
10
|
-
**📚 Human-Readable Standard**: See [10-error-handling-patterns.md](../../packages/core/docs/standards/10-error-handling-patterns.md) for complete documentation.
|
|
11
|
-
|
|
12
|
-
This guide enforces consistent error handling patterns to ensure user-friendly errors, type-safe handling, and proper logging.
|
|
13
|
-
|
|
14
|
-
**AI Agent Instructions**: When writing error handling code, ALWAYS follow these patterns. Never expose internal details to users. Always use type-safe error handling.
|
|
15
|
-
|
|
16
|
-
## MUST: Use Type-Safe Error Handling
|
|
17
|
-
|
|
18
|
-
**MUST use type guards or Result types, NEVER use `any`:**
|
|
19
|
-
|
|
20
|
-
```tsx
|
|
21
|
-
// ❌ WRONG: Using any
|
|
22
|
-
catch (error: any) {
|
|
23
|
-
console.log(error.message);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ✅ CORRECT: Type guard
|
|
27
|
-
function isApiError(error: unknown): error is ApiError {
|
|
28
|
-
return typeof error === 'object' && error !== null && 'ok' in error && (error as ApiError).ok === false;
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
if (isApiError(error)) {
|
|
32
|
-
handleApiError(error);
|
|
33
|
-
} else {
|
|
34
|
-
handleUnknownError(error);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ✅ CORRECT: Result type
|
|
39
|
-
type Result<T> = { ok: true; data: T } | { ok: false; error: ApiError };
|
|
40
|
-
const result = await fetchData();
|
|
41
|
-
if (result.ok) {
|
|
42
|
-
useData(result.data);
|
|
43
|
-
} else {
|
|
44
|
-
showError(result.error.message);
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## MUST: Never Expose Internal Details
|
|
49
|
-
|
|
50
|
-
**MUST NOT expose SQL, stack traces, file paths, or internal errors to users:**
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
// ❌ WRONG: Exposing internal details
|
|
54
|
-
toast.error(error.message); // May contain SQL, stack traces
|
|
55
|
-
|
|
56
|
-
// ✅ CORRECT: User-friendly message
|
|
57
|
-
toast.error('Unable to save changes. Please try again.');
|
|
58
|
-
logger.error('Save failed', { error: error.message, context: 'saveUser' });
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## MUST: Use Consistent Error Shapes
|
|
62
|
-
|
|
63
|
-
**MUST use ApiResult shape for API errors:**
|
|
64
|
-
|
|
65
|
-
```tsx
|
|
66
|
-
// ✅ CORRECT: Consistent error shape
|
|
67
|
-
type ApiError = {
|
|
68
|
-
ok: false;
|
|
69
|
-
error: {
|
|
70
|
-
code: string;
|
|
71
|
-
message: string; // User-friendly
|
|
72
|
-
details?: object; // Non-sensitive context
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## MUST: Log Errors with Context
|
|
78
|
-
|
|
79
|
-
**MUST log errors with context, but NEVER log sensitive data:**
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
// ✅ CORRECT: Log with context, no sensitive data
|
|
83
|
-
logger.error('Failed to save user', {
|
|
84
|
-
userId: user.id,
|
|
85
|
-
operation: 'updateUser',
|
|
86
|
-
errorCode: error.code,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ❌ WRONG: Logging sensitive data
|
|
90
|
-
logger.error('Failed to save user', {
|
|
91
|
-
password: user.password, // NEVER
|
|
92
|
-
token: authToken, // NEVER
|
|
93
|
-
ssn: user.ssn, // NEVER
|
|
94
|
-
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## SHOULD: Provide Recovery Paths
|
|
98
|
-
|
|
99
|
-
**SHOULD provide retry logic or fallback values when appropriate:**
|
|
100
|
-
|
|
101
|
-
```tsx
|
|
102
|
-
// ✅ CORRECT: Retry with exponential backoff
|
|
103
|
-
async function fetchWithRetry<T>(fn: () => Promise<Result<T>>, maxRetries = 3): Promise<Result<T>> {
|
|
104
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
105
|
-
const result = await fn();
|
|
106
|
-
if (result.ok) return result;
|
|
107
|
-
if (result.error.code?.startsWith('4')) return result; // Don't retry 4xx
|
|
108
|
-
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
|
109
|
-
}
|
|
110
|
-
return { ok: false, error: { code: 'MAX_RETRIES', message: 'Operation failed after retries' } };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ✅ CORRECT: Fallback values
|
|
114
|
-
const preferences = await fetchPreferences().catch(() => ({ theme: 'light', language: 'en' }));
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## SHOULD: Use Error Boundaries for React
|
|
118
|
-
|
|
119
|
-
**SHOULD use ErrorBoundary for React component errors:**
|
|
120
|
-
|
|
121
|
-
```tsx
|
|
122
|
-
// ✅ CORRECT: Error boundary
|
|
123
|
-
import { ErrorBoundary } from '@jmruthers/pace-core';
|
|
124
|
-
<ErrorBoundary fallback={<ErrorFallback />} onError={(error, errorInfo) => logger.error('React error', { error, errorInfo })}>
|
|
125
|
-
<YourApp />
|
|
126
|
-
</ErrorBoundary>
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Decision Tree: Error Handling
|
|
130
|
-
|
|
131
|
-
**ALWAYS follow this decision tree:**
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
1. What type of error is this?
|
|
135
|
-
├─ API Error → Use ApiResult shape, user-friendly message
|
|
136
|
-
├─ Validation Error → Use Zod errors, field-specific messages
|
|
137
|
-
├─ Network Error → Retry logic, connection message
|
|
138
|
-
└─ Unknown Error → Generic user message, detailed log
|
|
139
|
-
|
|
140
|
-
2. Should user see this error?
|
|
141
|
-
├─ YES → User-friendly message (no internals)
|
|
142
|
-
└─ NO → Log only, show generic message
|
|
143
|
-
|
|
144
|
-
3. Can we recover?
|
|
145
|
-
├─ YES → Retry logic or fallback value
|
|
146
|
-
└─ NO → Show error, allow user to retry
|
|
147
|
-
|
|
148
|
-
4. Is this sensitive data?
|
|
149
|
-
├─ YES → Never log (passwords, tokens, PII)
|
|
150
|
-
└─ NO → Log with context
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Error Handling Checklist
|
|
154
|
-
|
|
155
|
-
Before committing error handling code, verify:
|
|
156
|
-
|
|
157
|
-
- [ ] Type-safe error handling (no `any`)
|
|
158
|
-
- [ ] User-friendly error messages (no internals)
|
|
159
|
-
- [ ] Errors logged with context (no sensitive data)
|
|
160
|
-
- [ ] Recovery paths provided when possible
|
|
161
|
-
- [ ] Consistent error shapes used
|
|
162
|
-
- [ ] Error boundaries for React components
|
|
163
|
-
- [ ] Async operations have proper error handling
|
|
164
|
-
- [ ] Validation errors use Zod
|
|
165
|
-
- [ ] Network errors handled gracefully
|
|
166
|
-
|
|
167
|
-
## Common Mistakes to Avoid
|
|
168
|
-
|
|
169
|
-
1. **Exposing internal details** - Never show SQL, stack traces, file paths
|
|
170
|
-
2. **Using `any` for errors** - Always use type guards or Result types
|
|
171
|
-
3. **Logging sensitive data** - Never log passwords, tokens, PII
|
|
172
|
-
4. **Ignoring errors** - Always handle errors, even if just logging
|
|
173
|
-
5. **Generic error messages** - Provide specific, actionable messages
|
|
174
|
-
|
|
175
|
-
## Reference
|
|
176
|
-
|
|
177
|
-
- **Standard**: `packages/core/docs/standards/10-error-handling-patterns.md`
|
|
178
|
-
- **Code Quality**: See `06-code-quality.mdc` for TypeScript standards
|
|
179
|
-
- **Security**: See `01-standards-compliance.mdc` for security requirements
|