@marimo-team/islands 0.25.3-dev191 → 0.25.3-dev193

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 (154) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BBbOElk3.js → ConnectedDataExplorerComponent-BRuwLOIx.js} +9 -9
  2. package/dist/{ErrorBoundary--X0z83Yx.js → ErrorBoundary-BVu_xT-p.js} +2 -2
  3. package/dist/{abnfDiagram-VCTEODGH-DhMT6Uhd.js → abnfDiagram-VCTEODGH-LlJTOOU_.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-BVbEiJLq.js → architectureDiagram-5GKGNRK7-9PQtGLLZ.js} +1 -1
  5. package/dist/{arrays-R6JsImiT.js → arrays-DA6KkLfq.js} +6 -12
  6. package/dist/assets/__vite-browser-external-BCJsx8QS.js +1 -0
  7. package/dist/assets/{worker-C6e9ERyd.js → worker-DodjWT_Z.js} +2 -2
  8. package/dist/{capabilities-CI4xUPsp.js → capabilities-CvWU-ey_.js} +1 -1
  9. package/dist/{chat-ui-BguxTQEL.js → chat-ui-Bl2euMBD.js} +93 -93
  10. package/dist/{chunk-IMKFNOWR-By5hSbJJ.js → chunk-IMKFNOWR-DcXv-BD5.js} +1 -1
  11. package/dist/{chunk-SHT3W25Y-DFrEYG54.js → chunk-SHT3W25Y-VwnELN23.js} +1 -1
  12. package/dist/{chunk-SVP7TREG-DFXGc9ew.js → chunk-SVP7TREG-DgQpI5Zi.js} +1 -1
  13. package/dist/{chunk-TICWLB2K-DZdNIpoZ.js → chunk-TICWLB2K-CTg6VTJT.js} +1 -1
  14. package/dist/{classDiagram-ZZMXUADV-Cgj_eEQA.js → classDiagram-ZZMXUADV-UzmcFrnW.js} +2 -2
  15. package/dist/{classDiagram-v2-VYDZK3BY-DLAUrSqj.js → classDiagram-v2-VYDZK3BY-DhjiI6IQ.js} +2 -2
  16. package/dist/{code-visibility-uDyLo9_i.js → code-visibility-BTVimCUw.js} +38 -42
  17. package/dist/{common-DWKY45Md.js → common-fTgq7gO-.js} +849 -849
  18. package/dist/{cynefinDiagram-5FMLGOSQ-Cikf9AKa.js → cynefinDiagram-5FMLGOSQ-O4ShIbJX.js} +1 -1
  19. package/dist/{dagre-GXQ25YYZ-C5KEi5_l.js → dagre-GXQ25YYZ-dtHhhcPu.js} +1 -1
  20. package/dist/{data-grid-overlay-editor-CRnYWD1L.js → data-grid-overlay-editor-DAZZkLWg.js} +1 -1
  21. package/dist/{diagram-S7CK7UJ4-BaoBTQjR.js → diagram-S7CK7UJ4-ppPYVZ6W.js} +1 -1
  22. package/dist/{diagram-UQ7AKVKN-DDufBsUA.js → diagram-UQ7AKVKN-CMZDf--k.js} +1 -1
  23. package/dist/{diagram-VX7I27RA-CY6RALJ8.js → diagram-VX7I27RA-CFvJdx-C.js} +1 -1
  24. package/dist/{diagram-Z3DM3KII-Bc-LHsD6.js → diagram-Z3DM3KII-Dt6_uFbv.js} +1 -1
  25. package/dist/{dropdown-menu-drDjxJyV.js → dropdown-menu-CeaOsjWZ.js} +2 -2
  26. package/dist/{ebnfDiagram-PWID7BFC-Cz0J-tcC.js → ebnfDiagram-PWID7BFC-DpRFrKK-.js} +2 -2
  27. package/dist/{erDiagram-RLTQ6QDP-DTXTDgFu.js → erDiagram-RLTQ6QDP-CqAU4b0u.js} +1 -1
  28. package/dist/{error-entries-CZ1NQ6Kv.js → error-entries-CYGwr5-O.js} +5596 -6012
  29. package/dist/feedback-button-DzFFn-n4.js +374 -0
  30. package/dist/{flowDiagram-HODETNUW-RbwWf2zc.js → flowDiagram-HODETNUW-i8-Gw8HO.js} +2 -2
  31. package/dist/{formats-DbXnijtH.js → formats-BtoHHy42.js} +1 -1
  32. package/dist/{glide-data-editor-CvRiA49N.js → glide-data-editor-DFsqVbWR.js} +5 -5
  33. package/dist/{globals-BR4CmGOS.js → globals-Dr0MRANu.js} +2 -2
  34. package/dist/{html-to-image-D_4_hQ8U.js → html-to-image-Cy5UDpQJ.js} +56 -56
  35. package/dist/{infoDiagram-27XIBGKW-DlmzZjmI.js → infoDiagram-27XIBGKW-DpfOKdLL.js} +1 -1
  36. package/dist/{ishikawaDiagram-5VMMS53U-EN3TYP_q.js → ishikawaDiagram-5VMMS53U-Dg7mrMG7.js} +1 -1
  37. package/dist/{kanban-definition-UXKFOSKX-CK_-Ru7Q.js → kanban-definition-UXKFOSKX-QB6e3Jrj.js} +1 -1
  38. package/dist/main.js +5875 -5876
  39. package/dist/{mermaid-DU9ZRmkG.js → mermaid-CeWG4Egr.js} +48 -48
  40. package/dist/{mindmap-definition-YA3MSWOX-C-hqgUvF.js → mindmap-definition-YA3MSWOX-BTz3uTGe.js} +1 -1
  41. package/dist/{number-overlay-editor-DiYGbxNa.js → number-overlay-editor-DupOSYHx.js} +1 -1
  42. package/dist/{once-eG8Vmvjd.js → once-DWGO2mQ2.js} +1 -1
  43. package/dist/{pegDiagram-XKGWAZYB-BiMejYPB.js → pegDiagram-XKGWAZYB-3ZIEa3mB.js} +2 -2
  44. package/dist/{pieDiagram-E7YTZNPT-B1zCZ1eq.js → pieDiagram-E7YTZNPT-BJAlk046.js} +1 -1
  45. package/dist/{process-output-DxSCHBWp.js → process-output-q9MTAIMX.js} +2 -2
  46. package/dist/{railroadDiagram-O6MQD6OU-BLcfaASl.js → railroadDiagram-O6MQD6OU-Dn3bsSRa.js} +2 -2
  47. package/dist/{requirementDiagram-BXWQKSXE-DtBOf52-.js → requirementDiagram-BXWQKSXE-wcOmQQCL.js} +1 -1
  48. package/dist/{reveal-component-D3DsXPIm.js → reveal-component-Bg8rP_Uo.js} +17 -17
  49. package/dist/{sequenceDiagram-WJ2MYXX4-DIwF19_n.js → sequenceDiagram-WJ2MYXX4-DSh_qxoF.js} +1 -1
  50. package/dist/{spec-DZDnnlgT.js → spec-C7Fos-If.js} +1 -1
  51. package/dist/{stateDiagram-D77RDMKH-sWk1AdYL.js → stateDiagram-D77RDMKH-DH_H8NMH.js} +2 -2
  52. package/dist/{stateDiagram-v2-MP3YSRHH-CiNo-OI7.js → stateDiagram-v2-MP3YSRHH-D_JM7MEK.js} +2 -2
  53. package/dist/style.css +1 -1
  54. package/dist/{swimlanes-42K2YHIH-Bj3hiOY5.js → swimlanes-42K2YHIH-CfoTJO-9.js} +1 -1
  55. package/dist/{swimlanesDiagram-VR7AAH4N-BzEo2r_N.js → swimlanesDiagram-VR7AAH4N-Z_QhvK0G.js} +2 -2
  56. package/dist/{swiper-component-BkCm-kLg.js → swiper-component-D2geL1Ku.js} +1 -1
  57. package/dist/{timeline-definition-24CTP7MA-CrPQk42H.js → timeline-definition-24CTP7MA-BFtdScaL.js} +1 -1
  58. package/dist/{toDate-Cu2pIZcC.js → toDate-CQAFSwos.js} +24 -24
  59. package/dist/{useAsyncData-DEhLdRIu.js → useAsyncData-BeyZyjso.js} +1 -1
  60. package/dist/{useDeepCompareMemoize-RjZGVhBV.js → useDeepCompareMemoize-DOQgae0W.js} +1 -1
  61. package/dist/{useIframeCapabilities-CSdWn6NP.js → useIframeCapabilities-DNvaj9wM.js} +1 -1
  62. package/dist/{useLifecycle-DtcB2s1z.js → useLifecycle-n2lnCFS8.js} +1 -1
  63. package/dist/{useTheme-CoEtr4mK.js → useTheme-CY777LXN.js} +303 -99
  64. package/dist/{vega-component-BhjTrP9-.js → vega-component-0nQzA-I8.js} +6 -6
  65. package/dist/{vennDiagram-4TSXK5OY-BaYMoIpj.js → vennDiagram-4TSXK5OY-BXY8IGCA.js} +1 -1
  66. package/dist/{wardleyDiagram-VM6X3IG4-DjHeuj8p.js → wardleyDiagram-VM6X3IG4-D0tFe6eC.js} +1 -1
  67. package/dist/{xychartDiagram-S5SC5T6Z-Bc_hcuXS.js → xychartDiagram-S5SC5T6Z-Dx8wFM-y.js} +1 -1
  68. package/package.json +1 -1
  69. package/src/__mocks__/notebook.ts +0 -1
  70. package/src/__mocks__/partial.ts +26 -0
  71. package/src/__tests__/main.test.tsx +24 -1
  72. package/src/components/app-config/app-config-form.tsx +5 -9
  73. package/src/components/editor/__tests__/data-attributes.test.tsx +0 -1
  74. package/src/components/editor/__tests__/output-persistence.test.tsx +0 -1
  75. package/src/components/editor/actions/useCellActionButton.tsx +6 -5
  76. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  77. package/src/components/editor/actions/useNotebookActions.tsx +5 -4
  78. package/src/components/editor/cell/code/cell-editor.tsx +13 -6
  79. package/src/components/editor/cell/collapse.tsx +6 -1
  80. package/src/components/editor/chrome/components/feedback-button.tsx +1 -7
  81. package/src/components/editor/controls/Controls.tsx +1 -1
  82. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +8 -6
  83. package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -15
  84. package/src/components/editor/navigation/__tests__/selection.test.ts +44 -21
  85. package/src/components/editor/navigation/focus-utils.ts +19 -0
  86. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +6 -13
  87. package/src/components/editor/navigation/navigation.ts +52 -43
  88. package/src/components/editor/navigation/selection.ts +30 -29
  89. package/src/components/editor/notebook-cell.tsx +10 -18
  90. package/src/components/editor/renderers/cell-array.tsx +6 -5
  91. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
  92. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
  93. package/src/components/pages/edit-page.tsx +0 -13
  94. package/src/components/scratchpad/scratchpad.tsx +0 -1
  95. package/src/components/slides/reveal-component.tsx +1 -1
  96. package/src/components/slides/slide-cell-view.tsx +0 -1
  97. package/src/components/sort/SortableCellsProvider.tsx +6 -5
  98. package/src/core/MarimoApp.tsx +1 -1
  99. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -37
  100. package/src/core/cells/__tests__/actions.test.ts +190 -80
  101. package/src/core/cells/__tests__/arrange.test.ts +263 -0
  102. package/src/core/cells/__tests__/cells.test.ts +11 -1101
  103. package/src/core/cells/__tests__/document.test.ts +3 -32
  104. package/src/core/cells/__tests__/editor.test.ts +57 -0
  105. package/src/core/cells/__tests__/harness.ts +82 -0
  106. package/src/core/cells/__tests__/layout.test.ts +189 -0
  107. package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
  108. package/src/core/cells/__tests__/session.test.ts +329 -174
  109. package/src/core/cells/__tests__/utils.test.ts +0 -1
  110. package/src/core/cells/actions.ts +247 -82
  111. package/src/core/cells/arrange.ts +341 -0
  112. package/src/core/cells/cells.ts +27 -516
  113. package/src/core/cells/document.ts +6 -5
  114. package/src/core/cells/edit.ts +30 -0
  115. package/src/core/cells/editor.ts +82 -0
  116. package/src/core/cells/layout.ts +152 -0
  117. package/src/core/cells/session.ts +75 -52
  118. package/src/core/cells/types.ts +0 -5
  119. package/src/core/cells/utils.ts +6 -4
  120. package/src/core/config/__tests__/app-config.test.ts +64 -0
  121. package/src/core/config/__tests__/config-schema.test.ts +1 -1
  122. package/src/core/config/config-schema.ts +6 -2
  123. package/src/core/config/config.ts +26 -12
  124. package/src/core/islands/bootstrap.ts +0 -1
  125. package/src/core/kernel/handlers.ts +1 -13
  126. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  127. package/src/core/meta/code-visibility.ts +18 -9
  128. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  129. package/src/core/notebook/document/__tests__/codemirror.test.ts +15 -0
  130. package/src/core/notebook/index.ts +2 -1
  131. package/src/core/notebook/operations.ts +30 -0
  132. package/src/core/saving/__tests__/state.test.ts +111 -0
  133. package/src/core/saving/save-component.tsx +19 -33
  134. package/src/core/saving/state.ts +38 -39
  135. package/src/core/saving/useAutoSave.ts +8 -35
  136. package/src/core/session/__tests__/backend.test.ts +1 -0
  137. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +1 -0
  138. package/src/core/session/active-notebook.ts +11 -0
  139. package/src/core/session/backend.ts +17 -19
  140. package/src/core/session/embedded.ts +7 -0
  141. package/src/core/session/useNotebookReplica.ts +3 -1
  142. package/src/core/session/useNotebookSession.tsx +7 -8
  143. package/src/core/transports/__tests__/static.test.ts +139 -8
  144. package/src/core/transports/static.ts +98 -16
  145. package/src/core/wasm/__tests__/bridge.test.ts +1 -0
  146. package/src/mount.tsx +19 -10
  147. package/src/stories/cell.stories.tsx +0 -2
  148. package/src/stories/layout/vertical/one-column.stories.tsx +0 -2
  149. package/src/utils/__tests__/id-tree.test.ts +20 -1548
  150. package/src/utils/id-tree.tsx +12 -537
  151. package/dist/assets/__vite-browser-external-C8gQho07.js +0 -1
  152. package/dist/feedback-button-CDf1yRm_.js +0 -379
  153. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  154. package/src/components/editor/navigation/state.ts +0 -65
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.25.3-dev191",
3
+ "version": "0.25.3-dev193",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -54,7 +54,6 @@ export const MockNotebook = {
54
54
  cellLogs: [],
55
55
  history: [],
56
56
  scrollKey: null,
57
- untouchedNewCells: new Set(),
58
57
  };
