@marimo-team/islands 0.23.9-dev2 → 0.23.9-dev20

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 (273) hide show
  1. package/dist/{ConnectedDataExplorerComponent-2lBNiUv6.js → ConnectedDataExplorerComponent-DSyAzzpW.js} +20 -20
  2. package/dist/ErrorBoundary-rULOrC_p.js +175 -0
  3. package/dist/{ImageComparisonComponent-CNHIsPDj.js → ImageComparisonComponent-CHrI72em.js} +1 -1
  4. package/dist/{Plot-4wn-lMVn.js → Plot-CAYS29h9.js} +1 -1
  5. package/dist/{_baseUniq-CxZRxRRo.js → _baseUniq-B_2Hw7zG.js} +3 -3
  6. package/dist/{any-language-editor-VWs_7v27.js → any-language-editor-DfdpyDv_.js} +23 -23
  7. package/dist/architecture-7HQA4BMR-Kyc44TmC.js +6 -0
  8. package/dist/{architectureDiagram-VXUJARFQ-CXVJxFhH.js → architectureDiagram-VXUJARFQ-CT2SuxNw.js} +15 -15
  9. package/dist/{arrays-CldYf7p7.js → arrays-sEtDRoG4.js} +1 -1
  10. package/dist/assets/__vite-browser-external-Ddfz4Fpd.js +1 -0
  11. package/dist/assets/{worker-CpBbwbQo.js → worker-CgL6N0XX.js} +2 -2
  12. package/dist/{blockDiagram-VD42YOAC-DGDaxR8I.js → blockDiagram-VD42YOAC-Dy7hlFla.js} +7 -7
  13. package/dist/{button-Dj4BTre0.js → button-C5K9fIPF.js} +2 -2
  14. package/dist/{c4Diagram-YG6GDRKO-C2hc6ne8.js → c4Diagram-YG6GDRKO-BXlAmZ8Z.js} +4 -4
  15. package/dist/{capabilities-C9rrYCzf.js → capabilities-BceAxrAW.js} +2 -2
  16. package/dist/{channel-BBoIVUrJ.js → channel-D_PHgcig.js} +1 -1
  17. package/dist/{chat-ui-D3XBept8.js → chat-ui-s5fKbcIr.js} +3084 -3057
  18. package/dist/{check-BcUIXnUT.js → check-DTbrK0zt.js} +1 -1
  19. package/dist/{chunk-4F5CHEZ2-BZq7Kom7.js → chunk-4F5CHEZ2-D9nGEHV8.js} +1 -1
  20. package/dist/{chunk-5FQGJX7Z-BOg95xG5.js → chunk-5FQGJX7Z-BNjes6Yx.js} +5 -5
  21. package/dist/{chunk-ABZYJK2D-D0cLy8Bb.js → chunk-ABZYJK2D-Dz0-H2B5.js} +1 -1
  22. package/dist/{chunk-ATLVNIR6-BXsEjlHF.js → chunk-ATLVNIR6-o0Z5MZLd.js} +1 -1
  23. package/dist/{chunk-B2363JML-D9-XOau1.js → chunk-B2363JML-KEJpLGGP.js} +1 -1
  24. package/dist/{chunk-B4BG7PRW-Q1usn6T3.js → chunk-B4BG7PRW-BL98U9B4.js} +4 -4
  25. package/dist/{chunk-DI55MBZ5-D1qLYNrb.js → chunk-DI55MBZ5-Dwkn0LWm.js} +4 -4
  26. package/dist/{chunk-EXTU4WIE-BKNXdLmD.js → chunk-EXTU4WIE-9sNjmQrB.js} +1 -1
  27. package/dist/{chunk-FRFDVMJY-BSBUAX7r.js → chunk-FRFDVMJY-DzQqMWrl.js} +1 -1
  28. package/dist/{chunk-JA3XYJ7Z-D6c6cOBG.js → chunk-JA3XYJ7Z-C32Y7Epf.js} +2 -2
  29. package/dist/{chunk-JZLCHNYA-BvsPHJmL.js → chunk-JZLCHNYA-C6ftyVMN.js} +4 -4
  30. package/dist/{chunk-N4CR4FBY-8ycT-O9a.js → chunk-N4CR4FBY-DUhGZhZs.js} +5 -5
  31. package/dist/{chunk-PL6DKKU2-B0MTXvyc.js → chunk-PL6DKKU2-D7km-08O.js} +1 -1
  32. package/dist/{chunk-QN33PNHL-Bb-eUBW3.js → chunk-QN33PNHL-0K6SDYn3.js} +1 -1
  33. package/dist/{chunk-QXUST7PY-DV8yRwBd.js → chunk-QXUST7PY-DMhsRpYK.js} +5 -5
  34. package/dist/{chunk-S3R3BYOJ-mQeCz5CE.js → chunk-S3R3BYOJ-oAe3dEbO.js} +3 -3
  35. package/dist/{chunk-SJTYNZTY-CEG4F0pB.js → chunk-SJTYNZTY-BkJrPRFC.js} +1 -1
  36. package/dist/{chunk-TCCFYFTB-d3HOqL2I.js → chunk-TCCFYFTB-D58KeXnC.js} +6 -6
  37. package/dist/{chunk-TQ3KTPDO-DiCtqVSi.js → chunk-TQ3KTPDO-D_yA_wAb.js} +1 -1
  38. package/dist/{chunk-TZMSLE5B-BqW10dHe.js → chunk-TZMSLE5B-yBKS_DQU.js} +1 -1
  39. package/dist/{chunk-UMXZTB3W-97iS1iEl.js → chunk-UMXZTB3W-D7uwvNjd.js} +1 -1
  40. package/dist/{classDiagram-2ON5EDUG--Yh__LHb.js → classDiagram-2ON5EDUG-QjoAcuFE.js} +10 -10
  41. package/dist/{classDiagram-v2-WZHVMYZB-BC7X7Xtc.js → classDiagram-v2-WZHVMYZB-bUCv4gu2.js} +10 -10
  42. package/dist/{clone-BuIIsfA8.js → clone-Q4Fqwn6q.js} +1 -1
  43. package/dist/{code-block-37QAKDTI-BsGy1AOJ.js → code-block-37QAKDTI-m92Yc8pv.js} +2 -2
  44. package/dist/{code-visibility-Dt0Aq6-t.js → code-visibility-DvMXzzJz.js} +8425 -8595
  45. package/dist/{constants-D0gkYoE2.js → constants-T20xxyNf.js} +2 -2
  46. package/dist/{copy-DLf4aN7I.js → copy-BuQpJEzp.js} +2 -2
  47. package/dist/{dagre-6UL2VRFP-DRBWoQUw.js → dagre-6UL2VRFP-J0JKgwOt.js} +11 -11
  48. package/dist/{dagre-VYEPqXIV.js → dagre-By_QsQgc.js} +11 -11
  49. package/dist/{data-grid-overlay-editor-efe5ZagF.js → data-grid-overlay-editor-mfEJ5475.js} +2 -2
  50. package/dist/{diagram-PSM6KHXK-H66ATWP2.js → diagram-PSM6KHXK-DYgJuNk9.js} +18 -18
  51. package/dist/{diagram-QEK2KX5R-DItl5Wns.js → diagram-QEK2KX5R-CKdBR2sb.js} +14 -14
  52. package/dist/{diagram-S2PKOQOG-CtuW_ZuL.js → diagram-S2PKOQOG-Dpi7mo5W.js} +14 -14
  53. package/dist/dist-0Fif7jnk.js +5 -0
  54. package/dist/{dist-Dh3wkoyH.js → dist-4j4c7bjm.js} +2 -2
  55. package/dist/{dist-CDFZi-QD.js → dist-B3P2fFpz.js} +1 -1
  56. package/dist/{dist-BNyrZfqT.js → dist-B3pZ0Ab6.js} +2 -2
  57. package/dist/dist-B5h_9sHB.js +6 -0
  58. package/dist/dist-B9M6R5ye.js +5 -0
  59. package/dist/dist-BCt3tnck.js +8 -0
  60. package/dist/{dist-BrBucRXs.js → dist-BTfv03uy.js} +2 -2
  61. package/dist/dist-BUIJwMwn.js +8 -0
  62. package/dist/{dist-CYEylvZA.js → dist-BbbIBDiQ.js} +1 -1
  63. package/dist/{dist-KnujRhFL.js → dist-BcuoonNH.js} +4 -4
  64. package/dist/{dist-DJ6zJQZ4.js → dist-Bde4a2kU.js} +2 -2
  65. package/dist/{dist-t_qL7eB8.js → dist-Bfwsv11D.js} +2 -2
  66. package/dist/{dist-CNtV21T_.js → dist-BhM8gdSO.js} +4 -4
  67. package/dist/{dist-nuW5EDYT.js → dist-BotSqB48.js} +2 -2
  68. package/dist/dist-BpquMd3k.js +5 -0
  69. package/dist/dist-BzJsqYfz.js +5 -0
  70. package/dist/{dist-D029TiHd.js → dist-Bz_sYWbr.js} +2 -2
  71. package/dist/{dist-D3ZI9nhS.js → dist-C1BYNeCR.js} +4 -4
  72. package/dist/{dist-Bc5pmZIw.js → dist-C5VC_yzu.js} +1 -1
  73. package/dist/dist-CA5ELXAf.js +6 -0
  74. package/dist/dist-CLBRs6Uv.js +5 -0
  75. package/dist/{dist-Dhk6FMb0.js → dist-CLJWPTX2.js} +3 -3
  76. package/dist/{dist-C34oIrQ9.js → dist-CLUtPrdy.js} +1 -1
  77. package/dist/dist-CStVCMbq.js +5 -0
  78. package/dist/{dist-B8RaFTRF.js → dist-CUCNs1ja.js} +2 -2
  79. package/dist/dist-CZRIEY3Y.js +8 -0
  80. package/dist/{dist-UcOPnRMa.js → dist-CcXxepx6.js} +3 -3
  81. package/dist/dist-CuUHbFD0.js +5 -0
  82. package/dist/{dist-B8BjrFUE.js → dist-Cy1WxgBD.js} +5 -5
  83. package/dist/{dist-WdPUFc56.js → dist-D4CewLk6.js} +1 -1
  84. package/dist/{dist-DMZNjfX4.js → dist-DRfcqpxJ.js} +2 -2
  85. package/dist/dist-DV7Iabxb.js +8 -0
  86. package/dist/{dist-usPCDYx8.js → dist-D_bzzWBm.js} +1 -1
  87. package/dist/{dist-BvCfQQQE.js → dist-DgnE8F-r.js} +1 -1
  88. package/dist/{dist-JEhxD_cn.js → dist-DhHh0jLg.js} +1 -1
  89. package/dist/{dist-DGAfI2rB.js → dist-DqAWR3CS.js} +2 -2
  90. package/dist/{dist--sWVZwjW.js → dist-Du8WkPuU.js} +1 -1
  91. package/dist/dist-DuEeHMvL.js +5 -0
  92. package/dist/{dist-BTyJtnNg.js → dist-DxvORzUR.js} +1 -1
  93. package/dist/{dist-B507mf_I.js → dist-RqXTaiir.js} +2 -2
  94. package/dist/{dist-Yrfc6L0I.js → dist-fQ0ViXGs.js} +3 -3
  95. package/dist/{dist-B4LJpMEg.js → dist-h2c8sZvT.js} +1 -1
  96. package/dist/{dist-C2ej4eOH.js → dist-luvabDEB.js} +2 -2
  97. package/dist/{dist-B52GXZbd.js → dist-p2qyWijU.js} +2 -2
  98. package/dist/{erDiagram-Q2GNP2WA--19X2kU5.js → erDiagram-Q2GNP2WA-BU-m41EQ.js} +10 -10
  99. package/dist/{error-banner-CVkfBUT3.js → error-banner-5bz0L9hS.js} +3 -3
  100. package/dist/{esm-CWp0KQeK.js → esm-BfhQmZjp.js} +4 -4
  101. package/dist/{esm-DjNnlmpf.js → esm-Duie8iU-.js} +23 -23
  102. package/dist/{extends-vAi97cpa.js → extends-BgdxCfYu.js} +6 -6
  103. package/dist/{flatten-CzBvFdvC.js → flatten-Bbw7g6-K.js} +1 -1
  104. package/dist/{flowDiagram-NV44I4VS-DQmWlo7f.js → flowDiagram-NV44I4VS-CRoXKjGq.js} +10 -10
  105. package/dist/{formats-Dsy9kkZu.js → formats-BiH6HX1V.js} +4 -4
  106. package/dist/{ganttDiagram-JELNMOA3-BOGXJ8Lk.js → ganttDiagram-JELNMOA3-7mq5f9cO.js} +7 -7
  107. package/dist/{gitGraph-G5XIXVHT-DGlbae5m.js → gitGraph-G5XIXVHT-DiniR35k.js} +3 -3
  108. package/dist/{gitGraphDiagram-V2S2FVAM-DjzxfF0P.js → gitGraphDiagram-V2S2FVAM-Dfuokq6w.js} +13 -13
  109. package/dist/{glide-data-editor-DucgdjRo.js → glide-data-editor-Ck-MRdns.js} +557 -557
  110. package/dist/{graphlib-CVPKjKCS.js → graphlib-Ns7y5crs.js} +5 -5
  111. package/dist/{hasIn-COs6vImh.js → hasIn-Deg7jl_j.js} +3 -3
  112. package/dist/{html-to-image-CpggM7u1.js → html-to-image-CTU_-PnW.js} +109 -109
  113. package/dist/{info-VBDWY6EO-D2lvLLw5.js → info-VBDWY6EO-DVZvGhkQ.js} +3 -3
  114. package/dist/{infoDiagram-HS3SLOUP-ChNufFsP.js → infoDiagram-HS3SLOUP-CEnzWruK.js} +13 -13
  115. package/dist/{input-D4kjoQUB.js → input-BwcGY_X1.js} +70 -67
  116. package/dist/{isEmpty-Dd8mx_WL.js → isEmpty-CJJMn-QP.js} +1 -1
  117. package/dist/{isSymbol-BvIfMnn6.js → isSymbol-CoUCgMCM.js} +1 -1
  118. package/dist/{journeyDiagram-XKPGCS4Q-BO_O4Ij1.js → journeyDiagram-XKPGCS4Q-8XYSU1GI.js} +3 -3
  119. package/dist/{kanban-definition-3W4ZIXB7-CPpiiiWk.js → kanban-definition-3W4ZIXB7--9pT9z1R.js} +7 -7
  120. package/dist/{label-BLqV33b1.js → label-LWtdw5i8.js} +3 -3
  121. package/dist/{linear-2NnK4cxi.js → linear-B5-AFRiR.js} +2 -2
  122. package/dist/{loader-Dr8Qem8p.js → loader-BWLPpjKK.js} +2 -2
  123. package/dist/main.js +1688 -1569
  124. package/dist/{memoize-C9ltv0Cw.js → memoize-BOtf2yFf.js} +1 -1
  125. package/dist/{merge-CHn7Yx0N.js → merge-Be1CqGnU.js} +1 -1
  126. package/dist/mermaid-4DMBBIKO-DIdL224_.js +6 -0
  127. package/dist/{mermaid-DO-Daq7u.js → mermaid-YK4c8MNC.js} +44 -44
  128. package/dist/{mermaid-parser.core-DreccfmS.js → mermaid-parser.core-C3XRsazI.js} +8 -8
  129. package/dist/{min-BNz2lZfk.js → min-Dtgc8txR.js} +4 -4
  130. package/dist/{mindmap-definition-VGOIOE7T-CC1_Vl0f.js → mindmap-definition-VGOIOE7T-B-4mnfFG.js} +9 -9
  131. package/dist/{now-Sgq5m3D-.js → now-Ch98bJO_.js} +2 -2
  132. package/dist/{number-overlay-editor-CpKi64Fy.js → number-overlay-editor-D-a0qCT8.js} +1 -1
  133. package/dist/{once-rJImu7SE.js → once-DPuqGUeo.js} +1 -1
  134. package/dist/{packet-DYOGHKS2-CmWtF3uO.js → packet-DYOGHKS2-34raHOiB.js} +3 -3
  135. package/dist/{pick-CRAXxDYn.js → pick-D1Qo8s2C.js} +4 -4
  136. package/dist/{pie-VRWISCQL-B6u8vus8.js → pie-VRWISCQL-BaLlzZa3.js} +3 -3
  137. package/dist/{pieDiagram-ADFJNKIX-Di34MOFQ.js → pieDiagram-ADFJNKIX-Cr3cNpZY.js} +15 -15
  138. package/dist/{precisionRound-CnHPY_5v.js → precisionRound-Tqb4mg-H.js} +1 -1
  139. package/dist/{process-output-X8TR20AK.js → process-output-CVDHJqo6.js} +36 -28
  140. package/dist/{quadrantDiagram-AYHSOK5B-B9kVk1ny.js → quadrantDiagram-AYHSOK5B-BuNL8Q93.js} +4 -4
  141. package/dist/{radar-ZZBFDIW7-XAmXSa8s.js → radar-ZZBFDIW7-Ci7bfoZa.js} +3 -3
  142. package/dist/{react-vega-Dh6-UKKe.js → react-vega-B0sAlDTL.js} +9 -9
  143. package/dist/react-vega-B6ncY2Tp.js +9 -0
  144. package/dist/{requirementDiagram-UZGBJVZJ-BxGfGYEx.js → requirementDiagram-UZGBJVZJ-BG2lLUN1.js} +9 -9
  145. package/dist/{reveal-component-Cj2fUG1Q.js → reveal-component-Dskn4JVk.js} +31 -31
  146. package/dist/{sankeyDiagram-TZEHDZUN-D09PBJ-n.js → sankeyDiagram-TZEHDZUN-DMal8sps.js} +3 -3
  147. package/dist/{sequenceDiagram-WL72ISMW-t_Dpemj0.js → sequenceDiagram-WL72ISMW-DT6Tk-Eo.js} +4 -4
  148. package/dist/{spec-hVaaZsY5.js → spec-CyLiCjSf.js} +4 -4
  149. package/dist/{stateDiagram-FKZM4ZOC-B18gTP_j.js → stateDiagram-FKZM4ZOC-CB_lodq3.js} +12 -12
  150. package/dist/{stateDiagram-v2-4FDKWEC3-B6e_t14A.js → stateDiagram-v2-4FDKWEC3-E0RGjKsm.js} +10 -10
  151. package/dist/stex-KfRnSHzF.js +4 -0
  152. package/dist/{strings-BiIhGaI8.js → strings-Bu3vlb6W.js} +7 -7
  153. package/dist/style.css +1 -1
  154. package/dist/{swiper-component-DlD2GU2g.js → swiper-component-B2t5sN1q.js} +3 -3
  155. package/dist/{time-C1SGcFMH.js → time-CsmIF9YZ.js} +3 -3
  156. package/dist/{timeline-definition-IT6M3QCI-DJnh1ks5.js → timeline-definition-IT6M3QCI-NfSKRvH0.js} +2 -2
  157. package/dist/{toDate-CIpC_34u.js → toDate-DNWCUEQp.js} +5 -5
  158. package/dist/{tooltip-DRaMBu06.js → tooltip-C5FYOpQc.js} +4 -4
  159. package/dist/{treemap-GDKQZRPO-Du95DV6u.js → treemap-GDKQZRPO-Cl6OQh8D.js} +3 -3
  160. package/dist/{types-Dzuoc3LN.js → types-CVvp1fKr.js} +2 -9
  161. package/dist/{useAsyncData-C56Khv_R.js → useAsyncData-xWFWzCee.js} +2 -2
  162. package/dist/{useDateFormatter-B_9k85Ex.js → useDateFormatter-BA4FCquG.js} +2 -2
  163. package/dist/{useDeepCompareMemoize-Dt98v2ua.js → useDeepCompareMemoize-DSChED4g.js} +1 -1
  164. package/dist/{useIframeCapabilities-BkYHTrss.js → useIframeCapabilities-C4JTXTIh.js} +1 -1
  165. package/dist/{useLifecycle-BF6-z62y.js → useLifecycle-B81PFEja.js} +4 -4
  166. package/dist/{useTheme-DykuNHR2.js → useTheme-EmVyK9N9.js} +23 -21
  167. package/dist/{vega-component-cSdqoAxe.js → vega-component-BCunE3-9.js} +18 -18
  168. package/dist/{vega-loader.browser-3_z8GoFC.js → vega-loader.browser-CZ-J8Py3.js} +3 -3
  169. package/dist/{xychartDiagram-PRI3JC2R-Dk2d_bX0.js → xychartDiagram-PRI3JC2R-BvwftqMA.js} +9 -9
  170. package/dist/{zod-BWkcDORu.js → zod-CoBiJ5v4.js} +3 -3
  171. package/package.json +1 -1
  172. package/src/components/app-config/user-config-form.tsx +36 -0
  173. package/src/components/chat/__tests__/chat-utils.test.ts +269 -0
  174. package/src/components/chat/chat-utils.ts +14 -58
  175. package/src/components/data-table/TableBottomBar.tsx +27 -6
  176. package/src/components/data-table/TableTopBar.tsx +7 -1
  177. package/src/components/data-table/__tests__/TableBottomBar.test.tsx +73 -0
  178. package/src/components/data-table/__tests__/column-explorer.test.tsx +128 -0
  179. package/src/components/data-table/__tests__/column-header.test.tsx +110 -277
  180. package/src/components/data-table/__tests__/data-table.test.tsx +52 -1
  181. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +33 -0
  182. package/src/components/data-table/__tests__/filter-pill-editor.test.tsx +75 -38
  183. package/src/components/data-table/__tests__/filter-pills.test.tsx +287 -0
  184. package/src/components/data-table/__tests__/filter-test-utils.ts +47 -0
  185. package/src/components/data-table/__tests__/filters.test.ts +5 -5
  186. package/src/components/data-table/__tests__/header-items.test.tsx +47 -1
  187. package/src/components/data-table/__tests__/useColumnVisibility.test.ts +42 -0
  188. package/src/components/data-table/add-filter-button.tsx +85 -0
  189. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +98 -26
  190. package/src/components/data-table/column-header.tsx +94 -691
  191. package/src/components/data-table/columns.tsx +3 -4
  192. package/src/components/data-table/context-menu.tsx +26 -12
  193. package/src/components/data-table/data-table.tsx +125 -56
  194. package/src/components/data-table/date-filter-inputs.tsx +13 -10
  195. package/src/components/data-table/export-actions.tsx +17 -6
  196. package/src/components/data-table/filter-by-values-picker.tsx +13 -19
  197. package/src/components/data-table/filter-editor-context.tsx +34 -0
  198. package/src/components/data-table/filter-pill-editor.tsx +152 -175
  199. package/src/components/data-table/filter-pills.tsx +190 -153
  200. package/src/components/data-table/filters/builders.ts +102 -0
  201. package/src/components/data-table/filters/defaults.ts +31 -0
  202. package/src/components/data-table/filters/format.ts +131 -0
  203. package/src/components/data-table/filters/guards.ts +51 -0
  204. package/src/components/data-table/filters/index.ts +7 -0
  205. package/src/components/data-table/filters/operators.ts +76 -0
  206. package/src/components/data-table/filters/serialize.ts +186 -0
  207. package/src/components/data-table/filters/types.ts +33 -0
  208. package/src/components/data-table/header-items.tsx +25 -85
  209. package/src/components/data-table/hooks/use-column-visibility.ts +56 -0
  210. package/src/components/data-table/pagination.tsx +16 -3
  211. package/src/components/data-table/table-explorer-panel/table-explorer-panel.tsx +16 -6
  212. package/src/components/data-table/value-chips.tsx +52 -0
  213. package/src/components/dependency-graph/minimap-content.tsx +2 -2
  214. package/src/components/editor/errors/mangled-local-chip.tsx +50 -0
  215. package/src/components/editor/output/MarimoErrorOutput.tsx +110 -27
  216. package/src/components/editor/output/MarimoTracebackOutput.tsx +51 -34
  217. package/src/components/ui/number-field.tsx +13 -1
  218. package/src/core/cells/__tests__/actions.test.ts +48 -0
  219. package/src/core/cells/actions.ts +5 -6
  220. package/src/core/codemirror/__tests__/setup.test.ts +29 -0
  221. package/src/core/codemirror/cells/traceback-decorations.ts +1 -1
  222. package/src/core/codemirror/cm.ts +3 -2
  223. package/src/core/config/__tests__/config-schema.test.ts +2 -0
  224. package/src/core/config/config-schema.ts +1 -0
  225. package/src/css/app/Cell.css +0 -1
  226. package/src/plugins/impl/DataTablePlugin.tsx +94 -33
  227. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +1 -0
  228. package/src/plugins/impl/chat/ChatPlugin.tsx +7 -1
  229. package/src/plugins/impl/chat/__tests__/chat-ui.test.ts +278 -0
  230. package/src/plugins/impl/chat/chat-ui.tsx +106 -59
  231. package/src/plugins/impl/chat/types.ts +5 -0
  232. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +8 -6
  233. package/src/stories/dataframe.stories.tsx +1 -0
  234. package/src/utils/__tests__/local-variables.test.ts +132 -0
  235. package/src/utils/dates.ts +39 -0
  236. package/src/utils/local-variables.ts +67 -0
  237. package/dist/ErrorBoundary-D3wrPNma.js +0 -167
  238. package/dist/architecture-7HQA4BMR-CS9jOrqM.js +0 -6
  239. package/dist/assets/__vite-browser-external-CAdMKBac.js +0 -1
  240. package/dist/dist-21ButRCu.js +0 -8
  241. package/dist/dist-B--tLnAP.js +0 -5
  242. package/dist/dist-BoHGySTM.js +0 -5
  243. package/dist/dist-ByAz19Qc.js +0 -5
  244. package/dist/dist-C1Ap5CYU.js +0 -5
  245. package/dist/dist-C93EysN4.js +0 -5
  246. package/dist/dist-CY-lVor6.js +0 -8
  247. package/dist/dist-CYDuv4bR.js +0 -8
  248. package/dist/dist-Cfo5EE2t.js +0 -6
  249. package/dist/dist-CjivSDvN.js +0 -5
  250. package/dist/dist-Cqwx-MH7.js +0 -5
  251. package/dist/dist-DbpcoFAV.js +0 -6
  252. package/dist/dist-FUNenbiQ.js +0 -5
  253. package/dist/dist-zhSud5X3.js +0 -8
  254. package/dist/mermaid-4DMBBIKO-B7VQMwJx.js +0 -6
  255. package/dist/react-vega-Cavbrg4l.js +0 -9
  256. package/dist/stex-ChDHQs3R.js +0 -4
  257. package/src/components/data-table/__tests__/column-header.test.ts +0 -65
  258. package/src/components/data-table/filters.ts +0 -386
  259. /package/dist/{_baseFor-BGiY-cm1.js → _baseFor-4jw-lnCC.js} +0 -0
  260. /package/dist/{clsx-CyyyQ8Ue.js → clsx-CIWA5tNO.js} +0 -0
  261. /package/dist/{defaultLocale-DoeErsX2.js → defaultLocale-BoHTsDG6.js} +0 -0
  262. /package/dist/{defaultLocale-BpsHxBd7.js → defaultLocale-u-3osm0P.js} +0 -0
  263. /package/dist/{dist-CCADb07R.js → dist-DNdhYsgW.js} +0 -0
  264. /package/dist/{emotion-is-prop-valid.esm-DtW2o230.js → emotion-is-prop-valid.esm-DzSb5hsH.js} +0 -0
  265. /package/dist/{invariant-UcGKQEhF.js → invariant-wRzNXIsJ.js} +0 -0
  266. /package/dist/{jsx-runtime-COBk7ree.js → jsx-runtime-DebpN0FN.js} +0 -0
  267. /package/dist/{main-CThhXnXU.js → main-Tj_-QTyF.js} +0 -0
  268. /package/dist/{micromark-factory-space-CwHmg6iz.js → micromark-factory-space-DF2w36zS.js} +0 -0
  269. /package/dist/{ordinal-B43ZeR68.js → ordinal-ArJavP1Q.js} +0 -0
  270. /package/dist/{purify.es-DT70lfR0.js → purify.es-H92eMd9-.js} +0 -0
  271. /package/dist/{range-BOiA8qqU.js → range-C-rmrM1O.js} +0 -0
  272. /package/dist/{react-dom-BWRJ_g_k.js → react-dom-BTJzcVJ9.js} +0 -0
  273. /package/dist/{stex-DrxP7bb3.js → stex-BIsgBmK4.js} +0 -0
