@marimo-team/islands 0.25.3-dev275 → 0.25.3-dev279

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 (60) hide show
  1. package/dist/{ErrorBoundary-BuIXiuc5.js → ErrorBoundary-DmTMuDPR.js} +1 -1
  2. package/dist/{chat-ui-SZ6lSH52.js → chat-ui-v1Fdh0NA.js} +270 -270
  3. package/dist/{code-visibility-BEAPMrC-.js → code-visibility-Dw4-TXN0.js} +2 -2
  4. package/dist/{common-CEmzaoJ8.js → common-BXMFi4lr.js} +4947 -4943
  5. package/dist/{data-grid-overlay-editor-CUo25_W8.js → data-grid-overlay-editor-hZzwX7Ia.js} +1 -1
  6. package/dist/{error-entries-B-1yUdcH.js → error-entries-C1S-Rmyt.js} +545 -535
  7. package/dist/{feedback-button-BqiR7zMO.js → feedback-button-6Ui2TDLR.js} +5 -5
  8. package/dist/{glide-data-editor-B7dguvqk.js → glide-data-editor-PZNlrSDI.js} +3 -3
  9. package/dist/{globals-CYykXfGI.js → globals-DX6FPZYq.js} +1 -1
  10. package/dist/{html-to-image-DC_pmZLs.js → html-to-image-CqGUd58T.js} +14452 -14452
  11. package/dist/main.js +6031 -6036
  12. package/dist/{number-overlay-editor-C4u7SJAJ.js → number-overlay-editor-D0CsL3_R.js} +1 -1
  13. package/dist/{process-output-DYu84VfZ.js → process-output-DskX9jLa.js} +2 -2
  14. package/dist/{reveal-component-DGi-irHV.js → reveal-component-D7xeI1QR.js} +906 -906
  15. package/package.json +1 -1
  16. package/src/__mocks__/executions.ts +4 -2
  17. package/src/__mocks__/notebook.ts +0 -2
  18. package/src/__tests__/CellStatus.test.tsx +6 -6
  19. package/src/components/editor/__tests__/data-attributes.test.tsx +0 -2
  20. package/src/components/editor/__tests__/output-persistence.test.tsx +20 -5
  21. package/src/components/editor/cell/CellStatus.tsx +6 -5
  22. package/src/components/editor/cell/__tests__/useRunCells.test.tsx +169 -0
  23. package/src/components/editor/cell/collapse.tsx +3 -4
  24. package/src/components/editor/cell/useRunCells.ts +32 -3
  25. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +2 -3
  26. package/src/components/editor/notebook-cell.tsx +23 -37
  27. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +4 -10
  28. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -2
  29. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +8 -5
  30. package/src/components/slides/minimap.tsx +3 -2
  31. package/src/components/slides/reveal-component.tsx +3 -2
  32. package/src/components/slides/slide-cell-view.tsx +8 -8
  33. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -101
  34. package/src/core/cells/__tests__/actions.test.ts +0 -1
  35. package/src/core/cells/__tests__/cell.test.ts +81 -17
  36. package/src/core/cells/__tests__/cells.test.ts +0 -100
  37. package/src/core/cells/__tests__/document.test.ts +2 -8
  38. package/src/core/cells/__tests__/utils.test.ts +93 -24
  39. package/src/core/cells/actions.ts +0 -1
  40. package/src/core/cells/cell.ts +37 -14
  41. package/src/core/cells/cells.ts +10 -99
  42. package/src/core/cells/document.ts +2 -13
  43. package/src/core/cells/types.ts +0 -23
  44. package/src/core/cells/utils.ts +44 -17
  45. package/src/core/codemirror/cells/extensions.ts +0 -14
  46. package/src/core/errors/__tests__/error-entries.test.ts +40 -1
  47. package/src/core/errors/error-entries.ts +2 -3
  48. package/src/core/executions/__tests__/freshness.test.ts +420 -0
  49. package/src/core/executions/__tests__/hooks.test.tsx +106 -0
  50. package/src/core/executions/atoms.ts +22 -0
  51. package/src/core/executions/freshness.ts +164 -0
  52. package/src/core/executions/hooks.ts +17 -1
  53. package/src/core/executions/index.ts +3 -1
  54. package/src/core/islands/bootstrap.ts +0 -1
  55. package/src/core/kernel/handlers.ts +0 -4
  56. package/src/core/session/useNotebookSession.tsx +2 -7
  57. package/src/stories/cell-status.stories.tsx +12 -14
  58. package/src/stories/cell.stories.tsx +44 -10
  59. package/src/stories/layout/vertical/one-column.stories.tsx +0 -10
  60. package/src/components/editor/cell/__tests__/useRunCells.test.ts +0 -45
