@jmruthers/pace-core 0.6.5 → 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 (473) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/audit-tool/00-dependencies.cjs +394 -0
  4. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  5. package/audit-tool/audits/02-project-structure.cjs +255 -0
  6. package/audit-tool/audits/03-architecture.cjs +196 -0
  7. package/audit-tool/audits/04-code-quality.cjs +149 -0
  8. package/audit-tool/audits/05-styling.cjs +224 -0
  9. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  10. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  11. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  12. package/audit-tool/audits/09-operations.cjs +208 -0
  13. package/audit-tool/index.cjs +291 -0
  14. package/audit-tool/utils/code-utils.cjs +218 -0
  15. package/audit-tool/utils/file-utils.cjs +230 -0
  16. package/audit-tool/utils/report-utils.cjs +241 -0
  17. package/core-usage-manifest.json +93 -0
  18. package/cursor-rules/00-standards-overview.mdc +156 -0
  19. package/cursor-rules/01-pace-core-compliance.mdc +586 -0
  20. package/cursor-rules/02-project-structure.mdc +42 -4
  21. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
  22. package/cursor-rules/04-code-quality.mdc +419 -0
  23. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
  24. package/cursor-rules/06-security-rbac.mdc +518 -0
  25. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  26. package/cursor-rules/08-testing-documentation.mdc +324 -0
  27. package/cursor-rules/09-operations.mdc +365 -0
  28. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  29. package/dist/DataTable-7PMH7XN7.js +15 -0
  30. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  31. package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
  32. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  33. package/dist/api-Y4MQWOFW.js +4 -0
  34. package/dist/audit-MYQXYZFU.js +3 -0
  35. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  36. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  37. package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
  38. package/dist/chunk-6F3IILHI.js +62 -0
  39. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  40. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  41. package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
  42. package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
  43. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  44. package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
  45. package/dist/chunk-C7NSAPTL.js +1 -0
  46. package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
  47. package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
  48. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  49. package/dist/chunk-GHYHJTYV.js +994 -0
  50. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  51. package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
  52. package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
  53. package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
  54. package/dist/chunk-MBADTM7L.js +64 -0
  55. package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
  56. package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
  57. package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
  58. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  59. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  60. package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
  61. package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
  62. package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
  63. package/dist/components.d.ts +7 -5
  64. package/dist/components.js +46 -257
  65. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  66. package/dist/eslint-rules/index.cjs +35 -0
  67. package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
  68. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  69. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  70. package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
  71. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  72. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  73. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  74. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  75. package/dist/hooks.d.ts +6 -6
  76. package/dist/hooks.js +62 -172
  77. package/dist/icons/index.d.ts +1 -0
  78. package/dist/icons/index.js +1 -0
  79. package/dist/index.d.ts +12 -11
  80. package/dist/index.js +67 -660
  81. package/dist/providers.d.ts +2 -2
  82. package/dist/providers.js +8 -35
  83. package/dist/rbac/eslint-rules.d.ts +46 -44
  84. package/dist/rbac/eslint-rules.js +7 -4
  85. package/dist/rbac/index.d.ts +109 -586
  86. package/dist/rbac/index.js +14 -207
  87. package/dist/styles/index.js +2 -12
  88. package/dist/theming/runtime.d.ts +14 -1
  89. package/dist/theming/runtime.js +3 -19
  90. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  91. package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
  92. package/dist/types-t9H8qKRw.d.ts +55 -0
  93. package/dist/types.d.ts +1 -1
  94. package/dist/types.js +7 -94
  95. package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
  96. package/dist/utils.d.ts +24 -117
  97. package/dist/utils.js +54 -392
  98. package/docs/README.md +17 -7
  99. package/docs/api/README.md +4 -402
  100. package/docs/api/modules.md +301 -871
  101. package/docs/api-reference/components.md +21 -21
  102. package/docs/api-reference/deprecated.md +31 -6
  103. package/docs/api-reference/hooks.md +80 -80
  104. package/docs/api-reference/rpc-functions.md +78 -3
  105. package/docs/api-reference/types.md +1 -1
  106. package/docs/api-reference/utilities.md +1 -1
  107. package/docs/architecture/README.md +1 -1
  108. package/docs/core-concepts/events.md +3 -3
  109. package/docs/core-concepts/organisations.md +6 -6
  110. package/docs/core-concepts/permissions.md +6 -6
  111. package/docs/documentation-index.md +12 -18
  112. package/docs/getting-started/cursor-rules.md +3 -23
  113. package/docs/getting-started/dependencies.md +650 -0
  114. package/docs/getting-started/documentation-index.md +1 -1
  115. package/docs/getting-started/examples/README.md +4 -4
  116. package/docs/getting-started/examples/full-featured-app.md +1 -1
  117. package/docs/getting-started/faq.md +2 -2
  118. package/docs/getting-started/installation-guide.md +20 -7
  119. package/docs/getting-started/quick-reference.md +4 -4
  120. package/docs/getting-started/quick-start.md +23 -12
  121. package/docs/implementation-guides/authentication.md +15 -15
  122. package/docs/implementation-guides/component-styling.md +1 -1
  123. package/docs/implementation-guides/data-tables.md +126 -33
  124. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  125. package/docs/implementation-guides/dynamic-colors.md +3 -3
  126. package/docs/implementation-guides/file-upload-storage.md +2 -2
  127. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  128. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  129. package/docs/implementation-guides/large-datasets.md +3 -2
  130. package/docs/implementation-guides/organisation-security.md +2 -2
  131. package/docs/implementation-guides/performance.md +2 -2
  132. package/docs/implementation-guides/permission-enforcement.md +5 -1
  133. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  134. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  135. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  136. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  137. package/docs/rbac/README.md +17 -8
  138. package/docs/rbac/advanced-patterns.md +6 -6
  139. package/docs/rbac/api-reference.md +20 -20
  140. package/docs/rbac/edge-functions-guide.md +376 -0
  141. package/docs/rbac/event-based-apps.md +3 -3
  142. package/docs/rbac/examples.md +41 -41
  143. package/docs/rbac/getting-started.md +37 -37
  144. package/docs/rbac/performance.md +1 -1
  145. package/docs/rbac/quick-start.md +52 -52
  146. package/docs/rbac/secure-client-protection.md +1 -35
  147. package/docs/rbac/troubleshooting.md +1 -1
  148. package/docs/security/README.md +5 -5
  149. package/docs/standards/0-standards-overview.md +220 -0
  150. package/docs/standards/1-pace-core-compliance-standards.md +986 -0
  151. package/docs/standards/2-project-structure-standards.md +949 -0
  152. package/docs/standards/3-architecture-standards.md +606 -0
  153. package/docs/standards/4-code-quality-standards.md +728 -0
  154. package/docs/standards/5-styling-standards.md +348 -0
  155. package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
  156. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  157. package/docs/standards/8-testing-documentation-standards.md +401 -0
  158. package/docs/standards/9-operations-standards.md +1102 -0
  159. package/docs/standards/README.md +185 -57
  160. package/docs/troubleshooting/README.md +4 -4
  161. package/docs/troubleshooting/common-issues.md +2 -2
  162. package/docs/troubleshooting/debugging.md +9 -9
  163. package/docs/troubleshooting/migration.md +4 -4
  164. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  165. package/eslint-config-pace-core.cjs +33 -6
  166. package/package.json +35 -23
  167. package/scripts/install-cursor-rules.cjs +25 -6
  168. package/scripts/install-eslint-config.cjs +284 -0
  169. package/src/__tests__/fixtures/supabase.ts +1 -1
  170. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
  171. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  172. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  173. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  174. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
  175. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  176. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  177. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  178. package/src/__tests__/public-recipe-view.test.ts +38 -9
  179. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  180. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  181. package/src/__tests__/templates/component.test.template.tsx +18 -15
  182. package/src/components/Button/Button.tsx +5 -1
  183. package/src/components/Calendar/Calendar.tsx +201 -47
  184. package/src/components/ContextSelector/ContextSelector.tsx +106 -119
  185. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  186. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  187. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  188. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  189. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  190. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  191. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  192. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  193. package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
  194. package/src/components/DataTable/components/EditFields.tsx +23 -3
  195. package/src/components/DataTable/components/EditableRow.tsx +12 -9
  196. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  197. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  198. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  199. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  200. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  201. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  202. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  203. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  204. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  205. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  206. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  207. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  208. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  209. package/src/components/DataTable/components/index.ts +2 -1
  210. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  211. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  212. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  213. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  214. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  215. package/src/components/DataTable/types.ts +5 -18
  216. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  217. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  218. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  219. package/src/components/DateTimeField/DateTimeField.tsx +10 -9
  220. package/src/components/Dialog/Dialog.test.tsx +128 -104
  221. package/src/components/Dialog/Dialog.tsx +742 -24
  222. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  223. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
  224. package/src/components/FileDisplay/FileDisplay.tsx +23 -17
  225. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  226. package/src/components/FileUpload/FileUpload.tsx +112 -130
  227. package/src/components/Form/Form.test.tsx +6 -8
  228. package/src/components/Form/Form.tsx +365 -4
  229. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  230. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  231. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  232. package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
  233. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  234. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  235. package/src/components/Progress/Progress.tsx +2 -4
  236. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  237. package/src/components/Select/Select.tsx +109 -98
  238. package/src/components/Select/types.ts +4 -1
  239. package/src/components/UserMenu/UserMenu.tsx +9 -6
  240. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  241. package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
  242. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  243. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  244. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  245. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  246. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  247. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  248. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  249. package/src/hooks/public/usePublicEvent.ts +67 -195
  250. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  251. package/src/hooks/public/usePublicEventLogo.ts +24 -14
  252. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  253. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  254. package/src/hooks/useAppConfig.ts +28 -26
  255. package/src/hooks/useEventTheme.test.ts +217 -239
  256. package/src/hooks/useEventTheme.ts +16 -28
  257. package/src/hooks/useFileDisplay.ts +2 -2
  258. package/src/hooks/useOrganisationPermissions.ts +5 -7
  259. package/src/hooks/useQueryCache.ts +0 -1
  260. package/src/hooks/useSessionDraft.ts +380 -0
  261. package/src/hooks/useSessionRestoration.ts +3 -1
  262. package/src/icons/index.ts +27 -0
  263. package/src/index.ts +5 -0
  264. package/src/providers/OrganisationProvider.tsx +23 -14
  265. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  266. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  267. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  268. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  269. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  270. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  271. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  272. package/src/providers/services/EventServiceProvider.tsx +1 -24
  273. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  274. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  275. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
  276. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  277. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  278. package/src/rbac/adapters.tsx +7 -295
  279. package/src/rbac/api.test.ts +44 -56
  280. package/src/rbac/api.ts +10 -17
  281. package/src/rbac/cache-invalidation.ts +0 -1
  282. package/src/rbac/compliance/index.ts +10 -0
  283. package/src/rbac/compliance/pattern-detector.ts +553 -0
  284. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  285. package/src/rbac/components/AccessDenied.tsx +150 -0
  286. package/src/rbac/components/NavigationGuard.tsx +12 -20
  287. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  288. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  289. package/src/rbac/components/index.ts +3 -41
  290. package/src/rbac/eslint-rules.js +1 -1
  291. package/src/rbac/hooks/index.ts +0 -3
  292. package/src/rbac/hooks/permissions/index.ts +0 -3
  293. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  294. package/src/rbac/hooks/usePermissions.ts +0 -3
  295. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  296. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  297. package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
  298. package/src/rbac/hooks/useResourcePermissions.ts +139 -48
  299. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  300. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  301. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  302. package/src/rbac/index.ts +7 -9
  303. package/src/rbac/utils/contextValidator.ts +9 -7
  304. package/src/services/AuthService.ts +130 -18
  305. package/src/services/EventService.ts +4 -97
  306. package/src/services/InactivityService.ts +16 -0
  307. package/src/services/OrganisationService.ts +7 -44
  308. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  309. package/src/services/base/BaseService.ts +0 -3
  310. package/src/styles/core.css +7 -0
  311. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  312. package/src/theming/parseEventColours.ts +22 -10
  313. package/src/types/database.generated.ts +4733 -3809
  314. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  315. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  316. package/src/utils/context/organisationContext.test.ts +13 -28
  317. package/src/utils/context/organisationContext.ts +21 -52
  318. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  319. package/src/utils/file-reference/index.ts +39 -15
  320. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  321. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  322. package/src/utils/index.ts +4 -1
  323. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  324. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  325. package/src/utils/persistence/keyDerivation.ts +304 -0
  326. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  327. package/src/utils/security/secureStorage.ts +5 -5
  328. package/src/utils/storage/README.md +1 -1
  329. package/src/utils/storage/helpers.ts +3 -3
  330. package/src/utils/supabase/createBaseClient.ts +147 -0
  331. package/src/utils/timezone/timezone.test.ts +1 -2
  332. package/src/utils/timezone/timezone.ts +1 -1
  333. package/src/utils/validation/csrf.ts +4 -4
  334. package/cursor-rules/00-pace-core-compliance.mdc +0 -331
  335. package/cursor-rules/01-standards-compliance.mdc +0 -244
  336. package/cursor-rules/04-testing-standards.mdc +0 -268
  337. package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
  338. package/cursor-rules/06-code-quality.mdc +0 -309
  339. package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
  340. package/cursor-rules/CHANGELOG.md +0 -119
  341. package/cursor-rules/README.md +0 -192
  342. package/dist/DataTable-AOVNCPTX.js +0 -175
  343. package/dist/DataTable-AOVNCPTX.js.map +0 -1
  344. package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
  345. package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
  346. package/dist/api-O6HTBX5Y.js +0 -52
  347. package/dist/api-O6HTBX5Y.js.map +0 -1
  348. package/dist/audit-V53FV5AG.js +0 -17
  349. package/dist/audit-V53FV5AG.js.map +0 -1
  350. package/dist/chunk-5DRSZLL2.js.map +0 -1
  351. package/dist/chunk-63FOKYGO.js.map +0 -1
  352. package/dist/chunk-6COVEUS7.js.map +0 -1
  353. package/dist/chunk-AFVQODI2.js +0 -263
  354. package/dist/chunk-AFVQODI2.js.map +0 -1
  355. package/dist/chunk-DGUM43GV.js.map +0 -1
  356. package/dist/chunk-E66EQZE6.js.map +0 -1
  357. package/dist/chunk-EFN2EIMK.js.map +0 -1
  358. package/dist/chunk-FFQEQTNW.js.map +0 -1
  359. package/dist/chunk-FMUCXFII.js.map +0 -1
  360. package/dist/chunk-G37KK66H.js.map +0 -1
  361. package/dist/chunk-G7QEZTYQ.js +0 -2053
  362. package/dist/chunk-G7QEZTYQ.js.map +0 -1
  363. package/dist/chunk-HU2C6SSC.js.map +0 -1
  364. package/dist/chunk-IHB5DR3H.js.map +0 -1
  365. package/dist/chunk-IVOFDYWT.js.map +0 -1
  366. package/dist/chunk-J36DSWQK.js.map +0 -1
  367. package/dist/chunk-JGRYX5UX.js.map +0 -1
  368. package/dist/chunk-KQCRWDSA.js +0 -1
  369. package/dist/chunk-KQCRWDSA.js.map +0 -1
  370. package/dist/chunk-L4OXEN46.js.map +0 -1
  371. package/dist/chunk-LMC26NLJ.js +0 -84
  372. package/dist/chunk-LMC26NLJ.js.map +0 -1
  373. package/dist/chunk-M43Y4SSO.js.map +0 -1
  374. package/dist/chunk-M7MPQISP.js.map +0 -1
  375. package/dist/chunk-NTM7ZSB6.js.map +0 -1
  376. package/dist/chunk-PWLANIRT.js.map +0 -1
  377. package/dist/chunk-QXHPKYJV.js.map +0 -1
  378. package/dist/chunk-RGAWHO7N.js.map +0 -1
  379. package/dist/chunk-UPPMRMYG.js.map +0 -1
  380. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  381. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  382. package/dist/components.js.map +0 -1
  383. package/dist/contextValidator-5OGXSPKS.js +0 -9
  384. package/dist/contextValidator-5OGXSPKS.js.map +0 -1
  385. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  386. package/dist/hooks.js.map +0 -1
  387. package/dist/index.js.map +0 -1
  388. package/dist/providers.js.map +0 -1
  389. package/dist/rbac/eslint-rules.js.map +0 -1
  390. package/dist/rbac/index.js.map +0 -1
  391. package/dist/styles/index.js.map +0 -1
  392. package/dist/theming/runtime.js.map +0 -1
  393. package/dist/types.js.map +0 -1
  394. package/dist/utils.js.map +0 -1
  395. package/docs/best-practices/README.md +0 -472
  396. package/docs/best-practices/accessibility.md +0 -601
  397. package/docs/best-practices/common-patterns.md +0 -516
  398. package/docs/best-practices/deployment.md +0 -1103
  399. package/docs/best-practices/performance.md +0 -1328
  400. package/docs/best-practices/security.md +0 -940
  401. package/docs/best-practices/testing.md +0 -1034
  402. package/docs/rbac/compliance/compliance-guide.md +0 -544
  403. package/docs/standards/01-architecture-standard.md +0 -44
  404. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  405. package/docs/standards/03-component-standard.md +0 -32
  406. package/docs/standards/04-code-style-standard.md +0 -32
  407. package/docs/standards/05-security-standard.md +0 -44
  408. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  409. package/docs/standards/pace-core-compliance.md +0 -432
  410. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  411. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  412. package/scripts/audit/core/checks/bundle.cjs +0 -142
  413. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  414. package/scripts/audit/core/checks/config.cjs +0 -54
  415. package/scripts/audit/core/checks/coverage.cjs +0 -84
  416. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  417. package/scripts/audit/core/checks/documentation.cjs +0 -268
  418. package/scripts/audit/core/checks/environment.cjs +0 -116
  419. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  420. package/scripts/audit/core/checks/forms.cjs +0 -172
  421. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  422. package/scripts/audit/core/checks/hooks.cjs +0 -334
  423. package/scripts/audit/core/checks/imports.cjs +0 -244
  424. package/scripts/audit/core/checks/performance.cjs +0 -325
  425. package/scripts/audit/core/checks/routes.cjs +0 -117
  426. package/scripts/audit/core/checks/state.cjs +0 -130
  427. package/scripts/audit/core/checks/structure.cjs +0 -65
  428. package/scripts/audit/core/checks/style.cjs +0 -584
  429. package/scripts/audit/core/checks/testing.cjs +0 -122
  430. package/scripts/audit/core/checks/typescript.cjs +0 -61
  431. package/scripts/audit/core/scanner.cjs +0 -199
  432. package/scripts/audit/core/utils.cjs +0 -137
  433. package/scripts/audit/index.cjs +0 -223
  434. package/scripts/audit/reporters/console.cjs +0 -151
  435. package/scripts/audit/reporters/json.cjs +0 -54
  436. package/scripts/audit/reporters/markdown.cjs +0 -124
  437. package/scripts/audit-consuming-app.cjs +0 -86
  438. package/src/components/DataTable/components/DataTableBody.tsx +0 -454
  439. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  440. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  441. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  442. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  443. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  444. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  445. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  446. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  447. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  448. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  449. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  450. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  451. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  452. package/src/components/DataTable/utils/debugTools.ts +0 -514
  453. package/src/eslint-rules/pace-core-compliance.js +0 -638
  454. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  455. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  456. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  457. package/src/rbac/components/NavigationProvider.tsx +0 -345
  458. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  459. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  460. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  461. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  462. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  463. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  464. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  465. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  466. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  467. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  468. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  469. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  470. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  471. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  472. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  473. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { LoadingSpinner } from '../../LoadingSpinner/LoadingSpinner';
