@marimo-team/islands 0.22.4-dev1 → 0.22.4-dev10

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 (43) hide show
  1. package/dist/{ConnectedDataExplorerComponent-DuD8BVl6.js → ConnectedDataExplorerComponent-mLj6D01z.js} +9 -9
  2. package/dist/{any-language-editor-BHH_pQ6M.js → any-language-editor-BIGc8RUt.js} +3 -3
  3. package/dist/assets/__vite-browser-external-C4JkHbyY.js +1 -0
  4. package/dist/assets/{worker-DUYMdbtA.js → worker-D-EdLKct.js} +2 -2
  5. package/dist/{chat-ui-Cel1kBfc.js → chat-ui-DfR3sT9K.js} +12 -12
  6. package/dist/{check-CWUkiHmb.js → check-Cex3x9fD.js} +1 -1
  7. package/dist/{copy-B7781WJ3.js → copy-BRF7ryOP.js} +1 -1
  8. package/dist/{dist-D_UjpfOY.js → dist-D56NKWim.js} +12 -11
  9. package/dist/{error-banner-Cjf0RU9I.js → error-banner-DexD-5js.js} +1 -1
  10. package/dist/{esm-4wmsH2lp.js → esm-BGo_Mcdt.js} +3 -3
  11. package/dist/{glide-data-editor-BqnvTmDo.js → glide-data-editor-BmyQCm0U.js} +6 -6
  12. package/dist/{input-CFY9gApZ.js → input-SSWXiS6n.js} +9 -9
  13. package/dist/{label-DbZGAoCH.js → label-CIR53v8V.js} +24 -24
  14. package/dist/main.js +98 -82
  15. package/dist/{mermaid-B2HDLx2g.js → mermaid-B93TKi2g.js} +4 -4
  16. package/dist/{process-output-DC1TOnIl.js → process-output-BvkX_OeE.js} +2841 -2279
  17. package/dist/{spec-CD7QaCV-.js → spec-ByDEU1T3.js} +3 -3
  18. package/dist/style.css +1 -1
  19. package/dist/{toDate-CUqpEbBS.js → toDate-D1_ZulwM.js} +2 -2
  20. package/dist/{tooltip-BXEpXV3R.js → tooltip-B5EnNyok.js} +2 -2
  21. package/dist/{types-D_ntCXg0.js → types-D4-TD_m0.js} +1 -1
  22. package/dist/{useAsyncData-rN1nzPaS.js → useAsyncData-C9ez7Ilo.js} +1 -1
  23. package/dist/{useDeepCompareMemoize-Ch-7Rk2x.js → useDeepCompareMemoize-BvvMxigY.js} +2 -2
  24. package/dist/{useLifecycle-4fA1pHoh.js → useLifecycle-2Vh-WDv6.js} +2 -2
  25. package/dist/{useTheme-MWfxn4oz.js → useTheme-CxjbgkRc.js} +3 -2
  26. package/dist/{vega-component-CPhNLfZZ.js → vega-component-Bzzut3-P.js} +7 -7
  27. package/dist/{zod-C6UGQ3fz.js → zod-D18k8Z52.js} +14 -12
  28. package/package.json +1 -1
  29. package/src/components/editor/TracebackModalContainer.tsx +22 -0
  30. package/src/components/editor/errors/traceback-modal.tsx +87 -0
  31. package/src/components/editor/output/MarimoErrorOutput.tsx +23 -3
  32. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +16 -2
  33. package/src/components/ui/toast.tsx +3 -1
  34. package/src/core/MarimoApp.tsx +2 -0
  35. package/src/core/cells/__tests__/apply-transaction.test.ts +28 -0
  36. package/src/core/cells/cells.ts +13 -1
  37. package/src/core/cells/logs.ts +48 -9
  38. package/src/core/config/__tests__/config-schema.test.ts +2 -0
  39. package/src/core/config/config-schema.ts +1 -0
  40. package/src/core/errors/traceback-atom.ts +9 -0
  41. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +8 -6
  42. package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +25 -0
  43. package/dist/assets/__vite-browser-external-WSlCcXn_.js +0 -1
