@mindexec/cli 0.2.483 → 0.2.494

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 (70) hide show
  1. package/codex-runtime.js +6 -2
  2. package/electron/main.cjs +56 -32
  3. package/electron/preload.cjs +178 -0
  4. package/electron/source-smoke.mjs +14 -1
  5. package/electron/windows-package-smoke.mjs +16 -0
  6. package/package.json +11 -10
  7. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  8. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  9. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  10. package/wwwroot/_content/MindExecution.Shared/css/app.css +1 -1
  11. package/wwwroot/_content/MindExecution.Shared/js/canvas-chat-task-switch.js +122 -0
  12. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +161 -16
  13. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +68 -2
  14. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js +44 -8
  15. package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +823 -111
  16. package/wwwroot/_content/MindExecution.Shared/js/mind-map-native-core-bridge.js +399 -0
  17. package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +53 -10
  18. package/wwwroot/_content/MindExecution.Shared/js/mind-map-visibility-worker.js +71 -11
  19. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-core-simd.js +18 -0
  20. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-core-simd.wasm +0 -0
  21. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-core.js +18 -0
  22. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-core.wasm +0 -0
  23. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-native-core-adapter.js +566 -0
  24. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-native-core-worker.js +89 -0
  25. package/wwwroot/_content/MindExecution.Shared/native-core/native-core-manifest.json +59 -0
  26. package/wwwroot/_framework/BenchmarkUtils.ytz7pn9azy.dll +0 -0
  27. package/wwwroot/_framework/Edgar.9yqq548b45.dll +0 -0
  28. package/wwwroot/_framework/GraphPlanarityTesting.lgk3vd0bh9.dll +0 -0
  29. package/wwwroot/_framework/Microsoft.Win32.SystemEvents.f8qhom7uq5.dll +0 -0
  30. package/wwwroot/_framework/{MindExecution.Core.zs4n1f9hjc.dll → MindExecution.Core.tre9kny2uo.dll} +0 -0
  31. package/wwwroot/_framework/{MindExecution.Kernel.l9idvg7mp7.dll → MindExecution.Kernel.lvfnhwdksf.dll} +0 -0
  32. package/wwwroot/_framework/{MindExecution.Plugins.Admin.l37qx5d8zi.dll → MindExecution.Plugins.Admin.k2vbe4lt64.dll} +0 -0
  33. package/wwwroot/_framework/{MindExecution.Plugins.Business.d7b282yazn.dll → MindExecution.Plugins.Business.u7i3r45xp5.dll} +0 -0
  34. package/wwwroot/_framework/{MindExecution.Plugins.Concept.iw98n0qz5k.dll → MindExecution.Plugins.Concept.7eoasvev17.dll} +0 -0
  35. package/wwwroot/_framework/{MindExecution.Plugins.Directory.cqgn6pa8di.dll → MindExecution.Plugins.Directory.rhh13u0z6c.dll} +0 -0
  36. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.j8xoa8nl57.dll → MindExecution.Plugins.PlanMaster.xzl2x2cqk4.dll} +0 -0
  37. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.zvpzk7om4z.dll → MindExecution.Plugins.YouTube.945tf0zc9m.dll} +0 -0
  38. package/wwwroot/_framework/{MindExecution.Shared.ovot7q606x.dll → MindExecution.Shared.e0w408b0ti.dll} +0 -0
  39. package/wwwroot/_framework/{MindExecution.Web.bzuaz35jv0.dll → MindExecution.Web.vc6ux7xjcw.dll} +0 -0
  40. package/wwwroot/_framework/RangeTree.iw8h1x5u2y.dll +0 -0
  41. package/wwwroot/_framework/System.Collections.Concurrent.jzwteolu29.dll +0 -0
  42. package/wwwroot/_framework/System.Collections.NonGeneric.uqtvwssxuq.dll +0 -0
  43. package/wwwroot/_framework/System.Collections.Specialized.85qtrfslgb.dll +0 -0
  44. package/wwwroot/_framework/System.Collections.xmhu72xgrp.dll +0 -0
  45. package/wwwroot/_framework/System.ComponentModel.Primitives.excbd02v5j.dll +0 -0
  46. package/wwwroot/_framework/System.ComponentModel.TypeConverter.ek2jcwibb9.dll +0 -0
  47. package/wwwroot/_framework/System.ComponentModel.wfe9zz2icv.dll +0 -0
  48. package/wwwroot/_framework/System.Console.5j283so6ps.dll +0 -0
  49. package/wwwroot/_framework/System.Drawing.Common.xzizafq144.dll +0 -0
  50. package/wwwroot/_framework/System.Private.CoreLib.7ouudb2fbl.dll +0 -0
  51. package/wwwroot/_framework/{System.Text.RegularExpressions.cvkox11l6l.dll → System.Text.RegularExpressions.b0vqxbob08.dll} +0 -0
  52. package/wwwroot/_framework/System.Threading.Tasks.Parallel.9tqazg1b0u.dll +0 -0
  53. package/wwwroot/_framework/System.Threading.Tasks.zl4yo3otxa.dll +0 -0
  54. package/wwwroot/_framework/YamlDotNet.dtey92juka.dll +0 -0
  55. package/wwwroot/_framework/blazor.boot.json +63 -45
  56. package/wwwroot/_framework/{dotnet.native.60hhmnxyoo.wasm → dotnet.native.58bvs6q392.wasm} +0 -0
  57. package/wwwroot/_framework/netstandard.he3m3p1s9q.dll +0 -0
  58. package/wwwroot/index.html +2 -1
  59. package/wwwroot/service-worker-assets.js +128 -56
  60. package/wwwroot/service-worker.js +1 -1
  61. package/wwwroot/_framework/System.Collections.3iuoxbe8my.dll +0 -0
  62. package/wwwroot/_framework/System.Collections.Concurrent.vow3rm1dku.dll +0 -0
  63. package/wwwroot/_framework/System.Collections.NonGeneric.npjkaz40oc.dll +0 -0
  64. package/wwwroot/_framework/System.Collections.Specialized.ugkjbs6p02.dll +0 -0
  65. package/wwwroot/_framework/System.ComponentModel.Primitives.end4v0xe2c.dll +0 -0
  66. package/wwwroot/_framework/System.ComponentModel.TypeConverter.tgtp5sm4iv.dll +0 -0
  67. package/wwwroot/_framework/System.ComponentModel.wupoltkk1t.dll +0 -0
  68. package/wwwroot/_framework/System.Console.9dik0wogo2.dll +0 -0
  69. package/wwwroot/_framework/System.Private.CoreLib.7s8wte759y.dll +0 -0
  70. package/wwwroot/_framework/netstandard.b50t77veor.dll +0 -0