59
58
  },
60
59
 
@@ -0,0 +1,26 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * A partial mock of `T` that throws when a member the test did not provide
5
+ * is read, so a test cannot silently depend on an undefined field.
6
+ */
7
+ export function partial<T extends object>(
8
+ members: Partial<T>,
9
+ name = "mock",
10
+ ): T {
11
+ return new Proxy(members as T, {
12
+ get(target, key, receiver) {
13
+ // Only the members the test gave count as provided, not those every
14
+ // object inherits. Promise detection and well-known symbols may probe
15
+ // any object.
16
+ if (
17
+ Object.hasOwn(target, key) ||
18
+ typeof key === "symbol" ||
19
+ key === "then"
20
+ ) {
21
+ return Reflect.get(target, key, receiver);
22
+ }
23
+ throw new Error(`${name}.${String(key)} is not implemented in this test`);
24
+ },
25
+ });
26
+ }
@@ -10,8 +10,11 @@ import {
10
10
  import {
11
11
  appConfigAtom,
12
12
  configOverridesAtom,
13
+ pageAppConfigAtom,
13
14
  userConfigAtom,
14
15
  } from "../core/config/config";
16
+ import { exportedForTesting, notebookAtom } from "../core/cells/cells";
17
+ import { SCRATCH_CELL_ID } from "../core/cells/ids";
15
18
  import { marimoVersionAtom, showCodeInRunModeAtom } from "../core/meta/state";
16
19
  import { type AppMode, viewStateAtom } from "../core/mode";
17
20
  import { codeAtom, filenameAtom } from "../core/saving/file-state";
@@ -40,7 +43,8 @@ describe("main", () => {
40
43
  store.set(filenameAtom, null);
41
44
  store.set(showCodeInRunModeAtom, false);
42
45
  store.set(marimoVersionAtom, "unknown");
43
- store.set(appConfigAtom, parseAppConfig({}));
46
+ store.set(notebookAtom, exportedForTesting.initialNotebookState());
47
+ store.set(pageAppConfigAtom, {});
44
48
  store.set(userConfigAtom, defaultUserConfig());
45
49
  store.set(configOverridesAtom, {});
46
50
  });
@@ -99,6 +103,25 @@ describe("main", () => {
99
103
  expect(store.get(viewStateAtom).mode).toBe("edit");
100
104
  });
101
105
 
106
+ it("seeds runtime from a session snapshot without dropping the scratch cell", () => {
107
+ const el = document.createElement("div");
108
+ const error = mount(
109
+ {
110
+ mode: "edit",
111
+ session: {
112
+ version: "1",
113
+ metadata: { marimo_version: "1", script_metadata_hash: null },
114
+ cells: [{ id: "cell-1", code_hash: null, console: [], outputs: [] }],
115
+ },
116
+ },
117
+ el,
118
+ );
119
+ expect(error).toBeUndefined();
120
+ const { cellRuntime } = store.get(notebookAtom);
121
+ expect(cellRuntime["cell-1" as never]).toBeDefined();
122
+ expect(cellRuntime[SCRATCH_CELL_ID]).toBeDefined();
123
+ });
124
+
102
125
  it("should mount with empty config", () => {
103
126
  const el = document.createElement("div");
104
127
  const error = mount(
@@ -37,7 +37,7 @@ import {
37
37
  const FORM_DEBOUNCE = 100; // ms;
38
38
 
39
39
  export const AppConfigForm: React.FC = () => {
40
- const [config, setConfig] = useAppConfig();
40
+ const config = useAppConfig();
41
41
  const { saveAppConfig } = useRequestClient();
42
42
  const htmlCheckboxId = useId();
43
43
  const ipynbCheckboxId = useId();
@@ -50,18 +50,14 @@ export const AppConfigForm: React.FC = () => {
50
50
  defaultValues: config,
51
51
  });
52
52
 
53
+ // The host document carries the saved config back.
53
54
  const onSubmit = async (values: AppConfig) => {
54
- await saveAppConfig({ config: values })
55
- .then(() => {
56
- setConfig(values);
57
- })
58
- .catch(() => {
59
- setConfig(values);
60
- });
55
+ await saveAppConfig({ config: values });
61
56
  };
62
57
 
63
58
  const debouncedSubmit = useDebouncedCallback((v: AppConfig) => {
64
- onSubmit(v);
59
+ // The request client reports a failed save, so nothing is left to handle.
60
+ onSubmit(v).catch(() => undefined);
65
61
  }, FORM_DEBOUNCE);
66
62
 
67
63
  // When width is changed, dispatch a resize event so widgets know to resize
@@ -94,7 +94,6 @@ describe("Cell data attributes", () => {
94
94
  code: "",
95
95
  name: cellName,
96
96
  edited: false,
97
- serializedEditorState: null,
98
97
  config: {
99
98
  disabled: false,
100
99
  hide_code: false,
@@ -41,7 +41,6 @@ function seedNotebook(
41
41
  code: "1 + 1",
42
42
  name: "test_cell",
43
43
  edited: false,
44
- serializedEditorState: null,
45
44
  config: {
46
45
  disabled: false,
47
46
  hide_code: false,
@@ -39,6 +39,7 @@ import { aiCompletionCellAtom } from "@/core/ai/state";
39
39
  import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
40
40
  import { hasOnlyOneCellAtom } from "@/core/cells/cells";
41
41
  import { useCellActions } from "@/core/cells/actions";
42
+ import { showHiddenCodeAtom } from "@/core/cells/editor";
42
43
  import { type CellId, SETUP_CELL_ID } from "@/core/cells/ids";
43
44
  import { useNotebookDocumentEdit } from "@/core/notebook/document";
44
45
  import type { CellData } from "@/core/cells/types";
@@ -54,8 +55,8 @@ import {
54
55
  appWidthAtom,
55
56
  autoInstantiateAtom,
56
57
  } from "@/core/config/config";
57
- import { kioskModeAtom } from "@/core/mode";
58
58
  import type { CellConfig, RuntimeState } from "@/core/network/types";
59
+ import { canEditAtom } from "@/core/notebook";
59
60
  import { canLinkToCell, createCellLink } from "@/utils/cell-urls";
60
61
  import { copyToClipboard } from "@/utils/copy";
61
62
  import { downloadCellOutputAsImage } from "@/utils/download";
@@ -89,9 +90,9 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
89
90
  sendToBottom,
90
91
  addColumnBreakpoint,
91
92
  clearCellOutput,
92
- markUntouched,
93
93
  createCell,
94
94
  } = useCellActions();
95
+ const showHiddenCode = useSetAtom(showHiddenCodeAtom);
95
96
  const edit = useNotebookDocumentEdit();
96
97
  const splitCell = useSplitCellCallback();
97
98
  const runCell = useRunCell(cell?.cellId);
@@ -102,10 +103,10 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
102
103
  const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
103
104
  const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
104
105
  const autoInstantiate = useAtomValue(autoInstantiateAtom);
105
- const kioskMode = useAtomValue(kioskModeAtom);
106
+ const canEdit = useAtomValue(canEditAtom);
106
107
  const appWidth = useAtomValue(appWidthAtom);
107
108
 
108
- if (!cell || kioskMode) {
109
+ if (!cell || !canEdit) {
109
110
  return [];
110
111
  }
111
112
 
@@ -229,7 +230,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
229
230
  hide_code: MARKDOWN_INITIAL_HIDE_CODE,
230
231
  }),
231
232
  );
232
- markUntouched({ cellId });
233
+ showHiddenCode({ cellId });
233
234
  }
234
235
  },
235
236
  hidden: isSetupCell,
@@ -8,7 +8,7 @@ import type { ActionButton } from "./types";
8
8
 
9
9
  export function useConfigActions() {
10
10
  const [config, setConfig] = useResolvedMarimoConfig();
11
- const [appConfig, setAppConfig] = useAppConfig();
11
+ const appConfig = useAppConfig();
12
12
  const { saveAppConfig, saveUserConfig } = useRequestClient();
13
13
 
14
14
  const handleUserConfig = async (values: Partial<UserConfig>) => {
@@ -17,10 +17,9 @@ export function useConfigActions() {
17
17
  });
18
18
  };
19
19
 
20
+ // The host document carries the saved config back.
20
21
  const handleAppConfig = async (values: AppConfig) => {
21
- await saveAppConfig({ config: values }).then(() => {
22
- setAppConfig(values);
23
- });
22
+ await saveAppConfig({ config: values });
24
23
  };
25
24
 
26
25
  const actions: ActionButton[] = [
@@ -65,13 +65,14 @@ import {
65
65
  } from "@/core/cells/cells";
66
66
  import { useCellActions } from "@/core/cells/actions";
67
67
  import { useNotebookDocumentEdit } from "@/core/notebook/document";
68
+ import { canEditAtom } from "@/core/notebook";
68
69
  import { disabledCellIds } from "@/core/cells/utils";
69
70
  import { capabilitiesAtom } from "@/core/config/capabilities";
70
71
  import { aiEnabledAtom, useResolvedMarimoConfig } from "@/core/config/config";
71
72
  import { Constants } from "@/core/constants";
72
73
  import { useLayoutActions, useLayoutState } from "@/core/layout/state";
73
74
  import { useTogglePresenting } from "@/core/layout/useTogglePresenting";
74
- import { kioskModeAtom, viewStateAtom } from "@/core/mode";
75
+ import { viewStateAtom } from "@/core/mode";
75
76
  import { useRequestClient } from "@/core/network/requests";
76
77
  import { useFilename } from "@/core/saving/filename";
77
78
  import { createShareableLink } from "@/core/wasm/share";
@@ -119,7 +120,7 @@ export function useNotebookActions({
119
120
  const { toggleApplication } = useChromeActions();
120
121
  const { selectedPanel } = useChromeState();
121
122
  const [viewState] = useAtom(viewStateAtom);
122
- const kioskMode = useAtomValue(kioskModeAtom);
123
+ const canEdit = useAtomValue(canEditAtom);
123
124
  const setCodeVisibility = useSetCodeVisibility();
124
125
  const setOutputsExpanded = useSetOutputsExpanded();
125
126
  const [resolvedConfig] = useResolvedMarimoConfig();
@@ -448,7 +449,7 @@ export function useNotebookActions({
448
449
  {
449
450
  icon: <ZapIcon size={14} strokeWidth={1.5} />,
450
451
  label: "Enable all cells",
451
- hidden: !hasDisabledCells || kioskMode,
452
+ hidden: !hasDisabledCells || !canEdit,
452
453
  handle: () => {
453
454
  const ids = disabledCellIds(getNotebook());
454
455
  edit((tx) => {
@@ -538,7 +539,7 @@ export function useNotebookActions({
538
539
  {
539
540
  icon: <Undo2Icon size={14} strokeWidth={1.5} />,
540
541
  label: undoLabel,
541
- hidden: !canUndoDeletes || kioskMode,
542
+ hidden: !canUndoDeletes || !canEdit,
542
543
  handle: () => {
543
544
  undoDeleteCell();
544
545
  },
@@ -3,7 +3,7 @@ import { historyField } from "@codemirror/commands";
3
3
  import { EditorState, type Extension, StateEffect } from "@codemirror/state";
4
4
  import { EditorView, ViewPlugin } from "@codemirror/view";
5
5
  import { useIntersectionObserver } from "@uidotdev/usehooks";
6
- import { useAtom, useAtomValue } from "jotai";
6
+ import { useAtom, useAtomValue, useSetAtom } from "jotai";
7
7
  import React, { memo, useEffect, useMemo, useRef, useState } from "react";
8
8
  import useEvent from "react-use-event-hook";
9
9
  import { Button } from "@/components/ui/button";
@@ -12,6 +12,11 @@ import { aiCompletionCellAtom } from "@/core/ai/state";
12
12
  import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
13
13
  import { getNotebook } from "@/core/cells/cells";
14
14
  import { useCellActions } from "@/core/cells/actions";
15
+ import {
16
+ showHiddenCodeAtom,
17
+ takeRestoredEditorStateAtom,
18
+ useRestoredEditorState,
19
+ } from "@/core/cells/editor";
15
20
  import { useNotebookDocumentEdit } from "@/core/notebook/document";
16
21
  import { SETUP_CELL_ID } from "@/core/cells/ids";
17
22
  import { usePendingDeleteService } from "@/core/cells/pending-delete-service";
@@ -58,7 +63,7 @@ import { LanguageToggles } from "./language-toggle";
58
63
  export interface CellEditorProps
59
64
  extends
60
65
  Pick<CellRuntimeState, "status">,
61
- Pick<CellData, "id" | "code" | "serializedEditorState" | "config"> {
66
+ Pick<CellData, "id" | "code" | "config"> {
62
67
  runCell: () => void;
63
68
  theme: Theme;
64
69
  showPlaceholder: boolean;
@@ -116,7 +121,6 @@ const CellEditorInternal = ({
116
121
  config: cellConfig,
117
122
  code,
118
123
  status,
119
- serializedEditorState,
120
124
  setEditorView,
121
125
  runCell,
122
126
  userConfig,
@@ -133,6 +137,9 @@ const CellEditorInternal = ({
133
137
  tooltipParentSelector,
134
138
  }: CellEditorProps) => {
135
139
  const [aiCompletionCell, setAiCompletionCell] = useAtom(aiCompletionCellAtom);
140
+ const serializedEditorState = useRestoredEditorState(cellId);
141
+ const takeRestoredEditorState = useSetAtom(takeRestoredEditorStateAtom);
142
+ const showHiddenCodeNow = useSetAtom(showHiddenCodeAtom);
136
143
  const deleteCell = useDeleteCellCallback();
137
144
  const { saveOrNameNotebook } = useSaveNotebook();
138
145
  const pendingDeleteService = usePendingDeleteService();
@@ -192,7 +199,7 @@ const CellEditorInternal = ({
192
199
  edit((tx) =>
193
200
  tx.updateConfig(cellId, { hide_code: MARKDOWN_INITIAL_HIDE_CODE }),
194
201
  );
195
- cellActions.markUntouched({ cellId });
202
+ showHiddenCodeNow({ cellId });
196
203
  }
197
204
  });
198
205
 
@@ -422,8 +429,8 @@ const CellEditorInternal = ({
422
429
  });
423
430
  }
424
431
  setEditorView(ev);
425
- // Clear the serialized state so that we don't re-create the editor next time
426
- cellActions.clearSerializedEditorState({ cellId });
432
+ // The restored state is used once; the next mount builds a fresh editor.
433
+ takeRestoredEditorState({ cellId });
427
434
  });
428
435
  const [isEditorMounted, setIsEditorMounted] = useState(
429
436
  serializedEditorState !== null,
@@ -14,6 +14,7 @@ import { Toolbar } from "@/components/layout/toolbar";
14
14
  import { Button } from "@/components/ui/button";
15
15
  import { Tooltip } from "@/components/ui/tooltip";
16
16
  import { useNotebook } from "@/core/cells/cells";
17
+ import { columnOf, useLayout } from "@/core/cells/layout";
17
18
  import type { CellId } from "@/core/cells/ids";
18
19
  import { getDescendantsStatus } from "@/core/cells/utils";
19
20
  import { cn } from "@/utils/cn";
@@ -64,7 +65,11 @@ export const CollapsedCellBanner: React.FC<{
64
65
  count: number;
65
66
  }> = memo(({ onClick, count, cellId }) => {
66
67
  const notebook = useNotebook();
67
- const states = getDescendantsStatus(notebook, cellId);
68
+ const layout = useLayout();
69
+ const states = getDescendantsStatus(
70
+ notebook,
71
+ columnOf(layout, cellId).folds.get(cellId) ?? [],
72
+ );
68
73
 
69
74
  return (
70
75
  <button
@@ -18,7 +18,6 @@ import {
18
18
  DialogTitle,
19
19
  } from "@/components/ui/dialog";
20
20
  import { Skeleton } from "@/components/ui/skeleton";
21
- import { notebookAtom } from "@/core/cells/cells";
22
21
  import { Constants } from "@/core/constants";
23
22
  import {
24
23
  buildBugReportUrl,
@@ -116,13 +115,8 @@ export const FeedbackModal: React.FC<{
116
115
  return requestClient.getEnvironmentInfo();
117
116
  }, [requestClient]);
118
117
 
119
- const notebook = useAtomValue(notebookAtom);
120
118
  const errors = getCellErrorEntries(store);
121
-
122
- const cells = notebook.cellIds.inOrderIds.map(
123
- (cellId) => notebook.cellData[cellId],
124
- );
125
- const codeAvailable = useNotebookCodeAvailable(cells);
119
+ const codeAvailable = useNotebookCodeAvailable();
126
120
  const filename = useAtomValue(filenameAtom);
127
121
  const connection = useAtomValue(connectionAtom);
128
122
  const notebookSourceReason = getNotebookSourceUnavailableReason({
@@ -111,7 +111,7 @@ export const Controls = ({
111
111
  className={cn(bottomRightControls)}
112
112
  >
113
113
  <HideInKioskMode>
114
- <SaveComponent kioskMode={false} />
114
+ <SaveComponent />
115
115
  </HideInKioskMode>
116
116
 
117
117
  <Tooltip content={renderShortcut("global.hideCode")}>
@@ -24,7 +24,7 @@ import {
24
24
  ERROR_MESSAGE_INSTRUCTIONS,
25
25
  } from "@/core/cells/error-report";
26
26
  import { pairConfigAtom } from "@/core/config/pair";
27
- import { notebookStateFromSession } from "@/core/cells/session";
27
+ import { readExport } from "@/core/cells/session";
28
28
  import type { MarimoError, OutputMessage } from "@/core/kernel/messages";
29
29
  import {
30
30
  type StableSessionId,
@@ -317,8 +317,8 @@ describe("SendErrorReportButton", () => {
317
317
  });
318
318
 
319
319
  it("sends the restored exception type and complete traceback", async () => {
320
- const state = notebookStateFromSession(
321
- {
320
+ const { cellRuntime } = readExport({
321
+ session: {
322
322
  version: "1",
323
323
  metadata: { marimo_version: "1", script_metadata_hash: null },
324
324
  cells: [
@@ -342,9 +342,11 @@ describe("SendErrorReportButton", () => {
342
342
  },
343
343
  ],
344
344
  },
345
- null,
346
- );
347
- const message = state?.cellRuntime[CELL_ID].output;
345
+ notebook: null,
346
+ filename: null,
347
+ appConfig: {},
348
+ });
349
+ const message = cellRuntime[CELL_ID].output;
348
350
  expect(message).toBeDefined();
349
351
  renderButton({
350
352
  content: message && <OutputRenderer cellId={CELL_ID} message={message} />,
@@ -27,6 +27,7 @@ import { aiCompletionCellAtom } from "@/core/ai/state";
27
27
  import type { CellActions } from "@/core/cells/actions";
28
28
  import { notebookAtom } from "@/core/cells/cells";
29
29
  import type { CellId } from "@/core/cells/ids";
30
+ import { layoutAtom } from "@/core/cells/layout";
30
31
  import type { DocumentModel } from "@/core/notebook/document";
31
32
  import {
32
33
  cell,
@@ -165,7 +166,6 @@ const mockCellActions = MockNotebook.cellActions({
165
166
  moveCell: vi.fn(),
166
167
  sendToTop: vi.fn(),
167
168
  sendToBottom: vi.fn(),
168
- markTouched: vi.fn(),
169
169
  deleteCells: vi.fn(),
170
170
  undoDeleteCell: vi.fn(),
171
171
  });
@@ -318,7 +318,7 @@ describe("useCellNavigationProps", () => {
318
318
  selectionActions.select({ cellId: cellId1 });
319
319
  selectionActions.extend({
320
320
  cellId: cellId2,
321
- allCellIds: store.get(notebookAtom).cellIds,
321
+ layout: store.get(layoutAtom),
322
322
  });
323
323
 
324
324
  const { result } = renderWithProvider(() =>
@@ -850,7 +850,7 @@ describe("useCellNavigationProps", () => {
850
850
  selectionActions.select({ cellId: cellId1 });
851
851
  selectionActions.extend({
852
852
  cellId: cellId2,
853
- allCellIds: store.get(notebookAtom).cellIds,
853
+ layout: store.get(layoutAtom),
854
854
  });
855
855
 
856
856
  const { result } = renderWithProvider(() =>
@@ -873,7 +873,7 @@ describe("useCellNavigationProps", () => {
873
873
  selectionActions.select({ cellId: cellId1 });
874
874
  selectionActions.extend({
875
875
  cellId: cellId3,
876
- allCellIds: store.get(notebookAtom).cellIds,
876
+ layout: store.get(layoutAtom),
877
877
  });
878
878
 
879
879
  const { result } = renderWithProvider(() =>
@@ -896,7 +896,7 @@ describe("useCellNavigationProps", () => {
896
896
  selectionActions.select({ cellId: cellId1 });
897
897
  selectionActions.extend({
898
898
  cellId: cellId3,
899
- allCellIds: store.get(notebookAtom).cellIds,
899
+ layout: store.get(layoutAtom),
900
900
  });
901
901
 
902
902
  const { result } = renderWithProvider(() =>
@@ -919,7 +919,7 @@ describe("useCellNavigationProps", () => {
919
919
  selectionActions.select({ cellId: cellId2 });
920
920
  selectionActions.extend({
921
921
  cellId: cellId3,
922
- allCellIds: store.get(notebookAtom).cellIds,
922
+ layout: store.get(layoutAtom),
923
923
  });
924
924
 
925
925
  const { result } = renderWithProvider(() =>
@@ -954,7 +954,7 @@ describe("useCellNavigationProps", () => {
954
954
  selectionActions.select({ cellId: cellId1 });
955
955
  selectionActions.extend({
956
956
  cellId: cellId2,
957
- allCellIds: store.get(notebookAtom).cellIds,
957
+ layout: store.get(layoutAtom),
958
958
  });
959
959
 
960
960
  const { result } = renderWithProvider(() =>
@@ -990,7 +990,7 @@ describe("useCellNavigationProps", () => {
990
990
  selectionActions.select({ cellId: cellId2 });
991
991
  selectionActions.extend({
992
992
  cellId: cellId3,
993
- allCellIds: store.get(notebookAtom).cellIds,
993
+ layout: store.get(layoutAtom),
994
994
  });
995
995
 
996
996
  const { result } = renderWithProvider(() =>
@@ -1023,7 +1023,7 @@ describe("useCellNavigationProps", () => {
1023
1023
  selectionActions.select({ cellId: cellId1 });
1024
1024
  selectionActions.extend({
1025
1025
  cellId: cellId2,
1026
- allCellIds: store.get(notebookAtom).cellIds,
1026
+ layout: store.get(layoutAtom),
1027
1027
  });
1028
1028
 
1029
1029
  const { result } = renderWithProvider(() =>
@@ -1056,7 +1056,7 @@ describe("useCellNavigationProps", () => {
1056
1056
  selectionActions.select({ cellId: cellId1 });
1057
1057
  selectionActions.extend({
1058
1058
  cellId: cellId2,
1059
- allCellIds: store.get(notebookAtom).cellIds,
1059
+ layout: store.get(layoutAtom),
1060
1060
  });
1061
1061
 
1062
1062
  const { result } = renderWithProvider(() =>
@@ -1084,7 +1084,7 @@ describe("useCellNavigationProps", () => {
1084
1084
  selectionActions.select({ cellId: cellId2 });
1085
1085
  selectionActions.extend({
1086
1086
  cellId: cellId3,
1087
- allCellIds: store.get(notebookAtom).cellIds,
1087
+ layout: store.get(layoutAtom),
1088
1088
  });
1089
1089
 
1090
1090
  const { result } = renderWithProvider(() =>
@@ -1138,7 +1138,7 @@ describe("useCellNavigationProps", () => {
1138
1138
  selectionActions.select({ cellId: cellId1 });
1139
1139
  selectionActions.extend({
1140
1140
  cellId: cellId2,
1141
- allCellIds: store.get(notebookAtom).cellIds,
1141
+ layout: store.get(layoutAtom),
1142
1142
  });
1143
1143
 
1144
1144
  const { result } = renderWithProvider(() =>
@@ -1166,7 +1166,7 @@ describe("useCellNavigationProps", () => {
1166
1166
  selectionActions.select({ cellId: cellId1 });
1167
1167
  selectionActions.extend({
1168
1168
  cellId: cellId2,
1169
- allCellIds: store.get(notebookAtom).cellIds,
1169
+ layout: store.get(layoutAtom),
1170
1170
  });
1171
1171
 
1172
1172
  const { result } = renderWithProvider(() =>
@@ -1228,7 +1228,7 @@ describe("useCellNavigationProps", () => {
1228
1228
  selectionActions.select({ cellId: cellId1 });
1229
1229
  selectionActions.extend({
1230
1230
  cellId: cellId2,
1231
- allCellIds: store.get(notebookAtom).cellIds,
1231
+ layout: store.get(layoutAtom),
1232
1232
  });
1233
1233
 
1234
1234
  const { result } = renderWithProvider(() =>
@@ -1622,7 +1622,7 @@ describe("useCellNavigationProps", () => {
1622
1622
  selectionActions.select({ cellId: cellId1 });
1623
1623
  selectionActions.extend({
1624
1624
  cellId: cellId2,
1625
- allCellIds: store.get(notebookAtom).cellIds,
1625
+ layout: store.get(layoutAtom),
1626
1626
  });
1627
1627
 
1628
1628
  const { result } = renderWithProvider(() =>