kritzel-stencil 0.1.74 → 0.1.76

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 (43) hide show
  1. package/dist/cjs/index.cjs.js +131 -86
  2. package/dist/cjs/kritzel-active-users_42.cjs.entry.js +132 -19
  3. package/dist/cjs/{workspace.migrations-Dyt35LBC.js → workspace.migrations-DkmVO6dE.js} +106 -44
  4. package/dist/collection/classes/core/viewport.class.js +32 -3
  5. package/dist/collection/classes/managers/anchor.manager.js +101 -44
  6. package/dist/collection/classes/providers/broadcast-sync-provider.class.js +5 -0
  7. package/dist/collection/classes/providers/hocuspocus-sync-provider.class.js +120 -85
  8. package/dist/collection/classes/providers/indexeddb-sync-provider.class.js +5 -0
  9. package/dist/collection/classes/providers/websocket-sync-provider.class.js +5 -0
  10. package/dist/collection/classes/structures/app-state-map.structure.js +15 -4
  11. package/dist/collection/classes/structures/object-map.structure.js +75 -7
  12. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.css +2 -2
  13. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js +7 -2
  14. package/dist/collection/constants/version.js +1 -1
  15. package/dist/components/index.js +1 -1
  16. package/dist/components/kritzel-awareness-cursors.js +1 -1
  17. package/dist/components/kritzel-editor.js +1 -1
  18. package/dist/components/kritzel-engine.js +1 -1
  19. package/dist/components/kritzel-settings.js +1 -1
  20. package/dist/components/{p-B4Oqnl55.js → p-31FVoNWR.js} +1 -1
  21. package/dist/components/p-jdYmu4SA.js +9 -0
  22. package/dist/components/p-xNwOWoiT.js +1 -0
  23. package/dist/esm/index.js +132 -87
  24. package/dist/esm/kritzel-active-users_42.entry.js +132 -19
  25. package/dist/esm/{workspace.migrations-B99F1MdT.js → workspace.migrations-D48_Bqvh.js} +106 -44
  26. package/dist/stencil/index.esm.js +1 -1
  27. package/dist/stencil/p-775a7246.entry.js +9 -0
  28. package/dist/stencil/{p-B99F1MdT.js → p-D48_Bqvh.js} +1 -1
  29. package/dist/stencil/stencil.esm.js +1 -1
  30. package/dist/types/classes/core/viewport.class.d.ts +8 -0
  31. package/dist/types/classes/managers/anchor.manager.d.ts +4 -0
  32. package/dist/types/classes/providers/broadcast-sync-provider.class.d.ts +2 -0
  33. package/dist/types/classes/providers/hocuspocus-sync-provider.class.d.ts +37 -1
  34. package/dist/types/classes/providers/indexeddb-sync-provider.class.d.ts +2 -0
  35. package/dist/types/classes/providers/websocket-sync-provider.class.d.ts +2 -0
  36. package/dist/types/classes/structures/object-map.structure.d.ts +6 -0
  37. package/dist/types/constants/version.d.ts +1 -1
  38. package/dist/types/interfaces/remote-cursor.interface.d.ts +1 -0
  39. package/dist/types/interfaces/sync-provider.interface.d.ts +16 -0
  40. package/package.json +1 -1
  41. package/dist/components/p-BSipRoFx.js +0 -1
  42. package/dist/components/p-RJWe82kG.js +0 -9
  43. package/dist/stencil/p-2a60e1bc.entry.js +0 -9
