From e5ff61219729d906ab94a2ca3b5cc8bd186227a3 Mon Sep 17 00:00:00 2001 From: enzotar Date: Wed, 25 Feb 2026 21:58:14 -0800 Subject: [PATCH] feat: per-signal version counters for conflict resolution - _signalVersions: monotonic counter per signal, incremented on each local mutation - Diffs include _v: {name: version} for version comparison - _applyRemoteDiff only applies if remote version >= local version - Prevents stale overwrites when both devices edit simultaneously - onopen snapshot includes version map for late-joiner consistency --- compiler/ds-codegen/src/js_emitter.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/compiler/ds-codegen/src/js_emitter.rs b/compiler/ds-codegen/src/js_emitter.rs index edd06a9..db38a59 100644 --- a/compiler/ds-codegen/src/js_emitter.rs +++ b/compiler/ds-codegen/src/js_emitter.rs @@ -1818,11 +1818,13 @@ const DS = (() => { // ── Signal registry for bidirectional sync ── var _signalRegistry = {}; + var _signalVersions = {}; // per-signal version counters for conflict resolution var _applyingRemoteDiff = false; var _peerId = Math.random().toString(36).substr(2, 8); // unique per client function _registerSignal(name, sig) { _signalRegistry[name] = sig; + _signalVersions[name] = 0; } function _applyRemoteDiff(json) { @@ -1830,12 +1832,18 @@ const DS = (() => { var data = JSON.parse(json); // Ignore our own diffs echoed back by the relay if (data._pid === _peerId) return; + var versions = data._v || {}; _applyingRemoteDiff = true; for (var name in data) { - if (name === '_pid') continue; + if (name === '_pid' || name === '_v') continue; var sig = _signalRegistry[name]; - if (sig) { + if (!sig) continue; + var remoteV = versions[name] || 0; + var localV = _signalVersions[name] || 0; + // Only apply if remote version >= local version (last-write-wins) + if (remoteV >= localV) { sig.value = data[name]; + _signalVersions[name] = remoteV; } } flush(); @@ -1869,11 +1877,12 @@ const DS = (() => { _streamWs.onclose = function() { setTimeout(function() { _initStream(url, mode); }, 2000); }; _streamWs.onopen = function() { console.log('[ds-stream] Peer connected:', peerUrl); - // Broadcast full state snapshot so other peers can sync - var fullState = { _pid: _peerId }; + // Broadcast full state snapshot with versions so other peers can sync + var fullState = { _pid: _peerId, _v: {} }; for (var name in _signalRegistry) { var sig = _signalRegistry[name]; fullState[name] = (typeof sig === 'object' && sig !== null && '_value' in sig) ? sig._value : sig; + fullState._v[name] = _signalVersions[name] || 0; } _streamSend(0x31, 0, new TextEncoder().encode(JSON.stringify(fullState))); }; @@ -1896,7 +1905,10 @@ const DS = (() => { function _streamDiff(name, value) { if (!_streamWs || _streamWs.readyState !== 1 || _streamMode !== 'signal') return; if (_applyingRemoteDiff) return; // prevent echo loops - var obj = { _pid: _peerId }; + // Increment version for conflict resolution + _signalVersions[name] = (_signalVersions[name] || 0) + 1; + var obj = { _pid: _peerId, _v: {} }; + obj._v[name] = _signalVersions[name]; obj[name] = (typeof value === 'object' && value !== null && 'value' in value) ? value.value : value; _streamSend(0x31, 0, new TextEncoder().encode(JSON.stringify(obj))); }