@marimo-team/islands 0.25.3-dev267 → 0.25.3-dev268
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/{ConnectedDataExplorerComponent-C4yCN38h.js → ConnectedDataExplorerComponent-eLHPh6uu.js} +8 -8
- package/dist/{ErrorBoundary-CewXPyL5.js → ErrorBoundary-CQYAoD7X.js} +4 -4
- package/dist/{abnfDiagram-VCTEODGH-o6Ay6Q63.js → abnfDiagram-VCTEODGH-hqd0IBtX.js} +2 -2
- package/dist/{architectureDiagram-5GKGNRK7-BhiwwlVg.js → architectureDiagram-5GKGNRK7-BzLAFcc0.js} +1 -1
- package/dist/{strings-BCKpLFNU.js → assertNever-DIU3JJbt.js} +14 -14
- package/dist/{chat-ui-CbgR5ivy.js → chat-ui-BAnLgpTe.js} +17 -17
- package/dist/{chunk-IMKFNOWR-WMSMf4Nj.js → chunk-IMKFNOWR-DOeVvuyE.js} +1 -1
- package/dist/{chunk-SHT3W25Y-BRfADrzD.js → chunk-SHT3W25Y-DZoFI8wR.js} +1 -1
- package/dist/{chunk-SVP7TREG-CuK5QFre.js → chunk-SVP7TREG-CWu86PSW.js} +1 -1
- package/dist/{chunk-TICWLB2K-Bcun_jhJ.js → chunk-TICWLB2K-BrM0GoP3.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-CgWDbUDT.js → classDiagram-ZZMXUADV-DYi4V2bB.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-DYmc9SFU.js → classDiagram-v2-VYDZK3BY-tN7FIk-m.js} +2 -2
- package/dist/{code-visibility-BKBl9Rh6.js → code-visibility-DcZ_RwaH.js} +44 -47
- package/dist/{common-N3Jd_6m9.js → common-BGeQilvh.js} +4866 -4857
- package/dist/{config-CmeG_Pb6.js → config-BsPruWMR.js} +2 -2
- package/dist/{createReducer-C_syF8Td.js → createReducer-DdtT_7ji.js} +1 -1
- package/dist/{cynefinDiagram-5FMLGOSQ-PPKWipjT.js → cynefinDiagram-5FMLGOSQ-D5nCnDc-.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-niRBZh--.js → dagre-GXQ25YYZ-BHkd7f_i.js} +1 -1
- package/dist/{data-grid-overlay-editor-Cp59CrqZ.js → data-grid-overlay-editor-Dc4lW4A3.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-DPiP9rot.js → diagram-S7CK7UJ4-D1ALppT2.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-B0WZuaxV.js → diagram-UQ7AKVKN-DLv0b83L.js} +1 -1
- package/dist/{diagram-VX7I27RA-Rn42WBuN.js → diagram-VX7I27RA-7XJ7J6qg.js} +1 -1
- package/dist/{diagram-Z3DM3KII-BB93--ry.js → diagram-Z3DM3KII-7M7zbHp_.js} +1 -1
- package/dist/{dialog-BtprQCUw.js → dialog-DzCj1qQv.js} +1 -1
- package/dist/{dropdown-menu-BpImEfn3.js → dropdown-menu-CdXE15an.js} +3 -3
- package/dist/{ebnfDiagram-PWID7BFC-V4t5h6mZ.js → ebnfDiagram-PWID7BFC-DHH1qlmV.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-C1MsAKXd.js → erDiagram-RLTQ6QDP-fI_0dGef.js} +1 -1
- package/dist/{error-entries-CJGdD2ky.js → error-entries-CxN-Nq3V.js} +519 -458
- package/dist/{feedback-button-Qc1f9iPq.js → feedback-button-BqaeQcvX.js} +22 -22
- package/dist/{flowDiagram-HODETNUW-DHjeJP96.js → flowDiagram-HODETNUW-Brg9MA6i.js} +2 -2
- package/dist/{formats-gGospKWJ.js → formats-DbSvAPY-.js} +1 -1
- package/dist/{glide-data-editor-hCq3K0sd.js → glide-data-editor-CiijPXA7.js} +11 -11
- package/dist/{glide-utils-DC3sk6EX.js → glide-utils-BSw3SSHF.js} +1 -1
- package/dist/{globals-B2_uMYDY.js → globals-CpmSWKI_.js} +2 -2
- package/dist/{html-to-image-S7spRQCi.js → html-to-image-8sywF4my.js} +5120 -5117
- package/dist/{infoDiagram-27XIBGKW-Dovv6iKx.js → infoDiagram-27XIBGKW-E9e403YT.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-D2nyygsc.js → ishikawaDiagram-5VMMS53U-C-RlYDRo.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-KyVx0uTY.js → kanban-definition-UXKFOSKX-CLFl_uqK.js} +1 -1
- package/dist/main.js +5972 -6003
- package/dist/{mermaid-B-jKSwGt.js → mermaid-rd09e1JL.js} +34 -34
- package/dist/{mindmap-definition-YA3MSWOX-Bz9tKQFl.js → mindmap-definition-YA3MSWOX-BQ8TWtyB.js} +1 -1
- package/dist/{number-overlay-editor-CfNYvbdz.js → number-overlay-editor-Ft4NiAEN.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-C_qpq6Tv.js → pegDiagram-XKGWAZYB-CN8UHFPi.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-Bp9ucgI4.js → pieDiagram-E7YTZNPT-DGuJ0EBd.js} +1 -1
- package/dist/{process-output-f34bkXTm.js → process-output-Dku5POvm.js} +3 -3
- package/dist/{railroadDiagram-O6MQD6OU-tq-VEH6j.js → railroadDiagram-O6MQD6OU-BPDA8wy8.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-DW-vHadb.js → requirementDiagram-BXWQKSXE-ChzLGbGW.js} +1 -1
- package/dist/{reveal-component-CUxv-kkP.js → reveal-component-D0cfwbbl.js} +1094 -1094
- package/dist/{select-C33FVPPN.js → select-OxgozOC8.js} +1 -1
- package/dist/{sequenceDiagram-WJ2MYXX4-Dv9U9qRP.js → sequenceDiagram-WJ2MYXX4-DFX-bE6O.js} +1 -1
- package/dist/{spec-DH1lmF7Q.js → spec-oJn0RhpQ.js} +2 -2
- package/dist/{stateDiagram-D77RDMKH-DTmPQ0EB.js → stateDiagram-D77RDMKH-BR7cI3wK.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-CkCif0Iw.js → stateDiagram-v2-MP3YSRHH-oGqB5UDI.js} +2 -2
- package/dist/{static-state-BldGd99T.js → static-state-CuV6Pc3a.js} +1 -1
- package/dist/{swimlanes-42K2YHIH-21kCq6l8.js → swimlanes-42K2YHIH-DwSZl1yC.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-BpRJP6KU.js → swimlanesDiagram-VR7AAH4N-BHlcd-Ri.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-Ckgh4lS4.js → timeline-definition-24CTP7MA-DMmP-G2O.js} +1 -1
- package/dist/{toast-DpczUryP.js → toast-DbBHmxmB.js} +1 -1
- package/dist/{useAsyncData-DsZMvNXn.js → useAsyncData-BYIAoMqn.js} +1 -1
- package/dist/{useDateFormatter-Dk_xFci4.js → useDateFormatter-ButWkdcK.js} +1 -1
- package/dist/{useDeepCompareMemoize-KMBBux8C.js → useDeepCompareMemoize-Bz0c5GN6.js} +1 -1
- package/dist/{useTheme-BZ2GGLgm.js → useTheme-DPLobSFs.js} +7 -7
- package/dist/{utils-DXkj82sK.js → utils-EBklsC5R.js} +31 -31
- package/dist/{vega-component-CF9kI6C4.js → vega-component-rxIxAAh9.js} +6 -6
- package/dist/{vennDiagram-4TSXK5OY-CDei2Bt3.js → vennDiagram-4TSXK5OY-Db8GtH1G.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-CXavT601.js → wardleyDiagram-VM6X3IG4-DmIiC3FU.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CvJhcZri.js → xychartDiagram-S5SC5T6Z-DS9OPrNq.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/executions.ts +195 -0
- package/src/__mocks__/notebook.ts +0 -1
- package/src/__tests__/CellStatus.test.tsx +19 -20
- package/src/components/dependency-graph/minimap-content.tsx +3 -2
- package/src/components/editor/__tests__/data-attributes.test.tsx +0 -7
- package/src/components/editor/__tests__/output-persistence.test.tsx +2 -2
- package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +1 -0
- package/src/components/editor/actions/useCellActionButton.tsx +7 -3
- package/src/components/editor/alerts/stdin-blocking-alert.tsx +3 -1
- package/src/components/editor/cell/CellStatus.tsx +23 -19
- package/src/components/editor/cell/RunButton.tsx +16 -6
- package/src/components/editor/cell/__tests__/useRunCells.test.ts +45 -0
- package/src/components/editor/cell/cell-context-menu.tsx +4 -1
- package/src/components/editor/cell/code/cell-editor.tsx +7 -5
- package/src/components/editor/cell/collapse.tsx +4 -0
- package/src/components/editor/cell/useRunCells.ts +18 -3
- package/src/components/editor/chrome/wrapper/sidebar.tsx +2 -4
- package/src/components/editor/header/status.tsx +4 -2
- package/src/components/editor/navigation/navigation.ts +3 -2
- package/src/components/editor/notebook-cell.tsx +82 -83
- package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +22 -16
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +12 -33
- package/src/components/editor/stdin-blocking-alert.tsx +3 -1
- package/src/components/scratchpad/scratchpad.tsx +4 -1
- package/src/components/slides/minimap.tsx +4 -2
- package/src/components/slides/reveal-component.tsx +14 -10
- package/src/components/slides/slide-cell-view.tsx +28 -24
- package/src/components/slides/slide.tsx +3 -4
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -389
- package/src/core/cells/__tests__/cell.test.ts +49 -154
- package/src/core/cells/__tests__/cells.test.ts +18 -127
- package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
- package/src/core/cells/__tests__/utils.test.ts +75 -247
- package/src/core/cells/actions.ts +5 -2
- package/src/core/cells/cell.ts +31 -133
- package/src/core/cells/cells.ts +14 -29
- package/src/core/cells/document.ts +3 -3
- package/src/core/cells/focus.ts +27 -6
- package/src/core/cells/layout.ts +1 -1
- package/src/core/cells/pending-delete-service.ts +5 -7
- package/src/core/cells/types.ts +13 -36
- package/src/core/cells/utils.ts +37 -68
- package/src/core/edit-app.tsx +2 -1
- package/src/core/executions/__tests__/atoms.test.ts +99 -0
- package/src/core/executions/__tests__/select.test.ts +268 -0
- package/src/core/executions/atoms.ts +206 -0
- package/src/core/executions/hooks.ts +31 -0
- package/src/core/executions/index.ts +31 -0
- package/src/core/executions/select.ts +175 -0
- package/src/core/export/__tests__/hooks.test.ts +0 -5
- package/src/core/islands/bootstrap.ts +4 -1
- package/src/core/islands/components/output-wrapper.tsx +5 -5
- package/src/core/kernel/handlers.ts +2 -11
- package/src/core/kernel/state.ts +0 -15
- package/src/core/lifecycle/render-policy.ts +12 -26
- package/src/core/network/__tests__/requests-lazy.test.ts +5 -5
- package/src/core/network/requests-lazy.ts +3 -3
- package/src/core/notebook/operations.ts +1 -1
- package/src/core/run-app.tsx +2 -1
- package/src/core/runtime/__tests__/adapter.test.ts +3 -3
- package/src/core/runtime/adapter.ts +4 -4
- package/src/core/session/useNotebookSession.tsx +3 -3
- package/src/core/wasm/bridge.ts +1 -1
- package/src/plugins/core/registerReactComponent.tsx +4 -13
- package/src/stories/cell-status.stories.tsx +17 -10
- package/src/stories/cell.stories.tsx +0 -31
- package/src/stories/layout/vertical/one-column.stories.tsx +0 -9
- package/src/utils/time.ts +10 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
/** Pure views over the replica's executions and results, by cell. */
|
|
4
|
+
|
|
5
|
+
import type * as Protocol from "@marimo-team/client/protocol";
|
|
6
|
+
import type { Execution, Executions, Results } from "@/core/notebook/replica";
|
|
7
|
+
import { type Milliseconds, Time } from "@/utils/time";
|
|
8
|
+
|
|
9
|
+
/** Where a cell is in its run. */
|
|
10
|
+
export type CellRunStatus = "idle" | "queued" | "running";
|
|
11
|
+
|
|
12
|
+
/** How a cell's last completed execution ended. */
|
|
13
|
+
export type CellOutcome = "value" | "error" | "interrupted" | "skipped";
|
|
14
|
+
|
|
15
|
+
export interface CellExecutionView {
|
|
16
|
+
readonly status: CellRunStatus;
|
|
17
|
+
/** Whether a cell it depends on is disabled, so it cannot run. */
|
|
18
|
+
readonly blocked: boolean;
|
|
19
|
+
/** Null until the cell completes once, and while it runs again. */
|
|
20
|
+
readonly outcome: CellOutcome | null;
|
|
21
|
+
/** When the current run started, or the last one, since the epoch. */
|
|
22
|
+
readonly startedAt: Milliseconds | null;
|
|
23
|
+
/** How long the last completed execution took, null while the cell runs again. */
|
|
24
|
+
readonly duration: Milliseconds | null;
|
|
25
|
+
/** Whether the running execution has shown a value so far. TODO(MO-8404) */
|
|
26
|
+
readonly rendered: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** What a cell's view is derived from. */
|
|
30
|
+
export interface CellExecutionInputs {
|
|
31
|
+
readonly executions: Executions;
|
|
32
|
+
readonly results: Results;
|
|
33
|
+
/**
|
|
34
|
+
* The execution each submitted cell had when its run was requested.
|
|
35
|
+
*
|
|
36
|
+
* A cell reads as queued until the host shows a newer execution for it.
|
|
37
|
+
*/
|
|
38
|
+
readonly submissions: ReadonlyMap<
|
|
39
|
+
Protocol.CellId,
|
|
40
|
+
Protocol.ExecutionId | null
|
|
41
|
+
>;
|
|
42
|
+
/** Cells the kernel reports as blocked by a disabled ancestor. */
|
|
43
|
+
readonly blocked: ReadonlySet<Protocol.CellId>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function outcomeOf(record: Protocol.Execution): CellOutcome | null {
|
|
47
|
+
const outcome = record.outcome;
|
|
48
|
+
if (outcome === null) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return outcome.kind;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function millis(iso: string | null): Milliseconds | null {
|
|
55
|
+
return iso === null ? null : Time.parse(iso).toMilliseconds();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function latestOf(
|
|
59
|
+
executions: Executions,
|
|
60
|
+
id: Protocol.CellId,
|
|
61
|
+
): Execution | null {
|
|
62
|
+
const executionId = executions.latest.get(id);
|
|
63
|
+
return executionId === undefined
|
|
64
|
+
? null
|
|
65
|
+
: (executions.executions.get(executionId) ?? null);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The execution a submission is measured against: the cell's latest on the
|
|
70
|
+
* stream, or its retained result once the stream has let that execution go.
|
|
71
|
+
*/
|
|
72
|
+
export function latestExecutionId(
|
|
73
|
+
inputs: Pick<CellExecutionInputs, "executions" | "results">,
|
|
74
|
+
id: Protocol.CellId,
|
|
75
|
+
): Protocol.ExecutionId | null {
|
|
76
|
+
return inputs.executions.latest.get(id) ?? inputs.results.get(id)?.id ?? null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function cellExecutionView(
|
|
80
|
+
inputs: CellExecutionInputs,
|
|
81
|
+
id: Protocol.CellId,
|
|
82
|
+
): CellExecutionView {
|
|
83
|
+
const latest = latestOf(inputs.executions, id);
|
|
84
|
+
const result = inputs.results.get(id) ?? null;
|
|
85
|
+
const submitted =
|
|
86
|
+
inputs.submissions.has(id) &&
|
|
87
|
+
inputs.submissions.get(id) === latestExecutionId(inputs, id);
|
|
88
|
+
let status: CellRunStatus = "idle";
|
|
89
|
+
let startedAt = result === null ? null : millis(result.started_at);
|
|
90
|
+
let rendered = false;
|
|
91
|
+
if (latest !== null && latest.record.status !== "completed") {
|
|
92
|
+
status = latest.record.status;
|
|
93
|
+
startedAt = millis(latest.record.started_at) ?? startedAt;
|
|
94
|
+
rendered = latest.record.outcome !== null;
|
|
95
|
+
} else if (submitted) {
|
|
96
|
+
status = "queued";
|
|
97
|
+
}
|
|
98
|
+
const settled = status === "idle";
|
|
99
|
+
const duration =
|
|
100
|
+
settled &&
|
|
101
|
+
result !== null &&
|
|
102
|
+
result.started_at !== null &&
|
|
103
|
+
result.completed_at !== null
|
|
104
|
+
? Time.parse(result.completed_at).since(Time.parse(result.started_at))
|
|
105
|
+
: null;
|
|
106
|
+
return {
|
|
107
|
+
status,
|
|
108
|
+
blocked: inputs.blocked.has(id),
|
|
109
|
+
outcome: settled && result !== null ? outcomeOf(result) : null,
|
|
110
|
+
startedAt,
|
|
111
|
+
duration,
|
|
112
|
+
rendered,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Executions the host has not completed, cells and scratch alike. */
|
|
117
|
+
function unfinished(executions: Executions): Execution[] {
|
|
118
|
+
return [...executions.executions.values()].filter(
|
|
119
|
+
(execution) => execution.record.status !== "completed",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function anyRunning(executions: Executions): boolean {
|
|
124
|
+
return unfinished(executions).some(
|
|
125
|
+
(execution) => execution.record.status === "running",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Cells and scratch work queued or running, plus submissions the host has not shown yet. */
|
|
130
|
+
export function queuedOrRunningCount(inputs: CellExecutionInputs): number {
|
|
131
|
+
const pending = unfinished(inputs.executions);
|
|
132
|
+
const shown = new Set(
|
|
133
|
+
pending.flatMap((execution) =>
|
|
134
|
+
execution.record.cell_id === null ? [] : [execution.record.cell_id],
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
let awaiting = 0;
|
|
138
|
+
for (const [cellId, executionId] of inputs.submissions) {
|
|
139
|
+
if (
|
|
140
|
+
!shown.has(cellId) &&
|
|
141
|
+
executionId === latestExecutionId(inputs, cellId)
|
|
142
|
+
) {
|
|
143
|
+
awaiting += 1;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return pending.length + awaiting;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function onlyScratchRunning(executions: Executions): boolean {
|
|
150
|
+
return unfinished(executions).every(
|
|
151
|
+
(execution) =>
|
|
152
|
+
execution.record.status !== "running" ||
|
|
153
|
+
execution.record.cell_id === null,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Where the scratchpad's work is, from its unfinished execution if any. */
|
|
158
|
+
export function scratchStatus(executions: Executions): CellRunStatus {
|
|
159
|
+
const scratch = unfinished(executions).find(
|
|
160
|
+
(execution) => execution.record.cell_id === null,
|
|
161
|
+
);
|
|
162
|
+
if (scratch === undefined || scratch.record.status === "completed") {
|
|
163
|
+
return "idle";
|
|
164
|
+
}
|
|
165
|
+
return scratch.record.status;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Cells with a running execution, in the order the host reported them. */
|
|
169
|
+
export function runningCellIds(executions: Executions): Protocol.CellId[] {
|
|
170
|
+
return unfinished(executions).flatMap((execution) =>
|
|
171
|
+
execution.record.status === "running" && execution.record.cell_id !== null
|
|
172
|
+
? [execution.record.cell_id]
|
|
173
|
+
: [],
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -67,11 +67,6 @@ describe("useEnrichCellOutputs", () => {
|
|
|
67
67
|
cellId,
|
|
68
68
|
{
|
|
69
69
|
output: cell.output || null,
|
|
70
|
-
status: cell.status || "idle",
|
|
71
|
-
interrupted: false,
|
|
72
|
-
errored: false,
|
|
73
|
-
runStartTimestamp: null,
|
|
74
|
-
runElapsedTimeMs: null,
|
|
75
70
|
stallTime: null as unknown as number,
|
|
76
71
|
...cell,
|
|
77
72
|
} as CellRuntimeState,
|
|
@@ -25,6 +25,7 @@ import { MarimoValueInputEvent } from "../dom/events";
|
|
|
25
25
|
import { UI_ELEMENT_REGISTRY } from "../dom/uiregistry";
|
|
26
26
|
import { FUNCTIONS_REGISTRY } from "../functions/FunctionRegistry";
|
|
27
27
|
import { createNotebookReplica } from "../notebook/replica";
|
|
28
|
+
import { observeCellMessage } from "../executions";
|
|
28
29
|
import {
|
|
29
30
|
handleCellNotificationeration,
|
|
30
31
|
handleKernelReady,
|
|
@@ -35,6 +36,7 @@ import { queryParamHandlers } from "../kernel/queryParamHandlers";
|
|
|
35
36
|
import { RuntimeState } from "../kernel/RuntimeState";
|
|
36
37
|
import { initialModeAtom } from "../mode";
|
|
37
38
|
import { requestClientAtom } from "../network/requests";
|
|
39
|
+
import { activeNotebookReplicaAtom } from "../session/active-notebook";
|
|
38
40
|
import { store as defaultStore } from "../state/jotai";
|
|
39
41
|
import type { IslandsPyodideBridge } from "./bridge";
|
|
40
42
|
import { MarimoIslandElement } from "./components/web-components";
|
|
@@ -131,6 +133,7 @@ export async function initializeIslands(
|
|
|
131
133
|
// The notebook's cells follow the managed session's host document.
|
|
132
134
|
const replica = createNotebookReplica(store);
|
|
133
135
|
replica.connect(bridge.notebookBackend, { onNotification: Functions.NOOP });
|
|
136
|
+
store.set(activeNotebookReplicaAtom, replica);
|
|
134
137
|
const documentModel = createDocumentModel({
|
|
135
138
|
store,
|
|
136
139
|
documentAtom: replica.documentAtom,
|
|
@@ -222,7 +225,6 @@ function handleNotification(
|
|
|
222
225
|
resetCellRuntimes: actions.resetCellRuntimes,
|
|
223
226
|
setLayoutData: Functions.NOOP,
|
|
224
227
|
setCapabilities: Functions.NOOP,
|
|
225
|
-
setKernelState: Functions.NOOP,
|
|
226
228
|
onError: Logger.error,
|
|
227
229
|
});
|
|
228
230
|
if (!window.customElements?.get(ISLAND_TAG_NAMES.ISLAND)) {
|
|
@@ -247,6 +249,7 @@ function handleNotification(
|
|
|
247
249
|
return;
|
|
248
250
|
|
|
249
251
|
case "cell-op":
|
|
252
|
+
observeCellMessage(store, msg);
|
|
250
253
|
handleCellNotificationeration(msg, actions.handleCellMessage);
|
|
251
254
|
return;
|
|
252
255
|
|
|
@@ -13,7 +13,7 @@ import { OutputRenderer } from "@/components/editor/Output";
|
|
|
13
13
|
import { type NotebookState, notebookAtom } from "@/core/cells/cells";
|
|
14
14
|
import type { CellId } from "@/core/cells/ids";
|
|
15
15
|
import { isOutputEmpty } from "@/core/cells/outputs";
|
|
16
|
-
import type
|
|
16
|
+
import { type CellRunStatus, useCellExecution } from "@/core/executions";
|
|
17
17
|
import { ISLAND_TAG_NAMES } from "../constants";
|
|
18
18
|
import { islandsHydratedAtom } from "../state";
|
|
19
19
|
import { IslandControls } from "./IslandControls";
|
|
@@ -69,7 +69,7 @@ export const MarimoOutputWrapper: React.FC<MarimoOutputWrapperProps> = ({
|
|
|
69
69
|
|
|
70
70
|
// Sync cell status to the host <marimo-island> element as a data attribute
|
|
71
71
|
// so downstream consumers can style based on status (e.g. [data-status="running"])
|
|
72
|
-
const status =
|
|
72
|
+
const { status } = useCellExecution(cellId);
|
|
73
73
|
useSyncStatusToIsland(wrapperRef, status);
|
|
74
74
|
|
|
75
75
|
// If no runtime yet, show static content
|
|
@@ -90,7 +90,7 @@ export const MarimoOutputWrapper: React.FC<MarimoOutputWrapperProps> = ({
|
|
|
90
90
|
return (
|
|
91
91
|
<div ref={wrapperRef} className="relative min-h-6">
|
|
92
92
|
<OutputRenderer message={runtime.output} />
|
|
93
|
-
<RunningIndicator
|
|
93
|
+
<RunningIndicator status={status} />
|
|
94
94
|
<IslandControls
|
|
95
95
|
cellId={cellId}
|
|
96
96
|
codeCallback={codeCallback}
|
|
@@ -118,8 +118,8 @@ function useSyncStatusToIsland(
|
|
|
118
118
|
/**
|
|
119
119
|
* Shows a spinning indicator when the cell is running
|
|
120
120
|
*/
|
|
121
|
-
const RunningIndicator: React.FC<{
|
|
122
|
-
if (
|
|
121
|
+
const RunningIndicator: React.FC<{ status: CellRunStatus }> = ({ status }) => {
|
|
122
|
+
if (status === "running") {
|
|
123
123
|
return (
|
|
124
124
|
<DelayRender>
|
|
125
125
|
<div className="absolute top-1 right-1">
|
|
@@ -14,7 +14,6 @@ import type {
|
|
|
14
14
|
CellMessage,
|
|
15
15
|
NotificationMessageData,
|
|
16
16
|
} from "./messages";
|
|
17
|
-
import type { KernelState } from "./state";
|
|
18
17
|
|
|
19
18
|
type KernelReadyData = NotificationMessageData<"kernel-ready">;
|
|
20
19
|
|
|
@@ -68,7 +67,6 @@ export function handleKernelReady(
|
|
|
68
67
|
data: LayoutData;
|
|
69
68
|
}) => void;
|
|
70
69
|
setCapabilities: (capabilities: Capabilities) => void;
|
|
71
|
-
setKernelState: (state: KernelState) => void;
|
|
72
70
|
onError: (error: Error) => void;
|
|
73
71
|
},
|
|
74
72
|
): LayoutState {
|
|
@@ -79,16 +77,13 @@ export function handleKernelReady(
|
|
|
79
77
|
resetCellRuntimes,
|
|
80
78
|
setLayoutData,
|
|
81
79
|
setCapabilities,
|
|
82
|
-
setKernelState,
|
|
83
80
|
onError,
|
|
84
81
|
} = opts;
|
|
85
82
|
const { resumed, ui_values, capabilities, auto_instantiated } = data;
|
|
86
83
|
|
|
87
84
|
resetCellRuntimes();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
lastExecutionTime: data.last_execution_time ?? {},
|
|
91
|
-
});
|
|
85
|
+
// TODO(MO-8403): Delete with `lastCodeRun`.
|
|
86
|
+
setLastRun({ lastCodeRun: data.last_executed_code ?? {} });
|
|
92
87
|
const layoutState = buildLayoutState(data, cells, setLayoutData);
|
|
93
88
|
|
|
94
89
|
setCapabilities(capabilities);
|
|
@@ -121,11 +116,7 @@ export function handleKernelReady(
|
|
|
121
116
|
values,
|
|
122
117
|
autoRun: autoInstantiate,
|
|
123
118
|
})
|
|
124
|
-
.then(() => {
|
|
125
|
-
setKernelState({ isInstantiated: true, error: null });
|
|
126
|
-
})
|
|
127
119
|
.catch((error) => {
|
|
128
|
-
setKernelState({ isInstantiated: false, error: error });
|
|
129
120
|
onError(new Error("Failed to instantiate", { cause: error }));
|
|
130
121
|
});
|
|
131
122
|
return layoutState;
|
package/src/core/kernel/state.ts
CHANGED
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { atom } from "jotai";
|
|
3
|
-
import { waitFor } from "../state/jotai";
|
|
4
|
-
|
|
5
|
-
export interface KernelState {
|
|
6
|
-
isInstantiated: boolean;
|
|
7
|
-
error: Error | null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const kernelStateAtom = atom<KernelState>({
|
|
11
|
-
isInstantiated: false,
|
|
12
|
-
error: null,
|
|
13
|
-
});
|
|
14
3
|
|
|
15
4
|
export const initialRunCompletedAtom = atom(false);
|
|
16
|
-
|
|
17
|
-
export function waitForKernelToBeInstantiated(): Promise<KernelState> {
|
|
18
|
-
return waitFor(kernelStateAtom, (value) => value.isInstantiated);
|
|
19
|
-
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { atom } from "jotai";
|
|
4
4
|
import { hasCellsAtom, notebookAtom } from "../cells/cells";
|
|
5
5
|
import { isOutputEmpty } from "../cells/outputs";
|
|
6
|
+
import { notebookQueuedOrRunningCountAtom } from "../executions";
|
|
6
7
|
import { KnownQueryParams } from "../constants";
|
|
7
8
|
import { showCodeInRunModeAtom } from "../meta/state";
|
|
8
9
|
import { type AppMode, initialModeAtom } from "../mode";
|
|
@@ -74,37 +75,22 @@ function computeRenderPolicy(input: {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* - `hasCachedOutputs`: any cell has a non-empty output.
|
|
80
|
-
* - `allCellsIdle`: every cell's status is idle — nothing queued or running.
|
|
81
|
-
* Vacuously true for an empty notebook, so `canPaint` still gates on
|
|
82
|
-
* `hasCells`.
|
|
83
|
-
*
|
|
84
|
-
* Fan out to two boolean selector atoms below so `renderPolicyAtom` only
|
|
85
|
-
* recomputes when one of the booleans actually flips, not on every notebook
|
|
78
|
+
* Whether any cell has a non-empty output. A boolean selector so
|
|
79
|
+
* `renderPolicyAtom` only recomputes when it flips, not on every notebook
|
|
86
80
|
* update.
|
|
87
81
|
*/
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (!isOutputEmpty(runtime.output)) {
|
|
93
|
-
hasCachedOutputs = true;
|
|
94
|
-
}
|
|
95
|
-
if (runtime.status !== "idle") {
|
|
96
|
-
allCellsIdle = false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return { hasCachedOutputs, allCellsIdle };
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const hasCachedOutputsAtom = atom(
|
|
103
|
-
(get) => get(cellRuntimeSummaryAtom).hasCachedOutputs,
|
|
82
|
+
const hasCachedOutputsAtom = atom((get) =>
|
|
83
|
+
Object.values(get(notebookAtom).cellRuntime).some(
|
|
84
|
+
(runtime) => !isOutputEmpty(runtime.output),
|
|
85
|
+
),
|
|
104
86
|
);
|
|
105
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Nothing queued or running, by the host's executions. Vacuously true for an
|
|
90
|
+
* empty notebook, so `canPaint` still gates on `hasCells`.
|
|
91
|
+
*/
|
|
106
92
|
const allCellsIdleAtom = atom(
|
|
107
|
-
(get) => get(
|
|
93
|
+
(get) => get(notebookQueuedOrRunningCountAtom) === 0,
|
|
108
94
|
);
|
|
109
95
|
|
|
110
96
|
export const renderPolicyAtom = atom<RenderPolicy>((get) =>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { cellId, requestId, uiElementId } from "@/__tests__/branded";
|
|
5
|
-
import {
|
|
5
|
+
import { waitForRuntimeRunning } from "../../executions";
|
|
6
6
|
import type { RuntimeManager } from "../../runtime/runtime";
|
|
7
7
|
import { waitForConnectionOpen } from "../connection";
|
|
8
8
|
import { createLazyRequests } from "../requests-lazy";
|
|
@@ -22,8 +22,8 @@ vi.mock("../connection", async () => {
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
// Mock the kernel state module
|
|
25
|
-
vi.mock("../../
|
|
26
|
-
|
|
25
|
+
vi.mock("../../executions", () => ({
|
|
26
|
+
waitForRuntimeRunning: vi.fn().mockResolvedValue(undefined),
|
|
27
27
|
}));
|
|
28
28
|
|
|
29
29
|
describe("createLazyRequests", () => {
|
|
@@ -119,7 +119,7 @@ describe("createLazyRequests", () => {
|
|
|
119
119
|
|
|
120
120
|
expect(mockInit).toHaveBeenCalledOnce();
|
|
121
121
|
expect(waitForConnectionOpen).toHaveBeenCalledOnce();
|
|
122
|
-
expect(
|
|
122
|
+
expect(waitForRuntimeRunning).not.toHaveBeenCalled();
|
|
123
123
|
expect(mockDelegate.installExportRequirements).toHaveBeenCalledWith({
|
|
124
124
|
format: "pdf",
|
|
125
125
|
});
|
|
@@ -139,7 +139,7 @@ describe("createLazyRequests", () => {
|
|
|
139
139
|
|
|
140
140
|
expect(mockInit).toHaveBeenCalledOnce();
|
|
141
141
|
expect(waitForConnectionOpen).toHaveBeenCalledOnce();
|
|
142
|
-
expect(
|
|
142
|
+
expect(waitForRuntimeRunning).not.toHaveBeenCalled();
|
|
143
143
|
expect(mockDelegate.sendInstantiate).toHaveBeenCalledWith(request);
|
|
144
144
|
});
|
|
145
145
|
|
|
@@ -4,7 +4,7 @@ import { NoKernelConnectedError } from "@/utils/errors";
|
|
|
4
4
|
import { Logger } from "@/utils/Logger";
|
|
5
5
|
import { Objects } from "@/utils/objects";
|
|
6
6
|
import { memoizeLastValue } from "@/utils/once";
|
|
7
|
-
import {
|
|
7
|
+
import { waitForRuntimeRunning } from "../executions";
|
|
8
8
|
import type { RuntimeManager } from "../runtime/runtime";
|
|
9
9
|
import { store } from "../state/jotai";
|
|
10
10
|
import { ConnectionState } from "../session/types";
|
|
@@ -192,7 +192,7 @@ export function createLazyRequests(
|
|
|
192
192
|
case "waitForConnectionOpen":
|
|
193
193
|
// Wait for connection but don't start it
|
|
194
194
|
await waitForConnectionOpen();
|
|
195
|
-
await
|
|
195
|
+
await waitForRuntimeRunning();
|
|
196
196
|
return request(...args);
|
|
197
197
|
|
|
198
198
|
case "startConnectionWithoutKernel":
|
|
@@ -201,7 +201,7 @@ export function createLazyRequests(
|
|
|
201
201
|
await initOnce(runtimeManager);
|
|
202
202
|
await waitForConnectionOpen();
|
|
203
203
|
if (action === "startConnection") {
|
|
204
|
-
await
|
|
204
|
+
await waitForRuntimeRunning();
|
|
205
205
|
}
|
|
206
206
|
return request(...args);
|
|
207
207
|
|
|
@@ -18,7 +18,7 @@ export const notebookOperationsAtom = atom((get) => {
|
|
|
18
18
|
/**
|
|
19
19
|
* Whether this client may edit the document.
|
|
20
20
|
*
|
|
21
|
-
* TODO(MO-
|
|
21
|
+
* TODO(MO-8352): The host advertises the same operations to every
|
|
22
22
|
* connection of a notebook, so a kiosk connection, an interactor that may
|
|
23
23
|
* not edit, narrows it here. Once the host narrows `operations` per
|
|
24
24
|
* connection by consumer capability, the kiosk term goes.
|
package/src/core/run-app.tsx
CHANGED
|
@@ -11,7 +11,8 @@ import { Spinner } from "@/components/icons/spinner";
|
|
|
11
11
|
import { buttonVariants } from "@/components/ui/button";
|
|
12
12
|
import { cn } from "@/utils/cn";
|
|
13
13
|
import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
|
|
14
|
-
import { hasCellsAtom
|
|
14
|
+
import { hasCellsAtom } from "./cells/cells";
|
|
15
|
+
import { notebookIsRunningAtom } from "./executions";
|
|
15
16
|
import type { AppConfig } from "./config/config-schema";
|
|
16
17
|
import { DocumentProvider } from "./notebook/document";
|
|
17
18
|
import { RuntimeState } from "./kernel/RuntimeState";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { createStore } from "jotai";
|
|
4
4
|
import { describe, expect, it } from "vitest";
|
|
5
5
|
import { islandsInitializedAtom } from "../../islands/state";
|
|
6
|
-
import {
|
|
6
|
+
import { MockExecutions } from "@/__mocks__/executions";
|
|
7
7
|
import { connectionAtom } from "../../network/connection";
|
|
8
8
|
import { wasmInitStateAtom } from "../../wasm/state";
|
|
9
9
|
import { ConnectionState } from "../../session/types";
|
|
@@ -122,13 +122,13 @@ describe("islandsAdapter", () => {
|
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
describe("canRunCellsAtom", () => {
|
|
125
|
-
it("is false on a remote adapter until
|
|
125
|
+
it("is false on a remote adapter until the host reports the runtime running", () => {
|
|
126
126
|
const store = createStore();
|
|
127
127
|
// Default adapter in test env is remote.
|
|
128
128
|
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
129
129
|
expect(store.get(canRunCellsAtom)).toBe(false);
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
MockExecutions.seed(store, [], MockExecutions.runtime("running"));
|
|
132
132
|
expect(store.get(canRunCellsAtom)).toBe(true);
|
|
133
133
|
});
|
|
134
134
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { type Atom, atom } from "jotai";
|
|
4
|
-
import {
|
|
4
|
+
import { runtimeStatusAtom } from "../executions";
|
|
5
5
|
import { islandsInitializedAtom } from "../islands/state";
|
|
6
6
|
import { isIslands } from "../islands/utils";
|
|
7
7
|
import { connectionAtom } from "../network/connection";
|
|
@@ -163,8 +163,8 @@ function selectAdapter(): RuntimeAdapter {
|
|
|
163
163
|
export const runtimeAdapterAtom = atom<RuntimeAdapter>(selectAdapter());
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
|
-
* Remote runtimes require both the WS to be open *and* the
|
|
167
|
-
*
|
|
166
|
+
* Remote runtimes require both the WS to be open *and* the host to report
|
|
167
|
+
* the runtime running. WASM/islands have no separate kernel handshake.
|
|
168
168
|
* Static notebooks never run cells.
|
|
169
169
|
*/
|
|
170
170
|
export const canRunCellsAtom = atom<boolean>((get) => {
|
|
@@ -176,7 +176,7 @@ export const canRunCellsAtom = atom<boolean>((get) => {
|
|
|
176
176
|
return false;
|
|
177
177
|
}
|
|
178
178
|
if (adapter.kind === "remote") {
|
|
179
|
-
return get(
|
|
179
|
+
return get(runtimeStatusAtom) === "running";
|
|
180
180
|
}
|
|
181
181
|
return true;
|
|
182
182
|
});
|
|
@@ -62,7 +62,8 @@ import {
|
|
|
62
62
|
type StableSessionId,
|
|
63
63
|
stableSessionIdAtom,
|
|
64
64
|
} from "../kernel/session";
|
|
65
|
-
import {
|
|
65
|
+
import { observeCellMessage } from "../executions";
|
|
66
|
+
import { initialRunCompletedAtom } from "../kernel/state";
|
|
66
67
|
import { useLayoutActions } from "../layout/state";
|
|
67
68
|
import { kioskModeAtom } from "../mode";
|
|
68
69
|
import { connectionAtom, startupProgressAtom } from "../network/connection";
|
|
@@ -99,7 +100,6 @@ export function useNotebookSession(opts: {
|
|
|
99
100
|
} = useReducerActions();
|
|
100
101
|
const { reconcileStagedCells } = useStagedAICellsActions();
|
|
101
102
|
const { addCellNotification } = useRunsActions();
|
|
102
|
-
const setKernelState = useSetAtom(kernelStateAtom);
|
|
103
103
|
const setInitialRunCompleted = useSetAtom(initialRunCompletedAtom);
|
|
104
104
|
const { setVariables, setMetadata } = useVariablesActions();
|
|
105
105
|
const { addColumnPreview } = useDatasetsActions();
|
|
@@ -158,7 +158,6 @@ export function useNotebookSession(opts: {
|
|
|
158
158
|
resetCellRuntimes,
|
|
159
159
|
setLayoutData,
|
|
160
160
|
setCapabilities,
|
|
161
|
-
setKernelState,
|
|
162
161
|
onError: showBoundary,
|
|
163
162
|
});
|
|
164
163
|
store.set(savedLayoutAtom, layout);
|
|
@@ -222,6 +221,7 @@ export function useNotebookSession(opts: {
|
|
|
222
221
|
FUNCTIONS_REGISTRY.resolve(msg.data.function_call_id, msg.data);
|
|
223
222
|
return;
|
|
224
223
|
case "cell-op": {
|
|
224
|
+
observeCellMessage(store, msg.data);
|
|
225
225
|
handleCellNotificationeration(msg.data, handleCellMessage);
|
|
226
226
|
const cellData = getNotebook().cellData[msg.data.cell_id];
|
|
227
227
|
if (!cellData) {
|
package/src/core/wasm/bridge.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { reloadSafe } from "@/utils/reload-safe";
|
|
|
13
13
|
import { once } from "@/utils/once";
|
|
14
14
|
import { generateUUID } from "@/utils/uuid";
|
|
15
15
|
import { createModuleWorker } from "@/utils/worker";
|
|
16
|
-
import { notebookIsRunningAtom } from "../
|
|
16
|
+
import { notebookIsRunningAtom } from "../executions";
|
|
17
17
|
import { activeNotebookReplicaAtom } from "../session/active-notebook";
|
|
18
18
|
import { flushDocument } from "../cells/document";
|
|
19
19
|
import { createWasmNotebookBackend } from "../transports/wasm";
|
|
@@ -24,9 +24,9 @@ import ReactDOM, { type Root } from "react-dom/client";
|
|
|
24
24
|
import useEvent from "react-use-event-hook";
|
|
25
25
|
import { type ZodSchema, z } from "zod";
|
|
26
26
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
27
|
-
import { notebookAtom } from "@/core/cells/cells.ts";
|
|
28
27
|
import { HTMLCellId } from "@/core/cells/ids.ts";
|
|
29
28
|
import { isUninstantiated } from "@/core/cells/utils";
|
|
29
|
+
import { cellExecutionInputsAtom, cellExecutionView } from "@/core/executions";
|
|
30
30
|
import { createInputEvent, MarimoValueUpdateEvent } from "@/core/dom/events";
|
|
31
31
|
import { getUIElementObjectId } from "@/core/dom/ui-element";
|
|
32
32
|
import { UIElementRegistry } from "@/core/dom/uiregistry";
|
|
@@ -205,18 +205,9 @@ function PluginSlotInternal<T>(
|
|
|
205
205
|
if (cellId && !isStatic) {
|
|
206
206
|
// If the cell is not initialized, throw an error
|
|
207
207
|
// Continue if the cell is static, so we can propagate a clearer error message.
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const cellNotInitialized = isUninstantiated({
|
|
213
|
-
executionTime:
|
|
214
|
-
cellRuntime.runElapsedTimeMs ?? cellData.lastExecutionTime,
|
|
215
|
-
status: cellRuntime.status,
|
|
216
|
-
errored: cellRuntime.errored,
|
|
217
|
-
interrupted: cellRuntime.interrupted,
|
|
218
|
-
stopped: cellRuntime.stopped,
|
|
219
|
-
});
|
|
208
|
+
const cellNotInitialized = isUninstantiated(
|
|
209
|
+
cellExecutionView(store.get(cellExecutionInputsAtom), cellId),
|
|
210
|
+
);
|
|
220
211
|
|
|
221
212
|
if (cellNotInitialized) {
|
|
222
213
|
throw new CellNotInitializedError();
|