@@ -0,0 +1 @@
1
+ import{p as t,H as e,h as s,d as r,t as o}from"./p-pebXO4LU.js";import{K as n,a as i}from"./p-DXpYcAnT.js";const a=t(class extends e{constructor(t){super(),!1!==t&&this.__registerHost(),this.__attachShadow()}core;showEdgeIndicators=!0;edgeIndicatorPadding=8;remoteCursors=new Map;objectVersion=0;cleanupIntervalId;objectChangeRafId=null;componentDidLoad(){this.core.store.state.objects?.onAwarenessChange((t=>{this.handleAwarenessChange(t)})),this.core.store.state.objects?.onObjectsChange((()=>{this.handleRemoteObjectChange()})),this.cleanupIntervalId=setInterval((()=>{this.cleanupStaleCursors()}),3e3)}disconnectedCallback(){this.cleanupIntervalId&&clearInterval(this.cleanupIntervalId),null!==this.objectChangeRafId&&cancelAnimationFrame(this.objectChangeRafId)}handleAwarenessChange(t){const e=this.core.store.state.objects?.localClientId,s=Date.now(),r=new Map(this.remoteCursors),o=new Set;t.forEach(((t,n)=>{if(n===e)return;if(!t.user)return;o.add(n);const i=t.user,a=t.cursor,l=t.activeObjectId||null,c=t.selectionBox||null,d=r.get(n);r.set(n,{clientId:n,user:i,cursor:a,activeObjectId:l,selectionBox:c,lastUpdated:s,lastCursorMove:!d||!d.cursor!=!a||a&&d.cursor&&(a.x!==d.cursor.x||a.y!==d.cursor.y)?s:d?.lastCursorMove??s})}));for(const t of r.keys())o.has(t)||r.delete(t);this.remoteCursors=r}cleanupStaleCursors(){const t=Date.now();let e=!1;const s=new Map(this.remoteCursors);for(const[r,o]of s)t-o.lastUpdated>3e4&&(s.delete(r),e=!0);e&&(this.remoteCursors=s)}isStale(t){return Date.now()-t.lastCursorMove>1e4}hasActiveDrawingCursors(){for(const t of this.remoteCursors.values())if(t.activeObjectId)return!0;return!1}handleRemoteObjectChange(){this.hasActiveDrawingCursors()&&null===this.objectChangeRafId&&(this.objectChangeRafId=requestAnimationFrame((()=>{this.objectChangeRafId=null,this.objectVersion++})))}getActiveObjectTip(t){const e=this.core.store.state.objects?.findById(t);if(!e)return null;if(e instanceof n&&!e.isCompleted){const t=e.points[e.points.length-1];return t?{x:(t[0]-e.x)/e.scale+e.translateX,y:(t[1]-e.y)/e.scale+e.translateY}:null}return e instanceof i&&!e.isCompleted?{x:(e.endX-e.x)/e.scale+e.translateX,y:(e.endY-e.y)/e.scale+e.translateY}:null}worldToScreen(t,e){const{scale:s,translateX:r,translateY:o}=this.core.store.state;return{x:t*s+r,y:e*s+o}}isInViewport(t,e){const{viewportWidth:s,viewportHeight:r}=this.core.store.state;return t>=0&&t<=s&&e>=0&&e<=r}clampToEdge(t,e){const{viewportWidth:s,viewportHeight:r}=this.core.store.state,o=this.edgeIndicatorPadding,n=Math.max(o,Math.min(s-o,t)),i=Math.max(o,Math.min(r-o,e)),a=n-o,l=s-o-n,c=i-o;let d="top";const h=Math.min(a,l,c,r-o-i);return d=h===a?"left":h===l?"right":h===c?"top":"bottom",{x:n,y:i,angle:Math.atan2(e-i,t-n),edge:d}}getUserDisplayName(t){return t.displayName?t.displayName:t.firstName||t.lastName?[t.firstName,t.lastName].filter(Boolean).join(" "):"Unknown"}render(){const t=Array.from(this.remoteCursors.values());return s(r,{key:"4dd962322c7e955b9038c55cb10f8ffda1e0b246"},t.map((t=>{if(!t.cursor)return null;let e,s=!1;if(t.activeObjectId){const r=this.getActiveObjectTip(t.activeObjectId);r?(s=!0,e=this.worldToScreen(r.x,r.y)):e=this.worldToScreen(t.cursor.x,t.cursor.y)}else e=this.worldToScreen(t.cursor.x,t.cursor.y);const r=this.isInViewport(e.x,e.y),o=this.isStale(t),n=t.user.color||"#6B7280";return r?this.renderCursor(t,e.x,e.y,n,o,s):this.showEdgeIndicators?this.renderEdgeIndicator(t,e.x,e.y,n,o,s):null})),t.map((t=>{if(!t.selectionBox)return null;const e=t.user.color||"#6B7280",r=t.selectionBox,o=this.worldToScreen(r.x,r.y),{scale:n}=this.core.store.state;return s("div",{key:`selection-box-${t.clientId}`,class:"remote-selection-box",style:{transform:`translate(${o.x}px, ${o.y}px)`,width:r.width*n+"px",height:r.height*n+"px",backgroundColor:`color-mix(in srgb, ${e} 20%, transparent)`,borderColor:`color-mix(in srgb, ${e} 50%, transparent)`}})})))}renderCursor(t,e,r,o,n,i){return s("div",{key:`cursor-${t.clientId}`,class:{"awareness-cursor":!0,stale:n,"tracking-object":i},style:{transform:`translate(${e}px, ${r}px)`}},s("svg",{class:"cursor-arrow",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg"},s("path",{d:"M5 3L19 12L12 13L9 20L5 3Z",fill:o,stroke:"#ffffff","stroke-width":"1.5","stroke-linejoin":"round"})),s("span",{class:"cursor-label",style:{backgroundColor:o}},this.getUserDisplayName(t.user)))}renderEdgeIndicator(t,e,r,o,n,i){const a=this.clampToEdge(e,r),l=180*a.angle/Math.PI+90;let c=0,d=0;"left"===a.edge?c=20:"right"===a.edge?c=-20:"top"===a.edge?d=20:"bottom"===a.edge&&(d=-20);const h=this.getUserDisplayName(t.user);return s("div",{key:`edge-${t.clientId}`,class:{"edge-indicator":!0,stale:n,"tracking-object":i},style:{transform:`translate(${a.x}px, ${a.y}px)`}},s("svg",{class:"edge-arrow",width:"16",height:"16",viewBox:"0 0 16 16",style:{transform:`rotate(${l}deg)`}},s("path",{d:"M8 1L14 13H2L8 1Z",fill:o,stroke:"#ffffff","stroke-width":"1.5","stroke-linejoin":"round"})),s("span",{class:"edge-label",style:{backgroundColor:o,transform:`translate(${c}px, ${d}px)`}},h))}static get style(){return":host{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9500}.awareness-cursor{position:absolute;top:0;left:0;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform}.awareness-cursor.stale{opacity:0}.awareness-cursor.tracking-object{transition-duration:0ms}.cursor-arrow{filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.cursor-label{position:absolute;left:16px;top:16px;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;user-select:none}.edge-indicator{position:absolute;top:-12px;left:-12px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform;pointer-events:auto;user-select:none;cursor:pointer}.edge-indicator.stale{opacity:0}.edge-indicator.tracking-object{transition-duration:0ms}.edge-arrow{position:absolute;filter:drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3))}.edge-label{position:absolute;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;opacity:0;transform-origin:center;transition:opacity 150ms ease}.edge-indicator:hover .edge-label{opacity:1}.remote-selection-box{position:absolute;top:0;left:0;border-width:2px;border-style:solid;pointer-events:none;will-change:transform, width, height;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, width var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, height var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out}"}},[513,"kritzel-awareness-cursors",{core:[16],showEdgeIndicators:[4,"show-edge-indicators"],edgeIndicatorPadding:[2,"edge-indicator-padding"],remoteCursors:[32],objectVersion:[32]}]);function l(){"undefined"!=typeof customElements&&["kritzel-awareness-cursors"].forEach((t=>{"kritzel-awareness-cursors"===t&&(customElements.get(o(t))||customElements.define(o(t),a))}))}export{a as K,l as d}
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { H as HocuspocusProvider, a as HocuspocusProviderWebsocket } from './workspace.migrations-B99F1MdT.js';
2
- export { A as APP_STATE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, w as CURRENT_WORKSPACE_SCHEMA_VERSION, D as DEFAULT_BRUSH_CONFIG, s as DEFAULT_LINE_TOOL_CONFIG, r as DEFAULT_TEXT_CONFIG, I as IndexedDBSyncProvider, v as KritzelAlignment, p as KritzelAnchorManager, g as KritzelBrushTool, m as KritzelCursorHelper, i as KritzelEraserTool, e as KritzelGroup, c as KritzelImage, j as KritzelImageTool, d as KritzelLine, h as KritzelLineTool, b as KritzelPath, n as KritzelSelectionTool, f as KritzelShape, l as KritzelShapeTool, K as KritzelText, k as KritzelTextTool, q as KritzelThemeManager, o as KritzelWorkspace, S as ShapeType, W as WORKSPACE_EXPORT_VERSION, y as WORKSPACE_MIGRATIONS, u as darkTheme, t as lightTheme, x as runMigrations } from './workspace.migrations-B99F1MdT.js';
1
+ import { H as HocuspocusProvider, a as HocuspocusProviderWebsocket } from './workspace.migrations-D48_Bqvh.js';
2
+ export { A as APP_STATE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, w as CURRENT_WORKSPACE_SCHEMA_VERSION, D as DEFAULT_BRUSH_CONFIG, s as DEFAULT_LINE_TOOL_CONFIG, r as DEFAULT_TEXT_CONFIG, I as IndexedDBSyncProvider, v as KritzelAlignment, p as KritzelAnchorManager, g as KritzelBrushTool, m as KritzelCursorHelper, i as KritzelEraserTool, e as KritzelGroup, c as KritzelImage, j as KritzelImageTool, d as KritzelLine, h as KritzelLineTool, b as KritzelPath, n as KritzelSelectionTool, f as KritzelShape, l as KritzelShapeTool, K as KritzelText, k as KritzelTextTool, q as KritzelThemeManager, o as KritzelWorkspace, S as ShapeType, W as WORKSPACE_EXPORT_VERSION, y as WORKSPACE_MIGRATIONS, u as darkTheme, t as lightTheme, x as runMigrations } from './workspace.migrations-D48_Bqvh.js';
3
3
  import * as Y from 'yjs';
4
4
  import { WebsocketProvider } from 'y-websocket';
5
5
  import 'y-indexeddb';
@@ -339,6 +339,7 @@ const readVarUint = decoder => {
339
339
  * This is a lightweight alternative to y-webrtc for browser-tab-only sync
340
340
  */
341
341
  class BroadcastSyncProvider {
342
+ type = 'local';
342
343
  doc;
343
344
  channel;
344
345
  _synced = false;
@@ -420,6 +421,10 @@ class BroadcastSyncProvider {
420
421
  disconnect() {
421
422
  // BroadcastChannel doesn't have explicit disconnect
422
423
  }
424
+ async reconnect() {
425
+ this.disconnect();
426
+ return this.connect();
427
+ }
423
428
  destroy() {
424
429
  this.doc.off('update', this.handleDocUpdate);
425
430
  this.channel.close();
@@ -430,6 +435,7 @@ class BroadcastSyncProvider {
430
435
  * WebSocket sync provider for real-time collaboration
431
436
  */
432
437
  class WebSocketSyncProvider {
438
+ type = 'network';
433
439
  provider;
434
440
  isConnected = false;
435
441
  _quiet = false;
@@ -518,6 +524,10 @@ class WebSocketSyncProvider {
518
524
  }
519
525
  this.isConnected = false;
520
526
  }
527
+ async reconnect() {
528
+ this.disconnect();
529
+ return this.connect();
530
+ }
521
531
  destroy() {
522
532
  if (this.provider) {
523
533
  this.provider.destroy();
@@ -531,6 +541,7 @@ class WebSocketSyncProvider {
531
541
  * Supports multiplexing - multiple documents can share the same WebSocket connection
532
542
  */
533
543
  class HocuspocusSyncProvider {
544
+ type = 'network';
534
545
  provider;
535
546
  isConnected = false;
536
547
  isSynced = false;
@@ -538,16 +549,87 @@ class HocuspocusSyncProvider {
538
549
  isDestroyed = false;
539
550
  connectTimeout = null;
540
551
  pendingConnectReject = null;
552
+ connectionTimeoutMs;
553
+ _connectionStatus = 'disconnected';
554
+ visibilityHandler = null;
555
+ onlineHandler = null;
541
556
  get awareness() {
542
557
  return this.provider.awareness;
543
558
  }
559
+ get connectionStatus() {
560
+ return this._connectionStatus;
561
+ }
544
562
  // Static shared WebSocket instance for multiplexing
545
563
  static sharedWebSocketProvider = null;
546
564
  constructor(docName, doc, options) {
547
565
  const name = options?.name || docName;
548
566
  const url = options?.url || 'ws://localhost:1234';
567
+ this.connectionTimeoutMs = options?.connectionTimeout ?? 10000;
549
568
  // Use provided websocketProvider or the static shared one
550
569
  const websocketProvider = options?.websocketProvider || HocuspocusSyncProvider.sharedWebSocketProvider;
570
+ // Build reconnect config from options
571
+ const reconnectConfig = {};
572
+ if (options?.delay !== undefined)
573
+ reconnectConfig.delay = options.delay;
574
+ if (options?.factor !== undefined)
575
+ reconnectConfig.factor = options.factor;
576
+ if (options?.maxAttempts !== undefined)
577
+ reconnectConfig.maxAttempts = options.maxAttempts;
578
+ if (options?.minDelay !== undefined)
579
+ reconnectConfig.minDelay = options.minDelay;
580
+ if (options?.maxDelay !== undefined)
581
+ reconnectConfig.maxDelay = options.maxDelay;
582
+ const onConnect = () => {
583
+ if (this.isDestroyed) {
584
+ return;
585
+ }
586
+ this.isConnected = true;
587
+ this._connectionStatus = 'connected';
588
+ if (!options?.quiet) {
589
+ console.info(`Hocuspocus connected: ${name}`);
590
+ }
591
+ if (options?.onConnect) {
592
+ options.onConnect();
593
+ }
594
+ };
595
+ const onDisconnect = () => {
596
+ if (this.isDestroyed) {
597
+ return;
598
+ }
599
+ this.isConnected = false;
600
+ this.isSynced = false;
601
+ this._connectionStatus = 'disconnected';
602
+ if (!options?.quiet) {
603
+ console.info(`Hocuspocus disconnected: ${name}`);
604
+ }
605
+ if (options?.onDisconnect) {
606
+ options.onDisconnect();
607
+ }
608
+ };
609
+ const onSynced = () => {
610
+ if (this.isDestroyed) {
611
+ return;
612
+ }
613
+ this.isSynced = true;
614
+ this._connectionStatus = 'synced';
615
+ if (!options?.quiet) {
616
+ console.info(`Hocuspocus synced: ${name}`);
617
+ }
618
+ if (options?.onSynced) {
619
+ options.onSynced();
620
+ }
621
+ };
622
+ const onStatus = (data) => {
623
+ if (this.isDestroyed) {
624
+ return;
625
+ }
626
+ if (data.status === 'connecting') {
627
+ this._connectionStatus = 'connecting';
628
+ }
629
+ if (options?.onStatus) {
630
+ options.onStatus(data);
631
+ }
632
+ };
551
633
  if (websocketProvider) {
552
634
  // Multiplexing mode - use shared WebSocket connection
553
635
  this.usesSharedSocket = true;
@@ -556,48 +638,11 @@ class HocuspocusSyncProvider {
556
638
  name,
557
639
  document: doc,
558
640
  token: options?.token || null,
559
- onStatus: (data) => {
560
- if (options?.onStatus) {
561
- options.onStatus(data);
562
- }
563
- },
564
- onConnect: () => {
565
- if (this.isConnected || this.isDestroyed) {
566
- return;
567
- }
568
- this.isConnected = true;
569
- if (!options?.quiet) {
570
- console.info(`Hocuspocus connected: ${name}`);
571
- }
572
- if (options?.onConnect) {
573
- options.onConnect();
574
- }
575
- },
576
- onDisconnect: () => {
577
- if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
578
- return;
579
- }
580
- this.isConnected = false;
581
- this.isSynced = false;
582
- if (!options?.quiet) {
583
- console.info(`Hocuspocus disconnected: ${name}`);
584
- }
585
- if (options?.onDisconnect) {
586
- options.onDisconnect();
587
- }
588
- },
589
- onSynced: () => {
590
- if (this.isSynced || this.isDestroyed) {
591
- return;
592
- }
593
- this.isSynced = true;
594
- if (!options?.quiet) {
595
- console.info(`Hocuspocus synced: ${name}`);
596
- }
597
- if (options?.onSynced) {
598
- options.onSynced();
599
- }
600
- },
641
+ onStatus,
642
+ onConnect,
643
+ onDisconnect,
644
+ onSynced,
645
+ ...reconnectConfig,
601
646
  };
602
647
  // Add optional settings
603
648
  if (options?.forceSyncInterval !== undefined) {
@@ -622,48 +667,11 @@ class HocuspocusSyncProvider {
622
667
  document: doc,
623
668
  token: options?.token || null,
624
669
  autoConnect: false,
625
- onStatus: (data) => {
626
- if (options?.onStatus) {
627
- options.onStatus(data);
628
- }
629
- },
630
- onConnect: () => {
631
- if (this.isConnected || this.isDestroyed) {
632
- return;
633
- }
634
- this.isConnected = true;
635
- if (!options?.quiet) {
636
- console.info(`Hocuspocus connected: ${name}`);
637
- }
638
- if (options?.onConnect) {
639
- options.onConnect();
640
- }
641
- },
642
- onDisconnect: () => {
643
- if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
644
- return;
645
- }
646
- this.isConnected = false;
647
- this.isSynced = false;
648
- if (!options?.quiet) {
649
- console.info(`Hocuspocus disconnected: ${name}`);
650
- }
651
- if (options?.onDisconnect) {
652
- options.onDisconnect();
653
- }
654
- },
655
- onSynced: () => {
656
- if (this.isSynced || this.isDestroyed) {
657
- return;
658
- }
659
- this.isSynced = true;
660
- if (!options?.quiet) {
661
- console.info(`Hocuspocus synced: ${name}`);
662
- }
663
- if (options?.onSynced) {
664
- options.onSynced();
665
- }
666
- },
670
+ onStatus,
671
+ onConnect,
672
+ onDisconnect,
673
+ onSynced,
674
+ ...reconnectConfig,
667
675
  };
668
676
  // Add optional settings
669
677
  if (options?.forceSyncInterval !== undefined) {
@@ -680,6 +688,35 @@ class HocuspocusSyncProvider {
680
688
  console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
681
689
  }
682
690
  }
691
+ this.setupBrowserEventListeners();
692
+ }
693
+ setupBrowserEventListeners() {
694
+ if (typeof document !== 'undefined') {
695
+ this.visibilityHandler = () => {
696
+ if (document.visibilityState === 'visible' && !this.isConnected && !this.isDestroyed) {
697
+ this.provider.connect();
698
+ }
699
+ };
700
+ document.addEventListener('visibilitychange', this.visibilityHandler);
701
+ }
702
+ if (typeof window !== 'undefined') {
703
+ this.onlineHandler = () => {
704
+ if (!this.isConnected && !this.isDestroyed) {
705
+ this.provider.connect();
706
+ }
707
+ };
708
+ window.addEventListener('online', this.onlineHandler);
709
+ }
710
+ }
711
+ removeBrowserEventListeners() {
712
+ if (this.visibilityHandler && typeof document !== 'undefined') {
713
+ document.removeEventListener('visibilitychange', this.visibilityHandler);
714
+ this.visibilityHandler = null;
715
+ }
716
+ if (this.onlineHandler && typeof window !== 'undefined') {
717
+ window.removeEventListener('online', this.onlineHandler);
718
+ this.onlineHandler = null;
719
+ }
683
720
  }
684
721
  /**
685
722
  * Create a shared WebSocket connection for multiplexing
@@ -742,6 +779,7 @@ class HocuspocusSyncProvider {
742
779
  if (this.isSynced || this.isDestroyed) {
743
780
  return;
744
781
  }
782
+ this._connectionStatus = 'connecting';
745
783
  return new Promise((resolve, reject) => {
746
784
  // Store reject function so we can cancel the connection if destroyed
747
785
  this.pendingConnectReject = reject;
@@ -749,7 +787,7 @@ class HocuspocusSyncProvider {
749
787
  this.pendingConnectReject = null;
750
788
  this.connectTimeout = null;
751
789
  reject(new Error('Hocuspocus connection timeout'));
752
- }, 10000); // 10 second timeout
790
+ }, this.connectionTimeoutMs);
753
791
  const syncHandler = () => {
754
792
  if (this.connectTimeout) {
755
793
  clearTimeout(this.connectTimeout);
@@ -779,6 +817,10 @@ class HocuspocusSyncProvider {
779
817
  }
780
818
  });
781
819
  }
820
+ async reconnect() {
821
+ this.disconnect();
822
+ return this.connect();
823
+ }
782
824
  disconnect() {
783
825
  // Cancel any pending connection attempt
784
826
  if (this.connectTimeout) {
@@ -799,6 +841,7 @@ class HocuspocusSyncProvider {
799
841
  }
800
842
  this.isConnected = false;
801
843
  this.isSynced = false;
844
+ this._connectionStatus = 'disconnected';
802
845
  }
803
846
  destroy() {
804
847
  // Mark as destroyed first to prevent any callbacks from doing work
@@ -811,11 +854,13 @@ class HocuspocusSyncProvider {
811
854
  if (this.pendingConnectReject) {
812
855
  this.pendingConnectReject = null; // Don't reject, just abandon the promise
813
856
  }
857
+ this.removeBrowserEventListeners();
814
858
  if (this.provider) {
815
859
  this.provider.destroy();
816
860
  }
817
861
  this.isConnected = false;
818
862
  this.isSynced = false;
863
+ this._connectionStatus = 'disconnected';
819
864
  }
820
865
  }
821
866
 
@@ -1,5 +1,5 @@
1
1
  import { r as registerInstance, h, H as Host, c as createEvent, g as getElement } from './index-MV-81ybv.js';
2
- import { b as KritzelPath, d as KritzelLine, z as KritzelColorHelper, n as KritzelSelectionTool, g as KritzelBrushTool, h as KritzelLineTool, l as KritzelShapeTool, k as KritzelTextTool, B as KritzelDevicesHelper, E as KritzelMouseButton, F as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, i as KritzelEraserTool, s as DEFAULT_LINE_TOOL_CONFIG, r as DEFAULT_TEXT_CONFIG, j as KritzelImageTool, v as KritzelAlignment, I as IndexedDBSyncProvider, G as KritzelSelectionGroup, J as KritzelSelectionBox, L as KritzelIconRegistry, M as KritzelKeyboardHelper, N as KritzelBaseHandler, O as KritzelToolRegistry, P as KritzelBaseObject, o as KritzelWorkspace, e as KritzelGroup, c as KritzelImage, f as KritzelShape, K as KritzelText, x as runMigrations, w as CURRENT_WORKSPACE_SCHEMA_VERSION, y as WORKSPACE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, A as APP_STATE_MIGRATIONS, Q as ObjectHelper, m as KritzelCursorHelper, p as KritzelAnchorManager, q as KritzelThemeManager, R as KritzelClassHelper, T as KritzelEventHelper, U as KritzelBaseTool, W as WORKSPACE_EXPORT_VERSION } from './workspace.migrations-B99F1MdT.js';
2
+ import { b as KritzelPath, d as KritzelLine, z as KritzelColorHelper, n as KritzelSelectionTool, g as KritzelBrushTool, h as KritzelLineTool, l as KritzelShapeTool, k as KritzelTextTool, B as KritzelDevicesHelper, E as KritzelMouseButton, F as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, i as KritzelEraserTool, s as DEFAULT_LINE_TOOL_CONFIG, r as DEFAULT_TEXT_CONFIG, j as KritzelImageTool, v as KritzelAlignment, I as IndexedDBSyncProvider, G as KritzelSelectionGroup, J as KritzelSelectionBox, L as KritzelIconRegistry, M as KritzelKeyboardHelper, N as KritzelBaseHandler, O as KritzelToolRegistry, P as KritzelBaseObject, o as KritzelWorkspace, e as KritzelGroup, c as KritzelImage, f as KritzelShape, K as KritzelText, x as runMigrations, w as CURRENT_WORKSPACE_SCHEMA_VERSION, y as WORKSPACE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, A as APP_STATE_MIGRATIONS, Q as ObjectHelper, m as KritzelCursorHelper, p as KritzelAnchorManager, q as KritzelThemeManager, R as KritzelClassHelper, T as KritzelEventHelper, U as KritzelBaseTool, W as WORKSPACE_EXPORT_VERSION } from './workspace.migrations-D48_Bqvh.js';
3
3
  import * as Y from 'yjs';
4
4
  import 'y-websocket';
5
5
  import 'y-indexeddb';
@@ -154,7 +154,7 @@ const KritzelAvatar = class {
154
154
  };
155
155
  KritzelAvatar.style = kritzelAvatarCss();
156
156
 
157
- const kritzelAwarenessCursorsCss = () => `:host{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9500}.awareness-cursor{position:absolute;top:0;left:0;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform}.awareness-cursor.stale{opacity:0.3}.awareness-cursor.tracking-object{transition-duration:0ms}.cursor-arrow{filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.cursor-label{position:absolute;left:16px;top:16px;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;user-select:none}.edge-indicator{position:absolute;top:-12px;left:-12px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform;pointer-events:auto;user-select:none;cursor:pointer}.edge-indicator.stale{opacity:0.3}.edge-indicator.tracking-object{transition-duration:0ms}.edge-arrow{position:absolute;filter:drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3))}.edge-label{position:absolute;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;opacity:0;transform-origin:center;transition:opacity 150ms ease}.edge-indicator:hover .edge-label{opacity:1}.remote-selection-box{position:absolute;top:0;left:0;border-width:2px;border-style:solid;pointer-events:none;will-change:transform, width, height;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, width var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, height var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out}`;
157
+ const kritzelAwarenessCursorsCss = () => `:host{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9500}.awareness-cursor{position:absolute;top:0;left:0;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform}.awareness-cursor.stale{opacity:0}.awareness-cursor.tracking-object{transition-duration:0ms}.cursor-arrow{filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.cursor-label{position:absolute;left:16px;top:16px;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;user-select:none}.edge-indicator{position:absolute;top:-12px;left:-12px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform;pointer-events:auto;user-select:none;cursor:pointer}.edge-indicator.stale{opacity:0}.edge-indicator.tracking-object{transition-duration:0ms}.edge-arrow{position:absolute;filter:drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3))}.edge-label{position:absolute;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;opacity:0;transform-origin:center;transition:opacity 150ms ease}.edge-indicator:hover .edge-label{opacity:1}.remote-selection-box{position:absolute;top:0;left:0;border-width:2px;border-style:solid;pointer-events:none;will-change:transform, width, height;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, width var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, height var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out}`;
158
158
 
159
159
  const STALE_THRESHOLD_MS = 10_000;
160
160
  const REMOVE_THRESHOLD_MS = 30_000;
@@ -205,6 +205,10 @@ const KritzelAwarenessCursors = class {
205
205
  const cursor = state.cursor;
206
206
  const activeObjectId = state.activeObjectId || null;
207
207
  const selectionBox = state.selectionBox || null;
208
+ const existing = updated.get(clientId);
209
+ const cursorMoved = !existing ||
210
+ !existing.cursor !== !cursor ||
211
+ (cursor && existing.cursor && (cursor.x !== existing.cursor.x || cursor.y !== existing.cursor.y));
208
212
  updated.set(clientId, {
209
213
  clientId,
210
214
  user,
@@ -212,6 +216,7 @@ const KritzelAwarenessCursors = class {
212
216
  activeObjectId,
213
217
  selectionBox,
214
218
  lastUpdated: now,
219
+ lastCursorMove: cursorMoved ? now : (existing?.lastCursorMove ?? now),
215
220
  });
216
221
  });
217
222
  // Remove cursors for disconnected clients
@@ -237,7 +242,7 @@ const KritzelAwarenessCursors = class {
237
242
  }
238
243
  }
239
244
  isStale(cursor) {
240
- return Date.now() - cursor.lastUpdated > STALE_THRESHOLD_MS;
245
+ return Date.now() - cursor.lastCursorMove > STALE_THRESHOLD_MS;
241
246
  }
242
247
  hasActiveDrawingCursors() {
243
248
  for (const cursor of this.remoteCursors.values()) {
@@ -326,7 +331,7 @@ const KritzelAwarenessCursors = class {
326
331
  }
327
332
  render() {
328
333
  const cursors = Array.from(this.remoteCursors.values());
329
- return (h(Host, { key: 'b8676d9a7a3d4a79ea8ad9763355ecf4c2f90e17' }, cursors.map(remoteCursor => {
334
+ return (h(Host, { key: '4dd962322c7e955b9038c55cb10f8ffda1e0b246' }, cursors.map(remoteCursor => {
330
335
  if (!remoteCursor.cursor)
331
336
  return null;
332
337
  // When a remote user is actively drawing, derive cursor position from
@@ -19818,6 +19823,14 @@ class KritzelViewport {
19818
19823
  startX = 0;
19819
19824
  /** Starting Y position for pan/zoom gestures */
19820
19825
  startY = 0;
19826
+ /** Minimum movement distance (in screen pixels) before broadcasting touch cursor position */
19827
+ static TOUCH_CURSOR_BROADCAST_THRESHOLD = 5;
19828
+ /** Screen X position where the current touch interaction started */
19829
+ _touchStartScreenX = 0;
19830
+ /** Screen Y position where the current touch interaction started */
19831
+ _touchStartScreenY = 0;
19832
+ /** Whether the touch movement threshold has been exceeded for cursor broadcasting */
19833
+ _touchCursorBroadcastActive = false;
19821
19834
  /**
19822
19835
  * Creates a new KritzelViewport instance and initializes viewport state.
19823
19836
  * Sets up debounced handlers for viewport updates and scaling end events.
@@ -19945,7 +19958,13 @@ class KritzelViewport {
19945
19958
  }
19946
19959
  if (event.pointerType === 'touch' || event.pointerType === 'pen') {
19947
19960
  const activePointers = Array.from(this._core.store.state.pointers.values());
19961
+ if (activePointers.length === 1) {
19962
+ this._touchStartScreenX = event.clientX;
19963
+ this._touchStartScreenY = event.clientY;
19964
+ this._touchCursorBroadcastActive = false;
19965
+ }
19948
19966
  if (activePointers.length === 2) {
19967
+ this._core.store.state.objects?.clearCursorPosition();
19949
19968
  const currentPath = this._core.store.currentPath;
19950
19969
  if (currentPath) {
19951
19970
  this._core.store.state.objects.remove(obj => obj.id === currentPath.id);
@@ -20006,10 +20025,24 @@ class KritzelViewport {
20006
20025
  const hostRect = this._core.store.state.host.getBoundingClientRect();
20007
20026
  const xRelativeToHost = event.clientX - hostRect.left;
20008
20027
  const yRelativeToHost = event.clientY - hostRect.top;
20009
- this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
20010
- this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
20011
- this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
20012
20028
  const activePointers = Array.from(this._core.store.state.pointers.values());
20029
+ if (this._core.store.state.isScaling || activePointers.length > 1) {
20030
+ this._core.store.state.objects?.clearCursorPosition();
20031
+ }
20032
+ else {
20033
+ this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
20034
+ this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
20035
+ if (!this._touchCursorBroadcastActive) {
20036
+ const dx = event.clientX - this._touchStartScreenX;
20037
+ const dy = event.clientY - this._touchStartScreenY;
20038
+ if (Math.sqrt(dx * dx + dy * dy) >= KritzelViewport.TOUCH_CURSOR_BROADCAST_THRESHOLD) {
20039
+ this._touchCursorBroadcastActive = true;
20040
+ }
20041
+ }
20042
+ if (this._touchCursorBroadcastActive) {
20043
+ this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
20044
+ }
20045
+ }
20013
20046
  if (activePointers.length === 2) {
20014
20047
  const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
20015
20048
  const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
@@ -20061,6 +20094,7 @@ class KritzelViewport {
20061
20094
  }
20062
20095
  }
20063
20096
  if (event.pointerType === 'touch' || event.pointerType === 'pen') {
20097
+ this._touchCursorBroadcastActive = false;
20064
20098
  if (this._core.store.state.pointers.size === 0) {
20065
20099
  this._debounceEndScaling();
20066
20100
  }
@@ -21340,6 +21374,42 @@ class KritzelObjectMap {
21340
21374
  }
21341
21375
  this._awareness.setLocalStateField('selectionBox', null);
21342
21376
  }
21377
+ /**
21378
+ * Removes selection groups whose owner is no longer present in awareness.
21379
+ * Called when remote clients disconnect to prevent orphaned selection groups
21380
+ * from persisting in the workspace state.
21381
+ */
21382
+ removeOrphanedSelectionGroups() {
21383
+ if (!this._awareness) {
21384
+ return;
21385
+ }
21386
+ const states = this._awareness.getStates();
21387
+ const activeUserIds = new Set();
21388
+ states.forEach(state => {
21389
+ const userId = state.user?.id;
21390
+ if (userId) {
21391
+ activeUserIds.add(userId);
21392
+ }
21393
+ });
21394
+ const localUserId = this._core?.user?.id;
21395
+ const orphanedGroups = this.quadtree.filter(o => o instanceof KritzelSelectionGroup
21396
+ && o.userId != null
21397
+ && o.userId !== localUserId
21398
+ && !activeUserIds.has(o.userId));
21399
+ for (const group of orphanedGroups) {
21400
+ this.quadtree.remove(o => o.id === group.id);
21401
+ this._idMap.delete(group.id);
21402
+ if (this._objectsMap) {
21403
+ this._ydoc.transact(() => {
21404
+ this._objectsMap.delete(group.id);
21405
+ }, 'local');
21406
+ }
21407
+ }
21408
+ if (orphanedGroups.length > 0) {
21409
+ this._core?.store.invalidateSelectionCache();
21410
+ this._core?.rerender();
21411
+ }
21412
+ }
21343
21413
  /**
21344
21414
  * Registers a callback to be invoked when the awareness state changes.
21345
21415
  * The callback receives the full awareness states map.
@@ -21473,16 +21543,28 @@ class KritzelObjectMap {
21473
21543
  this.handleObjectsChange(event);
21474
21544
  };
21475
21545
  this._objectsMap.observe(this._objectsObserver);
21476
- // Connect all providers in parallel (settle individually so one failure doesn't block the rest)
21477
- const results = await Promise.allSettled(this._providers.map(p => p.connect()));
21478
- results.forEach((result, i) => {
21546
+ // Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
21547
+ // Local providers are awaited so data is available immediately; network providers sync in the background
21548
+ // via Yjs observers that are already registered above.
21549
+ const localProviders = this._providers.filter(p => p.type === 'local');
21550
+ const networkProviders = this._providers.filter(p => p.type === 'network');
21551
+ // Await local providers for immediate data availability
21552
+ const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
21553
+ localResults.forEach((result, i) => {
21479
21554
  if (result.status === 'rejected') {
21480
- console.error(`[Kritzel] Sync provider "${this._providers[i]?.constructor.name}" failed to connect:`, result.reason);
21555
+ console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
21481
21556
  }
21482
21557
  });
21558
+ // Connect network providers in the background (remote data arrives via Yjs observers)
21559
+ for (const provider of networkProviders) {
21560
+ provider.connect().catch(err => {
21561
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
21562
+ });
21563
+ }
21483
21564
  this._isReady = true;
21484
21565
  // Find the first provider that exposes awareness (network providers)
21485
- for (const provider of this._providers) {
21566
+ // Awareness is available immediately after provider construction, before connect() resolves
21567
+ for (const provider of networkProviders) {
21486
21568
  if (provider.awareness) {
21487
21569
  this._awareness = provider.awareness;
21488
21570
  break;
@@ -21490,7 +21572,11 @@ class KritzelObjectMap {
21490
21572
  }
21491
21573
  // Subscribe to awareness changes
21492
21574
  if (this._awareness) {
21493
- this._awarenessChangeHandler = () => {
21575
+ this._awarenessChangeHandler = (change) => {
21576
+ // Clean up selection groups belonging to disconnected users
21577
+ if (change.removed.length > 0) {
21578
+ this.removeOrphanedSelectionGroups();
21579
+ }
21494
21580
  const now = Date.now();
21495
21581
  const timeSinceLastEmit = now - this._lastAwarenessEmitTime;
21496
21582
  // Clear any pending timeout since we have a new event
@@ -21753,11 +21839,27 @@ class KritzelObjectMap {
21753
21839
  }
21754
21840
  this.quadtree.reset();
21755
21841
  this._idMap.clear();
21756
- this._objectsMap.forEach(serialized => {
21842
+ const localUserId = this._core?.user?.id;
21843
+ const staleSelectionGroupIds = [];
21844
+ this._objectsMap.forEach((serialized, key) => {
21757
21845
  const object = this._reviver.revive(serialized);
21846
+ // Remove remote selection groups on startup — they are transient UI state
21847
+ // that should not survive an app restart. The owning user's session is gone.
21848
+ if (object instanceof KritzelSelectionGroup && object.userId != null && object.userId !== localUserId) {
21849
+ staleSelectionGroupIds.push(key);
21850
+ return;
21851
+ }
21758
21852
  this.quadtree.insert(object);
21759
21853
  this._idMap.set(object.id, object);
21760
21854
  });
21855
+ // Clean up stale remote selection groups from Yjs
21856
+ if (staleSelectionGroupIds.length > 0) {
21857
+ this._ydoc.transact(() => {
21858
+ for (const id of staleSelectionGroupIds) {
21859
+ this._objectsMap.delete(id);
21860
+ }
21861
+ }, 'local');
21862
+ }
21761
21863
  }
21762
21864
  /**
21763
21865
  * Resets the object map by clearing both the local quadtree and the Yjs objects map.
@@ -22429,13 +22531,24 @@ class KritzelAppStateMap {
22429
22531
  this.handleWorkspacesChange(event);
22430
22532
  };
22431
22533
  this._workspacesMap.observe(this._workspacesObserver);
22432
- // Connect all providers in parallel (settle individually so one failure doesn't block the rest)
22433
- const results = await Promise.allSettled(this._providers.map(p => p.connect()));
22434
- results.forEach((result, i) => {
22534
+ // Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
22535
+ // Local providers are awaited so data is available immediately; network providers sync in the background
22536
+ // via Yjs observers that are already registered above.
22537
+ const localProviders = this._providers.filter(p => p.type === 'local');
22538
+ const networkProviders = this._providers.filter(p => p.type === 'network');
22539
+ // Await local providers for immediate data availability
22540
+ const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
22541
+ localResults.forEach((result, i) => {
22435
22542
  if (result.status === 'rejected') {
22436
- console.error(`[Kritzel] Sync provider "${this._providers[i]?.constructor.name}" failed to connect:`, result.reason);
22543
+ console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
22437
22544
  }
22438
22545
  });
22546
+ // Connect network providers in the background (remote data arrives via Yjs observers)
22547
+ for (const provider of networkProviders) {
22548
+ provider.connect().catch(err => {
22549
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
22550
+ });
22551
+ }
22439
22552
  this._isReady = true;
22440
22553
  // Run any pending schema migrations before loading data
22441
22554
  const quietMigrations = !core.store.state.debugInfo.showMigrationInfo;
@@ -28386,7 +28499,7 @@ const KritzelPortal = class {
28386
28499
  * This file is auto-generated by the version bump scripts.
28387
28500
  * Do not modify manually.
28388
28501
  */
28389
- const KRITZEL_VERSION = '0.1.74';
28502
+ const KRITZEL_VERSION = '0.1.76';
28390
28503
 
28391
28504
  const kritzelSettingsCss = () => `:host{display:contents}kritzel-dialog{--kritzel-dialog-body-padding:0;--kritzel-dialog-width-large:800px;--kritzel-dialog-height-large:500px}.footer-button{padding:8px 16px;border-radius:6px;cursor:pointer;font-size:14px}.cancel-button{border:1px solid #ebebeb;background:#fff;color:inherit}.cancel-button:hover{background:#f5f5f5}.settings-content{padding:0}.settings-content h3{margin:0 0 16px 0;font-size:18px;font-weight:600;color:var(--kritzel-settings-content-heading-color, #333333)}.settings-content p{margin:0;font-size:14px;color:var(--kritzel-settings-content-text-color, #666666);line-height:1.5}.settings-group{display:flex;flex-direction:column;gap:24px}.settings-item{display:flex;flex-direction:column;gap:8px}.settings-row{display:flex;align-items:center;justify-content:space-between;gap:16px}.settings-label{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.settings-description{font-size:12px;color:var(--kritzel-settings-description-color, #888888);margin:0;line-height:1.4}.shortcuts-list{display:flex;flex-direction:column;gap:24px}.shortcuts-category{display:flex;flex-direction:column;gap:8px}.shortcuts-category-title{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.shortcuts-group{display:flex;flex-direction:column;gap:4px}.shortcut-item{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-item-bg, rgba(0, 0, 0, 0.02))}.shortcut-label{font-size:14px;color:var(--kritzel-settings-content-text-color, #666666)}.shortcut-key{font-family:monospace;font-size:12px;padding:2px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-key-bg, #f0f0f0);color:var(--kritzel-settings-shortcut-key-color, #333333);border:1px solid var(--kritzel-settings-shortcut-key-border, #ddd)}`;
28392
28505