@marimo-team/islands 0.24.1-dev73 → 0.24.1-dev76

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.
@@ -18570,19 +18570,23 @@ function Kj(e, t) {
18570
18570
  //#endregion
18571
18571
  //#region src/core/packages/toast-components.tsx
18572
18572
  var qj = (e, t) => {
18573
+ let n = Array.isArray(e) ? e : [e];
18573
18574
  m(t ? {
18574
- title: "Failed to add package",
18575
+ title: n.length > 1 ? "Failed to add packages" : "Failed to add package",
18575
18576
  description: t,
18576
18577
  variant: "danger"
18577
18578
  } : {
18578
- title: "Package added",
18579
+ title: n.length > 1 ? "Packages added" : "Package added",
18579
18580
  description: /* @__PURE__ */ (0, U.jsxs)("div", { children: [/* @__PURE__ */ (0, U.jsxs)("div", { children: [
18580
- "The package ",
18581
- /* @__PURE__ */ (0, U.jsx)(ej, {
18581
+ n.length > 1 ? "The packages " : "The package ",
18582
+ n.map((e, t) => /* @__PURE__ */ (0, U.jsxs)("span", { children: [t > 0 && ", ", /* @__PURE__ */ (0, U.jsx)(ej, {
18582
18583
  className: "inline",
18583
18584
  children: e
18584
- }),
18585
- " and its dependencies has been added to your environment."
18585
+ })] }, e)),
18586
+ " ",
18587
+ "and ",
18588
+ n.length > 1 ? "their" : "its",
18589
+ " dependencies have been added to your environment."
18586
18590
  ] }), /* @__PURE__ */ (0, U.jsx)("div", {
18587
18591
  className: "text-xs text-muted-foreground mt-1",
18588
18592
  children: "Some Python packages may require a kernel restart to see changes."
@@ -18598,11 +18602,8 @@ function Jj() {
18598
18602
  handleInstallPackages: async (e, r) => {
18599
18603
  t(!0);
18600
18604
  try {
18601
- for (let [t, r] of e.entries()) {
18602
- let i = await n({ package: r });
18603
- i.success ? qj(r) : qj(r, i.error), t < e.length - 1 && await new Promise((e) => setTimeout(e, 1e3));
18604
- }
18605
- r?.();
18605
+ let t = await n({ package: e.join(" ") });
18606
+ t.success ? qj(e) : qj(e, t.error), r?.();
18606
18607
  } catch (e) {
18607
18608
  i.error(e);
18608
18609
  } finally {
@@ -31786,7 +31787,7 @@ function QG() {
31786
31787
  return i.warn("Failed to get version from mount config"), null;
31787
31788
  }
31788
31789
  }
31789
- var $G = QG() || "0.24.1-dev73", eK = Ci($G), tK = Ci(!0);
31790
+ var $G = QG() || "0.24.1-dev76", eK = Ci($G), tK = Ci(!0);
31790
31791
  Ci(null);
31791
31792
  //#endregion
31792
31793
  //#region src/core/meta/code-visibility.ts
package/dist/main.js CHANGED
@@ -6,7 +6,7 @@ import { n as x, r as S, t as C } from "./copy-D9h0DuU7.js";
6
6
  import { $ as w, $n as T, B as E, Bn as D, C as O, Cn as k, Ct as A, Dn as j, Dt as M, Et as N, F as P, Fn as F, Ft as I, G as L, Gn as R, Ht as ee, I as z, It as te, J as ne, K as re, Kn as ie, Kt as ae, L as oe, Ln as se, Mn as ce, Mt as le, Nn as ue, On as de, Ot as fe, P as pe, Pn as me, Qn as he, R as ge, Rn as _e, Sn as ve, St as ye, U as be, Un as xe, Ut as Se, W as Ce, Wn as we, Wt as Te, X as Ee, Xn as De, Xt as Oe, Y as ke, Yn as Ae, Z as je, _ as Me, _t as Ne, a as Pe, an as Fe, at as Ie, b as Le, bn as Re, bt as ze, ct as Be, dn as Ve, en as He, g as Ue, gn as We, hn as Ge, i as Ke, in as qe, it as Je, j as Ye, jt as Xe, k as Ze, kn as Qe, kt as $e, lt as et, mn as tt, n as nt, nn as rt, o as it, on as at, q as ot, qn as st, qt as ct, rn as lt, rt as ut, st as dt, tn as ft, v as pt, vn as mt, vt as ht, wn as gt, wt as _t, xn as vt, xt as yt, y as bt, yt as xt, z as St, zt as Ct } from "./html-to-image-B7RQgQQy.js";
7
7
  import { o as wt, s as Tt, u as Et } from "./dist-wdOJ5T7-.js";
8
8
  import { D as Dt, E as Ot, O as kt, S as At, T as jt, _ as Mt, a as Nt, c as Pt, d as Ft, f as It, g as Lt, h as Rt, i as zt, l as Bt, m as Vt, n as Ht, o as Ut, p as Wt, r as Gt, s as Kt, t as qt, u as Jt, v as Yt, w as Xt, x as Zt, y as Qt } from "./spec-BQ36ell9.js";
9
- import { $ as $t, $t as en, A as tn, At as nn, B as rn, Bt as an, C as on, Ct as sn, D as cn, Dt as ln, E as un, Et as dn, F as fn, Ft as pn, G as mn, Gt as hn, H as gn, Ht as _n, I as vn, It as yn, Jt as bn, K as xn, Kt as Sn, L as Cn, Lt as wn, M as Tn, Mt as En, N as Dn, Nt as On, O as kn, Ot as An, P as jn, Pt as Mn, Q as Nn, Qt as Pn, R as Fn, Rt as In, S as Ln, St as Rn, T as zn, Tt as Bn, U as Vn, Ut as Hn, V as Un, Vt as Wn, W as Gn, Wt as Kn, Xt as qn, Yt as Jn, Z as Yn, Zt as Xn, _ as Zn, an as Qn, at as $n, b as er, bt as tr, c as nr, cn as rr, ct as ir, d as ar, dn as or, dt as sr, en as cr, et as lr, f as ur, fn as dr, ft as fr, g as pr, gt as mr, h as hr, ht as gr, i as _r, it as vr, j as yr, jt as br, k as xr, kt as Sr, ln as Cr, lt as wr, m as Tr, mt as Er, n as Dr, nn as Or, nt as kr, on as Ar, ot as jr, p as Mr, pn as Nr, pt as Pr, q as Fr, qt as Ir, r as Lr, rn as Rr, rt as zr, sn as Br, st as Vr, t as Hr, tn as Ur, tt as Wr, u as Gr, un as Kr, ut as qr, v as Jr, w as Yr, wt as Xr, x as Zr, xt as Qr, y as $r, yt as ei, z as ti, zt as ni } from "./common-D37C4Qj7.js";
9
+ import { $ as $t, $t as en, A as tn, At as nn, B as rn, Bt as an, C as on, Ct as sn, D as cn, Dt as ln, E as un, Et as dn, F as fn, Ft as pn, G as mn, Gt as hn, H as gn, Ht as _n, I as vn, It as yn, Jt as bn, K as xn, Kt as Sn, L as Cn, Lt as wn, M as Tn, Mt as En, N as Dn, Nt as On, O as kn, Ot as An, P as jn, Pt as Mn, Q as Nn, Qt as Pn, R as Fn, Rt as In, S as Ln, St as Rn, T as zn, Tt as Bn, U as Vn, Ut as Hn, V as Un, Vt as Wn, W as Gn, Wt as Kn, Xt as qn, Yt as Jn, Z as Yn, Zt as Xn, _ as Zn, an as Qn, at as $n, b as er, bt as tr, c as nr, cn as rr, ct as ir, d as ar, dn as or, dt as sr, en as cr, et as lr, f as ur, fn as dr, ft as fr, g as pr, gt as mr, h as hr, ht as gr, i as _r, it as vr, j as yr, jt as br, k as xr, kt as Sr, ln as Cr, lt as wr, m as Tr, mt as Er, n as Dr, nn as Or, nt as kr, on as Ar, ot as jr, p as Mr, pn as Nr, pt as Pr, q as Fr, qt as Ir, r as Lr, rn as Rr, rt as zr, sn as Br, st as Vr, t as Hr, tn as Ur, tt as Wr, u as Gr, un as Kr, ut as qr, v as Jr, w as Yr, wt as Xr, x as Zr, xt as Qr, y as $r, yt as ei, z as ti, zt as ni } from "./common-nuR9LfxI.js";
10
10
  import { c as ri, i as ii, n as ai, o as oi, t as si } from "./useLifecycle-hEcsyQ2P.js";
11
11
  import { t as ci } from "./check-BcJDmQdF.js";
12
12
  import { A as li, C as ui, D as di, E as fi, F as pi, I as mi, M as hi, N as gi, P as _i, R as vi, S as yi, a as bi, b as xi, c as Si, i as Ci, j as wi, l as Ti, n as Ei, o as Di, r as Oi, s as ki, t as Ai, w as ji, x as Mi } from "./select-DQ9jU_B_.js";
@@ -35256,7 +35256,7 @@ function Gq(e) {
35256
35256
  }
35257
35257
  //#endregion
35258
35258
  //#region src/components/editor/renderers/slides-layout/slides-layout.tsx
35259
- var Kq = q.lazy(() => import("./reveal-component-B3r0D5zd.js")), qq = (e) => {
35259
+ var Kq = q.lazy(() => import("./reveal-component-CzLgRK9f.js")), qq = (e) => {
35260
35260
  let t = (0, Y.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = Nm(_t), s = a === "read" || o, c = Nm(ne) > 1, [l, u] = (0, q.useState)(null), d;
35261
35261
  t[0] !== i || t[1] !== n ? (d = UU(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
35262
35262
  let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
@@ -4,7 +4,7 @@ import { t as o } from "./react-DMWcHin-.js";
4
4
  import { t as s } from "./compiler-runtime-CAH9RLw2.js";
5
5
  import { it as c, wt as l } from "./html-to-image-B7RQgQQy.js";
6
6
  import { u } from "./dist-wdOJ5T7-.js";
7
- import { J as d, X as f, Y as p, in as m, l as h, n as g, rn as _, sn as v, t as y, u as b } from "./common-D37C4Qj7.js";
7
+ import { J as d, X as f, Y as p, in as m, l as h, n as g, rn as _, sn as v, t as y, u as b } from "./common-nuR9LfxI.js";
8
8
  import { R as x } from "./select-DQ9jU_B_.js";
9
9
  import { X as S } from "./dropdown-menu-C8N-kZzD.js";
10
10
  import { t as C } from "./jsx-runtime-CpoKNRgu.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.24.1-dev73",
3
+ "version": "0.24.1-dev76",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,123 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { act, renderHook } from "@testing-library/react";
3
+ import { beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { useInstallPackages } from "../useInstallPackage";
5
+
6
+ const toast = vi.fn();
7
+ const addPackage = vi.fn();
8
+
9
+ vi.mock("@/components/ui/use-toast", () => ({
10
+ toast: (...args: unknown[]) => toast(...args),
11
+ }));
12
+
13
+ vi.mock("../../network/requests", () => ({
14
+ useRequestClient: () => ({
15
+ addPackage: (...args: unknown[]) => addPackage(...args),
16
+ }),
17
+ }));
18
+
19
+ describe("useInstallPackages", () => {
20
+ beforeEach(() => {
21
+ toast.mockClear();
22
+ addPackage.mockClear();
23
+ });
24
+
25
+ it("batches all packages into a single install call", async () => {
26
+ addPackage.mockResolvedValue({ success: true, error: null });
27
+ const { result } = renderHook(() => useInstallPackages());
28
+
29
+ await act(async () => {
30
+ await result.current.handleInstallPackages(["numpy", "pandas", "scipy"]);
31
+ });
32
+
33
+ // A single batched install call, not one per package.
34
+ expect(addPackage).toHaveBeenCalledTimes(1);
35
+ expect(addPackage).toHaveBeenCalledWith({ package: "numpy pandas scipy" });
36
+ });
37
+
38
+ it("shows a single aggregate success toast for multiple packages", async () => {
39
+ addPackage.mockResolvedValue({ success: true, error: null });
40
+ const { result } = renderHook(() => useInstallPackages());
41
+
42
+ await act(async () => {
43
+ await result.current.handleInstallPackages(["numpy", "pandas", "scipy"]);
44
+ });
45
+
46
+ // Only one toast, regardless of package count — no false per-package status.
47
+ expect(toast).toHaveBeenCalledTimes(1);
48
+ expect(toast).toHaveBeenCalledWith(
49
+ expect.objectContaining({ title: "Packages added" }),
50
+ );
51
+ });
52
+
53
+ it("shows a single aggregate error toast when the batch fails", async () => {
54
+ addPackage.mockResolvedValue({ success: false, error: "boom" });
55
+ const { result } = renderHook(() => useInstallPackages());
56
+
57
+ await act(async () => {
58
+ await result.current.handleInstallPackages(["numpy", "pandas"]);
59
+ });
60
+
61
+ expect(toast).toHaveBeenCalledTimes(1);
62
+ expect(toast).toHaveBeenCalledWith(
63
+ expect.objectContaining({
64
+ title: "Failed to add packages",
65
+ description: "boom",
66
+ variant: "danger",
67
+ }),
68
+ );
69
+ });
70
+
71
+ it("collapses a partial (mixed) failure into one aggregate error toast", async () => {
72
+ // The backend resolves the batch as a single transaction and reports an
73
+ // aggregate outcome, so even when only some packages fail we surface a
74
+ // single error toast covering the whole batch — never a per-package
75
+ // success/failure split the response can't actually represent.
76
+ addPackage.mockResolvedValue({
77
+ success: false,
78
+ error: "pandas failed to install",
79
+ });
80
+ const { result } = renderHook(() => useInstallPackages());
81
+
82
+ await act(async () => {
83
+ await result.current.handleInstallPackages(["numpy", "pandas"]);
84
+ });
85
+
86
+ expect(addPackage).toHaveBeenCalledTimes(1);
87
+ expect(addPackage).toHaveBeenCalledWith({ package: "numpy pandas" });
88
+ expect(toast).toHaveBeenCalledTimes(1);
89
+ expect(toast).toHaveBeenCalledWith(
90
+ expect.objectContaining({
91
+ title: "Failed to add packages",
92
+ description: "pandas failed to install",
93
+ variant: "danger",
94
+ }),
95
+ );
96
+ });
97
+
98
+ it("uses singular wording for a single package", async () => {
99
+ addPackage.mockResolvedValue({ success: true, error: null });
100
+ const { result } = renderHook(() => useInstallPackages());
101
+
102
+ await act(async () => {
103
+ await result.current.handleInstallPackages(["numpy"]);
104
+ });
105
+
106
+ expect(toast).toHaveBeenCalledTimes(1);
107
+ expect(toast).toHaveBeenCalledWith(
108
+ expect.objectContaining({ title: "Package added" }),
109
+ );
110
+ });
111
+
112
+ it("calls onSuccess after a successful install", async () => {
113
+ addPackage.mockResolvedValue({ success: true, error: null });
114
+ const onSuccess = vi.fn();
115
+ const { result } = renderHook(() => useInstallPackages());
116
+
117
+ await act(async () => {
118
+ await result.current.handleInstallPackages(["numpy"], onSuccess);
119
+ });
120
+
121
+ expect(onSuccess).toHaveBeenCalledTimes(1);
122
+ });
123
+ });
@@ -4,23 +4,34 @@ import { Kbd } from "@/components/ui/kbd";
4
4
  import { toast } from "@/components/ui/use-toast";
5
5
 
6
6
  export const showAddPackageToast = (
7
- packageName: string,
7
+ packageName: string | string[],
8
8
  error?: string | null,
9
9
  ) => {
10
+ const packageNames = Array.isArray(packageName) ? packageName : [packageName];
10
11
  if (error) {
11
12
  toast({
12
- title: "Failed to add package",
13
+ title:
14
+ packageNames.length > 1
15
+ ? "Failed to add packages"
16
+ : "Failed to add package",
13
17
  description: error,
14
18
  variant: "danger",
15
19
  });
16
20
  } else {
17
21
  toast({
18
- title: "Package added",
22
+ title: packageNames.length > 1 ? "Packages added" : "Package added",
19
23
  description: (
20
24
  <div>
21
25
  <div>
22
- The package <Kbd className="inline">{packageName}</Kbd> and its
23
- dependencies has been added to your environment.
26
+ {packageNames.length > 1 ? "The packages " : "The package "}
27
+ {packageNames.map((name, index) => (
28
+ <span key={name}>
29
+ {index > 0 && ", "}
30
+ <Kbd className="inline">{name}</Kbd>
31
+ </span>
32
+ ))}{" "}
33
+ and {packageNames.length > 1 ? "their" : "its"} dependencies have
34
+ been added to your environment.
24
35
  </div>
25
36
  <div className="text-xs text-muted-foreground mt-1">
26
37
  Some Python packages may require a kernel restart to see changes.
@@ -22,17 +22,18 @@ export function useInstallPackages(): {
22
22
  setLoading(true);
23
23
 
24
24
  try {
25
- for (const [idx, packageName] of packages.entries()) {
26
- const response = await addPackage({ package: packageName });
27
- if (response.success) {
28
- showAddPackageToast(packageName);
29
- } else {
30
- showAddPackageToast(packageName, response.error);
31
- }
32
- // Wait 1s if there are more packages to install
33
- if (idx < packages.length - 1) {
34
- await new Promise((resolve) => setTimeout(resolve, 1000));
35
- }
25
+ // Batch all packages into a single install call.
26
+ // The worker splits by space and passes the full list to micropip,
27
+ // which resolves and downloads in parallel internally.
28
+ const response = await addPackage({ package: packages.join(" ") });
29
+ // The backend resolves the whole list as a single transaction, so the
30
+ // response only carries an aggregate success/error. Report a single
31
+ // toast covering all requested packages rather than implying a
32
+ // per-package outcome we don't actually have.
33
+ if (response.success) {
34
+ showAddPackageToast(packages);
35
+ } else {
36
+ showAddPackageToast(packages, response.error);
36
37
  }
37
38
  onSuccess?.();
38
39
  } catch (error) {