@marimo-team/frontend 0.25.2-dev66 → 0.25.2-dev89

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 (153) hide show
  1. package/dist/assets/{CellStatus-CRFBVGjI.js → CellStatus-Bj7PvY_u.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-BKmN7zye.js → ErrorBoundary-CG6gtgym.js} +2 -2
  3. package/dist/assets/{JsonOutput-nqqzL-n0.js → JsonOutput-NVH7GWyC.js} +1 -1
  4. package/dist/assets/{MarimoErrorOutput-DCu6fm2y.js → MarimoErrorOutput-z5iFryzf.js} +1 -1
  5. package/dist/assets/{RenderHTML-DRMkieKn.js → RenderHTML-C7QcdAgB.js} +1 -1
  6. package/dist/assets/{RunButton-CPZWjCL9.js → RunButton-A__3wcpF.js} +1 -1
  7. package/dist/assets/{add-cell-with-ai-D20NyZO4.js → add-cell-with-ai-Wyt6GBhk.js} +1 -1
  8. package/dist/assets/{add-connection-dialog-pQcdKIlO.js → add-connection-dialog-bl8dqCVs.js} +1 -1
  9. package/dist/assets/{agent-panel-tNErBgmq.js → agent-panel-BcoO8S6Q.js} +1 -1
  10. package/dist/assets/{ai-model-dropdown-BIeRPKed.js → ai-model-dropdown-CvJsRj9y.js} +1 -1
  11. package/dist/assets/{api-DYL9A86_.js → api-DVLtXyp7.js} +1 -1
  12. package/dist/assets/{app-config-button-DkNGV0b1.js → app-config-button-C7QrZbqr.js} +1 -1
  13. package/dist/assets/{blob-BIYte8oT.js → blob-Bx8A10xl.js} +1 -1
  14. package/dist/assets/{cell-editor-C5RTy4Y0.js → cell-editor-CevZDS63.js} +4 -4
  15. package/dist/assets/cell-link-DC1UZQr_.js +1 -0
  16. package/dist/assets/{cells-h5u00Nz8.js → cells-CxY8RSp7.js} +2 -2
  17. package/dist/assets/{chat-display-DzTkUWMw.js → chat-display-YETh6yXV.js} +1 -1
  18. package/dist/assets/{chat-panel-CKqEpnjN.js → chat-panel-DyaePe7B.js} +1 -1
  19. package/dist/assets/{chat-ui-CGzOsYVb.js → chat-ui-D_QL46qM.js} +1 -1
  20. package/dist/assets/code-visibility-DNzJgrTz.js +1 -0
  21. package/dist/assets/{column-preview-DtrgJzVI.js → column-preview-sL_Jo0hX.js} +1 -1
  22. package/dist/assets/{command-palette-BKYGTfS5.js → command-palette-DMQFuWz1.js} +1 -1
  23. package/dist/assets/{common-BMeq0Bqi.js → common-Cy3lJDvJ.js} +1 -1
  24. package/dist/assets/{components-CWcf8098.js → components-D9ZIJGG8.js} +1 -1
  25. package/dist/assets/{config-BAnYSdpO.js → config-D_c_6ce1.js} +1 -1
  26. package/dist/assets/{datasource-BLHGFSsT.js → datasource-Tst92Pzi.js} +1 -1
  27. package/dist/assets/{dependency-graph-panel-C8X3Nn8W.js → dependency-graph-panel-BwMayTyZ.js} +1 -1
  28. package/dist/assets/{documentation-panel-C8wrSKlX.js → documentation-panel-OMUbXgKW.js} +1 -1
  29. package/dist/assets/{download-CUgiCx7s.js → download-CPMAf7re.js} +1 -1
  30. package/dist/assets/{edit-page-CqPNBpAP.js → edit-page-DD1NFkiu.js} +6 -6
  31. package/dist/assets/{error-entries-DQD4A9cx.js → error-entries-DBpwg0GC.js} +1 -1
  32. package/dist/assets/{error-panel-BJYkwYEY.js → error-panel-ByOEJuZN.js} +1 -1
  33. package/dist/assets/{feedback-button-DbTjGwcX.js → feedback-button-7KY7nL-j.js} +2 -2
  34. package/dist/assets/feedback-button-DxjoRfo3.js +1 -0
  35. package/dist/assets/{file-explorer-panel-BQi88S1o.js → file-explorer-panel-B92ugwxr.js} +1 -1
  36. package/dist/assets/{file-name-input-DPJSxW_X.js → file-name-input-DZehOHLA.js} +1 -1
  37. package/dist/assets/{floating-outline-3PlTr0K1.js → floating-outline-K7cVdOlC.js} +1 -1
  38. package/dist/assets/{gallery-page-BqvVmwde.js → gallery-page-DoriMrLH.js} +1 -1
  39. package/dist/assets/{glide-data-editor-CAa14cIr.js → glide-data-editor-DWyeHe3u.js} +1 -1
  40. package/dist/assets/{home-page-Bg-WqjiD.js → home-page-BcJJsD_Q.js} +1 -1
  41. package/dist/assets/{hooks-DLBvBo8c.js → hooks-DNPsQf79.js} +1 -1
  42. package/dist/assets/index-B7ZZVUcj.css +2 -0
  43. package/dist/assets/{index-BtV91sqJ.js → index-CqJKdR1j.js} +5 -5
  44. package/dist/assets/{layout-mWXZofX2.js → layout-BaluekG6.js} +3 -3
  45. package/dist/assets/{logs-panel-IKL6F2OX.js → logs-panel-OkoO5mbN.js} +1 -1
  46. package/dist/assets/{markdown-renderer-DHvAXF8E.js → markdown-renderer-mDlWBE1M.js} +1 -1
  47. package/dist/assets/{name-cell-input-j4eGLLyd.js → name-cell-input-D_Q2IhP9.js} +1 -1
  48. package/dist/assets/{outline-panel-CprDWmxn.js → outline-panel-Dy2cmYDF.js} +1 -1
  49. package/dist/assets/packages-panel-CIvhx0hb.js +1 -0
  50. package/dist/assets/{panel-accordion-state-8xXVhawu.js → panel-accordion-state-BI4SU22L.js} +1 -1
  51. package/dist/assets/panels-CO6Clgel.js +4 -0
  52. package/dist/assets/{process-output-DOmeJ1VR.js → process-output-Crm0vqbi.js} +1 -1
  53. package/dist/assets/{readonly-code-display-xvH6MyT6.js → readonly-code-display-CNbw6v1-.js} +1 -1
  54. package/dist/assets/{readonly-python-code-D4YGmSvB.js → readonly-python-code-DcYhdd1B.js} +1 -1
  55. package/dist/assets/{reveal-component-CDUG1x74.js → reveal-component-DeZTBbnn.js} +1 -1
  56. package/dist/assets/{run-page-BAqLgcq9.js → run-page-BNEg-Ro9.js} +1 -1
  57. package/dist/assets/runtime-details-state-lxXYwJcI.js +1 -0
  58. package/dist/assets/{scratchpad-panel-BFbUuzdn.js → scratchpad-panel-lDJWcpVs.js} +1 -1
  59. package/dist/assets/{session-panel-BdN43Lzi.js → session-panel-BqutyoYd.js} +1 -1
  60. package/dist/assets/{snippets-panel-5_jR08Zd.js → snippets-panel-YttkYAW9.js} +1 -1
  61. package/dist/assets/state-B2ZbImvm.js +1 -0
  62. package/dist/assets/{state-BxJ_FX3k.js → state-B85OOscw.js} +1 -1
  63. package/dist/assets/{state-7os84dnI.js → state-sbCCk7ZP.js} +1 -1
  64. package/dist/assets/{terminal-DHWTOXu3.js → terminal-3hxM6K9q.js} +1 -1
  65. package/dist/assets/{tracing-CRkNpBaH.js → tracing-DZYfCw6p.js} +1 -1
  66. package/dist/assets/{tracing-panel-DV2-2TUI.js → tracing-panel-BUPJ77jB.js} +2 -2
  67. package/dist/assets/{tree-actions-CShqSonp.js → tree-actions-D5clvgKl.js} +1 -1
  68. package/dist/assets/{useAddCell-DesMfnE4.js → useAddCell-Bhv6GT3m.js} +1 -1
  69. package/dist/assets/{useBoolean-BofUVeKg.js → useBoolean-Co-_MyFz.js} +1 -1
  70. package/dist/assets/{useCellActionButton-BO5SFMxA.js → useCellActionButton-DwAUf_Cn.js} +1 -1
  71. package/dist/assets/{useDeleteCell-DSbUjpSC.js → useDeleteCell-BKN7t4DH.js} +1 -1
  72. package/dist/assets/{useDependencyPanelTab-JIFiQrr2.js → useDependencyPanelTab-CbcGEmqM.js} +1 -1
  73. package/dist/assets/{useInstallPackage-CCQcFc6U.js → useInstallPackage-HTUlGg0U.js} +1 -1
  74. package/dist/assets/{useNotebookActions-CjjED3t-.js → useNotebookActions-pXzVjrlk.js} +1 -1
  75. package/dist/assets/{useRunCells-Bx3wXUWO.js → useRunCells-zMO43eNR.js} +1 -1
  76. package/dist/assets/{useSplitCell-BRmQP02b.js → useSplitCell-xEtFLByO.js} +1 -1
  77. package/dist/assets/{vega-component-C28fL5fD.js → vega-component-Chbfi__Q.js} +1 -1
  78. package/dist/index.html +24 -24
  79. package/package.json +1 -1
  80. package/src/__tests__/mount.test.ts +6 -6
  81. package/src/components/editor/actions/useRestartKernel.tsx +2 -2
  82. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +224 -17
  83. package/src/components/editor/alerts/connection-details.tsx +61 -0
  84. package/src/components/editor/alerts/connection-notice.tsx +46 -53
  85. package/src/components/editor/alerts/startup-progress.tsx +73 -129
  86. package/src/components/editor/app-container.tsx +2 -2
  87. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +2 -2
  88. package/src/components/editor/cell/CreateCellButton.tsx +2 -2
  89. package/src/components/editor/cell/DeleteButton.tsx +2 -2
  90. package/src/components/editor/cell/RunButton.tsx +3 -3
  91. package/src/components/editor/cell/StopButton.tsx +2 -2
  92. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +4 -4
  93. package/src/components/editor/chrome/components/feedback-button.tsx +3 -3
  94. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +8 -8
  95. package/src/components/editor/chrome/panels/__tests__/{sandbox-details.test.tsx → runtime-details.test.tsx} +94 -22
  96. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +4 -4
  97. package/src/components/editor/chrome/panels/packages-panel.tsx +8 -29
  98. package/src/components/editor/chrome/panels/{sandbox-details-state.ts → runtime-details-state.ts} +1 -1
  99. package/src/components/editor/chrome/panels/{sandbox-panel.tsx → runtime-panel.tsx} +38 -66
  100. package/src/components/editor/chrome/panels/runtime-status-toggle.tsx +56 -0
  101. package/src/components/editor/chrome/panels/sandbox-controller.tsx +2 -2
  102. package/src/components/editor/chrome/wrapper/app-chrome.tsx +3 -3
  103. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +2 -2
  104. package/src/components/editor/controls/Controls.tsx +3 -3
  105. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +2 -2
  106. package/src/components/editor/header/__tests__/status.test.tsx +4 -4
  107. package/src/components/editor/header/app-header.tsx +2 -2
  108. package/src/components/editor/header/status.tsx +3 -3
  109. package/src/components/ui/progress-stages.tsx +114 -0
  110. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +106 -5
  111. package/src/core/codemirror/lsp/notebook-lsp.ts +16 -2
  112. package/src/core/codemirror/readonly/__tests__/extension.test.ts +6 -6
  113. package/src/core/codemirror/readonly/extension.ts +2 -2
  114. package/src/core/datasets/__tests__/data-source-discovery.test.ts +2 -2
  115. package/src/core/edit-app.tsx +5 -1
  116. package/src/core/export/hooks.ts +2 -2
  117. package/src/core/network/__tests__/connection.test.ts +3 -3
  118. package/src/core/network/connection-notice.ts +5 -5
  119. package/src/core/network/connection.ts +9 -9
  120. package/src/core/network/requests-lazy.ts +2 -2
  121. package/src/core/packages/__tests__/useSandboxController.test.tsx +2 -2
  122. package/src/core/packages/useSandboxController.ts +6 -6
  123. package/src/core/runtime/__tests__/adapter.test.ts +6 -6
  124. package/src/core/runtime/adapter.ts +7 -7
  125. package/src/core/runtime/config.ts +2 -2
  126. package/src/core/saving/filename.ts +2 -2
  127. package/src/core/saving/save-component.tsx +5 -5
  128. package/src/core/saving/useAutoSave.ts +2 -2
  129. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +30 -25
  130. package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +5 -5
  131. package/src/core/websocket/connection-utils.ts +21 -21
  132. package/src/core/websocket/types.ts +8 -9
  133. package/src/core/websocket/useMarimoKernelConnection.tsx +21 -21
  134. package/src/mount.tsx +2 -2
  135. package/src/plugins/core/__test__/registerReactComponent.test.ts +55 -0
  136. package/src/plugins/core/registerReactComponent.tsx +3 -1
  137. package/src/stories/cell.stories.tsx +2 -2
  138. package/src/stories/layout/vertical/one-column.stories.tsx +5 -5
  139. package/src/stories/progress-stages.stories.tsx +62 -0
  140. package/src/stories/runtime-existing.stories.tsx +44 -0
  141. package/src/stories/runtime-maintenance.stories.tsx +40 -0
  142. package/src/stories/runtime-pixi.stories.tsx +28 -0
  143. package/src/stories/{sandbox.stories.tsx → runtime-story.tsx} +191 -152
  144. package/src/stories/runtime-uv.stories.tsx +56 -0
  145. package/dist/assets/cell-link-BeZAiE1D.js +0 -1
  146. package/dist/assets/code-visibility-B5QjA0ie.js +0 -1
  147. package/dist/assets/feedback-button-JqvnTSfN.js +0 -1
  148. package/dist/assets/index-CF8Gfz1R.css +0 -2
  149. package/dist/assets/packages-panel-CDDChziK.js +0 -1
  150. package/dist/assets/panels-CU7deLqc.js +0 -4
  151. package/dist/assets/sandbox-panel-D5bRunTM.js +0 -1
  152. package/dist/assets/state-Bo8h64Rn.js +0 -1
  153. package/src/components/editor/chrome/panels/sandbox-toggle.tsx +0 -52
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import { useAtomValue } from "jotai";
2
+ import { useAtom, useAtomValue } from "jotai";
3
3
  import { ArrowRightIcon, ChevronRightIcon } from "lucide-react";