@@ -0,0 +1,164 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * Whether each cell's result still reflects the document and the values
5
+ * it read, by the host protocol's rules.
6
+ *
7
+ * A completed execution is current when its code fingerprint equals the
8
+ * cell's, every variable it depends on still holds the recorded revision,
9
+ * every name it depends on that was unbound is still unbound, and the
10
+ * executions that bound those variables are current. See the freshness
11
+ * section of `protocols/host/spec.md`.
12
+ *
13
+ * The protocol also ranks why a result is outdated. The editor asks two
14
+ * questions of its own, so that is what this reports; the ranked reason
15
+ * can come back with a reader. Two editor concerns are folded in: a name
16
+ * bound only by an earlier runtime is unbound in this one, and a queued or
17
+ * running execution for the cell's current source already covers it.
18
+ */
19
+
20
+ import type * as Protocol from "@marimo-team/client/protocol";
21
+ import type { Executions, Results } from "@/core/notebook/replica";
22
+
23
+ export interface CellFreshness {
24
+ /** This cell's source changed since its result. */
25
+ readonly sourceChanged: boolean;
26
+ /**
27
+ * A value the result read changed since, by a rerun or an update, here
28
+ * or upstream. An ancestor that was only edited does not count: its
29
+ * values are still the ones this result read.
30
+ */
31
+ readonly inputsChanged: boolean;
32
+ }
33
+
34
+ /** Current, and also a cell with no completed execution to compare. */
35
+ const CURRENT: CellFreshness = { sourceChanged: false, inputsChanged: false };
36
+
37
+ /** What freshness is derived from. */
38
+ export interface CellFreshnessInputs {
39
+ /** The document's cells, for the fingerprint of each cell's source. */
40
+ readonly cells: ReadonlyMap<Protocol.CellId, Protocol.Cell>;
41
+ /** The stream's executions, for the variables currently bound. */
42
+ readonly executions: Executions;
43
+ /** Each cell's last completed record, the result being compared. */
44
+ readonly results: Results;
45
+ }
46
+
47
+ /** Freshness by cell id, as `freshnessOf` builds it. */
48
+ export type CellFreshnessLookup = (id: Protocol.CellId) => CellFreshness;
49
+
50
+ /** The variables bound in the namespace right now, by name. */
51
+ function boundVariables(
52
+ executions: Executions,
53
+ ): ReadonlyMap<string, Protocol.Variable[]> {
54
+ const byName = new Map<string, Protocol.Variable[]>();
55
+ for (const execution of executions.executions.values()) {
56
+ for (const variable of execution.variables.values()) {
57
+ const bound = byName.get(variable.name);
58
+ if (bound === undefined) {
59
+ byName.set(variable.name, [variable]);
60
+ } else {
61
+ bound.push(variable);
62
+ }
63
+ }
64
+ }
65
+ return byName;
66
+ }
67
+
68
+ /**
69
+ * Compares results against the document and the namespace.
70
+ *
71
+ * Build one per set of inputs and ask it about each cell, since the
72
+ * bound variables are indexed once and a binding execution's freshness is
73
+ * computed once however many results depend on it.
74
+ */
75
+ export function freshnessOf(inputs: CellFreshnessInputs): CellFreshnessLookup {
76
+ const bound = boundVariables(inputs.executions);
77
+ const memo = new Map<Protocol.ExecutionId, CellFreshness>();
78
+ const recordOf = (id: Protocol.ExecutionId) =>
79
+ inputs.executions.executions.get(id)?.record;
80
+
81
+ /** Whether a value the record read has changed, or one upstream has. */
82
+ function inputsChanged(
83
+ record: Protocol.Execution,
84
+ visiting: ReadonlySet<Protocol.ExecutionId>,
85
+ ): boolean {
86
+ const path = new Set(visiting).add(record.id);
87
+ for (const [name, revision] of Object.entries(record.depends_on)) {
88
+ const variables = bound.get(name) ?? [];
89
+ if (revision === null) {
90
+ // A result retained from an earlier runtime binds nothing here.
91
+ if (
92
+ variables.some(
93
+ (v) => recordOf(v.execution_id)?.runtime_id === record.runtime_id,
94
+ )
95
+ ) {
96
+ return true;
97
+ }
98
+ continue;
99
+ }
100
+ const variable = variables.find(
101
+ (candidate) => candidate.revision.execution === revision.execution,
102
+ );
103
+ if (
104
+ variable === undefined ||
105
+ variable.revision.update !== revision.update
106
+ ) {
107
+ return true;
108
+ }
109
+ const binder = recordOf(variable.execution_id);
110
+ if (binder === undefined) {
111
+ return true;
112
+ }
113
+ if (!path.has(binder.id) && ofRecord(binder, path).inputsChanged) {
114
+ return true;
115
+ }
116
+ }
117
+ return false;
118
+ }
119
+
120
+ /** Whether a newer execution of the cell already runs its current source. */
121
+ function coveredByPending(
122
+ cell: Protocol.Cell,
123
+ record: Protocol.Execution,
124
+ ): boolean {
125
+ const latest = inputs.executions.latest.get(cell.id);
126
+ if (latest === undefined || latest === record.id) {
127
+ return false;
128
+ }
129
+ const pending = recordOf(latest);
130
+ return (
131
+ pending !== undefined &&
132
+ pending.status !== "completed" &&
133
+ pending.code_fingerprint === cell.code_fingerprint
134
+ );
135
+ }
136
+
137
+ function ofRecord(
138
+ record: Protocol.Execution,
139
+ visiting: ReadonlySet<Protocol.ExecutionId>,
140
+ ): CellFreshness {
141
+ const known = memo.get(record.id);
142
+ if (known !== undefined) {
143
+ return known;
144
+ }
145
+ const cell =
146
+ record.cell_id === null ? undefined : inputs.cells.get(record.cell_id);
147
+ const freshness: CellFreshness = {
148
+ sourceChanged:
149
+ cell === undefined ||
150
+ (cell.code_fingerprint !== record.code_fingerprint &&
151
+ !coveredByPending(cell, record)),
152
+ inputsChanged: inputsChanged(record, visiting),
153
+ };
154
+ memo.set(record.id, freshness);
155
+ return freshness;
156
+ }
157
+
158
+ return (id) => {
159
+ const result = inputs.results.get(id);
160
+ return result === null || result === undefined
161
+ ? CURRENT
162
+ : ofRecord(result, new Set());
163
+ };
164
+ }
@@ -5,7 +5,8 @@ import { dequal } from "dequal";
5
5
  import { useAtomValue } from "jotai";
