@marimo-team/frontend 0.22.5-dev2 → 0.22.5-dev21

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 (142) hide show
  1. package/dist/assets/{CellStatus-HZQ0Twqj.js → CellStatus-CP1jrQ3A.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-C9jSHm_Y.js → ConnectedDataExplorerComponent-CfU-ThkK.js} +1 -1
  3. package/dist/assets/{ImperativeModal-Ka_MU6gk.js → ImperativeModal-DoGv2BXV.js} +1 -1
  4. package/dist/assets/JsonOutput-Bk2by9qo.js +49 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-Bb2dITRz.js → LazyAnyLanguageCodeMirror-CoYqQxHb.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-Cyd8ikG3.js +7 -0
  7. package/dist/assets/RSPContexts-LWFBF00h.js +1 -0
  8. package/dist/assets/{RenderHTML-Cyj8OBbz.js → RenderHTML-BOW_1WUn.js} +1 -1
  9. package/dist/assets/{add-cell-with-ai-BccDsMLb.js → add-cell-with-ai-DUq7Y523.js} +9 -9
  10. package/dist/assets/{add-connection-dialog-BO_nxLQi.js → add-connection-dialog-20N0EHLl.js} +1 -1
  11. package/dist/assets/{agent-panel-BMTl3Cgd.js → agent-panel-DEza5aFa.js} +3 -3
  12. package/dist/assets/{ai-model-dropdown-CS5ryevV.js → ai-model-dropdown-B-sbS07W.js} +4 -4
  13. package/dist/assets/{any-language-editor-BKxF_o5k.js → any-language-editor-DYgTL8eG.js} +1 -1
  14. package/dist/assets/app-config-button-CJFpYJ_2.js +1 -0
  15. package/dist/assets/{cache-panel-PA0cVzei.js → cache-panel-8E_Y5OSb.js} +1 -1
  16. package/dist/assets/{cell-editor-qLPKK49y.js → cell-editor-DLL6kX__.js} +11 -11
  17. package/dist/assets/{cell-link-CY7i_e4O.js → cell-link-Dtonmtv_.js} +1 -1
  18. package/dist/assets/{cells-CuaAKcwV.js → cells-BVG4a-Qn.js} +42 -42
  19. package/dist/assets/{chat-display-BLYrUQhO.js → chat-display-DsIGz-Wy.js} +1 -1
  20. package/dist/assets/{chat-panel-w9jaKRAD.js → chat-panel-aXOgktdv.js} +2 -2
  21. package/dist/assets/{chat-ui-BqpUGrwS.js → chat-ui-C--RApyx.js} +4 -4
  22. package/dist/assets/{column-preview-Dk6-wUK-.js → column-preview-CB_HZPdz.js} +1 -1
  23. package/dist/assets/{command-DkwDd6Ey.js → command-DvF_4mQa.js} +1 -1
  24. package/dist/assets/{command-palette-C0hZrhup.js → command-palette-DLKNdcX_.js} +1 -1
  25. package/dist/assets/{common-CL1iSwod.js → common-CLVI2BhO.js} +1 -1
  26. package/dist/assets/{components-CBihADZo.js → components-CGwwQ0Y4.js} +1 -1
  27. package/dist/assets/{components-CtOW1DR4.js → components-DiALLQRt.js} +1 -1
  28. package/dist/assets/{copy-icon-n6r2uyq3.js → copy-icon-BGs1Pbai.js} +1 -1
  29. package/dist/assets/{datasource-DwmhT5-D.js → datasource-BzTPyEw3.js} +1 -1
  30. package/dist/assets/{dependency-graph-panel-3AdEtWPZ.js → dependency-graph-panel-Pxmnzmd4.js} +1 -1
  31. package/dist/assets/{diagram-PSM6KHXK-D6nw8Fvd.js → diagram-PSM6KHXK-UdiW7QQb.js} +1 -1
  32. package/dist/assets/{documentation-panel-CVIUk8ga.js → documentation-panel-bgaPBvan.js} +1 -1
  33. package/dist/assets/{download-pkTDt-sj.js → download-QIfZjflb.js} +3 -3
  34. package/dist/assets/{edit-page-6Fpl3Fez.js → edit-page-DB_go-vo.js} +7 -7
  35. package/dist/assets/{error-banner-Bfe6BopK.js → error-banner-bXc_9BBZ.js} +1 -1
  36. package/dist/assets/{error-panel-Cy3_isDL.js → error-panel-DfHzC7q-.js} +1 -1
  37. package/dist/assets/{field-CXDGiU3T.js → field-DTzXkFLZ.js} +1 -1
  38. package/dist/assets/{file-explorer-panel-qM-Iflzm.js → file-explorer-panel-CEd-m832.js} +3 -3
  39. package/dist/assets/{file-icons-DaGma7HH.js → file-icons-ma7AC7Pi.js} +1 -1
  40. package/dist/assets/{floating-outline-oPCmn9_F.js → floating-outline-BluCz3vD.js} +1 -1
  41. package/dist/assets/{focus-B524Cy57.js → focus-BHXzPxK_.js} +1 -1
  42. package/dist/assets/{form-IHIhviLb.js → form-C0W8wa3f.js} +1 -1
  43. package/dist/assets/{formats-DC_7w22O.js → formats-BQHP_yQk.js} +1 -1
  44. package/dist/assets/{formatting-Dw_0ktP1.js → formatting-BCAZDWQ0.js} +1 -1
  45. package/dist/assets/{gallery-page-DCkpFwYi.js → gallery-page-XSrY7bw_.js} +1 -1
  46. package/dist/assets/{ganttDiagram-JELNMOA3-BC4H0xrj.js → ganttDiagram-JELNMOA3-KWj06bYC.js} +1 -1
  47. package/dist/assets/{glide-data-editor-BkvzN8Tk.js → glide-data-editor-Bd4FOxvW.js} +1 -1
  48. package/dist/assets/{home-page-bB7oHRxE.js → home-page-nNrKA7jH.js} +2 -2
  49. package/dist/assets/{hooks-DmeHnl8J.js → hooks-C7qnb_ft.js} +1 -1
  50. package/dist/assets/{html-to-image-C-c-Hfuw.js → html-to-image-B3VySJ1_.js} +1 -1
  51. package/dist/assets/index-CLnKZIHZ.js +42 -0
  52. package/dist/assets/index-DBs2il8a.css +2 -0
  53. package/dist/assets/input-Bg12i6qY.js +1 -0
  54. package/dist/assets/{kiosk-mode-CnJjuo6B.js → kiosk-mode-BSY8y4kL.js} +1 -1
  55. package/dist/assets/{layout-Ce-vzLs_.js → layout-V4eTNc4M.js} +3 -3
  56. package/dist/assets/{linear-bY7qDnP9.js → linear-BICTUQLW.js} +1 -1
  57. package/dist/assets/{logs-panel-CzlMq5La.js → logs-panel-B1AysC8S.js} +1 -1
  58. package/dist/assets/{markdown-renderer-B4-BiMy9.js → markdown-renderer-BiKTlki_.js} +1 -1
  59. package/dist/assets/{mermaid-DDNZ06H7.js → mermaid-BJFSZcG6.js} +3 -3
  60. package/dist/assets/{name-cell-input-CUG9-cgv.js → name-cell-input-BA5B0KJl.js} +1 -1
  61. package/dist/assets/{outline-panel-BXefyCJ4.js → outline-panel-BfGPA_Sj.js} +1 -1
  62. package/dist/assets/{packages-panel-Bc42thuF.js → packages-panel-BgoT3rrV.js} +1 -1
  63. package/dist/assets/{panels-xqtEg2DV.js → panels-BeW4RaML.js} +1 -1
  64. package/dist/assets/{pieDiagram-ADFJNKIX-BeLuFsHa.js → pieDiagram-ADFJNKIX-ixj1Ihpk.js} +1 -1
  65. package/dist/assets/{precisionRound-D8yxZYIF.js → precisionRound-CU2C3Vxx.js} +1 -1
  66. package/dist/assets/{process-output-C-VBRULx.js → process-output-MnxG8jwi.js} +1 -1
  67. package/dist/assets/{quadrantDiagram-AYHSOK5B-7-MiutmN.js → quadrantDiagram-AYHSOK5B-DAgQxEHI.js} +1 -1
  68. package/dist/assets/{react-vega-LNfJwi6n.js → react-vega-BVBnH7ue.js} +1 -1
  69. package/dist/assets/react-vega-BfEZWy9T.js +1 -0
  70. package/dist/assets/{readonly-python-code-DinZuT8P.js → readonly-python-code-DuH_nOq_.js} +1 -1
  71. package/dist/assets/{run-page-GmVLTzzU.js → run-page-kI3bwb2R.js} +1 -1
  72. package/dist/assets/{sankeyDiagram-TZEHDZUN-DuPJkvrj.js → sankeyDiagram-TZEHDZUN-DAWWzAvZ.js} +1 -1
  73. package/dist/assets/{scratchpad-panel-CnzHN40A.js → scratchpad-panel-617FvSKA.js} +1 -1
  74. package/dist/assets/{secrets-panel-DxBoNgTM.js → secrets-panel-C6X5jB8Q.js} +1 -1
  75. package/dist/assets/{session-panel-CpeJADG_.js → session-panel-D6Sf5vhq.js} +1 -1
  76. package/dist/assets/snippets-panel-DfWq0wh2.js +1 -0
  77. package/dist/assets/{state-9-n7I_Bo.js → state-BgrGQPFs.js} +1 -1
  78. package/dist/assets/{state-DuVk71Dw.js → state-Blw6taKi.js} +1 -1
  79. package/dist/assets/{state-oW8A8maF.js → state-urfwd716.js} +2 -2
  80. package/dist/assets/{terminal-bXHNLBHU.js → terminal-BBTjIXBz.js} +3 -3
  81. package/dist/assets/{textarea-CI3yaazO.js → textarea-B5p9_myy.js} +1 -1
  82. package/dist/assets/{time-BdrzW2P6.js → time-DLcLlk0u.js} +1 -1
  83. package/dist/assets/{tracing-BRo4IXxX.js → tracing-CazNvlp4.js} +1 -1
  84. package/dist/assets/{tracing-panel-7QrwywKQ.js → tracing-panel-Bj0Bx8ZJ.js} +2 -2
  85. package/dist/assets/{useAddCell-BMYemCZ-.js → useAddCell-g3zSMJKs.js} +1 -1
  86. package/dist/assets/{useAsyncData-CaAFMbY9.js → useAsyncData-aCoWDe-l.js} +1 -1
  87. package/dist/assets/useBoolean-ugd5JdXd.js +1 -0
  88. package/dist/assets/{useCellActionButton-DMySRby1.js → useCellActionButton-DdsVswP2.js} +1 -1
  89. package/dist/assets/{useDateFormatter-BBQkEV05.js → useDateFormatter-BEz9SEXo.js} +1 -1
  90. package/dist/assets/{useDeleteCell-6SLN_jZa.js → useDeleteCell-CIdiI4Pk.js} +1 -1
  91. package/dist/assets/{useDependencyPanelTab-DRoK7Mvg.js → useDependencyPanelTab-DhXqfAqL.js} +1 -1
  92. package/dist/assets/{useNotebookActions-SGiWvk1I.js → useNotebookActions-ULfiGMMl.js} +1 -1
  93. package/dist/assets/{useNumberFormatter-DIM21HH4.js → useNumberFormatter-wQU1z0W_.js} +1 -1
  94. package/dist/assets/{usePress-CLNv4Iws.js → usePress-BXMIcLWP.js} +3 -3
  95. package/dist/assets/{useRunCells-a5YC9YVQ.js → useRunCells-q5CA-QoL.js} +1 -1
  96. package/dist/assets/{useSplitCell-Dn4N4Evl.js → useSplitCell-B0jDwUEM.js} +1 -1
  97. package/dist/assets/{utils-BaSt_0sN.js → utils-BZwMGX81.js} +1 -1
  98. package/dist/assets/{vega-component-B_GT243z.js → vega-component-B2_J2GQq.js} +1 -1
  99. package/dist/assets/{vega-loader.browser-DLukqq1q.js → vega-loader.browser-DXARUlxo.js} +1 -1
  100. package/dist/assets/{write-secret-modal-C87c9nwO.js → write-secret-modal-CHfFN0H8.js} +1 -1
  101. package/dist/assets/{xychartDiagram-PRI3JC2R-BRVF_pwk.js → xychartDiagram-PRI3JC2R-DgjhbmH4.js} +1 -1
  102. package/dist/index.html +52 -51
  103. package/package.json +2 -2
  104. package/src/components/data-table/__tests__/columns.test.tsx +92 -13
  105. package/src/components/data-table/column-header.tsx +81 -56
  106. package/src/components/data-table/columns.tsx +25 -32
  107. package/src/components/data-table/data-table.tsx +8 -1
  108. package/src/components/data-table/renderers.tsx +19 -6
  109. package/src/components/data-table/types.ts +4 -0
  110. package/src/components/editor/errors/traceback-modal.tsx +6 -5
  111. package/src/components/editor/output/MarimoErrorOutput.tsx +7 -14
  112. package/src/components/editor/output/MarimoTracebackOutput.tsx +4 -3
  113. package/src/core/cells/document-changes.ts +12 -0
  114. package/src/core/saving/file-state.ts +16 -0
  115. package/src/hooks/useAsyncData.ts +1 -1
  116. package/src/mount.tsx +17 -1
  117. package/src/plugins/impl/DataTablePlugin.tsx +1 -1
  118. package/src/plugins/impl/plotly/__tests__/selection.test.ts +22 -0
  119. package/src/plugins/impl/plotly/selection.ts +1 -0
  120. package/dist/assets/JsonOutput-C0_rp6zm.js +0 -49
  121. package/dist/assets/MarimoErrorOutput-C3-dwG-M.js +0 -7
  122. package/dist/assets/VisuallyHidden-CuyBIaeU.js +0 -1
  123. package/dist/assets/app-config-button-2bbgrHC2.js +0 -1
  124. package/dist/assets/index-B2hkIK4p.js +0 -42
  125. package/dist/assets/index-BNN_F0CC.css +0 -2
  126. package/dist/assets/input-DfDSaPYh.js +0 -1
  127. package/dist/assets/react-vega-Bpff0f7C.js +0 -1
  128. package/dist/assets/snippets-panel-Z3fr4trp.js +0 -1
  129. package/dist/assets/useBoolean-Dsw5twOt.js +0 -1
  130. /package/dist/assets/{blob-Brr8JjWU.js → blob-CTort_or.js} +0 -0
  131. /package/dist/assets/{context-BoP98Msj.js → context-DGqo1TbK.js} +0 -0
  132. /package/dist/assets/{copy-Acb9G8ns.js → copy-Bizk-4hH.js} +0 -0
  133. /package/dist/assets/{defaultLocale-B8Omxjnr.js → defaultLocale-BLne0bXb.js} +0 -0
  134. /package/dist/assets/{defaultLocale-Cyhs3Xd2.js → defaultLocale-JieDVWC_.js} +0 -0
  135. /package/dist/assets/{dialog-Do1NuJPs.js → dialog-BYjetQgE.js} +0 -0
  136. /package/dist/assets/{errors-Dn17Z1-5.js → errors-CZb6hI2x.js} +0 -0
  137. /package/dist/assets/{numbers--q6KLqqK.js → numbers--nDjbAPa.js} +0 -0
  138. /package/dist/assets/{ordinal-CN_bk_zt.js → ordinal-DuTuuUgV.js} +0 -0
  139. /package/dist/assets/{range-BmYMHsC2.js → range-1DwpgXvM.js} +0 -0
  140. /package/dist/assets/{react-icons.esm-3sYgjjif.js → react-icons.esm-BUYTQ32a.js} +0 -0
  141. /package/dist/assets/{share-CkOka71S.js → share-0LPgOyiW.js} +0 -0
  142. /package/dist/assets/{useHotkey-CX3-tpiy.js → useHotkey-DALUNe8I.js} +0 -0
