@marimo-team/islands 0.24.3-dev40 → 0.24.3-dev42

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.
@@ -135,7 +135,7 @@ function F(e) {
135
135
  }
136
136
  //#endregion
137
137
  //#region src/components/editor/boundary/ErrorBoundary.tsx
138
- var I = (0, T.lazy)(() => import("./feedback-button-BkFFL-wb.js").then((e) => ({ default: e.FeedbackModal }))), L = (e) => {
138
+ var I = (0, T.lazy)(() => import("./feedback-button-DPuKw0-w.js").then((e) => ({ default: e.FeedbackModal }))), L = (e) => {
139
139
  let t = (0, w.c)(2), n;
140
140
  return t[0] === e.children ? n = t[1] : (n = /* @__PURE__ */ (0, j.jsx)(O, {
141
141
  FallbackComponent: R,
@@ -18,7 +18,7 @@ function x() {
18
18
  return e.warn("Failed to get version from mount config"), null;
19
19
  }
20
20
  }
21
- var S = x() || "0.24.3-dev40", C = u(S), w = u(!0);
21
+ var S = x() || "0.24.3-dev42", C = u(S), w = u(!0);
22
22
  u(null);
23
23
  //#endregion
24
24
  //#region src/core/wasm/state.ts
@@ -12,7 +12,7 @@ import { t as dt } from "./check-B7cIK6gb.js";
12
12
  import { A as ft, C as pt, D as mt, M as ht, O as gt, S as _t, _ as vt, b as yt, c as bt, d as xt, g as St, h as Ct, i as wt, m as Tt, n as Et, p as Dt, s as Ot, t as kt, u as At, v as jt, y as Mt } from "./select-C6plhmZr.js";
13
13
  import { $ as Nt, $t as Pt, A as Ft, At as It, B as Lt, Bt as Rt, Ct as zt, D as Bt, Dt as Vt, E as Ht, Et as Ut, F as Wt, Ft as Gt, G as Kt, Gt as qt, H as Jt, Ht as Yt, I as Xt, It as Zt, Jt as Qt, K as $t, Kt as en, L as tn, Lt as nn, M as rn, Mt as an, N as on, Nt as sn, O as cn, Ot as ln, P as un, Pt as dn, Q as fn, Qt as pn, R as mn, Rt as hn, S as gn, St as _n, T as vn, Tt as yn, U as bn, Ut as xn, V as Sn, Vt as Cn, W as wn, Wt as Tn, X as En, Xt as Dn, Y as On, Yt as kn, Z as An, Zt as jn, _ as Mn, _t as Nn, a as Pn, an as Fn, b as In, bt as Ln, cn as Rn, ct as zn, d as Bn, dn as Vn, dt as Hn, en as Un, et as Wn, f as Gn, fn as Kn, ft as qn, gt as Jn, h as Yn, ht as Xn, i as Zn, it as Qn, j as $n, jt as er, k as tr, kt as nr, l as rr, ln as ir, lt as ar, mt as or, n as sr, nn as cr, nt as lr, o as ur, on as dr, ot as fr, pn as pr, pt as mr, qt as hr, rn as gr, rt as _r, s as vr, sn as yr, st as br, t as xr, tn as Sr, tt as Cr, u as wr, un as Tr, ut as Er, vt as Dr, w as Or, wt as kr, x as Ar, xt as jr, y as Mr, yt as Nr, z as Pr, zt as Fr } from "./dropdown-menu-CZgjYSAh.js";
14
14
  import { _ as Ir, a as Lr, c as Rr, d as zr, i as Br, n as Vr, o as Hr, p as Ur, r as Wr, s as Gr, t as Kr } from "./toDate-WXg1yLcI.js";
15
- import { n as qr, t as Jr } from "./ErrorBoundary-CacTZz7K.js";
15
+ import { n as qr, t as Jr } from "./ErrorBoundary-CRcTuG6a.js";
16
16
  import { t as Yr } from "./clsx-CoeGtWvn.js";
17
17
  import { t as Xr } from "./react-dom-BUgJ-ab_.js";
18
18
  import { t as Zr } from "./jsx-runtime-DgDPJV9S.js";
@@ -2,7 +2,7 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { t } from "./react-ofJbxM4x.js";
3
3
  import { t as n } from "./react-dom-BUgJ-ab_.js";
4
4
  import { c as r, l as i, u as a } from "./data-grid-types-BucVmDDp.js";
5
- import { a as o, i as s, n as c, r as l } from "./glide-data-editor-CeAOHG6x.js";
5
+ import { a as o, i as s, n as c, r as l } from "./glide-data-editor-arTF-cqI.js";
6
6
  //#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.js
7
7
  var u = /* @__PURE__ */ e(t(), 1), d = /* @__PURE__ */ e(n(), 1), f = /*#__PURE__*/ o("div")({
8
8
  name: "DataGridOverlayEditorStyle",
@@ -5,12 +5,12 @@ import { t as i } from "./compiler-runtime-Cj8ZWiNS.js";
5
5
  import { t as a } from "./copy-icon-CLdENY__.js";
6
6
  import { Bt as o, H as s, Ht as c, L as l, Ot as u, V as d, d as f, hn as p } from "./cells-BSgNCBZ8.js";
7
7
  import { b as m } from "./select-C6plhmZr.js";
8
- import { n as h, t as ee } from "./globals-C74RodJy.js";
8
+ import { n as h, t as ee } from "./globals-D8pFSo8a.js";
9
9
  import { t as g } from "./jsx-runtime-DgDPJV9S.js";
10
10
  import { C as _, _ as te, i as v } from "./useTheme-DzXmYZ5V.js";
11
11
  import { a as ne, i as re, n as ie, r as ae } from "./dialog-CgNq6y9-.js";
12
12
  import { t as oe } from "./useAsyncData-BD6AStMt.js";
13
- import { t as se } from "./code-visibility-BBNwvBcV.js";
13
+ import { t as se } from "./code-visibility-CCZ-5fSi.js";
14
14
  import { i as ce, n as le } from "./error-entries-iJn-Nryx.js";
15
15
  //#region src/components/ui/skeleton.tsx
16
16
  var y = /* @__PURE__ */ e(r(), 1), b = i(), x = g(), ue = (e) => {
@@ -6,7 +6,7 @@ import { i as s, n as c, r as l } from "./copy-icon-CLdENY__.js";
6
6
  import { C as u, c as d, i as f, n as p, s as m, t as h, x as g } from "./select-C6plhmZr.js";
7
7
  import { d as _, h as v, i as y, l as b, n as x, o as S, s as C, t as w, u as T } from "./dropdown-menu-CZgjYSAh.js";
8
8
  import { c as E, d as D, l as O, n as k, o as A, s as ee, t as j, u as te } from "./glide-utils-B3rUT5PI.js";
9
- import { n as ne, t as re } from "./ErrorBoundary-CacTZz7K.js";
9
+ import { n as ne, t as re } from "./ErrorBoundary-CRcTuG6a.js";
10
10
  import { t as M } from "./react-dom-BUgJ-ab_.js";
11
11
  import { t as N } from "./jsx-runtime-DgDPJV9S.js";
12
12
  import { t as ie } from "./label-BJ49YTvk.js";
@@ -7062,7 +7062,7 @@ function rs(e, t, n, r, i) {
7062
7062
  }
7063
7063
  //#endregion
7064
7064
  //#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/data-editor/data-editor.js
7065
- var is = /* @__PURE__ */ e(_r(), 1), as = /* @__PURE__ */ e(xr(), 1), os = K.lazy(async () => await import("./data-grid-overlay-editor-DjRvSwGI.js")), ss = 0;
7065
+ var is = /* @__PURE__ */ e(_r(), 1), as = /* @__PURE__ */ e(xr(), 1), os = K.lazy(async () => await import("./data-grid-overlay-editor-B3BEydN0.js")), ss = 0;
7066
7066
  function cs(e) {
7067
7067
  return (0, is.default)((0, as.default)((0, as.default)(e).filter((e) => e.span !== void 0).map((e) => (0, Za.default)((e.span?.[0] ?? 0) + 1, (e.span?.[1] ?? 0) + 1))));
7068
7068
  }
@@ -9293,7 +9293,7 @@ function Vs(e, t, n, r, i) {
9293
9293
  }
9294
9294
  //#endregion
9295
9295
  //#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/cells/number-cell.js
9296
- var Hs = K.lazy(async () => await import("./number-overlay-editor-DDyIkEk3.js")), Us = {
9296
+ var Hs = K.lazy(async () => await import("./number-overlay-editor-Cz7-ixuZ.js")), Us = {
9297
9297
  getAccessibilityString: (e) => e.data?.toString() ?? "",
9298
9298
  kind: W.Number,
9299
9299
  needsHover: (e) => e.hoverEffect === !0,
@@ -1,6 +1,6 @@
1
1
  import { g as e } from "./tooltip-qB8NlSqV.js";
2
2
  import { _ as t } from "./useTheme-DzXmYZ5V.js";
3
- import { r as n } from "./code-visibility-BBNwvBcV.js";
3
+ import { r as n } from "./code-visibility-CCZ-5fSi.js";
4
4
  var r = e("triangle-alert", [
5
5
  ["path", {
6
6
  d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",
package/dist/main.js CHANGED
@@ -7,7 +7,7 @@ import { C as T, F as E, G as D, I as O, J as k, K as A, L as j, P as M, R as N,
7
7
  import { a as se, f as ce, g as V, i as le, n as ue, o as de, p as fe, r as pe, t as me } from "./tooltip-qB8NlSqV.js";
8
8
  import { $ as he, $t as ge, At as _e, B as ve, C as ye, D as be, Dt as xe, F as Se, G as Ce, Gt as we, I as Te, J as Ee, L as De, Mt as Oe, Nt as ke, O as Ae, Ot as je, P as Me, Q as Ne, Qt as Pe, S as Fe, T as Ie, Tt as Le, U as Re, Ut as ze, W as Be, Wt as Ve, X as He, Xt as Ue, Y as We, Yt as Ge, Zt as Ke, _n as qe, _t as Je, an as Ye, bn as Xe, c as Ze, cn as Qe, ct as $e, d as et, dn as tt, dt as nt, f as rt, fn as it, ft as at, g as ot, gn as st, gt as ct, h as lt, in as ut, it as dt, jt as ft, k as pt, kt as mt, l as ht, m as gt, mn as _t, n as vt, o as yt, on as bt, p as xt, pn as St, pt as Ct, q as wt, qt as Tt, r as Et, rn as Dt, rt as Ot, u as kt, v as At, w as jt, xt as Mt, yn as Nt, zt as Pt } from "./cells-BSgNCBZ8.js";
9
9
  import { D as Ft, E as It, O as Lt, S as Rt, T as zt, _ as Bt, a as Vt, c as Ht, d as Ut, f as Wt, g as Gt, h as Kt, i as qt, l as Jt, m as Yt, n as Xt, o as Zt, p as Qt, r as $t, s as en, t as tn, u as nn, v as rn, w as an, x as on, y as sn } from "./spec-CAYwFeV6.js";
10
- import { $t as cn, A as ln, At as un, B as dn, Bt as fn, C as pn, Ct as mn, D as hn, Dt as gn, E as _n, Et as vn, F as yn, Ft as bn, G as xn, Gt as Sn, H as Cn, Ht as wn, I as Tn, It as En, J as Dn, Jt as On, K as kn, Kt as An, L as jn, Lt as Mn, M as Nn, Mt as Pn, N as Fn, Nt as In, O as Ln, Ot as Rn, P as zn, Pt as Bn, Qt as Vn, R as Hn, Rt as Un, S as Wn, St as Gn, T as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Zt as ir, _ as ar, _t as or, an as sr, at as cr, b as lr, c as ur, cn as dr, ct as fr, d as pr, dn as mr, dt as hr, en as gr, et as _r, f as vr, fn as yr, ft as br, g as xr, gt as Sr, h as Cr, hn as wr, ht as Tr, in as Er, it as Dr, j as Or, jt as kr, k as Ar, kt as jr, ln as Mr, lt as Nr, m as Pr, mn as Fr, mt as Ir, nn as Lr, nt as Rr, ot as zr, p as Br, pn as Vr, pt as Hr, q as Ur, qt as Wr, r as Gr, rn as Kr, rt as qr, sn as Jr, st as Yr, t as Xr, tn as Zr, tt as Qr, u as $r, un as ei, ut as ti, v as ni, vt as ri, w as ii, wt as ai, x as oi, y as si, yt as ci, z as li, zt as ui } from "./common-DM3lblo7.js";
10
+ import { $t as cn, A as ln, At as un, B as dn, Bt as fn, C as pn, Ct as mn, D as hn, Dt as gn, E as _n, Et as vn, F as yn, Ft as bn, G as xn, Gt as Sn, H as Cn, Ht as wn, I as Tn, It as En, J as Dn, Jt as On, K as kn, Kt as An, L as jn, Lt as Mn, M as Nn, Mt as Pn, N as Fn, Nt as In, O as Ln, Ot as Rn, P as zn, Pt as Bn, Qt as Vn, R as Hn, Rt as Un, S as Wn, St as Gn, T as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Zt as ir, _ as ar, _t as or, an as sr, at as cr, b as lr, c as ur, cn as dr, ct as fr, d as pr, dn as mr, dt as hr, en as gr, et as _r, f as vr, fn as yr, ft as br, g as xr, gt as Sr, h as Cr, hn as wr, ht as Tr, in as Er, it as Dr, j as Or, jt as kr, k as Ar, kt as jr, ln as Mr, lt as Nr, m as Pr, mn as Fr, mt as Ir, nn as Lr, nt as Rr, ot as zr, p as Br, pn as Vr, pt as Hr, q as Ur, qt as Wr, r as Gr, rn as Kr, rt as qr, sn as Jr, st as Yr, t as Xr, tn as Zr, tt as Qr, u as $r, un as ei, ut as ti, v as ni, vt as ri, w as ii, wt as ai, x as oi, y as si, yt as ci, z as li, zt as ui } from "./common-FDUX7iSS.js";
11
11
  import { c as di, i as fi, n as pi, o as mi, t as hi } from "./useLifecycle-DPgiiwwU.js";
12
12
  import { t as gi } from "./check-B7cIK6gb.js";
13
13
  import { A as _i, C as vi, D as yi, E as bi, M as xi, O as Si, S as Ci, T as wi, a as Ti, b as Ei, c as Di, i as Oi, k as ki, l as Ai, n as ji, o as Mi, r as Ni, s as Pi, t as Fi, w as Ii, x as Li } from "./select-C6plhmZr.js";
@@ -15,8 +15,8 @@ import { B as Ri, C as zi, J as Bi, Mt as Vi, Nt as Hi, S as Ui, Vt as Wi, W as
15
15
  import { _ as ha, c as ga, d as _a, f as va, g as ya, h as ba, m as xa, u as Sa, v as Ca } from "./toDate-WXg1yLcI.js";
16
16
  import { a as wa, c as Ta, d as Ea, f as Da, i as Oa, l as ka, n as Aa, o as ja, r as Ma, s as Na, t as Pa } from "./process-output-DFe2tVIY.js";
17
17
  import { a as Fa, c as Ia, d as La, i as Ra, l as za, r as Ba, u as Va } from "./glide-utils-B3rUT5PI.js";
18
- import { n as Ha, t as Ua } from "./ErrorBoundary-CacTZz7K.js";
19
- import { n as Wa, t as Ga } from "./globals-C74RodJy.js";
18
+ import { n as Ha, t as Ua } from "./ErrorBoundary-CRcTuG6a.js";
19
+ import { n as Wa, t as Ga } from "./globals-D8pFSo8a.js";
20
20
  import { t as Ka } from "./react-dom-BUgJ-ab_.js";
21
21
  import { t as qa } from "./jsx-runtime-DgDPJV9S.js";
22
22
  import { B as Ja, C as H, Ct as Ya, D as Xa, E as Za, Et as Qa, F as U, G as $a, H as eo, I as to, L as no, M as W, N as ro, O as io, P as ao, Q as oo, R as so, S as G, St as co, Tt as lo, U as uo, W as fo, Y as po, Z as mo, _ as ho, a as go, b as _o, bt as vo, c as yo, d as bo, dt as xo, f as So, g as Co, gt as wo, h as To, i as Eo, j as K, k as Do, l as Oo, m as ko, n as Ao, o as jo, p as Mo, r as No, s as Po, u as Fo, v as Io, vt as Lo, w as Ro, wt as zo, x as Bo, xt as Vo, y as Ho, yt as Uo } from "./use-restore-focus-D5D9xEjZ.js";
@@ -40,7 +40,7 @@ import { t as Ns } from "./useAsyncData-BD6AStMt.js";
40
40
  import { i as Ps, n as Fs, r as Is } from "./formats-BR5iGb4s.js";
41
41
  import { M as Ls, N as Rs } from "./vega-loader.browser-D95WfUcj.js";
42
42
  import { a as zs, n as Bs } from "./loader-D2W4oM-3.js";
43
- import { c as Vs, d as Hs, i as Us, l as Ws, o as Gs, s as Ks, t as qs, u as Js } from "./code-visibility-BBNwvBcV.js";
43
+ import { c as Vs, d as Hs, i as Us, l as Ws, o as Gs, s as Ks, t as qs, u as Js } from "./code-visibility-CCZ-5fSi.js";
44
44
  import { t as Ys } from "./useDeepCompareMemoize-D_HNdyvI.js";
45
45
  import { t as Xs } from "./esm-BElI3qKH.js";
46
46
  //#region ../node_modules/.pnpm/iconify-icon@2.3.0/node_modules/iconify-icon/dist/iconify-icon.mjs
@@ -10458,7 +10458,7 @@ function RC(e, t) {
10458
10458
  }
10459
10459
  //#endregion
10460
10460
  //#region src/plugins/impl/DataEditorPlugin.tsx
10461
- var zC = J.lazy(() => import("./glide-data-editor-CeAOHG6x.js").then((e) => e.t)), BC = xS("marimo-data-editor", { cssStyles: [WS] }).withData(W({
10461
+ var zC = J.lazy(() => import("./glide-data-editor-arTF-cqI.js").then((e) => e.t)), BC = xS("marimo-data-editor", { cssStyles: [WS] }).withData(W({
10462
10462
  initialValue: W({ edits: G(W({
10463
10463
  rowIdx: K(),
10464
10464
  columnId: U(),
@@ -29922,7 +29922,7 @@ function bR(e) {
29922
29922
  }
29923
29923
  //#endregion
29924
29924
  //#region src/components/editor/renderers/slides-layout/slides-layout.tsx
29925
- var xR = J.lazy(() => import("./reveal-component-CRy68FxJ.js")), SR = (e) => {
29925
+ var xR = J.lazy(() => import("./reveal-component-DjevUWwr.js")), SR = (e) => {
29926
29926
  let t = (0, X.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = rs(Ce), s = a === "read" || o, c = rs(xt) > 1, [l, u] = (0, J.useState)(null), d;
29927
29927
  t[0] !== i || t[1] !== n ? (d = vP(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
29928
29928
  let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
@@ -1,6 +1,6 @@
1
1
  import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { t } from "./react-ofJbxM4x.js";
3
- import { a as n } from "./glide-data-editor-CeAOHG6x.js";
3
+ import { a as n } from "./glide-data-editor-arTF-cqI.js";
4
4
  //#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js
5
5
  var r = /* @__PURE__ */ e(t()), i = /*#__PURE__*/ n("div")({
6
6
  name: "NumberOverlayEditorStyle",
@@ -4,13 +4,13 @@ import { t as o } from "./react-ofJbxM4x.js";
4
4
  import { t as s } from "./compiler-runtime-Cj8ZWiNS.js";
5
5
  import { g as c, t as l } from "./tooltip-qB8NlSqV.js";
6
6
  import { G as u, w as d } from "./cells-BSgNCBZ8.js";
7
- import { $ as f, Q as p, Z as m, an as h, i as g, l as _, ln as v, n as y, on as b, r as x, t as S, u as C } from "./common-DM3lblo7.js";
7
+ import { $ as f, Q as p, Z as m, an as h, i as g, l as _, ln as v, n as y, on as b, r as x, t as S, u as C } from "./common-FDUX7iSS.js";
8
8
  import { M as w } from "./select-C6plhmZr.js";
9
9
  import { J as T } from "./dropdown-menu-CZgjYSAh.js";
10
10
  import { t as E } from "./jsx-runtime-DgDPJV9S.js";
11
11
  import { q as ee } from "./use-restore-focus-D5D9xEjZ.js";
12
12
  import { A as D, C as O, b as k } from "./useTheme-DzXmYZ5V.js";
13
- import { a as A, n as te, t as ne } from "./code-visibility-BBNwvBcV.js";
13
+ import { a as A, n as te, t as ne } from "./code-visibility-CCZ-5fSi.js";
14
14
  /* empty css */
15
15
  var j = c("sticky-note", [["path", {
16
16
  d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.24.3-dev40",
3
+ "version": "0.24.3-dev42",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { render } from "@testing-library/react";
3
+ import { render, waitFor } from "@testing-library/react";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { useCellErrors } from "@/core/cells/cells";
6
6
  import { DynamicFavicon } from "../dynamic-favicon";
@@ -14,6 +14,10 @@ describe("DynamicFavicon", () => {
14
14
  let favicon: HTMLLinkElement;
15
15
 
16
16
  beforeEach(() => {
17
+ document
18
+ .querySelectorAll("link[rel~='icon']")
19
+ .forEach((element) => element.remove());
20
+
17
21
  // Mock favicon element
18
22
  favicon = document.createElement("link");
19
23
  favicon.rel = "icon";
@@ -28,7 +32,9 @@ describe("DynamicFavicon", () => {
28
32
  });
29
33
 
30
34
  afterEach(() => {
31
- favicon.remove();
35
+ document
36
+ .querySelectorAll("link[rel~='icon']")
37
+ .forEach((element) => element.remove());
32
38
  vi.clearAllMocks();
33
39
  vi.useRealTimers();
34
40
  });
@@ -113,6 +119,34 @@ describe("DynamicFavicon", () => {
113
119
  expect(newFavicon).not.toBeNull();
114
120
  });
115
121
 
122
+ it("recreates a removed favicon when the window regains focus", async () => {
123
+ render(<DynamicFavicon isRunning={false} />);
124
+ favicon.remove();
125
+
126
+ window.dispatchEvent(new Event("focus"));
127
+
128
+ await waitFor(() => {
129
+ const currentFavicon =
130
+ document.querySelector<HTMLLinkElement>("link[rel~='icon']");
131
+ expect(currentFavicon?.href.endsWith("favicon.ico")).toBe(true);
132
+ });
133
+ });
134
+
135
+ it("resets the current favicon after the link is replaced", async () => {
136
+ render(<DynamicFavicon isRunning={false} />);
137
+ const replacement = document.createElement("link");
138
+ replacement.rel = "icon";
139
+ replacement.href = "./replacement.ico";
140
+ favicon.replaceWith(replacement);
141
+
142
+ window.dispatchEvent(new Event("focus"));
143
+
144
+ await waitFor(() => {
145
+ expect(replacement.href.endsWith("favicon.ico")).toBe(true);
146
+ expect(document.querySelector("link[rel~='icon']")).toBe(replacement);
147
+ });
148
+ });
149
+
116
150
  describe("notifications", () => {
117
151
  beforeEach(() => {
118
152
  vi.spyOn(document, "visibilityState", "get").mockReturnValue("hidden");
@@ -0,0 +1,50 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { EditorState } from "@codemirror/state";
4
+ import { describe, expect, it } from "vitest";
5
+ import { composeRtcEditorConfig } from "../cell-editor";
6
+
7
+ describe("composeRtcEditorConfig", () => {
8
+ it("does not accumulate RTC extensions across reconfigurations", () => {
9
+ const baseExtension = EditorState.readOnly.of(false);
10
+ const baseExtensions = [baseExtension];
11
+ const firstRtc = {
12
+ code: "first RTC code",
13
+ extension: EditorState.tabSize.of(2),
14
+ };
15
+ const secondRtc = {
16
+ code: "second RTC code",
17
+ extension: EditorState.tabSize.of(4),
18
+ };
19
+
20
+ const firstConfig = composeRtcEditorConfig(
21
+ "local code",
22
+ baseExtensions,
23
+ firstRtc,
24
+ );
25
+ const secondConfig = composeRtcEditorConfig(
26
+ "local code",
27
+ baseExtensions,
28
+ secondRtc,
29
+ );
30
+
31
+ expect(baseExtensions).toEqual([baseExtension]);
32
+ expect(firstConfig).toEqual({
33
+ code: firstRtc.code,
34
+ extensions: [baseExtension, firstRtc.extension],
35
+ });
36
+ expect(secondConfig).toEqual({
37
+ code: secondRtc.code,
38
+ extensions: [baseExtension, secondRtc.extension],
39
+ });
40
+ });
41
+
42
+ it("reuses the base configuration when RTC is disabled", () => {
43
+ const extensions = [EditorState.readOnly.of(false)];
44
+
45
+ const config = composeRtcEditorConfig("local code", extensions, undefined);
46
+
47
+ expect(config).toEqual({ code: "local code", extensions });
48
+ expect(config.extensions).toBe(extensions);
49
+ });
50
+ });
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { historyField } from "@codemirror/commands";
3
- import { EditorState, StateEffect } from "@codemirror/state";
3
+ import { EditorState, type Extension, StateEffect } from "@codemirror/state";
4
4
  import { EditorView, ViewPlugin } from "@codemirror/view";
5
5
  import { useIntersectionObserver } from "@uidotdev/usehooks";
6
6
  import { useAtom, useAtomValue } from "jotai";
@@ -95,6 +95,21 @@ export interface CellEditorProps
95
95
  tooltipParentSelector?: string;
96
96
  }
97
97
 
98
+ export function composeRtcEditorConfig(
99
+ code: string,
100
+ extensions: Extension[],
101
+ rtc: ReturnType<typeof realTimeCollaboration> | undefined,
102
+ ): { code: string; extensions: Extension[] } {
103
+ if (!rtc) {
104
+ return { code, extensions };
105
+ }
106
+
107
+ return {
108
+ code: rtc.code,
109
+ extensions: [...extensions, rtc.extension],
110
+ };
111
+ }
112
+
98
113
  const CellEditorInternal = ({
99
114
  theme,
100
115
  showPlaceholder,
@@ -343,27 +358,31 @@ const CellEditorInternal = ({
343
358
  ]);
344
359
 
345
360
  const rtcEnabled = isRtcEnabled() && canUseRtc(cellId);
346
- const handleInitializeEditor = useEvent(() => {
347
- // If rtc is enabled, use collaborative editing
348
- if (rtcEnabled) {
349
- const rtc = realTimeCollaboration(
350
- cellId,
351
- (code) => {
352
- // It's not really a formatting change,
353
- // but this means it won't be marked as stale
354
- cellActions.updateCellCode({ cellId, code, formattingChange: true });
355
- },
356
- code,
357
- );
358
- extensions.push(rtc.extension);
359
- code = rtc.code;
361
+ const createEditorConfig = (initialCode?: string) => {
362
+ if (!rtcEnabled) {
363
+ return composeRtcEditorConfig(code, extensions, undefined);
360
364
  }
361
365
 
366
+ const rtc = realTimeCollaboration(
367
+ cellId,
368
+ (code) => {
369
+ // It's not really a formatting change,
370
+ // but this means it won't be marked as stale
371
+ cellActions.updateCellCode({ cellId, code, formattingChange: true });
372
+ },
373
+ initialCode,
374
+ );
375
+ return composeRtcEditorConfig(code, extensions, rtc);
376
+ };
377
+
378
+ const handleInitializeEditor = useEvent(() => {
379
+ const editorConfig = createEditorConfig(code);
380
+
362
381
  // Create a new editor
363
382
  const ev = new EditorView({
364
383
  state: EditorState.create({
365
- doc: code,
366
- extensions: extensions,
384
+ doc: editorConfig.code,
385
+ extensions: editorConfig.extensions,
367
386
  }),
368
387
  });
369
388
  setEditorView(ev);
@@ -377,19 +396,11 @@ const CellEditorInternal = ({
377
396
 
378
397
  const handleReconfigureEditor = useEvent(() => {
379
398
  invariant(editorViewRef.current !== null, "Editor view is not initialized");
380
- // If rtc is enabled, use collaborative editing
381
- if (rtcEnabled) {
382
- const rtc = realTimeCollaboration(cellId, (code) => {
383
- // It's not really a formatting change,
384
- // but this means it won't be marked as stale
385
- cellActions.updateCellCode({ cellId, code, formattingChange: true });
386
- });
387
- extensions.push(rtc.extension);
388
- }
399
+ const editorConfig = createEditorConfig();
389
400
 
390
401
  editorViewRef.current.dispatch({
391
402
  effects: [
392
- StateEffect.reconfigure.of([extensions]),
403
+ StateEffect.reconfigure.of([editorConfig.extensions]),
393
404
  reconfigureLanguageEffect(editorViewRef.current, {
394
405
  completionConfig: userConfig.completion,
395
406
  hotkeysProvider: new OverridingHotkeyProvider(
@@ -406,26 +417,14 @@ const CellEditorInternal = ({
406
417
 
407
418
  const handleDeserializeEditor = useEvent(() => {
408
419
  invariant(serializedEditorState, "Editor view is not initialized");
409
- if (rtcEnabled) {
410
- const rtc = realTimeCollaboration(
411
- cellId,
412
- (code) => {
413
- // It's not really a formatting change,
414
- // but this means it won't be marked as stale
415
- cellActions.updateCellCode({ cellId, code, formattingChange: true });
416
- },
417
- code,
418
- );
419
- extensions.push(rtc.extension);
420
- code = rtc.code;
421
- }
420
+ const editorConfig = createEditorConfig(code);
422
421
 
423
422
  const ev = new EditorView({
424
423
  state: EditorState.fromJSON(
425
424
  serializedEditorState,
426
425
  {
427
- doc: code,
428
- extensions: extensions,
426
+ doc: editorConfig.code,
427
+ extensions: editorConfig.extensions,
429
428
  },
430
429
  { history: historyField },
431
430
  ),
@@ -22,6 +22,19 @@ async function getFaviconUrl(key: FaviconKey): Promise<string> {
22
22
  return FAVICON_PATHS[key];
23
23
  }
24
24
 
25
+ function getOrCreateFavicon(): HTMLLinkElement {
26
+ const existingFavicon =
27
+ document.querySelector<HTMLLinkElement>("link[rel~='icon']");
28
+ if (existingFavicon) {
29
+ return existingFavicon;
30
+ }
31
+
32
+ const favicon = document.createElement("link");
33
+ favicon.rel = "icon";
34
+ document.head.append(favicon);
35
+ return favicon;
36
+ }
37
+
25
38
  interface Props {
26
39
  isRunning: boolean;
27
40
  }
@@ -64,16 +77,9 @@ export const DynamicFavicon = (props: Props) => {
64
77
  const { isRunning } = props;
65
78
  const errors = useCellErrors();
66
79
 
67
- let favicon: HTMLLinkElement | null =
68
- document.querySelector("link[rel~='icon']");
69
-
70
- if (!favicon) {
71
- favicon = document.createElement("link");
72
- favicon.rel = "icon";
73
- document.getElementsByTagName("head")[0].append(favicon);
74
- }
75
-
76
80
  useEffect(() => {
81
+ const favicon = getOrCreateFavicon();
82
+
77
83
  // No change on startup (autorun enabled or not)
78
84
  // Treat the default marimo favicon as "idle"
79
85
  if (!isRunning && favicon.href.includes("favicon")) {
@@ -105,7 +111,7 @@ export const DynamicFavicon = (props: Props) => {
105
111
  };
106
112
 
107
113
  updateFavicon();
108
- }, [isRunning, errors, favicon]);
114
+ }, [isRunning, errors]);
109
115
 
110
116
  // Send user notification when run has completed
111
117
  const prevRunning = usePrevious(isRunning) ?? isRunning;
@@ -118,6 +124,7 @@ export const DynamicFavicon = (props: Props) => {
118
124
  // When notebook comes back in focus, reset favicon
119
125
  useEventListener(window, "focus", async (_) => {
120
126
  if (!isRunning) {
127
+ const favicon = getOrCreateFavicon();
121
128
  favicon.href = await getFaviconUrl("idle");
122
129
  }
123
130
  });
@@ -6,31 +6,36 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { TooltipProvider } from "@/components/ui/tooltip";
7
7
  import { GlideDataEditor } from "../glide-data-editor";
8
8
 
9
- const capturedDataEditor = vi.hoisted(() => ({
10
- ref: undefined as React.RefObject<HTMLElement> | undefined,
11
- onCellEdited: undefined as
12
- | ((cell: [number, number], value: { data: unknown }) => void)
13
- | undefined,
14
- onRowAppended: undefined as (() => void) | undefined,
15
- }));
9
+ interface MockDataEditorProps {
10
+ portalElementRef?: React.RefObject<HTMLElement>;
11
+ onCellEdited?: (cell: [number, number], value: { data: unknown }) => void;
12
+ onRowAppended?: () => void;
13
+ }
14
+
15
+ const captureDataEditorProps = vi.hoisted(() =>
16
+ vi.fn<(props: MockDataEditorProps) => void>(),
17
+ );
18
+
19
+ const capturedDataEditor = {
20
+ get ref() {
21
+ return captureDataEditorProps.mock.lastCall?.[0].portalElementRef;
22
+ },
23
+ get onCellEdited() {
24
+ return captureDataEditorProps.mock.lastCall?.[0].onCellEdited;
25
+ },
26
+ get onRowAppended() {
27
+ return captureDataEditorProps.mock.lastCall?.[0].onRowAppended;
28
+ },
29
+ };
16
30
 
17
31
  vi.mock("@glideapps/glide-data-grid", async () => {
18
32
  const React = await import("react");
19
33
  return {
20
34
  default: React.forwardRef(function MockDataEditor(
21
- props: {
22
- portalElementRef?: React.RefObject<HTMLElement>;
23
- onCellEdited?: (
24
- cell: [number, number],
25
- value: { data: unknown },
26
- ) => void;
27
- onRowAppended?: () => void;
28
- },
35
+ props: MockDataEditorProps,
29
36
  _ref: React.Ref<HTMLDivElement>,
30
37
  ) {
31
- capturedDataEditor.ref = props.portalElementRef;
32
- capturedDataEditor.onCellEdited = props.onCellEdited;
33
- capturedDataEditor.onRowAppended = props.onRowAppended;
38
+ captureDataEditorProps(props);
34
39
  return <div data-testid="mock-data-editor" />;
35
40
  }),
36
41
  CompactSelection: {