@vibecook/ice 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/{chunk-5OU3OWDH.js → chunk-3SUDKHUF.js} +3 -3
  2. package/dist/{chunk-LATXYFMO.js → chunk-BE4F4X3Q.js} +148 -37
  3. package/dist/{chunk-LATXYFMO.js.map → chunk-BE4F4X3Q.js.map} +1 -1
  4. package/dist/{chunk-55QCOE74.js → chunk-CBCSIEXW.js} +2 -2
  5. package/dist/devtools.js +1 -1
  6. package/dist/dom.js +2 -2
  7. package/dist/ground.js +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/r3f.js +3 -3
  10. package/dist/react.js +3 -3
  11. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +28 -6
  12. package/dist/types/packages/core/src/index.d.ts +1 -1
  13. package/dist/types/packages/core/src/interaction/install.d.ts +1 -1
  14. package/dist/types/packages/core/src/nav/nested-canvas.d.ts +1 -1
  15. package/dist/types/packages/core/src/ops/point-pick.d.ts +1 -1
  16. package/dist/types/packages/core/src/ops/port-geometry.d.ts +1 -1
  17. package/dist/types/packages/core/src/presence/presence-kit.d.ts +11 -0
  18. package/dist/types/packages/core/src/presence/remote-cursors.d.ts +10 -7
  19. package/dist/types/packages/core/src/systems/l1-pick.d.ts +1 -1
  20. package/dist/types/packages/core/src/systems/l1-wires.d.ts +1 -1
  21. package/dist/types/packages/core/src/systems/l3-connect.d.ts +1 -1
  22. package/dist/types/packages/core/src/systems/l3-drop.d.ts +1 -1
  23. package/dist/types/packages/core/src/systems/l3-marquee.d.ts +1 -1
  24. package/dist/types/packages/core/src/systems/l3-ports.d.ts +1 -1
  25. package/dist/types/packages/core/src/systems/l3-snap.d.ts +1 -1
  26. package/dist/types/packages/core/src/systems/nav-flight.d.ts +1 -1
  27. package/dist/types/packages/devtools/src/attach.d.ts +1 -1
  28. package/dist/types/packages/dom/src/loop.d.ts +1 -1
  29. package/dist/types/packages/dom/src/measure-adapter.d.ts +1 -1
  30. package/dist/types/packages/dom/src/measure-wiring.d.ts +1 -1
  31. package/dist/types/packages/dom/src/pointer-adapter.d.ts +1 -1
  32. package/dist/types/packages/dom/src/reflectors/chrome.d.ts +1 -1
  33. package/dist/types/packages/dom/src/reflectors/cursor.d.ts +1 -1
  34. package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +1 -1
  35. package/dist/types/packages/dom/src/reflectors/graybox.d.ts +1 -1
  36. package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +1 -1
  37. package/dist/types/packages/dom/src/reflectors/remote-cursors.d.ts +1 -1
  38. package/dist/types/packages/dom/src/widget-focus.d.ts +1 -1
  39. package/dist/types/packages/ground/src/index.d.ts +2 -2
  40. package/dist/types/packages/ground/src/layer.d.ts +1 -1
  41. package/dist/types/packages/ground/src/pass.d.ts +1 -1
  42. package/dist/types/packages/ground/src/passes/grid.d.ts +1 -1
  43. package/dist/types/packages/ground/src/passes/guides-collect.d.ts +1 -1
  44. package/dist/types/packages/ground/src/passes/guides.d.ts +1 -1
  45. package/dist/types/packages/ground/src/passes/wires-collect.d.ts +1 -1
  46. package/dist/types/packages/ground/src/passes/wires.d.ts +1 -1
  47. package/dist/types/packages/r3f/src/bridge.d.ts +1 -1
  48. package/dist/types/packages/r3f/src/compositor-pass.d.ts +1 -1
  49. package/dist/types/packages/r3f/src/dev-write-trap.d.ts +1 -1
  50. package/dist/types/packages/r3f/src/gl-root.d.ts +1 -1
  51. package/dist/types/packages/r3f/src/gl-router.d.ts +2 -2
  52. package/dist/types/packages/r3f/src/island-state.d.ts +1 -1
  53. package/dist/types/packages/r3f/src/island.d.ts +1 -1
  54. package/dist/types/packages/r3f/src/use-island-frame.d.ts +1 -1
  55. package/dist/types/packages/react/src/engine-context.d.ts +1 -1
  56. package/dist/types/packages/react/src/hooks.d.ts +1 -1
  57. package/dist/types/packages/react/src/infinite-canvas.d.ts +2 -2
  58. package/dist/types/packages/react/src/keymap.d.ts +1 -1
  59. package/dist/types/packages/react/src/use-commit.d.ts +1 -1
  60. package/dist/types/packages/react/src/use-presence.d.ts +1 -1
  61. package/dist/types/packages/react/src/widget-root.d.ts +1 -1
  62. package/package.json +2 -2
  63. /package/dist/{chunk-5OU3OWDH.js.map → chunk-3SUDKHUF.js.map} +0 -0
  64. /package/dist/{chunk-55QCOE74.js.map → chunk-CBCSIEXW.js.map} +0 -0
