@marimo-team/islands 0.25.3-dev5 → 0.25.3-dev6

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 (32) hide show
  1. package/dist/{ErrorBoundary-BiZWOrv4.js → ErrorBoundary-Ca-Sa492.js} +1 -1
  2. package/dist/{code-visibility-CW5_8ZOI.js → code-visibility-NGRjhbtA.js} +1 -1
  3. package/dist/{common-mzWw4FdI.js → common-B7_ZSg0P.js} +4755 -4744
  4. package/dist/{data-grid-overlay-editor-CJmehSdc.js → data-grid-overlay-editor-B_cmrkBm.js} +1 -1
  5. package/dist/{feedback-button-BcT1FBud.js → feedback-button-CTF2Nj9s.js} +2 -2
  6. package/dist/{glide-data-editor-XckatD0j.js → glide-data-editor-BQHF9puC.js} +3 -3
  7. package/dist/{globals-jRrLCkrP.js → globals-Cj8iqMEp.js} +1 -1
  8. package/dist/main.js +6 -6
  9. package/dist/{number-overlay-editor-pZx8dUDV.js → number-overlay-editor-DBcnm0SY.js} +1 -1
  10. package/dist/{reveal-component-79ez8-D5.js → reveal-component-DTBSuGkC.js} +2 -2
  11. package/dist/style.css +1 -1
  12. package/package.json +1 -1
  13. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  14. package/src/__tests__/mount.test.ts +8 -8
  15. package/src/components/editor/Output.tsx +5 -1
  16. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  17. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
  18. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  19. package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
  20. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +267 -62
  21. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +196 -41
  22. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +127 -13
  23. package/src/components/editor/errors/send-error-report-button.tsx +10 -7
  24. package/src/components/editor/output/MarimoTracebackOutput.tsx +27 -21
  25. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  26. package/src/core/attachments/toast.ts +15 -0
  27. package/src/core/cells/__tests__/error-report.test.ts +53 -3
  28. package/src/core/cells/__tests__/session.test.ts +18 -3
  29. package/src/core/cells/error-report.ts +14 -6
  30. package/src/core/cells/session.ts +19 -1
  31. package/src/core/config/pair.ts +5 -3
  32. package/src/mount.tsx +3 -3
@@ -0,0 +1,58 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ // @vitest-environment jsdom
3
+
4
+ import { act, render, renderHook } from "@testing-library/react";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { Toaster } from "@/components/ui/toaster";
7
+ import { clearToasts, toast, useToast } from "@/components/ui/use-toast";
8
+ import { agentToast } from "../toast";
9
+
10
+ describe("agent notifications", () => {
11
+ beforeEach(() => {
12
+ vi.useFakeTimers();
13
+ clearToasts();
14
+ });
15
+
16
+ afterEach(() => {
17
+ act(() => {
18
+ clearToasts();
19
+ vi.runOnlyPendingTimers();
20
+ });
21
+ vi.useRealTimers();
22
+ });
23
+
24
+ it.each([
25
+ { variant: undefined, duration: 3000 },
26
+ { variant: "danger" as const, duration: 5000 },
27
+ ])("expires while the window is blurred: %j", ({ variant, duration }) => {
28
+ const { result } = renderHook(useToast);
29
+ render(<Toaster />);
30
+ act(() => agentToast({ title: "Agent notice", variant }));
31
+ act(() => window.dispatchEvent(new Event("blur")));
32
+ act(() => vi.advanceTimersByTime(duration - 1));
33
+ expect(result.current.toasts[0].open).toBe(true);
34
+ act(() => vi.advanceTimersByTime(1));
35
+ expect(result.current.toasts[0].open).toBe(false);
36
+ });
37
+
38
+ it("preserves pause behavior for ordinary notifications", () => {
39
+ const { result } = renderHook(useToast);
40
+ render(<Toaster />);
41
+ act(() => toast({ title: "Ordinary notice", duration: 3000 }));
42
+ act(() => window.dispatchEvent(new Event("blur")));
43
+ act(() => vi.advanceTimersByTime(5000));
44
+ expect(result.current.toasts[0].open).toBe(true);
45
+ });
46
+
47
+ it("does not let an older timer dismiss a newer notification", () => {
48
+ const { result } = renderHook(useToast);
49
+ act(() => agentToast({ title: "First" }));
50
+ act(() => vi.advanceTimersByTime(1000));
51
+ act(() => agentToast({ title: "Second" }));
52
+ act(() => vi.advanceTimersByTime(2000));
53
+ expect(result.current.toasts[0].title).toBe("Second");
54
+ expect(result.current.toasts[0].open).toBe(true);
55
+ act(() => vi.advanceTimersByTime(1000));
56
+ expect(result.current.toasts[0].open).toBe(false);
57
+ });
58
+ });
@@ -0,0 +1,15 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { toast } from "@/components/ui/use-toast";
4
+
5
+ export function agentToast(
6
+ options: Pick<
7
+ Parameters<typeof toast>[0],
8
+ "title" | "variant" | "description"
9
+ >,
10
+ ) {
11
+ const duration = options.variant === "danger" ? 5000 : 3000;
12
+ const { dismiss } = toast({ ...options, duration });
13
+ // Agent notices must also expire while the user focuses the agent terminal.
14
+ window.setTimeout(dismiss, duration);
15
+ }
@@ -4,11 +4,51 @@
4
4
  import { describe, expect, it } from "vitest";
