@marimo-team/islands 0.23.16-dev54 → 0.23.16-dev56

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.
@@ -36338,7 +36338,7 @@ ${d}`,
36338
36338
  return Logger.warn("Failed to get version from mount config"), null;
36339
36339
  }
36340
36340
  }
36341
- marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.16-dev54");
36341
+ marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.16-dev56");
36342
36342
  showCodeInRunModeAtom = atom(true);
36343
36343
  atom(null);
36344
36344
  var import_compiler_runtime = require_compiler_runtime();
package/dist/main.js CHANGED
@@ -26,7 +26,7 @@ import { $ as notebookOutline, $t as publishedCellClasses, A as ChevronDownIcon,
26
26
  import { __tla as __tla_1 } from "./chunk-5FQGJX7Z-B9WoXK1R.js";
27
27
  import { o as useSize, s as Root$1, u as createLucideIcon } from "./dist-DIlxLFXG.js";
28
28
  import { A as SquareFunction, C as DEFAULT_COLOR_SCHEME, D as SCALE_TYPE_DESCRIPTIONS, E as EMPTY_VALUE$1, O as TIME_UNIT_DESCRIPTIONS, S as DEFAULT_AGGREGATION, T as DEFAULT_TIME_UNIT, _ as AGGREGATION_TYPE_DESCRIPTIONS, a as AGGREGATION_FNS$1, b as COLOR_SCHEMES, c as COLOR_BY_FIELDS, d as NONE_VALUE, f as SELECTABLE_DATA_TYPES, g as TIME_UNITS, h as STRING_AGGREGATION_FNS, i as convertDataTypeToSelectable, j as ChartColumn, k as escapeFieldName, l as COMBINED_TIME_UNITS, m as SORT_TYPES, n as createSpecWithoutData, o as BIN_AGGREGATION, p as SINGLE_TIME_UNITS, r as isFieldSet, s as CHART_TYPES, t as augmentSpecWithData, u as ChartType, v as AGGREGATION_TYPE_ICON, w as DEFAULT_MAX_BINS_FACET, x as COUNT_FIELD, y as CHART_TYPE_ICON } from "./spec-CQKL_D3i.js";
29
- import { $ as CommandSeparator, $t as Kbd, A as ErrorState, At as getPageIndexForRow, B as ContextMenuTrigger, Bt as dateToLocalISODate, C as downloadHTMLAsImage, Ct as inferFieldTypes, D as ColumnName, Dt as DelayMount, E as downloadSizeLimitAtom, Et as ColumnChartSpecModel, F as getUserColumnVisibilityCounts, Ft as TOO_MANY_ROWS, G as DateRangePicker, Gt as TabsList, H as useSelectList, Ht as dateToLocalISOTime, I as ContextMenu, It as toFieldTypes, J as Command, Jt as ChartInfoState, K as Combobox, Kt as TabsTrigger, L as ContextMenuContent, Lt as getMimeValues, M as prettifyRowColumnCount, Mt as loadTableData, N as prettifyRowCount, Nt as INDEX_COLUMN_NAME, O as ColumnPreviewContainer, Ot as useIntersectionObserver, P as getColumnCountForDisplay, Pt as SELECT_COLUMN_ID, Q as CommandList, Qt as RenderTextWithLinks, R as ContextMenuItem, Rt as isNullishFilter, S as downloadExportedFile, St as generateColumns, T as Filenames, Tt as ColumnChartContext, U as CompactChipRow, Ut as Tabs, V as useInternalStateWithSync, Vt as dateToLocalISODateTime, W as DatePicker, Wt as TabsContent, X as CommandInput, Xt as LazyVegaEmbed, Y as CommandEmpty, Yt as ChartLoadingState, Z as CommandItem, Zt as useOverflowDetection, _ as DataTable, _n as ChevronLeft, _t as TableCell, an as TextWrap, at as contextAwarePanelType, b as downloadBlob, bt as TableRow, c as SLIDE_TYPE_OPTIONS_BY_VALUE, cn as EyeOff, ct as slotsController, d as Switch, dn as Download, en as HtmlOutput, et as smartMatch, f as RadioGroup, fn as Code, g as OutputRenderer, gn as ChevronsDownUp, gt as TableBody, h as OutputArea, hn as ChevronsLeft, ht as Table, i as showCodeInRunModeAtom, in as $fae977aafc393c5c$export$6b862160d295c8e, it as contextAwarePanelOwner, j as LoadingState, jt as loadTableAndRawData, k as EmptyState, kt as usePrevious$1, lt as Toggle, m as JsonOutput, mn as ChevronsRight, mt as Provider$1, n as useNotebookCodeAvailable, nn as safeLocale, nt as PANEL_TYPES, on as GripHorizontal, ot as isCellAwareAtom, p as RadioGroupItem, pn as ChevronsUpDown, pt as Fill, q as ComboboxItem, qt as ChartErrorState, r as marimoVersionAtom, rn as $fae977aafc393c5c$export$588937bcd60ade55, rt as contextAwarePanelOpen, sn as Funnel, st as SlotNames, t as cellDomProps, tn as EmotionCacheProvider, tt as ContextAwarePanelItem, u as Slide, un as Ellipsis, v as InstallPackageButton, vn as ArrowDownWideNarrow, vt as TableHead, w as Progress, wt as renderCellValue, x as downloadByURL, xt as NAMELESS_COLUMN_PREFIX, y as ADD_PRINTING_CLASS, yt as TableHeader, z as ContextMenuSeparator, zt as Maps, __tla as __tla_2 } from "./common-DwIK9Ruq.js";
29
+ import { $ as CommandSeparator, $t as Kbd, A as ErrorState, At as getPageIndexForRow, B as ContextMenuTrigger, Bt as dateToLocalISODate, C as downloadHTMLAsImage, Ct as inferFieldTypes, D as ColumnName, Dt as DelayMount, E as downloadSizeLimitAtom, Et as ColumnChartSpecModel, F as getUserColumnVisibilityCounts, Ft as TOO_MANY_ROWS, G as DateRangePicker, Gt as TabsList, H as useSelectList, Ht as dateToLocalISOTime, I as ContextMenu, It as toFieldTypes, J as Command, Jt as ChartInfoState, K as Combobox, Kt as TabsTrigger, L as ContextMenuContent, Lt as getMimeValues, M as prettifyRowColumnCount, Mt as loadTableData, N as prettifyRowCount, Nt as INDEX_COLUMN_NAME, O as ColumnPreviewContainer, Ot as useIntersectionObserver, P as getColumnCountForDisplay, Pt as SELECT_COLUMN_ID, Q as CommandList, Qt as RenderTextWithLinks, R as ContextMenuItem, Rt as isNullishFilter, S as downloadExportedFile, St as generateColumns, T as Filenames, Tt as ColumnChartContext, U as CompactChipRow, Ut as Tabs, V as useInternalStateWithSync, Vt as dateToLocalISODateTime, W as DatePicker, Wt as TabsContent, X as CommandInput, Xt as LazyVegaEmbed, Y as CommandEmpty, Yt as ChartLoadingState, Z as CommandItem, Zt as useOverflowDetection, _ as DataTable, _n as ChevronLeft, _t as TableCell, an as TextWrap, at as contextAwarePanelType, b as downloadBlob, bt as TableRow, c as SLIDE_TYPE_OPTIONS_BY_VALUE, cn as EyeOff, ct as slotsController, d as Switch, dn as Download, en as HtmlOutput, et as smartMatch, f as RadioGroup, fn as Code, g as OutputRenderer, gn as ChevronsDownUp, gt as TableBody, h as OutputArea, hn as ChevronsLeft, ht as Table, i as showCodeInRunModeAtom, in as $fae977aafc393c5c$export$6b862160d295c8e, it as contextAwarePanelOwner, j as LoadingState, jt as loadTableAndRawData, k as EmptyState, kt as usePrevious$1, lt as Toggle, m as JsonOutput, mn as ChevronsRight, mt as Provider$1, n as useNotebookCodeAvailable, nn as safeLocale, nt as PANEL_TYPES, on as GripHorizontal, ot as isCellAwareAtom, p as RadioGroupItem, pn as ChevronsUpDown, pt as Fill, q as ComboboxItem, qt as ChartErrorState, r as marimoVersionAtom, rn as $fae977aafc393c5c$export$588937bcd60ade55, rt as contextAwarePanelOpen, sn as Funnel, st as SlotNames, t as cellDomProps, tn as EmotionCacheProvider, tt as ContextAwarePanelItem, u as Slide, un as Ellipsis, v as InstallPackageButton, vn as ArrowDownWideNarrow, vt as TableHead, w as Progress, wt as renderCellValue, x as downloadByURL, xt as NAMELESS_COLUMN_PREFIX, y as ADD_PRINTING_CLASS, yt as TableHeader, z as ContextMenuSeparator, zt as Maps, __tla as __tla_2 } from "./common-Dwe0Uq2u.js";
30
30
  import { c as Calendar, i as createReducerAndAtoms, n as useOnUnmount, o as ToggleLeft, t as useOnMount } from "./useLifecycle-CMJGqBaY.js";
31
31
  import { t as Check } from "./check-DP7U0H0G.js";
32
32
  import { A as Icon, C as logNever, D as $18f2051aff69b9bf$export$a54013f0d02a8f82, E as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7, F as createCollection, I as X, M as clamp$2, N as usePrevious$2, P as useDirection, R as ChevronDown, S as assertNever, a as SelectGroup, c as SelectSeparator, d as NativeSelect, i as SelectContent, j as Trigger$1, l as SelectTrigger, n as capitalize, o as SelectItem, r as Select, s as SelectLabel, t as Strings, u as SelectValue, w as $a916eb452884faea$export$b7a616150fdb9f44 } from "./strings-DIy9NAmg.js";
@@ -3574,7 +3574,7 @@ let __tla = Promise.all([
3574
3574
  Logger.error("Error emitting event", e2);
3575
3575
  }
3576
3576
  }, 0));
3577
- __privateSet(this, _n2, e), __privateSet(this, _r, r), __privateSet(this, _t2, /* @__PURE__ */ new Map()), c && c.addEventListener("abort", () => {
3577
+ __privateSet(this, _n2, e), __privateSet(this, _r, r), __privateSet(this, _t2, /* @__PURE__ */ new Set()), c && c.addEventListener("abort", () => {
3578
3578
  Logger.debug("[Model] Signal aborted, clearing all listeners"), __privateSet(this, _i, {});
3579
3579
  });
3580
3580
  }
@@ -3601,11 +3601,16 @@ let __tla = Promise.all([
3601
3601
  __privateSet(this, _n2, {
3602
3602
  ...__privateGet(this, _n2),
3603
3603
  [e]: r
3604
- }), __privateGet(this, _t2).set(e, r), __privateMethod(this, _e_instances, a_fn).call(this, `change:${e}`, r), __privateGet(this, _c).call(this);
3604
+ }), __privateGet(this, _t2).add(e), __privateMethod(this, _e_instances, a_fn).call(this, `change:${e}`, r), __privateGet(this, _c).call(this);
3605
3605
  }
3606
3606
  save_changes() {
3607
3607
  if (__privateGet(this, _t2).size === 0) return;
3608
- let e = Object.fromEntries(__privateGet(this, _t2).entries());
3608
+ let e = Object.fromEntries([
3609
+ ...__privateGet(this, _t2)
3610
+ ].map((e2) => [
3611
+ e2,
3612
+ __privateGet(this, _n2)[e2]
3613
+ ]));
3609
3614
  __privateGet(this, _t2).clear(), __privateGet(this, _r).sendUpdate(e);
3610
3615
  }
3611
3616
  on(e, r, c) {
@@ -3626,7 +3631,10 @@ let __tla = Promise.all([
3626
3631
  }, o_fn = function(e) {
3627
3632
  e != null && Object.keys(e).forEach((r) => {
3628
3633
  let c = r;
3629
- __privateGet(this, _n2)[c] !== e[c] && this.set(c, e[c]);
3634
+ __privateGet(this, _t2).delete(c), __privateGet(this, _n2)[c] !== e[c] && (__privateSet(this, _n2, {
3635
+ ...__privateGet(this, _n2),
3636
+ [c]: e[c]
3637
+ }), __privateMethod(this, _e_instances, a_fn).call(this, `change:${c}`, e[c]), __privateGet(this, _c).call(this));
3630
3638
  });
3631
3639
  }, s_fn = function(e, r = []) {
3632
3640
  let c = __privateGet(this, _i)["msg:custom"];
@@ -38147,7 +38155,7 @@ ${c}
38147
38155
  function _temp2$2(e) {
38148
38156
  e.target === e.currentTarget && e.key === "Enter" && (e.preventDefault(), e.stopPropagation(), e.currentTarget.click());
38149
38157
  }
38150
- var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-DlbR8R40.js"));
38158
+ var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-DentpkqC.js"));
38151
38159
  const SlidesLayoutRenderer = ({ layout: e, setLayout: r, cells: c, mode: l }) => {
38152
38160
  var _a5;
38153
38161
  let u = useAtomValue(kioskModeAtom), d = l === "read" || u, f = useAtomValue(numColumnsAtom) > 1, [p, m] = (0, import_react.useState)(null), { slideCells: h, skippedIds: g, noOutputIds: _, slideTypes: v, startCellIndex: y } = (0, import_react.useMemo)(() => computeSlideCellsInfo(c, e), [
@@ -9,7 +9,7 @@ import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
9
9
  import { ut as outputIsStale } from "./html-to-image-DzQw9y4M.js";
10
10
  import "./chunk-5FQGJX7Z-B9WoXK1R.js";
11
11
  import { u as createLucideIcon } from "./dist-DIlxLFXG.js";
12
- import { a as DEFAULT_DECK_TRANSITION, cn as EyeOff, dt as PanelGroup, fn as Code, ft as PanelResizeHandle, l as SlideSidebar, ln as Expand, n as useNotebookCodeAvailable, o as DEFAULT_DECK_VERTICAL_ALIGN, s as DEFAULT_SLIDE_TYPE, t as cellDomProps, u as Slide, ut as Panel } from "./common-DwIK9Ruq.js";
12
+ import { a as DEFAULT_DECK_TRANSITION, cn as EyeOff, dt as PanelGroup, fn as Code, ft as PanelResizeHandle, l as SlideSidebar, ln as Expand, n as useNotebookCodeAvailable, o as DEFAULT_DECK_VERTICAL_ALIGN, s as DEFAULT_SLIDE_TYPE, t as cellDomProps, u as Slide, ut as Panel } from "./common-Dwe0Uq2u.js";
13
13
  import { R as ChevronDown } from "./strings-DIy9NAmg.js";
14
14
  import { X as useDebouncedCallback } from "./input-C5K4VBvB.js";
15
15
  import { v as kioskModeAtom } from "./toDate-CqhBWjvO.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.16-dev54",
3
+ "version": "0.23.16-dev56",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -263,6 +263,61 @@ describe("Model", () => {
263
263
  await TestUtils.nextTick(); // flush
264
264
  expect(callback).toHaveBeenCalledTimes(1);
265
265
  });
266
+
267
+ it("should not mark kernel-pushed state dirty", () => {
268
+ getMarimoInternal(model).updateAndEmitDiffs({ foo: "changed", bar: 456 });
269
+
270
+ model.save_changes();
271
+ expect(mockComm.sendUpdate).not.toHaveBeenCalled();
272
+ });
273
+
274
+ it("should not echo kernel-pushed state alongside a local set", () => {
275
+ // Kernel pushes a large trait; widget ESM then sets one field.
276
+ getMarimoInternal(model).updateAndEmitDiffs({
277
+ foo: "from kernel",
278
+ bar: 123,
279
+ });
280
+ model.set("bar", 456);
281
+
282
+ model.save_changes();
283
+ expect(mockComm.sendUpdate).toHaveBeenCalledExactlyOnceWith({
284
+ bar: 456,
285
+ });
286
+ });
287
+
288
+ it("should drop a pending local write superseded by a kernel push", () => {
289
+ // Local write, not yet saved...
290
+ model.set("foo", "local");
291
+ // ...then the kernel pushes a newer value for the same key.
292
+ getMarimoInternal(model).updateAndEmitDiffs({
293
+ foo: "from kernel",
294
+ bar: 123,
295
+ });
296
+ expect(model.get("foo")).toBe("from kernel");
297
+
298
+ // Saving must not resend the stale local value.
299
+ model.save_changes();
300
+ expect(mockComm.sendUpdate).not.toHaveBeenCalled();
301
+ });
302
+
303
+ it("should drop a pending local write the kernel pushed the same value for", () => {
304
+ const callback = vi.fn();
305
+ model.on("change:foo", callback);
306
+
307
+ // Local write, not yet saved...
308
+ model.set("foo", "agreed");
309
+ expect(callback).toHaveBeenCalledTimes(1);
310
+
311
+ // ...then the kernel independently pushes the same value.
312
+ getMarimoInternal(model).updateAndEmitDiffs({
313
+ foo: "agreed",
314
+ bar: 123,
315
+ });
316
+ expect(callback).toHaveBeenCalledTimes(1); // no spurious re-emit
317
+
318
+ model.save_changes();
319
+ expect(mockComm.sendUpdate).not.toHaveBeenCalled();
320
+ });
266
321
  });
267
322
 
268
323
  describe("emitCustomMessage", () => {
@@ -53,7 +53,8 @@ export function getMarimoInternal<T extends ModelState>(
53
53
 
54
54
  export class Model<T extends ModelState> implements AnyModel<T> {
55
55
  #ANY_CHANGE_EVENT = "change";
56
- #dirtyFields: Map<keyof T, unknown>;
56
+ /** Keys last written by the widget that the kernel hasn't seen yet. */
57
+ #dirtyFields: Set<keyof T>;
57
58
  #data: T;
58
59
  #comm: MarimoComm<T>;
59
60
  #listeners: Record<string, Set<EventHandler> | undefined> = {};
@@ -70,7 +71,7 @@ export class Model<T extends ModelState> implements AnyModel<T> {
70
71
  constructor(data: T, comm: MarimoComm<T>, signal?: AbortSignal) {
71
72
  this.#data = data;
72
73
  this.#comm = comm;
73
- this.#dirtyFields = new Map();
74
+ this.#dirtyFields = new Set();
74
75
  if (signal) {
75
76
  signal.addEventListener("abort", () => {
76
77
  Logger.debug("[Model] Signal aborted, clearing all listeners");
@@ -140,7 +141,7 @@ export class Model<T extends ModelState> implements AnyModel<T> {
140
141
 
141
142
  set<K extends keyof T>(key: K, value: T[K]): void {
142
143
  this.#data = { ...this.#data, [key]: value };
143
- this.#dirtyFields.set(key, value);
144
+ this.#dirtyFields.add(key);
144
145
  this.#emit(`change:${key as K & string}`, value);
145
146
  this.#emitAnyChange();
146
147
  }
@@ -151,7 +152,7 @@ export class Model<T extends ModelState> implements AnyModel<T> {
151
152
  }
152
153
  // Only send the dirty fields, not the entire state.
153
154
  const partialData = Object.fromEntries(
154
- this.#dirtyFields.entries(),
155
+ [...this.#dirtyFields].map((key) => [key, this.#data[key]]),
155
156
  ) as Partial<T>;
156
157
 
157
158
  // Clear the dirty fields to avoid sending again.
@@ -204,6 +205,15 @@ export class Model<T extends ModelState> implements AnyModel<T> {
204
205
  }
205
206
  }
206
207
 
208
+ /**
209
+ * Apply a kernel-originated state update.
210
+ *
211
+ * Unlike `set()`, this never marks fields dirty — the kernel already
212
+ * has these values, so `save_changes()` must not echo them back.
213
+ *
214
+ * The kernel wins conflicts: a pending unsaved widget write to the same
215
+ * key is dropped, not replayed on top.
216
+ */
207
217
  #updateAndEmitDiffs(value: T) {
208
218
  if (value == null) {
209
219
  return;
@@ -211,9 +221,14 @@ export class Model<T extends ModelState> implements AnyModel<T> {
211
221
 
212
222
  Object.keys(value).forEach((key) => {
213
223
  const k = key as keyof T;
224
+ // Unconditional: guarding on inequality would leave a same-valued
225
+ // pending write dirty, and echo it back on save.
226
+ this.#dirtyFields.delete(k);
214
227
  // Shallow equal since these can be large objects
215
228
  if (this.#data[k] !== value[k]) {
216
- this.set(k, value[k]);
229
+ this.#data = { ...this.#data, [k]: value[k] };
230
+ this.#emit(`change:${k as keyof T & string}`, value[k]);
231
+ this.#emitAnyChange();
217
232
  }
218
233
  });
219
234
  }