@@ -182,6 +182,7 @@ export const basicBundle = (opts: CodeMirrorSetupOpts): Extension[] => {
182
182
  diagnosticsConfig,
183
183
  } = opts;
184
184
  const placeholderType = getPlaceholderType(opts);
185
+ const autoClosePairs = completionConfig.auto_close_pairs !== false;
185
186
 
186
187
  return [
187
188
  ///// View
@@ -208,10 +209,10 @@ export const basicBundle = (opts: CodeMirrorSetupOpts): Extension[] => {
208
209
  copilotBundle(completionConfig),
209
210
  foldGutter(),
210
211
  stringsAutoCloseBraces(),
211
- closeBrackets(),
212
+ autoClosePairs ? closeBrackets() : [],
212
213
  completionKeymap(acceptCompletionOnEnter),
213
214
  // to avoid clash with charDeleteBackward keymap
214
- Prec.high(keymap.of(closeBracketsKeymap)),
215
+ autoClosePairs ? Prec.high(keymap.of(closeBracketsKeymap)) : [],
215
216
  bracketMatching(),
216
217
  indentOnInput(),
217
218
  indentUnit.of(" "),
@@ -56,6 +56,7 @@ test("default UserConfig - empty", () => {
56
56
  },
57
57
  "completion": {
58
58
  "activate_on_typing": true,
59
+ "auto_close_pairs": true,
59
60
  "copilot": false,
60
61
  "signature_hint_on_typing": false,
61
62
  },
@@ -127,6 +128,7 @@ test("default UserConfig - one level", () => {
127
128
  },
128
129
  "completion": {
129
130
  "activate_on_typing": true,
131
+ "auto_close_pairs": true,
130
132
  "copilot": false,
131
133
  "signature_hint_on_typing": false,
132
134
  },
@@ -75,6 +75,7 @@ export const UserConfigSchema = z
75
75
  .object({
76
76
  activate_on_typing: z.boolean().prefault(true),
77
77
  signature_hint_on_typing: z.boolean().prefault(false),
78
+ auto_close_pairs: z.boolean().prefault(true),
78
79
  copilot: z
79
80
  .union([z.boolean(), z.enum(["github", "codeium", "custom"])])
80
81
  .prefault(false)
@@ -214,7 +214,6 @@
214
214
  &.error-outline,
215
215
  &.error-outline:focus-within {
216
216
  box-shadow: 8px 8px 0 0 color-mix(in srgb, var(--error), transparent 80%);
217
- background-color: var(--red-2);
218
217
  }
219
218
 
220
219
  /* Needs Run */
@@ -8,8 +8,9 @@ import type {
8
8
  PaginationState,
9
9
  RowSelectionState,
10
10
  SortingState,
11
+ Table as TanstackTable,
11
12
  } from "@tanstack/react-table";
12
- import { Provider } from "jotai";
13
+ import { Provider, useAtomValue } from "jotai";
13
14
  import { Table2Icon } from "lucide-react";
14
15
  import type { JSX } from "react";
15
16
  import React, {
@@ -28,6 +29,7 @@ import { TablePanel } from "@/components/data-table/charts/charts";
28
29
  import { hasChart } from "@/components/data-table/charts/storage";
29
30
  import { ColumnChartSpecModel } from "@/components/data-table/column-summary/chart-spec-model";
30
31
  import { ColumnChartContext } from "@/components/data-table/column-summary/column-summary";
32
+ import { downloadSizeLimitAtom } from "@/components/data-table/download-policy/atoms";
31
33
  import { filtersToFilterGroup } from "@/components/data-table/filters";
32
34
  import { usePanelOwnership } from "@/components/data-table/hooks/use-panel-ownership";
33
35
  import { LoadingTable } from "@/components/data-table/loading-table";
@@ -190,11 +192,11 @@ interface Data<T> {
190
192
  fieldTypes?: FieldTypesWithExternalType | null;
191
193
  freezeColumnsLeft?: string[];
192
194
  freezeColumnsRight?: string[];
195
+ hiddenColumns?: string[];
193
196
  textJustifyColumns?: Record<string, "left" | "center" | "right">;
194
197
  wrappedColumns?: string[];
195
198
  headerTooltip?: Record<string, string>;
196
199
  totalColumns: number;
197
- sizeBytes?: number | null;
198
200
  maxColumns: number | "all";
199
201
  hasStableRowId: boolean;
200
202
  lazy: boolean;
@@ -221,12 +223,14 @@ type DataTableFunctions = {
221
223
  cell_styles?: CellStyleState | null;
222
224
  cell_hover_texts?: Record<string, Record<string, string | null>> | null;
223
225
  raw_data?: TableData<T> | null;
224
- size_bytes?: number | null;
225
226
  }>;
226
227
  get_data_url?: GetDataUrl;
227
228
  get_row_ids?: GetRowIds;
228
229
  calculate_top_k_rows?: CalculateTopKRows;
229
230
  preview_column?: PreviewColumn;
231
+ get_size_bytes: (opts: Record<string, never>) => Promise<{
232
+ size_bytes?: number | null;
233
+ }>;
230
234
  };
231
235
 
232
236
  type S = (number | string | { rowId: string; columnName?: string })[];
@@ -265,6 +269,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
265
269
  rowHeaders: columnToFieldTypesSchema,
266
270
  freezeColumnsLeft: z.array(z.string()).optional(),
267
271
  freezeColumnsRight: z.array(z.string()).optional(),
272
+ hiddenColumns: z.array(z.string()).optional(),
268
273
  textJustifyColumns: z
269
274
  .record(z.string(), z.enum(["left", "center", "right"]))
270
275
  .optional(),
@@ -272,7 +277,6 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
272
277
  headerTooltip: z.record(z.string(), z.string()).optional(),
273
278
  fieldTypes: columnToFieldTypesSchema.nullish(),
274
279
  totalColumns: z.number(),
275
- sizeBytes: z.number().nullish(),
276
280
  maxColumns: z.union([z.number(), z.literal("all")]).default("all"),
277
281
  hasStableRowId: z.boolean().default(false),
278
282
  maxHeight: z.number().optional(),
@@ -330,7 +334,6 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
330
334
  .nullable(),
331
335
  cell_hover_texts: cellHoverTextSchema.nullable(),
332
336
  raw_data: z.union([z.string(), z.array(z.looseObject({}))]).nullish(),
333
- size_bytes: z.number().nullish(),
334
337
  }),
335
338
  ),
336
339
  get_row_ids: rpc.input(z.object({}).passthrough()).output(
@@ -362,6 +365,9 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
362
365
  stats: columnStats.nullable(),
363
366
  }),
364
367
  ),
368
+ get_size_bytes: rpc
369
+ .input(z.object({}))
370
+ .output(z.object({ size_bytes: z.number().nullish() })),
365
371
  })