@@ -206,41 +206,30 @@ export function generateColumns<T>({
206
206
  </div>
207
207
  ) : null;
208
208
 
209
- const justify = getJustify(key);
210
-
211
- const headerWithType = (
212
- <div
209
+ const headerName = (
210
+ <span
213
211
  className={cn(
214
- "flex flex-col",
215
- justify === "center" && "items-center",
216
- justify === "right" && "items-end",
212
+ "font-bold",
213
+ headerTitle && "underline decoration-dotted",
217
214
  )}
218
215
  >
219
- <span
220
- className={cn(
221
- "font-bold",
222
- headerTitle && "underline decoration-dotted",
223
- )}
224
- >
225
- {key === "" ? " " : key}
226
- </span>
227
- {dtypeHeader}
228
- </div>
216
+ {key === "" ? " " : key}
217
+ </span>
229
218
  );
230
219
 
231
220
  const headerWithTooltip = headerTitle ? (
232
221
  <Tooltip content={headerTitle} delayDuration={300}>
233
- {headerWithType}
222
+ {headerName}
234
223
  </Tooltip>
235
224
  ) : (
236
- headerWithType
225
+ headerName
237
226
  );
238
227
 
239
228
  const dataTableColumnHeader = (
240
229
  <DataTableColumnHeader
241
230
  header={headerWithTooltip}
231
+ subheader={dtypeHeader}
242
232
  column={column}
243
- justify={justify}
244
233
  calculateTopKRows={calculateTopKRows}
245
234
  table={table}
246
235
  />
@@ -255,8 +244,6 @@ export function generateColumns<T>({
255
244
  <div
256
245
  className={cn(
257
246
  "flex flex-col h-full pt-0.5 pb-3 justify-between items-start",
258
- justify === "center" && "items-center",
259
- justify === "right" && "items-end",
260
247
  )}
261
248
  >
262
249
  {dataTableColumnHeader}
@@ -283,13 +270,13 @@ export function generateColumns<T>({
283
270
 
284
271
  const dataType = column.columnDef.meta?.dataType;
285
272
  const isNumeric = dataType === "number" || dataType === "integer";
286
- const cellStyles = getCellStyleClass(
273
+ const cellStyles = getCellStyleClass({
287
274
  justify,
288
275
  wrapped,
289
276
  canSelectCell,
290
- isCellSelected,
277
+ isSelected: isCellSelected,
291
278
  isNumeric,
292
- );
279
+ });
293
280
 
294
281
  const renderedCell = renderCellValue({
295
282
  column,
@@ -448,13 +435,19 @@ function getFilterTypeForFieldType(
448
435
  }
449
436
  }
450
437
 
451
- function getCellStyleClass(
452
- justify: "left" | "center" | "right" | undefined,
453
- wrapped: boolean | undefined,
454
- canSelectCell: boolean,
455
- isSelected: boolean,
456
- isNumeric?: boolean,
457
- ): string {
438
+ function getCellStyleClass({
439
+ justify = "left",
440
+ wrapped,
441
+ canSelectCell,
442
+ isSelected,
443
+ isNumeric = false,
444
+ }: {
445
+ justify: "left" | "center" | "right" | undefined;
446
+ wrapped: boolean | undefined;
447
+ canSelectCell: boolean;
448
+ isSelected: boolean;
449
+ isNumeric?: boolean;
450
+ }): string {
458
451
  return cn(
459
452
  canSelectCell && "cursor-pointer",
460
453
  isSelected &&
@@ -47,6 +47,7 @@ import { DataTableBody, renderTableHeader } from "./renderers";
47
47
  import { TableBottomBar } from "./TableBottomBar";
48
48
  import { TableTopBar } from "./TableTopBar";
49
49
  import {
50
+ AUTO_WIDTH_MAX_COLUMNS,
50
51
  type DataTableSelection,
51
52
  MIN_ROWS_TO_VIRTUALIZE,
52
53
  type TooManyRows,
@@ -300,7 +301,13 @@ const DataTableInternal = <TData,>({
300
301
  isAnyPanelOpen={isAnyPanelOpen}
301
302
  downloadAs={downloadAs}
302
303
  />
303
- <Table className="relative" ref={tableRef}>
304
+ <Table
305
+ className={cn(
306
+ "relative",
307
+ columns.length <= AUTO_WIDTH_MAX_COLUMNS ? "w-auto" : "w-full",
308
+ )}
309
+ ref={tableRef}
310
+ >
304
311
  {showLoadingBar && (
305
312
  <thead className="absolute top-0 left-0 h-[3px] w-1/2 bg-primary animate-slide" />
306
313
  )}
@@ -27,7 +27,7 @@ import { DataTableContextMenu } from "./context-menu";
27
27
  import { CellRangeSelectionIndicator } from "./range-focus/cell-selection-indicator";
28
28
  import { useCellRangeSelection } from "./range-focus/use-cell-range-selection";
29
29
  import { useScrollIntoViewOnFocus } from "./range-focus/use-scroll-into-view";
30
- import { TABLE_ROW_HEIGHT_PX } from "./types";
30
+ import { AUTO_WIDTH_MAX_COLUMNS, TABLE_ROW_HEIGHT_PX } from "./types";
31
31
  import { stringifyUnknownValue } from "./utils";
32
32
 
33
33
  export function renderTableHeader<TData>(
@@ -46,7 +46,7 @@ export function renderTableHeader<TData>(
46
46
  <TableHead
47
47
  key={header.id}
48
48
  className={cn(
49
- "h-auto min-h-10 whitespace-pre align-top",
49
+ "h-auto min-h-10 whitespace-pre align-top border-r border-r-border/75",
50
50
  className,
51
51
  )}
52
52
  style={style}
@@ -69,6 +69,13 @@ export function renderTableHeader<TData>(
69
69
  {renderHeaderGroup(table.getLeftHeaderGroups())}
70
70
  {renderHeaderGroup(table.getCenterHeaderGroups())}
71
71
  {renderHeaderGroup(table.getRightHeaderGroups())}
72
+ {table.getAllColumns().length <= AUTO_WIDTH_MAX_COLUMNS && (
73
+ <th
74
+ className="w-full border-0"
75
+ aria-hidden="true"
76
+ role="presentation"
77
+ />
78
+ )}
72
79
  </TableRow>
73
80
  </TableHeader>
74
81
  );
@@ -163,7 +170,7 @@ export const DataTableBody = <TData,>({
163
170
  {...getCellDomProps(cell.id)}
164
171
  key={cell.id}
165
172
  className={cn(
166
- "whitespace-pre truncate max-w-[300px] outline-hidden",
173
+ "whitespace-pre truncate max-w-[300px] outline-hidden border-r border-r-border/75",
167
174
  cell.column.getColumnWrapping &&
168
175
  cell.column.getColumnWrapping?.() === "wrap" &&
169
176
  COLUMN_WRAPPING_STYLES,
@@ -230,15 +237,21 @@ export const DataTableBody = <TData,>({
230
237
  {renderCells(row.getLeftVisibleCells())}
231
238
  {renderCells(row.getCenterVisibleCells())}
232
239
  {renderCells(row.getRightVisibleCells())}
240
+ {columns.length <= AUTO_WIDTH_MAX_COLUMNS && (
241
+ <td className="border-0" aria-hidden="true" role="presentation" />
242
+ )}
233
243
  </TableRow>
234
244
  );
235
245
  };
236
246
 
247
+ const hasFillerColumn = columns.length <= AUTO_WIDTH_MAX_COLUMNS;
248
+ const totalColSpan = columns.length + (hasFillerColumn ? 1 : 0);
249
+
237
250
  const renderRows = () => {
238
251
  if (rows.length === 0) {
239
252
  return (
240
253
  <TableRow>
241
- <TableCell colSpan={columns.length} className="h-24 text-center">
254
+ <TableCell colSpan={totalColSpan} className="h-24 text-center">
242
255
  No results.
243
256
  </TableCell>
244
257
  </TableRow>
@@ -255,7 +268,7 @@ export const DataTableBody = <TData,>({
255
268
  data-virtual-spacer=""
256
269
  style={{ height: virtualItems[0].start }}
257
270
  >
258
- <td colSpan={columns.length} />
271
+ <td colSpan={totalColSpan} />
259
272
  </tr>
260
273
  )}
261
274
  {virtualItems.map((vItem) => renderRow(rows[vItem.index]))}
@@ -266,7 +279,7 @@ export const DataTableBody = <TData,>({
266
279
  height: totalSize - (virtualItems.at(-1)?.end ?? totalSize),
267
280
  }}
268
281
  >
269
- <td colSpan={columns.length} />
282
+ <td colSpan={totalColSpan} />
270
283
  </tr>
271
284
  )}
272
285
  </>
@@ -16,6 +16,10 @@ declare module "@tanstack/react-table" {
16
16
  export const TABLE_ROW_HEIGHT_PX = 24;
17
17
  export const TABLE_HEADER_HEIGHT_PX = 40;
18
18
 
19
+ // Below this column count, the table uses w-auto with a filler column
20
+ // to prevent columns from stretching unnecessarily
21
+ export const AUTO_WIDTH_MAX_COLUMNS = 4;
22
+
19
23
  // Default number of visible rows when virtualizing without an explicit maxHeight.
20
24
  export const DEFAULT_VIRTUAL_ROWS = 15;
21
25
 
@@ -12,6 +12,8 @@ import {
12
12
  } from "@/components/ui/alert-dialog";
13
13
  import { CopyIcon } from "lucide-react";
14
14
  import { toast } from "@/components/ui/use-toast";
15
+ import { renderHTML } from "@/plugins/core/RenderHTML";
16
+ import { sanitizeHtml } from "@/plugins/core/sanitize-html";
15
17
 
16
18
  interface TracebackModalProps {
17
19
  isOpen: boolean;
@@ -29,7 +31,7 @@ export const TracebackModal: React.FC<TracebackModalProps> = ({
29
31
  const handleCopy = async () => {
30
32
  // Strip HTML tags for clipboard
31
33
  const tempDiv = document.createElement("div");
32
- tempDiv.innerHTML = traceback;
34
+ tempDiv.innerHTML = sanitizeHtml(traceback);
33
35
  const textContent = tempDiv.textContent || tempDiv.innerText || "";
34
36
 
35
37
  try {
@@ -73,10 +75,9 @@ export const TracebackModal: React.FC<TracebackModalProps> = ({
73
75
  Copy
74
76
  </Button>
75
77
  </div>
76
- <div
77
- className="font-code text-sm p-4 bg-muted rounded border overflow-auto max-h-[50vh] cursor-text select-text"
78
- dangerouslySetInnerHTML={{ __html: traceback }}
79
- />
78
+ <div className="font-code text-sm p-4 bg-muted rounded border overflow-auto max-h-[50vh] cursor-text select-text">
79
+ {renderHTML({ html: traceback })}
80
+ </div>
80
81
  </div>
81
82
  <AlertDialogFooter>
82
83
  <AlertDialogAction onClick={onClose}>Close</AlertDialogAction>
@@ -25,6 +25,7 @@ import { useChromeActions } from "../chrome/state";
25
25
  import { AutoFixButton } from "../errors/auto-fix";
26
26
  import { CellLinkError } from "../links/cell-link";
27
27
  import { processTextForUrls } from "./console/text-rendering";
28
+ import { renderHTML } from "@/plugins/core/RenderHTML";
28
29
 
29
30
  const Tip = (props: {
30
31
  title?: string;
@@ -486,13 +487,9 @@ export const MarimoErrorOutput = ({
486
487
  {processTextForUrls(error.msg, `exception-${idx}`)}
487
488
  </p>
488
489
  {"traceback" in error && error.traceback ? (
489
- <div
490
- className="font-code text-sm mt-2 p-3 bg-muted rounded border overflow-auto max-h-[50vh] cursor-text select-text"
491
- // biome-ignore lint/security/noDangerouslySetInnerHtml: traceback from backend
492
- dangerouslySetInnerHTML={{
493
- __html: error.traceback,
494
- }}
495
- />
490
+ <div className="font-code text-sm mt-2 p-3 bg-muted rounded border overflow-auto max-h-[50vh] cursor-text select-text">
491
+ {renderHTML({ html: error.traceback })}
492
+ </div>
496
493
  ) : (
497
494
  <div className="text-muted-foreground mt-2">
498
495
  See the console area for a traceback.
@@ -504,13 +501,9 @@ export const MarimoErrorOutput = ({
504
501
  {processTextForUrls(error.msg, `exception-${idx}`)}
505
502
  <CellLinkError cellId={error.raising_cell} />
506
503
  {"traceback" in error && error.traceback && (
507
- <div
508
- className="font-code text-sm mt-2 p-3 bg-muted rounded border overflow-auto max-h-[50vh] cursor-text select-text"
509
- // biome-ignore lint/security/noDangerouslySetInnerHtml: traceback from backend
510
- dangerouslySetInnerHTML={{
511
- __html: error.traceback,
512
- }}
513
- />
504
+ <div className="font-code text-sm mt-2 p-3 bg-muted rounded border overflow-auto max-h-[50vh] cursor-text select-text">
505
+ {renderHTML({ html: error.traceback })}
506
+ </div>
514
507
  )}
515
508
  </div>
516
509
  )}
@@ -34,6 +34,7 @@ import { getRequestClient } from "@/core/network/requests";
34
34
  import { isStaticNotebook } from "@/core/static/static-state";
35
35
  import { isWasm } from "@/core/wasm/utils";
36
36
  import { renderHTML } from "@/plugins/core/RenderHTML";
37
+ import { sanitizeHtml } from "@/plugins/core/sanitize-html";
37
38
  import { copyToClipboard } from "@/utils/copy";
38
39
  import {
39
40
  elementContainsMarimoCellFile,
@@ -173,9 +174,9 @@ export const MarimoTracebackOutput = ({
173
174
  </DropdownMenuItem>
174
175
  <DropdownMenuItem
175
176
  onClick={() => {
176
- // Strip HTML from the traceback
177
+ // Strip HTML from the traceback (sanitize first to prevent XSS)
177
178
  const div = document.createElement("div");
178
- div.innerHTML = traceback;
179
+ div.innerHTML = sanitizeHtml(traceback);
179
180
  const textContent = div.textContent || "";
180
181
  copyToClipboard(textContent);
181
182
  }}
@@ -193,7 +194,7 @@ export const MarimoTracebackOutput = ({
193
194
 
194
195
  function lastLine(text: string): string {
195
196
  const el = document.createElement("div");
196
- el.innerHTML = text;
197
+ el.innerHTML = sanitizeHtml(text);
197
198
  const lines = el.textContent?.split("\n").filter(Boolean);
198
199
  return lines?.at(-1) || "";
199
200
  }
@@ -25,6 +25,7 @@ import type { NotebookDocumentTransactionRequest } from "../network/types";
25
25
  import { store } from "../state/jotai";
26
26
  import type { CellActions, NotebookState } from "./cells";
27
27
  import type { CellId } from "./ids";
28
+ import { SCRATCH_CELL_ID } from "./ids";
28
29
  import type { CellData } from "./types";
29
30
 
30
31
  export type DocumentChange =
@@ -572,10 +573,21 @@ const flushChanges = debounce(() => {
572
573
  void getRequestClient().sendDocumentTransaction({ changes });
573
574
  }, 400);
574
575
 
576
+ function isScratchChange(change: DocumentChange): boolean {
577
+ if ("cellId" in change && change.cellId === SCRATCH_CELL_ID) {
578
+ return true;
579
+ }
580
+ return false;
581
+ }
582
+
575
583
  function enqueue(change: DocumentChange) {
576
584
  if (store.get(kioskModeAtom)) {
577
585
  return;
578
586
  }
587
+ // The scratchpad cell is local-only — don't sync it to the document.
588
+ if (isScratchChange(change)) {
589
+ return;
590
+ }
579
591
  pendingChanges.push(change);
580
592
  flushChanges();
581
593
  }
@@ -16,6 +16,22 @@ export const filenameAtom = atom<string | null>(getFilenameFromDOM());
16
16
  */
17
17
  export const cwdAtom = atom<string | null>(null);
18
18
 
19
+ /**
20
+ * LSP workspace information from the backend.
21
+ * Contains the project root and the document's file URI.
22
+ */
23
+ export interface LspWorkspace {
24
+ rootUri: string;
25
+ documentUri: string;
26
+ }
27
+
28
+ /**
29
+ * Atom for storing the LSP workspace information.
30
+ * This is populated during active notebook sessions
31
+ * and null for other pages.
32
+ */
33
+ export const lspWorkspaceAtom = atom<LspWorkspace | null>(null);
34
+
19
35
  /**
20
36
  * Set for static notebooks.
21
37
  */
@@ -341,7 +341,7 @@ export function useAsyncData<T>(
341
341
  };
342
342
  setResult((prevResult) => {
343
343
  // If we have previous data, show reloading state
344
- if (prevResult.status === "success") {
344
+ if (prevResult.status === "success" || prevResult.status === "loading") {
345
345
  return Result.loading(prevResult.data);
346
346
  }
347
347
  // Otherwise, show initial loading state
package/src/mount.tsx CHANGED
@@ -36,7 +36,12 @@ import {
36
36
  DEFAULT_RUNTIME_CONFIG,
37
37
  runtimeConfigAtom,
38
38
  } from "./core/runtime/config";
39
- import { codeAtom, cwdAtom, filenameAtom } from "./core/saving/file-state";
39
+ import {
40
+ codeAtom,
41
+ cwdAtom,
42
+ filenameAtom,
43
+ lspWorkspaceAtom,
44
+ } from "./core/saving/file-state";
40
45
  import { store } from "./core/state/jotai";
41
46
  import { patchFetch, patchVegaLoader } from "./core/static/files";
42
47
  import {
@@ -150,6 +155,16 @@ const mountOptionsSchema = z.object({
150
155
  * absolute working directory of the notebook
151
156
  */
152
157
  cwd: z.string().nullish().default(null),
158
+ /**
159
+ * LSP workspace information
160
+ */
161
+ lspWorkspace: z
162
+ .object({
163
+ rootUri: z.string(),
164
+ documentUri: z.string(),
165
+ })
166
+ .nullish()
167
+ .default(null),
153
168
  /**
154
169
  * notebook code
155
170
  */
@@ -287,6 +302,7 @@ function initStore(options: unknown) {
287
302
  // Files
288
303
  store.set(filenameAtom, parsedOptions.data.filename);
289
304
  store.set(cwdAtom, parsedOptions.data.cwd ?? null);
305
+ store.set(lspWorkspaceAtom, parsedOptions.data.lspWorkspace);
290
306
  store.set(codeAtom, parsedOptions.data.code);
291
307
  store.set(initialModeAtom, mode);
292
308
 
@@ -704,7 +704,7 @@ export const LoadingDataTableComponent = memo(
704
704
  <LoadingTable
705
705
  pageSize={
706
706
  props.totalRows !== TOO_MANY_ROWS && props.totalRows > 0
707
- ? props.totalRows
707
+ ? Math.min(props.totalRows, props.pageSize)
708
708
  : props.pageSize
709
709
  }
710
710
  />
@@ -117,6 +117,14 @@ describe("shouldHandleClickSelection", () => {
117
117
  expect(shouldHandleClickSelection([linePoint])).toBe(true);
118
118
  });
119
119
 
120
+ it("accepts waterfall clicks", () => {
121
+ const waterfallPoint = createPlotDatum({
122
+ data: { type: "waterfall" },
123
+ });
124
+
125
+ expect(shouldHandleClickSelection([waterfallPoint])).toBe(true);
126
+ });
127
+
120
128
  it("rejects non-line scatter marker clicks", () => {
121
129
  const markerPoint = createPlotDatum({
122
130
  data: { type: "scatter", mode: "markers" },
@@ -196,4 +204,18 @@ describe("extractPoints", () => {
196
204
 
197
205
  expect(extractPoints([point])).toEqual([{ x: 1, y: 2, z: 3 }]);
198
206
  });
207
+
208
+ it("returns x/y/pointIndex for waterfall clicks", () => {
209
+ const point = createPlotDatum({
210
+ x: "Revenue",
211
+ y: 400,
212
+ pointIndex: 1,
213
+ curveNumber: 0,
214
+ data: { type: "waterfall" },
215
+ });
216
+
217
+ expect(extractPoints([point])).toEqual([
218
+ { x: "Revenue", y: 400, pointIndex: 1, curveNumber: 0 },
219
+ ]);
220
+ });
199
221
  });
@@ -227,6 +227,7 @@ export function shouldHandleClickSelection(
227
227
  type === "bar" ||
228
228
  type === "heatmap" ||
229
229
  type === "histogram" ||
230
+ type === "waterfall" ||
230
231
  isLinePoint(point)
231
232
  );
232
233
  });