6
6
  import { selectAtom } from "jotai/utils";
7
7
  import { useMemo } from "react";
8
- import { cellExecutionInputsAtom } from "./atoms";
8
+ import { cellExecutionInputsAtom, cellFreshnessAtom } from "./atoms";
9
+ import type { CellFreshness } from "./freshness";
9
10
  import { type CellExecutionView, cellExecutionView } from "./select";
10
11
 
11
12
  /**
@@ -29,3 +30,18 @@ export function useCellExecution(id: Protocol.CellId): CellExecutionView {
29
30
  );
30
31
  return useAtomValue(cellAtom);
31
32
  }
33
+
34
+ /**
35
+ * Whether a cell's result is current, by the host protocol's rules over
36
+ * the confirmed document and the executions mirror.
37
+ *
38
+ * The confirmed document is what the host has accepted, so an edit reads
39
+ * as such once the model has flushed it, not on the keystroke.
40
+ */
41
+ export function useCellFreshness(id: Protocol.CellId): CellFreshness {
42
+ const cellAtom = useMemo(
43
+ () => selectAtom(cellFreshnessAtom, (freshness) => freshness(id), dequal),
44
+ [id],
45
+ );
46
+ return useAtomValue(cellAtom);
47
+ }
@@ -10,6 +10,7 @@
10
10
 
