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