2
3
 
3
4
  /**
4
5
  * Loading state component for DataTable.
@@ -8,11 +9,9 @@ import React from 'react';
8
9
  */
9
10
  export function LoadingState() {
10
11
  return (
11
- <div className="p-8 text-center">
12
- <div className="flex items-center justify-center space-x-2">
13
- <div className="animate-spin rounded-full size-6 border-b-2 border-primary"></div>
14
- <span aria-live="polite" className="text-muted-foreground">Loading...</span>
15
- </div>
16
- </div>
12
+ <p className="grid place-items-center text-center p-8">
13
+ <LoadingSpinner />
14
+ <strong>Loading...</strong>
15
+ </p>
17
16
  );
18
17
  }
@@ -381,6 +381,18 @@ const areRowPropsEqual = (prevProps: RowProps, nextProps: RowProps): boolean =>
381
381
  return false;
382
382
  }
383
383
 
384
+ // CRITICAL FIX: Check if editingData has changed
385
+ // This ensures EditableRow re-renders when editingData updates (e.g., when dropdown value changes)
386
+ // For React 19: Manual memoization is still beneficial for table rows to prevent unnecessary re-renders
387
+ // of hundreds/thousands of rows when only one row's editingData changes
388
+ if (prevProps.isEditing && nextProps.isEditing) {
389
+ // Simple reference equality check - if editingData object reference changed, it's different
390
+ // This works because setEditingRow creates a new object, so reference equality is sufficient
391
+ if (prevProps.editingData !== nextProps.editingData) {
392
+ return false;
393
+ }
394
+ }
395
+
384
396
  return true;