11
11
  export {
12
12
  cellExecutionInputsAtom,
13
+ cellFreshnessAtom,
13
14
  notebookIsRunningAtom,
14
15
  notebookQueuedOrRunningCountAtom,
15
16
  observeCellMessage,
@@ -18,7 +19,8 @@ export {
18
19
  waitForRuntimeRunning,
19
20
  withdrawCells,
20
21
  } from "./atoms";
21
- export { useCellExecution } from "./hooks";
22
+ export type { CellFreshness, CellFreshnessLookup } from "./freshness";
23
+ export { useCellExecution, useCellFreshness } from "./hooks";
22
24
  export {
23
25
  type CellExecutionInputs,
24
26
  type CellExecutionView,
@@ -221,7 +221,6 @@ function handleNotification(
221
221
  handleKernelReady(msg, {
222
222
  autoInstantiate: true,
223
223
  cells: notebookCells(store.get(notebookAtom)),
224
- setLastRun: actions.setLastRun,
225
224
  resetCellRuntimes: actions.resetCellRuntimes,
226
225
  setLayoutData: Functions.NOOP,
227
226
  setCapabilities: Functions.NOOP,
@@ -60,7 +60,6 @@ export function handleKernelReady(
60
60
  autoInstantiate: boolean;
61
61
  /** The notebook's cells, as the host document provided them. */
62
62
  cells: CellData[];
63
- setLastRun: ReducerCellActions["setLastRun"];
64
63
  resetCellRuntimes: ReducerCellActions["resetCellRuntimes"];
65
64
  setLayoutData: (payload: {
66
65
  layoutView: LayoutType;
@@ -73,7 +72,6 @@ export function handleKernelReady(
73
72
  const {
74
73
  autoInstantiate,
75
74
  cells,
76
- setLastRun,
77
75
  resetCellRuntimes,
78
76
  setLayoutData,
79
77
  setCapabilities,
@@ -82,8 +80,6 @@ export function handleKernelReady(
82
80
  const { resumed, ui_values, capabilities, auto_instantiated } = data;
83
81
 
84
82
  resetCellRuntimes();
85
- // TODO(MO-8403): Delete with `lastCodeRun`.
86
- setLastRun({ lastCodeRun: data.last_executed_code ?? {} });
87
83
  const layoutState = buildLayoutState(data, cells, setLayoutData);
88
84
 
89
85
  setCapabilities(capabilities);
@@ -92,12 +92,8 @@ export function useNotebookSession(opts: {
92
92
  const { showBoundary } = useErrorBoundary();
93
93
  const store = useStore();
94
94
 
95
- const {
96
- handleCellMessage,
97
- reconcileDocument,
98
- setLastRun,
99
- resetCellRuntimes,
100
- } = useReducerActions();
95
+ const { handleCellMessage, reconcileDocument, resetCellRuntimes } =
96
+ useReducerActions();
101
97
  const { reconcileStagedCells } = useStagedAICellsActions();
102
98
  const { addCellNotification } = useRunsActions();
103
99
  const setInitialRunCompleted = useSetAtom(initialRunCompletedAtom);
@@ -154,7 +150,6 @@ export function useNotebookSession(opts: {
154
150
  const layout = handleKernelReady(msg.data, {
155
151
  autoInstantiate,
156
152
  cells: notebookCells(store.get(notebookAtom)),
157
- setLastRun,
158
153
  resetCellRuntimes,
159
154
  setLayoutData,
160
155
  setCapabilities,
@@ -27,7 +27,7 @@ export const Idle = Template.bind({});
27
27
  Idle.args = {
28
28
  editing: true,
29
29
  status: "idle",
30
- edited: false,
30
+ freshness: { sourceChanged: false, inputsChanged: false },
31
31
  interrupted: false,
32
32
  disabled: false,
33
33
  elapsedTime: null,
@@ -37,7 +37,7 @@ export const Edited = Template.bind({});
37
37
  Edited.args = {
38
38
  editing: true,
39
39
  status: "idle",
40
- edited: true,
40
+ freshness: { sourceChanged: true, inputsChanged: false },
41
41
  interrupted: false,
42
42
  disabled: false,
43
43
  elapsedTime: 100 as Milliseconds,
@@ -47,7 +47,7 @@ export const Interrupted = Template.bind({});
47
47
  Interrupted.args = {
48
48
  editing: true,
49
49
  status: "idle",
50
- edited: false,
50
+ freshness: { sourceChanged: false, inputsChanged: false },
51
51
  interrupted: true,
52
52
  disabled: false,
53
53
  elapsedTime: 50 as Milliseconds,
@@ -57,7 +57,7 @@ export const Running = Template.bind({});
57
57
  Running.args = {
58
58
  editing: true,
59
59
  status: "running",
60
- edited: false,
60
+ freshness: { sourceChanged: false, inputsChanged: false },
61
61
  interrupted: true,
62
62
  disabled: false,
63
63
  elapsedTime: 50 as Milliseconds,
@@ -67,7 +67,7 @@ export const Queued = Template.bind({});
67
67
  Queued.args = {
68
68
  editing: true,
69
69
  status: "queued",
70
- edited: false,
70
+ freshness: { sourceChanged: false, inputsChanged: false },
71
71
  interrupted: true,
72
72
  disabled: false,
73
73
  elapsedTime: 50 as Milliseconds,
@@ -77,7 +77,7 @@ export const Disabled = Template.bind({});
77
77
  Disabled.args = {
78
78
  editing: true,
79
79
  status: "idle",
80
- edited: false,
80
+ freshness: { sourceChanged: false, inputsChanged: false },
81
81
  interrupted: false,
82
82
  disabled: true,
83
83
  elapsedTime: null,
@@ -88,7 +88,7 @@ DisabledTransitively.args = {
88
88
  editing: true,
89
89
  status: "idle",
90
90
  blocked: true,
91
- edited: false,
91
+ freshness: { sourceChanged: false, inputsChanged: false },
92
92
  interrupted: false,
93
93
  disabled: false,
94
94
  elapsedTime: null,
@@ -99,7 +99,7 @@ DisabledTransitivelyAndEdited.args = {
99
99
  editing: true,
100
100
  status: "idle",
101
101
  blocked: true,
102
- edited: true,
102
+ freshness: { sourceChanged: true, inputsChanged: false },
103
103
  interrupted: false,
104
104
  disabled: false,
105
105
  elapsedTime: null,
@@ -110,8 +110,7 @@ Stale.args = {
110
110
  editing: true,
111
111
  status: "idle",
112
112
  blocked: true,
113
- staleInputs: true,
114
- edited: false,
113
+ freshness: { sourceChanged: false, inputsChanged: true },
115
114
  interrupted: false,
116
115
  disabled: false,
117
116
  elapsedTime: null,
@@ -122,8 +121,7 @@ StaleAndDisabled.args = {
122
121
  editing: true,
123
122
  status: "idle",
124
123
  blocked: true,
125
- staleInputs: true,
126
- edited: false,
124
+ freshness: { sourceChanged: false, inputsChanged: true },
127
125
  interrupted: false,
128
126
  disabled: true,
129
127
  elapsedTime: null,
@@ -134,7 +132,7 @@ EditedStaleAndDisabled.args = {
134
132
  editing: true,
135
133
  status: "idle",
136
134
  blocked: true,
137
- staleInputs: true,
135
+ freshness: { sourceChanged: false, inputsChanged: true },
138
136
  interrupted: false,
139
137
  disabled: true,
140
138
  elapsedTime: null,
@@ -145,7 +143,7 @@ EditedAndStale.args = {
145
143
  editing: true,
146
144
  status: "idle",
147
145
  blocked: true,
148
- edited: true,
146
+ freshness: { sourceChanged: true, inputsChanged: false },
149
147
  interrupted: false,
150
148
  disabled: false,
151
149
  elapsedTime: null,
@@ -3,7 +3,14 @@
3
3
  import type { Meta, StoryObj } from "@storybook/react-vite";
4
4
  import { createStore, Provider } from "jotai";
5
5
  import { createRef } from "react";
6
+ import { MockExecutions } from "@/__mocks__/executions";
6
7
  import { cellId } from "@/__tests__/branded";
8
+ import type { CellId } from "@/core/cells/ids";
9
+ import {
10
+ cell,
11
+ document as documentOf,
12
+ withCells,
13
+ } from "@/core/notebook/document/__tests__/fixtures";
7
14
  import { type NotebookState, notebookAtom } from "@/core/cells/cells";
8
15
  import {
9
16
  type CellRuntimeState,
@@ -22,13 +29,41 @@ type Story = StoryObj<typeof Cell>;
22
29
 
23
30
  interface CellOverrides {
24
31
  output?: CellRuntimeState["output"];
25
- edited?: boolean;
26
- staleInputs?: boolean;
32
+ /** How the cell's result is outdated, as the host would report it. */
33
+ freshness?: "edited" | "replaced" | "both";
27
34
  config?: CellConfig;
28
35
  }
29
36
 
30
37
  const EMPTY_OVERRIDES: Readonly<CellOverrides> = {};
31
38
 
39
+ /**
40
+ * Give the replica a completed execution whose source was edited since,
41
+ * whose input a newer execution replaced, or both.
42
+ */
43
+ function seedFreshness(
44
+ store: ReturnType<typeof createStore>,
45
+ cid: CellId,
46
+ freshness: NonNullable<CellOverrides["freshness"]>,
47
+ ) {
48
+ const ran = MockExecutions.completed(cid, {
49
+ code_fingerprint: "ran",
50
+ depends_on:
51
+ freshness === "edited" ? {} : { x: { execution: "gone", update: 0 } },
52
+ });
53
+ const document = withCells(
54
+ documentOf(),
55
+ {
56
+ c1: null,
57
+ c2: null,
58
+ [cid]: cell(cid, "import marimo as mo", {
59
+ code_fingerprint: freshness === "replaced" ? "ran" : "edited",
60
+ }),
61
+ },
62
+ [cid],
63
+ );
64
+ MockExecutions.seed(store, [ran], null, document);
65
+ }
66
+
32
67
  const Cell: React.FC<{
33
68
  overrides?: CellOverrides;
34
69
  }> = ({ overrides = EMPTY_OVERRIDES }) => {
@@ -39,13 +74,11 @@ const Cell: React.FC<{
39
74
  id: cid,
40
75
  name: "cell_1",
41
76
  code: "import marimo as mo",
42
- edited: overrides.edited ?? false,
43
77
  config: {
44
78
  hide_code: overrides.config?.hide_code ?? false,
45
79
  disabled: overrides.config?.disabled ?? false,
46
80
  column: null,
47
81
  },
48
- lastCodeRun: null,
49
82
  },
50
83
  },
51
84
  cellIds: [cid],
@@ -53,7 +86,6 @@ const Cell: React.FC<{
53
86
  [cid]: createCellRuntimeState({
54
87
  output: overrides.output ?? null,
55
88
  consoleOutputs: [],
56
- staleInputs: overrides.staleInputs ?? false,
57
89
  debuggerActive: false,
58
90
  outline: null,
59
91
  }),
@@ -68,6 +100,9 @@ const Cell: React.FC<{
68
100
 
69
101
  const store = createStore();
70
102
  store.set(notebookAtom, notebook);
103
+ if (overrides.freshness !== undefined) {
104
+ seedFreshness(store, cid, overrides.freshness);
105
+ }
71
106
  store.set(connectionAtom, { state: ConnectionState.OPEN });
72
107
  store.set(requestClientAtom, resolveRequestClient());
73
108
  return (
@@ -145,7 +180,7 @@ export const UnsavedEditsOutput: Story = {
145
180
  <TooltipProvider>
146
181
  <Cell
147
182
  overrides={{
148
- edited: true,
183
+ freshness: "edited",
149
184
  output: {
150
185
  channel: "output",
151
186
  data: "<span class='markdown'><h1>Layout</h1>\n<p><code>marimo</code> provides functions to help you lay out your output, such as\nin rows and columns, accordions, tabs, and callouts. This tutorial\nshows some examples.</p></span>",
@@ -239,7 +274,7 @@ export const StaleStatus: Story = {
239
274
  <div className="p-20 max-w-4xl">
240
275
  <Cell
241
276
  overrides={{
242
- staleInputs: true,
277
+ freshness: "replaced",
243
278
  output: {
244
279
  channel: "output",
245
280
  data: "This data is stale because a parent is disabled",
@@ -257,8 +292,7 @@ export const StaleAndEditedStatus: Story = {
257
292
  <div className="p-20 max-w-4xl">
258
293
  <Cell
259
294
  overrides={{
260
- staleInputs: true,
261
- edited: true,
295
+ freshness: "both",
262
296
  output: {
263
297
  channel: "output",
264
298
  data: "This data is stale because a parent is disabled, but this cell has been edited since.",
@@ -281,7 +315,7 @@ export const DisabledAndStaleStatus: Story = {
281
315
  disabled: true,
282
316
  hide_code: false,
283
317
  },
284
- staleInputs: true,
318
+ freshness: "replaced",
285
319
  output: {
286
320
  channel: "output",
287
321
  data: "<span class='markdown'><h1>Layout</h1>\n<p><code>marimo</code> provides functions to help you lay out your output, such as\nin rows and columns, accordions, tabs, and callouts. This tutorial\nshows some examples.</p></span>",
@@ -35,20 +35,11 @@ const createLongReprNotebook = (
35
35
 
36
36
  print('hello')
37
37
  LongReprA()`,
38
- edited: false,
39
38
  config: {
40
39
  hide_code: hideCode,
41
40
  disabled: false,
42
41
  column: null,
43
42
  },
44
- lastCodeRun: `class LongReprA:
45
- def __repr__(self):
46
- return "<dlt.Relation(dataset='" + ("jaffle_ingest_dataset_" * 80) + \\
47
- "', destination='<dlt.destinations.duckdb(destination_type=\\'duckdb\\'," + \\
48
- " credentials=\\'local_jaffle.duckdb\\')>')>"
49
-
50
- print('hello')
51
- LongReprA()`,
52
43
  },
53
44
  },
54
45
  cellIds: [cellId],
@@ -68,7 +59,6 @@ LongReprA()`,
68
59
  timestamp: 1_686_863_705,
69
60
  },
70
61
  ],
71
- staleInputs: false,
72
62
  debuggerActive: false,
73
63
  outline: null,
74
64
  }),
@@ -1,45 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import { describe, expect, it, vi } from "vitest";
4
- import { MockExecutions } from "@/__mocks__/executions";
5
- import { MockNotebook } from "@/__mocks__/notebook";
6
- import { cellId } from "@/__tests__/branded";
7
- import {
8
- cellExecutionInputsAtom,
9
- cellExecutionView,
10
- notebookQueuedOrRunningCountAtom,
11
- } from "@/core/executions";
12
- import { store } from "@/core/state/jotai";
13
- import { runCells } from "../useRunCells";
14
-
15
- const ENABLED = cellId("enabled");
16
- const DISABLED = cellId("disabled");
17
-
18
- describe("runCells", () => {
19
- it("sends a disabled cell to the kernel without reading it as queued", async () => {
20
- MockExecutions.seed(store, []);
21
- const notebook = MockNotebook.notebookState({
22
- cellData: {
23
- [ENABLED]: { code: "x = 1" },
24
- [DISABLED]: { code: "y = 2", config: { disabled: true } },
25
- },
26
- });
27
- const sendRun = vi.fn(async () => null);
28
-
29
- await runCells({
30
- cellIds: [ENABLED, DISABLED],
31
- sendRun,
32
- prepareForRun: vi.fn(),
33
- notebook,
34
- });
35
-
36
- expect(sendRun).toHaveBeenCalledWith({
37
- cellIds: [ENABLED, DISABLED],
38
- codes: ["x = 1", "y = 2"],
39
- });
40
- const inputs = store.get(cellExecutionInputsAtom);
41
- expect(cellExecutionView(inputs, ENABLED).status).toBe("queued");
42
- expect(cellExecutionView(inputs, DISABLED).status).toBe("idle");
43
- expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
44
- });
45
- });