/* Copyright 2026 Marimo. All rights reserved. */ import type * as api from "@marimo-team/marimo-api"; import { Provider } from "jotai"; import { createRoot } from "react-dom/client"; import { z } from "zod"; import { appConfigAtom, configOverridesAtom, userConfigAtom, } from "@/core/config/config"; import { KnownQueryParams } from "@/core/constants"; import { getFilenameFromDOM } from "@/core/dom/htmlUtils"; import { getMarimoCode } from "@/core/meta/globals"; import { marimoVersionAtom, serverTokenAtom, showCodeInRunModeAtom, } from "@/core/meta/state"; import { Logger } from "@/utils/Logger"; import { ErrorBoundary } from "./components/editor/boundary/ErrorBoundary"; import { notebookAtom } from "./core/cells/cells"; import { notebookStateFromSession } from "./core/cells/session"; import { parseAppConfig, parseConfigOverrides, parseUserConfig, } from "./core/config/config-schema"; import { MarimoApp, preloadPage } from "./core/MarimoApp"; import { type AppMode, initialModeAtom, viewStateAtom } from "./core/mode"; import { cleanupAuthQueryParams } from "./core/network/auth"; import { connectionAtom } from "./core/network/connection"; import { requestClientAtom } from "./core/network/requests"; import { resolveRequestClient } from "./core/network/resolve"; import { DEFAULT_RUNTIME_CONFIG, runtimeConfigAtom, } from "./core/runtime/config"; import { codeAtom, cwdAtom, filenameAtom, lspWorkspaceAtom, } from "./core/saving/file-state"; import { store } from "./core/state/jotai"; import { patchFetch, patchVegaLoader } from "./core/static/files"; import { getStaticModelNotifications, isStaticNotebook, } from "./core/static/static-state"; import { maybeRegisterVSCodeBindings } from "./core/vscode/vscode-bindings"; import type { FileStore } from "./core/wasm/store"; import { notebookFileStore } from "./core/wasm/store"; import { WebSocketState } from "./core/websocket/types"; import { handleWidgetMessage, MODEL_MANAGER, } from "./plugins/impl/anywidget/model"; import { vegaLoader } from "./plugins/impl/vega/loader"; import { initializePlugins } from "./plugins/plugins"; import { ThemeProvider } from "./theme/ThemeProvider"; import { reportVitals } from "./utils/vitals"; let hasMounted = false; /** * Main entry point for the marimo app. * * Sets up the marimo app with a theme provider. */ export function mount(options: unknown, el: Element): Error | undefined { if (hasMounted) { Logger.warn("marimo app has already been mounted."); return new Error("marimo app has already been mounted."); } hasMounted = true; const root = createRoot(el); try { // Init side-effects maybeRegisterVSCodeBindings(); initializePlugins(); cleanupAuthQueryParams(); // Patches if (isStaticNotebook()) { // If we're in static mode, we need to patch fetch to use the virtual file patchFetch(); patchVegaLoader(vegaLoader); hydrateStaticModels(); } // Init store initStore(options); root.render( , ); } catch (error) { // Most likely, configuration failed to parse. const Throw = () => { throw error; }; root.render( , ); return error as Error; } finally { reportVitals(); } } const passthroughObject = z .looseObject({}) .nullish() .default({}) // Default to empty object .transform((val) => { if (val) { return val; } if (typeof val === "string") { Logger.warn( "[marimo] received JSON string instead of object. Parsing...", ); return JSON.parse(val); } Logger.warn("[marimo] missing config data"); return {}; }); // This should be extremely backwards compatible and require no options const mountOptionsSchema = z.object({ /** * filename of the notebook to open */ filename: z .string() .nullish() .transform((val) => { if (val) { return val; } Logger.warn("No filename provided, using fallback"); return getFilenameFromDOM(); }), /** * absolute working directory of the notebook */ cwd: z.string().nullish().default(null), /** * LSP workspace information */ lspWorkspace: z .object({ rootUri: z.string(), documentUri: z.string(), }) .nullish() .default(null), /** * notebook code */ code: z .string() .nullish() .transform((val) => val ?? getMarimoCode() ?? ""), /** * marimo version */ version: z .string() .nullish() .transform((val) => val ?? "unknown"), /** * 'edit' or 'read'/'run' or 'home' or 'gallery' */ mode: z .enum(["edit", "read", "home", "run", "gallery"]) .transform((val): AppMode => { if (val === "run") { return "read"; } return val; }), /** * marimo config */ config: passthroughObject, /** * marimo config overrides */ configOverrides: passthroughObject, /** * marimo app config */ appConfig: passthroughObject, /** * show code in run mode */ view: z .object({ showAppCode: z.boolean().default(true), }) .nullish() .transform((val) => val ?? { showAppCode: true }), /** * server token */ serverToken: z .string() .nullish() .transform((val) => val ?? ""), /** * File stores for persistence */ fileStores: z.array(z.custom()).optional(), /** * Serialized Session["NotebookSessionV1"] snapshot */ session: z.union([ z.null().optional(), z .looseObject({ // Rough shape, we don't need to validate the full schema version: z.literal("1"), metadata: z.any(), cells: z.array(z.any()), }) .transform((val) => val as api.Session["NotebookSessionV1"]), ]), /** * Serialized Notebook["NotebookV1"] snapshot */ notebook: z.union([ z.null().optional(), z .looseObject({ // Rough shape, we don't need to validate the full schema version: z.literal("1"), metadata: z.any(), cells: z.array(z.any()), }) .transform((val) => val as api.Notebook["NotebookV1"]), ]), /** * Runtime configs */ runtimeConfig: z .array( z.looseObject({ url: z.string(), // Lazy by default, but can be overridden by the runtime config lazy: z.boolean().default(true), authToken: z.string().nullish(), }), ) .nullish() .transform((val) => val ?? []), }); function initStore(options: unknown) { const parsedOptions = mountOptionsSchema.safeParse(options); if (!parsedOptions.success) { Logger.error("Invalid marimo mount options", parsedOptions.error); throw new Error("Invalid marimo mount options"); } const mode = parsedOptions.data.mode; preloadPage(mode); // Initialize file stores if provided if ( parsedOptions.data.fileStores && parsedOptions.data.fileStores.length > 0 ) { Logger.log("🗄️ Initializing file stores via mount..."); // Insert file stores at the beginning (highest priority) // Insert in reverse order so first in array gets highest priority for (let i = parsedOptions.data.fileStores.length - 1; i >= 0; i--) { notebookFileStore.insert(0, parsedOptions.data.fileStores[i]); } Logger.log( `🗄️ Injected ${parsedOptions.data.fileStores.length} file store(s) into notebookFileStore`, ); } // Configure networking layer store.set(requestClientAtom, resolveRequestClient()); // Files store.set(filenameAtom, parsedOptions.data.filename); store.set(cwdAtom, parsedOptions.data.cwd ?? null); store.set(lspWorkspaceAtom, parsedOptions.data.lspWorkspace); store.set(codeAtom, parsedOptions.data.code); store.set(initialModeAtom, mode); // Meta store.set(marimoVersionAtom, parsedOptions.data.version); store.set(showCodeInRunModeAtom, parsedOptions.data.view.showAppCode); // Check for view-as parameter to start in present mode const shouldStartInPresentMode = (() => { const url = new URL(window.location.href); return url.searchParams.get(KnownQueryParams.viewAs) === "present"; })(); const initialViewMode = mode === "edit" && shouldStartInPresentMode ? "present" : mode; store.set(viewStateAtom, { mode: initialViewMode, cellAnchor: null }); store.set(serverTokenAtom, parsedOptions.data.serverToken); // Config store.set( configOverridesAtom, parseConfigOverrides(parsedOptions.data.configOverrides), ); store.set(userConfigAtom, parseUserConfig(parsedOptions.data.config)); store.set(appConfigAtom, parseAppConfig(parsedOptions.data.appConfig)); // Runtime config if (parsedOptions.data.runtimeConfig.length > 0) { const firstRuntimeConfig = parsedOptions.data.runtimeConfig[0]; Logger.debug("⚡ Runtime URL", firstRuntimeConfig.url); store.set(runtimeConfigAtom, { ...firstRuntimeConfig, serverToken: parsedOptions.data.serverToken, }); // If the remote runtime is not lazy, start it in CONNECTING if (!firstRuntimeConfig.lazy && !isStaticNotebook()) { store.set(connectionAtom, { state: WebSocketState.CONNECTING }); } } else { store.set(runtimeConfigAtom, { ...DEFAULT_RUNTIME_CONFIG, lazy: false, serverToken: parsedOptions.data.serverToken, }); } // Session/notebook const notebook = notebookStateFromSession( parsedOptions.data.session, parsedOptions.data.notebook, ); if (notebook) { store.set(notebookAtom, notebook); } } /** * Hydrate anywidget models from embedded static state so widgets * render immediately without a kernel connection. */ function hydrateStaticModels(): void { const notifications = getStaticModelNotifications(); if (!notifications) { return; } for (const notification of notifications) { handleWidgetMessage(MODEL_MANAGER, notification); } } export const visibleForTesting = { reset: () => { hasMounted = false; }, };