385
397
  };
386
398
 
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @file Sort Indicator Component
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Components
5
+ * @since 0.4.0
6
+ *
7
+ * Shared component for displaying column sort indicators.
8
+ * Provides consistent sorting chevron icons across all DataTable components.
9
+ */
10
+
11
+ import React from 'react';
12
+ import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';
13
+ import { cn } from '../../../utils/core/cn';
14
+
15
+ /**
16
+ * Props for the SortIndicator component
17
+ */
18
+ export interface SortIndicatorProps {
19
+ /** Current sort state: 'asc' for ascending, 'desc' for descending, false for unsorted */
20
+ sortState: 'asc' | 'desc' | false;
21
+ /** Optional className for styling */
22
+ className?: string;
23
+ }
24
+
25
+ /**
26
+ * Sort indicator component that displays the appropriate chevron icon
27
+ * based on the current sort state.
28
+ *
29
+ * @param props - Sort indicator configuration
30
+ * @returns The rendered sort indicator icon
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * <SortIndicator sortState={column.getIsSorted()} />
35
+ * ```
36
+ */
37
+ export function SortIndicator({ sortState, className }: SortIndicatorProps) {
38
+ return (
39
+ <>
40
+ {sortState === 'asc' ? (
41
+ <ChevronUp className={cn('size-4', className)} />
42
+ ) : sortState === 'desc' ? (
43
+ <ChevronDown className={cn('size-4', className)} />
44
+ ) : (
45
+ <ChevronsUpDown className={cn('size-4', className)} />
46
+ )}
47
+ </>
48
+ );
49
+ }
50
+
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Summary
4
4
 
