@marimo-team/islands 0.23.14-dev6 → 0.23.14-dev61
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-Du3_nUzI.js → ConnectedDataExplorerComponent-CU4fZJzG.js} +4 -4
- package/dist/assets/__vite-browser-external-BQCLNwri.js +1 -0
- package/dist/assets/{worker-DEDLIQQV.js → worker-DAWRHcPq.js} +2 -2
- package/dist/{chat-ui-BZxLHwyD.js → chat-ui-DinOvbWu.js} +3248 -3044
- package/dist/{click-outside-container-BDd67_1U.js → click-outside-container-BLPjMamz.js} +141 -113
- package/dist/{code-visibility-C90Am97q.js → code-visibility-EvXXzjfW.js} +1169 -960
- package/dist/data-grid-overlay-editor-C6lxUJp-.js +136 -0
- package/dist/{dist-D_bzzWBm.js → dist-Bf9f8MuT.js} +3 -3
- package/dist/{formats-d6MhLuQ9.js → formats-Dzx4J_z1.js} +1 -1
- package/dist/{glide-data-editor-DkzAInWG.js → glide-data-editor-CjTu7ukN.js} +2084 -1889
- package/dist/{html-to-image-CGp_08St.js → html-to-image-_wGfk8V-.js} +2389 -2322
- package/dist/{input-CbEz_aj_.js → input-DtsN7xm-.js} +1 -1
- package/dist/main.js +3630 -1801
- package/dist/{mermaid-CJW9vIyO.js → mermaid-BYqXy_NE.js} +2 -2
- package/dist/{number-overlay-editor-D-a0qCT8.js → number-overlay-editor-BLJXvX9c.js} +1 -1
- package/dist/{process-output-R6JsYrv3.js → process-output-Mh4UrjwM.js} +1 -1
- package/dist/{reveal-component-C_u9FY4_.js → reveal-component-CKfV5wlk.js} +600 -596
- package/dist/{spec-Bv-XlYiv.js → spec-Cz-Bj1JI.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-D-l5s8nn.js → toDate-CWNNlFEX.js} +15 -7
- package/dist/{useAsyncData-1Dhzjfwf.js → useAsyncData-KfHB8wQR.js} +1 -1
- package/dist/{useDeepCompareMemoize-CDWT3BDz.js → useDeepCompareMemoize-nJMtxhm4.js} +1 -1
- package/dist/{useLifecycle-AHlswLw-.js → useLifecycle-DegSo0lV.js} +1 -1
- package/dist/{useTheme-BrYvK-_A.js → useTheme-6eZ3GOTS.js} +31 -26
- package/dist/{vega-component-Pk6lyc_a.js → vega-component-DzyyM9fc.js} +5 -5
- package/package.json +4 -4
- package/src/__mocks__/requests.ts +1 -0
- package/src/__tests__/CellStatus.test.tsx +1 -2
- package/src/components/app-config/user-config-form.tsx +52 -0
- package/src/components/chat/acp/agent-panel.tsx +35 -1
- package/src/components/chat/chat-panel.tsx +68 -29
- package/src/components/data-table/__tests__/column-explorer.test.tsx +25 -0
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +60 -3
- package/src/components/data-table/charts/__tests__/altair-generator.test.ts +24 -0
- package/src/components/data-table/charts/__tests__/merge-index-fields.test.ts +33 -0
- package/src/components/data-table/charts/chart-spec/altair-generator.ts +4 -1
- package/src/components/data-table/charts/charts.tsx +23 -1
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +33 -12
- package/src/components/data-table/column-visibility-dropdown.tsx +15 -0
- package/src/components/debugger/debugger-code.tsx +7 -2
- package/src/components/editor/actions/useNotebookActions.tsx +2 -2
- package/src/components/editor/cell/CellStatus.tsx +1 -20
- package/src/components/editor/cell/PendingDeleteConfirmation.tsx +1 -1
- package/src/components/editor/cell/cell-context-menu.tsx +2 -2
- package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -3
- package/src/components/editor/chrome/wrapper/__tests__/useOpenAiAssistant.test.ts +36 -0
- package/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx +6 -36
- package/src/components/editor/chrome/wrapper/useAiPanel.ts +3 -1
- package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +88 -0
- package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +79 -0
- package/src/components/editor/code/readonly-python-code.tsx +60 -33
- package/src/components/editor/errors/__tests__/auto-fix.test.ts +23 -0
- package/src/components/editor/errors/auto-fix.tsx +88 -34
- package/src/components/editor/errors/fix-mode.ts +1 -1
- package/src/components/editor/notebook-cell.tsx +7 -0
- package/src/components/editor/output/ImageOutput.tsx +20 -6
- package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -2
- package/src/components/editor/output/__tests__/ImageOutput.test.tsx +53 -0
- package/src/components/editor/output/__tests__/traceback.test.tsx +14 -6
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +7 -2
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +7 -3
- package/src/components/lifecycle/ProgressiveBoundary.tsx +42 -0
- package/src/components/lifecycle/RuntimeStatusBadge.tsx +59 -0
- package/src/components/lifecycle/__tests__/ProgressiveBoundary.test.tsx +147 -0
- package/src/components/lifecycle/__tests__/RuntimeStatusBadge.test.tsx +72 -0
- package/src/components/slides/__tests__/slide.test.tsx +33 -0
- package/src/components/slides/minimap.tsx +7 -1
- package/src/components/slides/reveal-component.tsx +3 -0
- package/src/components/slides/slide-cell-view.tsx +10 -6
- package/src/components/slides/slide.tsx +16 -13
- package/src/components/tracing/tracing.tsx +2 -1
- package/src/core/ai/config.ts +2 -2
- package/src/core/ai/context/__tests__/registry.test.ts +133 -3
- package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -1
- package/src/core/ai/context/providers/__tests__/error.test.ts +200 -15
- package/src/core/ai/context/providers/datasource.ts +27 -2
- package/src/core/ai/context/providers/error.ts +226 -36
- package/src/core/ai/context/registry.ts +77 -43
- package/src/core/ai/state.ts +11 -0
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/__tests__/readonly-code-display.test.ts +46 -0
- package/src/core/cells/cell.ts +5 -3
- package/src/core/cells/readonly-code-display.ts +35 -0
- package/src/core/codemirror/__tests__/setup.test.ts +33 -1
- package/src/core/codemirror/ai/resources.ts +15 -10
- package/src/core/codemirror/cells/__tests__/debugger-decorations.test.ts +185 -0
- package/src/core/codemirror/cells/__tests__/debugger-state.test.ts +224 -0
- package/src/core/codemirror/cells/__tests__/line-timing-decorations.test.ts +126 -0
- package/src/core/codemirror/cells/debugger-decorations.ts +188 -0
- package/src/core/codemirror/cells/debugger-state.ts +133 -0
- package/src/core/codemirror/cells/extensions.ts +32 -4
- package/src/core/codemirror/cells/line-timing-decorations.ts +165 -0
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +75 -3
- package/src/core/codemirror/go-to-definition/extension.ts +3 -3
- package/src/core/codemirror/go-to-definition/underline.ts +5 -14
- package/src/core/codemirror/go-to-definition/utils.ts +49 -1
- package/src/core/codemirror/utils.ts +15 -0
- package/src/core/config/__tests__/config-schema.test.ts +17 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/config/config.ts +10 -0
- package/src/core/config/feature-flag.tsx +4 -0
- package/src/core/edit-app.tsx +8 -5
- package/src/core/errors/errors.ts +2 -1
- package/src/core/islands/bootstrap.ts +4 -3
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/lifecycle/__tests__/render-policy.test.ts +247 -0
- package/src/core/lifecycle/render-policy.ts +125 -0
- package/src/core/mime.ts +11 -4
- package/src/core/network/__tests__/api.test.ts +17 -0
- package/src/core/network/__tests__/requests-toasting.test.tsx +46 -0
- package/src/core/network/api.ts +17 -6
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +10 -1
- package/src/core/network/types.ts +2 -0
- package/src/core/run-app.tsx +27 -23
- package/src/core/runtime/__tests__/adapter.test.ts +160 -0
- package/src/core/runtime/__tests__/runtime.test.ts +45 -0
- package/src/core/runtime/adapter.ts +182 -0
- package/src/core/runtime/runtime.ts +30 -16
- package/src/core/wasm/PyodideLoader.tsx +20 -62
- package/src/core/wasm/bridge.ts +13 -4
- package/src/core/wasm/state.ts +8 -19
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +4 -0
- package/src/core/websocket/__tests__/useWebSocket.test.ts +49 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +338 -0
- package/src/core/websocket/transports/sse.ts +308 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +58 -7
- package/src/core/websocket/useWebSocket.tsx +12 -2
- package/src/css/app/codemirror.css +14 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +55 -0
- package/src/hooks/useDelayElapsed.ts +27 -0
- package/src/mount.tsx +3 -3
- package/src/plugins/core/RenderHTML.tsx +35 -1
- package/src/plugins/core/__test__/RenderHTML.test.ts +44 -0
- package/src/plugins/impl/DataTablePlugin.tsx +1 -0
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +33 -254
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +116 -174
- package/src/plugins/impl/anywidget/__tests__/host.test.ts +313 -0
- package/src/plugins/impl/anywidget/__tests__/model-proxy.test.ts +158 -0
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +8 -177
- package/src/plugins/impl/anywidget/__tests__/registry.test.ts +708 -0
- package/src/plugins/impl/anywidget/__tests__/resolve-widget.test.ts +132 -0
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +305 -133
- package/src/plugins/impl/anywidget/__tests__/widget-ref.test.ts +28 -0
- package/src/plugins/impl/anywidget/host.ts +54 -0
- package/src/plugins/impl/anywidget/model-proxy.ts +70 -0
- package/src/plugins/impl/anywidget/model.ts +59 -239
- package/src/plugins/impl/anywidget/registry.ts +268 -0
- package/src/plugins/impl/anywidget/resolve-widget.ts +138 -0
- package/src/plugins/impl/anywidget/runtime.ts +422 -0
- package/src/plugins/impl/anywidget/types.ts +14 -0
- package/src/plugins/impl/anywidget/widget-binding.ts +280 -119
- package/src/plugins/impl/anywidget/widget-ref.ts +29 -0
- package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +187 -0
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +6 -0
- package/src/plugins/impl/data-editor/glide-portal.tsx +73 -0
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +3 -2
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +16 -13
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +1 -1
- package/src/utils/errors.ts +15 -0
- package/src/utils/time.ts +20 -0
- package/dist/assets/__vite-browser-external-DAm_YW43.js +0 -1
- package/dist/data-grid-overlay-editor-mfEJ5475.js +0 -128
- package/src/components/editor/chrome/panels/__tests__/snippet-display.test.ts +0 -22
- package/src/components/editor/chrome/panels/snippet-display.ts +0 -27
- package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +0 -3
- package/src/core/wasm/__tests__/PyodideLoader.test.ts +0 -72
- package/src/core/wasm/__tests__/state.test.ts +0 -124
- package/src/plugins/impl/anywidget/schemas.ts +0 -32
|
@@ -55,12 +55,9 @@ class MetaUnderlineVariablePlugin {
|
|
|
55
55
|
private view: EditorView;
|
|
56
56
|
private commandClickMode: boolean;
|
|
57
57
|
private hoveredRange: { from: number; to: number; position: number } | null;
|
|
58
|
-
private onClick: (view: EditorView
|
|
58
|
+
private onClick: (view: EditorView) => void;
|
|
59
59
|
|
|
60
|
-
constructor(
|
|
61
|
-
view: EditorView,
|
|
62
|
-
onClick: (view: EditorView, variableName: string) => void,
|
|
63
|
-
) {
|
|
60
|
+
constructor(view: EditorView, onClick: (view: EditorView) => void) {
|
|
64
61
|
this.view = view;
|
|
65
62
|
this.commandClickMode = false;
|
|
66
63
|
this.hoveredRange = null;
|
|
@@ -191,20 +188,16 @@ class MetaUnderlineVariablePlugin {
|
|
|
191
188
|
// If we have a hovered range, go to it
|
|
192
189
|
private click = (event: MouseEvent) => {
|
|
193
190
|
if (this.hoveredRange) {
|
|
194
|
-
const variableName = this.view.state.doc.sliceString(
|
|
195
|
-
this.hoveredRange.from,
|
|
196
|
-
this.hoveredRange.to,
|
|
197
|
-
);
|
|
198
191
|
event.preventDefault();
|
|
199
192
|
event.stopPropagation();
|
|
200
|
-
|
|
201
|
-
// Move the cursor to the clicked position
|
|
193
|
+
// Move the cursor before resolving so LSP uses the clicked position.
|
|
202
194
|
this.view.dispatch({
|
|
203
195
|
selection: {
|
|
204
196
|
head: this.hoveredRange.position,
|
|
205
197
|
anchor: this.hoveredRange.position,
|
|
206
198
|
},
|
|
207
199
|
});
|
|
200
|
+
this.onClick(this.view);
|
|
208
201
|
}
|
|
209
202
|
};
|
|
210
203
|
|
|
@@ -217,7 +210,5 @@ class MetaUnderlineVariablePlugin {
|
|
|
217
210
|
}
|
|
218
211
|
}
|
|
219
212
|
|
|
220
|
-
export const createUnderlinePlugin = (
|
|
221
|
-
onClick: (view: EditorView, variableName: string) => void,
|
|
222
|
-
) =>
|
|
213
|
+
export const createUnderlinePlugin = (onClick: (view: EditorView) => void) =>
|
|
223
214
|
ViewPlugin.define((view) => new MetaUnderlineVariablePlugin(view, onClick));
|
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { closeCompletion } from "@codemirror/autocomplete";
|
|
4
4
|
import type { EditorState } from "@codemirror/state";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
closeHoverTooltips,
|
|
7
|
+
type EditorView,
|
|
8
|
+
type KeyBinding,
|
|
9
|
+
keymap,
|
|
10
|
+
} from "@codemirror/view";
|
|
6
11
|
import type { CellId } from "@/core/cells/ids";
|
|
12
|
+
import { hotkeysAtom, platformAtom } from "../../config/config";
|
|
7
13
|
import { notebookAtom } from "../../cells/cells";
|
|
8
14
|
import { store } from "../../state/jotai";
|
|
9
15
|
import { variablesAtom } from "../../variables/state";
|
|
@@ -11,6 +17,20 @@ import type { VariableName, Variables } from "../../variables/types";
|
|
|
11
17
|
import { getPositionAtWordBounds } from "../completion/hints";
|
|
12
18
|
import { goToLine, goToVariableDefinition } from "./commands";
|
|
13
19
|
|
|
20
|
+
function keymapBindingMatchesHotkey(
|
|
21
|
+
binding: KeyBinding,
|
|
22
|
+
hotkey: string,
|
|
23
|
+
): boolean {
|
|
24
|
+
const platform = store.get(platformAtom);
|
|
25
|
+
const bindingKey =
|
|
26
|
+
platform === "mac"
|
|
27
|
+
? (binding.mac ?? binding.key)
|
|
28
|
+
: platform === "windows"
|
|
29
|
+
? (binding.win ?? binding.key)
|
|
30
|
+
: (binding.linux ?? binding.key);
|
|
31
|
+
return bindingKey === hotkey;
|
|
32
|
+
}
|
|
33
|
+
|
|
14
34
|
/**
|
|
15
35
|
* Get the word under the cursor.
|
|
16
36
|
*/
|
|
@@ -68,6 +88,34 @@ export function goToDefinitionAtCursorPosition(view: EditorView): boolean {
|
|
|
68
88
|
return goToDefinition(view, word, position);
|
|
69
89
|
}
|
|
70
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Invoke the editor's go-to-definition keymap handlers for the configured
|
|
93
|
+
* hotkey. Matches CodeMirror key strings directly (including customized
|
|
94
|
+
* shortcuts like `Ctrl-F12`) instead of synthesizing keyboard events.
|
|
95
|
+
*/
|
|
96
|
+
export function requestLspGoToDefinition(
|
|
97
|
+
view: EditorView,
|
|
98
|
+
hotkey = store.get(hotkeysAtom).getHotkey("cell.goToDefinition").key,
|
|
99
|
+
): boolean {
|
|
100
|
+
for (const binding of view.state.facet(keymap).flat()) {
|
|
101
|
+
if (keymapBindingMatchesHotkey(binding, hotkey) && binding.run?.(view)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Go to the definition under the cursor, falling back to the language server
|
|
110
|
+
* when marimo cannot resolve the symbol (e.g. external library code).
|
|
111
|
+
*/
|
|
112
|
+
export function goToDefinitionWithLspFallback(view: EditorView): boolean {
|
|
113
|
+
if (goToDefinitionAtCursorPosition(view)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return requestLspGoToDefinition(view);
|
|
117
|
+
}
|
|
118
|
+
|
|
71
119
|
/**
|
|
72
120
|
* Go to the definition of the variable under the cursor.
|
|
73
121
|
* @param view The editor view at which the command was invoked.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { EditorState, Transaction } from "@codemirror/state";
|
|
3
3
|
import type { EditorView, ViewUpdate } from "@codemirror/view";
|
|
4
4
|
import { getCM } from "@replit/codemirror-vim";
|
|
5
|
+
import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
5
6
|
|
|
6
7
|
export function isAtStartOfEditor(ev: { state: EditorState }) {
|
|
7
8
|
const main = ev.state.selection.main;
|
|
@@ -37,6 +38,20 @@ export function moveToEndOfEditor(ev: EditorView | undefined) {
|
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** We delay the focus and move to end of the editor until React has rendered the prefilled value. */
|
|
42
|
+
export function focusInputAndMoveToEnd(
|
|
43
|
+
ref: React.RefObject<ReactCodeMirrorRef | null>,
|
|
44
|
+
) {
|
|
45
|
+
requestAnimationFrame(() => {
|
|
46
|
+
const view = ref.current?.view;
|
|
47
|
+
if (!view) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
view.focus();
|
|
51
|
+
moveToEndOfEditor(view);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
export function isInVimMode(ev: EditorView): boolean {
|
|
41
56
|
return getCM(ev)?.state.vim != null;
|
|
42
57
|
}
|
|
@@ -4,6 +4,7 @@ import { createStore } from "jotai";
|
|
|
4
4
|
import { expect, test } from "vitest";
|
|
5
5
|
import {
|
|
6
6
|
configOverridesAtom,
|
|
7
|
+
connectionTransportTypeAtom,
|
|
7
8
|
resolvedMarimoConfigAtom,
|
|
8
9
|
userConfigAtom,
|
|
9
10
|
} from "../config";
|
|
@@ -296,3 +297,19 @@ test("resolvedMarimoConfigAtom overrides correctly and does not mutate the origi
|
|
|
296
297
|
formatting: { line_length: 79 },
|
|
297
298
|
});
|
|
298
299
|
});
|
|
300
|
+
|
|
301
|
+
test("connectionTransportTypeAtom defaults to websocket", () => {
|
|
302
|
+
const store = createStore();
|
|
303
|
+
store.set(userConfigAtom, defaultUserConfig());
|
|
304
|
+
expect(store.get(connectionTransportTypeAtom)).toBe("websocket");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("connectionTransportTypeAtom reads server.transport", () => {
|
|
308
|
+
const store = createStore();
|
|
309
|
+
const config = defaultUserConfig();
|
|
310
|
+
store.set(userConfigAtom, {
|
|
311
|
+
...config,
|
|
312
|
+
server: { ...config.server, transport: "sse" },
|
|
313
|
+
});
|
|
314
|
+
expect(store.get(connectionTransportTypeAtom)).toBe("sse");
|
|
315
|
+
});
|
|
@@ -44,6 +44,16 @@ export const autoSaveConfigAtom = atom((get) => {
|
|
|
44
44
|
return get(resolvedMarimoConfigAtom).save;
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The transport used to stream kernel messages from the server.
|
|
49
|
+
* Experimental; configured via `server.transport`.
|
|
50
|
+
*/
|
|
51
|
+
export const connectionTransportTypeAtom = atom<"websocket" | "sse">((get) => {
|
|
52
|
+
return get(resolvedMarimoConfigAtom).server.transport === "sse"
|
|
53
|
+
? "sse"
|
|
54
|
+
: "websocket";
|
|
55
|
+
});
|
|
56
|
+
|
|
47
57
|
export const aiAtom = atom((get) => {
|
|
48
58
|
return get(resolvedMarimoConfigAtom).ai;
|
|
49
59
|
});
|
|
@@ -11,6 +11,8 @@ export interface ExperimentalFeatures {
|
|
|
11
11
|
rtc_v2: boolean;
|
|
12
12
|
cache_panel: boolean;
|
|
13
13
|
external_agents: boolean;
|
|
14
|
+
debugger: boolean; // Live frame-watching debugger (gutter breakpoints + pdb)
|
|
15
|
+
line_timing: boolean; // Green active-line highlight + per-line elapsed timer
|
|
14
16
|
// Add new feature flags here
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -20,6 +22,8 @@ const defaultValues: ExperimentalFeatures = {
|
|
|
20
22
|
rtc_v2: false,
|
|
21
23
|
cache_panel: false,
|
|
22
24
|
external_agents: import.meta.env.DEV,
|
|
25
|
+
debugger: false,
|
|
26
|
+
line_timing: false,
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
export function getFeatureFlag<T extends keyof ExperimentalFeatures>(
|
package/src/core/edit-app.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import { AppHeader } from "@/components/editor/header/app-header";
|
|
|
13
13
|
import { FilenameForm } from "@/components/editor/header/filename-form";
|
|
14
14
|
import { MultiCellActionToolbar } from "@/components/editor/navigation/multi-cell-action-toolbar";
|
|
15
15
|
import { ViewerBanner } from "@/components/editor/viewer-banner";
|
|
16
|
+
import { ProgressiveBoundary } from "@/components/lifecycle/ProgressiveBoundary";
|
|
16
17
|
import { cn } from "@/utils/cn";
|
|
17
18
|
import { Paths } from "@/utils/paths";
|
|
18
19
|
import { AppContainer } from "../components/editor/app-container";
|
|
@@ -64,7 +65,6 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
64
65
|
useCellActions();
|
|
65
66
|
const viewState = useAtomValue(viewStateAtom);
|
|
66
67
|
const numColumns = useAtomValue(numColumnsAtom);
|
|
67
|
-
const hasCells = useAtomValue(hasCellsAtom);
|
|
68
68
|
const filename = useFilename();
|
|
69
69
|
const setLastSavedNotebook = useSetAtom(lastSavedNotebookAtom);
|
|
70
70
|
const { sendComponentValues, sendInterrupt } = useRequestClient();
|
|
@@ -181,13 +181,16 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
181
181
|
|
|
182
182
|
<ViewerBanner />
|
|
183
183
|
|
|
184
|
-
{/* Don't render until we have a single cell
|
|
185
|
-
|
|
184
|
+
{/* Don't render until we have a single cell. NotStartedConnectionAlert
|
|
185
|
+
still covers the "no remote runtime started" prompt. */}
|
|
186
|
+
<ProgressiveBoundary
|
|
187
|
+
requires={hasCellsAtom}
|
|
188
|
+
fallback={<NotStartedConnectionAlert />}
|
|
189
|
+
>
|
|
186
190
|
<CellsRenderer appConfig={appConfig} mode={viewState.mode}>
|
|
187
191
|
{editableCellsArray}
|
|
188
192
|
</CellsRenderer>
|
|
189
|
-
|
|
190
|
-
{!hasCells && <NotStartedConnectionAlert />}
|
|
193
|
+
</ProgressiveBoundary>
|
|
191
194
|
</AppContainer>
|
|
192
195
|
<MultiCellActionToolbar />
|
|
193
196
|
{!hideControls && (
|
|
@@ -6,6 +6,7 @@ import type { AiCompletionCell } from "../ai/state";
|
|
|
6
6
|
import type { CellId } from "../cells/ids";
|
|
7
7
|
import type { MarimoError } from "../kernel/messages";
|
|
8
8
|
import { wrapInFunction } from "./utils";
|
|
9
|
+
import { store } from "../state/jotai";
|
|
9
10
|
|
|
10
11
|
interface AIFix {
|
|
11
12
|
setAiCompletionCell: (opts: AiCompletionCell) => void;
|
|
@@ -84,7 +85,7 @@ export function getAutoFixes(
|
|
|
84
85
|
description: "Fix the SQL statement",
|
|
85
86
|
fixType: "ai",
|
|
86
87
|
onFix: async (ctx) => {
|
|
87
|
-
const datasourceContext = getDatasourceContext(ctx.cellId);
|
|
88
|
+
const datasourceContext = getDatasourceContext(ctx.cellId, store);
|
|
88
89
|
let initialPrompt = `Fix the SQL statement: ${error.msg}.`;
|
|
89
90
|
if (datasourceContext) {
|
|
90
91
|
initialPrompt += `\nDatabase schema: ${datasourceContext}`;
|
|
@@ -5,8 +5,8 @@ import { ISLAND_CSS_CLASSES, ISLAND_TAG_NAMES } from "@/core/islands/constants";
|
|
|
5
5
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
6
6
|
import {
|
|
7
7
|
handleWidgetMessage,
|
|
8
|
-
|
|
9
|
-
} from "@/plugins/impl/anywidget/
|
|
8
|
+
WIDGET_REGISTRY,
|
|
9
|
+
} from "@/plugins/impl/anywidget/registry";
|
|
10
10
|
import { initializePlugins } from "@/plugins/plugins";
|
|
11
11
|
import { Functions } from "@/utils/functions";
|
|
12
12
|
import {
|
|
@@ -174,6 +174,7 @@ function handleMessage(
|
|
|
174
174
|
case "completion-result":
|
|
175
175
|
case "reload":
|
|
176
176
|
case "focus-cell":
|
|
177
|
+
case "active-line":
|
|
177
178
|
case "variables":
|
|
178
179
|
case "variable-values":
|
|
179
180
|
case "data-column-preview":
|
|
@@ -252,7 +253,7 @@ function handleMessage(
|
|
|
252
253
|
return;
|
|
253
254
|
|
|
254
255
|
case "model-lifecycle":
|
|
255
|
-
handleWidgetMessage(
|
|
256
|
+
handleWidgetMessage(WIDGET_REGISTRY, msg.data);
|
|
256
257
|
return;
|
|
257
258
|
|
|
258
259
|
case "consumer-capabilities":
|
|
@@ -244,6 +244,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
244
244
|
sendListFiles = throwNotImplemented;
|
|
245
245
|
sendSearchFiles = throwNotImplemented;
|
|
246
246
|
sendPdb = throwNotImplemented;
|
|
247
|
+
sendSetBreakpoints = throwNotImplemented;
|
|
247
248
|
sendCreateFileOrFolder = throwNotImplemented;
|
|
248
249
|
sendDeleteFileOrFolder = throwNotImplemented;
|
|
249
250
|
sendCopyFileOrFolder = throwNotImplemented;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { visibleForTesting } from "../render-policy";
|
|
5
|
+
|
|
6
|
+
const { computeRenderPolicy } = visibleForTesting;
|
|
7
|
+
|
|
8
|
+
const originalHref = window.location.href;
|
|
9
|
+
function setSearch(search: string) {
|
|
10
|
+
window.history.replaceState(null, "", `/${search}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("computeRenderPolicy", () => {
|
|
14
|
+
it("shows code in edit mode", () => {
|
|
15
|
+
const policy = computeRenderPolicy({
|
|
16
|
+
mode: "edit",
|
|
17
|
+
showAppCode: false, // ignored in edit mode
|
|
18
|
+
hasCells: true,
|
|
19
|
+
hasCachedOutputs: false,
|
|
20
|
+
allCellsIdle: false,
|
|
21
|
+
});
|
|
22
|
+
expect(policy.showCode).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("hides code in present mode regardless of config", () => {
|
|
26
|
+
const policy = computeRenderPolicy({
|
|
27
|
+
mode: "present",
|
|
28
|
+
showAppCode: true,
|
|
29
|
+
hasCells: true,
|
|
30
|
+
hasCachedOutputs: true,
|
|
31
|
+
allCellsIdle: false,
|
|
32
|
+
});
|
|
33
|
+
expect(policy.showCode).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("respects showAppCode in read mode", () => {
|
|
37
|
+
expect(
|
|
38
|
+
computeRenderPolicy({
|
|
39
|
+
mode: "read",
|
|
40
|
+
showAppCode: true,
|
|
41
|
+
hasCells: true,
|
|
42
|
+
hasCachedOutputs: false,
|
|
43
|
+
allCellsIdle: false,
|
|
44
|
+
}).showCode,
|
|
45
|
+
).toBe(true);
|
|
46
|
+
|
|
47
|
+
expect(
|
|
48
|
+
computeRenderPolicy({
|
|
49
|
+
mode: "read",
|
|
50
|
+
showAppCode: false,
|
|
51
|
+
hasCells: true,
|
|
52
|
+
hasCachedOutputs: false,
|
|
53
|
+
allCellsIdle: false,
|
|
54
|
+
}).showCode,
|
|
55
|
+
).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("reports showCachedOutputs when cells and cached outputs are present", () => {
|
|
59
|
+
expect(
|
|
60
|
+
computeRenderPolicy({
|
|
61
|
+
mode: "read",
|
|
62
|
+
showAppCode: false,
|
|
63
|
+
hasCells: true,
|
|
64
|
+
hasCachedOutputs: true,
|
|
65
|
+
allCellsIdle: false,
|
|
66
|
+
}).showCachedOutputs,
|
|
67
|
+
).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("does not report showCachedOutputs when there are no cells", () => {
|
|
71
|
+
expect(
|
|
72
|
+
computeRenderPolicy({
|
|
73
|
+
mode: "read",
|
|
74
|
+
showAppCode: false,
|
|
75
|
+
hasCells: false,
|
|
76
|
+
hasCachedOutputs: true,
|
|
77
|
+
allCellsIdle: false,
|
|
78
|
+
}).showCachedOutputs,
|
|
79
|
+
).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("only reports showCachedOutputs for real outputs, not an idle notebook", () => {
|
|
83
|
+
// Regression guard: `showCachedOutputs` must stay honest even when the
|
|
84
|
+
// notebook is settled with no outputs (that only affects `canPaint`).
|
|
85
|
+
expect(
|
|
86
|
+
computeRenderPolicy({
|
|
87
|
+
mode: "read",
|
|
88
|
+
showAppCode: false,
|
|
89
|
+
hasCells: true,
|
|
90
|
+
hasCachedOutputs: false,
|
|
91
|
+
allCellsIdle: true,
|
|
92
|
+
}).showCachedOutputs,
|
|
93
|
+
).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("computeRenderPolicy — read-mode code visibility", () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
setSearch("");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
window.history.replaceState(null, "", originalHref);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("hides code when the server stripped sources (?include-code=false)", () => {
|
|
107
|
+
setSearch("?include-code=false");
|
|
108
|
+
const policy = computeRenderPolicy({
|
|
109
|
+
mode: "read",
|
|
110
|
+
showAppCode: true, // config would show code, but sources are stripped
|
|
111
|
+
hasCells: true,
|
|
112
|
+
hasCachedOutputs: false,
|
|
113
|
+
allCellsIdle: false,
|
|
114
|
+
});
|
|
115
|
+
expect(policy.showCode).toBe(false);
|
|
116
|
+
// With no code, no outputs, and still running, there is nothing to paint.
|
|
117
|
+
expect(policy.canPaint).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("include-code=false still paints when cached outputs exist", () => {
|
|
121
|
+
setSearch("?include-code=false");
|
|
122
|
+
expect(
|
|
123
|
+
computeRenderPolicy({
|
|
124
|
+
mode: "read",
|
|
125
|
+
showAppCode: true,
|
|
126
|
+
hasCells: true,
|
|
127
|
+
hasCachedOutputs: true,
|
|
128
|
+
allCellsIdle: false,
|
|
129
|
+
}).canPaint,
|
|
130
|
+
).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("computeRenderPolicy.canPaint", () => {
|
|
135
|
+
it("is false when no cells exist, even if all (zero) cells are vacuously idle", () => {
|
|
136
|
+
expect(
|
|
137
|
+
computeRenderPolicy({
|
|
138
|
+
mode: "read",
|
|
139
|
+
showAppCode: true,
|
|
140
|
+
hasCells: false,
|
|
141
|
+
hasCachedOutputs: false,
|
|
142
|
+
allCellsIdle: true,
|
|
143
|
+
}).canPaint,
|
|
144
|
+
).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("is true in edit mode whenever cells exist", () => {
|
|
148
|
+
expect(
|
|
149
|
+
computeRenderPolicy({
|
|
150
|
+
mode: "edit",
|
|
151
|
+
showAppCode: false,
|
|
152
|
+
hasCells: true,
|
|
153
|
+
hasCachedOutputs: false,
|
|
154
|
+
allCellsIdle: false,
|
|
155
|
+
}).canPaint,
|
|
156
|
+
).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("is true in read mode when code is visible", () => {
|
|
160
|
+
expect(
|
|
161
|
+
computeRenderPolicy({
|
|
162
|
+
mode: "read",
|
|
163
|
+
showAppCode: true,
|
|
164
|
+
hasCells: true,
|
|
165
|
+
hasCachedOutputs: false,
|
|
166
|
+
allCellsIdle: false,
|
|
167
|
+
}).canPaint,
|
|
168
|
+
).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("is false in headless read mode while the notebook is still running with no outputs", () => {
|
|
172
|
+
expect(
|
|
173
|
+
computeRenderPolicy({
|
|
174
|
+
mode: "read",
|
|
175
|
+
showAppCode: false,
|
|
176
|
+
hasCells: true,
|
|
177
|
+
hasCachedOutputs: false,
|
|
178
|
+
allCellsIdle: false,
|
|
179
|
+
}).canPaint,
|
|
180
|
+
).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("is true in headless read mode with cached outputs", () => {
|
|
184
|
+
expect(
|
|
185
|
+
computeRenderPolicy({
|
|
186
|
+
mode: "read",
|
|
187
|
+
showAppCode: false,
|
|
188
|
+
hasCells: true,
|
|
189
|
+
hasCachedOutputs: true,
|
|
190
|
+
allCellsIdle: false,
|
|
191
|
+
}).canPaint,
|
|
192
|
+
).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("is true in headless read mode for a settled notebook with no outputs", () => {
|
|
196
|
+
// A notebook that ran to completion producing no outputs must still paint
|
|
197
|
+
// (empty) rather than sit on a spinner — parity with the pre-framework
|
|
198
|
+
// `hasAnyOutputAtom` idle fallback.
|
|
199
|
+
expect(
|
|
200
|
+
computeRenderPolicy({
|
|
201
|
+
mode: "read",
|
|
202
|
+
showAppCode: false,
|
|
203
|
+
hasCells: true,
|
|
204
|
+
hasCachedOutputs: false,
|
|
205
|
+
allCellsIdle: true,
|
|
206
|
+
}).canPaint,
|
|
207
|
+
).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// In present mode `showCode` is always false, so `canPaint` rides entirely
|
|
211
|
+
// on cached outputs / a settled notebook.
|
|
212
|
+
it("is true in present mode with cached outputs", () => {
|
|
213
|
+
expect(
|
|
214
|
+
computeRenderPolicy({
|
|
215
|
+
mode: "present",
|
|
216
|
+
showAppCode: true,
|
|
217
|
+
hasCells: true,
|
|
218
|
+
hasCachedOutputs: true,
|
|
219
|
+
allCellsIdle: false,
|
|
220
|
+
}).canPaint,
|
|
221
|
+
).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("is false in present mode while still running with no outputs", () => {
|
|
225
|
+
expect(
|
|
226
|
+
computeRenderPolicy({
|
|
227
|
+
mode: "present",
|
|
228
|
+
showAppCode: true,
|
|
229
|
+
hasCells: true,
|
|
230
|
+
hasCachedOutputs: false,
|
|
231
|
+
allCellsIdle: false,
|
|
232
|
+
}).canPaint,
|
|
233
|
+
).toBe(false);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("is true in present mode for a settled notebook with no outputs", () => {
|
|
237
|
+
expect(
|
|
238
|
+
computeRenderPolicy({
|
|
239
|
+
mode: "present",
|
|
240
|
+
showAppCode: true,
|
|
241
|
+
hasCells: true,
|
|
242
|
+
hasCachedOutputs: false,
|
|
243
|
+
allCellsIdle: true,
|
|
244
|
+
}).canPaint,
|
|
245
|
+
).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { atom } from "jotai";
|
|
4
|
+
import { hasCellsAtom, notebookAtom } from "../cells/cells";
|
|
5
|
+
import { isOutputEmpty } from "../cells/outputs";
|
|
6
|
+
import { KnownQueryParams } from "../constants";
|
|
7
|
+
import { showCodeInRunModeAtom } from "../meta/state";
|
|
8
|
+
import { type AppMode, initialModeAtom } from "../mode";
|
|
9
|
+
import { hasQueryParam } from "@/utils/urls";
|
|
10
|
+
|
|
11
|
+
export interface RenderPolicy {
|
|
12
|
+
/** False in `present` mode, and in `read` mode when the user has opted to hide code. */
|
|
13
|
+
showCode: boolean;
|
|
14
|
+
/** True only when there are real, non-empty cached outputs to display. */
|
|
15
|
+
showCachedOutputs: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the current view has anything to paint without waiting on the
|
|
18
|
+
* runtime:
|
|
19
|
+
* - In edit/home/gallery mode, this is `hasCells` — the editor renders the
|
|
20
|
+
* cell skeleton even when output is empty.
|
|
21
|
+
* - In read/present mode with code visible, the cell source is enough.
|
|
22
|
+
* - In headless read mode (code hidden), we need either cached outputs or a
|
|
23
|
+
* settled notebook (all cells idle) — a completed but output-less notebook
|
|
24
|
+
* is still "done" and shouldn't strand on an infinite spinner.
|
|
25
|
+
*/
|
|
26
|
+
canPaint: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readCodeVisibility(
|
|
30
|
+
mode: AppMode | undefined,
|
|
31
|
+
showAppCode: boolean,
|
|
32
|
+
): boolean {
|
|
33
|
+
if (mode === "edit" || mode === "home" || mode === "gallery") {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (mode === "present") {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
// read mode (or undefined). If `marimo run` stripped the sources
|
|
40
|
+
// (`--include-code` off), there is no code to show regardless of any other
|
|
41
|
+
// signal — mirror `core/meta/code-visibility.ts` so the two don't drift.
|
|
42
|
+
if (hasQueryParam(KnownQueryParams.includeCode, "false")) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// Otherwise the URL `show-code` param wins, then the view config.
|
|
46
|
+
if (hasQueryParam(KnownQueryParams.showCode, "false")) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (hasQueryParam(KnownQueryParams.showCode, "true")) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return showAppCode;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function computeRenderPolicy(input: {
|
|
56
|
+
mode: AppMode | undefined;
|
|
57
|
+
showAppCode: boolean;
|
|
58
|
+
hasCells: boolean;
|
|
59
|
+
hasCachedOutputs: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* True when every cell is idle. A settled notebook is paintable even with no
|
|
62
|
+
* visible outputs — otherwise a completed but output-less notebook viewed in
|
|
63
|
+
* headless read mode would strand on an infinite spinner. Mirrors the
|
|
64
|
+
* pre-framework `hasAnyOutputAtom` idle fallback.
|
|
65
|
+
*/
|
|
66
|
+
allCellsIdle: boolean;
|
|
67
|
+
}): RenderPolicy {
|
|
68
|
+
const showCode = readCodeVisibility(input.mode, input.showAppCode);
|
|
69
|
+
const showCachedOutputs = input.hasCells && input.hasCachedOutputs;
|
|
70
|
+
const canPaint =
|
|
71
|
+
input.hasCells && (showCode || showCachedOutputs || input.allCellsIdle);
|
|
72
|
+
|
|
73
|
+
return { showCode, showCachedOutputs, canPaint };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Both cell-runtime facts the render policy needs, computed in a single pass
|
|
78
|
+
* over `cellRuntime`:
|
|
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
|
|
86
|
+
* update.
|
|
87
|
+
*/
|
|
88
|
+
const cellRuntimeSummaryAtom = atom((get) => {
|
|
89
|
+
let hasCachedOutputs = false;
|
|
90
|
+
let allCellsIdle = true;
|
|
91
|
+
for (const runtime of Object.values(get(notebookAtom).cellRuntime)) {
|
|
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,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const allCellsIdleAtom = atom(
|
|
107
|
+
(get) => get(cellRuntimeSummaryAtom).allCellsIdle,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
export const renderPolicyAtom = atom<RenderPolicy>((get) =>
|
|
111
|
+
computeRenderPolicy({
|
|
112
|
+
mode: get(initialModeAtom),
|
|
113
|
+
showAppCode: get(showCodeInRunModeAtom),
|
|
114
|
+
hasCells: get(hasCellsAtom),
|
|
115
|
+
hasCachedOutputs: get(hasCachedOutputsAtom),
|
|
116
|
+
allCellsIdle: get(allCellsIdleAtom),
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
/** Sugar over `renderPolicyAtom.canPaint`. */
|
|
121
|
+
export const canPaintAtom = atom((get) => get(renderPolicyAtom).canPaint);
|
|
122
|
+
|
|
123
|
+
export const visibleForTesting = {
|
|
124
|
+
computeRenderPolicy,
|
|
125
|
+
};
|