@jmruthers/pace-core 0.6.4 → 0.6.6

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 (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -301,15 +301,23 @@ export function DataTableBody<TData extends DataRecord>({
301
301
  <tr>
302
302
  {table.getVisibleFlatColumns().map((column) => (
303
303
  <td key={column.id}>
304
- {renderEditField(column, creationData[column.id], (value) => {
305
- if (typeof value === 'object' && value !== null) {
306
- // Handle editAccessorKey case
307
- onCreationDataChange({ ...creationData, ...value });
308
- } else {
309
- // Handle simple value case
310
- onCreationDataChange({ ...creationData, [column.id]: value });
311
- }
312
- }, creationData)}
304
+ {(() => {
305
+ // CRITICAL FIX: Use the correct accessor key (editAccessorKey or column.id) to get the value
306
+ // This ensures that when editAccessorKey is different from column.id, we use the correct key
307
+ const columnDef = column.columnDef;
308
+ const accessorKey = columnDef.editAccessorKey || column.id;
309
+ const currentValue = creationData[accessorKey] ?? creationData[column.id] ?? '';
310
+
311
+ return renderEditField(column, currentValue, (value) => {
312
+ if (typeof value === 'object' && value !== null) {
313
+ // Handle editAccessorKey case
314
+ onCreationDataChange({ ...creationData, ...value });
315
+ } else {
316
+ // Handle simple value case
317
+ onCreationDataChange({ ...creationData, [column.id]: value });
318
+ }
319
+ }, creationData);
320
+ })()}
313
321
  </td>
314
322
  ))}
315
323
  <td className="flex gap-1">
@@ -397,19 +405,27 @@ export function DataTableBody<TData extends DataRecord>({
397
405
  <tr key={subRow.id} className="border-l-2 border-l-blue-200">
398
406
  {subRow.getVisibleCells().map((cell) => (
399
407
  <td key={cell.id} className="pl-8">
400
- {isSubRowEditing && cell.column.id !== 'actions' ? (
401
- renderEditField(cell.column, editingData[cell.column.id], (value) => {
402
- if (typeof value === 'object' && value !== null) {
403
- // Handle editAccessorKey case
404
- onEditingDataChange({ ...editingData, ...value });
405
- } else {
406
- // Handle simple value case
407
- onEditingDataChange({ ...editingData, [cell.column.id]: value });
408
- }
409
- }, editingData)
410
- ) : (
411
- flexRender(cell.column.columnDef.cell, cell.getContext())
412
- )}
408
+ {isSubRowEditing && cell.column.id !== 'actions' ? (
409
+ (() => {
410
+ // CRITICAL FIX: Use the correct accessor key (editAccessorKey or column.id) to get the value
411
+ // This ensures that when editAccessorKey is different from column.id, we use the correct key
412
+ const columnDef = cell.column.columnDef;
413
+ const accessorKey = columnDef.editAccessorKey || cell.column.id;
414
+ const currentValue = editingData[accessorKey] ?? editingData[cell.column.id] ?? cell.getValue();
415
+
416
+ return renderEditField(cell.column, currentValue, (value) => {
417
+ if (typeof value === 'object' && value !== null) {
418
+ // Handle editAccessorKey case
419
+ onEditingDataChange({ ...editingData, ...value });
420
+ } else {
421
+ // Handle simple value case
422
+ onEditingDataChange({ ...editingData, [cell.column.id]: value });
423
+ }
424
+ }, editingData);
425
+ })()
426
+ ) : (
427
+ flexRender(cell.column.columnDef.cell, cell.getContext())
428
+ )}
413
429
  </td>
414
430
  ))}
415
431
  </tr>
@@ -431,15 +447,23 @@ export function DataTableBody<TData extends DataRecord>({
431
447
  {row.getVisibleCells().map((cell) => (
432
448
  <td key={cell.id}>
433
449
  {isEditing && cell.column.id !== 'actions' ? (
434
- renderEditField(cell.column, editingData[cell.column.id], (value) => {
435
- if (typeof value === 'object' && value !== null) {
436
- // Handle editAccessorKey case
437
- onEditingDataChange({ ...editingData, ...value });
438
- } else {
439
- // Handle simple value case
440
- onEditingDataChange({ ...editingData, [cell.column.id]: value });
441
- }
442
- }, editingData)
450
+ (() => {
451
+ // CRITICAL FIX: Use the correct accessor key (editAccessorKey or column.id) to get the value
452
+ // This ensures that when editAccessorKey is different from column.id, we use the correct key
453
+ const columnDef = cell.column.columnDef;
454
+ const accessorKey = columnDef.editAccessorKey || cell.column.id;
455
+ const currentValue = editingData[accessorKey] ?? editingData[cell.column.id] ?? cell.getValue();
456
+
457
+ return renderEditField(cell.column, currentValue, (value) => {
458
+ if (typeof value === 'object' && value !== null) {
459
+ // Handle editAccessorKey case
460
+ onEditingDataChange({ ...editingData, ...value });
461
+ } else {
462
+ // Handle simple value case
463
+ onEditingDataChange({ ...editingData, [cell.column.id]: value });
464
+ }
465
+ }, editingData);
466
+ })()
443
467
  ) : (
444
468
  flexRender(cell.column.columnDef.cell, cell.getContext())
445
469
  )}
@@ -8,8 +8,9 @@
8
8
  * This is the main component that consumers will use.
9
9
  */
10
10
 
11
- import React, { useMemo, useCallback, useEffect, useRef } from 'react';
11
+ import React, { useMemo, useCallback, useEffect, useLayoutEffect, useRef, useState, startTransition, useDeferredValue } from 'react';
12
12
  import { useReactTable } from '@tanstack/react-table';
13
+ import { useLocation } from 'react-router-dom';
13
14
  import type {
14
15
  SortingState,
15
16
  } from '@tanstack/react-table';
@@ -182,6 +183,81 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
182
183
 
183
184
  const logger = createLogger('DataTableCore');
184
185
 
186
+ // Track deletion state to batch updates and prevent flashing
187
+ const [isDeleting, setIsDeleting] = useState(false);
188
+ const deletionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
189
+ const pendingDeletionsRef = useRef<Set<string>>(new Set());
190
+ const dataSnapshotRef = useRef<TData[]>(data);
191
+ const dataChangeCountRef = useRef(0);
192
+ const previousDataLengthRef = useRef(data.length);
193
+
194
+ // CRITICAL: Use useLayoutEffect to capture previous length synchronously
195
+ // This runs before paint, so we can detect changes before React commits
196
+ useLayoutEffect(() => {
197
+ const currentLength = data.length;
198
+ const previousLength = previousDataLengthRef.current;
199
+
200
+ // Check if length decreased - this is the key detection
201
+ const lengthDecreased = currentLength < previousLength;
202
+
203
+ if (lengthDecreased && !isDeleting) {
204
+ // Data length decreased - start deletion batching
205
+ const snapshotLength = dataSnapshotRef.current.length;
206
+
207
+ // Data decrease detected (logging removed for performance)
208
+
209
+ // If snapshot is larger, it's valid (has pre-deletion state)
210
+ // Otherwise, update it to preserve current state
211
+ if (snapshotLength <= currentLength) {
212
+ dataSnapshotRef.current = [...data];
213
+ }
214
+
215
+ setIsDeleting(true);
216
+
217
+ // Set timeout to reset after batching
218
+ if (deletionTimeoutRef.current) {
219
+ clearTimeout(deletionTimeoutRef.current);
220
+ }
221
+ deletionTimeoutRef.current = setTimeout(() => {
222
+ setIsDeleting(false);
223
+ dataSnapshotRef.current = data;
224
+ previousDataLengthRef.current = data.length;
225
+ // Batching complete (logging removed for performance)
226
+ }, 150);
227
+
228
+ // Update previous length AFTER setting up batching
229
+ previousDataLengthRef.current = currentLength;
230
+ } else if (!lengthDecreased && !isDeleting) {
231
+ // No deletion - update snapshot and previous length normally
232
+ dataSnapshotRef.current = data;
233
+ previousDataLengthRef.current = currentLength;
234
+ } else if (isDeleting) {
235
+ // Already deleting - just update previous length for next comparison
236
+ previousDataLengthRef.current = currentLength;
237
+ }
238
+ }, [data, isDeleting]);
239
+
240
+ // Keep data snapshot stable during deletions to prevent flashing
241
+ // Logging effect - runs after layout to show what happened
242
+ useEffect(() => {
243
+ dataChangeCountRef.current += 1;
244
+
245
+ const previousLength = previousDataLengthRef.current;
246
+ const currentLength = data.length;
247
+ const snapshotLength = dataSnapshotRef.current.length;
248
+ const lengthDecreased = currentLength < previousLength;
249
+
250
+ // Data prop changed (logging removed for performance)
251
+ }, [data, isDeleting]);
252
+
253
+ // Use snapshot data during deletions to prevent flashing
254
+ // CRITICAL: If we're deleting and data length decreased, use snapshot to prevent flashing
255
+ const dataLengthChanged = data.length !== dataSnapshotRef.current.length;
256
+ const isDataDecreasing = data.length < dataSnapshotRef.current.length;
257
+ const effectiveData = (isDeleting && isDataDecreasing) ? dataSnapshotRef.current : data;
258
+
259
+ // Effective data tracking (logging removed for performance)
260
+
185
261
  // ============================================================================
186
262
  // ALL HOOKS MUST BE CALLED IN THE SAME ORDER EVERY RENDER
187
263
  // ============================================================================
@@ -241,15 +317,38 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
241
317
  return {};
242
318
  }, [secureFeatures.columnVisibility, savedColumnVisibility]);
243
319
 
320
+ // Get location for route-based key derivation
321
+ let location: { pathname: string } | null = null;
322
+ try {
323
+ // useLocation may not be available if React Router is not set up
324
+ const routerLocation = useLocation();
325
+ location = routerLocation;
326
+ } catch {
327
+ // Router not available, location will be null
328
+ location = null;
329
+ }
330
+
331
+ // Extract column field names for sensitive field filtering
332
+ const columnFieldNames = useMemo(() => {
333
+ return columns
334
+ .map((col) => col.accessorKey || col.id)
335
+ .filter((key): key is string => Boolean(key));
336
+ }, [columns]);
337
+
244
338
  // Use the centralized state management hook for ALL table state
245
339
  // Note: 'actions' prop parameter is shadowed by destructuring, so we rename to stateActions
246
- const { state, actions: stateActions } = useDataTableState<TData>({
340
+ const { state, actions: stateActions, clearDraft } = useDataTableState<TData>({
247
341
  initialPageSize,
248
342
  columnIds: effectiveColumnOrder,
249
343
  initialRowSelection: selection || {},
250
344
  onRowSelectionChange,
251
345
  defaultSorting: defaultSorting || [],
252
- defaultGrouping: defaultGrouping || []
346
+ defaultGrouping: defaultGrouping || [],
347
+ // Persistence props
348
+ rbacPageId: rbac.pageId,
349
+ title,
350
+ location,
351
+ columnFieldNames,
253
352
  });
254
353
 
255
354
  // Apply saved visibility to state if available
@@ -344,7 +443,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
344
443
  hierarchicalState,
345
444
  hierarchicalValidation,
346
445
  } = useDataTableDataPipeline<TData>({
347
- data,
446
+ data: effectiveData,
348
447
  features: secureFeatures,
349
448
  hierarchical,
350
449
  sorting: state.sorting,
@@ -357,6 +456,34 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
357
456
  logger.error('Hierarchical data validation failed:', hierarchicalValidation.errors);
358
457
  }
359
458
  }, [hierarchicalValidation, logger]);
459
+
460
+ // Final table data tracking (logging removed for performance)
461
+
462
+ // Cleanup deletion timeout on unmount
463
+ useEffect(() => {
464
+ return () => {
465
+ if (deletionTimeoutRef.current) {
466
+ clearTimeout(deletionTimeoutRef.current);
467
+ }
468
+ pendingDeletionsRef.current.clear();
469
+ };
470
+ }, []);
471
+
472
+ // Update data snapshot when data changes and we're not deleting
473
+ useEffect(() => {
474
+ if (!isDeleting && data !== dataSnapshotRef.current) {
475
+ dataSnapshotRef.current = data;
476
+ }
477
+ }, [data, isDeleting]);
478
+
479
+ // Cleanup deletion timeout on unmount
480
+ useEffect(() => {
481
+ return () => {
482
+ if (deletionTimeoutRef.current) {
483
+ clearTimeout(deletionTimeoutRef.current);
484
+ }
485
+ };
486
+ }, []);
360
487
 
361
488
  const {
362
489
  columnOrder: savedColumnOrder,
@@ -563,19 +690,24 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
563
690
  // RBAC VALIDATION AND SECURE CONFIGURATION - ALWAYS call these hooks
564
691
  // ============================================================================
565
692
 
693
+ // Wrap handlers - persistence is handled automatically via useDataTableState
694
+ // The state actions (clearCreationData, clearEditing) will automatically update persisted state
695
+ const wrappedOnCreateRow = onCreateRow;
696
+ const wrappedOnEditRow = onEditRow;
697
+
566
698
  // MANDATORY: Handlers are automatically secured
567
699
  const secureHandlers = useMemo(() => {
568
700
  const handlers = {
569
- onEditRow: permissions.canUpdate.can ? onEditRow : undefined,
701
+ onEditRow: permissions.canUpdate.can ? wrappedOnEditRow : undefined,
570
702
  onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
571
- onCreateRow: permissions.canCreate.can ? onCreateRow : undefined,
703
+ onCreateRow: permissions.canCreate.can ? wrappedOnCreateRow : undefined,
572
704
  onImport: permissions.canImport.can ? onImport : undefined,
573
705
  onExport: permissions.canExport.can ? onExport : undefined,
574
706
  onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
575
707
  };
576
708
 
577
709
  return handlers;
578
- }, [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can, onEditRow, onDeleteRow, onCreateRow, onImport, onExport, onDeleteSelected, secureFeatures.creation]);
710
+ }, [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can, wrappedOnEditRow, onDeleteRow, wrappedOnCreateRow, onImport, onExport, onDeleteSelected, secureFeatures.creation]);
579
711
 
580
712
  // MANDATORY: Process actions with RBAC checks
581
713
  const effectiveActions = useMemo(() => {
@@ -617,19 +749,60 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
617
749
  });
618
750
  return;
619
751
  }
752
+
753
+ // Get row ID for tracking - use current data, not effectiveData
754
+ const rowIndex = data.findIndex(r => r === row);
755
+ const rowId = resolvedGetRowId(row, rowIndex >= 0 ? rowIndex : 0);
756
+
757
+ // Mark deletion as in progress and track this deletion
758
+ // CRITICAL: Set isDeleting and snapshot BEFORE calling the handler
759
+ // This prevents the parent's data update from causing a flash
760
+ if (!isDeleting) {
761
+ // Snapshot current data BEFORE deletion
762
+ dataSnapshotRef.current = [...data]; // Create a copy to prevent reference issues
763
+ setIsDeleting(true);
764
+ }
765
+ pendingDeletionsRef.current.add(rowId);
766
+
767
+ // Clear any existing timeout
768
+ if (deletionTimeoutRef.current) {
769
+ clearTimeout(deletionTimeoutRef.current);
770
+ }
771
+
620
772
  try {
773
+ // Call the delete handler - this may update the parent's data prop
774
+ const deleteStartTime = Date.now();
621
775
  const result = secureHandlers.onDeleteRow!(row) as any;
622
776
  // Handle async operations
623
777
  if (result !== undefined && result !== null && typeof result === 'object' && typeof result.then === 'function') {
624
778
  await result;
625
779
  }
626
- toast({
627
- title: "Delete Successful",
628
- description: "Row deleted successfully",
629
- variant: "default"
630
- });
780
+ // Remove from pending deletions
781
+ pendingDeletionsRef.current.delete(rowId);
782
+
783
+ // Reset deletion state after a delay to allow batching
784
+ // This prevents the table from refreshing after each individual deletion
785
+ deletionTimeoutRef.current = setTimeout(() => {
786
+ // Only reset if all deletions are complete
787
+ if (pendingDeletionsRef.current.size === 0) {
788
+ setIsDeleting(false);
789
+ // Update snapshot to latest data
790
+ dataSnapshotRef.current = data;
791
+ toast({
792
+ title: "Delete Successful",
793
+ description: "Row deleted successfully",
794
+ variant: "default"
795
+ });
796
+ }
797
+ }, 150); // Delay to batch rapid deletions
798
+
631
799
  } catch (error) {
632
800
  logger.error('Delete error:', error);
801
+ pendingDeletionsRef.current.delete(rowId);
802
+ if (pendingDeletionsRef.current.size === 0) {
803
+ setIsDeleting(false);
804
+ dataSnapshotRef.current = data;
805
+ }
633
806
  toast({
634
807
  title: "Delete Failed",
635
808
  description: error instanceof Error ? error.message : 'Failed to delete row',
@@ -645,7 +818,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
645
818
  }
646
819
 
647
820
  return result;
648
- }, [actions, secureFeatures, permissions, secureHandlers, resolvedGetRowId, stateActions, data]);
821
+ }, [actions, secureFeatures, permissions, secureHandlers, resolvedGetRowId, stateActions, effectiveData, isDeleting, pendingDeletionsRef, deletionTimeoutRef, dataSnapshotRef]);
649
822
 
650
823
  // Normalize columnOrder for useTableColumns: ensure 'select' is always first
651
824
  const normalizedColumnOrderForColumns = useMemo(() => {
@@ -13,6 +13,7 @@ import { getTableClasses } from '../styles';
13
13
  import { toast } from '../../../hooks/useToast';
14
14
  import type {
15
15
  AggregateConfig,
16
+ CellValue,
16
17
  DataRecord,
17
18
  DataTableAction,
18
19
  DataTableColumn,
@@ -130,6 +131,12 @@ export function DataTableLayout<TData extends DataRecord>({
130
131
  keyboardNavigation,
131
132
  lastFocusedElementRef,
132
133
  }: DataTableLayoutProps<TData>) {
134
+ // CRITICAL FIX: Use a ref to track the latest editingData to avoid stale closure issues
135
+ // The ref is always kept in sync with state.editingData via useEffect
136
+ const editingDataRef = React.useRef(state.editingData);
137
+ React.useEffect(() => {
138
+ editingDataRef.current = state.editingData;
139
+ }, [state.editingData]);
133
140
  const handleExport = async () => {
134
141
  try {
135
142
  const tableRows = table.getFilteredRowModel().rows;
@@ -282,6 +289,8 @@ export function DataTableLayout<TData extends DataRecord>({
282
289
  if (result !== undefined && result !== null && typeof result === 'object' && typeof result.then === 'function') {
283
290
  await result;
284
291
  }
292
+ // Clear selection after successful deletion
293
+ stateActions.clearRowSelection();
285
294
  toast({
286
295
  title: 'Delete Successful',
287
296
  description: `Successfully deleted ${selectedCount} ${selectedCount === 1 ? 'row' : 'rows'}`,
@@ -424,8 +433,18 @@ export function DataTableLayout<TData extends DataRecord>({
424
433
  creationData={state.creationData}
425
434
  onCreationDataChange={stateActions.setCreationData}
426
435
  onSaveCreation={() => {
436
+ const creationDataToUse = state.creationData;
437
+ // CRITICAL FIX: Ensure status field is included with default value "active" if not already set
438
+ // This ensures new rows are created with the correct status
439
+ const finalCreationData = {
440
+ ...creationDataToUse,
441
+ // Only set status to "active" if it's not already set and if status is a column in the table
442
+ ...(creationDataToUse.status === undefined && table.getAllColumns().some(col => col.id === 'status')
443
+ ? { status: 'active' as CellValue }
444
+ : {}),
445
+ };
427
446
  if (onCreateRow) {
428
- onCreateRow(state.creationData as Partial<TData>);
447
+ onCreateRow(finalCreationData as Partial<TData>);
429
448
  stateActions.clearCreationData();
430
449
  stateActions.setCreating(false);
431
450
  }
@@ -442,17 +461,23 @@ export function DataTableLayout<TData extends DataRecord>({
442
461
  }
443
462
  }}
444
463
  onSaveEditing={() => {
464
+ // CRITICAL FIX: Use ref to get the latest editingData to avoid stale closure issues
465
+ // The ref is always kept in sync with state.editingData, so it should always have the correct value
466
+ const latestEditingData = editingDataRef.current;
445
467
  if (onEditRow && state.editingRowId) {
446
- const originalRow = data.find((row) => {
468
+ // CRITICAL FIX: Use findIndex to get the actual index, then use that index for getRowId
469
+ const originalRowIndex = data.findIndex((row, index) => {
447
470
  try {
448
- const rowId = resolvedGetRowId(row, 0);
471
+ const rowId = resolvedGetRowId(row, index);
449
472
  return rowId === state.editingRowId;
450
473
  } catch {
451
474
  return false;
452
475
  }
453
476
  });
454
- if (originalRow) {
455
- onEditRow(originalRow, state.editingData as Partial<TData>);
477
+
478
+ if (originalRowIndex >= 0) {
479
+ const originalRow = data[originalRowIndex];
480
+ onEditRow(originalRow, latestEditingData as Partial<TData>);
456
481
  }
457
482
  }
458
483
  stateActions.clearEditing();
@@ -33,7 +33,7 @@ export function SelectEditField<TData extends DataRecord>({
33
33
  const logger = createLogger('SelectEditField');
34
34
  const isSearchable = columnDef.selectSearchable !== false;
35
35
  const isCreatable = columnDef.creatable === true;
36
- const selectRef = React.useRef<HTMLFormElement>(null);
36
+ const selectRef = React.useRef<HTMLFieldSetElement>(null);
37
37
  const [searchTerm, setSearchTerm] = React.useState('');
38
38
  const [isOpen, setIsOpen] = React.useState(false);
39
39
  const [showCreateOption, setShowCreateOption] = React.useState(false);
@@ -258,7 +258,27 @@ export const renderEditField = <TData extends DataRecord>(
258
258
 
259
259
  if (columnDef.fieldType === 'select' && columnDef.fieldOptions) {
260
260
  const accessorKey = columnDef.editAccessorKey || column.id;
261
- const currentValue = editingData[accessorKey] ?? value ?? '';
261
+ // CRITICAL FIX: Ensure we use the value from editingData first, then fall back to the passed value
262
+ // Convert to string to match Select component's string-based value system
263
+ // Always prioritize editingData[accessorKey] to ensure we use the most recent value
264
+ const rawValue = editingData[accessorKey] !== undefined && editingData[accessorKey] !== null
265
+ ? editingData[accessorKey]
266
+ : (value !== undefined && value !== null ? value : '');
267
+ const currentValue = rawValue !== null && rawValue !== undefined ? String(rawValue) : '';
268
+
269
+ // Create onChange handler that immediately updates state
270
+ const handleValueChange = (newValue: CellValue) => {
271
+ // Store the new value - preserve original type if option values are numbers
272
+ // Check if any option has a numeric value to determine if we should convert
273
+ const hasNumericValues = columnDef.fieldOptions?.some((opt: any) =>
274
+ 'value' in opt && !('type' in opt) && typeof opt.value === 'number'
275
+ );
276
+ const finalValue = hasNumericValues && !isNaN(Number(newValue)) && newValue !== ''
277
+ ? Number(newValue)
278
+ : newValue;
279
+ // Immediately update state - this happens synchronously
280
+ onChange({ [accessorKey]: finalValue });
281
+ };
262
282
 
263
283
  return (
264
284
  <SelectEditField
@@ -266,7 +286,7 @@ export const renderEditField = <TData extends DataRecord>(
266
286
  accessorKey={accessorKey}
267
287
  currentValue={currentValue}
268
288
  placeholder={placeholder}
269
- onChange={(newValue) => onChange({ [accessorKey]: newValue })}
289
+ onChange={handleValueChange}
270
290
  />
271
291
  );
272
292
  }
@@ -51,7 +51,7 @@ function SelectEditField<TData extends DataRecord>({
51
51
  // When selectSearchable is false, hide the search input (type-to-search still works via SelectContent internals)
52
52
  const isSearchable = columnDef.selectSearchable !== false;
53
53
  const isCreatable = columnDef.creatable === true;
54
- const selectRef = React.useRef<HTMLFormElement>(null);
54
+ const selectRef = React.useRef<HTMLFieldSetElement>(null);
55
55
  const [searchTerm, setSearchTerm] = React.useState('');
56
56
  const [isOpen, setIsOpen] = React.useState(false);
57
57
  const [showCreateOption, setShowCreateOption] = React.useState(false);
@@ -434,9 +434,14 @@ export function EditableRow<TData extends DataRecord>({
434
434
  hasAssignedRef.current = true;
435
435
  }
436
436
 
437
+ // CRITICAL FIX: Use the correct accessor key (editAccessorKey or column.id) to get the value
438
+ // This ensures that when editAccessorKey is different from column.id, we use the correct key
439
+ const accessorKey = columnDef.editAccessorKey || cell.column.id;
440
+ const currentValue = editingData[accessorKey] ?? (cell.getValue() as CellValue);
441
+
437
442
  return renderEditField(
438
443
  cell.column,
439
- editingData[cell.column.id] ?? (cell.getValue() as CellValue),
444
+ currentValue,
440
445
  (value) => {
441
446
  if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
442
447
  onEditingDataChange({ ...editingData, ...(value as Record<string, CellValue>) });
@@ -36,7 +36,7 @@
36
36
  * - Customizable text content
37
37
  */
38
38
  import React, { useState, useRef, useEffect } from 'react';
39
- import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../Dialog';
39
+ import { Dialog, DialogContent, DialogHeader } from '../../Dialog';
40
40
  import { Button } from '../../Button/Button';
41
41
  import { Input } from '../../Input/Input';
42
42
  import { Progress } from '../../Progress';
@@ -372,12 +372,10 @@ export function ImportModal({ isOpen, onClose, onImport, config = {} }: ImportMo
372
372
 
373
373
  return (
374
374
  <Dialog open={isOpen} onOpenChange={handleClose}>
375
- <DialogContent className="sm:max-w-2xl bg-main-50">
375
+ <DialogContent className="sm:max-w-2xl bg-main-50" title={title} description={description}>
376
376
  <DialogHeader>
377
- <DialogTitle>{title}</DialogTitle>
378
- <DialogDescription>
379
- {description}
380
- </DialogDescription>
377
+ <h2>{title}</h2>
378
+ <p>{description}</p>
381
379
  </DialogHeader>
382
380
 
383
381
  <div className="space-y-4">
@@ -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
 
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import React from 'react';
9
- import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../Dialog/Dialog';
9
+ import { Dialog, DialogContent, DialogHeader } from '../../Dialog/Dialog';
10
10
  import { Button } from '../../Button/Button';
11
11
  import { X } from 'lucide-react';
12
12
 
@@ -27,9 +27,9 @@ export function ViewRowModal<TData extends Record<string, any>>({
27
27
 
28
28
  return (
29
29
  <Dialog open={isOpen} onOpenChange={onClose}>
30
- <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
30
+ <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto" title={title}>
31
31
  <DialogHeader>
32
- <DialogTitle className="flex items-center justify-between">
32
+ <h2 className="flex items-center justify-between">
33
33
  {title}
34
34
  <Button
35
35
  variant="ghost"
@@ -39,7 +39,7 @@ export function ViewRowModal<TData extends Record<string, any>>({
39
39
  >
40
40
  <X className="size-4" />
41
41
  </Button>
42
- </DialogTitle>
42
+ </h2>
43
43
  </DialogHeader>
44
44
 
45
45
  <div className="mt-4">