@marimo-team/islands 0.20.3-dev88 → 0.20.3-dev90

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.
package/dist/main.js CHANGED
@@ -68637,7 +68637,11 @@ Image URL: ${r.imageUrl}`)), contextToXml({
68637
68637
  if (isDataPart(d2)) return Logger.debug("Found data part", d2), null;
68638
68638
  switch (d2.type) {
68639
68639
  case "text":
68640
- return (0, import_jsx_runtime.jsx)(MarkdownRenderer, {
68640
+ return d2.text.includes("<marimo-") ? (0, import_jsx_runtime.jsx)(import_react.Fragment, {
68641
+ children: renderHTML({
68642
+ html: d2.text
68643
+ })
68644
+ }, f) : (0, import_jsx_runtime.jsx)(MarkdownRenderer, {
68641
68645
  content: d2.text
68642
68646
  }, f);
68643
68647
  case "reasoning":
@@ -70347,7 +70351,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
70347
70351
  return Logger.warn("Failed to get version from mount config"), null;
70348
70352
  }
70349
70353
  }
70350
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.3-dev88"), showCodeInRunModeAtom = atom(true);
70354
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.3-dev90"), showCodeInRunModeAtom = atom(true);
70351
70355
  atom(null);
70352
70356
  var import_compiler_runtime$88 = require_compiler_runtime();
70353
70357
  function useKeydownOnElement(e, r) {
@@ -99662,26 +99666,31 @@ ${c}
99662
99666
  }), r[0] = e, r[1] = c), c;
99663
99667
  };
99664
99668
  var ConsoleOutputInternal = (e) => {
99665
- let r = (0, import_compiler_runtime$4.c)(58), c = import_react.useRef(null), { wrapText: d, setWrapText: f } = useWrapText(), [_, v] = useExpandedConsoleOutput(e.cellId), { consoleOutputs: y, stale: S, cellName: w, cellId: E, onSubmitDebugger: O, onClear: M, onRefactorWithAI: I, className: z } = e, G = y.length > 0, q = useSelectAllContent(G), IY = useOverflowDetection(c, G), LY;
99666
- if (r[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (LY = () => {
99669
+ let r = (0, import_compiler_runtime$4.c)(62), c = import_react.useRef(null), { wrapText: d, setWrapText: f } = useWrapText(), [_, v] = useExpandedConsoleOutput(e.cellId), [y, S] = import_react.useState(""), w;
99670
+ r[0] === y ? w = r[1] : (w = {
99671
+ value: y,
99672
+ setValue: S
99673
+ }, r[0] = y, r[1] = w);
99674
+ let E = useInputHistory(w), { consoleOutputs: O, stale: M, cellName: I, cellId: z, onSubmitDebugger: G, onClear: q, onRefactorWithAI: IY, className: LY } = e, RY = O.length > 0, zY = useSelectAllContent(RY), BY = useOverflowDetection(c, RY), VY;
99675
+ if (r[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (VY = () => {
99667
99676
  let e2 = c.current;
99668
99677
  if (!e2) return;
99669
99678
  let r2 = e2.scrollHeight - e2.clientHeight;
99670
99679
  r2 - e2.scrollTop < 120 && (e2.scrollTop = r2);
99671
- }, r[0] = LY) : LY = r[0], (0, import_react.useLayoutEffect)(LY), !G && isInternalCellName(w)) return null;
99672
- let RY, zY, BY, VY, HY, UY, WY, GY, KY, qY;
99673
- if (r[1] !== E || r[2] !== z || r[3] !== y || r[4] !== G || r[5] !== _ || r[6] !== IY || r[7] !== M || r[8] !== I || r[9] !== O || r[10] !== q || r[11] !== v || r[12] !== f || r[13] !== S || r[14] !== d) {
99680
+ }, r[2] = VY) : VY = r[2], (0, import_react.useLayoutEffect)(VY), !RY && isInternalCellName(I)) return null;
99681
+ let HY, UY, WY, GY, KY, qY, JY, YY, XY, ZY;
99682
+ if (r[3] !== z || r[4] !== LY || r[5] !== O || r[6] !== RY || r[7] !== E || r[8] !== _ || r[9] !== BY || r[10] !== q || r[11] !== IY || r[12] !== G || r[13] !== zY || r[14] !== v || r[15] !== f || r[16] !== M || r[17] !== y || r[18] !== d) {
99674
99683
  let e2 = [
99675
- ...y
99676
- ].reverse(), w2 = e2.some(_temp$2), LY2 = e2.findIndex(_temp2$2), JY2;
99677
- r[25] === y ? JY2 = r[26] : (JY2 = () => y.filter(_temp3$1).map(_temp4$1).join("\n"), r[25] = y, r[26] = JY2);
99678
- let YY2 = JY2;
99679
- qY = "relative group", r[27] !== YY2 || r[28] !== G || r[29] !== _ || r[30] !== IY || r[31] !== v || r[32] !== f || r[33] !== d ? (zY = G && (0, import_jsx_runtime.jsxs)("div", {
99684
+ ...O
99685
+ ].reverse(), w2 = e2.some(_temp$2), I2 = e2.findIndex(_temp2$2), VY2;
99686
+ r[29] === O ? VY2 = r[30] : (VY2 = () => O.filter(_temp3$1).map(_temp4$1).join("\n"), r[29] = O, r[30] = VY2);
99687
+ let QY2 = VY2;
99688
+ HY = "relative group", r[31] !== QY2 || r[32] !== RY || r[33] !== _ || r[34] !== BY || r[35] !== v || r[36] !== f || r[37] !== d ? (UY = RY && (0, import_jsx_runtime.jsxs)("div", {
99680
99689
  className: "absolute top-1 right-4 z-10 opacity-0 group-hover:opacity-100 flex items-center gap-1 print:hidden",
99681
99690
  children: [
99682
99691
  (0, import_jsx_runtime.jsx)(CopyClipboardIcon, {
99683
99692
  tooltip: "Copy console output",
99684
- value: YY2,
99693
+ value: QY2,
99685
99694
  className: "h-4 w-4"
99686
99695
  }),
99687
99696
  (0, import_jsx_runtime.jsx)(Tooltip, {
@@ -99698,7 +99707,7 @@ ${c}
99698
99707
  })
99699
99708
  })
99700
99709
  }),
99701
- (IY || _) && (0, import_jsx_runtime.jsx)(Button, {
99710
+ (BY || _) && (0, import_jsx_runtime.jsx)(Button, {
99702
99711
  "aria-label": _ ? "Collapse output" : "Expand output",
99703
99712
  className: "p-0 mb-px",
99704
99713
  onClick: () => v(!_),
@@ -99717,21 +99726,24 @@ ${c}
99717
99726
  })
99718
99727
  })
99719
99728
  ]
99720
- }), r[27] = YY2, r[28] = G, r[29] = _, r[30] = IY, r[31] = v, r[32] = f, r[33] = d, r[34] = zY) : zY = r[34], RY = S ? "This console output is stale" : void 0, BY = "console-output-area", VY = c, HY = q, UY = 0;
99721
- let XY2 = S && "marimo-output-stale", ZY = G ? "p-5" : "p-3";
99722
- r[35] !== z || r[36] !== XY2 || r[37] !== ZY ? (WY = cn("console-output-area overflow-hidden rounded-b-lg flex flex-col-reverse w-full gap-1 focus:outline-hidden", XY2, ZY, z), r[35] = z, r[36] = XY2, r[37] = ZY, r[38] = WY) : WY = r[38], r[39] === _ ? GY = r[40] : (GY = _ ? {
99729
+ }), r[31] = QY2, r[32] = RY, r[33] = _, r[34] = BY, r[35] = v, r[36] = f, r[37] = d, r[38] = UY) : UY = r[38], WY = M ? "This console output is stale" : void 0, GY = "console-output-area", KY = c, qY = zY, JY = 0;
99730
+ let $Y2 = M && "marimo-output-stale", eX2 = RY ? "p-5" : "p-3";
99731
+ r[39] !== LY || r[40] !== $Y2 || r[41] !== eX2 ? (YY = cn("console-output-area overflow-hidden rounded-b-lg flex flex-col-reverse w-full gap-1 focus:outline-hidden", $Y2, eX2, LY), r[39] = LY, r[40] = $Y2, r[41] = eX2, r[42] = YY) : YY = r[42], r[43] === _ ? XY = r[44] : (XY = _ ? {
99723
99732
  maxHeight: "none"
99724
- } : void 0, r[39] = _, r[40] = GY), KY = e2.map((e3, r2) => {
99733
+ } : void 0, r[43] = _, r[44] = XY), ZY = e2.map((e3, r2) => {
99725
99734
  if (e3.channel === "pdb") return null;
99726
99735
  if (e3.channel === "stdin") {
99727
99736
  invariant(typeof e3.data == "string", "Expected data to be a string");
99728
- let c2 = y.length - r2 - 1, d2 = e3.mimetype === "text/password";
99729
- return e3.response == null && LY2 === r2 ? (0, import_jsx_runtime.jsx)(StdInput, {
99737
+ let c2 = O.length - r2 - 1, d2 = e3.mimetype === "text/password";
99738
+ return e3.response == null && I2 === r2 ? (0, import_jsx_runtime.jsx)(StdInput, {
99730
99739
  output: e3.data,
99731
99740
  isPdb: w2,
99732
99741
  isPassword: d2,
99733
- onSubmit: (e4) => O(e4, c2),
99734
- onClear: M
99742
+ onSubmit: (e4) => G(e4, c2),
99743
+ onClear: q,
99744
+ value: y,
99745
+ setValue: S,
99746
+ inputHistory: E
99735
99747
  }, r2) : (0, import_jsx_runtime.jsx)(StdInputWithResponse, {
99736
99748
  output: e3.data,
99737
99749
  response: e3.response,
@@ -99740,94 +99752,91 @@ ${c}
99740
99752
  }
99741
99753
  return (0, import_jsx_runtime.jsx)(import_react.Fragment, {
99742
99754
  children: (0, import_jsx_runtime.jsx)(OutputRenderer, {
99743
- cellId: E,
99744
- onRefactorWithAI: I,
99755
+ cellId: z,
99756
+ onRefactorWithAI: IY,
99745
99757
  message: e3,
99746
99758
  wrapText: d
99747
99759
  })
99748
99760
  }, r2);
99749
- }), r[1] = E, r[2] = z, r[3] = y, r[4] = G, r[5] = _, r[6] = IY, r[7] = M, r[8] = I, r[9] = O, r[10] = q, r[11] = v, r[12] = f, r[13] = S, r[14] = d, r[15] = RY, r[16] = zY, r[17] = BY, r[18] = VY, r[19] = HY, r[20] = UY, r[21] = WY, r[22] = GY, r[23] = KY, r[24] = qY;
99750
- } else RY = r[15], zY = r[16], BY = r[17], VY = r[18], HY = r[19], UY = r[20], WY = r[21], GY = r[22], KY = r[23], qY = r[24];
99751
- let JY;
99752
- r[41] !== E || r[42] !== w ? (JY = (0, import_jsx_runtime.jsx)(NameCellContentEditable, {
99753
- value: w,
99754
- cellId: E,
99761
+ }), r[3] = z, r[4] = LY, r[5] = O, r[6] = RY, r[7] = E, r[8] = _, r[9] = BY, r[10] = q, r[11] = IY, r[12] = G, r[13] = zY, r[14] = v, r[15] = f, r[16] = M, r[17] = y, r[18] = d, r[19] = HY, r[20] = UY, r[21] = WY, r[22] = GY, r[23] = KY, r[24] = qY, r[25] = JY, r[26] = YY, r[27] = XY, r[28] = ZY;
99762
+ } else HY = r[19], UY = r[20], WY = r[21], GY = r[22], KY = r[23], qY = r[24], JY = r[25], YY = r[26], XY = r[27], ZY = r[28];
99763
+ let QY;
99764
+ r[45] !== z || r[46] !== I ? (QY = (0, import_jsx_runtime.jsx)(NameCellContentEditable, {
99765
+ value: I,
99766
+ cellId: z,
99755
99767
  className: "bg-(--slate-4) border-(--slate-4) hover:bg-(--slate-5) dark:border-(--sky-5) dark:bg-(--sky-6) dark:text-(--sky-12) text-(--slate-12) rounded-l rounded-br-lg absolute right-0 bottom-0 text-xs px-1.5 py-0.5 font-mono max-w-[75%] whitespace-nowrap overflow-hidden"
99756
- }), r[41] = E, r[42] = w, r[43] = JY) : JY = r[43];
99757
- let YY;
99758
- r[44] !== RY || r[45] !== JY || r[46] !== BY || r[47] !== VY || r[48] !== HY || r[49] !== UY || r[50] !== WY || r[51] !== GY || r[52] !== KY ? (YY = (0, import_jsx_runtime.jsxs)("div", {
99759
- title: RY,
99760
- "data-testid": BY,
99761
- ref: VY,
99762
- ...HY,
99763
- tabIndex: UY,
99764
- className: WY,
99765
- style: GY,
99768
+ }), r[45] = z, r[46] = I, r[47] = QY) : QY = r[47];
99769
+ let $Y;
99770
+ r[48] !== QY || r[49] !== WY || r[50] !== GY || r[51] !== KY || r[52] !== qY || r[53] !== JY || r[54] !== YY || r[55] !== XY || r[56] !== ZY ? ($Y = (0, import_jsx_runtime.jsxs)("div", {
99771
+ title: WY,
99772
+ "data-testid": GY,
99773
+ ref: KY,
99774
+ ...qY,
99775
+ tabIndex: JY,
99776
+ className: YY,
99777
+ style: XY,
99766
99778
  children: [
99767
- KY,
99768
- JY
99779
+ ZY,
99780
+ QY
99769
99781
  ]
99770
- }), r[44] = RY, r[45] = JY, r[46] = BY, r[47] = VY, r[48] = HY, r[49] = UY, r[50] = WY, r[51] = GY, r[52] = KY, r[53] = YY) : YY = r[53];
99771
- let XY;
99772
- return r[54] !== zY || r[55] !== YY || r[56] !== qY ? (XY = (0, import_jsx_runtime.jsxs)("div", {
99773
- className: qY,
99782
+ }), r[48] = QY, r[49] = WY, r[50] = GY, r[51] = KY, r[52] = qY, r[53] = JY, r[54] = YY, r[55] = XY, r[56] = ZY, r[57] = $Y) : $Y = r[57];
99783
+ let eX;
99784
+ return r[58] !== HY || r[59] !== UY || r[60] !== $Y ? (eX = (0, import_jsx_runtime.jsxs)("div", {
99785
+ className: HY,
99774
99786
  children: [
99775
- zY,
99776
- YY
99787
+ UY,
99788
+ $Y
99777
99789
  ]
99778
- }), r[54] = zY, r[55] = YY, r[56] = qY, r[57] = XY) : XY = r[57], XY;
99790
+ }), r[58] = HY, r[59] = UY, r[60] = $Y, r[61] = eX) : eX = r[61], eX;
99779
99791
  }, StdInput = (e) => {
99780
- let r = (0, import_compiler_runtime$4.c)(24), [c, d] = import_react.useState(""), f;
99781
- r[0] === c ? f = r[1] : (f = {
99782
- value: c,
99783
- setValue: d
99784
- }, r[0] = c, r[1] = f);
99785
- let { navigateUp: _, navigateDown: v, addToHistory: y } = useInputHistory(f), S;
99786
- r[2] === e.output ? S = r[3] : (S = renderText(e.output), r[2] = e.output, r[3] = S);
99787
- let w = e.isPassword ? "password" : "text", E, O;
99788
- r[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (E = (e2) => d(e2.target.value), O = (0, import_jsx_runtime.jsx)(ChevronRight, {
99792
+ let r = (0, import_compiler_runtime$4.c)(25), { value: c, setValue: d, inputHistory: f, output: _, isPassword: v, isPdb: y, onSubmit: S, onClear: w } = e, { navigateUp: E, navigateDown: O, addToHistory: M } = f, I;
99793
+ r[0] === _ ? I = r[1] : (I = renderText(_), r[0] = _, r[1] = I);
99794
+ let z = v ? "password" : "text", G;
99795
+ r[2] === d ? G = r[3] : (G = (e2) => d(e2.target.value), r[2] = d, r[3] = G);
99796
+ let q;
99797
+ r[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (q = (0, import_jsx_runtime.jsx)(ChevronRight, {
99789
99798
  className: "w-5 h-5"
99790
- }), r[4] = E, r[5] = O) : (E = r[4], O = r[5]);
99791
- let M;
99792
- r[6] !== y || r[7] !== v || r[8] !== _ || r[9] !== e || r[10] !== c ? (M = (r2) => {
99793
- if (r2.key === "ArrowUp") {
99794
- _(), r2.preventDefault();
99799
+ }), r[4] = q) : q = r[4];
99800
+ let IY;
99801
+ r[5] !== M || r[6] !== O || r[7] !== E || r[8] !== S || r[9] !== d || r[10] !== c ? (IY = (e2) => {
99802
+ if (e2.key === "ArrowUp") {
99803
+ E(), e2.preventDefault();
99795
99804
  return;
99796
99805
  }
99797
- if (r2.key === "ArrowDown") {
99798
- v(), r2.preventDefault();
99806
+ if (e2.key === "ArrowDown") {
99807
+ O(), e2.preventDefault();
99799
99808
  return;
99800
99809
  }
99801
- r2.key === "Enter" && !r2.shiftKey && (c && (y(c), e.onSubmit(c), d("")), r2.preventDefault(), r2.stopPropagation()), r2.key === "Enter" && r2.metaKey && (r2.preventDefault(), r2.stopPropagation());
99802
- }, r[6] = y, r[7] = v, r[8] = _, r[9] = e, r[10] = c, r[11] = M) : M = r[11];
99803
- let I;
99804
- r[12] !== w || r[13] !== M || r[14] !== c ? (I = (0, import_jsx_runtime.jsx)(Input, {
99810
+ e2.key === "Enter" && !e2.shiftKey && (c && (M(c), S(c), d("")), e2.preventDefault(), e2.stopPropagation()), e2.key === "Enter" && e2.metaKey && (e2.preventDefault(), e2.stopPropagation());
99811
+ }, r[5] = M, r[6] = O, r[7] = E, r[8] = S, r[9] = d, r[10] = c, r[11] = IY) : IY = r[11];
99812
+ let LY;
99813
+ r[12] !== z || r[13] !== G || r[14] !== IY || r[15] !== c ? (LY = (0, import_jsx_runtime.jsx)(Input, {
99805
99814
  "data-testid": "console-input",
99806
99815
  "data-stdin-blocking": true,
99807
- type: w,
99816
+ type: z,
99808
99817
  autoComplete: "off",
99809
99818
  autoFocus: true,
99810
99819
  value: c,
99811
- onChange: E,
99812
- icon: O,
99820
+ onChange: G,
99821
+ icon: q,
99813
99822
  className: "m-0 h-8 focus-visible:shadow-xs-solid",
99814
99823
  placeholder: "stdin",
99815
- onKeyDownCapture: M
99816
- }), r[12] = w, r[13] = M, r[14] = c, r[15] = I) : I = r[15];
99817
- let z;
99818
- r[16] !== e.isPdb || r[17] !== e.onClear || r[18] !== e.onSubmit ? (z = e.isPdb && (0, import_jsx_runtime.jsx)(DebuggerControls, {
99819
- onSubmit: e.onSubmit,
99820
- onClear: e.onClear
99821
- }), r[16] = e.isPdb, r[17] = e.onClear, r[18] = e.onSubmit, r[19] = z) : z = r[19];
99822
- let G;
99823
- return r[20] !== S || r[21] !== I || r[22] !== z ? (G = (0, import_jsx_runtime.jsxs)("div", {
99824
+ onKeyDownCapture: IY
99825
+ }), r[12] = z, r[13] = G, r[14] = IY, r[15] = c, r[16] = LY) : LY = r[16];
99826
+ let RY;
99827
+ r[17] !== y || r[18] !== w || r[19] !== S ? (RY = y && (0, import_jsx_runtime.jsx)(DebuggerControls, {
99828
+ onSubmit: S,
99829
+ onClear: w
99830
+ }), r[17] = y, r[18] = w, r[19] = S, r[20] = RY) : RY = r[20];
99831
+ let zY;
99832
+ return r[21] !== I || r[22] !== LY || r[23] !== RY ? (zY = (0, import_jsx_runtime.jsxs)("div", {
99824
99833
  className: "flex gap-2 items-center pt-2",
99825
99834
  children: [
99826
- S,
99827
99835
  I,
99828
- z
99836
+ LY,
99837
+ RY
99829
99838
  ]
99830
- }), r[20] = S, r[21] = I, r[22] = z, r[23] = G) : G = r[23], G;
99839
+ }), r[21] = I, r[22] = LY, r[23] = RY, r[24] = zY) : zY = r[24], zY;
99831
99840
  }, StdInputWithResponse = (e) => {
99832
99841
  let r = (0, import_compiler_runtime$4.c)(8), c;
99833
99842
  r[0] === e.output ? c = r[1] : (c = renderText(e.output), r[0] = e.output, r[1] = c);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.20.3-dev88",
3
+ "version": "0.20.3-dev90",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -1,6 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import type { DataUIPart, ToolUIPart, UIMessage } from "ai";
4
+ import React from "react";
5
+ import { renderHTML } from "@/plugins/core/RenderHTML";
4
6
  import { logNever } from "@/utils/assertNever";
5
7
  import { Logger } from "@/utils/Logger";
6
8
  import { MarkdownRenderer } from "../markdown/markdown-renderer";
@@ -46,6 +48,17 @@ export const renderUIMessage = ({
46
48
 
47
49
  switch (part.type) {
48
50
  case "text":
51
+ // Streamdown sanitizes the HTML which strips out marimo elements
52
+ // So instead, we render the HTML with our custom renderer.
53
+ if (part.text.includes("<marimo-")) {
54
+ return (
55
+ <React.Fragment key={index}>
56
+ {renderHTML({
57
+ html: part.text,
58
+ })}
59
+ </React.Fragment>
60
+ );
61
+ }
49
62
  return <MarkdownRenderer key={index} content={part.text} />;
50
63
  case "reasoning":
51
64
  return (
@@ -18,7 +18,10 @@ import { isInternalCellName } from "@/core/cells/names";
18
18
  import { useExpandedConsoleOutput } from "@/core/cells/outputs";
19
19
  import type { WithResponse } from "@/core/cells/types";
20
20
  import type { OutputMessage } from "@/core/kernel/messages";
21
- import { useInputHistory } from "@/hooks/useInputHistory";
21
+ import {
22
+ type UseInputHistoryReturn,
23
+ useInputHistory,
24
+ } from "@/hooks/useInputHistory";
22
25
  import { useOverflowDetection } from "@/hooks/useOverflowDetection";
23
26
  import { useSelectAllContent } from "@/hooks/useSelectAllContent";
24
27
  import { cn } from "@/utils/cn";
@@ -54,6 +57,11 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
54
57
  const ref = React.useRef<HTMLDivElement>(null);
55
58
  const { wrapText, setWrapText } = useWrapText();
56
59
  const [isExpanded, setIsExpanded] = useExpandedConsoleOutput(props.cellId);
60
+ const [stdinValue, setStdinValue] = React.useState("");
61
+ const inputHistory = useInputHistory({
62
+ value: stdinValue,
63
+ setValue: setStdinValue,
64
+ });
57
65
  const {
58
66
  consoleOutputs,
59
67
  stale,
@@ -210,6 +218,9 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
210
218
  isPassword={isPassword}
211
219
  onSubmit={(text) => onSubmitDebugger(text, originalIdx)}
212
220
  onClear={onClear}
221
+ value={stdinValue}
222
+ setValue={setStdinValue}
223
+ inputHistory={inputHistory}
213
224
  />
214
225
  );
215
226
  }
@@ -249,25 +260,32 @@ const StdInput = (props: {
249
260
  onSubmit: (text: string) => void;
250
261
  onClear?: () => void;
251
262
  output: string;
252
- response?: string;
253
263
  isPdb: boolean;
254
264
  isPassword?: boolean;
265
+ value: string;
266
+ setValue: (value: string) => void;
267
+ inputHistory: UseInputHistoryReturn;
255
268
  }) => {
256
- const [value, setValue] = React.useState("");
257
-
258
- const { navigateUp, navigateDown, addToHistory } = useInputHistory({
269
+ const {
259
270
  value,
260
271
  setValue,
261
- });
272
+ inputHistory,
273
+ output,
274
+ isPassword,
275
+ isPdb,
276
+ onSubmit,
277
+ onClear,
278
+ } = props;
279
+ const { navigateUp, navigateDown, addToHistory } = inputHistory;
262
280
 
263
281
  return (
264
282
  <div className="flex gap-2 items-center pt-2">
265
- {renderText(props.output)}
283
+ {renderText(output)}
266
284
  <Input
267
285
  data-testid="console-input"
268
286
  // This is used in <StdinBlockingAlert> to find the input
269
287
  data-stdin-blocking={true}
270
- type={props.isPassword ? "password" : "text"}
288
+ type={isPassword ? "password" : "text"}
271
289
  autoComplete="off"
272
290
  autoFocus={true}
273
291
  value={value}
@@ -292,7 +310,7 @@ const StdInput = (props: {
292
310
  if (e.key === "Enter" && !e.shiftKey) {
293
311
  if (value) {
294
312
  addToHistory(value);
295
- props.onSubmit(value);
313
+ onSubmit(value);
296
314
  setValue("");
297
315
  }
298
316
  e.preventDefault();
@@ -306,9 +324,7 @@ const StdInput = (props: {
306
324
  }
307
325
  }}
308
326
  />
309
- {props.isPdb && (
310
- <DebuggerControls onSubmit={props.onSubmit} onClear={props.onClear} />
311
- )}
327
+ {isPdb && <DebuggerControls onSubmit={onSubmit} onClear={onClear} />}
312
328
  </div>
313
329
  );
314
330
  };
@@ -1,9 +1,10 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { render, screen } from "@testing-library/react";
4
- import { describe, expect, it } from "vitest";
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { describe, expect, it, vi } from "vitest";
5
5
  import { TooltipProvider } from "@/components/ui/tooltip";
6
6
  import type { CellId } from "@/core/cells/ids";
7
+ import type { WithResponse } from "@/core/cells/types";
7
8
  import type { OutputMessage } from "@/core/kernel/messages";
8
9
  import { ConsoleOutput } from "../ConsoleOutput";
9
10
 
@@ -20,6 +21,10 @@ global.ResizeObserver = class ResizeObserver {
20
21
  }
21
22
  };
22
23
 
24
+ const renderWithProvider = (ui: React.ReactElement) => {
25
+ return render(<TooltipProvider>{ui}</TooltipProvider>);
26
+ };
27
+
23
28
  describe("ConsoleOutput integration", () => {
24
29
  const createOutput = (data: string, channel = "stdout"): OutputMessage => ({
25
30
  channel: channel as "stdout" | "stderr",
@@ -31,7 +36,7 @@ describe("ConsoleOutput integration", () => {
31
36
  const defaultProps = {
32
37
  cellId: "cell-1" as CellId,
33
38
  cellName: "test_cell",
34
- consoleOutputs: [],
39
+ consoleOutputs: [] as WithResponse<OutputMessage>[],
35
40
  stale: false,
36
41
  debuggerActive: false,
37
42
  onSubmitDebugger: () => {
@@ -50,14 +55,151 @@ describe("ConsoleOutput integration", () => {
50
55
  ],
51
56
  };
52
57
 
53
- render(
54
- <TooltipProvider>
55
- <ConsoleOutput {...props} />
56
- </TooltipProvider>,
57
- );
58
+ renderWithProvider(<ConsoleOutput {...props} />);
58
59
 
59
60
  const link = screen.getByRole("link", { name: "https://marimo.io" });
60
61
  expect(link).toBeInTheDocument();
61
62
  expect(link).toHaveAttribute("href", "https://marimo.io");
62
63
  });
63
64
  });
65
+
66
+ describe("ConsoleOutput pdb history", () => {
67
+ const defaultProps = {
68
+ cellId: "cell-1" as CellId,
69
+ cellName: "test_cell",
70
+ consoleOutputs: [] as WithResponse<OutputMessage>[],
71
+ stale: false,
72
+ debuggerActive: false,
73
+ onSubmitDebugger: vi.fn(),
74
+ };
75
+
76
+ const stdinPrompt = (
77
+ data: string,
78
+ response?: string,
79
+ ): WithResponse<OutputMessage> => ({
80
+ channel: "stdin" as const,
81
+ mimetype: "text/plain",
82
+ data,
83
+ timestamp: 0,
84
+ response,
85
+ });
86
+
87
+ it("should persist command history across StdInput remounts", () => {
88
+ // Initial state: pdb prompt waiting for input
89
+ const outputs1: WithResponse<OutputMessage>[] = [stdinPrompt("(Pdb) ")];
90
+
91
+ const onSubmitDebugger = vi.fn();
92
+ const { rerender } = renderWithProvider(
93
+ <ConsoleOutput
94
+ {...defaultProps}
95
+ consoleOutputs={outputs1}
96
+ onSubmitDebugger={onSubmitDebugger}
97
+ />,
98
+ );
99
+
100
+ const input = screen.getByTestId("console-input");
101
+
102
+ // Type "next" and submit
103
+ fireEvent.change(input, { target: { value: "next" } });
104
+ fireEvent.keyDown(input, { key: "Enter" });
105
+
106
+ expect(onSubmitDebugger).toHaveBeenCalledWith("next", 0);
107
+
108
+ // Simulate server response: old stdin gets a response, new stdin prompt appears
109
+ const outputs2: WithResponse<OutputMessage>[] = [
110
+ stdinPrompt("(Pdb) ", "next"),
111
+ stdinPrompt("(Pdb) "),
112
+ ];
113
+
114
+ rerender(
115
+ <TooltipProvider>
116
+ <ConsoleOutput
117
+ {...defaultProps}
118
+ consoleOutputs={outputs2}
119
+ onSubmitDebugger={onSubmitDebugger}
120
+ />
121
+ </TooltipProvider>,
122
+ );
123
+
124
+ // New StdInput mounted — press ArrowUp to recall previous command
125
+ const newInput = screen.getByTestId("console-input");
126
+ fireEvent.keyDown(newInput, { key: "ArrowUp" });
127
+
128
+ expect(newInput).toHaveValue("next");
129
+ });
130
+
131
+ it("should navigate through multiple history entries across remounts", () => {
132
+ const onSubmitDebugger = vi.fn();
133
+
134
+ // First prompt
135
+ const outputs1: WithResponse<OutputMessage>[] = [stdinPrompt("(Pdb) ")];
136
+
137
+ const { rerender } = renderWithProvider(
138
+ <ConsoleOutput
139
+ {...defaultProps}
140
+ consoleOutputs={outputs1}
141
+ onSubmitDebugger={onSubmitDebugger}
142
+ />,
143
+ );
144
+
145
+ // Submit "step"
146
+ let input = screen.getByTestId("console-input");
147
+ fireEvent.change(input, { target: { value: "step" } });
148
+ fireEvent.keyDown(input, { key: "Enter" });
149
+
150
+ // Second prompt
151
+ const outputs2: WithResponse<OutputMessage>[] = [
152
+ stdinPrompt("(Pdb) ", "step"),
153
+ stdinPrompt("(Pdb) "),
154
+ ];
155
+
156
+ rerender(
157
+ <TooltipProvider>
158
+ <ConsoleOutput
159
+ {...defaultProps}
160
+ consoleOutputs={outputs2}
161
+ onSubmitDebugger={onSubmitDebugger}
162
+ />
163
+ </TooltipProvider>,
164
+ );
165
+
166
+ // Submit "print(x)"
167
+ input = screen.getByTestId("console-input");
168
+ fireEvent.change(input, { target: { value: "print(x)" } });
169
+ fireEvent.keyDown(input, { key: "Enter" });
170
+
171
+ // Third prompt
172
+ const outputs3: WithResponse<OutputMessage>[] = [
173
+ stdinPrompt("(Pdb) ", "step"),
174
+ stdinPrompt("(Pdb) ", "print(x)"),
175
+ stdinPrompt("(Pdb) "),
176
+ ];
177
+
178
+ rerender(
179
+ <TooltipProvider>
180
+ <ConsoleOutput
181
+ {...defaultProps}
182
+ consoleOutputs={outputs3}
183
+ onSubmitDebugger={onSubmitDebugger}
184
+ />
185
+ </TooltipProvider>,
186
+ );
187
+
188
+ // ArrowUp should show most recent command first
189
+ input = screen.getByTestId("console-input");
190
+ fireEvent.keyDown(input, { key: "ArrowUp" });
191
+ expect(input).toHaveValue("print(x)");
192
+
193
+ // ArrowUp again should show older command
194
+ fireEvent.keyDown(input, { key: "ArrowUp" });
195
+ expect(input).toHaveValue("step");
196
+
197
+ // ArrowDown should go back to "print(x)"
198
+ fireEvent.keyDown(input, { key: "ArrowDown" });
199
+ expect(input).toHaveValue("print(x)");
200
+
201
+ // ArrowDown again should return to empty input
202
+ fireEvent.keyDown(input, { key: "ArrowDown" });
203
+ expect(input).toHaveValue("");
204
+ });
205
+ });
@@ -8,7 +8,7 @@ interface UseInputHistoryOptions {
8
8
  setValue: (value: string) => void;
9
9
  }
10
10
 
11
- interface UseInputHistoryReturn {
11
+ export interface UseInputHistoryReturn {
12
12
  /** Command history array */
13
13
  history: string[];
14
14
  /** Navigate to previous command (ArrowUp) */