veryfront 0.1.30 → 0.1.31

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.
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-template.d.ts","sourceRoot":"","sources":["../../../src/src/studio/bridge-template.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAigI/E"}
1
+ {"version":3,"file":"bridge-template.d.ts","sourceRoot":"","sources":["../../../src/src/studio/bridge-template.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAyqI/E"}
@@ -70,6 +70,12 @@ export function generateStudioBridgeScript(options) {
70
70
  let markdownLatestSelections = [];
71
71
  let markdownHasUnsavedChanges = false;
72
72
  let markdownSaveInProgress = false;
73
+ let markdownYDoc = null;
74
+ let markdownYProvider = null;
75
+ let markdownYText = null;
76
+ let markdownYjsConnected = false;
77
+ let markdownYjsSetupId = 0;
78
+ const LEXICAL_YJS_ORIGIN = 'lexical-yjs-binding';
73
79
 
74
80
  const MARKDOWN_SLASH_COMMANDS = [
75
81
  {
@@ -1410,6 +1416,142 @@ export function generateStudioBridgeScript(options) {
1410
1416
  }, 120);
1411
1417
  }
1412
1418
 
1419
+ function computeTextDiff(oldText, newText) {
1420
+ var prefixLen = 0;
1421
+ var minLen = Math.min(oldText.length, newText.length);
1422
+ while (prefixLen < minLen && oldText.charCodeAt(prefixLen) === newText.charCodeAt(prefixLen)) {
1423
+ prefixLen++;
1424
+ }
1425
+ var suffixLen = 0;
1426
+ var maxSuffix = minLen - prefixLen;
1427
+ while (suffixLen < maxSuffix &&
1428
+ oldText.charCodeAt(oldText.length - 1 - suffixLen) === newText.charCodeAt(newText.length - 1 - suffixLen)) {
1429
+ suffixLen++;
1430
+ }
1431
+ return {
1432
+ index: prefixLen,
1433
+ deleteCount: oldText.length - prefixLen - suffixLen,
1434
+ insertText: newText.slice(prefixLen, suffixLen > 0 ? newText.length - suffixLen : undefined)
1435
+ };
1436
+ }
1437
+
1438
+ function syncLocalChangeToYText(fullContent) {
1439
+ if (!markdownYText || !markdownYDoc) {
1440
+ return;
1441
+ }
1442
+ var currentYContent = markdownYText.toString();
1443
+ if (currentYContent === fullContent) {
1444
+ return;
1445
+ }
1446
+ var diff = computeTextDiff(currentYContent, fullContent);
1447
+ if (diff.deleteCount === 0 && diff.insertText === '') {
1448
+ return;
1449
+ }
1450
+ markdownYDoc.transact(function() {
1451
+ if (diff.deleteCount > 0) {
1452
+ markdownYText.delete(diff.index, diff.deleteCount);
1453
+ }
1454
+ if (diff.insertText) {
1455
+ markdownYText.insert(diff.index, diff.insertText);
1456
+ }
1457
+ }, LEXICAL_YJS_ORIGIN);
1458
+ }
1459
+
1460
+ function setupMarkdownYjsConnection(config) {
1461
+ if (markdownYDoc) {
1462
+ return;
1463
+ }
1464
+
1465
+ var setupId = ++markdownYjsSetupId;
1466
+
1467
+ Promise.all([
1468
+ import('https://esm.sh/yjs@13.6.28?target=es2022'),
1469
+ import('https://esm.sh/y-websocket@2.1.0?target=es2022')
1470
+ ]).then(function(modules) {
1471
+ // Abort if edit mode was closed while imports were loading
1472
+ if (setupId !== markdownYjsSetupId) {
1473
+ return;
1474
+ }
1475
+
1476
+ var Y = modules[0];
1477
+ var WebsocketProvider = modules[1].WebsocketProvider;
1478
+
1479
+ var doc = new Y.Doc({ guid: config.guid });
1480
+ var provider = new WebsocketProvider(config.wsUrl, config.guid, doc, {
1481
+ resyncInterval: -1,
1482
+ params: { token: config.authToken }
1483
+ });
1484
+
1485
+ var ytext = doc.getText(config.fileId);
1486
+
1487
+ markdownYDoc = doc;
1488
+ markdownYProvider = provider;
1489
+ markdownYText = ytext;
1490
+
1491
+ // Filter non-binary messages to prevent y-websocket parse errors
1492
+ provider.on('status', function(event) {
1493
+ console.debug('[StudioBridge] Yjs status:', event.status);
1494
+ if (event.status === 'connected' && provider.ws) {
1495
+ var origOnMessage = provider.ws.onmessage;
1496
+ provider.ws.onmessage = function(wsEvent) {
1497
+ if (typeof wsEvent.data === 'string') {
1498
+ return;
1499
+ }
1500
+ if (origOnMessage) {
1501
+ origOnMessage.call(provider.ws, wsEvent);
1502
+ }
1503
+ };
1504
+ }
1505
+ });
1506
+
1507
+ provider.on('sync', function(synced) {
1508
+ if (synced && !markdownYjsConnected) {
1509
+ markdownYjsConnected = true;
1510
+
1511
+ var ytextContent = ytext.toString();
1512
+ if (markdownCurrentContent && markdownCurrentContent !== ytextContent) {
1513
+ // User typed before sync completed — push local edits to Y.Text
1514
+ syncLocalChangeToYText(markdownCurrentContent);
1515
+ } else if (ytextContent) {
1516
+ // No local edits — seed editor from Y.Text
1517
+ applyMarkdownContent(ytextContent);
1518
+ }
1519
+
1520
+ // Observe Y.Text for remote changes (from other users / Monaco)
1521
+ ytext.observe(function(event) {
1522
+ if (event.transaction.origin === LEXICAL_YJS_ORIGIN) {
1523
+ return;
1524
+ }
1525
+ var fullContent = ytext.toString();
1526
+ if (fullContent === markdownCurrentContent) {
1527
+ return;
1528
+ }
1529
+ applyMarkdownContent(fullContent);
1530
+ });
1531
+
1532
+ console.debug('[StudioBridge] Yjs synced, bound to Y.Text for fileId:', config.fileId);
1533
+ }
1534
+ });
1535
+ }).catch(function(error) {
1536
+ console.error('[StudioBridge] Failed to setup Yjs connection:', error);
1537
+ });
1538
+ }
1539
+
1540
+ function disposeMarkdownYjs() {
1541
+ markdownYjsSetupId++;
1542
+ if (markdownYProvider) {
1543
+ markdownYProvider.disconnect();
1544
+ markdownYProvider.destroy();
1545
+ markdownYProvider = null;
1546
+ }
1547
+ if (markdownYDoc) {
1548
+ markdownYDoc.destroy();
1549
+ markdownYDoc = null;
1550
+ }
1551
+ markdownYText = null;
1552
+ markdownYjsConnected = false;
1553
+ }
1554
+
1413
1555
  function getTextOffsetWithinRoot(root, targetNode, targetOffset) {
1414
1556
  if (!root || !targetNode) {
1415
1557
  return 0;
@@ -2909,7 +3051,11 @@ export function generateStudioBridgeScript(options) {
2909
3051
  }
2910
3052
  markdownCurrentContent = fullContent;
2911
3053
  markdownHasUnsavedChanges = true;
2912
- scheduleMarkdownSync(fullContent);
3054
+ if (markdownYjsConnected) {
3055
+ syncLocalChangeToYText(fullContent);
3056
+ } else {
3057
+ scheduleMarkdownSync(fullContent);
3058
+ }
2913
3059
  scheduleMarkdownSelectionOverlayRender();
2914
3060
  }
2915
3061
 
@@ -3711,6 +3857,7 @@ export function generateStudioBridgeScript(options) {
3711
3857
  markdownOverlaySelections = [];
3712
3858
  clearMarkdownSelectionOverlay();
3713
3859
  clearMarkdownSelectionSync();
3860
+ disposeMarkdownYjs();
3714
3861
  }
3715
3862
 
3716
3863
  const nextUrl = new URL(window.location.href);
@@ -3941,9 +4088,30 @@ export function generateStudioBridgeScript(options) {
3941
4088
  if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
3942
4089
  return;
3943
4090
  }
4091
+ if (markdownYjsConnected) {
4092
+ return;
4093
+ }
3944
4094
  applyMarkdownContent(message.content || '');
3945
4095
  return;
3946
4096
 
4097
+ case 'initYjsConnection':
4098
+ if (!isMarkdownPage()) {
4099
+ return;
4100
+ }
4101
+ if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
4102
+ return;
4103
+ }
4104
+ if (message.initialContent) {
4105
+ applyMarkdownContent(message.initialContent);
4106
+ }
4107
+ setupMarkdownYjsConnection({
4108
+ wsUrl: message.wsUrl,
4109
+ guid: message.guid,
4110
+ fileId: message.fileId || markdownFileId,
4111
+ authToken: message.authToken
4112
+ });
4113
+ return;
4114
+
3947
4115
  case 'setMarkdownPersistState':
3948
4116
  if (!isMarkdownPage()) {
3949
4117
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -78,6 +78,12 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
78
78
  let markdownLatestSelections = [];
79
79
  let markdownHasUnsavedChanges = false;
80
80
  let markdownSaveInProgress = false;
81
+ let markdownYDoc = null;
82
+ let markdownYProvider = null;
83
+ let markdownYText = null;
84
+ let markdownYjsConnected = false;
85
+ let markdownYjsSetupId = 0;
86
+ const LEXICAL_YJS_ORIGIN = 'lexical-yjs-binding';
81
87
 
82
88
  const MARKDOWN_SLASH_COMMANDS = [
83
89
  {
@@ -1418,6 +1424,142 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
1418
1424
  }, 120);
1419
1425
  }
1420
1426
 
1427
+ function computeTextDiff(oldText, newText) {
1428
+ var prefixLen = 0;
1429
+ var minLen = Math.min(oldText.length, newText.length);
1430
+ while (prefixLen < minLen && oldText.charCodeAt(prefixLen) === newText.charCodeAt(prefixLen)) {
1431
+ prefixLen++;
1432
+ }
1433
+ var suffixLen = 0;
1434
+ var maxSuffix = minLen - prefixLen;
1435
+ while (suffixLen < maxSuffix &&
1436
+ oldText.charCodeAt(oldText.length - 1 - suffixLen) === newText.charCodeAt(newText.length - 1 - suffixLen)) {
1437
+ suffixLen++;
1438
+ }
1439
+ return {
1440
+ index: prefixLen,
1441
+ deleteCount: oldText.length - prefixLen - suffixLen,
1442
+ insertText: newText.slice(prefixLen, suffixLen > 0 ? newText.length - suffixLen : undefined)
1443
+ };
1444
+ }
1445
+
1446
+ function syncLocalChangeToYText(fullContent) {
1447
+ if (!markdownYText || !markdownYDoc) {
1448
+ return;
1449
+ }
1450
+ var currentYContent = markdownYText.toString();
1451
+ if (currentYContent === fullContent) {
1452
+ return;
1453
+ }
1454
+ var diff = computeTextDiff(currentYContent, fullContent);
1455
+ if (diff.deleteCount === 0 && diff.insertText === '') {
1456
+ return;
1457
+ }
1458
+ markdownYDoc.transact(function() {
1459
+ if (diff.deleteCount > 0) {
1460
+ markdownYText.delete(diff.index, diff.deleteCount);
1461
+ }
1462
+ if (diff.insertText) {
1463
+ markdownYText.insert(diff.index, diff.insertText);
1464
+ }
1465
+ }, LEXICAL_YJS_ORIGIN);
1466
+ }
1467
+
1468
+ function setupMarkdownYjsConnection(config) {
1469
+ if (markdownYDoc) {
1470
+ return;
1471
+ }
1472
+
1473
+ var setupId = ++markdownYjsSetupId;
1474
+
1475
+ Promise.all([
1476
+ import('https://esm.sh/yjs@13.6.28?target=es2022'),
1477
+ import('https://esm.sh/y-websocket@2.1.0?target=es2022')
1478
+ ]).then(function(modules) {
1479
+ // Abort if edit mode was closed while imports were loading
1480
+ if (setupId !== markdownYjsSetupId) {
1481
+ return;
1482
+ }
1483
+
1484
+ var Y = modules[0];
1485
+ var WebsocketProvider = modules[1].WebsocketProvider;
1486
+
1487
+ var doc = new Y.Doc({ guid: config.guid });
1488
+ var provider = new WebsocketProvider(config.wsUrl, config.guid, doc, {
1489
+ resyncInterval: -1,
1490
+ params: { token: config.authToken }
1491
+ });
1492
+
1493
+ var ytext = doc.getText(config.fileId);
1494
+
1495
+ markdownYDoc = doc;
1496
+ markdownYProvider = provider;
1497
+ markdownYText = ytext;
1498
+
1499
+ // Filter non-binary messages to prevent y-websocket parse errors
1500
+ provider.on('status', function(event) {
1501
+ console.debug('[StudioBridge] Yjs status:', event.status);
1502
+ if (event.status === 'connected' && provider.ws) {
1503
+ var origOnMessage = provider.ws.onmessage;
1504
+ provider.ws.onmessage = function(wsEvent) {
1505
+ if (typeof wsEvent.data === 'string') {
1506
+ return;
1507
+ }
1508
+ if (origOnMessage) {
1509
+ origOnMessage.call(provider.ws, wsEvent);
1510
+ }
1511
+ };
1512
+ }
1513
+ });
1514
+
1515
+ provider.on('sync', function(synced) {
1516
+ if (synced && !markdownYjsConnected) {
1517
+ markdownYjsConnected = true;
1518
+
1519
+ var ytextContent = ytext.toString();
1520
+ if (markdownCurrentContent && markdownCurrentContent !== ytextContent) {
1521
+ // User typed before sync completed — push local edits to Y.Text
1522
+ syncLocalChangeToYText(markdownCurrentContent);
1523
+ } else if (ytextContent) {
1524
+ // No local edits — seed editor from Y.Text
1525
+ applyMarkdownContent(ytextContent);
1526
+ }
1527
+
1528
+ // Observe Y.Text for remote changes (from other users / Monaco)
1529
+ ytext.observe(function(event) {
1530
+ if (event.transaction.origin === LEXICAL_YJS_ORIGIN) {
1531
+ return;
1532
+ }
1533
+ var fullContent = ytext.toString();
1534
+ if (fullContent === markdownCurrentContent) {
1535
+ return;
1536
+ }
1537
+ applyMarkdownContent(fullContent);
1538
+ });
1539
+
1540
+ console.debug('[StudioBridge] Yjs synced, bound to Y.Text for fileId:', config.fileId);
1541
+ }
1542
+ });
1543
+ }).catch(function(error) {
1544
+ console.error('[StudioBridge] Failed to setup Yjs connection:', error);
1545
+ });
1546
+ }
1547
+
1548
+ function disposeMarkdownYjs() {
1549
+ markdownYjsSetupId++;
1550
+ if (markdownYProvider) {
1551
+ markdownYProvider.disconnect();
1552
+ markdownYProvider.destroy();
1553
+ markdownYProvider = null;
1554
+ }
1555
+ if (markdownYDoc) {
1556
+ markdownYDoc.destroy();
1557
+ markdownYDoc = null;
1558
+ }
1559
+ markdownYText = null;
1560
+ markdownYjsConnected = false;
1561
+ }
1562
+
1421
1563
  function getTextOffsetWithinRoot(root, targetNode, targetOffset) {
1422
1564
  if (!root || !targetNode) {
1423
1565
  return 0;
@@ -2917,7 +3059,11 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
2917
3059
  }
2918
3060
  markdownCurrentContent = fullContent;
2919
3061
  markdownHasUnsavedChanges = true;
2920
- scheduleMarkdownSync(fullContent);
3062
+ if (markdownYjsConnected) {
3063
+ syncLocalChangeToYText(fullContent);
3064
+ } else {
3065
+ scheduleMarkdownSync(fullContent);
3066
+ }
2921
3067
  scheduleMarkdownSelectionOverlayRender();
2922
3068
  }
2923
3069
 
@@ -3719,6 +3865,7 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
3719
3865
  markdownOverlaySelections = [];
3720
3866
  clearMarkdownSelectionOverlay();
3721
3867
  clearMarkdownSelectionSync();
3868
+ disposeMarkdownYjs();
3722
3869
  }
3723
3870
 
3724
3871
  const nextUrl = new URL(window.location.href);
@@ -3949,9 +4096,30 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
3949
4096
  if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
3950
4097
  return;
3951
4098
  }
4099
+ if (markdownYjsConnected) {
4100
+ return;
4101
+ }
3952
4102
  applyMarkdownContent(message.content || '');
3953
4103
  return;
3954
4104
 
4105
+ case 'initYjsConnection':
4106
+ if (!isMarkdownPage()) {
4107
+ return;
4108
+ }
4109
+ if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
4110
+ return;
4111
+ }
4112
+ if (message.initialContent) {
4113
+ applyMarkdownContent(message.initialContent);
4114
+ }
4115
+ setupMarkdownYjsConnection({
4116
+ wsUrl: message.wsUrl,
4117
+ guid: message.guid,
4118
+ fileId: message.fileId || markdownFileId,
4119
+ authToken: message.authToken
4120
+ });
4121
+ return;
4122
+
3955
4123
  case 'setMarkdownPersistState':
3956
4124
  if (!isMarkdownPage()) {
3957
4125
  return;