366
372
  .renderer((props) => {
367
373
  return (
@@ -536,7 +542,6 @@ export const LoadingDataTableComponent = memo(
536
542
  rows: T[];
537
543
  rawRows?: T[];
538
544
  totalRows: number | TooManyRows;
539
- sizeBytes?: number | null;
540
545
  cellStyles: CellStyleState | undefined | null;
541
546
  cellHoverTexts?: Record<string, Record<string, string | null>> | null;
542
547
  }>(async () => {
@@ -553,7 +558,6 @@ export const LoadingDataTableComponent = memo(
553
558
  let tableData = props.data;
554
559
  let rawTableData: TableData<T> | undefined | null = props.rawData;
555
560
  let totalRows = props.totalRows;
556
- let sizeBytes = props.sizeBytes ?? null;
557
561
  let cellStyles = props.cellStyles;
558
562
  let cellHoverTexts = props.cellHoverTexts;
559
563
 
@@ -597,7 +601,6 @@ export const LoadingDataTableComponent = memo(
597
601
  tableData = searchResults.data;
598
602
  rawTableData = searchResults.raw_data;
599
603
  totalRows = searchResults.total_rows;
600
- sizeBytes = searchResults.size_bytes ?? null;
601
604
  cellStyles = searchResults.cell_styles || {};
602
605
  cellHoverTexts = searchResults.cell_hover_texts || {};
603
606
  }
@@ -610,7 +613,6 @@ export const LoadingDataTableComponent = memo(
610
613
  rows: tableData,
611
614
  rawRows: rawData,
612
615
  totalRows: totalRows,
613
- sizeBytes,
614
616
  cellStyles,
615
617
  cellHoverTexts,
616
618
  };
@@ -622,7 +624,6 @@ export const LoadingDataTableComponent = memo(
622
624
  useDeepCompareMemoize(props.fieldTypes),
623
625
  props.data,
624
626
  props.totalRows,
625
- props.sizeBytes,
626
627
  props.lazy,
627
628
  props.cellHoverTexts,
628
629
  props.cellStyles,
@@ -630,6 +631,34 @@ export const LoadingDataTableComponent = memo(
630
631
  paginationState.pageIndex,
631
632
  ]);
632
633
 
634
+ const policy = useAtomValue(downloadSizeLimitAtom);
635
+ const { data: sizeBytesData, isPending: sizeBytesPending } = useAsyncData<
636
+ number | null
637
+ >(async () => {
638
+ if (
639
+ !policy ||
640
+ !props.showDownload ||
641
+ props.lazy ||
642
+ props.totalRows === 0
643
+ ) {
644
+ return null;
645
+ }
646
+ const result = await props.get_size_bytes({});
647
+ return result.size_bytes ?? null;
648
+ }, [
649
+ policy,
650
+ props.showDownload,
651
+ props.get_size_bytes,
652
+ props.lazy,
653
+ props.totalRows,
654
+ searchQuery,
655
+ useDeepCompareMemoize(filters),
656
+ useDeepCompareMemoize(sorting),
657
+ ]);
658
+ const sizeBytes = sizeBytesData ?? null;
659
+ const sizeBytesIsLoading =
660
+ !!policy && props.showDownload && sizeBytesPending;
661
+
633
662
  const getRow = useCallback(
634
663
  async (rowId: number) => {
635
664
  const sortArgs =
@@ -737,7 +766,8 @@ export const LoadingDataTableComponent = memo(
737
766
  setFilters={setFilters}
738
767
  reloading={isFetching && !isPending}
739
768
  totalRows={data?.totalRows ?? props.totalRows}
740
- sizeBytes={data?.sizeBytes ?? props.sizeBytes ?? null}
769
+ sizeBytes={sizeBytes}
770
+ sizeBytesIsLoading={sizeBytesIsLoading}
741
771
  paginationState={paginationState}
742
772
  setPaginationState={setPaginationState}
743
773
  cellStyles={data?.cellStyles ?? props.cellStyles}
@@ -785,6 +815,7 @@ const DataTableComponent = ({
785
815
  rawData,
786
816
  totalRows,
787
817
  sizeBytes,
818
+ sizeBytesIsLoading,
788
819
  maxColumns,
789
820
  pagination,
790
821
  selection,
@@ -814,6 +845,7 @@ const DataTableComponent = ({
814
845
  reloading,
815
846
  freezeColumnsLeft,
816
847
  freezeColumnsRight,
848
+ hiddenColumns,
817
849
  textJustifyColumns,
818
850
  wrappedColumns,
819
851
  headerTooltip,
@@ -835,6 +867,8 @@ const DataTableComponent = ({
835
867
  rawData?: unknown[];
836
868
  columnSummaries?: ColumnSummaries;
837
869
  getRow: (rowIdx: number) => Promise<GetRowResult>;
870
+ sizeBytes?: number | null;
871
+ sizeBytesIsLoading?: boolean;
838
872
  }): JSX.Element => {
839
873
  const id = useId();
840
874
  const [viewedRowIdx, setViewedRowIdx] = useState(0);
@@ -1007,6 +1041,52 @@ const DataTableComponent = ({
1007
1041
  const isInVscode = isInVscodeExtension();
1008
1042
  const isIslandsMode = isIslands();
1009
1043
 
1044
+ const renderTableExplorerPanel = useMemo(() => {
1045
+ if (!isAnyPanelOpen || !(showRowExplorer || canShowColumnExplorer)) {
1046
+ return undefined;
1047
+ }
1048
+ return (table: TanstackTable<unknown>) => (
1049
+ <ContextAwarePanelItem>
1050
+ <TableExplorerPanel
1051
+ rowIdx={viewedRowIdx}
1052
+ setRowIdx={setViewedRow}
1053
+ totalRows={totalRows}
1054
+ fieldTypes={memoizedUnclampedFieldTypes}
1055
+ getRow={getRow}
1056
+ isSelectable={isSelectable}
1057
+ isRowSelected={Boolean(rowSelection[viewedRowIdx])}
1058
+ handleRowSelectionChange={handleRowSelectionChange}
1059
+ previewColumn={preview_column}
1060
+ totalColumns={totalColumns}
1061
+ tableId={id}
1062
+ table={table}
1063
+ showRowExplorer={showRowExplorer && !isInVscode}
1064
+ showColumnExplorer={canShowColumnExplorer && !isInVscode}
1065
+ activeTab={panelType}
1066
+ onTabChange={setPanelType}
1067
+ />
1068
+ </ContextAwarePanelItem>
1069
+ );
1070
+ }, [
1071
+ isAnyPanelOpen,
1072
+ showRowExplorer,
1073
+ canShowColumnExplorer,
1074
+ viewedRowIdx,
1075
+ setViewedRow,
1076
+ totalRows,
1077
+ memoizedUnclampedFieldTypes,
1078
+ getRow,
1079
+ isSelectable,
1080
+ rowSelection,
1081
+ handleRowSelectionChange,
1082
+ preview_column,
1083
+ totalColumns,
1084
+ id,
1085
+ isInVscode,
1086
+ panelType,
1087
+ setPanelType,
1088
+ ]);
1089
+
1010
1090
  return (
1011
1091
  <>
1012
1092
  {/* When the totalRows is "too_many" and the pageSize is the same as the
@@ -1032,28 +1112,6 @@ const DataTableComponent = ({
1032
1112
  </Banner>
1033
1113
  )}
1034
1114
 
1035
- {isAnyPanelOpen && (showRowExplorer || canShowColumnExplorer) && (
1036
- <ContextAwarePanelItem>
1037
- <TableExplorerPanel
1038
- rowIdx={viewedRowIdx}
1039
- setRowIdx={setViewedRow}
1040
- totalRows={totalRows}
1041
- fieldTypes={memoizedUnclampedFieldTypes}
1042
- getRow={getRow}
1043
- isSelectable={isSelectable}
1044
- isRowSelected={Boolean(rowSelection[viewedRowIdx])}
1045
- handleRowSelectionChange={handleRowSelectionChange}
1046
- previewColumn={preview_column}
1047
- totalColumns={totalColumns}
1048
- tableId={id}
1049
- showRowExplorer={showRowExplorer && !isInVscode}
1050
- showColumnExplorer={canShowColumnExplorer && !isInVscode}
1051
- activeTab={panelType}
1052
- onTabChange={setPanelType}
1053
- />
1054
- </ContextAwarePanelItem>
1055
- )}
1056
-
1057
1115
  <ColumnChartContext value={chartSpecModel}>
1058
1116
  <Labeled label={label} align="top" fullWidth={true}>
1059
1117
  <DataTable
@@ -1065,6 +1123,7 @@ const DataTableComponent = ({
1065
1123
  sorting={sorting}
1066
1124
  totalRows={totalRows}
1067
1125
  sizeBytes={sizeBytes}
1126
+ sizeBytesIsLoading={sizeBytesIsLoading}
1068
1127
  totalColumns={totalColumns}
1069
1128
  manualSorting={true}
1070
1129
  setSorting={setSorting}
@@ -1090,6 +1149,7 @@ const DataTableComponent = ({
1090
1149
  onRowSelectionChange={handleRowSelectionChange}
1091
1150
  freezeColumnsLeft={freezeColumnsLeft}
1092
1151
  freezeColumnsRight={freezeColumnsRight}
1152
+ hiddenColumns={hiddenColumns}
1093
1153
  onCellSelectionChange={handleCellSelectionChange}
1094
1154
  getRowIds={get_row_ids}
1095
1155
  toggleDisplayHeader={toggleDisplayHeader}
@@ -1108,6 +1168,7 @@ const DataTableComponent = ({
1108
1168
  isAnyPanelOpen={isAnyPanelOpen}
1109
1169
  viewedRowIdx={viewedRowIdx}
1110
1170
  onViewedRowChange={(rowIdx) => setViewedRowIdx(rowIdx)}
1171
+ renderTableExplorerPanel={renderTableExplorerPanel}
1111
1172
  />
1112
1173
  </Labeled>
1113
1174
  </ColumnChartContext>
@@ -107,6 +107,7 @@ describe("LoadingDataTableComponent", () => {
107
107
  }),
108
108
  get_data_url: vi.fn() as GetDataUrl,
109
109
  get_row_ids: vi.fn() as GetRowIds,
110
+ get_size_bytes: vi.fn().mockResolvedValue({ size_bytes: null }),
110
111
  };
111
112
 
112
113
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
@@ -6,7 +6,7 @@ import { z } from "zod";
6
6
  import { createPlugin } from "@/plugins/core/builder";
7
7
  import { rpc } from "@/plugins/core/rpc";
8
8
  import { Arrays } from "@/utils/arrays";
9
- import type { SendMessageRequest } from "./types";
9
+ import type { CancelPromptRequest, SendMessageRequest } from "./types";
10
10
 
11
11
  const LazyChatbot = React.lazy(() =>
12
12
  import("./chat-ui").then((m) => ({ default: m.Chatbot })),
@@ -18,6 +18,7 @@ export type PluginFunctions = {
18
18
  delete_chat_history: (req: {}) => Promise<null>;
19
19
  delete_chat_message: (req: { index: number }) => Promise<null>;
20
20
  send_prompt: (req: SendMessageRequest) => Promise<unknown>;
21
+ cancel_prompt: (req: CancelPromptRequest) => Promise<null>;
21
22
  };
22
23
 
23
24
  const messageSchema = z.array(
@@ -65,11 +66,15 @@ export const ChatPlugin = createPlugin<{ messages: UIMessage[] }>(
65
66
  send_prompt: rpc
66
67
  .input(
67
68
  z.object({
69
+ request_id: z.string(),
68
70
  messages: messageSchema,
69
71
  config: configSchema,
70
72
  }),
71
73
  )
72
74
  .output(z.unknown()),
75
+ cancel_prompt: rpc
76
+ .input(z.object({ request_id: z.string() }))
77
+ .output(z.null()),
73
78
  })
74
79
  .renderer((props) => (
75
80
  <Suspense>
@@ -84,6 +89,7 @@ export const ChatPlugin = createPlugin<{ messages: UIMessage[] }>(
84
89
  delete_chat_history={props.functions.delete_chat_history}
85
90
  delete_chat_message={props.functions.delete_chat_message}
86
91
  send_prompt={props.functions.send_prompt}
92
+ cancel_prompt={props.functions.cancel_prompt}
87
93
  value={props.value?.messages || Arrays.EMPTY}
88
94
  setValue={(messages) => props.setValue({ messages })}
89
95
  host={props.host}
@@ -0,0 +1,278 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { UIMessageChunk } from "ai";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { routeIncomingChatChunk } from "../chat-ui";
6
+
7
+ /**
8
+ * The stale-chunk filter prevents chunks from an aborted run from being enqueued into a new run's stream.
9
+ *
10
+ * It triggers when:
11
+ * 1. User sends a prompt (request_id = OLD), kernel starts emitting chunks
12
+ * 2. User clicks Stop — frontend tears down its controller, fires cancel_prompt
13
+ * 3. Kernel hasn't received the cancel yet and is still emitting chunks
14
+ * 4. User sends a new prompt (request_id = NEW), new controller opens
15
+ * 5. Late chunks tagged OLD arrive after NEW's controller is in place
16
+ */
17
+
18
+ const makeChunk = (opts: {
19
+ messageId: string;
20
+ content: unknown;
21
+ isFinal?: boolean;
22
+ }): Parameters<typeof routeIncomingChatChunk>[0] => ({
23
+ type: "stream_chunk",
24
+ message_id: opts.messageId,
25
+ content: opts.content as UIMessageChunk | null,
26
+ is_final: opts.isFinal ?? false,
27
+ });
28
+
29
+ const makeRefs = () => ({
30
+ controllerRef: {
31
+ current: null as ReadableStreamDefaultController<UIMessageChunk> | null,
32
+ },
33
+ activeRequestIdRef: { current: null as string | null },
34
+ });
35
+
36
+ const makeMockController = () => {
37
+ return {
38
+ enqueue: vi.fn(),
39
+ close: vi.fn(),
40
+ error: vi.fn(),
41
+ desiredSize: 0,
42
+ } as unknown as ReadableStreamDefaultController<UIMessageChunk> & {
43
+ enqueue: ReturnType<typeof vi.fn>;
44
+ close: ReturnType<typeof vi.fn>;
45
+ };
46
+ };
47
+
48
+ describe("routeIncomingChatChunk", () => {
49
+ it("drops chunks when there is no active controller", () => {
50
+ const refs = makeRefs();
51
+
52
+ const result = routeIncomingChatChunk(
53
+ makeChunk({
54
+ messageId: "req-A",
55
+ content: { type: "text-delta", id: "t1", delta: "hi" },
56
+ }),
57
+ refs,
58
+ );
59
+
60
+ expect(result).toBe("dropped-no-controller");
61
+ });
62
+
63
+ it("enqueues chunks that match the active request_id", () => {
64
+ const refs = makeRefs();
65
+ const controller = makeMockController();
66
+ refs.controllerRef.current = controller;
67
+ refs.activeRequestIdRef.current = "req-A";
68
+
69
+ const chunk = { type: "text-delta", id: "t1", delta: "hi" } as const;
70
+ const result = routeIncomingChatChunk(
71
+ makeChunk({ messageId: "req-A", content: chunk }),
72
+ refs,
73
+ );
74
+
75
+ expect(result).toBe("enqueued");
76
+ expect(controller.enqueue).toHaveBeenCalledWith(chunk);
77
+ expect(controller.close).not.toHaveBeenCalled();
78
+ });
79
+
80
+ it("closes the controller and clears refs on is_final", () => {
81
+ const refs = makeRefs();
82
+ const controller = makeMockController();
83
+ refs.controllerRef.current = controller;
84
+ refs.activeRequestIdRef.current = "req-A";
85
+
86
+ const result = routeIncomingChatChunk(
87
+ makeChunk({ messageId: "req-A", content: null, isFinal: true }),
88
+ refs,
89
+ );
90
+
91
+ expect(result).toBe("closed");
92
+ expect(controller.close).toHaveBeenCalledTimes(1);
93
+ expect(refs.controllerRef.current).toBeNull();
94
+ expect(refs.activeRequestIdRef.current).toBeNull();
95
+ });
96
+
97
+ it("drops chunks whose message_id does not match the active run", () => {
98
+ // Simulates the bug: kernel hasn't received cancel for OLD yet but the
99
+ // user has already started a NEW run. A reasoning-delta for OLD arrives
100
+ // here; it must not be enqueued into NEW's stream.
101
+ const refs = makeRefs();
102
+ const controller = makeMockController();
103
+ refs.controllerRef.current = controller;
104
+ refs.activeRequestIdRef.current = "req-NEW";
105
+
106
+ const staleChunk = {
107
+ type: "reasoning-delta",
108
+ id: "r-old",
109
+ delta: "...",
110
+ } as const;
111
+ const result = routeIncomingChatChunk(
112
+ makeChunk({ messageId: "req-OLD", content: staleChunk }),
113
+ refs,
114
+ );
115
+
116
+ expect(result).toBe("dropped-stale");
117
+ expect(controller.enqueue).not.toHaveBeenCalled();
118
+ expect(controller.close).not.toHaveBeenCalled();
119
+ expect(refs.activeRequestIdRef.current).toBe("req-NEW");
120
+ });
121
+
122
+ it("drops is_final from a stale run without closing the active stream", () => {
123
+ // Belt-and-suspenders: an `is_final` for OLD that races in after NEW
124
+ // started must not tear down NEW's controller.
125
+ const refs = makeRefs();
126
+ const controller = makeMockController();
127
+ refs.controllerRef.current = controller;
128
+ refs.activeRequestIdRef.current = "req-NEW";
129
+
130
+ const result = routeIncomingChatChunk(
131
+ makeChunk({ messageId: "req-OLD", content: null, isFinal: true }),
132
+ refs,
133
+ );
134
+
135
+ expect(result).toBe("dropped-stale");
136
+ expect(controller.close).not.toHaveBeenCalled();
137
+ expect(refs.controllerRef.current).toBe(controller);
138
+ expect(refs.activeRequestIdRef.current).toBe("req-NEW");
139
+ });
140
+
141
+ it("forwards reasoning-start/delta/end sequences when ids match", () => {
142
+ // Walks the canonical happy path for a reasoning stream end-to-end.
143
+ const refs = makeRefs();
144
+ const controller = makeMockController();
145
+ refs.controllerRef.current = controller;
146
+ refs.activeRequestIdRef.current = "req-A";
147
+
148
+ const sequence = [
149
+ { type: "reasoning-start", id: "r1" },
150
+ { type: "reasoning-delta", id: "r1", delta: "thinking" },
151
+ { type: "reasoning-end", id: "r1" },
152
+ ] as const;
153
+ for (const chunk of sequence) {
154
+ const result = routeIncomingChatChunk(
155
+ makeChunk({ messageId: "req-A", content: chunk }),
156
+ refs,
157
+ );
158
+ expect(result).toBe("enqueued");
159
+ }
160
+
161
+ expect(controller.enqueue).toHaveBeenCalledTimes(3);
162
+ expect(controller.enqueue).toHaveBeenNthCalledWith(1, sequence[0]);
163
+ expect(controller.enqueue).toHaveBeenNthCalledWith(2, sequence[1]);
164
+ expect(controller.enqueue).toHaveBeenNthCalledWith(3, sequence[2]);
165
+ });
166
+
167
+ it(
168
+ "drops stale reasoning-delta after Stop → new run sequence " +
169
+ "(regression for missing reasoning part error)",
170
+ () => {
171
+ // Full scenario: A runs, A is stopped, B starts, A's late chunk arrives.
172
+ const refs = makeRefs();
173
+
174
+ // 1. Run A starts: controller A active.
175
+ const controllerA = makeMockController();
176
+ refs.controllerRef.current = controllerA;
177
+ refs.activeRequestIdRef.current = "req-A";
178
+
179
+ // First reasoning chunks for A flow through.
180
+ routeIncomingChatChunk(
181
+ makeChunk({
182
+ messageId: "req-A",
183
+ content: { type: "reasoning-start", id: "rA" },
184
+ }),
185
+ refs,
186
+ );
187
+ routeIncomingChatChunk(
188
+ makeChunk({
189
+ messageId: "req-A",
190
+ content: {
191
+ type: "reasoning-delta",
192
+ id: "rA",
193
+ delta: "thinking",
194
+ },
195
+ }),
196
+ refs,
197
+ );
198
+ expect(controllerA.enqueue).toHaveBeenCalledTimes(2);
199
+
200
+ // 2. User clicks Stop: abort handler clears refs (simulated).
201
+ refs.controllerRef.current = null;
202
+ refs.activeRequestIdRef.current = null;
203
+
204
+ // A late chunk for A arrives in this window — must be a no-op.
205
+ const between = routeIncomingChatChunk(
206
+ makeChunk({
207
+ messageId: "req-A",
208
+ content: {
209
+ type: "reasoning-delta",
210
+ id: "rA",
211
+ delta: "leftover",
212
+ },
213
+ }),
214
+ refs,
215
+ );
216
+ expect(between).toBe("dropped-no-controller");
217
+
218
+ // 3. User sends Run B: new controller, new active id.
219
+ const controllerB = makeMockController();
220
+ refs.controllerRef.current = controllerB;
221
+ refs.activeRequestIdRef.current = "req-B";
222
+
223
+ // 4. Another late chunk for A arrives AFTER B opened. This is the
224
+ // case that previously threw `Received reasoning-delta for missing
225
+ // reasoning part with ID "rA"` in the SDK parser.
226
+ const stale = routeIncomingChatChunk(
227
+ makeChunk({
228
+ messageId: "req-A",
229
+ content: {
230
+ type: "reasoning-delta",
231
+ id: "rA",
232
+ delta: "still leaking",
233
+ },
234
+ }),
235
+ refs,
236
+ );
237
+ expect(stale).toBe("dropped-stale");
238
+ expect(controllerB.enqueue).not.toHaveBeenCalled();
239
+
240
+ // 5. B's own chunks flow normally.
241
+ routeIncomingChatChunk(
242
+ makeChunk({
243
+ messageId: "req-B",
244
+ content: { type: "reasoning-start", id: "rB" },
245
+ }),
246
+ refs,
247
+ );
248
+ routeIncomingChatChunk(
249
+ makeChunk({
250
+ messageId: "req-B",
251
+ content: { type: "reasoning-delta", id: "rB", delta: "fresh" },
252
+ }),
253
+ refs,
254
+ );
255
+ expect(controllerB.enqueue).toHaveBeenCalledTimes(2);
256
+ },
257
+ );
258
+
259
+ it("enqueues content alongside is_final and then closes", () => {
260
+ // Sanity: a single chunk that carries both `content` and `is_final` (rare
261
+ // but legal — backend may bundle final content with the terminator)
262
+ // should enqueue then close.
263
+ const refs = makeRefs();
264
+ const controller = makeMockController();
265
+ refs.controllerRef.current = controller;
266
+ refs.activeRequestIdRef.current = "req-A";
267
+
268
+ const chunk = { type: "text-delta", id: "t1", delta: "bye" } as const;
269
+ const result = routeIncomingChatChunk(
270
+ makeChunk({ messageId: "req-A", content: chunk, isFinal: true }),
271
+ refs,
272
+ );
273
+
274
+ expect(result).toBe("closed");
275
+ expect(controller.enqueue).toHaveBeenCalledWith(chunk);
276
+ expect(controller.close).toHaveBeenCalledTimes(1);
277
+ });
278
+ });