@marimo-team/islands 0.25.3-dev102 → 0.25.3-dev103

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-CoLi84rn.js").then((e) => ({ default: e.FeedbackModal }))), L = (e) => {
138
+ var I = (0, T.lazy)(() => import("./feedback-button-9Gkf9CUC.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,
@@ -3,13 +3,13 @@ import { c as n, g as r, h as i, t as a } from "./button-BxB3RDjN.js";
3
3
  import { t as o } from "./react-sHtZdjeP.js";
4
4
  import { t as s } from "./compiler-runtime-CldRdSsv.js";
5
5
  import { i as c, t as l } from "./copy-icon-CPoxUG7G.js";
6
- import { F as u, H as d, U as f, V as p, X as m, Y as h, Z as g, _, a as v, c as y, d as b, f as x, g as S, h as C, l as w, m as T, n as E, p as D, r as O, t as k, v as A, x as ee, y as te, z as ne } from "./html-to-image-CWdayGZU.js";
6
+ import { F as u, H as d, U as f, V as p, X as m, Y as h, Z as g, _, a as v, c as y, d as b, f as x, g as S, h as C, l as w, m as T, n as E, p as D, r as O, t as k, v as A, x as ee, y as te, z as ne } from "./html-to-image-DQn1HTpP.js";
7
7
  import { h as j, t as re } from "./tooltip-CKO1pHJo.js";
8
- import { Ct as ie, Et as ae, Ht as oe, L as se, Lt as ce, Nt as le, St as ue, X as de, cn as fe, ct as pe, dn as me, en as he, et as ge, h as _e, i as ve, ln as ye, mn as be, mt as xe, r as Se, rt as Ce, st as we, t as Te, w as Ee, xt as De } from "./error-entries-TAO6kEhF.js";
8
+ import { Ct as ie, Et as ae, Ht as oe, L as se, Lt as ce, Nt as le, St as ue, X as de, cn as fe, ct as pe, dn as me, en as he, et as ge, h as _e, i as ve, ln as ye, mn as be, mt as xe, r as Se, rt as Ce, st as we, t as Te, w as Ee, xt as De } from "./error-entries-m7wjcSR-.js";
9
9
  import { A as Oe, C as ke, M as Ae, b as je } from "./select-B0LRDH0g.js";
10
10
  import { _ as Me, f as Ne, h as Pe, i as Fe, n as Ie, t as Le } from "./dropdown-menu-B_7xD2X4.js";
11
11
  import { C as Re, d as ze } from "./toDate-DKH0_kMo.js";
12
- import { d as Be, f as Ve, i as He, n as Ue, t as We, u as Ge } from "./process-output-DD-oSvZk.js";
12
+ import { d as Be, f as Ve, i as He, n as Ue, t as We, u as Ge } from "./process-output-DIMk7xHI.js";
13
13
  import { t as Ke } from "./jsx-runtime-3BD1kT0s.js";
14
14
  import { A as qe, C as Je, D as Ye, E as Xe, F as Ze, L as Qe, M as $e, O as et, P as tt, R as nt, S as rt, T as it, V as at, b as ot, j as st, v as ct, w as lt, x as ut, y as dt, z as ft } from "./use-restore-focus-DBCITLeF.js";
15
15
  import { n as pt } from "./error-banner-Sl2FlSJ4.js";
@@ -1,6 +1,6 @@
1
1
  import { g as e } from "./button-BxB3RDjN.js";
2
2
  import { t } from "./compiler-runtime-CldRdSsv.js";
3
- import { G as n, K as r } from "./error-entries-TAO6kEhF.js";
3
+ import { G as n, K as r } from "./error-entries-m7wjcSR-.js";
4
4
  import { C as i } from "./select-B0LRDH0g.js";
5
5
  import { _ as a, c as o, x as s, y as c } from "./toDate-DKH0_kMo.js";
6
6
  import { C as l, E as u, a as d, b as f } from "./useTheme-CfeaVw5D.js";
@@ -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.25.3-dev102", C = u(S), w = u(!0);
21
+ var S = x() || "0.25.3-dev103", C = u(S), w = u(!0);
22
22
  u(null);
23
23
  //#endregion
24
24
  //#region src/core/wasm/state.ts
@@ -3,16 +3,16 @@ import { _ as t, c as n, f as r, g as i, h as a, m as o, n as s, p as c, r as l,
3
3
  import { t as d } from "./react-sHtZdjeP.js";
4
4
  import { t as f } from "./compiler-runtime-CldRdSsv.js";
5
5
  import { i as p, n as m, r as h, t as g } from "./copy-icon-CPoxUG7G.js";
6
- import { $ as _, A as v, B as y, D as b, E as x, F as S, H as C, M as w, N as T, Q as E, S as D, T as O, V as k, W as A, X as j, Y as M, Z as N, _ as P, g as F, n as I, r as L, s as R, t as z, u as B, v as ee, w as V, y as te, z as ne } from "./html-to-image-CWdayGZU.js";
6
+ import { $ as _, A as v, B as y, D as b, E as x, F as S, H as C, M as w, N as T, Q as E, S as D, T as O, V as k, W as A, X as j, Y as M, Z as N, _ as P, g as F, n as I, r as L, s as R, t as z, u as B, v as ee, w as V, y as te, z as ne } from "./html-to-image-DQn1HTpP.js";
7
7
  import { a as re, d as ie, h as H, n as ae, o as oe, r as se, t as U } from "./tooltip-CKO1pHJo.js";
8
- import { $ as ce, A as le, D as ue, Et as de, F as fe, G as pe, Ht as me, I as he, J as ge, K as _e, L as ve, Ot as ye, Rt as be, T as xe, Ut as Se, V as Ce, Xt as we, Yt as Te, Zt as Ee, _t as De, an as Oe, at as ke, b as Ae, c as je, cn as Me, dn as Ne, en as Pe, gn as Fe, gt as Ie, h as Le, hn as Re, l as ze, ln as Be, lt as Ve, mn as He, mt as Ue, on as We, ot as Ge, rn as Ke, st as qe, t as Je, un as Ye, vt as Xe, w as Ze, x as Qe, y as $e } from "./error-entries-TAO6kEhF.js";
8
+ import { $ as ce, A as le, D as ue, Et as de, F as fe, G as pe, Ht as me, I as he, J as ge, K as _e, L as ve, Ot as ye, Rt as be, T as xe, Ut as Se, V as Ce, Xt as we, Yt as Te, Zt as Ee, _t as De, an as Oe, at as ke, b as Ae, c as je, cn as Me, dn as Ne, en as Pe, gn as Fe, gt as Ie, h as Le, hn as Re, l as ze, ln as Be, lt as Ve, mn as He, mt as Ue, on as We, ot as Ge, rn as Ke, st as qe, t as Je, un as Ye, vt as Xe, w as Ze, x as Qe, y as $e } from "./error-entries-m7wjcSR-.js";
9
9
  import { c as et, i as tt, r as nt } from "./useLifecycle-Dum8WSXq.js";
10
10
  import { a as rt, i as it, n as at, o as ot, r as st, t as ct } from "./useDateFormatter-c_yJtkAe.js";
11
11
  import { t as lt } from "./check-P35dc2dH.js";
12
12
  import { A as ut, C as dt, M as ft, O as pt, S as mt, _ as ht, b as gt, c as _t, d as vt, g as yt, h as bt, i as xt, l as St, m as Ct, n as wt, p as Tt, s as Et, t as Dt, u as Ot, v as kt, y as At } from "./select-B0LRDH0g.js";
13
13
  import { $ as jt, $t as Mt, A as Nt, At as Pt, B as Ft, Bt as It, Ct as Lt, D as Rt, Dt as zt, E as Bt, Et as Vt, F as Ht, Ft as Ut, G as Wt, Gt, H as Kt, Ht as qt, I as Jt, It as Yt, J as Xt, Jt as Zt, Kt as Qt, L as $t, Lt as en, M as tn, Mt as nn, N as rn, Nt as an, O as on, Ot as sn, P as cn, Pt as ln, Q as un, Qt as dn, R as fn, Rt as pn, S as mn, St as hn, T as gn, Tt as _n, U as vn, Ut as yn, V as bn, Vt as xn, W as Sn, Wt as Cn, X as wn, Xt as Tn, Y as En, Yt as Dn, Z as On, Zt as kn, _ as An, _n as jn, _t as Mn, a as Nn, an as Pn, at as Fn, b as In, bt as Ln, cn as Rn, ct as zn, d as Bn, dn as Vn, en as Hn, et as Un, f as Wn, fn as Gn, ft as Kn, gn as qn, gt as Jn, h as Yn, hn as Xn, ht as Zn, i as Qn, in as $n, it as er, j as tr, jt as nr, k as rr, kt as ir, l as ar, ln as or, lt as sr, mn as cr, mt as lr, n as ur, nn as dr, nt as fr, o as pr, ot as mr, pn as hr, pt as gr, qt as _r, rn as vr, rt as yr, s as br, sn as xr, st as Sr, t as Cr, tt as wr, u as Tr, ut as Er, vn as Dr, vt as Or, w as kr, wt as Ar, x as jr, xt as Mr, y as Nr, yn as Pr, yt as Fr, z as Ir, zt as Lr } from "./dropdown-menu-B_7xD2X4.js";
14
14
  import { a as Rr, c as zr, d as Br, i as Vr, l as Hr, n as Ur, o as Wr, r as Gr, t as Kr, v as qr, x as Jr, y as Yr } from "./toDate-DKH0_kMo.js";
15
- import { n as Xr, t as Zr } from "./ErrorBoundary-CTqzlCgA.js";
15
+ import { n as Xr, t as Zr } from "./ErrorBoundary-DvJIREfH.js";
16
16
  import { t as Qr } from "./clsx-CoeGtWvn.js";
17
17
  import { t as $r } from "./react-dom-DUmH0GXv.js";
18
18
  import { t as ei } from "./jsx-runtime-3BD1kT0s.js";
@@ -2,7 +2,7 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
2
2
  import { t } from "./react-sHtZdjeP.js";
3
3
  import { t as n } from "./react-dom-DUmH0GXv.js";
4
4
  import { c as r, l as i, u as a } from "./data-grid-types-0PJ-9N5n.js";
5
- import { a as o, i as s, n as c, r as l } from "./glide-data-editor-bMB-b4l2.js";
5
+ import { a as o, i as s, n as c, r as l } from "./glide-data-editor-D2F4hynZ.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.3.0_27cffe7f1bc30287d59ac27db3240c5b/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",
@@ -20670,7 +20670,7 @@ var xf = class e {
20670
20670
  "redo"
20671
20671
  ];
20672
20672
  function Cf({ docChanged: e, transactions: t, predicate: n = () => !0, hasFocus: r = !1 }) {
20673
- return !e || !n() || t.some((e) => e.effects.some((e) => e.is(Eu))) ? !1 : t.some((e) => e.annotation(hd) === void 0 ? Sf.some((t) => e.isUserEvent(t)) || r : !1);
20673
+ return !e || !n() || t.some((e) => e.effects.some((e) => e.is(Eu))) ? !1 : t.some((e) => e.annotation(at.remote) || e.annotation(hd) !== void 0 ? !1 : Sf.some((t) => e.isUserEvent(t)) || r);
20674
20674
  }
20675
20675
  function wf({ predicate: e }) {
20676
20676
  return He.updateListener.of((t) => {
@@ -3,14 +3,14 @@ import { h as t, t as n } from "./button-BxB3RDjN.js";
3
3
  import { t as r } from "./react-sHtZdjeP.js";
4
4
  import { t as i } from "./compiler-runtime-CldRdSsv.js";
5
5
  import { t as a } from "./copy-icon-CPoxUG7G.js";
6
- import { Dt as o, G as s, It as c, K as l, Rt as u, V as d, dn as f, h as p, i as m, n as h } from "./error-entries-TAO6kEhF.js";
6
+ import { Dt as o, G as s, It as c, K as l, Rt as u, V as d, dn as f, h as p, i as m, n as h } from "./error-entries-m7wjcSR-.js";
7
7
  import { b as g } from "./select-B0LRDH0g.js";
8
- import { n as ee, t as te } from "./globals-CmP7eFM1.js";
8
+ import { n as ee, t as te } from "./globals-BBV0iOn6.js";
9
9
  import { t as _ } from "./jsx-runtime-3BD1kT0s.js";
10
10
  import { C as v, _ as ne, i as y } from "./useTheme-CfeaVw5D.js";
11
11
  import { a as re, i as ie, n as ae, r as oe } from "./dialog-C1PcpCqX.js";
12
12
  import { t as se } from "./useAsyncData-D0mLJstx.js";
13
- import { t as ce } from "./code-visibility-BXe6iFL5.js";
13
+ import { t as ce } from "./code-visibility-CfeBTR2t.js";
14
14
  //#region src/components/ui/skeleton.tsx
15
15
  var b = /* @__PURE__ */ e(r(), 1), x = i(), S = _(), le = (e) => {
16
16
  let n = (0, x.c)(8), r, i;
@@ -6,7 +6,7 @@ import { i as s, n as c, r as l } from "./copy-icon-CPoxUG7G.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-B0LRDH0g.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-B_7xD2X4.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-Bt8_pBa2.js";
9
- import { n as ne, t as re } from "./ErrorBoundary-CTqzlCgA.js";
9
+ import { n as ne, t as re } from "./ErrorBoundary-DvJIREfH.js";
10
10
  import { t as ie } from "./react-dom-DUmH0GXv.js";
11
11
  import { t as M } from "./jsx-runtime-3BD1kT0s.js";
12
12
  import { t as N } from "./label-BRnUC0NX.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.3.0_27cffe7f1bc30287d59ac27db3240c5b/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-D-lYQf1c.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-DEC7lFIy.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.3.0_27cffe7f1bc30287d59ac27db3240c5b/node_modules/@glideapps/glide-data-grid/dist/esm/cells/number-cell.js
9296
- var Hs = K.lazy(async () => await import("./number-overlay-editor-JFjUfbs3.js")), Us = {
9296
+ var Hs = K.lazy(async () => await import("./number-overlay-editor-B3XQt_PV.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 { h as e } from "./tooltip-CKO1pHJo.js";
2
2
  import { _ as t } from "./useTheme-CfeaVw5D.js";
3
- import { r as n } from "./code-visibility-BXe6iFL5.js";
3
+ import { r as n } from "./code-visibility-CfeBTR2t.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",
@@ -5,7 +5,7 @@ import { t as l } from "./compiler-runtime-CldRdSsv.js";
5
5
  import { n as u, t as d } from "./copy-icon-CPoxUG7G.js";
6
6
  import { a as f, c as p, l as m, s as h, t as g } from "./chunk-YOKDWASO-DKplTrBY.js";
7
7
  import { c as _, h as v, l as y, s as b, t as x, u as ee } from "./tooltip-CKO1pHJo.js";
8
- import { A as te, Ct as ne, Et as re, H as ie, M as ae, P as oe, Pt as se, Qt as ce, St as le, Tt as ue, U as de, Y as fe, a as pe, bt as me, d as he, it as ge, pt as _e, tn as ve, tt as ye, xt as be, y as xe, yt as Se } from "./error-entries-TAO6kEhF.js";
8
+ import { A as te, Ct as ne, Et as re, H as ie, M as ae, P as oe, Pt as se, Qt as ce, St as le, Tt as ue, U as de, Y as fe, a as pe, bt as me, d as he, it as ge, pt as _e, tn as ve, tt as ye, xt as be, y as xe, yt as Se } from "./error-entries-m7wjcSR-.js";
9
9
  import { M as Ce, O as we, k as Te } from "./select-B0LRDH0g.js";
10
10
  import { ft as Ee, q as De } from "./dropdown-menu-B_7xD2X4.js";
11
11
  import { m as Oe, y as ke } from "./toDate-DKH0_kMo.js";
package/dist/main.js CHANGED
@@ -3,20 +3,20 @@ import { _ as a, c as o, g as s, h as c, i as l, l as u, m as d, n as f, o as p,
3
3
  import { t as v } from "./react-sHtZdjeP.js";
4
4
  import { t as y } from "./compiler-runtime-CldRdSsv.js";
5
5
  import { i as b, n as x, r as S, t as C } from "./copy-icon-CPoxUG7G.js";
6
- import { C as w, F as T, G as E, I as D, J as O, K as k, L as A, P as j, R as M, X as N, Z as P, _ as F, a as I, b as L, g as R, i as z, j as B, k as ee, n as te, o as ne, q as re, v as ie, y as ae } from "./html-to-image-CWdayGZU.js";
6
+ import { C as w, F as T, G as E, I as D, J as O, K as k, L as A, P as j, R as M, X as N, Z as P, _ as F, a as I, b as L, g as R, i as z, j as B, k as ee, n as te, o as ne, q as re, v as ie, y as ae } from "./html-to-image-DQn1HTpP.js";
7
7
  import { a as oe, d as se, f as ce, h as V, i as le, n as ue, o as de, r as fe, t as pe } from "./tooltip-CKO1pHJo.js";
8
- import { $t as me, At as he, B as ge, Bt as _e, D as ve, Dt as ye, E as be, Et as xe, Ft as Se, Gt as Ce, J as we, Jt as Te, Kt as Ee, M as De, Mt as Oe, N as ke, O as Ae, Ot as je, Q as Me, R as Ne, S as Pe, Ut as Fe, V as Ie, Vt as Le, W as Re, Y as ze, Yt as Be, Z as Ve, _ as He, b as Ue, bt as We, cn as Ge, dt as Ke, en as qe, f as Je, fn as Ye, ft as Xe, g as Ze, gn as Qe, gt as $e, h as et, hn as tt, ht as nt, in as rt, j as it, jt as at, k as ot, kt as st, ln as ct, m as lt, nn as ut, nt as dt, o as ft, p as pt, pn as mt, q as ht, qt as gt, rt as _t, s as vt, sn as yt, st as bt, tn as xt, u as St, un as Ct, ut as wt, v as Tt, wt as Et, y as Dt, z as Ot, zt as kt } from "./error-entries-TAO6kEhF.js";
8
+ import { $t as me, At as he, B as ge, Bt as _e, D as ve, Dt as ye, E as be, Et as xe, Ft as Se, Gt as Ce, J as we, Jt as Te, Kt as Ee, M as De, Mt as Oe, N as ke, O as Ae, Ot as je, Q as Me, R as Ne, S as Pe, Ut as Fe, V as Ie, Vt as Le, W as Re, Y as ze, Yt as Be, Z as Ve, _ as He, b as Ue, bt as We, cn as Ge, dt as Ke, en as qe, f as Je, fn as Ye, ft as Xe, g as Ze, gn as Qe, gt as $e, h as et, hn as tt, ht as nt, in as rt, j as it, jt as at, k as ot, kt as st, ln as ct, m as lt, nn as ut, nt as dt, o as ft, p as pt, pn as mt, q as ht, qt as gt, rt as _t, s as vt, sn as yt, st as bt, tn as xt, u as St, un as Ct, ut as wt, v as Tt, wt as Et, y as Dt, z as Ot, zt as kt } from "./error-entries-m7wjcSR-.js";
9
9
  import { D as At, E as jt, O as Mt, S as Nt, T as Pt, _ as Ft, a as It, c as Lt, d as Rt, f as zt, g as Bt, h as Vt, i as Ht, l as Ut, m as Wt, n as Gt, o as Kt, p as qt, r as Jt, s as Yt, t as Xt, u as Zt, v as Qt, w as $t, x as en, y as tn } from "./spec-CcT0slVc.js";
10
- import { $ as nn, $t as rn, A as an, At as on, B as sn, Bt as cn, C as ln, Ct as un, D as dn, Dt as fn, E as pn, Et as mn, F as hn, Ft as gn, G as _n, Gt as vn, H as yn, Ht as bn, I as xn, It as Sn, J as Cn, Jt as wn, K as Tn, Kt as En, L as Dn, Lt as On, M as kn, Mt as An, N as jn, Nt as Mn, O as Nn, Ot as Pn, P as Fn, Pt as In, Qt as Ln, R as Rn, Rt as zn, S as Bn, St as Vn, T as Hn, Tt as Un, U as Wn, Ut as Gn, V as Kn, Vt as qn, W as Jn, Wt as Yn, Xt as Xn, Y as Zn, Yt as Qn, Zt as $n, _ as er, _t as tr, at as nr, b as rr, c as ir, cn as ar, ct as or, d as sr, dn as cr, dt as lr, en as ur, et as dr, f as fr, fn as pr, ft as mr, g as hr, gt as gr, h as _r, ht as vr, in as yr, it as br, j as xr, jt as Sr, k as Cr, kt as wr, ln as Tr, lt as Er, m as Dr, mn as Or, mt as kr, nn as Ar, nt as jr, on as Mr, ot as Nr, p as Pr, pn as Fr, pt as Ir, q as Lr, qt as Rr, r as zr, rn as Br, rt as Vr, sn as Hr, st as Ur, t as Wr, tn as Gr, tt as Kr, u as qr, un as Jr, ut as Yr, v as Xr, vt as Zr, w as Qr, wt as $r, x as ei, xt as ti, y as ni, z as ri, zt as ii } from "./common-DKpWKeru.js";
10
+ import { $ as nn, $t as rn, A as an, At as on, B as sn, Bt as cn, C as ln, Ct as un, D as dn, Dt as fn, E as pn, Et as mn, F as hn, Ft as gn, G as _n, Gt as vn, H as yn, Ht as bn, I as xn, It as Sn, J as Cn, Jt as wn, K as Tn, Kt as En, L as Dn, Lt as On, M as kn, Mt as An, N as jn, Nt as Mn, O as Nn, Ot as Pn, P as Fn, Pt as In, Qt as Ln, R as Rn, Rt as zn, S as Bn, St as Vn, T as Hn, Tt as Un, U as Wn, Ut as Gn, V as Kn, Vt as qn, W as Jn, Wt as Yn, Xt as Xn, Y as Zn, Yt as Qn, Zt as $n, _ as er, _t as tr, at as nr, b as rr, c as ir, cn as ar, ct as or, d as sr, dn as cr, dt as lr, en as ur, et as dr, f as fr, fn as pr, ft as mr, g as hr, gt as gr, h as _r, ht as vr, in as yr, it as br, j as xr, jt as Sr, k as Cr, kt as wr, ln as Tr, lt as Er, m as Dr, mn as Or, mt as kr, nn as Ar, nt as jr, on as Mr, ot as Nr, p as Pr, pn as Fr, pt as Ir, q as Lr, qt as Rr, r as zr, rn as Br, rt as Vr, sn as Hr, st as Ur, t as Wr, tn as Gr, tt as Kr, u as qr, un as Jr, ut as Yr, v as Xr, vt as Zr, w as Qr, wt as $r, x as ei, xt as ti, y as ni, z as ri, zt as ii } from "./common-BRSsfrNv.js";
11
11
  import { c as ai, i as oi, n as si, o as ci, t as li } from "./useLifecycle-Dum8WSXq.js";
12
12
  import { t as ui } from "./check-P35dc2dH.js";
13
13
  import { A as di, C as fi, D as pi, E as mi, M as hi, O as gi, S as _i, T as vi, a as yi, b as bi, c as xi, i as Si, k as Ci, l as wi, n as Ti, o as Ei, r as Di, s as Oi, t as ki, w as Ai, x as ji } from "./select-B0LRDH0g.js";
14
14
  import { At as Mi, C as Ni, Dt as Pi, J as Fi, K as Ii, R as Li, S as Ri, U as zi, X as Bi, Y as Vi, _ as Hi, a as Ui, c as Wi, dt as Gi, et as Ki, f as qi, g as Ji, h as Yi, ht as Xi, i as Zi, m as Qi, n as $i, p as ea, q as ta, r as na, s as ra, t as ia, v as aa, vn as oa, x as sa, yn as ca, z as la } from "./dropdown-menu-B_7xD2X4.js";
15
15
  import { C as ua, _ as da, a as fa, b as pa, c as ma, f as ha, h as ga, m as _a, p as va, s as ya, u as ba, x as xa, y as Sa } from "./toDate-DKH0_kMo.js";
16
- import { a as Ca, c as wa, d as Ta, f as Ea, i as Da, l as Oa, n as ka, o as Aa, r as ja, s as Ma, t as Na } from "./process-output-DD-oSvZk.js";
16
+ import { a as Ca, c as wa, d as Ta, f as Ea, i as Da, l as Oa, n as ka, o as Aa, r as ja, s as Ma, t as Na } from "./process-output-DIMk7xHI.js";
17
17
  import { a as Pa, c as Fa, d as Ia, i as La, l as Ra, r as za, u as Ba } from "./glide-utils-Bt8_pBa2.js";
18
- import { n as Va, t as Ha } from "./ErrorBoundary-CTqzlCgA.js";
19
- import { n as Ua, t as Wa } from "./globals-CmP7eFM1.js";
18
+ import { n as Va, t as Ha } from "./ErrorBoundary-DvJIREfH.js";
19
+ import { n as Ua, t as Wa } from "./globals-BBV0iOn6.js";
20
20
  import { t as Ga } from "./react-dom-DUmH0GXv.js";
21
21
  import { t as Ka } from "./jsx-runtime-3BD1kT0s.js";
22
22
  import { B as qa, C as H, Ct as Ja, D as Ya, E as Xa, F as U, G as Za, H as Qa, I as $a, L as eo, M as W, N as to, O as no, P as ro, Q as io, R as ao, S as G, St as oo, Tt as so, U as co, W as lo, Y as uo, Z as fo, _ as po, _t as mo, a as ho, b as go, bt as _o, c as vo, d as yo, f as bo, g as xo, h as So, ht as Co, i as wo, j as K, k as To, l as Eo, m as Do, n as Oo, o as ko, p as Ao, r as jo, s as Mo, u as No, ut as Po, v as Fo, vt as Io, w as Lo, wt as Ro, x as zo, xt as Bo, y as Vo, yt as Ho } from "./use-restore-focus-DBCITLeF.js";
@@ -39,7 +39,7 @@ import { t as Ms } from "./useAsyncData-D0mLJstx.js";
39
39
  import { i as Ns, n as Ps, r as Fs } from "./formats-V8ifA0Bk.js";
40
40
  import { D as Is, O as Ls } from "./vega-loader.browser-B9Pz0EGP.js";
41
41
  import { a as Rs, n as zs } from "./loader-Bm8cy3e8.js";
42
- import { c as Bs, d as Vs, i as Hs, l as Us, o as Ws, s as Gs, t as Ks, u as qs } from "./code-visibility-BXe6iFL5.js";
42
+ import { c as Bs, d as Vs, i as Hs, l as Us, o as Ws, s as Gs, t as Ks, u as qs } from "./code-visibility-CfeBTR2t.js";
43
43
  import { t as Js } from "./useDeepCompareMemoize-BpPWi3G3.js";
44
44
  import { t as Ys } from "./esm-D2gTsXEV.js";
45
45
  //#region ../node_modules/.pnpm/iconify-icon@2.3.0/node_modules/iconify-icon/dist/iconify-icon.mjs
@@ -10112,7 +10112,7 @@ var LS = (e) => {
10112
10112
  output: t
10113
10113
  };
10114
10114
  } };
10115
- } }, US = J.lazy(() => import("./chat-ui-BIgPxfPP.js").then((e) => ({ default: e.Chatbot }))), WS = G(W({
10115
+ } }, US = J.lazy(() => import("./chat-ui-BjFJNtik.js").then((e) => ({ default: e.Chatbot }))), WS = G(W({
10116
10116
  id: U(),
10117
10117
  role: Fo([
10118
10118
  "system",
@@ -10578,7 +10578,7 @@ function VC(e, t) {
10578
10578
  }
10579
10579
  //#endregion
10580
10580
  //#region src/plugins/impl/DataEditorPlugin.tsx
10581
- var HC = J.lazy(() => import("./glide-data-editor-bMB-b4l2.js").then((e) => e.t)), UC = wS("marimo-data-editor", { cssStyles: [qS] }).withData(W({
10581
+ var HC = J.lazy(() => import("./glide-data-editor-D2F4hynZ.js").then((e) => e.t)), UC = wS("marimo-data-editor", { cssStyles: [qS] }).withData(W({
10582
10582
  initialValue: W({ edits: G(W({
10583
10583
  rowIdx: K(),
10584
10584
  columnId: U(),
@@ -30305,7 +30305,7 @@ function VR(e) {
30305
30305
  }
30306
30306
  //#endregion
30307
30307
  //#region src/components/editor/renderers/slides-layout/slides-layout.tsx
30308
- var HR = J.lazy(() => import("./reveal-component-BT1D05hU.js")), UR = (e) => {
30308
+ var HR = J.lazy(() => import("./reveal-component-DEasliPP.js")), UR = (e) => {
30309
30309
  let t = (0, X.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = ns(da), s = a === "read" || o, c = ns(He) > 1, [l, u] = (0, J.useState)(null), d;
30310
30310
  t[0] !== i || t[1] !== n ? (d = zP(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
30311
30311
  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-sHtZdjeP.js";
3
- import { a as n } from "./glide-data-editor-bMB-b4l2.js";
3
+ import { a as n } from "./glide-data-editor-D2F4hynZ.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.3.0_27cffe7f1bc30287d59ac27db3240c5b/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",
@@ -1,6 +1,6 @@
1
- import { O as e } from "./html-to-image-CWdayGZU.js";
1
+ import { O as e } from "./html-to-image-DQn1HTpP.js";
2
2
  import { h as t } from "./tooltip-CKO1pHJo.js";
3
- import { C as n, w as r } from "./error-entries-TAO6kEhF.js";
3
+ import { C as n, w as r } from "./error-entries-m7wjcSR-.js";
4
4
  import { b as i } from "./select-B0LRDH0g.js";
5
5
  import { n as a } from "./semaphore-89jsoHtS.js";
6
6
  var o = t("file", [["path", {
@@ -3,15 +3,15 @@ import { c as t, g as n, h as r, m as i, t as a } from "./button-BxB3RDjN.js";
3
3
  import { t as o } from "./react-sHtZdjeP.js";
4
4
  import { t as s } from "./compiler-runtime-CldRdSsv.js";
5
5
  import { h as c, t as l } from "./tooltip-CKO1pHJo.js";
6
- import { O as u } from "./error-entries-TAO6kEhF.js";
7
- import { Q as d, X as f, Z as p, an as m, cn as h, i as g, in as _, l as v, n as y, r as b, t as x, u as S } from "./common-DKpWKeru.js";
6
+ import { O as u } from "./error-entries-m7wjcSR-.js";
7
+ import { Q as d, X as f, Z as p, an as m, cn as h, i as g, in as _, l as v, n as y, r as b, t as x, u as S } from "./common-BRSsfrNv.js";
8
8
  import { M as C } from "./select-B0LRDH0g.js";
9
9
  import { q as ee } from "./dropdown-menu-B_7xD2X4.js";
10
10
  import { _ as w } from "./toDate-DKH0_kMo.js";
11
11
  import { t as T } from "./jsx-runtime-3BD1kT0s.js";
12
12
  import { q as E } from "./use-restore-focus-DBCITLeF.js";
13
13
  import { A as D, C as O, b as k } from "./useTheme-CfeaVw5D.js";
14
- import { a as A, n as te, t as ne } from "./code-visibility-BXe6iFL5.js";
14
+ import { a as A, n as te, t as ne } from "./code-visibility-CfeBTR2t.js";
15
15
  /* empty css */
16
16
  var j = c("sticky-note", [["path", {
17
17
  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.25.3-dev102",
3
+ "version": "0.25.3-dev103",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -68,6 +68,21 @@ describe("shouldAutorunMarkdownUpdate", () => {
68
68
  ).toBe(false);
69
69
  });
70
70
 
71
+ it("ignores remote transactions while focused", () => {
72
+ const transaction = createTransaction({
73
+ changes: { from: 0, insert: "#" },
74
+ annotations: [Transaction.remote.of(true)],
75
+ });
76
+
77
+ expect(
78
+ shouldAutorunMarkdownUpdate({
79
+ docChanged: transaction.docChanged,
80
+ transactions: [transaction],
81
+ hasFocus: true,
82
+ }),
83
+ ).toBe(false);
84
+ });
85
+
71
86
  it("ignores programmatic doc changes without a user event", () => {
72
87
  const transaction = createTransaction({
73
88
  changes: { from: 0, insert: "#" },
@@ -1,7 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { closeCompletion, completionStatus } from "@codemirror/autocomplete";
4
- import { type Extension, Prec } from "@codemirror/state";
4
+ import { type Extension, Prec, Transaction } from "@codemirror/state";
5
5
  import {
6
6
  EditorView,
7
7
  type KeyBinding,
@@ -382,8 +382,12 @@ function shouldAutorunMarkdownUpdate({
382
382
  }
383
383
 
384
384
  return transactions.some((tr) => {
385
- // Ignore RTC sync changes to avoid duplicate runs from remote edits.
386
- if (tr.annotation(loroSyncAnnotation) !== undefined) {
385
+ // Ignore remote changes to avoid duplicate runs. The client that made the
386
+ // edit runs it.
387
+ if (
388
+ tr.annotation(Transaction.remote) ||
389
+ tr.annotation(loroSyncAnnotation) !== undefined
390
+ ) {
387
391
  return false;
388
392
  }
389
393
 
@@ -0,0 +1,483 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { history, undo } from "@codemirror/commands";
4
+ import { EditorSelection, EditorState } from "@codemirror/state";
5
+ import { EditorView } from "@codemirror/view";
6
+ import type { CellId } from "@marimo-team/client/protocol";
7
+ import { atom, createStore } from "jotai/vanilla";
8
+ import { afterEach, describe, expect, it, vi } from "vitest";
9
+ import { cellId as brandedCellId } from "@/__tests__/branded";
10
+ import { createNotebookActions } from "@/core/cells/cells";
11
+ import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
12
+ import { cellActionsState } from "../../codemirror/cells/state";
13
+ import { cellConfigExtension } from "../../codemirror/config/extension";
14
+ import {
15
+ adaptiveLanguageConfiguration,
16
+ languageAdapterState,
17
+ switchLanguage,
18
+ } from "../../codemirror/language/extension";
19
+ import {
20
+ languageMetadataField,
21
+ setLanguageMetadata,
22
+ } from "../../codemirror/language/metadata";
23
+ import { SCHEMA_CACHE } from "../../codemirror/language/languages/sql/completion-store";
24
+ import type { Document } from "../../host/resources/document";
25
+ import { cellCodeBinding } from "../codemirror";
26
+ import { createDocumentModel } from "../model";
27
+ import { cell, document, withCells } from "./fixtures";
28
+
29
+ const views: EditorView[] = [];
30
+ const lifetimes: AbortController[] = [];
31
+
32
+ afterEach(() => {
33
+ for (const view of views.splice(0)) {
34
+ view.destroy();
35
+ }
36
+ lifetimes.splice(0).forEach((controller) => controller.abort());
37
+ });
38
+
39
+ const completionConfig = {
40
+ activate_on_typing: true,
41
+ signature_hint_on_typing: false,
42
+ copilot: false,
43
+ codeium_api_key: null,
44
+ } as const;
45
+
46
+ /** The language configuration a marimo cell editor installs. */
47
+ function languages(id: CellId) {
48
+ const options = {
49
+ cellId: brandedCellId(id),
50
+ completionConfig,
51
+ hotkeys: new OverridingHotkeyProvider({}),
52
+ placeholderType: "marimo-import",
53
+ lspConfig: {},
54
+ } as const;
55
+ return [
56
+ cellActionsState.of({
57
+ ...createNotebookActions(vi.fn()),
58
+ toggleHideCode: () => false,
59
+ aiCellCompletion: () => false,
60
+ createManyBelow: vi.fn(),
61
+ addOrAppendSetupCell: vi.fn(),
62
+ onRun: vi.fn(),
63
+ deleteCell: vi.fn(),
64
+ afterToggleMarkdown: vi.fn(),
65
+ afterToggleSQL: vi.fn(),
66
+ saveNotebook: vi.fn(),
67
+ }),
68
+ adaptiveLanguageConfiguration(options),
69
+ cellConfigExtension({ ...options, diagnosticsConfig: {} }),
70
+ ];
71
+ }
72
+
73
+ function setup({
74
+ initial = document(),
75
+ cellId = "c1",
76
+ doc = "",
77
+ adapters = false,
78
+ }: {
79
+ initial?: Document | null;
80
+ cellId?: CellId;
81
+ doc?: string;
82
+ adapters?: boolean;
83
+ } = {}) {
84
+ const store = createStore();
85
+ const source = atom(initial);
86
+ const controller = new AbortController();
87
+ lifetimes.push(controller);
88
+ const model = createDocumentModel({
89
+ store,
90
+ notebook: { documentAtom: source, edit: () => new Promise(() => {}) },
91
+ signal: controller.signal,
92
+ });
93
+ const editor = (text = doc) => {
94
+ const view = new EditorView({
95
+ state: EditorState.create({
96
+ doc: text,
97
+ extensions: [
98
+ history(),
99
+ adapters ? languages(cellId) : [],
100
+ cellCodeBinding({ store, model, cellId }),
101
+ ],
102
+ }),
103
+ });
104
+ views.push(view);
105
+ return view;
106
+ };
107
+ const view = editor();
108
+ return {
109
+ store,
110
+ source,
111
+ model,
112
+ view,
113
+ editor,
114
+ code: () => store.get(model.documentAtom)?.cells.get(cellId)?.code,
115
+ status: () => store.get(model.syncStatusAtom),
116
+ type: (insert: string) =>
117
+ view.dispatch({
118
+ changes: { from: view.state.doc.length, insert },
119
+ userEvent: "input.type",
120
+ }),
121
+ };
122
+ }
123
+
124
+ // Inbound changes apply in a microtask.
125
+ const settle = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
126
+
127
+ describe("cellCodeBinding", () => {
128
+ it("seeds the editor from the cell outside undo history", async () => {
129
+ const { view, status } = setup();
130
+ await settle();
131
+ expect(view.state.doc.toString()).toBe("x = 1");
132
+ expect(undo(view)).toBe(false);
133
+ expect(status()).toEqual({ status: "idle" });
134
+ });
135
+
136
+ it("writes local changes to the draft before the update returns", async () => {
137
+ const { code, status, type } = setup();
138
+ await settle();
139
+ type(" + 1");
140
+ expect(code()).toBe("x = 1 + 1");
141
+ expect(status()).toEqual({ status: "pending" });
142
+ });
143
+
144
+ it("shows a change another subscriber makes while a push notifies", async () => {
145
+ const { store, model, view, type } = setup();
146
+ await settle();
147
+ // Another subscriber normalizes code synchronously, inside the push.
148
+ const unsubscribe = store.sub(model.documentAtom, () => {
149
+ const code = store.get(model.documentAtom)?.cells.get("c1")?.code;
150
+ if (code?.includes(" + ")) {
151
+ const normalized = code.replaceAll(" + ", "+");
152
+ store.set(model.editAtom, (tx) => tx.setCode("c1", normalized));
153
+ }
154
+ });
155
+ type(" + 1");
156
+ await settle();
157
+ expect(view.state.doc.toString()).toBe("x = 1+1");
158
+ unsubscribe();
159
+ });
160
+
161
+ it("keeps the selection between separate host edits", async () => {
162
+ const { store, source, view } = setup({
163
+ initial: withCells(document(), { c1: cell("c1", "a = 1\nb = 2\nc = 3") }),
164
+ });
165
+ await settle();
166
+ view.dispatch({ selection: EditorSelection.cursor(8) });
167
+
168
+ store.set(
169
+ source,
170
+ withCells(document(), { c1: cell("c1", "A = 1\nb = 2\nC = 3") }),
171
+ );
172
+ await settle();
173
+
174
+ expect(view.state.doc.toString()).toBe("A = 1\nb = 2\nC = 3");
175
+ expect(view.state.selection.main.head).toBe(8);
176
+ });
177
+
178
+ it("waits for composition to end before applying host changes", async () => {
179
+ const { store, source, view } = setup();
180
+ await settle();
181
+ let composing = true;
182
+ Object.defineProperty(view, "composing", { get: () => composing });
183
+
184
+ store.set(source, withCells(document(), { c1: cell("c1", "x = 2") }));
185
+ await settle();
186
+ expect(view.state.doc.toString()).toBe("x = 1");
187
+
188
+ // A composition can end without an update, so the binding watches for it.
189
+ composing = false;
190
+ view.contentDOM.dispatchEvent(new Event("compositionend"));
191
+ await settle();
192
+ expect(view.state.doc.toString()).toBe("x = 2");
193
+ });
194
+
195
+ it("applies a waiting host change on the first update after composition", async () => {
196
+ const { store, source, view } = setup();
197
+ await settle();
198
+ let composing = true;
199
+ Object.defineProperty(view, "composing", { get: () => composing });
200
+ store.set(source, withCells(document(), { c1: cell("c1", "x = 2") }));
201
+ await settle();
202
+
203
+ composing = false;
204
+ view.dispatch({ selection: EditorSelection.cursor(0) });
205
+ await settle();
206
+ expect(view.state.doc.toString()).toBe("x = 2");
207
+ });
208
+
209
+ it("keeps two editors on one cell in step", async () => {
210
+ const { editor, type } = setup();
211
+ const other = editor();
212
+ await settle();
213
+
214
+ type("!");
215
+ await settle();
216
+
217
+ expect(other.state.doc.toString()).toBe("x = 1!");
218
+ });
219
+
220
+ it("shows host code that returns to what this editor wrote", async () => {
221
+ const { store, source, view, type } = setup();
222
+ await settle();
223
+ type("!");
224
+ // The host confirms the edit, which releases the draft.
225
+ store.set(source, withCells(document(), { c1: cell("c1", "x = 1!") }));
226
+ await settle();
227
+
228
+ store.set(source, withCells(document(), { c1: cell("c1", "x = 2") }));
229
+ await settle();
230
+ expect(view.state.doc.toString()).toBe("x = 2");
231
+
232
+ store.set(source, withCells(document(), { c1: cell("c1", "x = 1!") }));
233
+ await settle();
234
+ expect(view.state.doc.toString()).toBe("x = 1!");
235
+ });
236
+
237
+ it("follows a cell once its code is available", async () => {
238
+ const absent = setup({ cellId: "c3" });
239
+ const withheld = setup({
240
+ initial: withCells(document(), { c1: cell("c1", "", { code: null }) }),
241
+ });
242
+ await settle();
243
+
244
+ absent.store.set(
245
+ absent.source,
246
+ withCells(document(), { c3: cell("c3", "new") }),
247
+ );
248
+ withheld.store.set(
249
+ withheld.source,
250
+ withCells(document(), { c1: cell("c1", "revealed") }),
251
+ );
252
+ await settle();
253
+
254
+ expect(absent.view.state.doc.toString()).toBe("new");
255
+ expect(withheld.view.state.doc.toString()).toBe("revealed");
256
+ });
257
+
258
+ it("shows the host's code after typing while the document was unavailable", async () => {
259
+ const { store, source, view, type, code } = setup();
260
+ await settle();
261
+
262
+ // All in one task, so no pull runs while the document is away.
263
+ store.set(source, null);
264
+ type(" + 1");
265
+ store.set(source, document());
266
+ await settle();
267
+
268
+ expect(view.state.doc.toString()).toBe(code());
269
+ });
270
+
271
+ it("leaves the editor alone for an absent cell or withheld source", async () => {
272
+ const absent = setup({ cellId: "c9", doc: "local" });
273
+ const withheld = setup({
274
+ initial: withCells(document(), { c1: cell("c1", "", { code: null }) }),
275
+ doc: "local",
276
+ });
277
+ await settle();
278
+
279
+ for (const { view, type, status } of [absent, withheld]) {
280
+ type("!");
281
+ expect(view.state.doc.toString()).toBe("local!");
282
+ expect(status()).toEqual({ status: "idle" });
283
+ }
284
+ });
285
+
286
+ it("stops following the cell when the view is destroyed", async () => {
287
+ const { store, source, view } = setup();
288
+ await settle();
289
+
290
+ // The change arrives before the view goes, and would apply just after.
291
+ store.set(source, withCells(document(), { c1: cell("c1", "x = 2") }));
292
+ view.destroy();
293
+ await settle();
294
+
295
+ expect(view.state.doc.toString()).toBe("x = 1");
296
+ });
297
+
298
+ describe("with language adapters", () => {
299
+ const language = (view: EditorView) =>
300
+ view.state.field(languageAdapterState).type;
301
+ /** What the editor's adapter reads back from cell code. */
302
+ const readBack = (view: EditorView, code: string | null | undefined) => {
303
+ const [text, , metadata] = view.state
304
+ .field(languageAdapterState)
305
+ .transformIn(code ?? "");
306
+ return { text, metadata };
307
+ };
308
+
309
+ it("edits markdown as markdown and writes Python with its metadata", async () => {
310
+ const { view, code, type } = setup({
311
+ adapters: true,
312
+ initial: withCells(document(), {
313
+ c1: cell("c1", 'mo.md(f"""hi {x}""")'),
314
+ }),
315
+ });
316
+ await settle();
317
+ expect(language(view)).toBe("markdown");
318
+ expect(view.state.doc.toString()).toBe("hi {x}");
319
+
320
+ type("!");
321
+ expect(readBack(view, code())).toMatchObject({
322
+ text: "hi {x}!",
323
+ metadata: { quotePrefix: "f" },
324
+ });
325
+ });
326
+
327
+ it("switches language for host code without rewriting it", async () => {
328
+ const { store, source, view, code, status } = setup({ adapters: true });
329
+ await settle();
330
+ expect(language(view)).toBe("python");
331
+
332
+ store.set(
333
+ source,
334
+ withCells(document(), { c1: cell("c1", 'mo.md("hi")') }),
335
+ );
336
+ await settle();
337
+
338
+ expect(language(view)).toBe("markdown");
339
+ expect(view.state.doc.toString()).toBe("hi");
340
+ expect(code()).toBe('mo.md("hi")');
341
+ expect(status()).toEqual({ status: "idle" });
342
+ });
343
+
344
+ it("writes changes to language metadata alone", async () => {
345
+ const { view, code } = setup({
346
+ adapters: true,
347
+ initial: withCells(document(), {
348
+ c1: cell("c1", 'mo.md("""\n# Title\n""")'),
349
+ }),
350
+ });
351
+ await settle();
352
+
353
+ view.dispatch({
354
+ effects: setLanguageMetadata.of({
355
+ ...view.state.field(languageMetadataField),
356
+ quotePrefix: "r",
357
+ }),
358
+ });
359
+ expect(readBack(view, code())).toMatchObject({
360
+ text: "# Title",
361
+ metadata: { quotePrefix: "r" },
362
+ });
363
+ });
364
+
365
+ it("applies a host change to language metadata alone", async () => {
366
+ const { store, source, view } = setup({
367
+ adapters: true,
368
+ initial: withCells(document(), {
369
+ c1: cell("c1", 'mo.md(r"""hi""")'),
370
+ }),
371
+ });
372
+ await settle();
373
+
374
+ store.set(
375
+ source,
376
+ withCells(document(), { c1: cell("c1", 'mo.md(f"""hi""")') }),
377
+ );
378
+ await settle();
379
+
380
+ expect(view.state.doc.toString()).toBe("hi");
381
+ expect(view.state.field(languageMetadataField)).toMatchObject({
382
+ quotePrefix: "f",
383
+ });
384
+ });
385
+
386
+ it("does not rewrite code when the editor switches language as it opens", async () => {
387
+ const original = 'mo.md("hello")';
388
+ const { view, code, status } = setup({
389
+ adapters: true,
390
+ doc: original,
391
+ initial: withCells(document(), { c1: cell("c1", original) }),
392
+ });
393
+ // The cell editor switches to the language its code implies on mount.
394
+ switchLanguage(view, { language: "markdown" });
395
+ await settle();
396
+
397
+ expect(code()).toBe(original);
398
+ expect(status()).toEqual({ status: "idle" });
399
+ });
400
+
401
+ it("keeps typed text that the adapter would normalize", async () => {
402
+ const { view, type } = setup({
403
+ adapters: true,
404
+ initial: withCells(document(), {
405
+ c1: cell("c1", 'mo.md("""\nhi\n""")'),
406
+ }),
407
+ });
408
+ await settle();
409
+
410
+ type("\n");
411
+ await settle();
412
+ type(" ");
413
+ await settle();
414
+
415
+ expect(view.state.doc.toString()).toBe("hi\n ");
416
+ });
417
+
418
+ it("keeps the language a person chose when the cell changes otherwise", async () => {
419
+ const { store, model, view } = setup({
420
+ adapters: true,
421
+ initial: withCells(document(), {
422
+ c1: cell("c1", 'mo.md("""\nhi\n""")'),
423
+ }),
424
+ });
425
+ await settle();
426
+ switchLanguage(view, { language: "python" });
427
+ await settle();
428
+
429
+ store.set(model.editAtom, (tx) => tx.setName("c1", "renamed"));
430
+ await settle();
431
+
432
+ expect(language(view)).toBe("python");
433
+ });
434
+
435
+ it("edits SQL as SQL and keeps its output name", async () => {
436
+ const { store, source, view, code, type } = setup({ adapters: true });
437
+ await settle();
438
+
439
+ store.set(
440
+ source,
441
+ withCells(document(), {
442
+ c1: cell("c1", 'df = mo.sql(f"""SELECT 1""")'),
443
+ }),
444
+ );
445
+ await settle();
446
+ expect(language(view)).toBe("sql");
447
+ expect(view.state.doc.toString()).toBe("SELECT 1");
448
+
449
+ type(" LIMIT 1");
450
+ expect(readBack(view, code())).toMatchObject({
451
+ text: "SELECT 1 LIMIT 1",
452
+ metadata: { dataframeName: "df" },
453
+ });
454
+ });
455
+
456
+ it("sets the SQL dialect from the host's engine", async () => {
457
+ const { store, source } = setup({ adapters: true });
458
+ await settle();
459
+ const dialect = vi.spyOn(SCHEMA_CACHE, "getDialect");
460
+ try {
461
+ store.set(
462
+ source,
463
+ withCells(document(), {
464
+ c1: cell("c1", '_df = mo.sql(f"""SELECT 1""", engine=warehouse)'),
465
+ }),
466
+ );
467
+ await settle();
468
+ expect(dialect).toHaveBeenLastCalledWith("warehouse");
469
+
470
+ store.set(
471
+ source,
472
+ withCells(document(), {
473
+ c1: cell("c1", '_df = mo.sql(f"""SELECT 1""", engine=lake)'),
474
+ }),
475
+ );
476
+ await settle();
477
+ expect(dialect).toHaveBeenLastCalledWith("lake");
478
+ } finally {
479
+ dialect.mockRestore();
480
+ }
481
+ });
482
+ });
483
+ });
@@ -0,0 +1,311 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * A CodeMirror binding between an editor and one cell's code.
5
+ *
6
+ * The editor and the document model each hold the code, and the binding keeps
7
+ * them equal in both directions.
8
+ *
9
+ * - Push. An update that could change the code writes it to the model before
10
+ * the update returns, so the model keeps pace with typing.
11
+ * - Pull. A model change reaches the editor in a microtask, because a view
12
+ * cannot dispatch while it is constructing or updating. The binding keeps
13
+ * the code both sides last agreed on, so the model's echo of a push is
14
+ * skipped while a different value, even one written during that push, is
15
+ * pulled. A real change is marked remote, stays out of undo history, and
16
+ * waits for IME composition to end.
17
+ *
18
+ * The editor shows code through marimo's language adapters, so a markdown
19
+ * cell edits the text inside `mo.md`. Both directions compare the cell's
20
+ * Python code rather than the editor text, so an adapter that normalizes what
21
+ * it reads back leaves typing alone.
22
+ *
23
+ * A pulled change replaces only what differs, using `diff` from
24
+ * `@codemirror/merge`. Separate edits stay separate, so the selection, folds,
25
+ * and diagnostics between them keep their place. The diff uses a scan limit of
26
+ * 500, as the merge view does. Past it, the search trades precision for speed,
27
+ * so a rewrite of a large cell costs microseconds and maps the selection
28
+ * coarsely instead of blocking the editor.
29
+ */
30
+
31
+ import { diff } from "@codemirror/merge";
32
+ import {
33
+ type ChangeSpec,
34
+ type EditorState,
35
+ type Extension,
36
+ type StateEffect,
37
+ Transaction,
38
+ } from "@codemirror/state";
39
+ import { type EditorView, ViewPlugin, type ViewUpdate } from "@codemirror/view";
40
+ import type { CellId } from "@marimo-team/client/protocol";
41
+ import { dequal } from "dequal";
42
+ import type { Atom, createStore } from "jotai/vanilla";
43
+ import {
44
+ languageAdapterFromCode,
45
+ languageAdapterState,
46
+ switchLanguage,
47
+ } from "../codemirror/language/extension";
48
+ import { initializeSQLDialect } from "../codemirror/language/languages/sql/sql";
49
+ import {
50
+ languageMetadataField,
51
+ setLanguageMetadata,
52
+ } from "../codemirror/language/metadata";
53
+ import type { DocumentModel } from "./model";
54
+ import { selectCellField } from "./select";
55
+
56
+ /** The diff scan limit, matching the default of the merge view. */
57
+ const SCAN_LIMIT = 500;
58
+
59
+ interface Binding {
60
+ readonly store: ReturnType<typeof createStore>;
61
+ readonly model: DocumentModel;
62
+ readonly cellId: CellId;
63
+ /** Undefined while the cell is absent, null while its source is withheld. */
64
+ readonly code: Atom<string | null | undefined>;
65
+ }
66
+
67
+ /**
68
+ * Bind an editor to one cell's code.
69
+ *
70
+ * Editors without marimo's language configuration bind their text as is.
71
+ */
72
+ export function cellCodeBinding({
73
+ store,
74
+ model,
75
+ cellId,
76
+ }: {
77
+ store: ReturnType<typeof createStore>;
78
+ model: DocumentModel;
79
+ cellId: CellId;
80
+ }): Extension {
81
+ const binding: Binding = {
82
+ store,
83
+ model,
84
+ cellId,
85
+ code: selectCellField(model.documentAtom, cellId, "code"),
86
+ };
87
+ return ViewPlugin.define((view) => new CellCodeSync(view, binding), {
88
+ eventObservers: {
89
+ compositionend() {
90
+ this.compositionEnded();
91
+ },
92
+ },
93
+ });
94
+ }
95
+
96
+ class CellCodeSync {
97
+ #scheduled = false;
98
+ /** A pull arrived during IME composition and runs once it ends. */
99
+ #deferred = false;
100
+ /**
101
+ * A pull is dispatching to the editor.
102
+ *
103
+ * A language switch dispatches transactions of its own before the text, and
104
+ * their in-between states represent the wrong code. Until the switch can be
105
+ * part of the pull's one remote transaction, this keeps them from pushing.
106
+ */
107
+ #pulling = false;
108
+ /**
109
+ * The code the editor and the model last agreed on.
110
+ *
111
+ * A change the model cannot take clears it, so once the cell is available
112
+ * again its code is pulled even if it matches what was agreed before.
113
+ */
114
+ #synced: string | undefined;
115
+ #destroyed = false;
116
+ readonly #view: EditorView;
117
+ readonly #binding: Binding;
118
+ readonly #unsubscribe: () => void;
119
+
120
+ public constructor(view: EditorView, binding: Binding) {
121
+ this.#view = view;
122
+ this.#binding = binding;
123
+ // A change to the agreed code is the echo of a push and needs no pull.
124
+ this.#unsubscribe = binding.store.sub(binding.code, () => {
125
+ if (binding.store.get(binding.code) !== this.#synced) {
126
+ this.#schedule();
127
+ }
128
+ });
129
+ this.#schedule();
130
+ }
131
+
132
+ public update(update: ViewUpdate) {
133
+ if (!this.#pulling && changesCode(update)) {
134
+ this.#push(update.state);
135
+ }
136
+ if (this.#deferred && !update.view.composing) {
137
+ this.#schedule();
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Run a pull that waited for the composition.
143
+ *
144
+ * A composition that ends without changing the text need not produce an
145
+ * update. CodeMirror finishes handling the end in a microtask, so the pull
146
+ * waits for the next task.
147
+ */
148
+ public compositionEnded() {
149
+ if (this.#deferred) {
150
+ setTimeout(() => this.#schedule(), 0);
151
+ }
152
+ }
153
+
154
+ public destroy() {
155
+ this.#destroyed = true;
156
+ this.#unsubscribe();
157
+ }
158
+
159
+ /** Write the editor's code to the model unless the editor already shows it. */
160
+ #push(state: EditorState) {
161
+ const { store, model, cellId, code } = this.#binding;
162
+ const current = store.get(code);
163
+ if (typeof current !== "string") {
164
+ this.#synced = undefined;
165
+ return;
166
+ }
167
+ if (shows(state, current)) {
168
+ this.#synced = current;
169
+ return;
170
+ }
171
+ const next = codeOf(state);
172
+ this.#synced = next;
173
+ store.set(model.editAtom, (tx) => tx.setCode(cellId, next));
174
+ }
175
+
176
+ /** Show the model's code unless the editor already shows it. */
177
+ #pull() {
178
+ this.#scheduled = false;
179
+ const next = this.#binding.store.get(this.#binding.code);
180
+ if (this.#destroyed || typeof next !== "string") {
181
+ return;
182
+ }
183
+ if (this.#view.composing) {
184
+ this.#deferred = true;
185
+ return;
186
+ }
187
+ this.#deferred = false;
188
+ if (next !== this.#synced && !shows(this.#view.state, next)) {
189
+ this.#pulling = true;
190
+ try {
191
+ replaceCode(this.#view, next);
192
+ } finally {
193
+ this.#pulling = false;
194
+ }
195
+ }
196
+ this.#synced = next;
197
+ }
198
+
199
+ /** Pull once after the current update, however many changes arrive. */
200
+ #schedule() {
201
+ if (!this.#scheduled) {
202
+ this.#scheduled = true;
203
+ queueMicrotask(() => this.#pull());
204
+ }
205
+ }
206
+ }
207
+
208
+ /**
209
+ * The cell code an editor state represents.
210
+ *
211
+ * With a language adapter, this is the adapter's Python for the editor text.
212
+ */
213
+ function codeOf(state: EditorState): string {
214
+ const text = state.doc.toString();
215
+ const adapter = state.field(languageAdapterState, false);
216
+ return adapter
217
+ ? adapter.transformOut(
218
+ text,
219
+ state.field(languageMetadataField, false) ?? {},
220
+ )[0]
221
+ : text;
222
+ }
223
+
224
+ /**
225
+ * Whether an editor state shows the given cell code.
226
+ *
227
+ * An adapter can spell the same code more than one way, such as `mo.md("hi")`
228
+ * and its canonical form, so a state shows code that reads into its text and
229
+ * metadata as well as the code its own spelling produces.
230
+ */
231
+ function shows(state: EditorState, code: string): boolean {
232
+ if (codeOf(state) === code) {
233
+ return true;
234
+ }
235
+ const adapter = state.field(languageAdapterState, false);
236
+ if (!adapter) {
237
+ return false;
238
+ }
239
+ const [text, , metadata] = adapter.transformIn(code);
240
+ return (
241
+ text === state.doc.toString() &&
242
+ dequal(metadata, state.field(languageMetadataField, false))
243
+ );
244
+ }
245
+
246
+ /**
247
+ * Whether an update could change the code an editor represents.
248
+ *
249
+ * Language metadata, such as an SQL cell's output name, changes the code
250
+ * without changing the text.
251
+ */
252
+ function changesCode(update: ViewUpdate): boolean {
253
+ return (
254
+ update.docChanged ||
255
+ update.state.field(languageAdapterState, false) !==
256
+ update.startState.field(languageAdapterState, false) ||
257
+ update.state.field(languageMetadataField, false) !==
258
+ update.startState.field(languageMetadataField, false)
259
+ );
260
+ }
261
+
262
+ /**
263
+ * Show cell code in the language it implies.
264
+ *
265
+ * The language switch happens first, in its own transactions, and clears undo
266
+ * history because its entries edit the previous language's text. The text and
267
+ * metadata follow in one remote transaction that undo history leaves out. An
268
+ * SQL dialect depends on the engine in the metadata, so it is set again after.
269
+ */
270
+ function replaceCode(view: EditorView, code: string) {
271
+ let text = code;
272
+ const effects: StateEffect<unknown>[] = [];
273
+ let sql = false;
274
+ const adapter = view.state.field(languageAdapterState, false);
275
+ if (adapter) {
276
+ const target = languageAdapterFromCode(code);
277
+ sql = target.type === "sql";
278
+ if (target.type !== adapter.type) {
279
+ switchLanguage(view, { language: target.type, keepCodeAsIs: true });
280
+ }
281
+ const [inner, , metadata] = target.transformIn(code);
282
+ text = inner;
283
+ if (!dequal(metadata, view.state.field(languageMetadataField, false))) {
284
+ effects.push(setLanguageMetadata.of(metadata));
285
+ }
286
+ }
287
+ const current = view.state.doc.toString();
288
+ if (current === text && effects.length === 0) {
289
+ return;
290
+ }
291
+ view.dispatch({
292
+ changes: changesBetween(current, text),
293
+ effects,
294
+ annotations: [
295
+ Transaction.remote.of(true),
296
+ Transaction.addToHistory.of(false),
297
+ ],
298
+ });
299
+ if (sql && effects.length > 0) {
300
+ initializeSQLDialect(view);
301
+ }
302
+ }
303
+
304
+ /** The changes that turn one text into another, one per edited range. */
305
+ function changesBetween(from: string, to: string): ChangeSpec {
306
+ return diff(from, to, { scanLimit: SCAN_LIMIT }).map((change) => ({
307
+ from: change.fromA,
308
+ to: change.toA,
309
+ insert: to.slice(change.fromB, change.toB),
310
+ }));
311
+ }
@@ -30,3 +30,12 @@ export function selectOrder(
30
30
  ): Atom<readonly Protocol.CellId[] | null> {
31
31
  return selectAtom(document, (value) => value?.order ?? null, dequal);
32
32
  }
33
+
34
+ /** Select one field of a cell, unchanged until that field's value changes. */
35
+ export function selectCellField<K extends keyof EditableCell>(
36
+ document: Atom<EditableDocument | null>,
37
+ id: Protocol.CellId,
38
+ field: K,
39
+ ): Atom<EditableCell[K] | undefined> {
40
+ return selectAtom(document, (value) => value?.cells.get(id)?.[field], dequal);
41
+ }