@marimo-team/frontend 0.19.3-dev4 → 0.19.3-dev41

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 (176) hide show
  1. package/dist/assets/{CellStatus-Ba6Af_Tb.js → CellStatus-b7Yo2X9j.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-KlUs_Sz3.js → ConnectedDataExplorerComponent-Cr6-n9Em.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-Drf1manw.js → ErrorBoundary-C7JBxSzd.js} +1 -1
  4. package/dist/assets/{ImperativeModal-q6QlC2aZ.js → ImperativeModal-DVhvP4lH.js} +1 -1
  5. package/dist/assets/{JsonOutput-4ruRfyOj.js → JsonOutput-C8Eo1zBR.js} +5 -5
  6. package/dist/assets/{LazyAnyLanguageCodeMirror-jpEDlD0M.js → LazyAnyLanguageCodeMirror-Cp2punaU.js} +2 -2
  7. package/dist/assets/{MarimoErrorOutput-DnjH3pD8.js → MarimoErrorOutput-CXBGzjO2.js} +2 -2
  8. package/dist/assets/{RenderHTML-DaJXe2U2.js → RenderHTML-SoetmcW2.js} +1 -1
  9. package/dist/assets/VisuallyHidden-B9t3FhTP.js +1 -0
  10. package/dist/assets/{add-cell-with-ai-Bsds_6SU.js → add-cell-with-ai-Csh7GHT3.js} +8 -8
  11. package/dist/assets/{add-database-form-CqIp3_WN.js → add-database-form-BBkiGMZ_.js} +2 -2
  12. package/dist/assets/agent-panel-C5C18dfm.js +287 -0
  13. package/dist/assets/{ai-model-dropdown-LK8Wr5iu.js → ai-model-dropdown-CrMTCgo7.js} +1 -1
  14. package/dist/assets/{alert-dialog-k5KxevGr.js → alert-dialog-jcHA5geR.js} +1 -1
  15. package/dist/assets/{any-language-editor-DQu1Tt2N.js → any-language-editor-Cm83E7D_.js} +1 -1
  16. package/dist/assets/{app-config-button-BaVc4Y5z.js → app-config-button-9izWmQ0X.js} +1 -1
  17. package/dist/assets/button-B8cGZzP5.js +1 -0
  18. package/dist/assets/{cache-panel-C1So4Zu3.js → cache-panel-1FqnpB9y.js} +1 -1
  19. package/dist/assets/cell-editor-gOJZyTG_.js +23 -0
  20. package/dist/assets/cell-link-BP7_Ns0N.js +1 -0
  21. package/dist/assets/{cells-KYKWFk6C.js → cells-Cv9PtwL9.js} +49 -49
  22. package/dist/assets/{chat-components-O6DUIpBx.js → chat-components-Be6BPrbT.js} +1 -1
  23. package/dist/assets/{chat-display-DD3KokYi.js → chat-display-BRKfnhbm.js} +1 -1
  24. package/dist/assets/{chat-panel-D4DIcOM1.js → chat-panel-BwP6HOKA.js} +2 -2
  25. package/dist/assets/client-CGOlSEYr.js +4 -0
  26. package/dist/assets/{column-preview-EpCGr4Xp.js → column-preview-MC6VOHbd.js} +1 -1
  27. package/dist/assets/{command-Dqe0kvHp.js → command-n_oMaKjl.js} +1 -1
  28. package/dist/assets/{command-palette-DWacsFDk.js → command-palette-DfZNcw7W.js} +1 -1
  29. package/dist/assets/common-MUZIZluQ.js +1 -0
  30. package/dist/assets/config-DFDEcYvy.js +1 -0
  31. package/dist/assets/context-DHfVoQfl.js +1 -0
  32. package/dist/assets/{copy-icon-B69c-352.js → copy-icon-jWsqdLn1.js} +1 -1
  33. package/dist/assets/{datasource-JeWYnuIr.js → datasource-CEsMStKs.js} +2 -2
  34. package/dist/assets/{dependency-graph-panel-BJibnwCO.js → dependency-graph-panel-CNTGbfLZ.js} +4 -4
  35. package/dist/assets/{dialog-DUEuLcT2.js → dialog-CF5DtF1E.js} +1 -1
  36. package/dist/assets/{dist-DOFFh6Ii.js → dist-Dg7UO_Vw.js} +1 -1
  37. package/dist/assets/{documentation-panel-B2W3q2YB.js → documentation-panel-Cb9AHO2C.js} +1 -1
  38. package/dist/assets/{download-NfnO_JCs.js → download-24bI2vH0.js} +1 -1
  39. package/dist/assets/edit-page-KoYkiOm1.js +12 -0
  40. package/dist/assets/{error-banner-DU5Qb8a8.js → error-banner-DvT0IGDZ.js} +1 -1
  41. package/dist/assets/{error-panel-Bv-7GYgJ.js → error-panel-CpYH0GfR.js} +1 -1
  42. package/dist/assets/{es-KtEicG7U.js → es-BITbuY9w.js} +1 -1
  43. package/dist/assets/{field-DDKGFzpC.js → field-Clr_fqUr.js} +1 -1
  44. package/dist/assets/{file-explorer-panel-CToUezud.js → file-explorer-panel-CdA81LHh.js} +1 -1
  45. package/dist/assets/{floating-outline-Db40vhG8.js → floating-outline-BbJ4ldyu.js} +1 -1
  46. package/dist/assets/{focus-BCdX47jS.js → focus-D1y1tXyC.js} +1 -1
  47. package/dist/assets/{form-DwtJQd_Z.js → form-BAtvsPJL.js} +2 -2
  48. package/dist/assets/{glide-data-editor-D_bRnWfy.js → glide-data-editor-Dv8ZW9dk.js} +1 -1
  49. package/dist/assets/{globals-MS86g8oR.js → globals-C6OH39EA.js} +1 -1
  50. package/dist/assets/{home-page-BfVf41OG.js → home-page-B_YprqxM.js} +2 -2
  51. package/dist/assets/house-CncUa_LL.js +1 -0
  52. package/dist/assets/index-Bq3Xa2YC.js +43 -0
  53. package/dist/assets/index-__6MNWbe.css +2 -0
  54. package/dist/assets/input-B80Yt1uu.js +1 -0
  55. package/dist/assets/{kiosk-mode-CEhvsEr0.js → kiosk-mode-DfyjlR7p.js} +1 -1
  56. package/dist/assets/{label-qwandMoh.js → label-CNZLffHW.js} +1 -1
  57. package/dist/assets/{layout-Cvaok8Kj.js → layout-9uQoV-6h.js} +4 -4
  58. package/dist/assets/links-DbDrjRnm.js +1 -0
  59. package/dist/assets/{logs-panel-J2FKnKaj.js → logs-panel-svcirwjp.js} +1 -1
  60. package/dist/assets/{markdown-renderer-BlG9DgUG.js → markdown-renderer-DlVqlHOL.js} +2 -2
  61. package/dist/assets/mode-PeuS_Lp-.js +1 -0
  62. package/dist/assets/{multi-map-fjX9ImVF.js → multi-map-CQd4MZr5.js} +1 -1
  63. package/dist/assets/name-cell-input-YMoA0SQj.js +1 -0
  64. package/dist/assets/{outline-panel-Doj3GJrQ.js → outline-panel-RKJ5Mqrt.js} +1 -1
  65. package/dist/assets/{packages-panel-nqWXQzKf.js → packages-panel-BuiAGEBw.js} +1 -1
  66. package/dist/assets/panels-BKsZUDjc.js +1 -0
  67. package/dist/assets/{process-output-DiSW8Nbo.js → process-output-KJWsSvCT.js} +1 -1
  68. package/dist/assets/{readonly-python-code-CKY5LsMp.js → readonly-python-code-HPlG_YPX.js} +1 -1
  69. package/dist/assets/run-page-CBDzVDX3.js +1 -0
  70. package/dist/assets/scratchpad-panel-BcE_ovEU.js +1 -0
  71. package/dist/assets/{secrets-panel-CDWmmmBS.js → secrets-panel-BMY6PPth.js} +1 -1
  72. package/dist/assets/{select-D0g5GnIs.js → select-D9lTzMzP.js} +1 -1
  73. package/dist/assets/{session-panel-CGFRSBw9.js → session-panel-Cv14Ehfm.js} +1 -1
  74. package/dist/assets/{slides-component-MkPkpql1.js → slides-component-Dp0Yv5b0.js} +1 -1
  75. package/dist/assets/{snippets-panel-ClHeSpc5.js → snippets-panel-OAdQXQ93.js} +1 -1
  76. package/dist/assets/state-DYG6kYly.js +1 -0
  77. package/dist/assets/state-xh6GqNrp.js +1 -0
  78. package/dist/assets/{switch-BmbGJWHc.js → switch-DPeh0R76.js} +1 -1
  79. package/dist/assets/{terminal-BvgBa6Ri.js → terminal-BbAhzgnR.js} +1 -1
  80. package/dist/assets/{textarea-WklymBeK.js → textarea-wbzgrXvB.js} +1 -1
  81. package/dist/assets/{tracing-D0WYhZdr.js → tracing-Bh3EJxAS.js} +1 -1
  82. package/dist/assets/{tracing-panel-CNxN58z7.js → tracing-panel-BzSQ7qvB.js} +2 -2
  83. package/dist/assets/{types-BrgXpvGt.js → types-B8Qb1FfB.js} +1 -1
  84. package/dist/assets/{useAddCell-a9qZ0_KE.js → useAddCell-DBGvrN8K.js} +1 -1
  85. package/dist/assets/{useBoolean-5kuXz69O.js → useBoolean-CyOFPk5r.js} +1 -1
  86. package/dist/assets/{useCellActionButton-9W_R41MM.js → useCellActionButton-BlS_HKk-.js} +1 -1
  87. package/dist/assets/{useDateFormatter-CV0QXb5P.js → useDateFormatter-DsANziQR.js} +1 -1
  88. package/dist/assets/useDeleteCell-BvQIJfpI.js +1 -0
  89. package/dist/assets/{useDependencyPanelTab-0reaqvvh.js → useDependencyPanelTab-BqEhbPr2.js} +1 -1
  90. package/dist/assets/useInterval-BGPIviJp.js +1 -0
  91. package/dist/assets/useNotebookActions-D1Woz3AV.js +1 -0
  92. package/dist/assets/{useNumberFormatter-D8ks3oPN.js → useNumberFormatter-FoXhpyAb.js} +1 -1
  93. package/dist/assets/usePress-DTwIUo40.js +7 -0
  94. package/dist/assets/useRunCells-B9Xr4tcH.js +1 -0
  95. package/dist/assets/useSplitCell-7xBW3b8-.js +1 -0
  96. package/dist/assets/utilities.esm-xahhGpny.js +3 -0
  97. package/dist/assets/{vega-component-DpAAiTdH.js → vega-component-dUiiVmIx.js} +1 -1
  98. package/dist/assets/{write-secret-modal-CLm48gMe.js → write-secret-modal-hOetwavI.js} +1 -1
  99. package/dist/index.html +54 -54
  100. package/package.json +5 -5
  101. package/src/__mocks__/requests.ts +1 -0
  102. package/src/__tests__/mount.test.ts +128 -0
  103. package/src/components/app-config/__tests__/get-dirty-values.test.ts +1 -1
  104. package/src/components/app-config/user-config-form.tsx +1 -1
  105. package/src/components/chat/acp/agent-panel.tsx +56 -43
  106. package/src/components/data-table/column-header.tsx +1 -1
  107. package/src/components/editor/KernelStartupErrorModal.tsx +101 -0
  108. package/src/components/editor/actions/name-cell-input.tsx +10 -4
  109. package/src/components/editor/alerts/connecting-alert.tsx +33 -6
  110. package/src/components/editor/chrome/types.ts +2 -4
  111. package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -58
  112. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +150 -96
  113. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +27 -0
  114. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +6 -0
  115. package/src/components/utils/lazy-mount.tsx +29 -8
  116. package/src/core/MarimoApp.tsx +2 -0
  117. package/src/core/cells/cells.ts +2 -0
  118. package/src/core/cells/scrollCellIntoView.ts +3 -2
  119. package/src/core/codemirror/cm.ts +2 -0
  120. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +123 -0
  121. package/src/core/codemirror/lsp/notebook-lsp.ts +44 -4
  122. package/src/core/codemirror/misc/__tests__/string-braces.test.ts +200 -0
  123. package/src/core/codemirror/misc/string-braces.ts +37 -0
  124. package/src/core/errors/state.ts +7 -1
  125. package/src/core/export/__tests__/hooks.test.ts +504 -0
  126. package/src/core/export/hooks.ts +93 -4
  127. package/src/core/islands/bridge.ts +1 -0
  128. package/src/core/islands/main.ts +2 -0
  129. package/src/core/kernel/__tests__/handlers.test.ts +2 -2
  130. package/src/core/kernel/state.ts +1 -0
  131. package/src/core/network/__tests__/requests-lazy.test.ts +1 -1
  132. package/src/core/network/__tests__/requests-network.test.ts +0 -18
  133. package/src/core/network/requests-lazy.ts +3 -2
  134. package/src/core/network/requests-network.ts +10 -7
  135. package/src/core/network/requests-static.ts +1 -0
  136. package/src/core/network/requests-toasting.tsx +1 -0
  137. package/src/core/network/types.ts +2 -0
  138. package/src/core/wasm/bridge.ts +1 -0
  139. package/src/core/websocket/types.ts +1 -0
  140. package/src/core/websocket/useMarimoKernelConnection.tsx +18 -1
  141. package/src/css/globals.css +2 -0
  142. package/src/hooks/__tests__/useInterval.test.tsx +104 -0
  143. package/src/hooks/useInterval.ts +32 -6
  144. package/src/mount.tsx +6 -0
  145. package/src/plugins/impl/chat/chat-ui.tsx +16 -4
  146. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +3 -1
  147. package/src/utils/events.ts +1 -0
  148. package/dist/assets/VisuallyHidden-BodIky8L.js +0 -1
  149. package/dist/assets/agent-panel-CaAPVPdJ.js +0 -287
  150. package/dist/assets/button-DuYGqRtX.js +0 -1
  151. package/dist/assets/cell-editor-OFm-OSAP.js +0 -23
  152. package/dist/assets/cell-link-CfLJRl3p.js +0 -1
  153. package/dist/assets/client-Cha_JfGC.js +0 -4
  154. package/dist/assets/common-A6YWtmpq.js +0 -1
  155. package/dist/assets/config-babG4OBR.js +0 -1
  156. package/dist/assets/context-BAYdLMF_.js +0 -1
  157. package/dist/assets/edit-page-nuU4FVXi.js +0 -12
  158. package/dist/assets/globe-CY9im410.js +0 -1
  159. package/dist/assets/index-BI88xbv4.js +0 -43
  160. package/dist/assets/index-Chgc_07S.css +0 -2
  161. package/dist/assets/input-CaEtLL8p.js +0 -1
  162. package/dist/assets/links-ENMiP32L.js +0 -1
  163. package/dist/assets/mode-CK5Oq-Jz.js +0 -1
  164. package/dist/assets/name-cell-input-D7axzd6k.js +0 -1
  165. package/dist/assets/panels-CdYbZBqo.js +0 -1
  166. package/dist/assets/run-page-GP8eGE39.js +0 -1
  167. package/dist/assets/scratchpad-panel-B1p8zqAE.js +0 -1
  168. package/dist/assets/state-BBgXjqJI.js +0 -1
  169. package/dist/assets/state-CP7_TGWl.js +0 -1
  170. package/dist/assets/useDeleteCell-5kJUaejE.js +0 -1
  171. package/dist/assets/useInterval-DpipYmgs.js +0 -1
  172. package/dist/assets/useNotebookActions-o341ZCMJ.js +0 -1
  173. package/dist/assets/usePress-C2LPFxyv.js +0 -7
  174. package/dist/assets/useRunCells-wXhl9zOP.js +0 -1
  175. package/dist/assets/useSplitCell-mmm5jxn2.js +0 -1
  176. package/dist/assets/utilities.esm-Ckt5kMF-.js +0 -3