5
5
  import { cellId } from "@/__tests__/branded";
6
6
  import type { MarimoError } from "@/core/kernel/messages";
7
- import { buildErrorReport } from "../error-report";
7
+ import { buildErrorReport, ERROR_MESSAGE_INSTRUCTIONS } from "../error-report";
8
8
 
9
9
  const CELL_ID = cellId("failed-cell");
10
10
 
11
11
  describe("buildErrorReport", () => {
12
+ it("preserves plain traceback text without treating angle brackets as HTML", () => {
13
+ const traceback =
14
+ ' File "<cell>", line 1\nValueError: expected <value> & got <other>';
15
+ expect(
16
+ buildErrorReport(CELL_ID, { traceback, tracebackFormat: "text" }).output,
17
+ ).toEqual({
18
+ type: "error",
19
+ instructions: ERROR_MESSAGE_INSTRUCTIONS,
20
+ message: "ValueError: expected <value> & got <other>",
21
+ traceback,
22
+ });
23
+ });
24
+ it("owns the instruction to repair the live notebook", () => {
25
+ const report = buildErrorReport(CELL_ID, {
26
+ errors: [{ type: "syntax", msg: "invalid code" }],
27
+ });
28
+ expect(report.output.instructions).toEqual("Fix this notebook error.");
29
+ });
30
+
31
+ it("keeps repair intent visible before oversized evidence", () => {
32
+ const report = buildErrorReport(CELL_ID, {
33
+ code: "# long code\n".repeat(10_000),
34
+ errors: [
35
+ {
36
+ type: "exception",
37
+ exception_type: "ValueError",
38
+ msg: "long error ".repeat(10_000),
39
+ raising_cell: null,
40
+ traceback: `<pre>Traceback\n${"long frame\n".repeat(10_000)}</pre>`,
41
+ },
42
+ ],
43
+ });
44
+ const serialized = JSON.stringify(report);
45
+
46
+ expect(serialized.length).toBeGreaterThan(8192);
47
+ expect(serialized.slice(0, 256)).toContain(
48
+ '"instructions":"Fix this notebook error."',
49
+ );
50
+ });
51
+
12
52
  it("retains every message and complete traceback without display limits", () => {
13
53
  const traceback = `Traceback\n${"long frame\n".repeat(10_000)}ValueError: bad <value>`;
14
54
  const errors: MarimoError[] = [
@@ -27,6 +67,7 @@ describe("buildErrorReport", () => {
27
67
  code: "failed()",
28
68
  output: {
29
69
  type: "error",
70
+ instructions: ERROR_MESSAGE_INSTRUCTIONS,
30
71
  message: "bad <value>\nanother error",
31
72
  traceback,
32
73
  },
@@ -49,7 +90,11 @@ describe("buildErrorReport", () => {
49
90
  ).toEqual({
50
91
  type: "cell-output",
51
92
  cell_id: CELL_ID,
52
- output: { type: "error", message: "legacy error" },
93
+ output: {
94
+ type: "error",
95
+ instructions: ERROR_MESSAGE_INSTRUCTIONS,
96
+ message: "legacy error",
97
+ },
53
98
  });
54
99
  });
55
100
 
@@ -63,7 +108,11 @@ describe("buildErrorReport", () => {
63
108
  type: "cell-output",
64
109
  cell_id: CELL_ID,
65
110
  code: "",
66
- output: { type: "error", message: "invalid code" },
111
+ output: {
112
+ type: "error",
113
+ instructions: ERROR_MESSAGE_INSTRUCTIONS,
114
+ message: "invalid code",
115
+ },
67
116
  });
68
117
  });
69
118
 
@@ -80,6 +129,7 @@ describe("buildErrorReport", () => {
80
129
  cell_id: CELL_ID,
81
130
  output: {
82
131
  type: "error",
132
+ instructions: ERROR_MESSAGE_INSTRUCTIONS,
83
133
  message: "first\nsecond",
84
134
  traceback:
85
135
  "Traceback\nValueError: first\n\nTraceback\nValueError: second",
@@ -174,8 +174,11 @@ describe("notebookStateFromSession", () => {
174
174
  channel: "marimo-error",
175
175
  data: [
176
176
  {
177
- type: "unknown",
177
+ type: "exception",
178
+ exception_type: "ValueError",
178
179
  msg: "Something went wrong",
180
+ raising_cell: null,
181
+ traceback: "Traceback line 1",
179
182
  },
180
183
  ],
181
184
  mimetype: "application/vnd.marimo+error",
@@ -201,8 +204,9 @@ describe("notebookStateFromSession", () => {
201
204
  {
202
205
  type: "stream",
203
206
  name: "stderr",
204
- text: "<pre>ValueError: cached failure</pre>",
207
+ text: "ValueError: cached <failure>",
205
208
  mimetype: "application/vnd.marimo+traceback",
209
+ traceback_format: "text",
206
210
  code,
207
211
  },
208
212
  ],
@@ -212,9 +216,20 @@ describe("notebookStateFromSession", () => {
212
216
  invariant(state, "state is null");
213
217
  expect(state.cellRuntime[CELL_1].output).toMatchObject({
214
218
  code,
215
- data: [{ type: "unknown", msg: "cached failure" }],
219
+ data: [
220
+ {
221
+ type: "exception",
222
+ exception_type: "ValueError",
223
+ msg: "cached failure",
224
+ traceback: "Traceback\nValueError: cached failure",
225
+ },
226
+ ],
216
227
  });
217
228
  expect(state.cellRuntime[CELL_1].consoleOutputs[0].code).toBe(code);
229
+ expect(state.cellRuntime[CELL_1].consoleOutputs[0]).toMatchObject({
230
+ traceback_format: "text",
231
+ data: "ValueError: cached <failure>",
232
+ });
218
233
  });
219
234
 
220
235
  it("handles data output in session cell", () => {
@@ -6,26 +6,29 @@ import { sanitizeHtml } from "@/plugins/core/sanitize-html";
6
6
  import { parseHtmlContent } from "@/utils/dom";
7
7
  import type { CellId } from "./ids";
8
8
 
9
- export interface CellOutputHandoff<Output> {
9
+ export interface CellOutputMessage<Output> {
10
10
  type: "cell-output";
11
11
  cell_id: CellId;
12
12
  code?: string;
13
13
  output: Output;
14
14
  }
15
15
 
16
- export interface ErrorHandoffOutput {
16
+ export interface ErrorMessageOutput {
17
17
  type: "error";
18
+ instructions: string;
18
19
  message: string;
19
20
  traceback?: string;
20
21
  }
21
22
 
23
+ export const ERROR_MESSAGE_INSTRUCTIONS = "Fix this notebook error.";
24
+
22
25
  export function buildErrorReport(
23
26
  cellId: CellId,
24
27
  evidence: { code?: string } & (
25
28
  | { errors: MarimoError[] }
26
- | { traceback: string }
29
+ | { traceback: string; tracebackFormat: "html" | "text" }
27
30
  ),
28
- ): CellOutputHandoff<ErrorHandoffOutput> {
31
+ ): CellOutputMessage<ErrorMessageOutput> {
29
32
  let message: string;
30
33
  let traceback: string;
31
34
  if ("errors" in evidence) {
@@ -38,18 +41,23 @@ export function buildErrorReport(
38
41
  )
39
42
  .join("\n\n");
40
43
  } else {
41
- traceback = parseHtmlContent(sanitizeHtml(evidence.traceback));
44
+ traceback =
45
+ evidence.tracebackFormat === "html"
46
+ ? parseHtmlContent(sanitizeHtml(evidence.traceback))
47
+ : evidence.traceback;
42
48
  message = traceback.split("\n").findLast((line) => line.length > 0) ?? "";
43
49
  }
44
50
 
51
+ // Put repair intent before evidence so it survives prefix truncation.
45
52
  return {
46
53
  type: "cell-output",
47
54
  cell_id: cellId,
48
- ...(evidence.code === undefined ? {} : { code: evidence.code }),
49
55
  output: {
50
56
  type: "error",
57
+ instructions: ERROR_MESSAGE_INSTRUCTIONS,
51
58
  message,
52
59
  ...(traceback ? { traceback } : {}),
53
60
  },
61
+ ...(evidence.code === undefined ? {} : { code: evidence.code }),
54
62
  };
55
63
  }
@@ -206,8 +206,20 @@ function createCellRuntimeFromSession(
206
206
  channel: "marimo-error",
207
207
  data: [
208
208
  {
209
- type: "unknown",
209
+ type: "exception",
210
+ exception_type: primaryOutput.ename,
210
211
  msg: primaryOutput.evalue,
212
+ raising_cell: null,
213
+ traceback:
214
+ primaryOutput.traceback.length === 0
215
+ ? null
216
+ : primaryOutput.traceback
217
+ .map((line, index, lines) =>
218
+ index < lines.length - 1 && !line.endsWith("\n")
219
+ ? `${line}\n`
220
+ : line,
221
+ )
222
+ .join(""),
211
223
  },
212
224
  ],
213
225
  mimetype: "application/vnd.marimo+error",
@@ -240,6 +252,9 @@ function createCellRuntimeFromSession(
240
252
  channel: "media",
241
253
  data: consoleOutput.data,
242
254
  mimetype: consoleOutput.mimetype,
255
+ ...(consoleOutput.traceback_format === undefined
256
+ ? {}
257
+ : { traceback_format: consoleOutput.traceback_format }),
243
258
  timestamp: DEFAULT_TIMESTAMP,
244
259
  ...(consoleOutput.code === undefined
245
260
  ? {}
@@ -251,6 +266,9 @@ function createCellRuntimeFromSession(
251
266
  channel: consoleOutput.name === "stderr" ? "stderr" : "stdout",
252
267
  data: consoleOutput.text,
253
268
  mimetype: consoleOutput.mimetype ?? "text/plain",
269
+ ...(consoleOutput.traceback_format === undefined
270
+ ? {}
271
+ : { traceback_format: consoleOutput.traceback_format }),
254
272
  timestamp: DEFAULT_TIMESTAMP,
255
273
  ...(consoleOutput.code === undefined
256
274
  ? {}
@@ -3,16 +3,18 @@
3
3
  import { atom } from "jotai";
4
4
  import { z } from "zod";
5
5
 
6
- export const pairPreviewSchema = z.object({
6
+ export const pairConfigSchema = z.object({
7
7
  command: z.string(),
8
+ hostEnabled: z.boolean().default(false),
8
9
  templates: z.object({
9
10
  prompt: z.string(),
11
+ notebook: z.string(),
10
12
  session: z.string(),
11
13
  token_file: z.string(),
12
14
  token: z.string(),
13
15
  }),
14
16
  });
15
17
 
16
- export type PairPreviewConfig = z.infer<typeof pairPreviewSchema>;
18
+ export type PairConfig = z.infer<typeof pairConfigSchema>;
17
19
 
18
- export const pairPreviewAtom = atom<PairPreviewConfig | undefined>(undefined);
20
+ export const pairConfigAtom = atom<PairConfig | undefined>(undefined);
package/src/mount.tsx CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  userConfigAtom,
11
11
  } from "@/core/config/config";
12
12
  import { KnownQueryParams } from "@/core/constants";
13
- import { pairPreviewAtom, pairPreviewSchema } from "@/core/config/pair";
13
+ import { pairConfigAtom, pairConfigSchema } from "@/core/config/pair";
14
14
  import { getFilenameFromDOM } from "@/core/dom/htmlUtils";
15
15
  import { getMarimoCode } from "@/core/meta/globals";
16
16
  import {
@@ -209,7 +209,7 @@ const mountOptionsSchema = z.object({
209
209
  * marimo config overrides
210
210
  */
211
211
  configOverrides: passthroughObject,
212
- pairPreview: pairPreviewSchema.optional(),
212
+ pairConfig: pairConfigSchema.optional(),
213
213
  /**
214
214
  * marimo app config
215
215
  */
@@ -347,7 +347,7 @@ function initStore(options: unknown) {
347
347
  );
348
348
  store.set(userConfigAtom, parseUserConfig(parsedOptions.data.config));
349
349
  store.set(appConfigAtom, parseAppConfig(parsedOptions.data.appConfig));
350
- store.set(pairPreviewAtom, parsedOptions.data.pairPreview);
350
+ store.set(pairConfigAtom, parsedOptions.data.pairConfig);
351
351
 
352
352
  // Runtime config
353
353
  if (parsedOptions.data.runtimeConfig.length > 0) {