5
- The DataTable subcomponents (FilterRow, EditableRow, ColumnFilter, GroupHeader, ViewRowModal, etc.) are **intentionally not tested in isolation** because:
5
+ The DataTable subcomponents (FilterRow, EditableRow, ColumnFilter, etc.) are **intentionally not tested in isolation** because:
6
6
 
7
7
  1. They are tightly integrated with TanStack React Table
8
8
  2. They are extensively tested through DataTable integration tests
@@ -35,9 +35,9 @@ The following DataTable integration tests provide comprehensive coverage:
35
35
  | **ColumnFilter** | ✅ | Integration tests validate filter input behavior |
36
36
  | **FilterRow** | ✅ | Integration tests validate filtering across columns |
37
37
  | **EditableRow** | ✅ | Integration tests validate editing workflows |
38
- | **GroupHeader** | ✅ | Integration tests validate grouping functionality |
39
- | **ViewRowModal** | ✅ | Integration tests validate modal display |
40
- | **DraggableColumnHeader** | ✅ | Integration tests validate column reordering |
38
+ | **Grouped rows** | ✅ | Integration tests validate grouping functionality (handled inline by RowComponent) |
39
+ | **Modal display** | ✅ | Integration tests validate modal functionality |
40
+ | **Column reordering** | ✅ | Integration tests validate column order persistence |
41
41
  | **ActionButtons** | ✅ | Integration tests validate action buttons |
