@jmruthers/pace-core 0.5.193 → 0.6.1
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 +29 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +372 -0
- package/cursor-rules/01-standards-compliance.mdc +275 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +341 -0
- package/cursor-rules/04-testing-standards.mdc +315 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +392 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
- package/cursor-rules/CHANGELOG.md +101 -0
- package/cursor-rules/README.md +191 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
- package/dist/{DataTable-5FU7IESH.js → DataTable-DQ7RSOHE.js} +6 -6
- package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +34 -155
- package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-ATAP5UTR.js} +2 -2
- package/dist/{chunk-6C4YBBJM 5.js → chunk-3QRJFVBR.js} +1 -1
- package/dist/chunk-3QRJFVBR.js.map +1 -0
- package/dist/{chunk-IIELH4DL.js → chunk-3XTALGJF.js} +2 -2
- package/dist/{chunk-IIELH4DL.js.map → chunk-3XTALGJF.js.map} +1 -1
- package/dist/{chunk-HWIIPPNI.js → chunk-4N5C5XZU.js} +20 -20
- package/dist/chunk-4N5C5XZU.js.map +1 -0
- package/dist/{chunk-7EQTDTTJ.js → chunk-4ZC4GX36.js} +5 -5
- package/dist/{chunk-7EQTDTTJ.js 2.map → chunk-4ZC4GX36.js.map} +1 -1
- package/dist/{chunk-7FLMSG37.js → chunk-BYFSK72L.js} +22 -22
- package/dist/chunk-BYFSK72L.js.map +1 -0
- package/dist/{chunk-LFNCN2SP.js → chunk-EXUD6RNJ.js} +46 -7
- package/dist/chunk-EXUD6RNJ.js.map +1 -0
- package/dist/{chunk-NOAYCWCX 5.js → chunk-GLK6VM3F.js} +167 -169
- package/dist/chunk-GLK6VM3F.js.map +1 -0
- package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
- package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
- package/dist/{chunk-BC4IJKSL.js → chunk-JBKQ3SAO.js} +2 -2
- package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
- package/dist/{chunk-E3SPN4VZ 5.js → chunk-T33XF5ZC.js} +119 -114
- package/dist/chunk-T33XF5ZC.js.map +1 -0
- package/dist/{chunk-XNXXZ43G.js → chunk-XM25TVIE.js} +27 -4
- package/dist/chunk-XM25TVIE.js.map +1 -0
- package/dist/components.d.ts +3 -3
- package/dist/components.js +8 -8
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +17 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +15 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +5 -5
- package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +3 -3
- package/docs/getting-started/cursor-rules.md +262 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/MIGRATION_GUIDE.md +4 -4
- package/docs/migration/REACT_19_MIGRATION.md +227 -0
- package/docs/standards/README.md +39 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +11 -6
- package/scripts/audit-consuming-app.cjs +961 -0
- package/scripts/check-pace-core-compliance.cjs +34 -15
- package/scripts/install-cursor-rules.cjs +236 -0
- package/src/__tests__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- package/src/components/Badge/Badge.tsx +2 -4
- package/src/components/Button/Button.tsx +5 -4
- package/src/components/Calendar/Calendar.tsx +1 -1
- package/src/components/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +2 -2
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +13 -5
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/DataTableCore.tsx +4 -7
- package/src/components/DataTable/components/DataTableModals.tsx +1 -1
- package/src/components/DataTable/components/EditableRow.tsx +1 -1
- package/src/components/DataTable/components/UnifiedTableBody.tsx +6 -8
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
- package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +6 -5
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
- package/src/components/EventSelector/EventSelector.tsx +1 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
- package/src/components/Footer/Footer.tsx +1 -1
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +30 -26
- package/src/components/Header/Header.tsx +1 -1
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.tsx +28 -30
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +8 -8
- package/src/components/NavigationMenu/NavigationMenu.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +50 -50
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +1 -1
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
- package/src/components/Select/Select.tsx +33 -22
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/components/UserMenu/UserMenu.tsx +1 -1
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/public/usePublicEvent.ts +1 -1
- package/src/hooks/public/usePublicEventLogo.ts +1 -1
- package/src/hooks/public/usePublicRouteParams.ts +1 -1
- package/src/hooks/useDataTableState.ts +8 -18
- package/src/hooks/useFocusManagement.ts +2 -2
- package/src/hooks/useFocusTrap.ts +4 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +1 -1
- package/src/hooks/usePermissionCache.ts +1 -1
- package/src/hooks/useSecureDataAccess.ts +19 -4
- package/src/hooks/useToast.ts +2 -2
- package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/UnifiedAuthProvider.tsx +22 -22
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +24 -24
- package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
- package/src/rbac/components/NavigationGuard.tsx +1 -1
- package/src/rbac/components/NavigationProvider.tsx +1 -1
- package/src/rbac/components/PagePermissionGuard.tsx +1 -1
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +1 -1
- package/src/rbac/components/RoleBasedRouter.tsx +1 -1
- package/src/rbac/components/SecureDataProvider.tsx +1 -1
- package/src/rbac/secureClient.ts +12 -0
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js +0 -628
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js.map +0 -1
- package/dist/chunk-7FLMSG37.js 2.map +0 -1
- package/dist/chunk-7FLMSG37.js.map +0 -1
- package/dist/chunk-E3SPN4VZ.js +0 -12917
- package/dist/chunk-E3SPN4VZ.js.map +0 -1
- package/dist/chunk-E66EQZE6 5.js +0 -37
- package/dist/chunk-E66EQZE6.js 2.map +0 -1
- package/dist/chunk-HWIIPPNI.js.map +0 -1
- package/dist/chunk-I7PSE6JW 5.js +0 -191
- package/dist/chunk-I7PSE6JW.js 2.map +0 -1
- package/dist/chunk-KNC55RTG.js 5.map +0 -1
- package/dist/chunk-KQCRWDSA.js 5.map +0 -1
- package/dist/chunk-LFNCN2SP.js 2.map +0 -1
- package/dist/chunk-LFNCN2SP.js.map +0 -1
- package/dist/chunk-LMC26NLJ 2.js +0 -84
- package/dist/chunk-NOAYCWCX.js +0 -4993
- package/dist/chunk-NOAYCWCX.js.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js.map +0 -1
- package/dist/chunk-QXHPKYJV 3.js +0 -113
- package/dist/chunk-R77UEZ4E 3.js +0 -68
- package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
- package/dist/chunk-XNXXZ43G.js.map +0 -1
- package/dist/chunk-ZSAAAMVR 6.js +0 -25
- package/dist/components.js 5.map +0 -1
- package/dist/styles/index 2.js +0 -12
- package/dist/styles/index.js 5.map +0 -1
- package/dist/theming/runtime 5.js +0 -19
- package/dist/theming/runtime.js 5.map +0 -1
- /package/dist/{DataTable-5FU7IESH.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
- /package/dist/{chunk-BC4IJKSL.js.map → chunk-JBKQ3SAO.js.map} +0 -0
- /package/dist/{chunk-QWWZ5CAQ.js 3.map → chunk-LXQLPRQ2.js.map} +0 -0
- /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
- /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/index.ts +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enforce code quality standards including TypeScript, ESLint, formatting, performance, and accessibility
|
|
3
|
+
globs: ["src/**/*.{ts,tsx,js,jsx}"]
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
paceCoreVersion: "0.5.x"
|
|
6
|
+
rulesVersion: "2025-01-15"
|
|
7
|
+
---
|
|
8
|
+
# Code Quality Guide
|
|
9
|
+
|
|
10
|
+
This guide enforces code quality standards to ensure maintainable, performant, and accessible code.
|
|
11
|
+
|
|
12
|
+
## TypeScript Standards
|
|
13
|
+
|
|
14
|
+
### MUST: Use Strict Mode
|
|
15
|
+
|
|
16
|
+
**TypeScript MUST use strict mode:**
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
// tsconfig.json
|
|
20
|
+
{
|
|
21
|
+
"compilerOptions": {
|
|
22
|
+
"strict": true,
|
|
23
|
+
"noImplicitAny": true,
|
|
24
|
+
"strictNullChecks": true,
|
|
25
|
+
"strictFunctionTypes": true,
|
|
26
|
+
"strictBindCallApply": true,
|
|
27
|
+
"strictPropertyInitialization": true,
|
|
28
|
+
"noImplicitThis": true,
|
|
29
|
+
"alwaysStrict": true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### MUST NOT: Use `any`
|
|
35
|
+
|
|
36
|
+
**MUST NOT use `any` type. Use `unknown` if type is truly unknown:**
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// ❌ WRONG - Using any
|
|
40
|
+
function processData(data: any) {
|
|
41
|
+
return data.value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ✅ CORRECT - Using unknown with type guard
|
|
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
|
+
|
|
52
|
+
// ✅ CORRECT - Using proper types
|
|
53
|
+
interface Data {
|
|
54
|
+
value: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function processData(data: Data) {
|
|
58
|
+
return data.value;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### MUST: Use Discriminated Unions
|
|
63
|
+
|
|
64
|
+
**MUST use discriminated unions instead of boolean flags:**
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// ❌ WRONG - Boolean flags
|
|
68
|
+
interface User {
|
|
69
|
+
isAdmin: boolean;
|
|
70
|
+
isGuest: boolean;
|
|
71
|
+
// Confusing: what if both are true?
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ✅ CORRECT - Discriminated union
|
|
75
|
+
type User =
|
|
76
|
+
| { type: 'admin'; permissions: Permission[] }
|
|
77
|
+
| { type: 'guest'; limitedAccess: boolean }
|
|
78
|
+
| { type: 'user'; role: UserRole };
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### SHOULD: Use ReadonlyArray
|
|
82
|
+
|
|
83
|
+
**SHOULD use ReadonlyArray for immutable arrays:**
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// ✅ CORRECT - ReadonlyArray
|
|
87
|
+
function processItems(items: ReadonlyArray<Item>) {
|
|
88
|
+
// Can't mutate items
|
|
89
|
+
return items.map(item => transform(item));
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## ESLint Configuration
|
|
94
|
+
|
|
95
|
+
### MUST: Use pace-core ESLint Config
|
|
96
|
+
|
|
97
|
+
**MUST extend pace-core ESLint configuration:**
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
// eslint.config.js
|
|
101
|
+
import paceCoreConfig from '@jmruthers/pace-core/eslint-config';
|
|
102
|
+
|
|
103
|
+
export default [
|
|
104
|
+
...paceCoreConfig,
|
|
105
|
+
// Your additional config
|
|
106
|
+
];
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### MUST: Fix ESLint Errors
|
|
110
|
+
|
|
111
|
+
**MUST fix all ESLint errors before committing:**
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run lint
|
|
115
|
+
npm run lint:fix # Auto-fix where possible
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Code Formatting
|
|
119
|
+
|
|
120
|
+
### MUST: Use Consistent Formatting
|
|
121
|
+
|
|
122
|
+
**MUST use Prettier or equivalent for consistent formatting:**
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
// .prettierrc
|
|
126
|
+
{
|
|
127
|
+
"semi": true,
|
|
128
|
+
"singleQuote": true,
|
|
129
|
+
"tabWidth": 2,
|
|
130
|
+
"trailingComma": "es5",
|
|
131
|
+
"printWidth": 100
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### MUST: Format Before Committing
|
|
136
|
+
|
|
137
|
+
**MUST format code before committing:**
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm run format
|
|
141
|
+
# Or use pre-commit hook
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Performance Considerations
|
|
145
|
+
|
|
146
|
+
### SHOULD: Optimize Re-renders
|
|
147
|
+
|
|
148
|
+
**SHOULD use React.memo, useMemo, useCallback appropriately:**
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
// ✅ CORRECT - Memoize expensive computations
|
|
152
|
+
const expensiveValue = useMemo(() => {
|
|
153
|
+
return computeExpensiveValue(data);
|
|
154
|
+
}, [data]);
|
|
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
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### SHOULD: Avoid Unnecessary Re-renders
|
|
168
|
+
|
|
169
|
+
**SHOULD avoid causing unnecessary re-renders:**
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
// ❌ WRONG - New object on every render
|
|
173
|
+
function Component({ items }) {
|
|
174
|
+
const config = { items, enabled: true }; // New object each render
|
|
175
|
+
return <Child config={config} />;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ✅ CORRECT - Memoize object
|
|
179
|
+
function Component({ items }) {
|
|
180
|
+
const config = useMemo(
|
|
181
|
+
() => ({ items, enabled: true }),
|
|
182
|
+
[items]
|
|
183
|
+
);
|
|
184
|
+
return <Child config={config} />;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### SHOULD: Lazy Load Heavy Components
|
|
189
|
+
|
|
190
|
+
**SHOULD lazy load heavy components:**
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
// ✅ CORRECT - Lazy loading
|
|
194
|
+
import { lazy, Suspense } from 'react';
|
|
195
|
+
|
|
196
|
+
const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
|
197
|
+
|
|
198
|
+
function App() {
|
|
199
|
+
return (
|
|
200
|
+
<Suspense fallback={<Loading />}>
|
|
201
|
+
<HeavyComponent />
|
|
202
|
+
</Suspense>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Accessibility Requirements
|
|
208
|
+
|
|
209
|
+
### MUST: Use Semantic HTML
|
|
210
|
+
|
|
211
|
+
**MUST use semantic HTML elements:**
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
// ❌ WRONG - Non-semantic
|
|
215
|
+
<div onClick={handleClick}>Click me</div>
|
|
216
|
+
|
|
217
|
+
// ✅ CORRECT - Semantic
|
|
218
|
+
<button onClick={handleClick}>Click me</button>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### MUST: Provide ARIA Labels
|
|
222
|
+
|
|
223
|
+
**MUST provide ARIA labels for interactive elements:**
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// ✅ CORRECT - ARIA labels
|
|
227
|
+
<button aria-label="Close dialog">×</button>
|
|
228
|
+
<input aria-label="Search" type="search" />
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### MUST: Ensure Keyboard Navigation
|
|
232
|
+
|
|
233
|
+
**MUST ensure all interactive elements are keyboard accessible:**
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
// ✅ CORRECT - Keyboard accessible
|
|
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>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### MUST: Provide Focus Indicators
|
|
250
|
+
|
|
251
|
+
**MUST provide visible focus indicators:**
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
// ✅ CORRECT - Focus styles
|
|
255
|
+
<button className="focus:outline-none focus:ring-2 focus:ring-main-500">
|
|
256
|
+
Click me
|
|
257
|
+
</button>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Code Review Checklist
|
|
261
|
+
|
|
262
|
+
**Before submitting code for review, verify:**
|
|
263
|
+
|
|
264
|
+
- [ ] TypeScript strict mode enabled
|
|
265
|
+
- [ ] No `any` types used
|
|
266
|
+
- [ ] ESLint passes with no errors
|
|
267
|
+
- [ ] Code is formatted consistently
|
|
268
|
+
- [ ] Performance optimizations applied where needed
|
|
269
|
+
- [ ] Accessibility requirements met
|
|
270
|
+
- [ ] Semantic HTML used
|
|
271
|
+
- [ ] ARIA labels provided
|
|
272
|
+
- [ ] Keyboard navigation works
|
|
273
|
+
- [ ] Focus indicators visible
|
|
274
|
+
- [ ] Code is readable and maintainable
|
|
275
|
+
- [ ] Comments explain "why", not "what"
|
|
276
|
+
|
|
277
|
+
## Code Complexity
|
|
278
|
+
|
|
279
|
+
### SHOULD: Keep Functions Small
|
|
280
|
+
|
|
281
|
+
**Functions SHOULD be small and focused:**
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
// ❌ WRONG - Large function
|
|
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
|
+
}
|
|
297
|
+
|
|
298
|
+
function processUserData(user: User) {
|
|
299
|
+
const validation = validateUser(user);
|
|
300
|
+
if (!validation.valid) return validation;
|
|
301
|
+
return transformUser(user);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### SHOULD: Limit Cyclomatic Complexity
|
|
306
|
+
|
|
307
|
+
**Functions SHOULD have low cyclomatic complexity (< 10):**
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
// ❌ WRONG - High complexity
|
|
311
|
+
function processData(data) {
|
|
312
|
+
if (condition1) {
|
|
313
|
+
if (condition2) {
|
|
314
|
+
if (condition3) {
|
|
315
|
+
// ... many nested conditions
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ✅ CORRECT - Lower complexity
|
|
322
|
+
function processData(data) {
|
|
323
|
+
if (!condition1) return;
|
|
324
|
+
if (!condition2) return;
|
|
325
|
+
if (!condition3) return;
|
|
326
|
+
// Process
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Error Handling
|
|
331
|
+
|
|
332
|
+
### MUST: Handle Errors Gracefully
|
|
333
|
+
|
|
334
|
+
**MUST handle errors and provide user feedback:**
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
// ✅ CORRECT - Error handling
|
|
338
|
+
try {
|
|
339
|
+
const data = await fetchData();
|
|
340
|
+
setData(data);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
logger.error('Failed to fetch data', error);
|
|
343
|
+
toast.error('Failed to load data. Please try again.');
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### MUST: Use Type-Safe Error Handling
|
|
348
|
+
|
|
349
|
+
**MUST use type-safe error handling:**
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
// ✅ CORRECT - Type-safe errors
|
|
353
|
+
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
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
await apiCall();
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (isApiError(error)) {
|
|
366
|
+
handleApiError(error);
|
|
367
|
+
} else {
|
|
368
|
+
handleUnknownError(error);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Code Quality Checklist
|
|
374
|
+
|
|
375
|
+
Before committing, verify:
|
|
376
|
+
- [ ] TypeScript strict mode enabled
|
|
377
|
+
- [ ] No `any` types
|
|
378
|
+
- [ ] ESLint passes
|
|
379
|
+
- [ ] Code formatted
|
|
380
|
+
- [ ] Performance optimized
|
|
381
|
+
- [ ] Accessible (semantic HTML, ARIA, keyboard)
|
|
382
|
+
- [ ] Functions are small and focused
|
|
383
|
+
- [ ] Error handling in place
|
|
384
|
+
- [ ] Code is readable
|
|
385
|
+
- [ ] Comments explain "why"
|
|
386
|
+
|
|
387
|
+
## Reference
|
|
388
|
+
|
|
389
|
+
- TypeScript Handbook: https://www.typescriptlang.org/docs/
|
|
390
|
+
- ESLint Rules: pace-core eslint-config
|
|
391
|
+
- React Performance: https://react.dev/learn/render-and-commit
|
|
392
|
+
- Web Accessibility: https://www.w3.org/WAI/
|
|
@@ -0,0 +1,309 @@
|
|
|
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: true
|
|
5
|
+
paceCoreVersion: "0.6.x"
|
|
6
|
+
rulesVersion: "2025-01-28"
|
|
7
|
+
---
|
|
8
|
+
# Tech Stack Compliance Guide
|
|
9
|
+
|
|
10
|
+
This guide ensures consuming apps use the correct versions and patterns for all technologies in the PACE stack.
|
|
11
|
+
|
|
12
|
+
## Tailwind CSS v4
|
|
13
|
+
|
|
14
|
+
### MUST: Use Tailwind v4 CSS-First Approach
|
|
15
|
+
|
|
16
|
+
**MUST use Tailwind v4 CSS-first syntax, NOT v3 patterns:**
|
|
17
|
+
|
|
18
|
+
```css
|
|
19
|
+
/* ✅ CORRECT - Tailwind v4 */
|
|
20
|
+
@import 'tailwindcss';
|
|
21
|
+
|
|
22
|
+
@theme {
|
|
23
|
+
--color-main-*: oklch(...);
|
|
24
|
+
--color-sec-*: oklch(...);
|
|
25
|
+
--color-acc-*: oklch(...);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ❌ WRONG - Tailwind v3 */
|
|
29
|
+
@tailwind base;
|
|
30
|
+
@tailwind components;
|
|
31
|
+
@tailwind utilities;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### MUST NOT: Use Bracket Syntax
|
|
35
|
+
|
|
36
|
+
**MUST NOT use bracket syntax like `bg-[oklch(...)]`. Map everything through theme variables:**
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// ❌ WRONG - Bracket syntax
|
|
40
|
+
<div className="bg-[oklch(0.5 0.2 200)]">
|
|
41
|
+
|
|
42
|
+
// ✅ CORRECT - Theme variable
|
|
43
|
+
<div className="bg-main-500">
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### MUST: Use Custom Color Namespaces
|
|
47
|
+
|
|
48
|
+
**MUST use custom color namespaces:**
|
|
49
|
+
- `main-*` for primary colors (green, emerald, teal, cyan, sky, blue)
|
|
50
|
+
- `sec-*` for secondary colors (indigo, violet, purple, fuchsia, slate, gray, zinc, neutral, stone)
|
|
51
|
+
- `acc-*` for accent colors (red, orange, amber, yellow, lime, pink, rose)
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// ✅ CORRECT - Custom namespaces
|
|
55
|
+
<div className="bg-main-500 text-sec-800 border-acc-300">
|
|
56
|
+
|
|
57
|
+
// ❌ WRONG - Standard Tailwind colors
|
|
58
|
+
<div className="bg-blue-500 text-gray-800 border-red-300">
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### MUST: Use Semantic Classes
|
|
62
|
+
|
|
63
|
+
**MUST use semantic classes mapped in index.css:**
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// ✅ CORRECT - Semantic classes
|
|
67
|
+
<div className="bg-background text-foreground">
|
|
68
|
+
|
|
69
|
+
// ❌ WRONG - Direct color classes (unless necessary)
|
|
70
|
+
<div className="bg-main-50 text-main-950">
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## React 19+
|
|
74
|
+
|
|
75
|
+
### MUST: Use React 19+ Features
|
|
76
|
+
|
|
77
|
+
**MUST use React 19+ patterns:**
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// ✅ CORRECT - React 19 features
|
|
81
|
+
import { Suspense, useTransition } from 'react';
|
|
82
|
+
|
|
83
|
+
function App() {
|
|
84
|
+
const [isPending, startTransition] = useTransition();
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Suspense fallback={<Loading />}>
|
|
88
|
+
<Component />
|
|
89
|
+
</Suspense>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### MUST: Use Functional Components
|
|
95
|
+
|
|
96
|
+
**MUST use functional components with hooks exclusively:**
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
// ✅ CORRECT - Functional component
|
|
100
|
+
function MyComponent() {
|
|
101
|
+
const [state, setState] = useState();
|
|
102
|
+
return <div>...</div>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ❌ WRONG - Class component
|
|
106
|
+
class MyComponent extends React.Component {
|
|
107
|
+
// ...
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Supabase
|
|
112
|
+
|
|
113
|
+
### MUST: Use Secure Supabase Client
|
|
114
|
+
|
|
115
|
+
**MUST use `useSecureSupabase()` for all database operations:**
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// ✅ CORRECT - Secure client
|
|
119
|
+
import { useSecureSupabase } from '@jmruthers/pace-core/rbac';
|
|
120
|
+
|
|
121
|
+
function Component() {
|
|
122
|
+
const secureSupabase = useSecureSupabase();
|
|
123
|
+
const { data } = await secureSupabase.from('table').select('*');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ❌ WRONG - Base client
|
|
127
|
+
import { createClient } from '@supabase/supabase-js';
|
|
128
|
+
const supabase = createClient(...);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### MUST: Enforce RLS
|
|
132
|
+
|
|
133
|
+
**MUST ensure RLS is enabled on all tables. Never bypass RLS.**
|
|
134
|
+
|
|
135
|
+
### SHOULD: Use RPC Functions
|
|
136
|
+
|
|
137
|
+
**SHOULD use RPC functions for complex queries:**
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// ✅ CORRECT - RPC function
|
|
141
|
+
const { data } = await secureSupabase.rpc('data_events_list', {
|
|
142
|
+
organisation_id: orgId
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ❌ AVOID - Complex client-side queries
|
|
146
|
+
const { data } = await secureSupabase
|
|
147
|
+
.from('events')
|
|
148
|
+
.select('*, organisations(*), users(*)')
|
|
149
|
+
.eq('organisation_id', orgId)
|
|
150
|
+
// ... many joins
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## TanStack Query
|
|
154
|
+
|
|
155
|
+
### MUST: Use TanStack Query for Server State
|
|
156
|
+
|
|
157
|
+
**MUST use TanStack Query for all server state management:**
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// ✅ CORRECT - TanStack Query
|
|
161
|
+
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
162
|
+
|
|
163
|
+
function Component() {
|
|
164
|
+
const { data, isLoading } = useQuery({
|
|
165
|
+
queryKey: ['events', orgId],
|
|
166
|
+
queryFn: () => fetchEvents(orgId)
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const mutation = useMutation({
|
|
170
|
+
mutationFn: createEvent,
|
|
171
|
+
onSuccess: () => {
|
|
172
|
+
queryClient.invalidateQueries({ queryKey: ['events'] });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ❌ WRONG - useState for server state
|
|
178
|
+
const [data, setData] = useState();
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
fetchEvents().then(setData);
|
|
181
|
+
}, []);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### SHOULD: Configure Query Client
|
|
185
|
+
|
|
186
|
+
**SHOULD configure QueryClient with appropriate defaults:**
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
// ✅ CORRECT - Configured QueryClient
|
|
190
|
+
const queryClient = new QueryClient({
|
|
191
|
+
defaultOptions: {
|
|
192
|
+
queries: {
|
|
193
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
194
|
+
cacheTime: 10 * 60 * 1000, // 10 minutes
|
|
195
|
+
retry: 1,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## React Hook Form + Zod
|
|
202
|
+
|
|
203
|
+
### MUST: Use React Hook Form with Zod
|
|
204
|
+
|
|
205
|
+
**MUST use React Hook Form with Zod for all forms:**
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
// ✅ CORRECT - React Hook Form + Zod
|
|
209
|
+
import { useZodForm } from '@jmruthers/pace-core';
|
|
210
|
+
import { z } from 'zod';
|
|
211
|
+
|
|
212
|
+
const schema = z.object({
|
|
213
|
+
email: z.string().email(),
|
|
214
|
+
name: z.string().min(1),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
function Form() {
|
|
218
|
+
const form = useZodForm(schema);
|
|
219
|
+
return <Form {...form}>...</Form>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ❌ WRONG - Manual form handling
|
|
223
|
+
const [email, setEmail] = useState('');
|
|
224
|
+
const [errors, setErrors] = useState({});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Vite
|
|
228
|
+
|
|
229
|
+
### MUST: Use Vite for Build Tooling
|
|
230
|
+
|
|
231
|
+
**MUST use Vite for all build tooling:**
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// vite.config.ts
|
|
235
|
+
import { defineConfig } from 'vite';
|
|
236
|
+
import react from '@vitejs/plugin-react';
|
|
237
|
+
|
|
238
|
+
export default defineConfig({
|
|
239
|
+
plugins: [react()],
|
|
240
|
+
// ...
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### MUST: Use Environment Variables Correctly
|
|
245
|
+
|
|
246
|
+
**MUST use `import.meta.env` for environment variables:**
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
// ✅ CORRECT - Vite env vars
|
|
250
|
+
const apiUrl = import.meta.env.VITE_API_URL;
|
|
251
|
+
|
|
252
|
+
// ❌ WRONG - Node.js env vars
|
|
253
|
+
const apiUrl = process.env.VITE_API_URL;
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## TypeScript
|
|
257
|
+
|
|
258
|
+
### MUST: Use TypeScript 5+
|
|
259
|
+
|
|
260
|
+
**MUST use TypeScript 5+ with strict mode:**
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
// tsconfig.json
|
|
264
|
+
{
|
|
265
|
+
"compilerOptions": {
|
|
266
|
+
"target": "ES2022",
|
|
267
|
+
"module": "ESNext",
|
|
268
|
+
"strict": true,
|
|
269
|
+
// ...
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Version Requirements
|
|
275
|
+
|
|
276
|
+
### MUST: Use Required Versions
|
|
277
|
+
|
|
278
|
+
**MUST use compatible versions:**
|
|
279
|
+
|
|
280
|
+
- React: `^19.0.0`
|
|
281
|
+
- TypeScript: `^5.0.0`
|
|
282
|
+
- Tailwind CSS: `^4.0.0`
|
|
283
|
+
- Vite: `^6.0.0`
|
|
284
|
+
- Supabase: `^2.49.0+`
|
|
285
|
+
|
|
286
|
+
**Check `package.json` for exact versions required by pace-core.**
|
|
287
|
+
|
|
288
|
+
## Tech Stack Checklist
|
|
289
|
+
|
|
290
|
+
Before committing, verify:
|
|
291
|
+
- [ ] Tailwind v4 CSS-first syntax (no @tailwind directives)
|
|
292
|
+
- [ ] No bracket syntax for colors (use theme variables)
|
|
293
|
+
- [ ] Custom color namespaces (main-*, sec-*, acc-*)
|
|
294
|
+
- [ ] React 19+ functional components
|
|
295
|
+
- [ ] Secure Supabase client (useSecureSupabase)
|
|
296
|
+
- [ ] TanStack Query for server state
|
|
297
|
+
- [ ] React Hook Form + Zod for forms
|
|
298
|
+
- [ ] Vite for build tooling
|
|
299
|
+
- [ ] TypeScript 5+ with strict mode
|
|
300
|
+
- [ ] All versions compatible with pace-core
|
|
301
|
+
|
|
302
|
+
## Reference
|
|
303
|
+
|
|
304
|
+
- Tailwind v4: https://tailwindcss.com/blog/tailwindcss-v4
|
|
305
|
+
- React 19: https://react.dev/blog/2024/04/25/react-19-upgrade-guide
|
|
306
|
+
- Supabase: https://supabase.com/docs
|
|
307
|
+
- TanStack Query: https://tanstack.com/query
|
|
308
|
+
- React Hook Form: https://react-hook-form.com
|
|
309
|
+
- Vite: https://vitejs.dev
|