@@ -2,6 +2,8 @@
2
2
 
3
3
  import {
4
4
  ChevronDownIcon,
5
+ ExternalLinkIcon,
6
+ InfoIcon,
5
7
  PowerOffIcon,
6
8
  ZapIcon,
7
9
  ZapOffIcon,
@@ -15,7 +17,9 @@ import {
15
17
  DropdownMenuSeparator,
16
18
  DropdownMenuTrigger,
17
19
  } from "@/components/ui/dropdown-menu";
20
+ import { ExternalLink } from "@/components/ui/links";
18
21
  import { Switch } from "@/components/ui/switch";
22
+ import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
19
23
  import { useResolvedMarimoConfig } from "@/core/config/config";
20
24
  import { useRequestClient } from "@/core/network/requests";
21
25
  import { isWasm } from "@/core/wasm/utils";
@@ -99,117 +103,167 @@ export const RuntimeSettings: React.FC<RuntimeSettingsProps> = ({
99
103
  </FooterItem>
100
104
  </DropdownMenuTrigger>
101
105
  <DropdownMenuContent align="start" className="w-64">
102
- <DropdownMenuLabel>Runtime reactivity</DropdownMenuLabel>
106
+ <DropdownMenuLabel>
107
+ <div className="flex items-center justify-between w-full">
108
+ <span>Runtime reactivity</span>
109
+ <ExternalLink href="https://links.marimo.app/runtime-configuration">
110
+ <span className="text-xs font-normal flex items-center gap-1">
111
+ Docs
112
+ <ExternalLinkIcon className="w-3 h-3" />
113
+ </span>
114
+ </ExternalLink>
115
+ </div>
116
+ </DropdownMenuLabel>
103
117
  <DropdownMenuSeparator />
104
118
 
105
- {/* On startup toggle */}
106
- <DisableIfOverridden name="runtime.auto_instantiate">
107
- <div className="flex items-center justify-between px-2 py-2">
108
- <div className="flex items-center space-x-2">
109
- {config.runtime.auto_instantiate ? (
110
- <ZapIcon size={14} className="text-amber-500" />
111
- ) : (
112
- <ZapOffIcon size={14} className="text-muted-foreground" />
113
- )}
114
- <div>
115
- <div className="text-sm font-medium">On startup</div>
116
- <div className="text-xs text-muted-foreground">
117
- {config.runtime.auto_instantiate ? "autorun" : "lazy"}
119
+ <TooltipProvider>
120
+ {/* On startup toggle */}
121
+ <DisableIfOverridden name="runtime.auto_instantiate">
122
+ <div className="flex items-center justify-between px-2 py-2">
123
+ <div className="flex items-center space-x-2">
124
+ {config.runtime.auto_instantiate ? (
125
+ <ZapIcon size={14} className="text-amber-500" />
126
+ ) : (
127
+ <ZapOffIcon size={14} className="text-muted-foreground" />
128
+ )}
129
+ <div>
130
+ <div className="text-sm font-medium flex items-center gap-1">
131
+ On startup
132
+ <Tooltip
133
+ content={
134
+ <div className="max-w-[200px]">
135
+ Whether to automatically run the notebook on startup
136
+ </div>
137
+ }
138
+ >
139
+ <InfoIcon className="w-3 h-3" />
140
+ </Tooltip>
141
+ </div>
142
+ <div className="text-xs text-muted-foreground">
143
+ {config.runtime.auto_instantiate ? "autorun" : "lazy"}
144
+ </div>
118
145
  </div>
119
146
  </div>
147
+ <Switch
148
+ checked={config.runtime.auto_instantiate}
149
+ onCheckedChange={handleStartupToggle}
150
+ size="sm"
151
+ />
120
152
  </div>
121
- <Switch
122
- checked={config.runtime.auto_instantiate}
123
- onCheckedChange={handleStartupToggle}
124
- size="sm"
125
- />
126
- </div>
127
- </DisableIfOverridden>
153
+ </DisableIfOverridden>
128
154
 
129
- <DropdownMenuSeparator />
155
+ <DropdownMenuSeparator />
130
156
 
131
- {/* On cell change toggle */}
132
- <DisableIfOverridden name="runtime.on_cell_change">
133
- <div className="flex items-center justify-between px-2 py-2">
134
- <div className="flex items-center space-x-2">
135
- {config.runtime.on_cell_change === "autorun" ? (
136
- <ZapIcon size={14} className="text-amber-500" />
137
- ) : (
138
- <ZapOffIcon size={14} className="text-muted-foreground" />
139
- )}
140
- <div>
141
- <div className="text-sm font-medium">On cell change</div>
142
- <div className="text-xs text-muted-foreground">
143
- {config.runtime.on_cell_change}
157
+ {/* On cell change toggle */}
158
+ <DisableIfOverridden name="runtime.on_cell_change">
159
+ <div className="flex items-center justify-between px-2 py-2">
160
+ <div className="flex items-center space-x-2">
161
+ {config.runtime.on_cell_change === "autorun" ? (
162
+ <ZapIcon size={14} className="text-amber-500" />
163
+ ) : (
164
+ <ZapOffIcon size={14} className="text-muted-foreground" />
165
+ )}
166
+ <div>
167
+ <div className="text-sm font-medium flex items-center gap-1">
168
+ On cell change
169
+ <Tooltip
170
+ content={
171
+ <div className="max-w-[300px]">
172
+ Whether to automatically run dependent cells after
173
+ running a cell
174
+ </div>
175
+ }
176
+ >
177
+ <InfoIcon className="w-3 h-3" />
178
+ </Tooltip>
179
+ </div>
180
+ <div className="text-xs text-muted-foreground">
181
+ {config.runtime.on_cell_change}
182
+ </div>
144
183
  </div>
145
184
  </div>
185
+ <Switch
186
+ checked={config.runtime.on_cell_change === "autorun"}
187
+ onCheckedChange={handleCellChangeToggle}
188
+ size="sm"
189
+ />
146
190
  </div>
147
- <Switch
148
- checked={config.runtime.on_cell_change === "autorun"}
149
- onCheckedChange={handleCellChangeToggle}
150
- size="sm"
151
- />
152
- </div>
153
- </DisableIfOverridden>
191
+ </DisableIfOverridden>
154
192
 
155
- {!isWasm() && (
156
- <>
157
- <DropdownMenuSeparator />
193
+ {!isWasm() && (
194
+ <>
195
+ <DropdownMenuSeparator />
158
196
 
159
- {/* On module change dropdown */}
160
- <DisableIfOverridden name="runtime.auto_reload">
161
- <div className="px-2 py-1">
162
- <div className="flex items-center space-x-2 mb-2">
163
- {config.runtime.auto_reload === "off" && (
164
- <PowerOffIcon size={14} className="text-muted-foreground" />
165
- )}
166
- {config.runtime.auto_reload === "lazy" && (
167
- <ZapOffIcon size={14} className="text-muted-foreground" />
168
- )}
169
- {config.runtime.auto_reload === "autorun" && (
170
- <ZapIcon size={14} className="text-amber-500" />
171
- )}
172
- <div>
173
- <div className="text-sm font-medium">On module change</div>
174
- <div className="text-xs text-muted-foreground">
175
- {config.runtime.auto_reload}
197
+ {/* On module change dropdown */}
198
+ <DisableIfOverridden name="runtime.auto_reload">
199
+ <div className="px-2 py-1">
200
+ <div className="flex items-center space-x-2 mb-2">
201
+ {config.runtime.auto_reload === "off" && (
202
+ <PowerOffIcon
203
+ size={14}
204
+ className="text-muted-foreground"
205
+ />
206
+ )}
207
+ {config.runtime.auto_reload === "lazy" && (
208
+ <ZapOffIcon size={14} className="text-muted-foreground" />
209
+ )}
210
+ {config.runtime.auto_reload === "autorun" && (
211
+ <ZapIcon size={14} className="text-amber-500" />
212
+ )}
213
+ <div>
214
+ <div className="text-sm font-medium flex items-center gap-1">
215
+ On module change
216
+ <Tooltip
217
+ content={
218
+ <div className="max-w-[300px]">
219
+ Whether to run affected cells, mark them as stale,
220
+ or do nothing when an external module is updated
221
+ </div>
222
+ }
223
+ >
224
+ <InfoIcon className="w-3 h-3" />
225
+ </Tooltip>
226
+ </div>
227
+ <div className="text-xs text-muted-foreground">
228
+ {config.runtime.auto_reload}
229
+ </div>
176
230
  </div>
177
231
  </div>
232
+ <div className="space-y-1">
233
+ {["off", "lazy", "autorun"].map((option) => (
234
+ <button
235
+ key={option}
236
+ onClick={() =>
237
+ handleModuleReloadChange(
238
+ option as "off" | "lazy" | "autorun",
239
+ )
240
+ }
241
+ className={cn(
242
+ "w-full flex items-center px-2 py-1 text-sm rounded hover:bg-accent",
243
+ option === config.runtime.auto_reload && "bg-accent",
244
+ )}
245
+ >
246
+ {option === "off" && (
247
+ <PowerOffIcon size={12} className="mr-2" />
248
+ )}
249
+ {option === "lazy" && (
250
+ <ZapOffIcon size={12} className="mr-2" />
251
+ )}
252
+ {option === "autorun" && (
253
+ <ZapIcon size={12} className="mr-2" />
254
+ )}
255
+ <span className="capitalize">{option}</span>
256
+ {option === config.runtime.auto_reload && (
257
+ <span className="ml-auto">✓</span>
258
+ )}
259
+ </button>
260
+ ))}
261
+ </div>
178
262
  </div>
179
- <div className="space-y-1">
180
- {["off", "lazy", "autorun"].map((option) => (
181
- <button
182
- key={option}
183
- onClick={() =>
184
- handleModuleReloadChange(
185
- option as "off" | "lazy" | "autorun",
186
- )
187
- }
188
- className={cn(
189
- "w-full flex items-center px-2 py-1 text-sm rounded hover:bg-accent",
190
- option === config.runtime.auto_reload && "bg-accent",
191
- )}
192
- >
193
- {option === "off" && (
194
- <PowerOffIcon size={12} className="mr-2" />
195
- )}
196
- {option === "lazy" && (
197
- <ZapOffIcon size={12} className="mr-2" />
198
- )}
199
- {option === "autorun" && (
200
- <ZapIcon size={12} className="mr-2" />
201
- )}
202
- <span className="capitalize">{option}</span>
203
- {option === config.runtime.auto_reload && (
204
- <span className="ml-auto">✓</span>
205
- )}
206
- </button>
207
- ))}
208
- </div>
209
- </div>
210
- </DisableIfOverridden>
211
- </>
212
- )}
263
+ </DisableIfOverridden>
264
+ </>
265
+ )}
266
+ </TooltipProvider>
213
267
  </DropdownMenuContent>
214
268
  </DropdownMenu>
215
269
  );
@@ -12,6 +12,8 @@ describe("useFocusFirstEditor", () => {
12
12
  cb(0);
13
13
  return 0;
14
14
  });
15
+ // Mock document.hasFocus() to return true so focus logic runs
16
+ vi.spyOn(document, "hasFocus").mockReturnValue(true);
15
17
  });
