@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.
Files changed (191) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +372 -0
  4. package/cursor-rules/01-standards-compliance.mdc +275 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +341 -0
  7. package/cursor-rules/04-testing-standards.mdc +315 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +392 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
  11. package/cursor-rules/CHANGELOG.md +101 -0
  12. package/cursor-rules/README.md +191 -0
  13. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
  14. package/dist/{DataTable-5FU7IESH.js → DataTable-DQ7RSOHE.js} +6 -6
  15. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +34 -155
  16. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-ATAP5UTR.js} +2 -2
  17. package/dist/{chunk-6C4YBBJM 5.js → chunk-3QRJFVBR.js} +1 -1
  18. package/dist/chunk-3QRJFVBR.js.map +1 -0
  19. package/dist/{chunk-IIELH4DL.js → chunk-3XTALGJF.js} +2 -2
  20. package/dist/{chunk-IIELH4DL.js.map → chunk-3XTALGJF.js.map} +1 -1
  21. package/dist/{chunk-HWIIPPNI.js → chunk-4N5C5XZU.js} +20 -20
  22. package/dist/chunk-4N5C5XZU.js.map +1 -0
  23. package/dist/{chunk-7EQTDTTJ.js → chunk-4ZC4GX36.js} +5 -5
  24. package/dist/{chunk-7EQTDTTJ.js 2.map → chunk-4ZC4GX36.js.map} +1 -1
  25. package/dist/{chunk-7FLMSG37.js → chunk-BYFSK72L.js} +22 -22
  26. package/dist/chunk-BYFSK72L.js.map +1 -0
  27. package/dist/{chunk-LFNCN2SP.js → chunk-EXUD6RNJ.js} +46 -7
  28. package/dist/chunk-EXUD6RNJ.js.map +1 -0
  29. package/dist/{chunk-NOAYCWCX 5.js → chunk-GLK6VM3F.js} +167 -169
  30. package/dist/chunk-GLK6VM3F.js.map +1 -0
  31. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  32. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  33. package/dist/{chunk-BC4IJKSL.js → chunk-JBKQ3SAO.js} +2 -2
  34. package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
  35. package/dist/{chunk-E3SPN4VZ 5.js → chunk-T33XF5ZC.js} +119 -114
  36. package/dist/chunk-T33XF5ZC.js.map +1 -0
  37. package/dist/{chunk-XNXXZ43G.js → chunk-XM25TVIE.js} +27 -4
  38. package/dist/chunk-XM25TVIE.js.map +1 -0
  39. package/dist/components.d.ts +3 -3
  40. package/dist/components.js +8 -8
  41. package/dist/hooks.d.ts +6 -6
  42. package/dist/hooks.js +17 -22
  43. package/dist/hooks.js.map +1 -1
  44. package/dist/index.d.ts +7 -7
  45. package/dist/index.js +15 -16
  46. package/dist/index.js.map +1 -1
  47. package/dist/providers.js +1 -1
  48. package/dist/rbac/index.d.ts +1 -1
  49. package/dist/rbac/index.js +5 -5
  50. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
  51. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  52. package/dist/utils.d.ts +1 -1
  53. package/dist/utils.js +3 -3
  54. package/docs/getting-started/cursor-rules.md +262 -0
  55. package/docs/getting-started/installation-guide.md +6 -1
  56. package/docs/getting-started/quick-start.md +6 -1
  57. package/docs/migration/MIGRATION_GUIDE.md +4 -4
  58. package/docs/migration/REACT_19_MIGRATION.md +227 -0
  59. package/docs/standards/README.md +39 -0
  60. package/docs/troubleshooting/migration.md +4 -4
  61. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  62. package/package.json +11 -6
  63. package/scripts/audit-consuming-app.cjs +961 -0
  64. package/scripts/check-pace-core-compliance.cjs +34 -15
  65. package/scripts/install-cursor-rules.cjs +236 -0
  66. package/src/__tests__/helpers/test-providers.tsx +1 -1
  67. package/src/__tests__/helpers/test-utils.tsx +1 -1
  68. package/src/components/Badge/Badge.tsx +2 -4
  69. package/src/components/Button/Button.tsx +5 -4
  70. package/src/components/Calendar/Calendar.tsx +1 -1
  71. package/src/components/DataTable/DataTable.test.tsx +57 -93
  72. package/src/components/DataTable/DataTable.tsx +2 -2
  73. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +13 -5
  74. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  75. package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
  76. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  77. package/src/components/DataTable/components/DataTableCore.tsx +4 -7
  78. package/src/components/DataTable/components/DataTableModals.tsx +1 -1
  79. package/src/components/DataTable/components/EditableRow.tsx +1 -1
  80. package/src/components/DataTable/components/UnifiedTableBody.tsx +6 -8
  81. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  82. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  83. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  84. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  85. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  86. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  87. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  88. package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
  89. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  90. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  91. package/src/components/Dialog/Dialog.tsx +6 -5
  92. package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
  93. package/src/components/EventSelector/EventSelector.tsx +1 -1
  94. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  95. package/src/components/Footer/Footer.tsx +1 -1
  96. package/src/components/Form/Form.test.tsx +36 -15
  97. package/src/components/Form/Form.tsx +30 -26
  98. package/src/components/Header/Header.tsx +1 -1
  99. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  100. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  101. package/src/components/Input/Input.tsx +28 -30
  102. package/src/components/Label/Label.tsx +1 -1
  103. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  104. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  105. package/src/components/LoginForm/LoginForm.tsx +8 -8
  106. package/src/components/NavigationMenu/NavigationMenu.tsx +1 -1
  107. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  108. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +50 -50
  109. package/src/components/PaceAppLayout/PaceAppLayout.tsx +1 -1
  110. package/src/components/PaceAppLayout/README.md +1 -1
  111. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  112. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  113. package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
  114. package/src/components/Progress/Progress.tsx +1 -1
  115. package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
  116. package/src/components/Select/Select.tsx +33 -22
  117. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
  118. package/src/components/Table/Table.tsx +1 -1
  119. package/src/components/Textarea/Textarea.tsx +27 -29
  120. package/src/components/Toast/Toast.tsx +1 -1
  121. package/src/components/Tooltip/Tooltip.tsx +1 -1
  122. package/src/components/UserMenu/UserMenu.tsx +1 -1
  123. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  124. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  125. package/src/hooks/public/usePublicEvent.ts +1 -1
  126. package/src/hooks/public/usePublicEventLogo.ts +1 -1
  127. package/src/hooks/public/usePublicRouteParams.ts +1 -1
  128. package/src/hooks/useDataTableState.ts +8 -18
  129. package/src/hooks/useFocusManagement.ts +2 -2
  130. package/src/hooks/useFocusTrap.ts +4 -4
  131. package/src/hooks/useFormDialog.ts +8 -7
  132. package/src/hooks/useInactivityTracker.ts +1 -1
  133. package/src/hooks/usePermissionCache.ts +1 -1
  134. package/src/hooks/useSecureDataAccess.ts +19 -4
  135. package/src/hooks/useToast.ts +2 -2
  136. package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
  137. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  138. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  139. package/src/providers/services/UnifiedAuthProvider.tsx +22 -22
  140. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
  141. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +24 -24
  142. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  143. package/src/rbac/components/NavigationGuard.tsx +1 -1
  144. package/src/rbac/components/NavigationProvider.tsx +1 -1
  145. package/src/rbac/components/PagePermissionGuard.tsx +1 -1
  146. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  147. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  148. package/src/rbac/components/RoleBasedRouter.tsx +1 -1
  149. package/src/rbac/components/SecureDataProvider.tsx +1 -1
  150. package/src/rbac/secureClient.ts +12 -0
  151. package/src/utils/security/secureDataAccess.test.ts +31 -20
  152. package/src/utils/security/secureDataAccess.ts +4 -3
  153. package/dist/chunk-6C4YBBJM.js +0 -628
  154. package/dist/chunk-6C4YBBJM.js.map +0 -1
  155. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  156. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  157. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  158. package/dist/chunk-7FLMSG37.js.map +0 -1
  159. package/dist/chunk-E3SPN4VZ.js +0 -12917
  160. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  161. package/dist/chunk-E66EQZE6 5.js +0 -37
  162. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  163. package/dist/chunk-HWIIPPNI.js.map +0 -1
  164. package/dist/chunk-I7PSE6JW 5.js +0 -191
  165. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  166. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  167. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  168. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  169. package/dist/chunk-LFNCN2SP.js.map +0 -1
  170. package/dist/chunk-LMC26NLJ 2.js +0 -84
  171. package/dist/chunk-NOAYCWCX.js +0 -4993
  172. package/dist/chunk-NOAYCWCX.js.map +0 -1
  173. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  174. package/dist/chunk-QXHPKYJV 3.js +0 -113
  175. package/dist/chunk-R77UEZ4E 3.js +0 -68
  176. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  177. package/dist/chunk-XNXXZ43G.js.map +0 -1
  178. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  179. package/dist/components.js 5.map +0 -1
  180. package/dist/styles/index 2.js +0 -12
  181. package/dist/styles/index.js 5.map +0 -1
  182. package/dist/theming/runtime 5.js +0 -19
  183. package/dist/theming/runtime.js 5.map +0 -1
  184. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
  185. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
  186. /package/dist/{chunk-BC4IJKSL.js.map → chunk-JBKQ3SAO.js.map} +0 -0
  187. /package/dist/{chunk-QWWZ5CAQ.js 3.map → chunk-LXQLPRQ2.js.map} +0 -0
  188. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  189. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  190. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  191. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -385,6 +385,13 @@ function scanFile(filePath, manifest) {
385
385
  const content = fs.readFileSync(filePath, 'utf8');
386
386
  const relativePath = path.relative(process.cwd(), filePath);
387
387
 
388
+ // Normalize path for cross-platform compatibility (handle both forward and backslash paths)
389
+ const normalizedPath = relativePath.replace(/\\/g, '/');
390
+
391
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
392
+ // Direct Supabase auth calls are the correct approach in Edge Functions
393
+ const isEdgeFunction = normalizedPath.includes('supabase/functions/');
394
+
388
395
  // Check for restricted imports
389
396
  manifest.restrictedImports.forEach(({ module, reason }) => {
390
397
  const importPattern = new RegExp(`from\\s+['"]${module.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]`, 'g');
@@ -660,9 +667,15 @@ function scanFile(filePath, manifest) {
660
667
 
661
668
  // Skip Edge Functions - they run in Deno, not React, so React hooks are not available
662
669
  // Edge Functions MUST use direct supabase.auth.getUser() calls - this is correct and required
663
- // Handle both forward and backslash paths (Windows vs Unix)
664
- const normalizedPath = relativePath.replace(/\\/g, '/');
665
- const isEdgeFunction = normalizedPath.includes('supabase/functions/');
670
+ // isEdgeFunction is already defined above
671
+
672
+ // Other auth patterns - defined here so it's accessible in all scopes
673
+ const otherAuthPatterns = [
674
+ { pattern: /\.auth\.signIn\s*\(/g, method: 'signIn' },
675
+ { pattern: /\.auth\.signUp\s*\(/g, method: 'signUp' },
676
+ { pattern: /\.auth\.signOut\s*\(/g, method: 'signOut' },
677
+ { pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
678
+ ];
666
679
 
667
680
  // Skip all auth checks for Edge Functions - they cannot use React hooks
668
681
  if (isEdgeFunction) {
@@ -684,14 +697,6 @@ function scanFile(filePath, manifest) {
684
697
  { pattern: /\w+\.auth\.getSession\s*\(/g, method: 'getSession', specific: false }
685
698
  ];
686
699
 
687
- // Other auth patterns
688
- const otherAuthPatterns = [
689
- { pattern: /\.auth\.signIn\s*\(/g, method: 'signIn' },
690
- { pattern: /\.auth\.signUp\s*\(/g, method: 'signUp' },
691
- { pattern: /\.auth\.signOut\s*\(/g, method: 'signOut' },
692
- { pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
693
- ];
694
-
695
700
  // Check if file actually uses useUnifiedAuth hook (not just imports it)
696
701
  const usesUnifiedAuthHook = /useUnifiedAuth\s*\(/.test(content);
697
702
  const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
@@ -760,7 +765,8 @@ function scanFile(filePath, manifest) {
760
765
  const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
761
766
 
762
767
  // Only skip if clearly in a comment - be conservative
763
- if (!isInLineComment) {
768
+ // Also skip Edge Functions - they run in Deno, not React, so React hooks aren't available
769
+ if (!isInLineComment && !isEdgeFunction) {
764
770
  violations.directSupabaseAuth.push({
765
771
  file: relativePath,
766
772
  line: getLineNumber(content, matchText),
@@ -794,7 +800,8 @@ function scanFile(filePath, manifest) {
794
800
  const lineUpToMatch = content.substring(lineStart, searchIndex);
795
801
  const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
796
802
 
797
- if (!isInLineComment) {
803
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
804
+ if (!isInLineComment && !isEdgeFunction) {
798
805
  // Calculate line number from index
799
806
  const lineNum = content.substring(0, searchIndex).split('\n').length;
800
807
 
@@ -855,7 +862,8 @@ function scanFile(filePath, manifest) {
855
862
  (doubleQuotes % 2 === 1 && beforeMatch.endsWith('"')) ||
856
863
  (backticks % 2 === 1 && beforeMatch.endsWith('`'));
857
864
 
858
- if (!isInLineComment && !isInBlockComment && !isInString && !usesUnifiedAuthHook) {
865
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
866
+ if (!isInLineComment && !isInBlockComment && !isInString && !usesUnifiedAuthHook && !isEdgeFunction) {
859
867
  violations.directSupabaseAuth.push({
860
868
  file: relativePath,
861
869
  line: getLineNumber(content, matchText),
@@ -2500,6 +2508,10 @@ function findSourceFiles(dir, fileList = []) {
2500
2508
  const stat = fs.statSync(fullPath);
2501
2509
 
2502
2510
  if (stat.isDirectory()) {
2511
+ // Skip Edge Functions directory - they run in Deno, not React, so React hooks aren't available
2512
+ if (item === 'functions' && dir.includes('supabase')) {
2513
+ return; // Skip supabase/functions directory
2514
+ }
2503
2515
  if (!ignoreDirs.includes(item) && !item.startsWith('.')) {
2504
2516
  findSourceFiles(fullPath, fileList);
2505
2517
  }
@@ -2584,5 +2596,12 @@ if (require.main === module) {
2584
2596
  }
2585
2597
  }
2586
2598
 
2587
- module.exports = { main, scanFile, generateReport };
2599
+ module.exports = {
2600
+ main,
2601
+ scanFile,
2602
+ generateReport,
2603
+ loadManifest,
2604
+ findProjectRoot,
2605
+ findSourceFiles
2606
+ };
2588
2607
 
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Installation Script for pace-core Cursor Rules
5
+ * @package @jmruthers/pace-core
6
+ * @module Scripts/install-cursor-rules
7
+ *
8
+ * Copies cursor rules from pace-core to consuming app's .cursor/rules/ directory.
9
+ * This is an opt-in script - it does NOT run automatically via postinstall.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // ANSI color codes for terminal output
16
+ const colors = {
17
+ reset: '\x1b[0m',
18
+ green: '\x1b[32m',
19
+ yellow: '\x1b[33m',
20
+ blue: '\x1b[34m',
21
+ cyan: '\x1b[36m',
22
+ bold: '\x1b[1m',
23
+ red: '\x1b[31m'
24
+ };
25
+
26
+ // Check for safety guards
27
+ function checkSafetyGuards() {
28
+ // Check environment variable
29
+ if (process.env.PACE_CURSOR_RULES_DISABLED === '1') {
30
+ console.log(`${colors.yellow}Skipping cursor rules installation: PACE_CURSOR_RULES_DISABLED=1${colors.reset}`);
31
+ process.exit(0);
32
+ }
33
+
34
+ // Check for .git folder (safety guard for CI)
35
+ const cwd = process.cwd();
36
+ if (!fs.existsSync(path.join(cwd, '.git'))) {
37
+ console.log(`${colors.yellow}Skipping cursor rules installation: No .git folder found${colors.reset}`);
38
+ console.log(`${colors.yellow}This is a safety guard. If you want to install anyway, run with --force${colors.reset}`);
39
+ if (!process.argv.includes('--force')) {
40
+ process.exit(0);
41
+ }
42
+ }
43
+ }
44
+
45
+ // Get pace-core package path
46
+ function getPaceCorePath() {
47
+ // Try to find pace-core in node_modules
48
+ const cwd = process.cwd();
49
+ const paceCorePath = path.join(cwd, 'node_modules', '@jmruthers', 'pace-core');
50
+
51
+ if (fs.existsSync(paceCorePath)) {
52
+ return paceCorePath;
53
+ }
54
+
55
+ // If not found, try relative to script location (for development)
56
+ const scriptDir = __dirname;
57
+ const relativePath = path.join(scriptDir, '..');
58
+ if (fs.existsSync(path.join(relativePath, 'cursor-rules'))) {
59
+ return relativePath;
60
+ }
61
+
62
+ console.error(`${colors.red}Error: Could not find @jmruthers/pace-core package${colors.reset}`);
63
+ console.error(`${colors.red}Make sure pace-core is installed: npm install @jmruthers/pace-core${colors.reset}`);
64
+ process.exit(1);
65
+ }
66
+
67
+ // Get cursor rules source directory
68
+ function getCursorRulesSource(paceCorePath) {
69
+ const rulesPath = path.join(paceCorePath, 'cursor-rules');
70
+ if (!fs.existsSync(rulesPath)) {
71
+ console.error(`${colors.red}Error: cursor-rules directory not found in pace-core${colors.reset}`);
72
+ process.exit(1);
73
+ }
74
+ return rulesPath;
75
+ }
76
+
77
+ // Get target directory (.cursor/rules in app root)
78
+ function getCursorRulesTarget() {
79
+ const cwd = process.cwd();
80
+ const targetDir = path.join(cwd, '.cursor', 'rules');
81
+ return targetDir;
82
+ }
83
+
84
+ // Read rule file and extract version metadata
85
+ function getRuleVersion(filePath) {
86
+ try {
87
+ const content = fs.readFileSync(filePath, 'utf8');
88
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
89
+ if (frontmatterMatch) {
90
+ const frontmatter = frontmatterMatch[1];
91
+ const versionMatch = frontmatter.match(/paceCoreVersion:\s*["']?([^"'\n]+)["']?/);
92
+ const rulesVersionMatch = frontmatter.match(/rulesVersion:\s*["']?([^"'\n]+)["']?/);
93
+ return {
94
+ paceCoreVersion: versionMatch ? versionMatch[1] : null,
95
+ rulesVersion: rulesVersionMatch ? rulesVersionMatch[1] : null
96
+ };
97
+ }
98
+ } catch (error) {
99
+ // Ignore errors
100
+ }
101
+ return { paceCoreVersion: null, rulesVersion: null };
102
+ }
103
+
104
+ // Check if file needs updating
105
+ function needsUpdate(sourcePath, targetPath, force) {
106
+ if (force) {
107
+ return true;
108
+ }
109
+
110
+ if (!fs.existsSync(targetPath)) {
111
+ return true;
112
+ }
113
+
114
+ // Compare versions first (fast check)
115
+ const sourceVersion = getRuleVersion(sourcePath);
116
+ const targetVersion = getRuleVersion(targetPath);
117
+
118
+ // If versions differ, needs update
119
+ if (sourceVersion.paceCoreVersion !== targetVersion.paceCoreVersion ||
120
+ sourceVersion.rulesVersion !== targetVersion.rulesVersion) {
121
+ return true;
122
+ }
123
+
124
+ // If versions match but content differs, also needs update
125
+ // (handles cases where version metadata wasn't updated)
126
+ try {
127
+ const sourceContent = fs.readFileSync(sourcePath, 'utf8');
128
+ const targetContent = fs.readFileSync(targetPath, 'utf8');
129
+ if (sourceContent !== targetContent) {
130
+ return true;
131
+ }
132
+ } catch (error) {
133
+ // If we can't read files, assume update needed
134
+ return true;
135
+ }
136
+
137
+ return false;
138
+ }
139
+
140
+ // Install cursor rules
141
+ function installCursorRules(force = false) {
142
+ checkSafetyGuards();
143
+
144
+ const paceCorePath = getPaceCorePath();
145
+ const sourceDir = getCursorRulesSource(paceCorePath);
146
+ const targetDir = getCursorRulesTarget();
147
+
148
+ // Create target directory if it doesn't exist
149
+ if (!fs.existsSync(targetDir)) {
150
+ fs.mkdirSync(targetDir, { recursive: true });
151
+ console.log(`${colors.green}Created .cursor/rules/ directory${colors.reset}`);
152
+ }
153
+
154
+ // Read all .mdc files from source
155
+ const files = fs.readdirSync(sourceDir)
156
+ .filter(file => file.endsWith('.mdc'))
157
+ .sort(); // Sort for consistent output
158
+
159
+ if (files.length === 0) {
160
+ console.log(`${colors.yellow}No cursor rules found in pace-core${colors.reset}`);
161
+ return;
162
+ }
163
+
164
+ console.log(`${colors.cyan}Installing cursor rules from pace-core...${colors.reset}\n`);
165
+
166
+ let installed = 0;
167
+ let skipped = 0;
168
+ let updated = 0;
169
+
170
+ files.forEach(file => {
171
+ const sourcePath = path.join(sourceDir, file);
172
+ const targetPath = path.join(targetDir, file);
173
+
174
+ // Check if this is a pace-core rule (00-09) or app rule (50+)
175
+ const ruleNumber = parseInt(file.match(/^(\d+)-/)?.[1] || '99');
176
+ const isPaceCoreRule = ruleNumber >= 0 && ruleNumber <= 9;
177
+
178
+ if (fs.existsSync(targetPath)) {
179
+ if (isPaceCoreRule) {
180
+ // pace-core rules should be automatically updated when they change
181
+ if (needsUpdate(sourcePath, targetPath, force)) {
182
+ // Automatically update pace-core rules when versions differ or content changed
183
+ fs.copyFileSync(sourcePath, targetPath);
184
+ console.log(`${colors.green}✓${colors.reset} Updated ${file}${force ? ' (forced)' : ''}`);
185
+ updated++;
186
+ } else {
187
+ console.log(`${colors.blue}○${colors.reset} ${file} is up to date`);
188
+ skipped++;
189
+ }
190
+ } else {
191
+ // App rules (50+) are never overwritten
192
+ console.log(`${colors.blue}○${colors.reset} ${file} is a custom rule, skipping`);
193
+ skipped++;
194
+ }
195
+ } else {
196
+ // File doesn't exist, install it
197
+ fs.copyFileSync(sourcePath, targetPath);
198
+ console.log(`${colors.green}✓${colors.reset} Installed ${file}`);
199
+ installed++;
200
+ }
201
+ });
202
+
203
+ console.log(`\n${colors.bold}Installation Summary:${colors.reset}`);
204
+ console.log(` ${colors.green}Installed:${colors.reset} ${installed}`);
205
+ console.log(` ${colors.yellow}Updated:${colors.reset} ${updated}`);
206
+ console.log(` ${colors.blue}Skipped:${colors.reset} ${skipped}`);
207
+ console.log(`\n${colors.cyan}Cursor rules are now available in .cursor/rules/${colors.reset}`);
208
+ console.log(`${colors.cyan}Restart Cursor to load the new rules.${colors.reset}`);
209
+ }
210
+
211
+ // Main execution
212
+ function main() {
213
+ const force = process.argv.includes('--force');
214
+
215
+ if (force) {
216
+ console.log(`${colors.yellow}Warning: --force flag is set. This will overwrite existing pace-core rules.${colors.reset}\n`);
217
+ }
218
+
219
+ try {
220
+ installCursorRules(force);
221
+ } catch (error) {
222
+ console.error(`${colors.red}Error installing cursor rules:${colors.reset}`);
223
+ console.error(error.message);
224
+ if (error.stack) {
225
+ console.error(error.stack);
226
+ }
227
+ process.exit(1);
228
+ }
229
+ }
230
+
231
+ // Run if called directly
232
+ if (require.main === module) {
233
+ main();
234
+ }
235
+
236
+ module.exports = { installCursorRules, getCursorRulesTarget };
@@ -246,7 +246,7 @@ interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
246
246
  }
247
247
 
248
248
  export function renderWithProviders(
249
- ui: React.ReactElement,
249
+ ui: React.ReactElement<any>,
250
250
  options: CustomRenderOptions = {}
251
251
  ) {
252
252
  const {
@@ -48,7 +48,7 @@ interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
48
48
  }
49
49
 
50
50
  export function renderWithProviders(
51
- ui: ReactElement,
51
+ ui: ReactElement<any>,
52
52
  options: CustomRenderOptions = {}
53
53
  ): ReturnType<typeof render> {
54
54
  const {
@@ -251,8 +251,7 @@ function getBadgeClasses(variant: BadgeVariant = 'solid-main-normal'): string {
251
251
  * <Badge variant="soft-acc-strong">Featured</Badge>
252
252
  * ```
253
253
  */
254
- const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
255
- ({ className, variant = 'solid-main-normal', ...props }, ref) => {
254
+ function Badge({ className, variant = 'solid-main-normal', ref, ...props }: BadgeProps & { ref?: React.Ref<HTMLSpanElement> }) {
256
255
  const isSoftVariant = variant.startsWith('soft-');
257
256
 
258
257
  if (isSoftVariant) {
@@ -291,8 +290,7 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
291
290
  {...props}
292
291
  />
293
292
  );
294
- }
295
- );
293
+ }
296
294
 
297
295
  Badge.displayName = 'Badge';
298
296
 
@@ -198,11 +198,12 @@ export function ButtonGroup({
198
198
  // Type assertion needed because React.ComponentType doesn't guarantee displayName
199
199
  const componentType = child.type as React.ComponentType<unknown> & { displayName?: string };
200
200
  if (componentType.displayName === 'Button') {
201
+ const childProps = child.props as { variant?: string; size?: string; [key: string]: unknown };
201
202
  return React.cloneElement(child, {
202
- variant: child.props.variant || variant,
203
- size: child.props.size || size,
204
- ...child.props
205
- });
203
+ variant: childProps.variant || variant,
204
+ size: childProps.size || size,
205
+ ...childProps
206
+ } as typeof childProps);
206
207
  }
207
208
  }
208
209
  return child;
@@ -350,7 +350,7 @@ const Calendar = React.forwardRef<HTMLTableElement, CalendarProps>(
350
350
  goToMonth(nextMonth);
351
351
  };
352
352
 
353
- const monthGridElement = monthGridChild as React.ReactElement & {
353
+ const monthGridElement = monthGridChild as React.ReactElement<any> & {
354
354
  ref?: React.Ref<HTMLTableElement | null>;
355
355
  };
356
356
  const mergedRef =