@@ -0,0 +1,399 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const BUILD_ID = '20260830-native-core-primary-v006';
5
+ const STORAGE_KEY = 'mindmap.nativeCore.mode';
6
+ const NATIVE_WORKER_URL = `_content/MindExecution.Shared/native-core/mindexec-native-core-worker.js?v=${BUILD_ID}`;
7
+ const VALID_MODES = new Set(['off', 'compare', 'native']);
8
+ const RESULT_TYPES = new Set(['VISIBILITY_RESULT', 'HIT_TEST_RESULT', 'SELECTION_QUERY_RESULT']);
9
+ const MIRRORED_TYPES = new Set([
10
+ 'RESET_BOARD',
11
+ 'REBUILD_BOARD',
12
+ 'UPSERT_NODE',
13
+ 'REMOVE_NODE',
14
+ 'VISIBILITY_QUERY',
15
+ 'HIT_TEST_QUERY',
16
+ 'SELECTION_QUERY'
17
+ ]);
18
+
19
+ function resolveMode() {
20
+ try {
21
+ const queryMode = new URLSearchParams(window.location?.search || '').get('nativeCore');
22
+ if (VALID_MODES.has(String(queryMode || '').toLowerCase())) {
23
+ return String(queryMode).toLowerCase();
24
+ }
25
+ } catch { }
26
+ try {
27
+ const saved = String(window.localStorage?.getItem(STORAGE_KEY) || '').toLowerCase();
28
+ if (VALID_MODES.has(saved)) return saved;
29
+ } catch { }
30
+ return 'off';
31
+ }
32
+
33
+ const mode = resolveMode();
34
+ const status = {
35
+ mode,
36
+ ready: false,
37
+ backend: '',
38
+ activeBackend: mode === 'off' ? 'off' : '',
39
+ fallbackActive: false,
40
+ fallbackCount: 0,
41
+ fallbackReason: '',
42
+ primaryResults: 0,
43
+ lastPrimaryResultType: '',
44
+ lastPrimaryDurationMs: 0,
45
+ lastPrimaryRoundTripMs: 0,
46
+ comparisons: 0,
47
+ mismatches: 0,
48
+ lastComparison: null,
49
+ lastError: '',
50
+ buildId: BUILD_ID
51
+ };
52
+ window.__mindExecutionNativeCoreStatus = status;
53
+
54
+ function publishRuntimeStatus() {
55
+ const root = document.documentElement;
56
+ if (!root) return;
57
+ root.dataset.mindexecNativeCoreMode = status.mode;
58
+ root.dataset.mindexecNativeCoreReady = status.ready ? 'true' : 'false';
59
+ root.dataset.mindexecNativeCoreBackend = status.backend || '';
60
+ root.dataset.mindexecNativeCoreActiveBackend = status.activeBackend || '';
61
+ root.dataset.mindexecNativeCoreFallback = status.fallbackActive ? 'true' : 'false';
62
+ root.dataset.mindexecNativeCoreError = status.lastError || '';
63
+ }
64
+
65
+ publishRuntimeStatus();
66
+
67
+ function normalizeToken(value) {
68
+ const number = Number(value || 0);
69
+ return Number.isFinite(number) && number >= 0 ? Math.floor(number) : 0;
70
+ }
71
+
72
+ function resultKey(payload) {
73
+ return [
74
+ String(payload?.type || ''),
75
+ String(payload?.boardId || '').trim(),
76
+ normalizeToken(payload?.engineGeneration),
77
+ normalizeToken(payload?.boardEpoch),
78
+ normalizeToken(payload?.boardRevision),
79
+ normalizeToken(payload?.seq)
80
+ ].join(':');
81
+ }
82
+
83
+ function normalizeIds(source) {
84
+ if (!Array.isArray(source)) return null;
85
+ return source
86
+ .map((value) => String(value || '').trim())
87
+ .filter(Boolean)
88
+ .sort();
89
+ }
90
+
91
+ function arraysEqual(left, right) {
92
+ if (left === null || right === null) return left === right;
93
+ if (left.length !== right.length) return false;
94
+ for (let index = 0; index < left.length; index++) {
95
+ if (left[index] !== right[index]) return false;
96
+ }
97
+ return true;
98
+ }
99
+
100
+ function numbersEqual(left, right, tolerance = 0.000001) {
101
+ const leftNumber = Number(left);
102
+ const rightNumber = Number(right);
103
+ if (!Number.isFinite(leftNumber) || !Number.isFinite(rightNumber)) {
104
+ return leftNumber === rightNumber;
105
+ }
106
+ return Math.abs(leftNumber - rightNumber) <= tolerance;
107
+ }
108
+
109
+ function viewportRectsEqual(left, right) {
110
+ if (!left || !right) return left === right;
111
+ return numbersEqual(left.left, right.left) &&
112
+ numbersEqual(left.right, right.right) &&
113
+ numbersEqual(left.bottom, right.bottom) &&
114
+ numbersEqual(left.top, right.top) &&
115
+ String(left.mode || '') === String(right.mode || '') &&
116
+ numbersEqual(left.cameraZ, right.cameraZ);
117
+ }
118
+
119
+ function comparePayloads(primary, native) {
120
+ const type = String(primary?.type || '');
121
+ const details = { type, seq: normalizeToken(primary?.seq), differences: [] };
122
+ if (primary?.stale === true || native?.stale === true) {
123
+ if (primary?.stale !== native?.stale) details.differences.push('stale');
124
+ return details;
125
+ }
126
+ if (type === 'VISIBILITY_RESULT') {
127
+ if (Number(primary.visibleCount) !== Number(native.visibleCount)) details.differences.push('visibleCount');
128
+ if (primary.visibilityChanged !== native.visibilityChanged) details.differences.push('visibilityChanged');
129
+ if (primary.fullResVisibilityReconcilePending !== native.fullResVisibilityReconcilePending) {
130
+ details.differences.push('fullResVisibilityReconcilePending');
131
+ }
132
+ if (Number(primary.lastVisibilityNodeCount) !== Number(native.lastVisibilityNodeCount)) {
133
+ details.differences.push('lastVisibilityNodeCount');
134
+ }
135
+ if (!viewportRectsEqual(primary.viewportRect, native.viewportRect)) details.differences.push('viewportRect');
136
+ if (!arraysEqual(normalizeIds(primary.enteredIds), normalizeIds(native.enteredIds))) details.differences.push('enteredIds');
137
+ if (!arraysEqual(normalizeIds(primary.exitedIds), normalizeIds(native.exitedIds))) details.differences.push('exitedIds');
138
+ const primaryHasVisibleIds = Array.isArray(primary.visibleIds);
139
+ const nativeHasVisibleIds = Array.isArray(native.visibleIds);
140
+ if (primaryHasVisibleIds !== nativeHasVisibleIds) details.differences.push('visibleIdsPresence');
141
+ else if (primaryHasVisibleIds && !arraysEqual(normalizeIds(primary.visibleIds), normalizeIds(native.visibleIds))) {
142
+ details.differences.push('visibleIds');
143
+ }
144
+ const primaryHasPriorityIds = Array.isArray(primary.priorityIds);
145
+ const nativeHasPriorityIds = Array.isArray(native.priorityIds);
146
+ if (primaryHasPriorityIds !== nativeHasPriorityIds) details.differences.push('priorityIdsPresence');
147
+ else if (primaryHasPriorityIds) {
148
+ const primaryOrder = primary.priorityIds.map((value) => String(value || '').trim()).filter(Boolean);
149
+ const nativeOrder = native.priorityIds.map((value) => String(value || '').trim()).filter(Boolean);
150
+ if (!arraysEqual(primaryOrder, nativeOrder)) details.differences.push('priorityOrder');
151
+ }
152
+ } else if (type === 'HIT_TEST_RESULT') {
153
+ if (!arraysEqual(normalizeIds(primary.candidateIds), normalizeIds(native.candidateIds))) {
154
+ details.differences.push('candidateIds');
155
+ }
156
+ } else if (type === 'SELECTION_QUERY_RESULT') {
157
+ if (!arraysEqual(normalizeIds(primary.selectedIds), normalizeIds(native.selectedIds))) {
158
+ details.differences.push('selectedIds');
159
+ }
160
+ }
161
+ return details;
162
+ }
163
+
164
+ function recordComparison(primary, native) {
165
+ const comparison = comparePayloads(primary, native);
166
+ comparison.matched = comparison.differences.length === 0;
167
+ comparison.primaryDurationMs = Number(primary?.profile?.durationMs || 0);
168
+ comparison.nativeDurationMs = Number(native?.profile?.durationMs || 0);
169
+ status.comparisons++;
170
+ if (!comparison.matched) status.mismatches++;
171
+ status.lastComparison = comparison;
172
+ window.MindCanvasTrace?.emit?.('nativeCore.compare', comparison);
173
+ if (!comparison.matched && status.mismatches <= 5) {
174
+ console.warn('[MindExecutionNativeCore] C++ shadow result differs from JS.', comparison);
175
+ }
176
+ }
177
+
178
+ class NativeCoreShadow {
179
+ constructor() {
180
+ this.primaryResults = new Map();
181
+ this.nativeResults = new Map();
182
+ this.worker = null;
183
+ try {
184
+ this.worker = new Worker(NATIVE_WORKER_URL);
185
+ this.worker.onmessage = (event) => this._onMessage(event?.data || null);
186
+ this.worker.onerror = (error) => {
187
+ this._disable(String(error?.message || 'native core worker error'));
188
+ };
189
+ } catch (error) {
190
+ status.lastError = String(error?.message || error || 'native core worker creation failed');
191
+ }
192
+ }
193
+
194
+ _onMessage(data) {
195
+ if (!data || typeof data !== 'object') return;
196
+ if (data.type === 'READY') {
197
+ status.ready = true;
198
+ status.backend = String(data.backend || 'wasm');
199
+ status.activeBackend = status.backend;
200
+ status.fallbackActive = false;
201
+ status.fallbackReason = '';
202
+ status.lastError = '';
203
+ publishRuntimeStatus();
204
+ window.MindCanvasTrace?.emit?.('nativeCore.ready', {
205
+ mode,
206
+ backend: status.backend,
207
+ apiVersion: Number(data.apiVersion || 0),
208
+ buildId: String(data.buildId || '')
209
+ });
210
+ return;
211
+ }
212
+ if (data.type === 'NATIVE_CORE_INIT_ERROR' || data.type === 'NATIVE_CORE_RUNTIME_ERROR') {
213
+ this._disable(String(data.message || data.type));
214
+ return;
215
+ }
216
+ if (!RESULT_TYPES.has(String(data.type || ''))) return;
217
+ const key = resultKey(data);
218
+ this.nativeResults.set(key, data);
219
+ this._compareIfReady(key);
220
+ this._trim();
221
+ }
222
+
223
+ _disable(message) {
224
+ status.lastError = String(message || 'native core disabled');
225
+ status.ready = false;
226
+ status.backend = '';
227
+ status.activeBackend = '';
228
+ publishRuntimeStatus();
229
+ try { this.worker?.terminate?.(); } catch { }
230
+ this.worker = null;
231
+ this.primaryResults.clear();
232
+ this.nativeResults.clear();
233
+ }
234
+
235
+ _compareIfReady(key) {
236
+ const primary = this.primaryResults.get(key);
237
+ const native = this.nativeResults.get(key);
238
+ if (!primary || !native) return;
239
+ this.primaryResults.delete(key);
240
+ this.nativeResults.delete(key);
241
+ recordComparison(primary, native);
242
+ this._trim();
243
+ }
244
+
245
+ _trim() {
246
+ const trimMap = (map) => {
247
+ while (map.size > 64) map.delete(map.keys().next().value);
248
+ };
249
+ trimMap(this.primaryResults);
250
+ trimMap(this.nativeResults);
251
+ }
252
+
253
+ post(payload) {
254
+ if (!this.worker || !MIRRORED_TYPES.has(String(payload?.type || ''))) return false;
255
+ try {
256
+ this.worker.postMessage(payload);
257
+ return true;
258
+ } catch (error) {
259
+ status.lastError = String(error?.message || error || 'native core shadow post failed');
260
+ return false;
261
+ }
262
+ }
263
+
264
+ observePrimaryResult(payload) {
265
+ if (!RESULT_TYPES.has(String(payload?.type || ''))) return;
266
+ const key = resultKey(payload);
267
+ this.primaryResults.set(key, payload);
268
+ this._compareIfReady(key);
269
+ this._trim();
270
+ }
271
+
272
+ dispose() {
273
+ try { this.worker?.terminate?.(); } catch { }
274
+ this.worker = null;
275
+ this.primaryResults.clear();
276
+ this.nativeResults.clear();
277
+ status.ready = false;
278
+ status.backend = '';
279
+ status.activeBackend = '';
280
+ publishRuntimeStatus();
281
+ }
282
+ }
283
+
284
+ window.MindMapNativeCoreBridge = Object.freeze({
285
+ BUILD_ID,
286
+ STORAGE_KEY,
287
+ NATIVE_WORKER_URL,
288
+ getMode() {
289
+ return mode;
290
+ },
291
+ getPrimaryWorkerUrl(defaultUrl) {
292
+ return mode === 'native' ? NATIVE_WORKER_URL : defaultUrl;
293
+ },
294
+ createShadow() {
295
+ return mode === 'compare' ? new NativeCoreShadow() : null;
296
+ },
297
+ notifyPrimaryReady(data) {
298
+ if (mode !== 'native') return false;
299
+ status.ready = true;
300
+ status.backend = String(data?.backend || 'wasm');
301
+ status.activeBackend = status.backend;
302
+ status.fallbackActive = false;
303
+ status.fallbackReason = '';
304
+ status.primaryResults = 0;
305
+ status.lastPrimaryResultType = '';
306
+ status.lastPrimaryDurationMs = 0;
307
+ status.lastPrimaryRoundTripMs = 0;
308
+ status.lastError = '';
309
+ publishRuntimeStatus();
310
+ window.MindCanvasTrace?.emit?.('nativeCore.primaryReady', {
311
+ mode,
312
+ backend: status.backend,
313
+ apiVersion: Number(data?.apiVersion || 0),
314
+ buildId: String(data?.buildId || '')
315
+ });
316
+ return true;
317
+ },
318
+ notifyPrimaryFallback(reason) {
319
+ if (mode !== 'native') return false;
320
+ const message = String(reason || 'native core fallback');
321
+ status.ready = false;
322
+ status.backend = '';
323
+ status.activeBackend = 'legacy-js';
324
+ status.fallbackActive = true;
325
+ status.fallbackCount++;
326
+ status.fallbackReason = message;
327
+ status.lastError = message;
328
+ publishRuntimeStatus();
329
+ window.MindCanvasTrace?.emit?.('nativeCore.primaryFallback', {
330
+ mode,
331
+ reason: message,
332
+ fallbackCount: status.fallbackCount
333
+ });
334
+ return true;
335
+ },
336
+ notifyPrimaryResultAccepted(data, roundTripMs = 0) {
337
+ if (mode !== 'native' || status.ready !== true || status.fallbackActive === true) return false;
338
+ status.primaryResults++;
339
+ status.lastPrimaryResultType = String(data?.type || '');
340
+ status.lastPrimaryDurationMs = Number(data?.profile?.durationMs || 0);
341
+ status.lastPrimaryRoundTripMs = Number(Math.max(0, Number(roundTripMs || 0)).toFixed(2));
342
+ return true;
343
+ },
344
+ notifyPrimaryDisposed(reason = 'native primary disposed') {
345
+ if (mode !== 'native') return false;
346
+ status.ready = false;
347
+ status.backend = '';
348
+ status.activeBackend = '';
349
+ status.fallbackActive = false;
350
+ status.fallbackReason = '';
351
+ status.primaryResults = 0;
352
+ status.lastPrimaryResultType = '';
353
+ status.lastPrimaryDurationMs = 0;
354
+ status.lastPrimaryRoundTripMs = 0;
355
+ status.lastError = '';
356
+ publishRuntimeStatus();
357
+ window.MindCanvasTrace?.emit?.('nativeCore.primaryDisposed', {
358
+ mode,
359
+ reason: String(reason || 'native primary disposed')
360
+ });
361
+ return true;
362
+ },
363
+ notifyFallbackReady() {
364
+ if (mode !== 'native' || status.fallbackActive !== true) return false;
365
+ status.activeBackend = 'legacy-js';
366
+ publishRuntimeStatus();
367
+ return true;
368
+ },
369
+ notifyFallbackUnavailable(reason) {
370
+ if (mode !== 'native' || status.fallbackActive !== true) return false;
371
+ const message = String(reason || 'legacy visibility fallback unavailable');
372
+ status.ready = false;
373
+ status.backend = '';
374
+ status.activeBackend = 'main-thread-js';
375
+ status.fallbackReason = `${status.fallbackReason || 'native core fallback'}; ${message}`;
376
+ status.lastError = message;
377
+ publishRuntimeStatus();
378
+ window.MindCanvasTrace?.emit?.('nativeCore.fallbackUnavailable', {
379
+ mode,
380
+ reason: message,
381
+ activeBackend: status.activeBackend
382
+ });
383
+ return true;
384
+ },
385
+ getStatus() {
386
+ return { ...status };
387
+ },
388
+ setMode(nextMode) {
389
+ const normalized = String(nextMode || '').toLowerCase();
390
+ if (!VALID_MODES.has(normalized)) return false;
391
+ try {
392
+ window.localStorage?.setItem(STORAGE_KEY, normalized);
393
+ return true;
394
+ } catch {
395
+ return false;
396
+ }
397
+ }
398
+ });
399
+ })();
@@ -15,6 +15,9 @@ const TOKEN_REGEX = /[\p{L}\p{M}\p{N}]{2,}/gu;
15
15
  function createState() {
16
16
  return {
17
17
  boardId: '',
18
+ boardEpoch: 0,
19
+ engineGeneration: 0,
20
+ boardRevision: 0,
18
21
  documents: new Map(),
19
22
  tokenIndex: new Map(),
20
23
  prefixIndex: new Map(),
@@ -28,6 +31,33 @@ function normalizeBoardId(boardId) {
28
31
  return String(boardId ?? '').trim();
29
32
  }
30
33
 
34
+ function normalizeOwnerToken(value) {
35
+ const number = Number(value || 0);
36
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : 0;
37
+ }
38
+
39
+ function normalizeBoardRevision(value) {
40
+ const number = Number(value || 0);
41
+ return Number.isFinite(number) && number >= 0 ? Math.floor(number) : 0;
42
+ }
43
+
44
+ function ownerMatches(message, requireRevision = true) {
45
+ if (normalizeBoardId(message?.boardId) !== runtime.boardId ||
46
+ normalizeOwnerToken(message?.boardEpoch) !== runtime.boardEpoch ||
47
+ normalizeOwnerToken(message?.engineGeneration) !== runtime.engineGeneration) {
48
+ return false;
49
+ }
50
+ return requireRevision !== true || normalizeBoardRevision(message?.boardRevision) === runtime.boardRevision;
51
+ }
52
+
53
+ function acceptMutationRevision(message) {
54
+ if (!ownerMatches(message, false)) return false;
55
+ const revision = normalizeBoardRevision(message?.boardRevision);
56
+ if (revision < runtime.boardRevision) return false;
57
+ runtime.boardRevision = revision;
58
+ return true;
59
+ }
60
+
31
61
  function limitText(value, maxLength) {
32
62
  const text = String(value ?? '');
33
63
  if (!text) {
@@ -212,9 +242,12 @@ function addPostingMatches(index, key, candidateScores, weight) {
212
242
  });
213
243
  }
214
244
 
215
- function clearState(boardId) {
245
+ function clearState(boardId, boardEpoch = 0, engineGeneration = 0, boardRevision = 0) {
216
246
  runtime = createState();
217
247
  runtime.boardId = normalizeBoardId(boardId);
248
+ runtime.boardEpoch = normalizeOwnerToken(boardEpoch);
249
+ runtime.engineGeneration = normalizeOwnerToken(engineGeneration);
250
+ runtime.boardRevision = normalizeBoardRevision(boardRevision);
218
251
  }
219
252
 
220
253
  function normalizeNodeSnapshot(node) {
@@ -303,8 +336,8 @@ function upsertNode(node) {
303
336
  document.ngrams.forEach((ngram) => addPosting(runtime.ngramIndex, ngram, document.nodeId));
304
337
  }
305
338
 
306
- function rebuildBoard(boardId, nodes) {
307
- clearState(boardId);
339
+ function rebuildBoard(boardId, nodes, boardEpoch, engineGeneration, boardRevision) {
340
+ clearState(boardId, boardEpoch, engineGeneration, boardRevision);
308
341
  const sourceNodes = Array.isArray(nodes) ? nodes : [];
309
342
  for (let i = 0; i < sourceNodes.length; i++) {
310
343
  upsertNode(sourceNodes[i]);
@@ -391,13 +424,17 @@ function calculateViewportBoost(document, camera) {
391
424
 
392
425
  function handleSearchQuery(message) {
393
426
  const boardId = normalizeBoardId(message.boardId);
394
- if (!boardId || boardId !== runtime.boardId) {
427
+ if (!boardId || !ownerMatches(message)) {
395
428
  self.postMessage({
396
429
  type: 'SEARCH_RESULT',
397
430
  boardId,
431
+ boardEpoch: normalizeOwnerToken(message.boardEpoch),
432
+ engineGeneration: normalizeOwnerToken(message.engineGeneration),
433
+ boardRevision: normalizeBoardRevision(message.boardRevision),
398
434
  seq: Number(message.seq || 0),
399
435
  query: String(message.query || ''),
400
- hits: []
436
+ hits: [],
437
+ stale: true
401
438
  });
402
439
  return;
403
440
  }
@@ -411,6 +448,9 @@ function handleSearchQuery(message) {
411
448
  self.postMessage({
412
449
  type: 'SEARCH_RESULT',
413
450
  boardId,
451
+ boardEpoch: runtime.boardEpoch,
452
+ engineGeneration: runtime.engineGeneration,
453
+ boardRevision: runtime.boardRevision,
414
454
  seq: Number(message.seq || 0),
415
455
  query: String(message.query || ''),
416
456
  hits: []
@@ -494,6 +534,9 @@ function handleSearchQuery(message) {
494
534
  self.postMessage({
495
535
  type: 'SEARCH_RESULT',
496
536
  boardId,
537
+ boardEpoch: runtime.boardEpoch,
538
+ engineGeneration: runtime.engineGeneration,
539
+ boardRevision: runtime.boardRevision,
497
540
  seq: Number(message.seq || 0),
498
541
  query: String(message.query || ''),
499
542
  hits: ranked.slice(0, normalizedLimit),
@@ -513,23 +556,23 @@ self.onmessage = function (event) {
513
556
 
514
557
  switch (message.type) {
515
558
  case 'RESET_BOARD':
516
- clearState(message.boardId);
559
+ clearState(message.boardId, message.boardEpoch, message.engineGeneration, message.boardRevision);
517
560
  break;
518
561
  case 'REBUILD_BOARD':
519
- rebuildBoard(message.boardId, message.nodes);
562
+ rebuildBoard(message.boardId, message.nodes, message.boardEpoch, message.engineGeneration, message.boardRevision);
520
563
  break;
521
564
  case 'UPSERT_NODE':
522
- if (normalizeBoardId(message.boardId) === runtime.boardId) {
565
+ if (acceptMutationRevision(message)) {
523
566
  upsertNode(message.node);
524
567
  }
525
568
  break;
526
569
  case 'REMOVE_NODE':
527
- if (normalizeBoardId(message.boardId) === runtime.boardId) {
570
+ if (acceptMutationRevision(message)) {
528
571
  removeDocument(String(message.nodeId || '').trim());
529
572
  }
530
573
  break;
531
574
  case 'BATCH_SYNC':
532
- if (normalizeBoardId(message.boardId) === runtime.boardId) {
575
+ if (ownerMatches(message)) {
533
576
  const removedIds = Array.isArray(message.removedIds) ? message.removedIds : [];
534
577
  for (let i = 0; i < removedIds.length; i++) {
535
578
  removeDocument(String(removedIds[i] || '').trim());