16
18
 
17
19
  afterEach(() => {
@@ -120,4 +122,29 @@ describe("useFocusFirstEditor", () => {
120
122
  writable: true,
121
123
  });
122
124
  });
125
+
126
+ it("should not focus when document does not have focus", () => {
127
+ // Mock document.hasFocus() to return false (e.g., when embedded in iframe)
128
+ vi.spyOn(document, "hasFocus").mockReturnValue(false);
129
+
130
+ const mockEditor = { focus: vi.fn() };
131
+ vi.spyOn(cellsModule, "getNotebook").mockReturnValue({
132
+ cellIds: { iterateTopLevelIds: ["cell-1"] },
133
+ cellData: {
134
+ "cell-1": { config: { hide_code: false } },
135
+ },
136
+ cellHandles: {
137
+ "cell-1": { current: { editorView: mockEditor } },
138
+ },
139
+ } as unknown as cellsModule.NotebookState);
140
+
141
+ renderHook(() => useFocusFirstEditor());
142
+
143
+ act(() => {
144
+ vi.advanceTimersByTime(100);
145
+ });
146
+
147
+ // Focus should NOT be called when document doesn't have focus
148
+ expect(mockEditor.focus).not.toHaveBeenCalled();
149
+ });
123
150
  });
@@ -19,6 +19,12 @@ export function useFocusFirstEditor() {
19
19
  const timeout = setTimeout(() => {
20
20
  // Let the DOM render
21
21
  requestAnimationFrame(() => {
22
+ // Skip auto-focus if the document doesn't have focus to avoid
23
+ // stealing focus from outside (e.g., when embedded in an iframe)
24
+ if (!document.hasFocus()) {
25
+ return;
26
+ }
27
+
22
28
  // Check if the URL contains a scrollTo parameter
23
29
  const hash = window.location.hash;
24
30
  const cellName = extractCellNameFromHash(hash);
@@ -1,5 +1,9 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import React, { type PropsWithChildren } from "react";
2
+ import React, {
3
+ Activity,
4
+ type ActivityProps,
5
+ type PropsWithChildren,
6
+ } from "react";
3
7
 
4
8
  interface Props {
5
9
  isOpen: boolean;
@@ -12,13 +16,30 @@ export const LazyMount: React.FC<PropsWithChildren<Props>> = ({
12
16
  isOpen,
13
17
  children,
14
18
  }) => {
15
- const [isMounted, setIsMounted] = React.useState(false);
19
+ const [hasMountedBefore, setHasMountedBefore] = React.useState(false);
16
20
 
17
- React.useEffect(() => {
18
- if (isOpen && !isMounted) {
19
- setIsMounted(true);
20
- }
21
- }, [isOpen, isMounted]);
21
+ if (isOpen && !hasMountedBefore) {
22
+ setHasMountedBefore(true);
23
+ }
22
24
 
23
- return isMounted ? children : null;
25
+ return hasMountedBefore || isOpen ? children : null;
26
+ };
27
+
28
+ /**
29
+ * Wraps a component in an Activity component. It is not mounted until it is open for the first time.
30
+ */
31
+ export const LazyActivity: React.FC<PropsWithChildren<ActivityProps>> = (
32
+ props,
33
+ ) => {
34
+ const [hasMountedBefore, setHasMountedBefore] = React.useState(false);
35
+
36
+ if (props.mode === "visible" && !hasMountedBefore) {
37
+ setHasMountedBefore(true);
38
+ }
39
+
40
+ if (hasMountedBefore) {
41
+ return <Activity {...props} />;
42
+ }
43
+
44
+ return null;
24
45
  };
@@ -12,6 +12,7 @@ import { getInitialAppMode } from "@/core/mode";
12
12
  import { CssVariables } from "@/theme/ThemeProvider";
13
13
  import { reactLazyWithPreload } from "@/utils/lazy";
14
14
  import { ErrorBoundary } from "../components/editor/boundary/ErrorBoundary";
15
+ import { KernelStartupErrorModal } from "../components/editor/KernelStartupErrorModal";
15
16
  import { ModalProvider } from "../components/modal/ImperativeModal";
16
17
  import { Toaster } from "../components/ui/toaster";
17
18
  import { TooltipProvider } from "../components/ui/tooltip";
@@ -91,6 +92,7 @@ const Providers = memo(({ children }: PropsWithChildren) => {
91
92
  {children}
92
93
  <Toaster />
93
94
  <TailwindIndicator />
95
+ <KernelStartupErrorModal />
94
96
  </ModalProvider>
95
97
  </LocaleProvider>
96
98
  </SlotzProvider>
@@ -1594,6 +1594,8 @@ const cellDataAtoms = splitAtom(
1594
1594
  );
1595
1595
  export const useCellDataAtoms = () => useAtom(cellDataAtoms);
1596
1596
 
1597
+ export const cellsRuntimeAtom = atom((get) => get(notebookAtom).cellRuntime);
1598
+
1597
1599
  export const notebookIsRunningAtom = atom((get) =>
1598
1600
  notebookIsRunning(get(notebookAtom)),
1599
1601
  );
@@ -53,8 +53,9 @@ export function focusAndScrollCellIntoView({
53
53
  Logger.warn("scrollCellIntoView: editor not found", cellId);
54
54
  return;
55
55
  }
56
- // If already focused, do nothing.
57
- if (editor.hasFocus) {
56
+ // Skip auto-focus if already focused, or if the document doesn't have
57
+ // focus to avoid stealing focus from outside (e.g., when embedded in an iframe)
58
+ if (editor.hasFocus || !document.hasFocus()) {
58
59
  return;
59
60
  }
60
61
 
@@ -64,6 +64,7 @@ import { getCurrentLanguageAdapter } from "./language/commands";
64
64
  import { adaptiveLanguageConfiguration } from "./language/extension";
65
65
  import { dndBundle } from "./misc/dnd";
66
66
  import { pasteBundle } from "./misc/paste";
67
+ import { stringsAutoCloseBraces } from "./misc/string-braces";
67
68
  import { reactiveReferencesBundle } from "./reactive-references/extension";
68
69
  import { darkTheme } from "./theme/dark";
69
70
  import { lightTheme } from "./theme/light";
@@ -203,6 +204,7 @@ export const basicBundle = (opts: CodeMirrorSetupOpts): Extension[] => {
203
204
  hintTooltip(lspConfig),
204
205
  copilotBundle(completionConfig),
205
206
  foldGutter(),
207
+ stringsAutoCloseBraces(),
206
208
  closeBrackets(),
207
209
  completionKeymap(),
208
210
  // to avoid clash with charDeleteBackward keymap
@@ -756,6 +756,129 @@ describe("NotebookLanguageServerClient", () => {
756
756
  // Verify that the import cell was not changed
757
757
  expect(mockView1.state.doc.toString()).toBe("import marimo as mo");
758
758
  });
759
+
760
+ it("should only rename private variables in the current cell (issue #7810)", async () => {
761
+ const props = {
762
+ workspaceFolders: null,
763
+ capabilities: {
764
+ textDocument: {
765
+ rename: {
766
+ prepareSupport: true,
767
+ },
768
+ },
769
+ },
770
+ languageId: "python",
771
+ transport: {
772
+ sendData: vi.fn(),
773
+ subscribe: vi.fn(),
774
+ connect: vi.fn(),
775
+ transportRequestManager: {
776
+ send: vi.fn(),
777
+ },
778
+ } as any,
779
+ };
780
+
781
+ // Setup editor views - both cells have a private variable _x
782
+ const cell1Code = "_x = 1\nprint(_x)";
783
+ const cell2Code = "_x = 2\nprint(_x)";
784
+
785
+ const mockView1 = new EditorView({
786
+ doc: cell1Code,
787
+ extensions: [
788
+ languageAdapterState.init(() => new PythonLanguageAdapter()),
789
+ languageMetadataField.init(() => ({})),
790
+ languageServerWithClient({
791
+ client: mockClient as unknown as LanguageServerClient,
792
+ documentUri: CellDocumentUri.of(Cells.cell1),
793
+ ...props,
794
+ }),
795
+ ],
796
+ });
797
+
798
+ const mockView2 = new EditorView({
799
+ doc: cell2Code,
800
+ extensions: [
801
+ languageAdapterState.init(() => new PythonLanguageAdapter()),
802
+ languageMetadataField.init(() => ({})),
803
+ languageServerWithClient({
804
+ client: mockClient as unknown as LanguageServerClient,
805
+ documentUri: CellDocumentUri.of(Cells.cell2),
806
+ ...props,
807
+ }),
808
+ ],
809
+ });
810
+
811
+ (notebookClient as any).getNotebookEditors = () => ({
812
+ [Cells.cell1]: mockView1,
813
+ [Cells.cell2]: mockView2,
814
+ });
815
+
816
+ // Update the mock to return the correct codes
817
+ vi.spyOn(store, "get").mockImplementation((atom) => {
818
+ if (atom === topologicalCodesAtom) {
819
+ return {
820
+ cellIds: [Cells.cell1, Cells.cell2],
821
+ codes: {
822
+ [Cells.cell1]: cell1Code,
823
+ [Cells.cell2]: cell2Code,
824
+ },
825
+ };
826
+ }
827
+ return undefined;
828
+ });
829
+
830
+ // Setup rename params - renaming _x in cell1
831
+ const renameParams: LSP.RenameParams = {
832
+ textDocument: { uri: CellDocumentUri.of(Cells.cell1) },
833
+ position: { line: 0, character: 0 }, // position of '_x'
834
+ newName: "_y",
835
+ };
836
+
837
+ // Open a document first to set up the lens
838
+ await notebookClient.textDocumentDidOpen({
839
+ textDocument: {
840
+ uri: CellDocumentUri.of(Cells.cell1),
841
+ languageId: "python",
842
+ version: 1,
843
+ text: cell1Code,
844
+ },
845
+ });
846
+
847
+ // Mock the response from the language server
848
+ // The LSP server would rename _x in BOTH cells (since it sees the merged doc)
849
+ const mockRenameResponse: LSP.WorkspaceEdit = {
850
+ documentChanges: [
851
+ {
852
+ textDocument: {
853
+ uri: "file:///__marimo_notebook__.py",
854
+ version: 1,
855
+ },
856
+ edits: [
857
+ {
858
+ range: {
859
+ start: { line: 0, character: 0 },
860
+ end: { line: 3, character: 10 },
861
+ },
862
+ // LSP renames _x to _y in both cells
863
+ newText: "_y = 1\nprint(_y)\n_y = 2\nprint(_y)",
864
+ },
865
+ ],
866
+ },
867
+ ],
868
+ };
869
+
870
+ mockClient.textDocumentRename = vi
871
+ .fn()
872
+ .mockResolvedValue(mockRenameResponse);
873
+
874
+ // Call rename
875
+ await notebookClient.textDocumentRename(renameParams);
876
+
877
+ // The fix: only cell1 should be renamed, cell2 should remain unchanged
878
+ // because private variables are cell-local in marimo
879
+ expect(mockView1.state.doc.toString()).toBe("_y = 1\nprint(_y)");
880
+ expect(mockView2.state.doc.toString()).toBe("_x = 2\nprint(_x)");
881
+ });
759
882
  });
760
883
 
761
884
  describe("diagnostics handling", () => {
@@ -9,6 +9,7 @@ import { invariant } from "@/utils/invariant";
9
9
  import { Logger } from "@/utils/Logger";
10
10
  import { LRUCache } from "@/utils/lru";
11
11
  import { Objects } from "@/utils/objects";
12
+ import { getPositionAtWordBounds } from "../completion/hints";
12
13
  import { topologicalCodesAtom } from "../copilot/getCodes";
13
14
  import {
14
15
  getEditorCodeAsPython,
@@ -22,6 +23,14 @@ import {
22
23
  } from "./types";
23
24
  import { getLSPDocument } from "./utils";
24
25
 
26
+ /**
27
+ * Check if a variable name is private (starts with underscore but not dunder).
28
+ * Private variables in marimo are cell-local and should not be renamed across cells.
29
+ */
30
+ function isPrivateVariable(name: string): boolean {
31
+ return name.startsWith("_") && !name.startsWith("__");
32
+ }
33
+
25
34
  class Snapshotter {
26
35
  private documentVersion = 0;
27
36
  private readonly getNotebookCode: () => {
@@ -433,15 +442,46 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
433
442
 
434
443
  // Update the code in the plugins manually
435
444
  const editors = this.getNotebookEditors();
436
- for (const [cellId, ev] of Objects.entries(editors)) {
437
- const newCode = editsToNewCode.get(cellId);
445
+
446
+ // Check if this is a private variable rename (should only affect current cell)
447
+ // Private variables in marimo are cell-local and should not be renamed across cells
448
+ const originEditor = editors[cellId];
449
+ let isPrivateRename = false;
450
+ if (originEditor) {
451
+ // Convert LSP position (line, character) to CodeMirror position
452
+ const line = originEditor.state.doc.line(params.position.line + 1);
453
+ const cmPosition = line.from + params.position.character;
454
+ const { startToken, endToken } = getPositionAtWordBounds(
455
+ originEditor.state.doc,
456
+ cmPosition,
457
+ );
458
+ const originalName = originEditor.state.doc.sliceString(
459
+ startToken,
460
+ endToken,
461
+ );
462
+ isPrivateRename = isPrivateVariable(originalName);
463
+ if (isPrivateRename) {
464
+ Logger.debug(
465
+ "[lsp] Private variable rename detected, limiting to current cell",
466
+ originalName,
467
+ );
468
+ }
469
+ }
470
+
471
+ for (const [currentCellId, ev] of Objects.entries(editors)) {
472
+ // For private variable renames, only update the originating cell
473
+ if (isPrivateRename && currentCellId !== cellId) {
474
+ continue;
475
+ }
476
+
477
+ const newCode = editsToNewCode.get(currentCellId);
438
478
  if (newCode == null) {
439
- Logger.warn("No new code for cell", cellId);
479
+ Logger.warn("No new code for cell", currentCellId);
440
480
  continue;
441
481
  }
442
482
 
443
483
  if (!ev) {
444
- Logger.warn("No view for plugin", cellId);
484
+ Logger.warn("No view for plugin", currentCellId);
445
485
  continue;
446
486
  }
447
487