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