4
4
  import { useState } from "react";
5
5
  import { Button } from "@/components/ui/button";
@@ -8,25 +8,31 @@ import type { AppConfig } from "@/core/config/config-schema";
8
8
  import { connectionAtom } from "@/core/network/connection";
9
9
  import { useConnectionNotice } from "@/core/network/useConnectionNotice";
10
10
  import { sandboxAtom } from "@/core/packages/sandbox-state";
11
- import { WebSocketState } from "@/core/websocket/types";
11
+ import { ConnectionState } from "@/core/websocket/types";
12
12
  import { useInterval } from "@/hooks/useInterval";
13
13
  import { cn } from "@/utils/cn";
14
- import { SandboxErrorOutput } from "../chrome/panels/sandbox-panel";
14
+ import { runtimeDetailsExpandedAtom } from "../chrome/panels/runtime-details-state";
15
15
  import { chromeAtom, useChromeActions } from "../chrome/state";
16
16
  import { VerticalLayoutWrapper } from "../renderers/vertical-layout/vertical-layout-wrapper";
17
- import { ConnectionStatusIcon, StartupProgress } from "./startup-progress";
17
+ import { ConnectionDetails } from "./connection-details";
18
+ import { ConnectionStatusIcon, hasStartupStages } from "./startup-progress";
18
19
 