42
42
 
43
43
  ## Why This Approach Works
@@ -57,8 +57,8 @@ vi.mock('../../Select/Select', async () => {
57
57
  {children}
58
58
  </button>
59
59
  ),
60
- SelectValue: ({ placeholder }: any) => (
61
- <span data-testid="select-value">{placeholder}</span>
60
+ SelectValue: ({ children }: any) => (
61
+ <span data-testid="select-value">{children}</span>
62
62
  ),
63
63
  SelectContent: ({ children }: any) => (
64
64
  <div data-testid="select-content">{children}</div>
@@ -449,79 +449,6 @@ describe('[component] ColumnFilter', () => {
449
449
  });
450
450
  });
451
451
 
452
- describe('Clear Filter Button', () => {
453
- it('shows clear button when filter has value', () => {
454
- const column = createMockColumn({
455
- getFilterValue: vi.fn(() => 'test'),
456
- });
457
-
458
- render(<ColumnFilter column={column} />);
459
-
460
- const clearButton = screen.getByRole('button');
461
- expect(clearButton).toBeInTheDocument();
462
- expect(screen.getByTestId('x-icon')).toBeInTheDocument();
463
- });
464
-
465
- it('hides clear button when filter has no value', () => {
466
- const column = createMockColumn({
467
- getFilterValue: vi.fn(() => undefined),
468
- });
469
-
470
- render(<ColumnFilter column={column} />);
471
-
472
- expect(screen.queryByTestId('x-icon')).not.toBeInTheDocument();
473
- });
474
-
475
- it('hides clear button when filter value is empty string', () => {
476
- const column = createMockColumn({
477
- getFilterValue: vi.fn(() => ''),
478
- });
479
-
480
- render(<ColumnFilter column={column} />);
481
-
482
- expect(screen.queryByTestId('x-icon')).not.toBeInTheDocument();
483
- });
484
-
485
- it('clears filter when clear button is clicked', async () => {
486
- const user = userEvent.setup();
487
- const setFilterValue = vi.fn();
488
- const column = createMockColumn({
489
- getFilterValue: vi.fn(() => 'test'),
490
- setFilterValue,
491
- });
492
-
493
- render(<ColumnFilter column={column} />);
494
-
495
- const clearButton = screen.getByRole('button');
496
- await user.click(clearButton);
497
-
498
- expect(setFilterValue).toHaveBeenCalledWith(undefined);
499
- });
500
- });
501
-
502
- describe('Filter Indicator', () => {
503
- it('shows filter indicator dot when filter has value', () => {
504
- const column = createMockColumn({
505
- getFilterValue: vi.fn(() => 'test'),
506
- });
507
-
508
- render(<ColumnFilter column={column} />);
509
-
510
- const indicator = document.querySelector('.bg-main-500.rounded-full');
511
- expect(indicator).toBeInTheDocument();
512
- });
513
-
514
- it('hides filter indicator dot when filter has no value', () => {
515
- const column = createMockColumn({
516
- getFilterValue: vi.fn(() => undefined),
517
- });
518
-
519
- render(<ColumnFilter column={column} />);
520
-
521
- const indicator = document.querySelector('.bg-main-500.rounded-full');
522
- expect(indicator).not.toBeInTheDocument();
523
- });
524
- });
525
452
 
526
453
  describe('Edge Cases', () => {
527
454
  it('handles undefined filter value gracefully', () => {
@@ -618,15 +545,29 @@ describe('[component] ColumnFilter', () => {
618
545
  expect(input).toBeInTheDocument();
619
546
  });
620
547
 
621
- it('clear button is accessible', () => {
622
- const column = createMockColumn({
623
- getFilterValue: vi.fn(() => 'test'),
624
- });
548
+ it('renders Filter icon in select filter', () => {
549
+ render(
550
+ <ColumnFilter
551
+ column={mockColumn}
552
+ filterType="select"
553
+ options={[{ value: 'option1', label: 'Option 1' }]}
554
+ />
555
+ );
625
556
 
626
- render(<ColumnFilter column={column} />);
557
+ expect(screen.getByTestId('filter-icon')).toBeInTheDocument();
558
+ });
559
+
560
+ it('renders column name in select filter value', () => {
561
+ render(
562
+ <ColumnFilter
563
+ column={mockColumn}
564
+ filterType="select"
565
+ options={[{ value: 'option1', label: 'Option 1' }]}
566
+ />
567
+ );
627
568
 
628
- const clearButton = screen.getByRole('button');
629
- expect(clearButton).toBeInTheDocument();
569
+ const selectValue = screen.getByTestId('select-value');
570
+ expect(selectValue).toHaveTextContent('test-column...');
630
571
  });
631
572
  });
632
573
 
@@ -89,8 +89,9 @@ describe('[component] DataTableErrorBoundary', () => {
89
89
  </DataTableErrorBoundary>
90
90
  );
91
91
 
92
- // Alert component uses role="alert", not data-testid
93
- expect(screen.getByRole('alert')).toBeInTheDocument();
92
+ // There may be multiple alerts (outer and inner), use getAllByRole
93
+ const alerts = screen.getAllByRole('alert');
94
+ expect(alerts.length).toBeGreaterThan(0);
94
95
  expect(screen.getByText('DataTable Error')).toBeInTheDocument();
95
96
  expect(screen.getByText('Something went wrong')).toBeInTheDocument();
96
97
  });
@@ -127,8 +128,16 @@ describe('[component] DataTableErrorBoundary', () => {
127
128
  </DataTableErrorBoundary>
128
129
  );
129
130
 
131
+ // Check for error details summary
130
132
  const details = screen.getByText('Error Details');
131
133
  expect(details).toBeInTheDocument();
134
+ // Check that error message is displayed in the details/pre element
135
+ // The error message is inside a <pre> tag within <details>
136
+ const preElement = screen.getByText((content, element) => {
137
+ return element?.tagName.toLowerCase() === 'pre' && content.includes('Test error');
138
+ }, { selector: 'pre' });
139
+ expect(preElement).toBeInTheDocument();
140
+ expect(preElement).toHaveTextContent('Test error');
132
141
  });
133
142
 
134
143
  it('hides error details when showErrorDetails is false', () => {
@@ -140,10 +149,11 @@ describe('[component] DataTableErrorBoundary', () => {
140
149
 
141
150
  // Error details section is shown when error.message exists
142
151
  // showErrorDetails only controls stack trace visibility, not the details section
143
- // So we check that the details section exists but stack trace is not shown
144
152
  expect(screen.getByText('Error Details')).toBeInTheDocument();
145
153
  // Stack trace should not be visible when showErrorDetails is false
146
154
  expect(screen.queryByText(/Stack Trace/i)).not.toBeInTheDocument();
155
+ // Error message should still be visible
156
+ expect(screen.getByText('Test error')).toBeInTheDocument();
147
157
  });
148
158
 
149
159
  it('displays stack trace when showErrorDetails is true', () => {
@@ -155,6 +165,8 @@ describe('[component] DataTableErrorBoundary', () => {
155
165
 
156
166
  // Stack trace should be in the details section
157
167
  expect(screen.getByText('Error Details')).toBeInTheDocument();
168
+ // Stack trace text should be present when showErrorDetails is true
169
+ expect(screen.getByText(/Stack Trace/i)).toBeInTheDocument();
158
170
  });
159
171
  });
160
172
 
@@ -237,7 +249,8 @@ describe('[component] DataTableErrorBoundary', () => {
237
249
  </DataTableErrorBoundary>
238
250
  );
239
251
 
240
- expect(screen.getByRole('alert')).toBeInTheDocument();
252
+ const alerts = screen.getAllByRole('alert');
253
+ expect(alerts.length).toBeGreaterThan(0);
241
254
 
242
255
  const retryButton = screen.getByRole('button', { name: /retry/i });
243
256
  await user.click(retryButton);
@@ -317,7 +330,8 @@ describe('[component] DataTableErrorBoundary', () => {
317
330
  </DataTableErrorBoundary>
318
331
  );
319
332
 
320
- expect(screen.getByRole('alert')).toBeInTheDocument();
333
+ const alerts = screen.getAllByRole('alert');
334
+ expect(alerts.length).toBeGreaterThan(0);
321
335
 
322
336
  const resetButton = screen.getByRole('button', { name: /reset/i });
323
337
 
@@ -352,7 +366,9 @@ describe('[component] DataTableErrorBoundary', () => {
352
366
  </DataTableErrorBoundary>
353
367
  );
354
368
 
355
- expect(screen.getByRole('alert')).toBeInTheDocument();
369
+ // There may be multiple alerts when error has no message
370
+ const alerts = screen.getAllByRole('alert');
371
+ expect(alerts.length).toBeGreaterThan(0);
356
372
  expect(screen.getByText('An unexpected error occurred')).toBeInTheDocument();
357
373
  });
358
374
 
@@ -365,19 +381,31 @@ describe('[component] DataTableErrorBoundary', () => {
365
381
  </DataTableErrorBoundary>
366
382
  );
367
383
 
368
- expect(screen.getByRole('alert')).toBeInTheDocument();
384
+ const alerts = screen.getAllByRole('alert');
385
+ expect(alerts.length).toBeGreaterThan(0);
369
386
 
370
387
  const retryButton = screen.getByRole('button', { name: /retry/i });
371
388
  await user.click(retryButton);
372
389
 
373
- // Trigger another error
390
+ // Wait for retry to complete and error state to reset
391
+ await waitFor(() => {
392
+ // Wait for the timeout to complete
393
+ }, { timeout: 200 });
394
+
395
+ // Trigger another error - rerender with new error
374
396
  rerender(
375
397
  <DataTableErrorBoundary showRetryButton={true}>
376
398
  <ThrowError shouldThrow={true} message="Second error" />
377
399
  </DataTableErrorBoundary>
378
400
  );
379
401
 
380
- expect(screen.getByRole('alert')).toBeInTheDocument();
402
+ // Wait for the new error to be caught and displayed
403
+ await waitFor(() => {
404
+ const newAlerts = screen.getAllByRole('alert');
405
+ expect(newAlerts.length).toBeGreaterThan(0);
406
+ const preElement = document.querySelector('pre');
407
+ expect(preElement).toHaveTextContent('Second error');
408
+ }, { timeout: 300 });
381
409
  });
382
410
 
383
411
  it('handles cleanup on unmount', () => {
@@ -306,7 +306,8 @@ describe('[component] EmptyState', () => {
306
306
  it('uses semantic heading for title', () => {
307
307
  render(<EmptyState title="Custom Title" />);
308
308
 
309
- const heading = screen.getByRole('heading', { level: 3 });
309
+ // AlertTitle renders as h5, not h3
310
+ const heading = screen.getByRole('heading', { level: 5 });
310
311
  expect(heading).toHaveTextContent('Custom Title');
311
312
  });
312
313
 
@@ -410,11 +411,12 @@ describe('[component] EmptyState', () => {
410
411
  });
411
412
 
412
413
  describe('Layout and Styling', () => {
413
- it('renders with centered flex layout', () => {
414
+ it('renders with centered grid layout', () => {
414
415
  render(<EmptyState />);
415
416
 
416
417
  const container = screen.getByRole('status');
417
- expect(container).toHaveClass('flex', 'flex-col', 'items-center', 'justify-center');
418
+ // EmptyState uses grid place-items-center, not flex
419
+ expect(container).toHaveClass('grid', 'place-items-center');
418
420
  });
419
421
 
420
422
  it('applies text-center class', () => {
@@ -428,7 +430,8 @@ describe('[component] EmptyState', () => {
428
430
  render(<EmptyState />);
429
431
 
430
432
  const container = screen.getByRole('status');
431
- expect(container).toHaveClass('p-8');
433
+ // EmptyState uses p-4, not p-8
434
+ expect(container).toHaveClass('p-4');
432
435
  });
433
436
  });
434
437
  });
@@ -123,7 +123,7 @@ describe('[component] FilterRow', () => {
123
123
  expect(filters.length).toBeGreaterThan(0);
124
124
  });
125
125
 
126
- it('displays "No filter" for non-filterable columns', () => {
126
+ it('displays non-breaking space for non-filterable columns', () => {
127
127
  const columns = [
128
128
  columnHelper.accessor('name', {
129
129
  header: 'Name',
@@ -133,9 +133,17 @@ describe('[component] FilterRow', () => {
133
133
  const table = createTable(columns);
134
134
  const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
135
135
 
136
- render(<FilterRow table={table} visibleColumns={visibleColumns} />);
137
-
138
- expect(screen.getByText('No filter')).toBeInTheDocument();
136
+ const { container } = render(<FilterRow table={table} visibleColumns={visibleColumns} />);
137
+
138
+ // Check that the cell exists and does not contain a filter component
139
+ const cell = container.querySelector('td');
140
+ expect(cell).toBeInTheDocument();
141
+ // The cell should not contain a ColumnFilter component
142
+ expect(cell?.querySelector('[data-testid="column-filter"]')).not.toBeInTheDocument();
143
+ // The cell should be empty or contain only whitespace (non-breaking space from React Fragment)
144
+ // React Fragment with &nbsp; renders as a non-breaking space, but textContent may normalize it
145
+ // The important thing is that no filter component is rendered
146
+ expect(cell?.children.length).toBe(0);
139
147
  });
140
148
 
141
149
  it('renders filter with correct placeholder', () => {
@@ -18,37 +18,45 @@ describe('[component] LoadingState', () => {
18
18
  it('renders loading spinner and text', () => {
19
19
  render(<LoadingState />);
20
20
 
21
- expect(screen.getByText('Loading...')).toBeInTheDocument();
21
+ // There are two "Loading..." texts (sr-only and visible), use getAllByText
22
+ const loadingTexts = screen.getAllByText('Loading...');
23
+ expect(loadingTexts.length).toBeGreaterThan(0);
24
+ // Check that the visible text is present
25
+ expect(screen.getByText('Loading...', { selector: 'strong' })).toBeInTheDocument();
22
26
  });
23
27
 
24
28
  it('renders with centered layout', () => {
25
29
  render(<LoadingState />);
26
30
 
27
- // Find the outer container with text-center and p-8 classes
28
- const container = screen.getByText('Loading...').parentElement?.parentElement;
31
+ // Find the container using getByRole for the spinner
32
+ const spinner = screen.getByRole('status');
33
+ const container = spinner.closest('p');
29
34
  expect(container).toHaveClass('text-center', 'p-8');
30
35
  });
31
36
 
32
37
  it('renders with padding', () => {
33
38
  render(<LoadingState />);
34
39
 
35
- // Find the outer container with p-8 class
36
- const container = screen.getByText('Loading...').parentElement?.parentElement;
40
+ // Find the container using getByRole for the spinner
41
+ const spinner = screen.getByRole('status');
42
+ const container = spinner.closest('p');
37
43
  expect(container).toHaveClass('p-8');
38
44
  });
39
45
 
40
46
  it('renders spinner with animation class', () => {
41
47
  render(<LoadingState />);
42
48
 
43
- const spinner = screen.getByText('Loading...').previousElementSibling;
49
+ const spinner = screen.getByRole('status');
44
50
  expect(spinner).toHaveClass('animate-spin');
45
51
  });
46
52
 
47
- it('renders flex container with items centered', () => {
53
+ it('renders grid container with items centered', () => {
48
54
  render(<LoadingState />);
49
55
 
50
- const flexContainer = screen.getByText('Loading...').parentElement;
51
- expect(flexContainer).toHaveClass('flex', 'items-center', 'justify-center');
56
+ // Container uses grid, not flex
57
+ const spinner = screen.getByRole('status');
58
+ const container = spinner.closest('p');
59
+ expect(container).toHaveClass('grid', 'place-items-center');
52
60
  });
53
61
  });
54
62
 
@@ -56,15 +64,17 @@ describe('[component] LoadingState', () => {
56
64
  it('provides aria-live region for loading state', () => {
57
65
  render(<LoadingState />);
58
66
 
59
- const loadingText = screen.getByText('Loading...');
60
- expect(loadingText).toHaveAttribute('aria-live', 'polite');
67
+ // The spinner has role="status" which provides the aria-live region
68
+ const spinner = screen.getByRole('status');
69
+ expect(spinner).toBeInTheDocument();
61
70
  });
62
71
 
63
72
  it('announces loading state to screen readers', () => {
64
73
  render(<LoadingState />);
65
74
 
66
- const loadingText = screen.getByText('Loading...');
67
- expect(loadingText).toBeInTheDocument();
75
+ // Check that the sr-only text is present for screen readers
76
+ const srOnlyText = screen.getByText('Loading...', { selector: 'span.sr-only' });
77
+ expect(srOnlyText).toBeInTheDocument();
68
78
  });
69
79
  });
70
80
 
@@ -72,49 +82,53 @@ describe('[component] LoadingState', () => {
72
82
  it('renders spinner before loading text', () => {
73
83
  render(<LoadingState />);
74
84
 
75
- const container = screen.getByText('Loading...').parentElement;
76
- const spinner = container?.firstElementChild;
77
- const text = container?.lastElementChild;
85
+ const spinner = screen.getByRole('status');
86
+ const container = spinner.closest('p');
87
+ const text = container?.querySelector('strong');
78
88
 
79
89
  expect(spinner).toBeInTheDocument();
80
90
  expect(text).toHaveTextContent('Loading...');
81
91
  });
82
92
 
83
- it('applies space between spinner and text', () => {
93
+ it('applies grid layout for centering', () => {
84
94
  render(<LoadingState />);
85
95
 
86
- const container = screen.getByText('Loading...').parentElement;
87
- expect(container).toHaveClass('space-x-2');
96
+ // Container uses grid place-items-center, not space-x-2
97
+ const spinner = screen.getByRole('status');
98
+ const container = spinner.closest('p');
99
+ expect(container).toHaveClass('grid', 'place-items-center');
88
100
  });
89
101
  });
90
102
 
91
103
  describe('Styling', () => {
92
- it('applies muted foreground color to text', () => {
104
+ it('renders visible loading text', () => {
93
105
  render(<LoadingState />);
94
106
 
95
- const text = screen.getByText('Loading...');
96
- expect(text).toHaveClass('text-muted-foreground');
107
+ // Check that the visible text (in strong) is present
108
+ const text = screen.getByText('Loading...', { selector: 'strong' });
109
+ expect(text).toBeInTheDocument();
97
110
  });
98
111
 
99
112
  it('spinner has rounded-full class', () => {
100
113
  render(<LoadingState />);
101
114
 
102
- const spinner = screen.getByText('Loading...').previousElementSibling;
115
+ const spinner = screen.getByRole('status');
103
116
  expect(spinner).toHaveClass('rounded-full');
104
117
  });
105
118
 
106
119
  it('spinner has border styling', () => {
107
120
  render(<LoadingState />);
108
121
 
109
- const spinner = screen.getByText('Loading...').previousElementSibling;
110
- expect(spinner).toHaveClass('border-b-2', 'border-primary');
122
+ const spinner = screen.getByRole('status');
123
+ // Spinner uses border-2 border-solid border-current border-r-transparent
124
+ expect(spinner).toHaveClass('border-2', 'border-solid', 'border-current', 'border-r-transparent');
111
125
  });
112
126
 
113
127
  it('spinner has appropriate size', () => {
114
128
  render(<LoadingState />);
115
129
 
116
- const spinner = screen.getByText('Loading...').previousElementSibling;
117
- // LoadingState spinner uses Tailwind v4 size-* utility instead of h-* w-*
130
+ const spinner = screen.getByRole('status');
131
+ // LoadingState spinner uses Tailwind v4 size-* utility
118
132
  expect(spinner).toHaveClass('size-6');
119
133
  });
120
134
  });
@@ -40,10 +40,6 @@ export function usePermissionTracking({
40
40
  if (permissions.canRead.isLoading && !hasStartedTracking.current) {
41
41
  hasStartedTracking.current = true;
42
42
  permissionCheckStartTime.current = Date.now();
43
- logger.debug('DataTable: Permission check started', {
44
- pageId: effectivePageId,
45
- timestamp: permissionCheckStartTime.current,
46
- });
47
43
 
48
44
  if (permissionWarningTimeoutId.current) {
49
45
  clearTimeout(permissionWarningTimeoutId.current);
@@ -7,9 +7,10 @@ export { DataTableToolbar } from './DataTableToolbar';
7
7
  export { DataTableModals } from './DataTableModals';
8
8
  export { ImportModal } from './ImportModal';
9
9
  export type { ImportModalConfig } from './ImportModal';
10
- export { GroupHeader } from './GroupHeader';
11
10
  export { GroupingDropdown } from './GroupingDropdown';
12
11
  export { DataTableErrorBoundary } from './DataTableErrorBoundary';
13
12
  export { PaginationControls } from './PaginationControls';
14
13
  export { LoadingState } from './LoadingState';
15
14
  export { EmptyState } from './EmptyState';
15
+ export { SortIndicator } from './SortIndicator';
16
+ export type { SortIndicatorProps } from './SortIndicator';