@marimo-team/frontend 0.23.1-dev9 → 0.23.2-dev25

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 (68) hide show
  1. package/dist/assets/{JsonOutput-BY31ccA7.js → JsonOutput-CavtrueA.js} +1 -1
  2. package/dist/assets/{MarimoErrorOutput--Yd2Aw0J.js → MarimoErrorOutput-Bmp8DLLo.js} +1 -1
  3. package/dist/assets/RenderHTML-CM3WMmA8.js +1 -0
  4. package/dist/assets/{add-connection-dialog-CjvNOKgb.js → add-connection-dialog-BGZvJkor.js} +1 -1
  5. package/dist/assets/{agent-panel-C24uwabG.js → agent-panel-BvL9Lu9c.js} +1 -1
  6. package/dist/assets/{cell-editor-zW0u82sK.js → cell-editor-B40o_zx_.js} +1 -1
  7. package/dist/assets/{chat-display-DsHMZa9F.js → chat-display-M_nvYuHH.js} +1 -1
  8. package/dist/assets/{chat-panel-o9D3upnX.js → chat-panel-BMOW93uQ.js} +1 -1
  9. package/dist/assets/{chat-ui-BYS03y86.js → chat-ui-DyeimpVh.js} +1 -1
  10. package/dist/assets/{column-preview-Dwv5a_zE.js → column-preview-AfcgbFG_.js} +1 -1
  11. package/dist/assets/{command-palette-BYbKGSF3.js → command-palette-BgvdyU3B.js} +1 -1
  12. package/dist/assets/{documentation-panel-CA2pWMgB.js → documentation-panel-DUPcsi8P.js} +1 -1
  13. package/dist/assets/{edit-page-CMUN3ESy.js → edit-page-DD4uEDmX.js} +4 -4
  14. package/dist/assets/{error-panel-CbqfK1HJ.js → error-panel-DQOeSv5-.js} +1 -1
  15. package/dist/assets/{file-explorer-panel-CbS8z-JR.js → file-explorer-panel-B67zjs2X.js} +1 -1
  16. package/dist/assets/{form-DLyXacSF.js → form-BJ6VFU8l.js} +1 -1
  17. package/dist/assets/{hooks-kZJc1iBf.js → hooks-DvwShzDb.js} +1 -1
  18. package/dist/assets/index-y6osgSWB.js +42 -0
  19. package/dist/assets/{layout-tmN-U1zs.js → layout-erv8pLIP.js} +1 -1
  20. package/dist/assets/{panels-CLfdzLPR.js → panels-1u-RE72f.js} +1 -1
  21. package/dist/assets/{run-page-DPuH6QY4.js → run-page-DfWH_1mz.js} +1 -1
  22. package/dist/assets/{scratchpad-panel-BsMm0GQP.js → scratchpad-panel-CnaiXtoJ.js} +1 -1
  23. package/dist/assets/{session-panel-CTDzGShO.js → session-panel-C68GBFwH.js} +1 -1
  24. package/dist/assets/{snippets-panel-CWof0wHk.js → snippets-panel-BmIdR0lc.js} +1 -1
  25. package/dist/assets/state-D1n-olwf.js +3 -0
  26. package/dist/assets/{useNotebookActions-DHBEqrc_.js → useNotebookActions-Ch1o32Jw.js} +1 -1
  27. package/dist/index.html +7 -7
  28. package/package.json +4 -4
  29. package/src/core/islands/__tests__/bridge.test.ts +2 -12
  30. package/src/core/islands/__tests__/islands-harness.test.ts +348 -0
  31. package/src/core/islands/__tests__/parse.test.ts +466 -24
  32. package/src/core/islands/__tests__/test-utils.tsx +263 -0
  33. package/src/core/islands/bootstrap.ts +265 -0
  34. package/src/core/islands/bridge.ts +154 -75
  35. package/src/core/islands/components/IslandControls.tsx +103 -0
  36. package/src/core/islands/components/__tests__/IslandControls.test.tsx +185 -0
  37. package/src/core/islands/components/__tests__/useIslandControls.test.ts +208 -0
  38. package/src/core/islands/components/output-wrapper.tsx +76 -93
  39. package/src/core/islands/components/useIslandControls.ts +60 -0
  40. package/src/core/islands/components/web-components.tsx +168 -40
  41. package/src/core/islands/constants.ts +28 -0
  42. package/src/core/islands/main.ts +7 -205
  43. package/src/core/islands/parse.ts +73 -26
  44. package/src/core/islands/worker-factory.ts +86 -0
  45. package/src/plugins/core/RenderHTML.tsx +9 -0
  46. package/src/plugins/core/__test__/RenderHTML.test.ts +27 -0
  47. package/src/plugins/core/__test__/trusted-url.test.ts +48 -0
  48. package/src/plugins/core/registerReactComponent.tsx +11 -8
  49. package/src/plugins/core/trusted-url.ts +20 -0
  50. package/src/plugins/impl/ButtonPlugin.tsx +4 -6
  51. package/src/plugins/impl/CodeEditorPlugin.tsx +15 -18
  52. package/src/plugins/impl/DataEditorPlugin.tsx +8 -14
  53. package/src/plugins/impl/DataTablePlugin.tsx +8 -9
  54. package/src/plugins/impl/FileUploadPlugin.tsx +39 -43
  55. package/src/plugins/impl/FormPlugin.tsx +2 -6
  56. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +27 -1
  57. package/src/plugins/impl/anywidget/widget-binding.ts +13 -0
  58. package/src/plugins/impl/chat/ChatPlugin.tsx +17 -20
  59. package/src/plugins/impl/data-explorer/DataExplorerPlugin.tsx +5 -8
  60. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +21 -0
  61. package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +119 -0
  62. package/src/plugins/impl/panel/PanelPlugin.tsx +31 -10
  63. package/src/plugins/impl/panel/__tests__/PanelPlugin.test.ts +60 -0
  64. package/src/plugins/impl/vega/VegaPlugin.tsx +5 -8
  65. package/src/plugins/layout/NavigationMenuPlugin.tsx +2 -6
  66. package/dist/assets/RenderHTML-CbuarQqA.js +0 -1
  67. package/dist/assets/index-Bm25ctN7.js +0 -42
  68. package/dist/assets/state-BvnlMKdT.js +0 -3
