From 55bf19e01390d211b99fbbf6a2b5beb8ac7b6417 Mon Sep 17 00:00:00 2001 From: ken <3939605+kenyiu@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:34:10 +0100 Subject: [PATCH 1/6] feat: bootstrap PostHog from parent page identity in OSO embed When marimo is embedded as a cross-origin iframe, read posthogDistinctId and posthogSessionId from the URL fragment and pass them as bootstrap options to posthog.init(). This stitches iframe events to the parent page's distinct_id without emitting a spurious $identify event. --- frontend/src/oso.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/oso.tsx b/frontend/src/oso.tsx index 842dcb01af3..171d4af3955 100644 --- a/frontend/src/oso.tsx +++ b/frontend/src/oso.tsx @@ -11,11 +11,18 @@ declare global { } if (import.meta.env.VITE_POSTHOG_PUBLIC_API_KEY) { + // Read identity passed from the parent page via URL fragment to stitch events + // under the same distinct_id without emitting a spurious $identify event. + const fragment = new URLSearchParams(window.location.hash.slice(1)); + const distinctID = fragment.get("posthogDistinctId") ?? undefined; + const sessionID = fragment.get("posthogSessionId") ?? undefined; + posthog.init(import.meta.env.VITE_POSTHOG_PUBLIC_API_KEY, { + bootstrap: distinctID ? { distinctID, sessionID, isIdentifiedID: true } : undefined, session_recording: { // WARNING: Only enable this if you understand the security implications recordCrossOriginIframes: true, - } + }, }); } else { console.warn("POSTHOG_PUBLIC_API_KEY not set, skipping posthog init"); From a56d9f66abeae5a50882af7f8a2c6647da684d91 Mon Sep 17 00:00:00 2001 From: ken <3939605+kenyiu@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:35:13 +0100 Subject: [PATCH 2/6] fix: remove unused imports, fix biome suppression, fix typos --- frontend/src/core/edit-app.tsx | 2 -- frontend/src/oso-extensions/notebook-rpc.tsx | 2 +- frontend/src/oso-extensions/wasm/controller.tsx | 2 +- frontend/src/utils/assertNever.ts | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/core/edit-app.tsx b/frontend/src/core/edit-app.tsx index 9a1275bcb90..85a5870d4e6 100644 --- a/frontend/src/core/edit-app.tsx +++ b/frontend/src/core/edit-app.tsx @@ -6,7 +6,6 @@ import { useAtomValue, useSetAtom } from "jotai"; import { useEffect } from "react"; import { Controls } from "@/components/editor/controls/Controls"; import { AppHeader } from "@/components/editor/header/app-header"; -import { FilenameForm } from "@/components/editor/header/filename-form"; import { MultiCellActionToolbar } from "@/components/editor/navigation/multi-cell-action-toolbar"; import { cn } from "@/utils/cn"; import { Paths } from "@/utils/paths"; @@ -68,7 +67,6 @@ export const EditApp: React.FC = ({ const setLastSavedNotebook = useSetAtom(lastSavedNotebookAtom); const { sendComponentValues, sendInterrupt } = useRequestClient(); - const isEditing = viewState.mode === "edit"; const isPresenting = viewState.mode === "present"; const isRunning = useAtomValue(notebookIsRunningAtom); diff --git a/frontend/src/oso-extensions/notebook-rpc.tsx b/frontend/src/oso-extensions/notebook-rpc.tsx index 13c61aec9d8..801dbabe255 100644 --- a/frontend/src/oso-extensions/notebook-rpc.tsx +++ b/frontend/src/oso-extensions/notebook-rpc.tsx @@ -93,7 +93,7 @@ export class NotebookRpc extends RpcTarget implements NotebookRpcServer { this.hostControls = stub; this.fsReadyResolve(); - // Send postMesage to the parent to establish the connection + // Send postMessage to the parent to establish the connection const init: InitializationCommand = { command: "initialize", id: reqCommand.id, diff --git a/frontend/src/oso-extensions/wasm/controller.tsx b/frontend/src/oso-extensions/wasm/controller.tsx index 74fc2cd26d0..004c1535a67 100644 --- a/frontend/src/oso-extensions/wasm/controller.tsx +++ b/frontend/src/oso-extensions/wasm/controller.tsx @@ -40,7 +40,7 @@ export class DefaultWasmController implements WasmController { version: string; pyodideVersion: string; }): Promise { - Logger.log(`boostrapping wasm with marimo version ${opts.version} pyodide version ${opts.pyodideVersion}`); + Logger.log(`bootstrapping wasm with marimo version ${opts.version} pyodide version ${opts.pyodideVersion}`); const pyodide = await this.loadPyodideAndPackages(opts); if (MAKE_SNAPSHOT) { diff --git a/frontend/src/utils/assertNever.ts b/frontend/src/utils/assertNever.ts index 009e3f0b79e..b4739da341d 100644 --- a/frontend/src/utils/assertNever.ts +++ b/frontend/src/utils/assertNever.ts @@ -15,6 +15,6 @@ export function assertNever(x: never): never { */ export function logNever(x: never): void { Logger.warn(`Unexpected object: ${JSON.stringify(x)}`); - // biome-ignore lint/correctness/noVoidTypeReturn: + // biome-ignore lint/correctness/noVoidTypeReturn: intentional — returning never satisfies void return x; } From 5d9677a942a8ec38beacbd50897768b1f021c828 Mon Sep 17 00:00:00 2001 From: ken <3939605+kenyiu@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:56:38 +0100 Subject: [PATCH 3/6] fix: suppress eslint errors from no-console and no-empty-function --- frontend/src/components/editor/ai/add-cell-with-ai.tsx | 4 ++-- frontend/src/core/wasm/store.ts | 1 + frontend/src/oso-mount.tsx | 2 ++ frontend/src/oso.tsx | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/editor/ai/add-cell-with-ai.tsx b/frontend/src/components/editor/ai/add-cell-with-ai.tsx index d1f850d91ae..b9e70316052 100644 --- a/frontend/src/components/editor/ai/add-cell-with-ai.tsx +++ b/frontend/src/components/editor/ai/add-cell-with-ai.tsx @@ -259,8 +259,8 @@ export const AddCellWithAI: React.FC<{ }, } })); - return () => { - } + // eslint-disable-next-line @typescript-eslint/no-empty-function + return () => {} }, []); return ( diff --git a/frontend/src/core/wasm/store.ts b/frontend/src/core/wasm/store.ts index ec1cf35b72c..e5fff131043 100644 --- a/frontend/src/core/wasm/store.ts +++ b/frontend/src/core/wasm/store.ts @@ -93,6 +93,7 @@ export class CompositeFileStore implements FileStore { this.stores.splice(index, 0, store); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any getStore(ctor: new (...args: any[]) => T): T | null { const store = this.stores.find((store) => store instanceof ctor) as T; return store ?? null; diff --git a/frontend/src/oso-mount.tsx b/frontend/src/oso-mount.tsx index d7933d165a0..c74a1e529ea 100644 --- a/frontend/src/oso-mount.tsx +++ b/frontend/src/oso-mount.tsx @@ -294,7 +294,9 @@ class FragmentNotebookFileStore implements FileStore { } saveFile(contents: string): void { + // eslint-disable-next-line no-console console.log("Saving file to fragment store"); + // eslint-disable-next-line no-console console.log("Contents:", contents); this.fragmentStore.setCompressedString("code", contents); this.fragmentStore.commit(); diff --git a/frontend/src/oso.tsx b/frontend/src/oso.tsx index 171d4af3955..309daab4d68 100644 --- a/frontend/src/oso.tsx +++ b/frontend/src/oso.tsx @@ -25,6 +25,7 @@ if (import.meta.env.VITE_POSTHOG_PUBLIC_API_KEY) { }, }); } else { + // eslint-disable-next-line no-console console.warn("POSTHOG_PUBLIC_API_KEY not set, skipping posthog init"); } @@ -35,6 +36,7 @@ if (el) { throw new Error("[marimo] mount config not found"); } mount(window.__MARIMO_MOUNT_CONFIG__, el).catch((e) => { + // eslint-disable-next-line no-console console.error("Failed to mount marimo app", e); }); } else { From d45ece1be2936239a8dffb3105ce4f16853160db Mon Sep 17 00:00:00 2001 From: ken <3939605+kenyiu@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:04:45 +0100 Subject: [PATCH 4/6] fix: replace console calls with Logger, fix empty function and any type --- frontend/src/components/editor/ai/add-cell-with-ai.tsx | 2 -- frontend/src/core/wasm/store.ts | 3 +-- frontend/src/oso-mount.tsx | 6 ++---- frontend/src/oso.tsx | 7 +++---- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/editor/ai/add-cell-with-ai.tsx b/frontend/src/components/editor/ai/add-cell-with-ai.tsx index b9e70316052..43f6b0e8f1d 100644 --- a/frontend/src/components/editor/ai/add-cell-with-ai.tsx +++ b/frontend/src/components/editor/ai/add-cell-with-ai.tsx @@ -259,8 +259,6 @@ export const AddCellWithAI: React.FC<{ }, } })); - // eslint-disable-next-line @typescript-eslint/no-empty-function - return () => {} }, []); return ( diff --git a/frontend/src/core/wasm/store.ts b/frontend/src/core/wasm/store.ts index e5fff131043..656bf0ae3e2 100644 --- a/frontend/src/core/wasm/store.ts +++ b/frontend/src/core/wasm/store.ts @@ -93,8 +93,7 @@ export class CompositeFileStore implements FileStore { this.stores.splice(index, 0, store); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getStore(ctor: new (...args: any[]) => T): T | null { + getStore(ctor: new (...args: unknown[]) => T): T | null { const store = this.stores.find((store) => store instanceof ctor) as T; return store ?? null; } diff --git a/frontend/src/oso-mount.tsx b/frontend/src/oso-mount.tsx index c74a1e529ea..bfdcc76682f 100644 --- a/frontend/src/oso-mount.tsx +++ b/frontend/src/oso-mount.tsx @@ -294,10 +294,8 @@ class FragmentNotebookFileStore implements FileStore { } saveFile(contents: string): void { - // eslint-disable-next-line no-console - console.log("Saving file to fragment store"); - // eslint-disable-next-line no-console - console.log("Contents:", contents); + Logger.log("Saving file to fragment store"); + Logger.log("Contents:", contents); this.fragmentStore.setCompressedString("code", contents); this.fragmentStore.commit(); } diff --git a/frontend/src/oso.tsx b/frontend/src/oso.tsx index 309daab4d68..5f4c8502d6a 100644 --- a/frontend/src/oso.tsx +++ b/frontend/src/oso.tsx @@ -2,6 +2,7 @@ import posthog from 'posthog-js' import { mount } from "@/oso-mount"; +import { Logger } from "@/utils/Logger"; declare global { @@ -25,8 +26,7 @@ if (import.meta.env.VITE_POSTHOG_PUBLIC_API_KEY) { }, }); } else { - // eslint-disable-next-line no-console - console.warn("POSTHOG_PUBLIC_API_KEY not set, skipping posthog init"); + Logger.warn("POSTHOG_PUBLIC_API_KEY not set, skipping posthog init"); } // eslint-disable-next-line ssr-friendly/no-dom-globals-in-module-scope @@ -36,8 +36,7 @@ if (el) { throw new Error("[marimo] mount config not found"); } mount(window.__MARIMO_MOUNT_CONFIG__, el).catch((e) => { - // eslint-disable-next-line no-console - console.error("Failed to mount marimo app", e); + Logger.error("Failed to mount marimo app", e); }); } else { throw new Error("[marimo] root element not found"); From 7aea51610e5abf052fcda7100de898f94cf303c8 Mon Sep 17 00:00:00 2001 From: ken <3939605+kenyiu@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:37:21 +0100 Subject: [PATCH 5/6] fix: revert unknown[] to any[] in getStore constructor signature --- frontend/src/core/wasm/store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/core/wasm/store.ts b/frontend/src/core/wasm/store.ts index 656bf0ae3e2..e5fff131043 100644 --- a/frontend/src/core/wasm/store.ts +++ b/frontend/src/core/wasm/store.ts @@ -93,7 +93,8 @@ export class CompositeFileStore implements FileStore { this.stores.splice(index, 0, store); } - getStore(ctor: new (...args: unknown[]) => T): T | null { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getStore(ctor: new (...args: any[]) => T): T | null { const store = this.stores.find((store) => store instanceof ctor) as T; return store ?? null; } From d10455b2a80567a1e257fd8eff7275b6a2179420 Mon Sep 17 00:00:00 2001 From: ken <3939605+kenyiu@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:54:29 +0100 Subject: [PATCH 6/6] revert: undo CI fixes to files not owned by this PR --- frontend/src/components/editor/ai/add-cell-with-ai.tsx | 2 ++ frontend/src/core/edit-app.tsx | 2 ++ frontend/src/core/wasm/store.ts | 1 - frontend/src/oso-extensions/notebook-rpc.tsx | 2 +- frontend/src/oso-extensions/wasm/controller.tsx | 2 +- frontend/src/oso-mount.tsx | 4 ++-- frontend/src/utils/assertNever.ts | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/editor/ai/add-cell-with-ai.tsx b/frontend/src/components/editor/ai/add-cell-with-ai.tsx index 43f6b0e8f1d..d1f850d91ae 100644 --- a/frontend/src/components/editor/ai/add-cell-with-ai.tsx +++ b/frontend/src/components/editor/ai/add-cell-with-ai.tsx @@ -259,6 +259,8 @@ export const AddCellWithAI: React.FC<{ }, } })); + return () => { + } }, []); return ( diff --git a/frontend/src/core/edit-app.tsx b/frontend/src/core/edit-app.tsx index 85a5870d4e6..9a1275bcb90 100644 --- a/frontend/src/core/edit-app.tsx +++ b/frontend/src/core/edit-app.tsx @@ -6,6 +6,7 @@ import { useAtomValue, useSetAtom } from "jotai"; import { useEffect } from "react"; import { Controls } from "@/components/editor/controls/Controls"; import { AppHeader } from "@/components/editor/header/app-header"; +import { FilenameForm } from "@/components/editor/header/filename-form"; import { MultiCellActionToolbar } from "@/components/editor/navigation/multi-cell-action-toolbar"; import { cn } from "@/utils/cn"; import { Paths } from "@/utils/paths"; @@ -67,6 +68,7 @@ export const EditApp: React.FC = ({ const setLastSavedNotebook = useSetAtom(lastSavedNotebookAtom); const { sendComponentValues, sendInterrupt } = useRequestClient(); + const isEditing = viewState.mode === "edit"; const isPresenting = viewState.mode === "present"; const isRunning = useAtomValue(notebookIsRunningAtom); diff --git a/frontend/src/core/wasm/store.ts b/frontend/src/core/wasm/store.ts index e5fff131043..ec1cf35b72c 100644 --- a/frontend/src/core/wasm/store.ts +++ b/frontend/src/core/wasm/store.ts @@ -93,7 +93,6 @@ export class CompositeFileStore implements FileStore { this.stores.splice(index, 0, store); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any getStore(ctor: new (...args: any[]) => T): T | null { const store = this.stores.find((store) => store instanceof ctor) as T; return store ?? null; diff --git a/frontend/src/oso-extensions/notebook-rpc.tsx b/frontend/src/oso-extensions/notebook-rpc.tsx index 801dbabe255..13c61aec9d8 100644 --- a/frontend/src/oso-extensions/notebook-rpc.tsx +++ b/frontend/src/oso-extensions/notebook-rpc.tsx @@ -93,7 +93,7 @@ export class NotebookRpc extends RpcTarget implements NotebookRpcServer { this.hostControls = stub; this.fsReadyResolve(); - // Send postMessage to the parent to establish the connection + // Send postMesage to the parent to establish the connection const init: InitializationCommand = { command: "initialize", id: reqCommand.id, diff --git a/frontend/src/oso-extensions/wasm/controller.tsx b/frontend/src/oso-extensions/wasm/controller.tsx index 004c1535a67..74fc2cd26d0 100644 --- a/frontend/src/oso-extensions/wasm/controller.tsx +++ b/frontend/src/oso-extensions/wasm/controller.tsx @@ -40,7 +40,7 @@ export class DefaultWasmController implements WasmController { version: string; pyodideVersion: string; }): Promise { - Logger.log(`bootstrapping wasm with marimo version ${opts.version} pyodide version ${opts.pyodideVersion}`); + Logger.log(`boostrapping wasm with marimo version ${opts.version} pyodide version ${opts.pyodideVersion}`); const pyodide = await this.loadPyodideAndPackages(opts); if (MAKE_SNAPSHOT) { diff --git a/frontend/src/oso-mount.tsx b/frontend/src/oso-mount.tsx index bfdcc76682f..d7933d165a0 100644 --- a/frontend/src/oso-mount.tsx +++ b/frontend/src/oso-mount.tsx @@ -294,8 +294,8 @@ class FragmentNotebookFileStore implements FileStore { } saveFile(contents: string): void { - Logger.log("Saving file to fragment store"); - Logger.log("Contents:", contents); + console.log("Saving file to fragment store"); + console.log("Contents:", contents); this.fragmentStore.setCompressedString("code", contents); this.fragmentStore.commit(); } diff --git a/frontend/src/utils/assertNever.ts b/frontend/src/utils/assertNever.ts index b4739da341d..009e3f0b79e 100644 --- a/frontend/src/utils/assertNever.ts +++ b/frontend/src/utils/assertNever.ts @@ -15,6 +15,6 @@ export function assertNever(x: never): never { */ export function logNever(x: never): void { Logger.warn(`Unexpected object: ${JSON.stringify(x)}`); - // biome-ignore lint/correctness/noVoidTypeReturn: intentional — returning never satisfies void + // biome-ignore lint/correctness/noVoidTypeReturn: return x; }