diff --git a/frontend/src/components/editor/__tests__/data-attributes.test.tsx b/frontend/src/components/editor/__tests__/data-attributes.test.tsx
index 51eda15383d..84b679b7f89 100644
--- a/frontend/src/components/editor/__tests__/data-attributes.test.tsx
+++ b/frontend/src/components/editor/__tests__/data-attributes.test.tsx
@@ -12,7 +12,7 @@ import type { UserConfig } from "@/core/config/config-schema";
import type { OutputMessage } from "@/core/kernel/messages";
import type { AppMode } from "@/core/mode";
import { requestClientAtom } from "@/core/network/requests";
-import { Cell } from "../Cell";
+import { Cell } from "../notebook-cell";
import { OutputArea } from "../Output";
function createTestWrapper() {
diff --git a/frontend/src/components/editor/actions/useNotebookActions.tsx b/frontend/src/components/editor/actions/useNotebookActions.tsx
index 585f2b24971..748ea3de4f1 100644
--- a/frontend/src/components/editor/actions/useNotebookActions.tsx
+++ b/frontend/src/components/editor/actions/useNotebookActions.tsx
@@ -102,7 +102,7 @@ export function useNotebookActions() {
updateCellConfig,
undoDeleteCell,
clearAllCellOutputs,
- upsertSetupCell,
+ addSetupCellIfDoesntExist,
collapseAllCells,
expandAllCells,
} = useCellActions();
@@ -401,9 +401,7 @@ export function useNotebookActions() {
icon: ,
label: "Add setup cell",
handle: () => {
- upsertSetupCell({
- code: "# Initialization code that runs before all other cells",
- });
+ addSetupCellIfDoesntExist({});
},
},
{
diff --git a/frontend/src/components/editor/cell/CreateCellButton.tsx b/frontend/src/components/editor/cell/CreateCellButton.tsx
index f133e3a79a7..198d0dbd496 100644
--- a/frontend/src/components/editor/cell/CreateCellButton.tsx
+++ b/frontend/src/components/editor/cell/CreateCellButton.tsx
@@ -1,5 +1,5 @@
/* Copyright 2024 Marimo. All rights reserved. */
-import { DatabaseIcon, PlusIcon } from "lucide-react";
+import { DatabaseIcon, DiamondPlusIcon, PlusIcon } from "lucide-react";
import { Button } from "@/components/editor/inputs/Inputs";
import {
ContextMenu,
@@ -69,7 +69,7 @@ const CreateCellButtonContextMenu = (props: {
children: React.ReactNode;
}) => {
const { children, onClick } = props;
- const { createNewCell } = useCellActions();
+ const { createNewCell, addSetupCellIfDoesntExist } = useCellActions();
if (!onClick) {
return children;
@@ -125,6 +125,18 @@ const CreateCellButtonContextMenu = (props: {
SQL cell
+ {
+ evt.stopPropagation();
+ addSetupCellIfDoesntExist({});
+ }}
+ >
+
+
+
+ Setup cell
+
);
diff --git a/frontend/src/components/editor/Cell.tsx b/frontend/src/components/editor/notebook-cell.tsx
similarity index 99%
rename from frontend/src/components/editor/Cell.tsx
rename to frontend/src/components/editor/notebook-cell.tsx
index 1c22f8c577a..fb09f9464ac 100644
--- a/frontend/src/components/editor/Cell.tsx
+++ b/frontend/src/components/editor/notebook-cell.tsx
@@ -1051,12 +1051,16 @@ const SetupCellComponent = ({
data-status={cellRuntime.status}
ref={cellRef}
{...mergeProps(navigationProps, {
- className,
+ className: cn(
+ className,
+ "focus:ring-1 focus:ring-(--blue-7) focus:ring-offset-0",
+ ),
onBlur: closeCompletionHandler,
onKeyDown: resumeCompletionHandler,
})}
{...cellDomProps(cellId, cellData.name)}
title={renderCellTitle()}
+ tabIndex={-1}
data-setup-cell={true}
>
diff --git a/frontend/src/components/editor/renderers/CellArray.tsx b/frontend/src/components/editor/renderers/cell-array.tsx
similarity index 99%
rename from frontend/src/components/editor/renderers/CellArray.tsx
rename to frontend/src/components/editor/renderers/cell-array.tsx
index bbdad02c062..89fdecd28a5 100644
--- a/frontend/src/components/editor/renderers/CellArray.tsx
+++ b/frontend/src/components/editor/renderers/cell-array.tsx
@@ -14,7 +14,7 @@ import {
} from "lucide-react";
import { useEffect } from "react";
import { StartupLogsAlert } from "@/components/editor/alerts/startup-logs-alert";
-import { Cell } from "@/components/editor/Cell";
+import { Cell } from "@/components/editor/notebook-cell";
import { PackageAlert } from "@/components/editor/package-alert";
import { SortableCellsProvider } from "@/components/sort/SortableCellsProvider";
import { Button } from "@/components/ui/button";
diff --git a/frontend/src/core/cells/__tests__/cells.test.ts b/frontend/src/core/cells/__tests__/cells.test.ts
index af0952ce91e..d9d7bab4dd6 100644
--- a/frontend/src/core/cells/__tests__/cells.test.ts
+++ b/frontend/src/core/cells/__tests__/cells.test.ts
@@ -12,7 +12,7 @@ import {
it,
vi,
} from "vitest";
-import type { CellHandle } from "@/components/editor/Cell";
+import type { CellHandle } from "@/components/editor/notebook-cell";
import { CellId } from "@/core/cells/ids";
import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
@@ -2094,9 +2094,9 @@ describe("cell reducer", () => {
`);
});
- it("can create and update a setup cell", () => {
+ it("can create and noop-update a setup cell", () => {
// Create the setup cell
- actions.upsertSetupCell({ code: "# Setup code" });
+ actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
// Check that setup cell was created
expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
@@ -2106,17 +2106,17 @@ describe("cell reducer", () => {
expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
// Update the setup cell
- actions.upsertSetupCell({ code: "# Updated setup code" });
+ actions.addSetupCellIfDoesntExist({ code: "# Updated setup code" });
// Check that the same setup cell was updated, not duplicated
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Updated setup code");
+ expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
});
it("can delete and undelete the setup cell", () => {
// Create the setup cell
- actions.upsertSetupCell({ code: "# Setup code" });
+ actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
// Check that setup cell was created
expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
diff --git a/frontend/src/core/cells/cells.ts b/frontend/src/core/cells/cells.ts
index 634904e2544..4b7ebc21719 100644
--- a/frontend/src/core/cells/cells.ts
+++ b/frontend/src/core/cells/cells.ts
@@ -5,7 +5,7 @@ import { type Atom, atom, useAtom, useAtomValue } from "jotai";
import { atomFamily, selectAtom, splitAtom } from "jotai/utils";
import { isEqual, zip } from "lodash-es";
import { createRef, type ReducerWithoutAction } from "react";
-import type { CellHandle } from "@/components/editor/Cell";
+import type { CellHandle } from "@/components/editor/notebook-cell";
import {
type CellColumnId,
type CellIndex,
@@ -1324,21 +1324,19 @@ const {
cellRuntime: newCellRuntime,
};
},
- upsertSetupCell: (state, action: { code: string }) => {
- const { code } = action;
+ addSetupCellIfDoesntExist: (state, action: { code?: string }) => {
+ let { code } = action;
+ if (code == null) {
+ code = "# Initialization code that runs before all other cells";
+ }
// First check if setup cell already exists
if (SETUP_CELL_ID in state.cellData) {
- // Update existing setup cell
- return updateCellData({
- state,
- cellId: SETUP_CELL_ID,
- cellReducer: (cell) => ({
- ...cell,
- code,
- edited: code.trim() !== cell.lastCodeRun?.trim(),
- }),
- });
+ // Just focus on the existing setup cell
+ return {
+ ...state,
+ scrollKey: SETUP_CELL_ID,
+ };
}
return {
@@ -1365,6 +1363,7 @@ const {
...state.cellHandles,
[SETUP_CELL_ID]: createRef(),
},
+ scrollKey: SETUP_CELL_ID,
};
},
});
diff --git a/frontend/src/core/cells/scrollCellIntoView.ts b/frontend/src/core/cells/scrollCellIntoView.ts
index 8458d516989..121023290bc 100644
--- a/frontend/src/core/cells/scrollCellIntoView.ts
+++ b/frontend/src/core/cells/scrollCellIntoView.ts
@@ -1,10 +1,10 @@
/* Copyright 2024 Marimo. All rights reserved. */
import type { RefObject } from "react";
-import type { CellHandle } from "@/components/editor/Cell";
import {
isAnyCellFocused,
tryFocus,
} from "@/components/editor/navigation/focus-utils";
+import type { CellHandle } from "@/components/editor/notebook-cell";
import { retryWithTimeout } from "@/utils/timeout";
import { Logger } from "../../utils/Logger";
import { goToVariableDefinition } from "../codemirror/go-to-definition/commands";
diff --git a/frontend/src/core/edit-app.tsx b/frontend/src/core/edit-app.tsx
index c39a619201e..5a2549ced73 100644
--- a/frontend/src/core/edit-app.tsx
+++ b/frontend/src/core/edit-app.tsx
@@ -15,7 +15,7 @@ import {
useRunAllCells,
useRunStaleCells,
} from "../components/editor/cell/useRunCells";
-import { CellArray } from "../components/editor/renderers/CellArray";
+import { CellArray } from "../components/editor/renderers/cell-array";
import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
import { useHotkey } from "../hooks/useHotkey";
import {
diff --git a/frontend/src/stories/cell.stories.tsx b/frontend/src/stories/cell.stories.tsx
index 05f918e3382..72eb9402b5c 100644
--- a/frontend/src/stories/cell.stories.tsx
+++ b/frontend/src/stories/cell.stories.tsx
@@ -16,7 +16,7 @@ import type { CellConfig } from "@/core/network/types";
import { WebSocketState } from "@/core/websocket/types";
import { MultiColumn } from "@/utils/id-tree";
import type { Milliseconds, Seconds } from "@/utils/time";
-import { Cell as EditorCell } from "../components/editor/Cell";
+import { Cell as EditorCell } from "../components/editor/notebook-cell";
import { TooltipProvider } from "../components/ui/tooltip";
import type { CellId } from "../core/cells/ids";
diff --git a/frontend/src/stories/layout/vertical/one-column.stories.tsx b/frontend/src/stories/layout/vertical/one-column.stories.tsx
index c6a9a4b8b8f..ff26a8f839f 100644
--- a/frontend/src/stories/layout/vertical/one-column.stories.tsx
+++ b/frontend/src/stories/layout/vertical/one-column.stories.tsx
@@ -13,7 +13,7 @@ import { resolveRequestClient } from "@/core/network/resolve";
import { WebSocketState } from "@/core/websocket/types";
import { MultiColumn } from "@/utils/id-tree";
import type { Milliseconds, Seconds } from "@/utils/time";
-import { CellArray } from "../../../components/editor/renderers/CellArray";
+import { CellArray } from "../../../components/editor/renderers/cell-array";
import { CellsRenderer } from "../../../components/editor/renderers/cells-renderer";
import { TooltipProvider } from "../../../components/ui/tooltip";
import type { CellId } from "../../../core/cells/ids";