19
20
  export const ConnectionNotice = ({
20
21
  appConfig,
21
22
  onRetry,
23
+ canOpenPackages = true,
22
24
  }: {
23
25
  appConfig: AppConfig;
24
26
  onRetry: () => void;
27
+ canOpenPackages?: boolean;
25
28
  }) => {
26
29
  const notice = useConnectionNotice();
27
30
  const connection = useAtomValue(connectionAtom);
28
31
  const hasCells = useAtomValue(hasCellsAtom);
29
32
  const sandbox = useAtomValue(sandboxAtom);
33
+ const [detailsExpanded, setDetailsExpanded] = useAtom(
34
+ runtimeDetailsExpandedAtom,
35
+ );
30
36
  const chrome = useAtomValue(chromeAtom);
31
37
  const packagesOpen =
32
38
  (chrome.isSidebarOpen && chrome.selectedPanel === "packages") ||
@@ -36,48 +42,60 @@ export const ConnectionNotice = ({
36
42
 
37
43
  // Reconnection uses the footer indicator while existing cells stay visible.
38
44
  const reconnecting =
39
- connection.state === WebSocketState.CONNECTING &&
45
+ connection.state === ConnectionState.CONNECTING &&
40
46
  connection.phase === "reconnecting";
41
47
 
42
- if (sandbox?.backend && hasCells) {
48
+ const openDetails = () => {
49
+ setDetailsExpanded(true);
50
+ openApplication("packages");
51
+ };
52
+
53
+ if (!notice && !sandbox?.backend) {
54
+ return null;
55
+ }
56
+
57
+ if (hasCells && canOpenPackages) {
43
58
  const title =
44
59
  notice?.ready && notice.kind === "startup" ? "Ready" : notice?.title;
45
60
  return (
46
61
  <VerticalLayoutWrapper
47
62
  appConfig={appConfig}
48
- // Reserve the status row and gutter so completion never shifts cells.
63
+ // Sandboxes retain this row for subsequent environment syncs.
49
64
  className="shrink-0 h-12 pb-0 sm:pb-0 print:hidden"
50
65
  innerClassName="pb-0 sm:pb-0 pr-4"
51
66
  >
52
67
  {notice && !reconnecting && (
53
- <section aria-label="Notebook startup">
68
+ <section
69
+ aria-label="Notebook startup"
70
+ className="flex items-center gap-4"
71
+ >
54
72
  <button
55
73
  type="button"
56
- onClick={() => openApplication("packages")}
74
+ onClick={openDetails}
57
75
  aria-label={`${title} Open Packages`}
58
- aria-expanded={packagesOpen}
76
+ aria-expanded={packagesOpen && detailsExpanded}
59
77
  className="flex h-6 items-center gap-2 whitespace-nowrap text-xs text-muted-foreground hover:text-foreground rounded focus-visible:outline-2 focus-visible:outline-ring"
60
78
  >
61
79
  <ConnectionStatusIcon notice={notice} />
62
80
  <output>{title}</output>
63
81
  <ChevronRightIcon className="size-3" aria-hidden={true} />
64
82
  </button>
83
+ {!notice.pending && !notice.ready && !notice.sandbox && (
84
+ <Button variant="text" size="xs" onClick={onRetry}>
85
+ Try again
86
+ </Button>
87
+ )}
65
88
  </section>
66
89
  )}
67
90
  </VerticalLayoutWrapper>
68
91
  );
69
92
  }
70
93
 
71
- if (!notice || (hasCells && reconnecting)) {
94
+ if (!notice) {
72
95
  return null;
73
96
  }
74
97
 
75
- const showSteps =
76
- notice.sandbox &&
77
- notice.kind === "startup" &&
78
- (notice.ready ||
79
- notice.phase === "preparing-environment" ||
80
- notice.phase === "starting-kernel");
98
+ const showSteps = hasStartupStages(notice);
81
99
  return (
82
100
  <VerticalLayoutWrapper
83
101
  appConfig={appConfig}
@@ -87,51 +105,26 @@ export const ConnectionNotice = ({
87
105
  <section
88
106
  aria-label="Notebook startup"
89
107
  className={cn(
90
- "text-muted-foreground",
91
- hasCells
92
- ? "mt-4 mb-8 border rounded-lg px-5 py-5"
93
- : "max-w-md mx-auto mt-16 sm:mt-24 px-4",
94
- hasCells && !notice.pending && "border-(--red-7)",
108
+ "text-muted-foreground max-w-md mx-auto px-4",
109
+ hasCells ? "mt-4 mb-8" : "mt-16 sm:mt-24",
95
110
  )}
96
111
  >
97
- {showSteps ? (
98
- <StartupProgress notice={notice} surface="notebook" />
99
- ) : (
100
- <div role="status" className="flex items-center gap-2.5">
101
- <ConnectionStatusIcon notice={notice} />
102
- <h2
103
- className={cn(
104
- "text-base",
105
- notice.pending || notice.ready
106
- ? "font-normal text-foreground"
107
- : "font-medium text-(--red-11)",
108
- )}
109
- >
110
- {notice.title}
111
- </h2>
112
- </div>
112
+ {!canOpenPackages && showSteps && (
113
+ <output className="sr-only">{notice.title}</output>
113
114
  )}
115
+ <ConnectionDetails
116
+ notice={notice}
117
+ surface={canOpenPackages ? "notebook" : "sidebar"}
118
+ />
114
119
  <div className={showSteps ? "mt-8" : "ml-6.5"}>
115
- {!showSteps && (
116
- <p
117
- className={cn(
118
- "mt-3 text-sm leading-7",
119
- !notice.pending && "text-foreground",
120
- )}
121
- >
122
- {notice.description}
123
- </p>
124
- )}
125
- {!notice.sandbox && notice.error && (
126
- <SandboxErrorOutput error={notice.error} />
127
- )}
128
120
  <div
129
121
  className={cn(
130
122
  "flex flex-wrap items-center gap-x-6 gap-y-3",
131
123
  showSteps ? "border-t border-border/60 pt-4" : "mt-5",
132
124
  )}
133
125
  >
134
- {!notice.ready &&
126
+ {canOpenPackages &&
127
+ !notice.ready &&
135
128
  !packagesOpen &&
136
129
  (notice.sandbox || notice.pending) && (
137
130
  <Button
@@ -141,7 +134,7 @@ export const ConnectionNotice = ({
141
134
  "p-0 text-xs gap-1.5",
142
135
  showSteps ? "h-6" : "h-auto",
143
136
  )}
144
- onClick={() => openApplication("packages")}
137
+ onClick={openDetails}
145
138
  >
146
139
  {notice.pending ? "View setup details" : "Open Packages"}
147
140
  <ArrowRightIcon className="size-3" aria-hidden={true} />
@@ -1,41 +1,44 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { useAtomValue } from "jotai";
3
- import { AlertCircleIcon, CheckIcon, CircleIcon } from "lucide-react";
4
- import { Spinner } from "@/components/icons/spinner";
3
+ import {
4
+ ProgressStages,
5
+ ProgressStatusIcon,
6
+ type ProgressStage,
7
+ type ProgressState,
8
+ } from "@/components/ui/progress-stages";
5
9
  import { preparationAtom } from "@/core/packages/sandbox-state";
6
10
  import { startupProgressAtom } from "@/core/network/connection";
7
11
  import type { DisplayConnectionNotice } from "@/core/network/useConnectionNotice";
8
- import { cn } from "@/utils/cn";
9
- import { StartupOutput, type StartupOutputState } from "./startup-output";
12
+ import { StartupOutput } from "./startup-output";
10
13
 
11
14
  const STEPS = [
12
15
  {
13
16
  phase: "preparing-environment",
14
17
  titles: {
15
- complete: "Environment prepared",
18
+ succeeded: "Environment prepared",
16
19
  failed: "Environment setup failed",
17
- pending: "Preparing environment",
20
+ running: "Preparing environment",
18
21
  waiting: "Prepare environment",
19
22
  },
20
23
  descriptions: {
21
- complete: "Python and dependencies are available.",
24
+ succeeded: "Python and dependencies are available.",
22
25
  failed: "Setup could not finish.",
23
- pending: "Set up Python and dependencies.",
26
+ running: "Set up Python and dependencies.",
24
27
  waiting: "Set up Python and dependencies.",
25
28
  },
26
29
  },
27
30
  {
28
31
  phase: "starting-kernel",
29
32
  titles: {
30
- complete: "Kernel started",
33
+ succeeded: "Kernel started",
31
34
  failed: "Kernel failed to start",
32
- pending: "Starting kernel",
35
+ running: "Starting kernel",
33
36
  waiting: "Start kernel",
34
37
  },
35
38
  descriptions: {
36
- complete: "The notebook is ready to run.",
39
+ succeeded: "The notebook is ready to run.",
37
40
  failed: "The kernel could not connect.",
38
- pending: "Connecting to this notebook.",
41
+ running: "Connecting to this notebook.",
39
42
  waiting: "After the environment is ready.",
40
43
  },
41
44
  },
@@ -46,31 +49,13 @@ interface StartupProgressProps {
46
49
  surface: "notebook" | "sidebar";
47
50
  }
48
51
 
49
- function StartupStepOutput({
50
- phase,
51
- state,
52
- }: {
53
- phase: (typeof STEPS)[number]["phase"];
54
- state: StartupOutputState;
55
- }) {
56
- const preparation = useAtomValue(preparationAtom);
57
- const progress = useAtomValue(startupProgressAtom);
58
- const preparing = phase === "preparing-environment";
59
- const logs = preparing
60
- ? (preparation?.logs.environment ?? "")
61
- : progress?.phase === phase
62
- ? progress.logs
63
- : "";
64
-
52
+ export function hasStartupStages(notice: DisplayConnectionNotice): boolean {
65
53
  return (
66
- <StartupOutput
67
- key={preparation?.operation_id}
68
- logs={logs}
69
- state={state}
70
- label={
71
- preparing ? "environment preparation output" : "kernel startup output"
72
- }
73
- />
54
+ notice.kind === "startup" &&
55
+ (!notice.sandbox ||
56
+ notice.ready ||
57
+ notice.phase === "preparing-environment" ||
58
+ notice.phase === "starting-kernel")
74
59
  );
75
60
  }
76
61
 
@@ -85,9 +70,13 @@ function StartupSummary({ notice }: { notice: DisplayConnectionNotice }) {
85
70
  </h2>
86
71
  <p className="text-muted-foreground mt-3 text-sm leading-6">
87
72
  {notice.ready
88
- ? "Your environment and kernel are ready."
73
+ ? notice.sandbox
74
+ ? "Your environment and kernel are ready."
75
+ : "Your kernel is ready."
89
76
  : notice.pending
90
- ? "Preparing Python and dependencies for this notebook."
77
+ ? notice.sandbox
78
+ ? "Preparing Python and dependencies for this notebook."
79
+ : "Connecting to this notebook’s Python kernel."
91
80
  : "Review the setup details below to continue."}
92
81
  </p>
93
82
  </header>
@@ -99,106 +88,61 @@ export function ConnectionStatusIcon({
99
88
  }: {
100
89
  notice: DisplayConnectionNotice;
101
90
  }) {
102
- if (notice.ready) {
103
- return (
104
- <CheckIcon
105
- className="size-4 shrink-0 text-(--grass-11)"
106
- aria-hidden={true}
107
- />
108
- );
109
- }
110
- if (notice.pending) {
111
- return (
112
- <Spinner
113
- className="size-4 shrink-0 motion-reduce:animate-none"
114
- aria-hidden={true}
115
- />
116
- );
117
- }
118
91
  return (
119
- <AlertCircleIcon
120
- className="size-4 shrink-0 text-(--red-11)"
121
- aria-hidden={true}
92
+ <ProgressStatusIcon
93
+ state={notice.ready ? "succeeded" : notice.pending ? "running" : "failed"}
122
94
  />
123
95
  );
124
96
  }
125
97
 
126
98
  export function StartupProgress({ notice, surface }: StartupProgressProps) {
99
+ const preparation = useAtomValue(preparationAtom);
100
+ const progress = useAtomValue(startupProgressAtom);
101
+ const activePhase = notice.sandbox ? notice.phase : "starting-kernel";
102
+ const stages: ProgressStage[] = STEPS.filter(
103
+ (step) => notice.sandbox || step.phase === "starting-kernel",
104
+ ).map(({ phase, titles, descriptions }) => {
105
+ const preparing = phase === "preparing-environment";
106
+ const state: ProgressState =
107
+ notice.ready || (preparing && activePhase === "starting-kernel")
108
+ ? "succeeded"
109
+ : activePhase === phase
110
+ ? notice.pending
111
+ ? "running"
112
+ : "failed"
113
+ : "waiting";
114
+ const logs = preparing
115
+ ? (preparation?.logs.environment ?? "")
116
+ : progress?.phase === phase
117
+ ? progress.logs
118
+ : "";
119
+ return {
120
+ id: phase,
121
+ state,
122
+ title: titles[state],
123
+ description: descriptions[state],
124
+ details: surface === "sidebar" && (
125
+ <StartupOutput
126
+ key={preparation?.operation_id}
127
+ logs={logs}
128
+ state={state === "waiting" ? "succeeded" : state}
129
+ label={
130
+ preparing
131
+ ? "environment preparation output"
132
+ : "kernel startup output"
133
+ }
134
+ />
135
+ ),
136
+ };
137
+ });
127
138
  return (
128
139
  <>
129
140
  {surface === "notebook" && <StartupSummary notice={notice} />}
130
- <ol
131
- className={cn("space-y-6", surface === "notebook" && "mt-8")}
132
- aria-label="Notebook startup stages"
133
- >
134
- {STEPS.map(({ phase, titles, descriptions }, index) => {
135
- const complete =
136
- notice.ready || (index === 0 && notice.phase === "starting-kernel");
137
- const active = !notice.ready && notice.phase === phase;
138
- const failed = active && !notice.pending;
139
- const state = complete
140
- ? "complete"
141
- : failed
142
- ? "failed"
143
- : active
144
- ? "pending"
145
- : "waiting";
146
- return (
147
- <li
148
- key={phase}
149
- aria-current={active ? "step" : undefined}
150
- className="relative flex items-start gap-3"
151
- >
152
- {index === 0 && (
153
- <span
154
- aria-hidden={true}
155
- className="absolute left-2 top-6 -bottom-4 border-l border-border"
156
- />
157
- )}
158
- <span
159
- className={cn(
160
- "mt-0.5",
161
- complete ? "text-(--grass-11)" : "text-muted-foreground",
162
- )}
163
- >
164
- {complete ? (
165
- <CheckIcon className="size-4" aria-hidden={true} />
166
- ) : active ? (
167
- <ConnectionStatusIcon notice={notice} />
168
- ) : (
169
- <CircleIcon
170
- className="size-4 text-muted-foreground/50"
171
- aria-hidden={true}
172
- />
173
- )}
174
- </span>
175
- <div className="min-w-0 flex-1">
176
- <div
177
- className={cn(
178
- "text-sm",
179
- failed
180
- ? "text-(--red-11)"
181
- : complete || active
182
- ? "text-foreground"
183
- : "text-muted-foreground",
184
- )}
185
- >
186
- {titles[state]}
187
- </div>
188
- <p className="mt-1 text-xs text-muted-foreground">
189
- {descriptions[state]}
190
- </p>
191
- {surface === "sidebar" && (
192
- <StartupStepOutput
193
- phase={phase}
194
- state={failed ? "failed" : active ? "running" : "succeeded"}
195
- />
196
- )}
197
- </div>
198
- </li>
199
- );
200
- })}
201
- </ol>
141
+ <ProgressStages
142
+ stages={stages}
143
+ label="Notebook startup stages"
144
+ className={surface === "notebook" ? "mt-8" : undefined}
145
+ />
202
146
  </>
203
147
  );
204
148
  }
@@ -7,7 +7,7 @@ import { PyodideLoader } from "@/core/wasm/PyodideLoader";
7
7
  import {
8
8
  type ConnectionStatus,
9
9
  WebSocketClosedReason,
10
- WebSocketState,
10
+ ConnectionState,
11
11
  } from "@/core/websocket/types";
12
12
  import { cn } from "@/utils/cn";
13
13
  import { DynamicFavicon } from "./dynamic-favicon";
@@ -47,7 +47,7 @@ export const AppContainer: React.FC<PropsWithChildren<Props>> = ({
47
47
  data-connection-state={connectionState}
48
48
  className={cn(
49
49
  "mathjax_ignore",
50
- connection.state === WebSocketState.CLOSED &&
50
+ connection.state === ConnectionState.CLOSED &&
51
51
  connection.code !==
52
52
  WebSocketClosedReason.KERNEL_STARTUP_ERROR &&
53
53
  "disconnected",
@@ -14,7 +14,7 @@ import { requestClientAtom } from "@/core/network/requests";
14
14
  import type { EnvironmentInfo } from "@/core/network/types";
15
15
  import { filenameAtom } from "@/core/saving/file-state";
16
16
  import { store } from "@/core/state/jotai";
17
- import { WebSocketState } from "@/core/websocket/types";
17
+ import { ConnectionState } from "@/core/websocket/types";
18
18
  import { ErrorBoundary } from "../ErrorBoundary";
19
19
 
20
20
  vi.mock("@/utils/copy", () => ({
@@ -144,7 +144,7 @@ function resetSharedState() {
144
144
  store.set(requestClientAtom, null);
145
145
  store.set(notebookAtom, initialNotebookState());
146
146
  store.set(viewStateAtom, { mode: "edit", cellAnchor: null });
147
- store.set(connectionAtom, { state: WebSocketState.OPEN });
147
+ store.set(connectionAtom, { state: ConnectionState.OPEN });
148
148
  store.set(filenameAtom, "/project/example.py");
149
149
  }
150
150
 
@@ -17,7 +17,7 @@ import {
17
17
  getConnectionTooltip,
18
18
  isAppInteractionDisabled,
19
19
  } from "@/core/websocket/connection-utils";
20
- import type { WebSocketState } from "@/core/websocket/types";
20
+ import type { ConnectionState } from "@/core/websocket/types";
21
21
  import { cn } from "@/utils/cn";
22
22
  import { Tooltip } from "../../ui/tooltip";
23
23
  import { MarkdownIcon, PythonIcon } from "./code/icons";
@@ -28,7 +28,7 @@ export const CreateCellButton = ({
28
28
  tooltipContent,
29
29
  oneClickShortcut,
30
30
  }: {
31
- connectionState: WebSocketState;
31
+ connectionState: ConnectionState;
32
32
  tooltipContent: React.ReactNode;
33
33
  onClick: ((opts: { code: string; hideCode?: boolean }) => void) | undefined;
34
34
  oneClickShortcut: "shift" | "mod";
@@ -6,7 +6,7 @@ import {
6
6
  getConnectionTooltip,
7
7
  isAppInteractionDisabled,
8
8
  } from "@/core/websocket/connection-utils";
9
- import type { WebSocketState } from "@/core/websocket/types";
9
+ import type { ConnectionState } from "@/core/websocket/types";
10
10
  import { cn } from "@/utils/cn";
11
11
  import { Events } from "@/utils/events";
12
12
  import { Button } from "../../ui/button";
@@ -14,7 +14,7 @@ import { Tooltip } from "../../ui/tooltip";
14
14
 
15
15
  export const DeleteButton = (props: {
16
16
  status: RuntimeState;
17
- connectionState: WebSocketState;
17
+ connectionState: ConnectionState;
18
18
  onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
19
19
  }): JSX.Element => {
20
20
  const { status, connectionState, onClick } = props;
@@ -6,7 +6,7 @@ import {
6
6
  getConnectionTooltip,
7
7
  isAppInteractionDisabled,
8
8
  } from "@/core/websocket/connection-utils";
9
- import type { WebSocketState } from "@/core/websocket/types";
9
+ import type { ConnectionState } from "@/core/websocket/types";
10
10
  import { renderShortcut } from "../../shortcuts/renderShortcut";
11
11
  import { ToolbarItem } from "./toolbar";
12
12
 
@@ -16,7 +16,7 @@ function computeColor({
16
16
  loading,
17
17
  inactive,
18
18
  }: {
19
- connectionState: WebSocketState;
19
+ connectionState: ConnectionState;
20
20
  needsRun: boolean;
21
21
  loading: boolean;
22
22
  inactive: boolean;
@@ -37,7 +37,7 @@ export const RunButton = (props: {
37
37
  edited: boolean;
38
38
  status: RuntimeState;
39
39
  needsRun: boolean;
40
- connectionState: WebSocketState;
40
+ connectionState: ConnectionState;
41
41
  config: CellConfig;
42
42
  onClick?: () => void;
43
43
  }): JSX.Element => {
@@ -4,7 +4,7 @@ import type { JSX } from "react";
4
4
  import { useRequestClient } from "@/core/network/requests";
5
5
  import type { RuntimeState } from "@/core/network/types";
6
6
  import { isAppInteractionDisabled } from "@/core/websocket/connection-utils";
7
- import type { WebSocketState } from "@/core/websocket/types";
7
+ import type { ConnectionState } from "@/core/websocket/types";
8
8
  import { Functions } from "@/utils/functions";
9
9
  import { renderShortcut } from "../../shortcuts/renderShortcut";
10
10
  import { ToolbarItem } from "./toolbar";
@@ -12,7 +12,7 @@ import { useShouldShowInterrupt } from "./useShouldShowInterrupt";
12
12
 
13
13
  export const StopButton = (props: {
14
14
  status: RuntimeState;
15
- connectionState: WebSocketState;
15
+ connectionState: ConnectionState;
16
16
  }): JSX.Element => {
17
17
  const { connectionState, status } = props;
18
18
  const { sendInterrupt } = useRequestClient();
@@ -14,7 +14,7 @@ import { requestClientAtom } from "@/core/network/requests";
14
14
  import type { EnvironmentInfo } from "@/core/network/types";
15
15
  import { filenameAtom } from "@/core/saving/file-state";
16
16
  import { store } from "@/core/state/jotai";
17
- import { WebSocketState } from "@/core/websocket/types";
17
+ import { ConnectionState } from "@/core/websocket/types";
18
18
  import * as copyModule from "@/utils/copy";
19
19
  import { FeedbackModal } from "../feedback-button";
20
20
 
@@ -53,7 +53,7 @@ describe("FeedbackModal issue reporting", () => {
53
53
  localStorage.clear();
54
54
  store.set(requestClientAtom, MockRequestClient.create());
55
55
  store.set(viewStateAtom, { mode: "edit", cellAnchor: null });
56
- store.set(connectionAtom, { state: WebSocketState.OPEN });
56
+ store.set(connectionAtom, { state: ConnectionState.OPEN });
57
57
  store.set(filenameAtom, "/project/example.py");
58
58
  });
59
59
 
@@ -156,7 +156,7 @@ describe("FeedbackModal issue reporting", () => {
156
156
  });
157
157
 
158
158
  it("disables include code and explains why when disconnected", async () => {
159
- store.set(connectionAtom, { state: WebSocketState.CONNECTING });
159
+ store.set(connectionAtom, { state: ConnectionState.CONNECTING });
160
160
  store.set(
161
161
  requestClientAtom,
162
162
  MockRequestClient.create({
@@ -244,7 +244,7 @@ describe("FeedbackModal issue reporting", () => {
244
244
  "marimo:issue-report:include-code",
245
245
  JSON.stringify(true),
246
246
  );
247
- store.set(connectionAtom, { state: WebSocketState.CONNECTING });
247
+ store.set(connectionAtom, { state: ConnectionState.CONNECTING });
248
248
  store.set(requestClientAtom, null);
249
249
  render(<FeedbackModal onClose={vi.fn()} />, { wrapper });
250
250
 
@@ -39,7 +39,7 @@ import { connectionAtom } from "@/core/network/connection";
39
39
  import { requestClientAtom } from "@/core/network/requests";
40
40
  import { filenameAtom } from "@/core/saving/file-state";
41
41
  import { store } from "@/core/state/jotai";
42
- import { WebSocketState } from "@/core/websocket/types";
42
+ import { ConnectionState } from "@/core/websocket/types";
43
43
  import { useAsyncData } from "@/hooks/useAsyncData";
44
44
  import { useLocalStorage } from "@/hooks/useLocalStorage";
45
45
  import { cn } from "@/utils/cn";
@@ -78,7 +78,7 @@ function getNotebookSourceUnavailableReason(args: {
78
78
  hasRequestClient: boolean;
79
79
  filename: string | null;
80
80
  codeAvailable: boolean;
81
- connectionState: WebSocketState;
81
+ connectionState: ConnectionState;
82
82
  }): string | undefined {
83
83
  if (!args.hasRequestClient) {
84
84
  return "Notebook source is unavailable.";
@@ -89,7 +89,7 @@ function getNotebookSourceUnavailableReason(args: {
89
89
  if (!args.codeAvailable) {
90
90
  return "Notebook source is hidden in this view.";
91
91
  }
92
- if (args.connectionState !== WebSocketState.OPEN) {
92
+ if (args.connectionState !== ConnectionState.OPEN) {
93
93
  return "Connect the notebook to include its source.";
94
94
  }
95
95
  return undefined;