@marimo-team/islands 0.23.14-dev46 → 0.23.14-dev49

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.
@@ -36207,7 +36207,7 @@ ${d}`,
36207
36207
  return Logger.warn("Failed to get version from mount config"), null;
36208
36208
  }
36209
36209
  }
36210
- marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.14-dev46");
36210
+ marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.14-dev49");
36211
36211
  showCodeInRunModeAtom = atom(true);
36212
36212
  atom(null);
36213
36213
  var import_compiler_runtime = require_compiler_runtime();
package/dist/main.js CHANGED
@@ -26,7 +26,7 @@ import { $ as reducer, $t as getRequestClient, A as ChevronDownIcon, An as Trigg
26
26
  import { __tla as __tla_1 } from "./chunk-5FQGJX7Z-BbqSm5gU.js";
27
27
  import { o as useSize, s as Root$1, u as createLucideIcon } from "./dist--2Bqjvs0.js";
28
28
  import { A as SquareFunction, C as DEFAULT_COLOR_SCHEME, D as SCALE_TYPE_DESCRIPTIONS, E as EMPTY_VALUE$1, O as TIME_UNIT_DESCRIPTIONS, S as DEFAULT_AGGREGATION, T as DEFAULT_TIME_UNIT, _ as AGGREGATION_TYPE_DESCRIPTIONS, a as AGGREGATION_FNS$1, b as COLOR_SCHEMES, c as COLOR_BY_FIELDS, d as NONE_VALUE, f as SELECTABLE_DATA_TYPES, g as TIME_UNITS, h as STRING_AGGREGATION_FNS, i as convertDataTypeToSelectable, j as ChartColumn, k as escapeFieldName, l as COMBINED_TIME_UNITS, m as SORT_TYPES, n as createSpecWithoutData, o as BIN_AGGREGATION, p as SINGLE_TIME_UNITS, r as isFieldSet, s as CHART_TYPES, t as augmentSpecWithData, u as ChartType, v as AGGREGATION_TYPE_ICON, w as DEFAULT_MAX_BINS_FACET, x as COUNT_FIELD, y as CHART_TYPE_ICON } from "./spec-CnTgI25l.js";
29
- import { $ as PANEL_TYPES, $t as HtmlOutput, A as prettifyRowCount, At as INDEX_COLUMN_NAME, B as CompactChipRow, Bt as dateToLocalISODateTime, C as downloadSizeLimitAtom, Ct as ColumnChartSpecModel, D as ErrorState, Dt as getPageIndexForRow, E as EmptyState, Et as usePrevious$1, F as ContextMenuItem, Ft as hasFunctionProperty, G as Command, Gt as TabsTrigger, H as DateRangePicker, Ht as Tabs, I as ContextMenuSeparator, It as isRecord, J as CommandItem, Jt as ChartLoadingState, K as CommandEmpty, Kt as ChartErrorState, L as ContextMenuTrigger, Lt as isNullishFilter, M as getUserColumnVisibilityCounts, Mt as TOO_MANY_ROWS, N as ContextMenu, Nt as toFieldTypes, O as LoadingState, Ot as loadTableAndRawData, P as ContextMenuContent, Pt as getMimeValues, Q as ContextAwarePanelItem, Qt as Kbd, R as useInternalStateWithSync, Rt as Maps, S as Filenames, St as ColumnChartContext, T as ColumnPreviewContainer, Tt as useIntersectionObserver, U as Combobox, Ut as TabsContent, V as DatePicker, Vt as dateToLocalISOTime, W as ComboboxItem, Wt as TabsList, X as CommandSeparator, Xt as useOverflowDetection, Y as CommandList, Yt as LazyVegaEmbed, Z as smartMatch, Zt as RenderTextWithLinks, _ as ADD_PRINTING_CLASS, _t as TableRow, an as Funnel, at as slotsController, b as downloadHTMLAsImage, bt as inferFieldTypes, c as Slide, cn as Ellipsis, d as RadioGroupItem, dn as ChevronsUpDown, dt as Provider$1, en as EmotionCacheProvider, et as contextAwarePanelOpen, f as JsonOutput, fn as ChevronsRight, ft as Table, g as InstallPackageButton, gn as ArrowDownWideNarrow, gt as TableHeader, h as DataTable, hn as ChevronLeft, ht as TableHead, in as GripHorizontal, it as SlotNames, j as getColumnCountForDisplay, jt as SELECT_COLUMN_ID, k as prettifyRowColumnCount, kt as loadTableData, l as Switch, ln as Download, m as OutputRenderer, mn as ChevronsDownUp, mt as TableCell, n as marimoVersionAtom, nn as $fae977aafc393c5c$export$6b862160d295c8e, nt as contextAwarePanelType, o as SLIDE_TYPE_OPTIONS_BY_VALUE, on as EyeOff, ot as Toggle, p as OutputArea, pn as ChevronsLeft, pt as TableBody, q as CommandInput, qt as ChartInfoState, r as showCodeInRunModeAtom, rn as TextWrap, rt as isCellAwareAtom, t as useNotebookCodeAvailable, tn as $fae977aafc393c5c$export$588937bcd60ade55, tt as contextAwarePanelOwner, u as RadioGroup, un as Code, ut as Fill, v as downloadBlob, vt as NAMELESS_COLUMN_PREFIX, w as ColumnName, wt as DelayMount, x as Progress, xt as renderCellValue, y as downloadByURL, yt as generateColumns, z as useSelectList, zt as dateToLocalISODate, __tla as __tla_2 } from "./code-visibility-CNAoaX9P.js";
29
+ import { $ as PANEL_TYPES, $t as HtmlOutput, A as prettifyRowCount, At as INDEX_COLUMN_NAME, B as CompactChipRow, Bt as dateToLocalISODateTime, C as downloadSizeLimitAtom, Ct as ColumnChartSpecModel, D as ErrorState, Dt as getPageIndexForRow, E as EmptyState, Et as usePrevious$1, F as ContextMenuItem, Ft as hasFunctionProperty, G as Command, Gt as TabsTrigger, H as DateRangePicker, Ht as Tabs, I as ContextMenuSeparator, It as isRecord, J as CommandItem, Jt as ChartLoadingState, K as CommandEmpty, Kt as ChartErrorState, L as ContextMenuTrigger, Lt as isNullishFilter, M as getUserColumnVisibilityCounts, Mt as TOO_MANY_ROWS, N as ContextMenu, Nt as toFieldTypes, O as LoadingState, Ot as loadTableAndRawData, P as ContextMenuContent, Pt as getMimeValues, Q as ContextAwarePanelItem, Qt as Kbd, R as useInternalStateWithSync, Rt as Maps, S as Filenames, St as ColumnChartContext, T as ColumnPreviewContainer, Tt as useIntersectionObserver, U as Combobox, Ut as TabsContent, V as DatePicker, Vt as dateToLocalISOTime, W as ComboboxItem, Wt as TabsList, X as CommandSeparator, Xt as useOverflowDetection, Y as CommandList, Yt as LazyVegaEmbed, Z as smartMatch, Zt as RenderTextWithLinks, _ as ADD_PRINTING_CLASS, _t as TableRow, an as Funnel, at as slotsController, b as downloadHTMLAsImage, bt as inferFieldTypes, c as Slide, cn as Ellipsis, d as RadioGroupItem, dn as ChevronsUpDown, dt as Provider$1, en as EmotionCacheProvider, et as contextAwarePanelOpen, f as JsonOutput, fn as ChevronsRight, ft as Table, g as InstallPackageButton, gn as ArrowDownWideNarrow, gt as TableHeader, h as DataTable, hn as ChevronLeft, ht as TableHead, in as GripHorizontal, it as SlotNames, j as getColumnCountForDisplay, jt as SELECT_COLUMN_ID, k as prettifyRowColumnCount, kt as loadTableData, l as Switch, ln as Download, m as OutputRenderer, mn as ChevronsDownUp, mt as TableCell, n as marimoVersionAtom, nn as $fae977aafc393c5c$export$6b862160d295c8e, nt as contextAwarePanelType, o as SLIDE_TYPE_OPTIONS_BY_VALUE, on as EyeOff, ot as Toggle, p as OutputArea, pn as ChevronsLeft, pt as TableBody, q as CommandInput, qt as ChartInfoState, r as showCodeInRunModeAtom, rn as TextWrap, rt as isCellAwareAtom, t as useNotebookCodeAvailable, tn as $fae977aafc393c5c$export$588937bcd60ade55, tt as contextAwarePanelOwner, u as RadioGroup, un as Code, ut as Fill, v as downloadBlob, vt as NAMELESS_COLUMN_PREFIX, w as ColumnName, wt as DelayMount, x as Progress, xt as renderCellValue, y as downloadByURL, yt as generateColumns, z as useSelectList, zt as dateToLocalISODate, __tla as __tla_2 } from "./code-visibility-Bzf3rjxi.js";
30
30
  import { c as Calendar, i as createReducerAndAtoms, n as useOnUnmount, o as ToggleLeft, t as useOnMount } from "./useLifecycle-CxffarYV.js";
31
31
  import { t as Check } from "./check-C9OoNtR4.js";
32
32
  import { A as Icon, C as logNever, D as $18f2051aff69b9bf$export$a54013f0d02a8f82, E as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7, F as createCollection, I as X, M as clamp$2, N as usePrevious$2, P as useDirection, R as ChevronDown, S as assertNever, a as SelectGroup, c as SelectSeparator, d as NativeSelect, i as SelectContent, j as Trigger$1, l as SelectTrigger, n as capitalize, o as SelectItem, r as Select, s as SelectLabel, t as Strings, u as SelectValue, w as $a916eb452884faea$export$b7a616150fdb9f44 } from "./strings-Dq_j3Rxw.js";
@@ -37732,7 +37732,7 @@ ${c}
37732
37732
  function _temp2$2(e) {
37733
37733
  e.target === e.currentTarget && e.key === "Enter" && (e.preventDefault(), e.stopPropagation(), e.currentTarget.click());
37734
37734
  }
37735
- var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-CB90I1dT.js"));
37735
+ var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-CwLqC8NP.js"));
37736
37736
  const SlidesLayoutRenderer = ({ layout: e, setLayout: r, cells: c, mode: l }) => {
37737
37737
  var _a3;
37738
37738
  let u = useAtomValue(kioskModeAtom), d = l === "read" || u, f = useAtomValue(numColumnsAtom) > 1, [p, m] = (0, import_react.useState)(null), { slideCells: h, skippedIds: g, noOutputIds: _, slideTypes: v, startCellIndex: y } = (0, import_react.useMemo)(() => computeSlideCellsInfo(c, e), [
@@ -9,7 +9,7 @@ import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
9
9
  import { lt as kioskModeAtom, pt as outputIsStale } from "./html-to-image-DOqQBSQc.js";
10
10
  import "./chunk-5FQGJX7Z-BbqSm5gU.js";
11
11
  import { u as createLucideIcon } from "./dist--2Bqjvs0.js";
12
- import { a as DEFAULT_SLIDE_TYPE, c as Slide, ct as PanelGroup, i as DEFAULT_DECK_TRANSITION, lt as PanelResizeHandle, on as EyeOff, s as SlideSidebar, sn as Expand, st as Panel, t as useNotebookCodeAvailable, un as Code } from "./code-visibility-CNAoaX9P.js";
12
+ import { a as DEFAULT_SLIDE_TYPE, c as Slide, ct as PanelGroup, i as DEFAULT_DECK_TRANSITION, lt as PanelResizeHandle, on as EyeOff, s as SlideSidebar, sn as Expand, st as Panel, t as useNotebookCodeAvailable, un as Code } from "./code-visibility-Bzf3rjxi.js";
13
13
  import { X as useDebouncedCallback } from "./input-BSdZp5Ng.js";
14
14
  import "./toDate-D1Z7ZXWh.js";
15
15
  import "./react-dom-BTJzcVJ9.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.14-dev46",
3
+ "version": "0.23.14-dev49",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,185 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { python } from "@codemirror/lang-python";
4
+ import { EditorState } from "@codemirror/state";
5
+ import { EditorView } from "@codemirror/view";
6
+ import { afterEach, describe, expect, it, vi } from "vitest";
7
+ import { cellId } from "@/__tests__/branded";
8
+ import type { CellId } from "@/core/cells/ids";
9
+ import { createMockObservable } from "@/core/state/__mocks__/mocks";
10
+ import type { Observable } from "@/core/state/observable";
11
+ import {
12
+ breakpointGutter,
13
+ debuggerLineHighlighter,
14
+ } from "../debugger-decorations";
15
+
16
+ vi.mock("../debugger-state", () => ({
17
+ toggleBreakpoint: vi.fn(),
18
+ }));
19
+
20
+ import { toggleBreakpoint } from "../debugger-state";
21
+
22
+ const CODE = `def my_function():
23
+ x = 1
24
+ y = 2
25
+ return x + y
26
+
27
+ result = my_function()`;
28
+
29
+ function createLineHighlighterEditor(
30
+ lineObservable: Observable<number | null>,
31
+ ) {
32
+ const state = EditorState.create({
33
+ doc: CODE,
34
+ extensions: [python(), debuggerLineHighlighter(lineObservable)],
35
+ });
36
+ return new EditorView({ state, parent: document.body });
37
+ }
38
+
39
+ function createBreakpointGutterEditor(
40
+ cid: CellId,
41
+ breakpointsObservable: Observable<ReadonlySet<number>>,
42
+ ) {
43
+ const state = EditorState.create({
44
+ doc: CODE,
45
+ extensions: [python(), breakpointGutter(cid, breakpointsObservable)],
46
+ });
47
+ return new EditorView({ state, parent: document.body });
48
+ }
49
+
50
+ /** Flush the microtask queue and apply the resulting state changes to `view`. */
51
+ async function flush(view: EditorView): Promise<void> {
52
+ await Promise.resolve();
53
+ view.dispatch({});
54
+ }
55
+
56
+ /**
57
+ * Count rendered breakpoint markers, excluding the always-present hidden
58
+ * spacer element (`initialSpacer`) that isn't an actual breakpoint.
59
+ */
60
+ function visibleMarkerCount(view: EditorView): number {
61
+ const gutterElement = view.dom.querySelector(".cm-breakpoint-gutter");
62
+ if (!gutterElement) {
63
+ return 0;
64
+ }
65
+ return gutterElement.querySelectorAll(
66
+ '.cm-gutterElement:not([style*="visibility: hidden"]) .cm-breakpoint-marker',
67
+ ).length;
68
+ }
69
+
70
+ describe("debugger-decorations", () => {
71
+ let view: EditorView | null = null;
72
+
73
+ afterEach(() => {
74
+ if (view) {
75
+ view.destroy();
76
+ view = null;
77
+ }
78
+ document.body.innerHTML = "";
79
+ vi.clearAllMocks();
80
+ });
81
+
82
+ describe("debuggerLineHighlighter", () => {
83
+ it("renders no highlight when the observable starts null", () => {
84
+ const lineObservable = createMockObservable<number | null>(null);
85
+ view = createLineHighlighterEditor(lineObservable);
86
+
87
+ expect(view.dom.querySelector(".cm-debugger-current-line")).toBeNull();
88
+ });
89
+
90
+ it("highlights the line reported by the observable", () => {
91
+ const lineObservable = createMockObservable<number | null>(null);
92
+ view = createLineHighlighterEditor(lineObservable);
93
+
94
+ lineObservable.set(2);
95
+ view.dispatch({});
96
+
97
+ expect(
98
+ view.dom.querySelector(".cm-debugger-current-line"),
99
+ ).not.toBeNull();
100
+ });
101
+
102
+ it("clears the highlight when the line is set back to null", () => {
103
+ const lineObservable = createMockObservable<number | null>(2);
104
+ view = createLineHighlighterEditor(lineObservable);
105
+ view.dispatch({});
106
+ expect(
107
+ view.dom.querySelector(".cm-debugger-current-line"),
108
+ ).not.toBeNull();
109
+
110
+ lineObservable.set(null);
111
+ view.dispatch({});
112
+
113
+ expect(view.dom.querySelector(".cm-debugger-current-line")).toBeNull();
114
+ });
115
+
116
+ it("does not throw for an out-of-range line", () => {
117
+ const lineObservable = createMockObservable<number | null>(null);
118
+ view = createLineHighlighterEditor(lineObservable);
119
+
120
+ expect(() => {
121
+ lineObservable.set(9999);
122
+ view?.dispatch({});
123
+ }).not.toThrow();
124
+ expect(view.dom.querySelector(".cm-debugger-current-line")).toBeNull();
125
+ });
126
+ });
127
+
128
+ describe("breakpointGutter", () => {
129
+ it("renders no markers when there are no breakpoints", () => {
130
+ const cid = cellId("cell1");
131
+ const breakpointsObservable = createMockObservable<ReadonlySet<number>>(
132
+ new Set(),
133
+ );
134
+ view = createBreakpointGutterEditor(cid, breakpointsObservable);
135
+
136
+ expect(visibleMarkerCount(view)).toBe(0);
137
+ });
138
+
139
+ it("syncs pre-existing breakpoints on mount", async () => {
140
+ const cid = cellId("cell1");
141
+ const breakpointsObservable = createMockObservable<ReadonlySet<number>>(
142
+ new Set([2]),
143
+ );
144
+ view = createBreakpointGutterEditor(cid, breakpointsObservable);
145
+ await flush(view); // initial sync is deferred to a microtask
146
+
147
+ expect(visibleMarkerCount(view)).toBe(1);
148
+ });
149
+
150
+ it("adds a marker when the observable reports a new breakpoint", () => {
151
+ const cid = cellId("cell1");
152
+ const breakpointsObservable = createMockObservable<ReadonlySet<number>>(
153
+ new Set(),
154
+ );
155
+ view = createBreakpointGutterEditor(cid, breakpointsObservable);
156
+
157
+ breakpointsObservable.set(new Set([1, 3]));
158
+ view.dispatch({});
159
+
160
+ expect(visibleMarkerCount(view)).toBe(2);
161
+ });
162
+
163
+ it("toggles a breakpoint when an existing marker is clicked", async () => {
164
+ const cid = cellId("cell1");
165
+ // A single breakpoint on line 1 renders exactly one non-spacer
166
+ // gutter element, so the click lands unambiguously on that line.
167
+ const breakpointsObservable = createMockObservable<ReadonlySet<number>>(
168
+ new Set([1]),
169
+ );
170
+ view = createBreakpointGutterEditor(cid, breakpointsObservable);
171
+ await flush(view);
172
+
173
+ const gutterElement = view.dom.querySelector(".cm-breakpoint-gutter");
174
+ const lineElement = gutterElement?.querySelector(
175
+ '.cm-gutterElement:not([style*="visibility: hidden"])',
176
+ );
177
+ expect(lineElement).not.toBeNull();
178
+ lineElement?.dispatchEvent(
179
+ new MouseEvent("mousedown", { bubbles: true }),
180
+ );
181
+
182
+ expect(toggleBreakpoint).toHaveBeenCalledWith(cid, 1);
183
+ });
184
+ });
185
+ });
@@ -0,0 +1,139 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { MockRequestClient } from "@/__mocks__/requests";
5
+ import { cellId } from "@/__tests__/branded";
6
+ import { store } from "@/core/state/jotai";
7
+ import {
8
+ breakpointsAtom,
9
+ clearCellBreakpoints,
10
+ createCellBreakpointsAtom,
11
+ createDebuggerLineAtom,
12
+ debuggerCurrentLineAtom,
13
+ resyncBreakpoints,
14
+ toggleBreakpoint,
15
+ } from "../debugger-state";
16
+
17
+ const mockRequestClient = MockRequestClient.create();
18
+ vi.mock("@/core/network/requests", () => ({
19
+ getRequestClient: () => mockRequestClient,
20
+ }));
21
+
22
+ const cell1 = cellId("cell1");
23
+ const cell2 = cellId("cell2");
24
+
25
+ describe("debugger-state", () => {
26
+ beforeEach(() => {
27
+ store.set(breakpointsAtom, new Map());
28
+ store.set(debuggerCurrentLineAtom, null);
29
+ vi.clearAllMocks();
30
+ });
31
+
32
+ describe("toggleBreakpoint", () => {
33
+ it("adds a breakpoint and syncs the full set to the kernel", () => {
34
+ toggleBreakpoint(cell1, 3);
35
+
36
+ expect(store.get(breakpointsAtom).get(cell1)).toEqual(new Set([3]));
37
+ expect(mockRequestClient.sendSetBreakpoints).toHaveBeenCalledWith({
38
+ breakpoints: { [cell1]: [3] },
39
+ });
40
+ });
41
+
42
+ it("removes a breakpoint on second toggle and drops the cell once empty", () => {
43
+ toggleBreakpoint(cell1, 3);
44
+ toggleBreakpoint(cell1, 3);
45
+
46
+ expect(store.get(breakpointsAtom).has(cell1)).toBe(false);
47
+ expect(mockRequestClient.sendSetBreakpoints).toHaveBeenLastCalledWith({
48
+ breakpoints: {},
49
+ });
50
+ });
51
+
52
+ it("sends lines sorted ascending for a cell with multiple breakpoints", () => {
53
+ toggleBreakpoint(cell1, 5);
54
+ toggleBreakpoint(cell1, 2);
55
+
56
+ expect(mockRequestClient.sendSetBreakpoints).toHaveBeenLastCalledWith({
57
+ breakpoints: { [cell1]: [2, 5] },
58
+ });
59
+ });
60
+
61
+ it("keeps breakpoints for other cells independent", () => {
62
+ toggleBreakpoint(cell1, 1);
63
+ toggleBreakpoint(cell2, 2);
64
+
65
+ expect(store.get(breakpointsAtom).get(cell1)).toEqual(new Set([1]));
66
+ expect(store.get(breakpointsAtom).get(cell2)).toEqual(new Set([2]));
67
+ });
68
+ });
69
+
70
+ describe("clearCellBreakpoints", () => {
71
+ it("removes all breakpoints for a cell and syncs", () => {
72
+ toggleBreakpoint(cell1, 1);
73
+ toggleBreakpoint(cell1, 2);
74
+ vi.clearAllMocks();
75
+
76
+ clearCellBreakpoints(cell1);
77
+
78
+ expect(store.get(breakpointsAtom).has(cell1)).toBe(false);
79
+ expect(mockRequestClient.sendSetBreakpoints).toHaveBeenCalledWith({
80
+ breakpoints: {},
81
+ });
82
+ });
83
+
84
+ it("is a no-op when the cell has no breakpoints", () => {
85
+ clearCellBreakpoints(cell1);
86
+
87
+ expect(mockRequestClient.sendSetBreakpoints).not.toHaveBeenCalled();
88
+ });
89
+ });
90
+
91
+ describe("resyncBreakpoints", () => {
92
+ it("re-sends the current breakpoint set", () => {
93
+ toggleBreakpoint(cell1, 4);
94
+ vi.clearAllMocks();
95
+
96
+ resyncBreakpoints();
97
+
98
+ expect(mockRequestClient.sendSetBreakpoints).toHaveBeenCalledWith({
99
+ breakpoints: { [cell1]: [4] },
100
+ });
101
+ });
102
+
103
+ it("does not send when there are no breakpoints", () => {
104
+ resyncBreakpoints();
105
+
106
+ expect(mockRequestClient.sendSetBreakpoints).not.toHaveBeenCalled();
107
+ });
108
+ });
109
+
110
+ describe("createCellBreakpointsAtom", () => {
111
+ it("returns the empty set for a cell with no breakpoints", () => {
112
+ const atom = createCellBreakpointsAtom(cell1);
113
+ expect(store.get(atom)).toEqual(new Set());
114
+ });
115
+
116
+ it("reflects breakpoints for its own cell only", () => {
117
+ toggleBreakpoint(cell1, 7);
118
+ const cell1Atom = createCellBreakpointsAtom(cell1);
119
+ const cell2Atom = createCellBreakpointsAtom(cell2);
120
+
121
+ expect(store.get(cell1Atom)).toEqual(new Set([7]));
122
+ expect(store.get(cell2Atom)).toEqual(new Set());
123
+ });
124
+ });
125
+
126
+ describe("createDebuggerLineAtom", () => {
127
+ it("returns null when no line is set", () => {
128
+ const atom = createDebuggerLineAtom(cell1);
129
+ expect(store.get(atom)).toBeNull();
130
+ });
131
+
132
+ it("returns the line only for the matching cell", () => {
133
+ store.set(debuggerCurrentLineAtom, { cellId: cell1, line: 10 });
134
+
135
+ expect(store.get(createDebuggerLineAtom(cell1))).toBe(10);
136
+ expect(store.get(createDebuggerLineAtom(cell2))).toBeNull();
137
+ });
138
+ });
139
+ });
@@ -72,6 +72,14 @@ export function clearCellBreakpoints(cellId: CellId): void {
72
72
  sendBreakpoints(next);
73
73
  }
74
74
 
75
+ /** Re-push the current breakpoints to a freshly started kernel, whose `MarimoPdb` starts empty. */
76
+ export function resyncBreakpoints(): void {
77
+ const current = store.get(breakpointsAtom);
78
+ if (current.size > 0) {
79
+ sendBreakpoints(current);
80
+ }
81
+ }
82
+
75
83
  /** Toggle a breakpoint at `(cellId, line)` and sync the full set to the kernel. */
76
84
  export function toggleBreakpoint(cellId: CellId, line: number): void {
77
85
  const prev = store.get(breakpointsAtom);
@@ -32,7 +32,10 @@ import { cacheInfoAtom } from "../cache/requests";
32
32
  import { SCRATCH_CELL_ID } from "../cells/ids";
33
33
  import { useRunsActions } from "../cells/runs";
34
34
  import { focusAndScrollCellOutputIntoView } from "../cells/scrollCellIntoView";
35
- import { debuggerCurrentLineAtom } from "../codemirror/cells/debugger-state";
35
+ import {
36
+ debuggerCurrentLineAtom,
37
+ resyncBreakpoints,
38
+ } from "../codemirror/cells/debugger-state";
36
39
  import type { CellData } from "../cells/types";
37
40
  import { capabilitiesAtom } from "../config/capabilities";
38
41
  import { useSetAppConfig } from "../config/config";
@@ -223,6 +226,11 @@ export function useMarimoKernelConnection(opts: {
223
226
  existingCells,
224
227
  });
225
228
  setKioskMode(msg.data.kiosk);
229
+ // A freshly started kernel has no breakpoints of its own; re-push
230
+ // the client's set so they still apply, and clear the stale
231
+ // highlighted line from the previous kernel instance.
232
+ setDebuggerCurrentLine(null);
233
+ resyncBreakpoints();
226
234
  return;
227
235
  }
228
236