@@ -12,7 +12,7 @@ import {
12
12
  keyboardClaimOf,
13
13
  startRafLoop,
14
14
  wireMeasurement
15
- } from "./chunk-55QCOE74.js";
15
+ } from "./chunk-CBCSIEXW.js";
16
16
  import {
17
17
  ActiveTool,
18
18
  DurableUndoStatus,
@@ -33,7 +33,7 @@ import {
33
33
  tools,
34
34
  widgets,
35
35
  writeRuntimeResource
36
- } from "./chunk-LATXYFMO.js";
36
+ } from "./chunk-BE4F4X3Q.js";
37
37
 
38
38
  // ../react/src/hooks.ts
39
39
  import { createContext, useCallback, useContext, useMemo, useRef, useSyncExternalStore } from "react";
@@ -673,4 +673,4 @@ export {
673
673
  InfiniteCanvas,
674
674
  REACT_VERSION
675
675
  };
676
- //# sourceMappingURL=chunk-5OU3OWDH.js.map
676
+ //# sourceMappingURL=chunk-3SUDKHUF.js.map
@@ -7594,6 +7594,15 @@ function createBehaviorRuntime(opts) {
7594
7594
  peersQuery;
7595
7595
  /** Ephemeral only: the local peer this node last published a facet on. */
7596
7596
  facetPeer;
7597
+ /**
7598
+ * The entity whose HOOK is currently executing (review finding 2, 0.8.0).
7599
+ * `writeFacet` consults it so a hook writes only the peer whose instance
7600
+ * it runs for — a same-gap presence swap otherwise lets the OLD peer's
7601
+ * dispose resolve `localPeer` LIVE and land a corpse's farewell on the
7602
+ * fresh peer. Unset outside hooks (captured `ctx.write` closures keep the
7603
+ * blessed behavior: they publish the CURRENT peer).
7604
+ */
7605
+ hookEntity;
7597
7606
  /** The ephemeral singleton was quarantined THIS generation (I17). */
7598
7607
  singletonQuarantined = false;
7599
7608
  /** Uninstalled — a captured ctx.write closure must go inert (I17). */
@@ -7878,6 +7887,10 @@ function createBehaviorRuntime(opts) {
7878
7887
  this.facetPeer = void 0;
7879
7888
  return;
7880
7889
  }
7890
+ if (this.instances.size > 0 && !this.instances.has(peer)) {
7891
+ this.facetPeer = void 0;
7892
+ return;
7893
+ }
7881
7894
  if (peer === this.facetPeer && world.has(peer, this.own)) return;
7882
7895
  this.facetPeer = peer;
7883
7896
  if (!world.has(peer, this.own)) {
@@ -8264,6 +8277,7 @@ function createBehaviorRuntime(opts) {
8264
8277
  }
8265
8278
  if (on.dispose !== void 0) {
8266
8279
  for (const inst of departed) {
8280
+ this.hookEntity = inst.e;
8267
8281
  try {
8268
8282
  invokeHook(
8269
8283
  this.b.name,
@@ -8272,6 +8286,8 @@ function createBehaviorRuntime(opts) {
8272
8286
  );
8273
8287
  } catch (err) {
8274
8288
  onFault(this.b.name, "dispose", inst.e, err);
8289
+ } finally {
8290
+ this.hookEntity = void 0;
8275
8291
  }
8276
8292
  }
8277
8293
  }
@@ -8310,6 +8326,7 @@ function createBehaviorRuntime(opts) {
8310
8326
  * kill a 1000-node layout.
8311
8327
  */
8312
8328
  callHook(hook, inst, faulted, body) {
8329
+ this.hookEntity = inst.e;
8313
8330
  try {
8314
8331
  invokeHook(this.b.name, hook, body);
8315
8332
  inst.consecutiveThrows = 0;
@@ -8339,6 +8356,8 @@ function createBehaviorRuntime(opts) {
8339
8356
  this.guest.fault(err);
8340
8357
  }
8341
8358
  return false;
8359
+ } finally {
8360
+ this.hookEntity = void 0;
8342
8361
  }
8343
8362
  }
8344
8363
  // --- generation --------------------------------------------------------
@@ -8346,6 +8365,7 @@ function createBehaviorRuntime(opts) {
8346
8365
  const on = this.b.on;
8347
8366
  if (on.dispose !== void 0) {
8348
8367
  for (const inst of this.instances.values()) {
8368
+ this.hookEntity = inst.e;
8349
8369
  try {
8350
8370
  invokeHook(
8351
8371
  this.b.name,
@@ -8354,6 +8374,8 @@ function createBehaviorRuntime(opts) {
8354
8374
  );
8355
8375
  } catch (err) {
8356
8376
  onFault(this.b.name, "dispose", inst.e, err);
8377
+ } finally {
8378
+ this.hookEntity = void 0;
8357
8379
  }
8358
8380
  }
8359
8381
  }
@@ -8462,6 +8484,7 @@ function createBehaviorRuntime(opts) {
8462
8484
  if (presence === void 0) return;
8463
8485
  const peer = presence.localPeer;
8464
8486
  if (!world.isAlive(peer)) return;
8487
+ if (this.hookEntity !== void 0 && this.hookEntity !== peer) return;
8465
8488
  const cell = cellForBehavior(world, this.b, peer, patch);
8466
8489
  if (world.has(peer, this.own)) presence.eph.edit(peer).set(this.own, cell);
8467
8490
  else presence.eph.addComponent(peer, this.own, cell);
@@ -9707,6 +9730,10 @@ var DEFAULT_PRESENCE_TTL_MS = 5e3;
9707
9730
  function attachPresence(world, opts) {
9708
9731
  const peerId = opts.peerId ?? crypto.randomUUID();
9709
9732
  const ttlMs = opts.ttlMs ?? DEFAULT_PRESENCE_TTL_MS;
9733
+ const onFault = opts.onFault ?? ((where, error) => {
9734
+ const detail = where === "outbound" ? "presence outbound subscriber threw \u2014 bytes dropped for that subscriber (transport handlers must not throw)" : where === "leave" ? "presence leave failed \u2014 tearing the binding down anyway (remote peers drop us at TTL instead of immediately)" : "presence reset-heal leave failed \u2014 rebinding anyway (stale keys age out at TTL)";
9735
+ console.error(`[ice] ${detail}`, error);
9736
+ });
9710
9737
  const loro = new LoroEphemeralStore(ttlMs);
9711
9738
  const source = new LoroEphemeralSnapshot(loro);
9712
9739
  const outbound = /* @__PURE__ */ new Set();
@@ -9714,7 +9741,13 @@ function attachPresence(world, opts) {
9714
9741
  peerId,
9715
9742
  ttlMs,
9716
9743
  send: (bytes) => {
9717
- for (const fn of [...outbound]) fn(bytes);
9744
+ for (const fn of [...outbound]) {
9745
+ try {
9746
+ fn(bytes);
9747
+ } catch (err) {
9748
+ onFault("outbound", err);
9749
+ }
9750
+ }
9718
9751
  }
9719
9752
  });
9720
9753
  let attachment = attachEphemeral(world, eph);
@@ -9732,7 +9765,11 @@ function attachPresence(world, opts) {
9732
9765
  queueMicrotask(() => {
9733
9766
  healQueued = false;
9734
9767
  if (detached) return;
9735
- eph.leave();
9768
+ try {
9769
+ eph.leave();
9770
+ } catch (err) {
9771
+ onFault("heal", err);
9772
+ }
9736
9773
  attachment.detach();
9737
9774
  attachment = attachEphemeral(world, eph);
9738
9775
  localPeer = spawnLocalPeer();
@@ -9758,7 +9795,11 @@ function attachPresence(world, opts) {
9758
9795
  if (detached) return;
9759
9796
  detached = true;
9760
9797
  stopResetHeal();
9761
- eph.leave();
9798
+ try {
9799
+ eph.leave();
9800
+ } catch (err) {
9801
+ onFault("leave", err);
9802
+ }
9762
9803
  attachment.detach();
9763
9804
  outbound.clear();
9764
9805
  loro.destroy();
@@ -9867,9 +9908,9 @@ function summaryEquals(a, b) {
9867
9908
  import { Not as Not9, defineQuery as defineQuery38, defineTickSystem as defineTickSystem15 } from "@vibecook/strata-ecs";
9868
9909
  import { Local as Local2 } from "@vibecook/strata-ecs";
9869
9910
  var remotePeerCursorsQ = defineQuery38([PresenceCursor, PresencePeer, Not9(Local2)]);
9870
- function createRemoteCursorsSystem(_world) {
9911
+ function createRemoteCursorsRig(world) {
9871
9912
  const pool = /* @__PURE__ */ new Map();
9872
- return defineTickSystem15(
9913
+ const system = defineTickSystem15(
9873
9914
  (ctx) => {
9874
9915
  const live = /* @__PURE__ */ new Set();
9875
9916
  ctx.query(remotePeerCursorsQ).each((b) => {
@@ -9903,16 +9944,38 @@ function createRemoteCursorsSystem(_world) {
9903
9944
  },
9904
9945
  {
9905
9946
  name: "remoteCursors",
9906
- access: { write: [Position, CursorVisual] }
9947
+ // orderIndependent: every row this system writes is its OWN pool's
9948
+ // cursor entity (one per remote peer) — row-disjoint from any other
9949
+ // Position writer by construction. The attestation is what lets a
9950
+ // userland `present`-phase behavior that co-writes Position silence
9951
+ // strata's double-writer advisory from its own side: suppression is
9952
+ // conjunctive over ALL co-writers, so an un-attested engine system
9953
+ // would make the warning unsilenceable for authors doing it right
9954
+ // (0.8.0 review finding).
9955
+ access: { write: [Position, CursorVisual], orderIndependent: [Position] }
9907
9956
  }
9908
9957
  );
9958
+ return {
9959
+ system,
9960
+ reap() {
9961
+ for (const [, entry] of pool) {
9962
+ if (world.isAlive(entry.cursor)) world.destroy(entry.cursor);
9963
+ }
9964
+ pool.clear();
9965
+ }
9966
+ };
9967
+ }
9968
+ function createRemoteCursorsSystem(world) {
9969
+ return createRemoteCursorsRig(world).system;
9909
9970
  }
9910
9971
  function installPresence(engine, session, opts = {}) {
9911
9972
  const removePublish = engine.onPublish(createPresencePublish(engine.world, session, opts));
9912
- const removeSystem = engine.addSystems("derive", createRemoteCursorsSystem(engine.world));
9973
+ const rig = createRemoteCursorsRig(engine.world);
9974
+ const removeSystem = engine.addSystems("present", rig.system);
9913
9975
  return () => {
9914
9976
  removePublish();
9915
9977
  removeSystem();
9978
+ rig.reap();
9916
9979
  };
9917
9980
  }
9918
9981
 
@@ -10381,15 +10444,36 @@ function createCanvasEngine(opts = {}) {
10381
10444
  sink.target = next.sink;
10382
10445
  return next;
10383
10446
  };
10447
+ const acquirePresence = (o) => {
10448
+ const p2 = attachPresence(world, o);
10449
+ presence = p2;
10450
+ uninstallPresence = installPresence(engine, p2, {
10451
+ keyOf: (e) => session?.store.keyOf(e)
10452
+ });
10453
+ return p2;
10454
+ };
10455
+ const releasePresence = () => {
10456
+ const uninstall = uninstallPresence;
10457
+ const p2 = presence;
10458
+ uninstallPresence = void 0;
10459
+ presence = void 0;
10460
+ try {
10461
+ uninstall?.();
10462
+ } catch (err) {
10463
+ console.error("[ice] presence uninstall threw \u2014 teardown continues", err);
10464
+ }
10465
+ try {
10466
+ p2?.detach();
10467
+ } catch (err) {
10468
+ console.error("[ice] presence detach threw \u2014 teardown continues", err);
10469
+ }
10470
+ };
10384
10471
  const closeDoc = () => {
10385
10472
  joinAbort?.abort(new Error("ice: docs.join superseded \u2014 another document was opened while joining."));
10386
10473
  joinAbort = void 0;
10387
10474
  for (const a of [...liveAutosaves]) a.stop();
10388
10475
  liveAutosaves.clear();
10389
- uninstallPresence?.();
10390
- uninstallPresence = void 0;
10391
- presence?.detach();
10392
- presence = void 0;
10476
+ releasePresence();
10393
10477
  joined?.leave();
10394
10478
  joined = void 0;
10395
10479
  session?.close();
@@ -10434,29 +10518,35 @@ function createCanvasEngine(opts = {}) {
10434
10518
  joinAbort = ac;
10435
10519
  const { presence: presenceOpts, ...joinOpts } = o;
10436
10520
  if (presenceOpts !== void 0) {
10437
- presence = attachPresence(world, { name: presenceOpts.name, color: presenceOpts.color });
10438
- uninstallPresence = installPresence(engine, presence, {
10439
- keyOf: (e) => session?.store.keyOf(e)
10440
- });
10521
+ acquirePresence({ name: presenceOpts.name, color: presenceOpts.color });
10441
10522
  }
10442
- const result = await joinDoc(world, channel, {
10443
- ...gatePolicy !== void 0 ? { docOpts: { onGate: gatePolicy } } : {},
10444
- ...presence !== void 0 ? { presence } : {},
10445
- ...joinOpts,
10446
- signal: ac.signal,
10447
- // Re-bootstrap swaps the session inside joinDoc (PendingImportError
10448
- // recovery) — re-target the forwarding sink or ops keep committing
10449
- // into the quarantined session.
10450
- onSession: (s) => {
10451
- if (joinAbort !== ac) return;
10452
- if (s !== void 0) {
10453
- adoptSession(s);
10454
- } else {
10455
- session = void 0;
10456
- sink.target = void 0;
10523
+ let result;
10524
+ try {
10525
+ result = await joinDoc(world, channel, {
10526
+ ...gatePolicy !== void 0 ? { docOpts: { onGate: gatePolicy } } : {},
10527
+ ...presence !== void 0 ? { presence } : {},
10528
+ ...joinOpts,
10529
+ signal: ac.signal,
10530
+ // Re-bootstrap swaps the session inside joinDoc (PendingImportError
10531
+ // recovery) — re-target the forwarding sink or ops keep committing
10532
+ // into the quarantined session.
10533
+ onSession: (s) => {
10534
+ if (joinAbort !== ac) return;
10535
+ if (s !== void 0) {
10536
+ adoptSession(s);
10537
+ } else {
10538
+ session = void 0;
10539
+ sink.target = void 0;
10540
+ }
10457
10541
  }
10542
+ });
10543
+ } catch (err) {
10544
+ if (joinAbort === ac) {
10545
+ joinAbort = void 0;
10546
+ releasePresence();
10458
10547
  }
10459
- });
10548
+ throw err;
10549
+ }
10460
10550
  if (joinAbort !== ac) {
10461
10551
  result.leave();
10462
10552
  throw new Error("ice: docs.join superseded \u2014 another document was opened while joining.");
@@ -10466,6 +10556,26 @@ function createCanvasEngine(opts = {}) {
10466
10556
  return result;
10467
10557
  },
10468
10558
  current: () => session,
10559
+ attachPresence(o) {
10560
+ if (session === void 0) {
10561
+ throw new Error(
10562
+ "ice: docs.attachPresence needs a live document \u2014 call docs.create()/open()/join() first."
10563
+ );
10564
+ }
10565
+ if (presence !== void 0) {
10566
+ throw new Error(
10567
+ "ice: docs.attachPresence \u2014 a presence session is already live (join({presence}) or a prior attach); detach it (its inverse, or docs.close()) before attaching another."
10568
+ );
10569
+ }
10570
+ const mine = acquirePresence(o);
10571
+ let done = false;
10572
+ return () => {
10573
+ if (done) return;
10574
+ done = true;
10575
+ if (presence !== mine) return;
10576
+ releasePresence();
10577
+ };
10578
+ },
10469
10579
  presence: () => presence,
10470
10580
  close: closeDoc,
10471
10581
  // Read-only documents must not mutate through history either — undo/redo
@@ -10495,10 +10605,11 @@ function createCanvasEngine(opts = {}) {
10495
10605
  world,
10496
10606
  engine,
10497
10607
  session: () => session,
10498
- // Presence attaches with `docs.join({presence})` and detaches with the
10499
- // document, so this reads through per publish: an ephemeral behavior
10500
- // installed before anyone joins simply lies dormant until there is a peer
10501
- // for it to be.
10608
+ // Presence attaches with `docs.join({presence})` OR `docs.attachPresence`
10609
+ // (petition I18) and detaches with the document / the attach inverse, so
10610
+ // this reads through per publish: an ephemeral behavior installed before
10611
+ // there is a presence session simply lies dormant until there is a peer
10612
+ // for it to be — and goes dormant again if the session detaches under it.
10502
10613
  presence: () => presence,
10503
10614
  ...opts.onBehaviorFault === void 0 ? {} : { onFault: opts.onBehaviorFault },
10504
10615
  ...opts.onBehaviorLog === void 0 ? {} : { onLog: opts.onBehaviorLog }
@@ -10733,7 +10844,7 @@ import {
10733
10844
  Local as Local3
10734
10845
  } from "@vibecook/strata-ecs";
10735
10846
  import { DurableUndoStatus } from "@vibecook/strata-ecs/durable";
10736
- var CORE_VERSION = "0.7.0";
10847
+ var CORE_VERSION = "0.8.0";
10737
10848
 
10738
10849
  export {
10739
10850
  defineSchemaOnce,
@@ -11018,4 +11129,4 @@ export {
11018
11129
  Local3 as Local,
11019
11130
  DurableUndoStatus
11020
11131
  };
11021
- //# sourceMappingURL=chunk-LATXYFMO.js.map
11132
+ //# sourceMappingURL=chunk-BE4F4X3Q.js.map