@@ -3,10 +3,10 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { t as __commonJSMin } from "./chunk-BNovOVIE.js";
5
5
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
6
- import { l as createLucideIcon } from "./dist-D_UjpfOY.js";
6
+ import { u as createLucideIcon } from "./dist-D56NKWim.js";
7
7
  import { g as Logger } from "./button-DNlNlZY_.js";
8
8
  import { r as KnownQueryParams } from "./constants-CvyfaCvs.js";
9
- import { f as waitFor, p as isIslands, u as store, y as atom } from "./useTheme-MWfxn4oz.js";
9
+ import { f as waitFor, p as isIslands, u as store, y as atom } from "./useTheme-CxjbgkRc.js";
10
10
  var CircleQuestionMark = createLucideIcon("circle-question-mark", [
11
11
  ["circle", {
12
12
  cx: "12",
@@ -1,10 +1,10 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { a as createPopperScope, i as Root2, n as Arrow, r as Content, s as Root, t as Anchor } from "./dist-D_UjpfOY.js";
4
+ import { a as createPopperScope, i as Root2, n as Arrow, r as Content, s as Root, t as Anchor } from "./dist-D56NKWim.js";
5
5
  import { f as createSlottable, m as useComposedRefs, y as cn } from "./button-DNlNlZY_.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
- import { $ as StyleNamespace, X as withFullScreenAsRoot, Z as withSmartCollisionBoundary, ct as useId, ft as composeEventHandlers, ht as Primitive, it as Portal, lt as Presence, mt as createContextScope, ot as DismissableLayer, ut as useControllableState } from "./zod-C6UGQ3fz.js";
7
+ import { $ as StyleNamespace, X as withFullScreenAsRoot, Z as withSmartCollisionBoundary, _t as Primitive, dt as Presence, ft as useControllableState, gt as createContextScope, it as Portal, mt as composeEventHandlers, st as DismissableLayer, ut as useId } from "./zod-D18k8Z52.js";
8
8
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1), import_jsx_runtime = /* @__PURE__ */ __toESM(require_jsx_runtime(), 1), [createTooltipContext, createTooltipScope] = createContextScope("Tooltip", [createPopperScope]), usePopperScope = createPopperScope(), PROVIDER_NAME = "TooltipProvider", DEFAULT_DELAY_DURATION = 700, TOOLTIP_OPEN = "tooltip.open", [TooltipProviderContextProvider, useTooltipProviderContext] = createTooltipContext(PROVIDER_NAME), TooltipProvider$1 = (t) => {
9
9
  let { __scopeTooltip: j, delayDuration: M = DEFAULT_DELAY_DURATION, skipDelayDuration: N = 300, disableHoverableContent: P = false, children: F } = t, I = import_react.useRef(true), L = import_react.useRef(false), R = import_react.useRef(0);
10
10
  return import_react.useEffect(() => {
@@ -1,7 +1,7 @@
1
1
  import { s as __toESM, t as __commonJSMin } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { l as createLucideIcon } from "./dist-D_UjpfOY.js";
4
+ import { u as createLucideIcon } from "./dist-D56NKWim.js";
5
5
  import { t as Button } from "./button-DNlNlZY_.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
7
  import { n as Constants } from "./constants-CvyfaCvs.js";
@@ -1,7 +1,7 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { w as useEvent_default } from "./useTheme-MWfxn4oz.js";
4
+ import { w as useEvent_default } from "./useTheme-CxjbgkRc.js";
5
5
  import { t as invariant } from "./invariant-e8eBgdux.js";
6
6
  var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1), Result = {
7
7
  error(e, s) {
@@ -1,10 +1,10 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { t as toDate } from "./toDate-CUqpEbBS.js";
4
+ import { t as toDate } from "./toDate-D1_ZulwM.js";
5
5
  import { r as cva, y as cn } from "./button-DNlNlZY_.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
- import { C as dequal } from "./useTheme-MWfxn4oz.js";
7
+ import { C as dequal } from "./useTheme-CxjbgkRc.js";
8
8
  import { i as tableFromIPC } from "./loader-Bd1kgLn7.js";
9
9
  function isDate(t) {
10
10
  return t instanceof Date || typeof t == "object" && Object.prototype.toString.call(t) === "[object Date]";
@@ -1,10 +1,10 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { l as createLucideIcon } from "./dist-D_UjpfOY.js";
4
+ import { u as createLucideIcon } from "./dist-D56NKWim.js";
5
5
  import { g as Logger, r as cva, y as cn } from "./button-DNlNlZY_.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
7
- import { _ as useSetAtom, y as atom } from "./useTheme-MWfxn4oz.js";
7
+ import { _ as useSetAtom, y as atom } from "./useTheme-CxjbgkRc.js";
8
8
  var Calendar = createLucideIcon("calendar", [
9
9
  ["path", {
10
10
  d: "M8 2v4",
@@ -2,7 +2,7 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
4
  import { a as OverridingHotkeyProvider, g as Logger, s as resolvePlatform } from "./button-DNlNlZY_.js";
5
- import { A as looseObject, B as union, I as record, N as number, P as object, R as string, T as boolean, b as _enum, w as array } from "./zod-C6UGQ3fz.js";
5
+ import { A as looseObject, B as union, I as record, N as number, P as object, R as string, T as boolean, b as _enum, w as array } from "./zod-D18k8Z52.js";
6
6
  import { t as merge_default } from "./merge-CVhG7q_o.js";
7
7
  var import_react = /* @__PURE__ */ __toESM(require_react()), useInsertionEffect = typeof window < "u" ? import_react.useInsertionEffect || import_react.useLayoutEffect : () => {
8
8
  };
@@ -585,7 +585,8 @@ const UserConfigSchema = looseObject({
585
585
  reactive_tests: boolean().prefault(true),
586
586
  watcher_on_save: _enum(["lazy", "autorun"]).prefault("lazy"),
587
587
  default_sql_output: _enum(VALID_SQL_OUTPUT_FORMATS).prefault("auto"),
588
- default_auto_download: array(_enum(AUTO_DOWNLOAD_FORMATS)).prefault([])
588
+ default_auto_download: array(_enum(AUTO_DOWNLOAD_FORMATS)).prefault([]),
589
+ show_tracebacks: boolean().prefault(false)
589
590
  }).prefault({}),
590
591
  display: looseObject({
591
592
  theme: _enum([
@@ -1,20 +1,20 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-Bs6Z0kvn.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
4
- import { c as asRemoteURL, g as CircleQuestionMark } from "./toDate-CUqpEbBS.js";
4
+ import { c as asRemoteURL, g as CircleQuestionMark } from "./toDate-D1_ZulwM.js";
5
5
  import { c as Objects, g as Logger, h as Events, y as cn } from "./button-DNlNlZY_.js";
6
6
  import "./react-dom-BSUuJjCR.js";
7
7
  import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
8
- import "./zod-C6UGQ3fz.js";
9
- import { n as ErrorBanner } from "./error-banner-Cjf0RU9I.js";
10
- import { t as Tooltip } from "./tooltip-BXEpXV3R.js";
8
+ import "./zod-D18k8Z52.js";
9
+ import { n as ErrorBanner } from "./error-banner-DexD-5js.js";
10
+ import { t as Tooltip } from "./tooltip-B5EnNyok.js";
11
11
  import { i as debounce_default } from "./constants-CvyfaCvs.js";
12
- import { n as useTheme, w as useEvent_default } from "./useTheme-MWfxn4oz.js";
12
+ import { n as useTheme, w as useEvent_default } from "./useTheme-CxjbgkRc.js";
13
13
  import { s as uniq } from "./arrays-beUWo8RF.js";
14
- import { a as AlertTitle, n as arrow, o as isValid, r as Alert, t as useDeepCompareMemoize } from "./useDeepCompareMemoize-Ch-7Rk2x.js";
14
+ import { a as AlertTitle, n as arrow, o as isValid, r as Alert, t as useDeepCompareMemoize } from "./useDeepCompareMemoize-BvvMxigY.js";
15
15
  import { n as formats } from "./vega-loader.browser-DqEcFOPD.js";
16
16
  import { a as getContainerWidth, n as vegaLoadData, s as tooltipHandler } from "./loader-Bd1kgLn7.js";
17
- import { t as useAsyncData } from "./useAsyncData-rN1nzPaS.js";
17
+ import { t as useAsyncData } from "./useAsyncData-C9ez7Ilo.js";
18
18
  import { t as j } from "./react-vega-CzRAIHrv.js";
19
19
  import "./defaultLocale-qS7DaAmi.js";
20
20
  import "./defaultLocale-Bxoo2-30.js";
@@ -349,7 +349,7 @@ function handleAndDispatchCustomEvent(e, E, D, { discrete: O }) {
349
349
  });
350
350
  E && k.addEventListener(e, E, { once: true }), O ? dispatchDiscreteCustomEvent(k, A) : k.dispatchEvent(A);
351
351
  }
352
- var AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount", AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount", EVENT_OPTIONS = {
352
+ var Root = DismissableLayer, Branch = DismissableLayerBranch, AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount", AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount", EVENT_OPTIONS = {
353
353
  bubbles: false,
354
354
  cancelable: true
355
355
  }, FOCUS_SCOPE_NAME = "FocusScope", FocusScope = import_react.forwardRef((e, E) => {
@@ -11786,44 +11786,46 @@ export {
11786
11786
  MAX_HEIGHT_OFFSET as Y,
11787
11787
  withSmartCollisionBoundary as Z,
11788
11788
  ZodString as _,
11789
+ Primitive as _t,
11789
11790
  ZodIssueCode as a,
11790
11791
  FocusScope as at,
11791
11792
  _enum as b,
11792
11793
  ZodBoolean as c,
11793
- useId as ct,
11794
+ Root as ct,
11794
11795
  ZodDiscriminatedUnion as d,
11795
- useLayoutEffect2 as dt,
11796
+ Presence as dt,
11796
11797
  hideOthers as et,
11797
11798
  ZodEnum as f,
11798
- composeEventHandlers as ft,
11799
+ useControllableState as ft,
11799
11800
  ZodOptional as g,
11800
- dispatchDiscreteCustomEvent as gt,
11801
+ createContextScope as gt,
11801
11802
  ZodObject as h,
11802
- Primitive as ht,
11803
+ createContext2 as ht,
11803
11804
  string as i,
11804
11805
  Portal as it,
11805
11806
  nan as j,
11806
11807
  literal as k,
11807
11808
  ZodDate as l,
11808
- Presence as lt,
11809
+ useCallbackRef$1 as lt,
11809
11810
  ZodNumber as m,
11810
- createContextScope as mt,
11811
+ composeEventHandlers as mt,
11811
11812
  date as n,
11812
11813
  __awaiter as nt,
11813
11814
  ZodAny as o,
11814
- DismissableLayer as ot,
11815
+ Branch as ot,
11815
11816
  ZodLiteral as p,
11816
- createContext2 as pt,
11817
+ useLayoutEffect2 as pt,
11817
11818
  $ZodError as q,
11818
11819
  number as r,
11819
11820
  useFocusGuards as rt,
11820
11821
  ZodArray as s,
11821
- useCallbackRef$1 as st,
11822
+ DismissableLayer as st,
11822
11823
  zod_default as t,
11823
11824
  Combination_default as tt,
11824
11825
  ZodDefault as u,
11825
- useControllableState as ut,
11826
+ useId as ut,
11826
11827
  ZodType as v,
11828
+ dispatchDiscreteCustomEvent as vt,
11827
11829
  array as w,
11828
11830
  _instanceof as x,
11829
11831
  ZodUnion as y,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.22.4-dev1",
3
+ "version": "0.22.4-dev10",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,22 @@
1
+ /* Copyright 2024 Marimo. All rights reserved. */
2
+ import React from "react";
3
+ import { useAtom } from "jotai";
4
+ import { tracebackModalAtom } from "@/core/errors/traceback-atom";
5
+ import { TracebackModal } from "./errors/traceback-modal";
6
+
7
+ export const TracebackModalContainer: React.FC = () => {
8
+ const [tracebackData, setTracebackData] = useAtom(tracebackModalAtom);
9
+
10
+ if (!tracebackData) {
11
+ return null;
12
+ }
13
+
14
+ return (
15
+ <TracebackModal
16
+ isOpen={true}
17
+ onClose={() => setTracebackData(null)}
18
+ traceback={tracebackData.traceback}
19
+ errorMessage={tracebackData.errorMessage}
20
+ />
21
+ );
22
+ };
@@ -0,0 +1,87 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import React from "react";
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ AlertDialog,
6
+ AlertDialogAction,
7
+ AlertDialogContent,
8
+ AlertDialogDescription,
9
+ AlertDialogFooter,
10
+ AlertDialogHeader,
11
+ AlertDialogTitle,
12
+ } from "@/components/ui/alert-dialog";
13
+ import { CopyIcon } from "lucide-react";
14
+ import { toast } from "@/components/ui/use-toast";
15
+
16
+ interface TracebackModalProps {
17
+ isOpen: boolean;
18
+ onClose: () => void;
19
+ traceback: string;
20
+ errorMessage: string;
21
+ }
22
+
23
+ export const TracebackModal: React.FC<TracebackModalProps> = ({
24
+ isOpen,
25
+ onClose,
26
+ traceback,
27
+ errorMessage,
28
+ }) => {
29
+ const handleCopy = async () => {
30
+ // Strip HTML tags for clipboard
31
+ const tempDiv = document.createElement("div");
32
+ tempDiv.innerHTML = traceback;
33
+ const textContent = tempDiv.textContent || tempDiv.innerText || "";
34
+
35
+ try {
36
+ await navigator.clipboard.writeText(textContent);
37
+ toast({
38
+ title: "Copied to clipboard",
39
+ description: "Traceback has been copied to your clipboard.",
40
+ });
41
+ } catch {
42
+ toast({
43
+ title: "Failed to copy",
44
+ description: "Could not copy to clipboard.",
45
+ variant: "danger",
46
+ });
47
+ }
48
+ };
49
+
50
+ return (
51
+ <AlertDialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
52
+ <AlertDialogContent className="max-w-4xl max-h-[80vh]">
53
+ <AlertDialogHeader>
54
+ <AlertDialogTitle className="text-destructive">
55
+ {errorMessage}
56
+ </AlertDialogTitle>
57
+ <AlertDialogDescription>
58
+ Click the traceback to select and copy.
59
+ </AlertDialogDescription>
60
+ </AlertDialogHeader>
61
+ <div className="my-4 overflow-auto">
62
+ <div className="flex items-center justify-between mb-2">
63
+ <span className="text-sm font-medium text-muted-foreground">
64
+ Traceback
65
+ </span>
66
+ <Button
67
+ variant="outline"
68
+ size="xs"
69
+ onClick={handleCopy}
70
+ className="flex items-center gap-1"
71
+ >
72
+ <CopyIcon className="h-3 w-3" />
73
+ Copy
74
+ </Button>
75
+ </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
+ />
80
+ </div>
81
+ <AlertDialogFooter>
82
+ <AlertDialogAction onClick={onClose}>Close</AlertDialogAction>
83
+ </AlertDialogFooter>
84
+ </AlertDialogContent>
85
+ </AlertDialog>
86
+ );
87
+ };
@@ -477,6 +477,7 @@ export const MarimoErrorOutput = ({
477
477
  );
478
478
  }
479
479
 
480
+ // All other exceptions
480
481
  return (
481
482
  <li className="my-2" key={`exception-${idx}`}>
482
483
  {error.raising_cell == null ? (
@@ -484,14 +485,33 @@ export const MarimoErrorOutput = ({
484
485
  <p className="text-muted-foreground">
485
486
  {processTextForUrls(error.msg, `exception-${idx}`)}
486
487
  </p>
487
- <div className="text-muted-foreground mt-2">
488
- See the console area for a traceback.
489
- </div>
488
+ {"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
+ />
496
+ ) : (
497
+ <div className="text-muted-foreground mt-2">
498
+ See the console area for a traceback.
499
+ </div>
500
+ )}
490
501
  </div>
491
502
  ) : (
492
503
  <div>
493
504
  {processTextForUrls(error.msg, `exception-${idx}`)}
494
505
  <CellLinkError cellId={error.raising_cell} />
506
+ {"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
+ />
514
+ )}
495
515
  </div>
496
516
  )}
497
517
  </li>
@@ -32,7 +32,7 @@ import type { CellData, CellRuntimeState } from "@/core/cells/types";
32
32
  import { MarkdownLanguageAdapter } from "@/core/codemirror/language/languages/markdown";
33
33
  import { useResolvedMarimoConfig } from "@/core/config/config";
34
34
  import { CSSClasses, KnownQueryParams } from "@/core/constants";
35
- import type { OutputMessage } from "@/core/kernel/messages";
35
+ import type { MarimoError, OutputMessage } from "@/core/kernel/messages";
36
36
  import { kernelStateAtom } from "@/core/kernel/state";
37
37
  import { showCodeInRunModeAtom } from "@/core/meta/state";
38
38
  import { isErrorMime } from "@/core/mime";
@@ -121,6 +121,7 @@ const VerticalLayoutRenderer: React.FC<VerticalLayoutProps> = ({
121
121
  staleInputs={cell.staleInputs}
122
122
  name={cell.name}
123
123
  kiosk={kioskMode}
124
+ showErrorTracebacks={userConfig.runtime.show_tracebacks ?? false}
124
125
  />
125
126
  );
126
127
  };
@@ -330,6 +331,7 @@ interface VerticalCellProps extends Pick<
330
331
  showCode: boolean;
331
332
  name: string;
332
333
  kiosk: boolean;
334
+ showErrorTracebacks: boolean;
333
335
  }
334
336
 
335
337
  const VerticalCell = memo(
@@ -350,6 +352,7 @@ const VerticalCell = memo(
350
352
  mode,
351
353
  name,
352
354
  kiosk,
355
+ showErrorTracebacks,
353
356
  }: VerticalCellProps) => {
354
357
  const cellRef = useRef<HTMLDivElement>(null);
355
358
 
@@ -427,7 +430,18 @@ const VerticalCell = memo(
427
430
  }
428
431
 
429
432
  const outputIsError = isErrorMime(output?.mimetype);
430
- const hidden = errored || interrupted || stopped || outputIsError;
433
+ // When show_tracebacks is enabled, show error outputs inline
434
+ // instead of hiding them
435
+ const hasTraceback =
436
+ showErrorTracebacks &&
437
+ outputIsError &&
438
+ Array.isArray(output?.data) &&
439
+ output.data.some(
440
+ (e: MarimoError) =>
441
+ e.type === "exception" && "traceback" in e && e.traceback,
442
+ );
443
+ const hidden =
444
+ (errored || interrupted || stopped || outputIsError) && !hasTraceback;
431
445
  if (hidden) {
432
446
  return null;
433
447
  }
@@ -118,7 +118,9 @@ ToastDescription.displayName = ToastPrimitives.Description.displayName;
118
118
 
119
119
  type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
120
120
 
121
- type ToastActionElement = React.ReactElement<typeof ToastAction>;
121
+ type ToastActionElement = React.ReactElement<
122
+ React.ComponentProps<typeof ToastAction>
123
+ >;
122
124
 
123
125
  export {
124
126
  type ToastProps,
@@ -13,6 +13,7 @@ import { CssVariables } from "@/theme/ThemeProvider";
13
13
  import { reactLazyWithPreload } from "@/utils/lazy";
14
14
  import { ErrorBoundary } from "../components/editor/boundary/ErrorBoundary";
15
15
  import { KernelStartupErrorModal } from "../components/editor/KernelStartupErrorModal";
16
+ import { TracebackModalContainer } from "../components/editor/TracebackModalContainer";
16
17
  import { ModalProvider } from "../components/modal/ImperativeModal";
17
18
  import { Toaster } from "../components/ui/toaster";
18
19
  import { TooltipProvider } from "../components/ui/tooltip";
@@ -105,6 +106,7 @@ const Providers = memo(({ children }: PropsWithChildren) => {
105
106
  <Toaster />
106
107
  <TailwindIndicator />
107
108
  <KernelStartupErrorModal />
109
+ <TracebackModalContainer />
108
110
  </ModalProvider>
109
111
  </LocaleProvider>
110
112
  </SlotzProvider>
@@ -267,6 +267,34 @@ describe("applyTransactionChanges edge cases", () => {
267
267
  `);
268
268
  });
269
269
 
270
+ it("delete-cell for nonexistent cell does not crash subsequent changes", () => {
271
+ setup("a", "b", "c");
272
+ const [, b] = state.cellIds.inOrderIds;
273
+ // Simulate the scenario from the bug report: a delete-cell for a cell ID
274
+ // that was never added to the frontend, followed by a create-cell and
275
+ // a set-code update. The delete should be silently skipped, and the rest
276
+ // of the transaction should still apply.
277
+ apply([
278
+ { type: "delete-cell", cellId: cellId("nonexistent") },
279
+ {
280
+ type: "create-cell",
281
+ cellId: cellId("VrZA"),
282
+ code: "import altair as alt",
283
+ name: "",
284
+ config: { hide_code: true },
285
+ },
286
+ { type: "set-code", cellId: b, code: "updated" },
287
+ ]);
288
+ expect(pretty(state)).toMatchInlineSnapshot(`
289
+ "
290
+ 0: 'a'
291
+ 1: 'updated'
292
+ 2: 'c'
293
+ VrZA: 'import altair as alt' [hide_code]
294
+ "
295
+ `);
296
+ });
297
+
270
298
  it("empty changes is a no-op", () => {
271
299
  setup("a", "b");
272
300
  apply([]);
@@ -6,6 +6,7 @@ import { type Atom, atom, useAtom, useAtomValue } from "jotai";
6
6
  import { atomFamily, selectAtom, splitAtom } from "jotai/utils";
7
7
  import { createRef, type ReducerWithoutAction } from "react";
8
8
  import type { CellHandle } from "@/components/editor/notebook-cell";
9
+ import type { CollapsibleTree } from "@/utils/id-tree";
9
10
  import {
10
11
  type CellColumnId,
11
12
  type CellIndex,
@@ -578,7 +579,18 @@ const {
578
579
  return state;
579
580
  }
580
581
 
581
- const column = state.cellIds.findWithId(cellId);
582
+ let column: CollapsibleTree<CellId>;
583
+ try {
584
+ column = state.cellIds.findWithId(cellId);
585
+ } catch (error) {
586
+ // Expected for kernel-only cells or out-of-order transactions.
587
+ Logger.warn("Skipping delete for missing cellId", {
588
+ cellId,
589
+ error,
590
+ });
591
+ return state;
592
+ }
593
+
582
594
  const cellIndex = column.indexOfOrThrow(cellId);
583
595
  const focusIndex = cellIndex === 0 ? 1 : cellIndex - 1;
584
596
  let scrollKey: CellId | null = null;
@@ -7,6 +7,10 @@ import { Strings } from "@/utils/strings";
7
7
  import type { CellMessage, OutputMessage } from "../kernel/messages";
8
8
  import { isErrorMime } from "../mime";
9
9
  import type { CellId } from "./ids";
10
+ import { store } from "../state/jotai";
11
+ import { tracebackModalAtom } from "../errors/traceback-atom";
12
+ import React from "react";
13
+ import { ToastAction } from "@/components/ui/toast";
10
14
 
11
15
  export interface CellLog {
12
16
  timestamp: number;
@@ -72,17 +76,52 @@ export function getCellLogsForMessage(cell: CellMessage): CellLog[] {
72
76
  });
73
77
  });
74
78
 
75
- const shouldToast = cell.output.data.some(
76
- (error) => error.type === "internal",
79
+ // Find exception errors and check for traceback
80
+ const exceptionErrors = cell.output.data.filter(
81
+ (error) =>
82
+ ("type" in error && error.type === "exception") ||
83
+ error.type === "internal",
77
84
  );
78
- if (!didAlreadyToastError && shouldToast) {
85
+
86
+ if (exceptionErrors.length > 0 && !didAlreadyToastError) {
79
87
  didAlreadyToastError = true;
80
- toast({
81
- title: "An internal error occurred",
82
- description: "See console for details.",
83
- className:
84
- "text-xs text-background bg-(--red-10) py-2 pl-3 *:flex *:gap-3",
85
- });
88
+
89
+ // Find first error with a traceback
90
+ const errorWithTraceback = exceptionErrors.find(
91
+ (error) => error.traceback,
92
+ );
93
+
94
+ if (errorWithTraceback) {
95
+ // Show toast with action button to open modal
96
+ const handleClick = () => {
97
+ store.set(tracebackModalAtom, {
98
+ traceback: errorWithTraceback.traceback,
99
+ errorMessage:
100
+ errorWithTraceback.msg || "An internal error occurred",
101
+ });
102
+ };
103
+
104
+ toast({
105
+ title: "An internal error occurred",
106
+ description:
107
+ errorWithTraceback.msg || "Click 'View' to see traceback",
108
+ variant: "danger",
109
+ action: React.createElement(
110
+ ToastAction,
111
+ {
112
+ altText: "View traceback",
113
+ onClick: handleClick,
114
+ },
115
+ "View",
116
+ ),
117
+ });
118
+ } else {
119
+ toast({
120
+ title: "An internal error occurred",
121
+ description: "See console for details.",
122
+ variant: "danger",
123
+ });
124
+ }
86
125
  }
87
126
  }
88
127
 
@@ -90,6 +90,7 @@ test("default UserConfig - empty", () => {
90
90
  "default_sql_output": "auto",
91
91
  "on_cell_change": "autorun",
92
92
  "reactive_tests": true,
93
+ "show_tracebacks": false,
93
94
  "watcher_on_save": "lazy",
94
95
  },
95
96
  "save": {
@@ -160,6 +161,7 @@ test("default UserConfig - one level", () => {
160
161
  "default_sql_output": "auto",
161
162
  "on_cell_change": "autorun",
162
163
  "reactive_tests": true,
164
+ "show_tracebacks": false,
163
165
  "watcher_on_save": "lazy",
164
166
  },
165
167
  "save": {
@@ -126,6 +126,7 @@ export const UserConfigSchema = z
126
126
  default_auto_download: z
127
127
  .array(z.enum(AUTO_DOWNLOAD_FORMATS))
128
128
  .prefault([]),
129
+ show_tracebacks: z.boolean().prefault(false),
129
130
  })
130
131
  .prefault({}),
131
132
  display: z
@@ -0,0 +1,9 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { atom } from "jotai";
3
+
4
+ export interface TracebackData {
5
+ traceback: string;
6
+ errorMessage: string;
7
+ }
8
+
9
+ export const tracebackModalAtom = atom<TracebackData | null>(null);