@@ -0,0 +1,263 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { type RenderOptions, render } from "@testing-library/react";
4
+ import { createStore, Provider } from "jotai";
5
+ import type { ReactElement } from "react";
6
+ import {
7
+ ISLAND_DATA_ATTRIBUTES,
8
+ ISLAND_TAG_NAMES,
9
+ } from "@/core/islands/constants";
10
+ import type { WorkerFactory } from "@/core/islands/worker-factory";
11
+
12
+ /**
13
+ * Test utilities for islands components and logic
14
+ */
15
+
16
+ // ============================================================================
17
+ // DOM Test Utilities
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Creates a mock marimo-island element in the DOM
22
+ */
23
+ export function createMockIslandElement(options: {
24
+ appId?: string;
25
+ cellIdx?: string;
26
+ code?: string;
27
+ innerHTML?: string;
28
+ }): HTMLElement {
29
+ const {
30
+ appId = "test-app",
31
+ cellIdx = "0",
32
+ code = "import marimo as mo",
33
+ innerHTML = "",
34
+ } = options;
35
+
36
+ const element = document.createElement(ISLAND_TAG_NAMES.ISLAND);
37
+ element.setAttribute(ISLAND_DATA_ATTRIBUTES.APP_ID, appId);
38
+ element.setAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX, cellIdx);
39
+
40
+ if (code) {
41
+ const codeElement = document.createElement(ISLAND_TAG_NAMES.CELL_CODE);
42
+ codeElement.textContent = encodeURIComponent(code);
43
+ element.appendChild(codeElement);
44
+ }
45
+
46
+ if (innerHTML) {
47
+ const outputElement = document.createElement(ISLAND_TAG_NAMES.CELL_OUTPUT);
48
+ outputElement.innerHTML = innerHTML;
49
+ element.appendChild(outputElement);
50
+ }
51
+
52
+ return element;
53
+ }
54
+
55
+ /**
56
+ * Creates multiple island elements for testing
57
+ */
58
+ export function createMockIslands(
59
+ count: number,
60
+ appId = "test-app",
61
+ ): HTMLElement[] {
62
+ return Array.from({ length: count }, (_, idx) =>
63
+ createMockIslandElement({
64
+ appId,
65
+ cellIdx: String(idx),
66
+ code: `cell_${idx} = ${idx}`,
67
+ innerHTML: `<div>output ${idx}</div>`,
68
+ }),
69
+ );
70
+ }
71
+
72
+ // ============================================================================
73
+ // React Test Utilities
74
+ // ============================================================================
75
+
76
+ interface IslandsRenderOptions extends Omit<RenderOptions, "wrapper"> {
77
+ initialStore?: ReturnType<typeof createStore>;
78
+ }
79
+
80
+ /**
81
+ * Renders a React component with Islands providers
82
+ */
83
+ export function renderWithIslandsProviders(
84
+ ui: ReactElement,
85
+ options?: IslandsRenderOptions,
86
+ ) {
87
+ const { initialStore, ...renderOptions } = options || {};
88
+ const store = initialStore || createStore();
89
+
90
+ function Wrapper({ children }: { children: React.ReactNode }) {
91
+ return <Provider store={store}>{children}</Provider>;
92
+ }
93
+
94
+ return {
95
+ store,
96
+ ...render(ui, { wrapper: Wrapper, ...renderOptions }),
97
+ };
98
+ }
99
+
100
+ // ============================================================================
101
+ // Assertion Helpers
102
+ // ============================================================================
103
+
104
+ /**
105
+ * Waits for a condition to be true with timeout
106
+ */
107
+ export async function waitForCondition(
108
+ condition: () => boolean,
109
+ timeout = 1000,
110
+ interval = 50,
111
+ ): Promise<void> {
112
+ const startTime = Date.now();
113
+ while (!condition()) {
114
+ if (Date.now() - startTime > timeout) {
115
+ throw new Error("Timeout waiting for condition");
116
+ }
117
+ await new Promise((resolve) => setTimeout(resolve, interval));
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Waits for an async function to not throw
123
+ */
124
+ export async function waitForNoError<T>(
125
+ fn: () => T | Promise<T>,
126
+ timeout = 1000,
127
+ ): Promise<T> {
128
+ const startTime = Date.now();
129
+ let lastError: Error | undefined;
130
+
131
+ while (Date.now() - startTime < timeout) {
132
+ try {
133
+ return await Promise.resolve(fn());
134
+ } catch (error) {
135
+ lastError = error as Error;
136
+ await new Promise((resolve) => setTimeout(resolve, 50));
137
+ }
138
+ }
139
+
140
+ throw lastError || new Error("Timeout waiting for function to succeed");
141
+ }
142
+
143
+ // ============================================================================
144
+ // HTML-based Island Harness
145
+ // ============================================================================
146
+
147
+ export interface IslandSpec {
148
+ appId?: string;
149
+ reactive?: boolean;
150
+ code?: string;
151
+ output?: string;
152
+ displayCode?: boolean;
153
+ }
154
+
155
+ /**
156
+ * Builds island HTML from a declarative spec — mirrors what generate.py produces.
157
+ * Use with `createIslandHarness()` to parse and inspect the resulting state.
158
+ */
159
+ export function buildIslandHTML(islands: IslandSpec[]): string {
160
+ return islands
161
+ .map((spec) => {
162
+ const appId = spec.appId ?? "test-app";
163
+ const reactive = spec.reactive ?? true;
164
+ const output = spec.output ?? "<div>output</div>";
165
+ const code = spec.code ?? 'print("hello")';
166
+
167
+ const codeTag = code
168
+ ? `<${ISLAND_TAG_NAMES.CELL_CODE}>${encodeURIComponent(code)}</${ISLAND_TAG_NAMES.CELL_CODE}>`
169
+ : "";
170
+ const outputTag = `<${ISLAND_TAG_NAMES.CELL_OUTPUT}>${output}</${ISLAND_TAG_NAMES.CELL_OUTPUT}>`;
171
+
172
+ return `<${ISLAND_TAG_NAMES.ISLAND} ${ISLAND_DATA_ATTRIBUTES.APP_ID}="${appId}" ${ISLAND_DATA_ATTRIBUTES.REACTIVE}="${reactive}">${outputTag}${codeTag}</${ISLAND_TAG_NAMES.ISLAND}>`;
173
+ })
174
+ .join("\n");
175
+ }
176
+
177
+ export interface IslandHarness {
178
+ /** The container element holding all islands */
179
+ container: HTMLElement;
180
+ /** All island elements found in the container */
181
+ islands: HTMLElement[];
182
+ /** Cleanup — removes container from DOM */
183
+ cleanup: () => void;
184
+ }
185
+
186
+ /**
187
+ * Creates a test harness from raw island HTML.
188
+ *
189
+ * Parses the HTML into real DOM elements attached to `document.body` so that
190
+ * `querySelectorAll`, `getAttribute`, etc. work correctly.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * const harness = createIslandHarness(buildIslandHTML([
195
+ * { reactive: true, code: 'x = 1', output: '<div>1</div>' },
196
+ * { reactive: false, output: '<div>static</div>' },
197
+ * ]));
198
+ * // ... assertions ...
199
+ * harness.cleanup();
200
+ * ```
201
+ */
202
+ export function createIslandHarness(html: string): IslandHarness {
203
+ const container = document.createElement("div");
204
+ container.innerHTML = html;
205
+ document.body.appendChild(container);
206
+
207
+ // eslint-disable-next-line unicorn/prefer-spread
208
+ const islands = Array.from(
209
+ container.querySelectorAll<HTMLElement>(ISLAND_TAG_NAMES.ISLAND),
210
+ );
211
+
212
+ return {
213
+ container,
214
+ islands,
215
+ cleanup: () => container.remove(),
216
+ };
217
+ }
218
+
219
+ // ============================================================================
220
+ // Mock Worker Factory
221
+ // ============================================================================
222
+
223
+ /**
224
+ * Mock worker factory for testing
225
+ */
226
+ export class MockWorkerFactory implements WorkerFactory {
227
+ public workers: Worker[] = [];
228
+ private readonly mockWorker?: Worker;
229
+
230
+ constructor(mockWorker?: Worker) {
231
+ this.mockWorker = mockWorker;
232
+ }
233
+
234
+ create(): Worker {
235
+ const worker = this.mockWorker || this.createMockWorker();
236
+ this.workers.push(worker);
237
+ return worker;
238
+ }
239
+
240
+ private createMockWorker(): Worker {
241
+ return {
242
+ postMessage: () => {},
243
+ terminate: () => {},
244
+ addEventListener: () => {},
245
+ removeEventListener: () => {},
246
+ dispatchEvent: () => true,
247
+ onmessage: null,
248
+ onerror: null,
249
+ onmessageerror: null,
250
+ } as unknown as Worker;
251
+ }
252
+
253
+ getCreatedWorkers(): Worker[] {
254
+ return this.workers;
255
+ }
256
+
257
+ terminateAll(): void {
258
+ for (const worker of this.workers) {
259
+ worker.terminate();
260
+ }
261
+ this.workers = [];
262
+ }
263
+ }
@@ -0,0 +1,265 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { toast } from "@/components/ui/use-toast";
4
+ import { ISLAND_CSS_CLASSES, ISLAND_TAG_NAMES } from "@/core/islands/constants";
5
+ import { renderHTML } from "@/plugins/core/RenderHTML";
6
+ import {
7
+ handleWidgetMessage,
8
+ MODEL_MANAGER,
9
+ } from "@/plugins/impl/anywidget/model";
10
+ import { initializePlugins } from "@/plugins/plugins";
11
+ import { Functions } from "@/utils/functions";
12
+ import {
13
+ safeExtractSetUIElementMessageBuffers,
14
+ type JsonString,
15
+ } from "@/utils/json/base64";
16
+ import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
17
+ import { Logger } from "@/utils/Logger";
18
+ import { logNever } from "@/utils/assertNever";
19
+ import {
20
+ createNotebookActions,
21
+ notebookAtom,
22
+ notebookReducer,
23
+ } from "../cells/cells";
24
+ import { defineCustomElement } from "../dom/defineCustomElement";
25
+ import { MarimoValueInputEvent } from "../dom/events";
26
+ import { UI_ELEMENT_REGISTRY } from "../dom/uiregistry";
27
+ import { FUNCTIONS_REGISTRY } from "../functions/FunctionRegistry";
28
+ import {
29
+ handleCellNotificationeration,
30
+ handleKernelReady,
31
+ handleRemoveUIElements,
32
+ } from "../kernel/handlers";
33
+ import type {
34
+ NotificationMessage,
35
+ NotificationMessageType,
36
+ } from "../kernel/messages";
37
+ import { queryParamHandlers } from "../kernel/queryParamHandlers";
38
+ import { RuntimeState } from "../kernel/RuntimeState";
39
+ import { initialModeAtom } from "../mode";
40
+ import { requestClientAtom } from "../network/requests";
41
+ import { store as defaultStore } from "../state/jotai";
42
+ import type { IslandsPyodideBridge } from "./bridge";
43
+ import { MarimoIslandElement } from "./components/web-components";
44
+ import {
45
+ shouldShowIslandsWarningIndicatorAtom,
46
+ userTriedToInteractWithIslandsAtom,
47
+ } from "./state";
48
+ import { dismissIslandsLoadingToast, toastIslandsLoading } from "./toast";
49
+
50
+ type Store = typeof defaultStore;
51
+ type NotebookActions = ReturnType<typeof createNotebookActions>;
52
+
53
+ /**
54
+ * Configuration for the islands bootstrap process
55
+ */
56
+ export interface IslandsBootstrapConfig {
57
+ bridge: IslandsPyodideBridge;
58
+ store?: Store;
59
+ root?: Document | Element;
60
+ autoInitializePlugins?: boolean;
61
+ }
62
+
63
+ /**
64
+ * Initialize marimo islands: sets up networking, discovers island elements,
65
+ * wires up the message consumer, and starts the runtime.
66
+ */
67
+ export async function initializeIslands(
68
+ config: IslandsBootstrapConfig,
69
+ ): Promise<void> {
70
+ const store = config.store || defaultStore;
71
+ const root = config.root || document;
72
+ const bridge = config.bridge;
73
+
74
+ // Setup networking
75
+ store.set(requestClientAtom, bridge);
76
+ store.set(initialModeAtom, "read");
77
+
78
+ // Initialize plugins for rendering static HTML
79
+ if (config.autoInitializePlugins !== false) {
80
+ initializePlugins();
81
+ }
82
+
83
+ // Find all island elements
84
+ // eslint-disable-next-line prefer-spread
85
+ const islands = Array.from(
86
+ root.querySelectorAll<HTMLElement>(ISLAND_TAG_NAMES.ISLAND),
87
+ );
88
+
89
+ if (islands.length === 0) {
90
+ Logger.log("No islands found, skipping initialization");
91
+ return;
92
+ }
93
+
94
+ Logger.log(`Initializing ${islands.length} island(s)`);
95
+
96
+ // Apply styles
97
+ for (const island of islands) {
98
+ island.classList.add(ISLAND_CSS_CLASSES.NAMESPACE);
99
+ }
100
+
101
+ // Setup notebook actions
102
+ const actions = createNotebookActions((action) => {
103
+ store.set(notebookAtom, (state: typeof notebookAtom.init) =>
104
+ notebookReducer(state, action),
105
+ );
106
+ });
107
+
108
+ // Loading indicator: dim islands while Pyodide initializes
109
+ store.sub(shouldShowIslandsWarningIndicatorAtom, () => {
110
+ const showing = store.get(shouldShowIslandsWarningIndicatorAtom);
111
+ if (showing) {
112
+ toastIslandsLoading();
113
+ for (const island of islands) {
114
+ island.style.setProperty("opacity", "0.5");
115
+ }
116
+ } else {
117
+ dismissIslandsLoadingToast();
118
+ for (const island of islands) {
119
+ island.style.removeProperty("opacity");
120
+ }
121
+ }
122
+ });
123
+
124
+ // Wire up kernel message handling
125
+ // The wire format is {"op": "...", "data": {"op": "...", ...}} — both
126
+ // the envelope and the payload carry the op. The bridge types the message
127
+ // as NotificationPayload (just {data}), but the actual wire format
128
+ // includes the outer op too.
129
+ bridge.consumeMessages((message) => {
130
+ handleMessage(
131
+ message as unknown as JsonString<IslandsNotificationMessage>,
132
+ actions,
133
+ );
134
+ });
135
+
136
+ // Track first user interaction before initialization completes
137
+ document.addEventListener(
138
+ MarimoValueInputEvent.TYPE,
139
+ () => {
140
+ store.set(userTriedToInteractWithIslandsAtom, true);
141
+ },
142
+ { once: true },
143
+ );
144
+
145
+ // Start the runtime
146
+ RuntimeState.INSTANCE.start(bridge.sendComponentValues);
147
+ }
148
+
149
+ type IslandsNotificationMessage = {
150
+ [K in NotificationMessageType]: {
151
+ data: Extract<NotificationMessage, { op: K }>;
152
+ op: K;
153
+ };
154
+ }[NotificationMessageType];
155
+
156
+ /**
157
+ * Handles a single message from the kernel.
158
+ *
159
+ * Wire format from Python: {"op": "<name>", "data": {"op": "<name>", ...}}
160
+ */
161
+ function handleMessage(
162
+ message: JsonString<IslandsNotificationMessage>,
163
+ actions: NotebookActions,
164
+ ): void {
165
+ try {
166
+ const msg = jsonParseWithSpecialChar(message);
167
+ Logger.debug("Islands received message:", msg.op);
168
+
169
+ switch (msg.op) {
170
+ // Unsupported operations in islands mode
171
+ case "banner":
172
+ case "missing-package-alert":
173
+ case "installing-package-alert":
174
+ case "completion-result":
175
+ case "reload":
176
+ case "focus-cell":
177
+ case "variables":
178
+ case "variable-values":
179
+ case "data-column-preview":
180
+ case "sql-table-preview":
181
+ case "sql-table-list-preview":
182
+ case "sql-schema-list-preview":
183
+ case "datasets":
184
+ case "data-source-connections":
185
+ case "validate-sql-result":
186
+ case "storage-namespaces":
187
+ case "storage-entries":
188
+ case "storage-download-ready":
189
+ case "secret-keys-result":
190
+ case "startup-logs":
191
+ case "completed-run":
192
+ case "interrupted":
193
+ case "reconnected":
194
+ case "cache-cleared":
195
+ case "cache-info":
196
+ case "kernel-startup-error":
197
+ case "notebook-document-transaction":
198
+ return;
199
+
200
+ case "kernel-ready":
201
+ handleKernelReady(msg.data, {
202
+ autoInstantiate: true,
203
+ setCells: actions.setCells,
204
+ setLayoutData: Functions.NOOP,
205
+ setAppConfig: Functions.NOOP,
206
+ setCapabilities: Functions.NOOP,
207
+ setKernelState: Functions.NOOP,
208
+ onError: Logger.error,
209
+ });
210
+ defineCustomElement(ISLAND_TAG_NAMES.ISLAND, MarimoIslandElement);
211
+ return;
212
+
213
+ case "send-ui-element-message":
214
+ UI_ELEMENT_REGISTRY.broadcastMessage(
215
+ msg.data.ui_element,
216
+ msg.data.message,
217
+ safeExtractSetUIElementMessageBuffers(msg.data),
218
+ );
219
+ return;
220
+
221
+ case "remove-ui-elements":
222
+ handleRemoveUIElements(msg.data);
223
+ return;
224
+
225
+ case "function-call-result":
226
+ FUNCTIONS_REGISTRY.resolve(msg.data.function_call_id, msg.data);
227
+ return;
228
+
229
+ case "cell-op":
230
+ handleCellNotificationeration(msg.data, actions.handleCellMessage);
231
+ return;
232
+
233
+ case "alert":
234
+ toast({
235
+ title: msg.data.title,
236
+ description: renderHTML({ html: msg.data.description }),
237
+ variant: msg.data.variant,
238
+ });
239
+ return;
240
+
241
+ case "query-params-append":
242
+ queryParamHandlers.append(msg.data);
243
+ return;
244
+ case "query-params-set":
245
+ queryParamHandlers.set(msg.data);
246
+ return;
247
+ case "query-params-delete":
248
+ queryParamHandlers.delete(msg.data);
249
+ return;
250
+ case "query-params-clear":
251
+ queryParamHandlers.clear();
252
+ return;
253
+
254
+ case "model-lifecycle":
255
+ handleWidgetMessage(MODEL_MANAGER, msg.data);
256
+ return;
257
+
258
+ default:
259
+ logNever(msg);
260
+ return;
261
+ }
262
+ } catch (error) {
263
+ Logger.error("Failed to handle kernel message:", error);
264
+ }
265
+ }