@mindexec/cli 0.2.493 → 0.2.495

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 (30) hide show
  1. package/electron/main.cjs +108 -7
  2. package/electron/native-renderer-owner-smoke.mjs +387 -0
  3. package/electron/native-renderer-owner.cjs +1044 -0
  4. package/electron/preload.cjs +27 -12
  5. package/electron/source-smoke.mjs +63 -0
  6. package/electron/windows-package-smoke.mjs +92 -0
  7. package/package.json +18 -8
  8. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  9. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  10. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  11. package/scripts/desktop-update-publisher-smoke.mjs +33 -5
  12. package/scripts/publish-mindexec-desktop-updates.mjs +34 -5
  13. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +3 -0
  14. package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +21 -1
  15. package/wwwroot/_content/MindExecution.Shared/js/mind-map-native-render-mirror.js +586 -0
  16. package/wwwroot/_content/MindExecution.Shared/native-core/native-core-manifest.json +2 -2
  17. package/wwwroot/_framework/{MindExecution.Core.60vfpag0ii.dll → MindExecution.Core.x2u2ssgfpb.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Kernel.mvd5iguar3.dll → MindExecution.Kernel.ii6sbi4gnx.dll} +0 -0
  19. package/wwwroot/_framework/{MindExecution.Plugins.Admin.vdu428rgb7.dll → MindExecution.Plugins.Admin.f8zjm0p5bt.dll} +0 -0
  20. package/wwwroot/_framework/{MindExecution.Plugins.Business.ew1bh09dyd.dll → MindExecution.Plugins.Business.dlkfqt93hm.dll} +0 -0
  21. package/wwwroot/_framework/{MindExecution.Plugins.Concept.jk98j1djoc.dll → MindExecution.Plugins.Concept.1dauzp0wt9.dll} +0 -0
  22. package/wwwroot/_framework/{MindExecution.Plugins.Directory.6ub1ud4o4q.dll → MindExecution.Plugins.Directory.3h32owxein.dll} +0 -0
  23. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.0as3sypjys.dll → MindExecution.Plugins.PlanMaster.xcrnct9qgp.dll} +0 -0
  24. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.b12xkgn7e0.dll → MindExecution.Plugins.YouTube.md7aqqgjj2.dll} +0 -0
  25. package/wwwroot/_framework/{MindExecution.Shared.2fytw4afkm.dll → MindExecution.Shared.a9wtfnqxlv.dll} +0 -0
  26. package/wwwroot/_framework/{MindExecution.Web.f6sfehwfzb.dll → MindExecution.Web.xg7sbkw72l.dll} +0 -0
  27. package/wwwroot/_framework/blazor.boot.json +21 -21
  28. package/wwwroot/index.html +2 -1
  29. package/wwwroot/service-worker-assets.js +30 -26
  30. package/wwwroot/service-worker.js +1 -1
@@ -0,0 +1,586 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const BUILD_ID = '20260830-actual-board-mirror-v002';
5
+ const EPOCH_STORAGE_KEY = 'mindexec.native-render-mirror.epoch.v1';
6
+ const MAX_NODES = 250000;
7
+ const MAX_EDGES = 500000;
8
+ const MAX_EDGE_POINTS = 2000000;
9
+ const RETRY_DELAY_MS = 120;
10
+ const MAX_SETTLE_RETRIES = 50;
11
+ const DEFAULT_NODE_COLOR = 0x4d75ffff;
12
+ const DEFAULT_EDGE_COLOR = 0x91a4c7ff;
13
+ const CONTENT_COLORS = Object.freeze({
14
+ text: 0x4d75ffff,
15
+ note: 0x4d75ffff,
16
+ memo: 0xf5b942ff,
17
+ markdown: 0x5378d9ff,
18
+ code: 0x3f8f73ff,
19
+ image: 0x9b6bd3ff,
20
+ video: 0xd46b78ff,
21
+ youtube: 0xe05b62ff,
22
+ agent: 0x3b82f6ff,
23
+ aitask: 0x3b82f6ff
24
+ });
25
+
26
+ let inMemoryEpochState = { engineGeneration: 0, boardEpoch: 0 };
27
+
28
+ function readEpochState() {
29
+ try {
30
+ const raw = window.sessionStorage?.getItem?.(EPOCH_STORAGE_KEY);
31
+ if (raw) {
32
+ const parsed = JSON.parse(raw);
33
+ const engineGeneration = Number(parsed?.engineGeneration);
34
+ const boardEpoch = Number(parsed?.boardEpoch);
35
+ if (Number.isSafeInteger(engineGeneration) && engineGeneration > 0 &&
36
+ Number.isSafeInteger(boardEpoch) && boardEpoch > 0) {
37
+ return { engineGeneration, boardEpoch };
38
+ }
39
+ }
40
+ } catch {
41
+ }
42
+ return { ...inMemoryEpochState };
43
+ }
44
+
45
+ function writeEpochState(state) {
46
+ inMemoryEpochState = { ...state };
47
+ try {
48
+ window.sessionStorage?.setItem?.(EPOCH_STORAGE_KEY, JSON.stringify(state));
49
+ } catch {
50
+ }
51
+ }
52
+
53
+ function allocateBoardEpoch(engineGeneration) {
54
+ const generation = Math.floor(Number(engineGeneration || 0));
55
+ if (!Number.isSafeInteger(generation) || generation <= 0) {
56
+ throw new Error('native-render-invalid-engine-generation');
57
+ }
58
+ const previous = readEpochState();
59
+ const boardEpoch = previous.engineGeneration === generation
60
+ ? previous.boardEpoch + 1
61
+ : 1;
62
+ if (!Number.isSafeInteger(boardEpoch) || boardEpoch <= 0) {
63
+ throw new Error('native-render-board-epoch-exhausted');
64
+ }
65
+ writeEpochState({ engineGeneration: generation, boardEpoch });
66
+ return boardEpoch;
67
+ }
68
+
69
+ function normalizeId(value) {
70
+ return String(value ?? '').trim();
71
+ }
72
+
73
+ function finite(value, fallback = 0) {
74
+ const number = Number(value);
75
+ return Number.isFinite(number) ? number : fallback;
76
+ }
77
+
78
+ function positive(value, fallback = 1) {
79
+ const number = Number(value);
80
+ return Number.isFinite(number) && number > 0 ? number : fallback;
81
+ }
82
+
83
+ function readMetadata(model, key) {
84
+ const metadata = model?.metadata ?? model?.Metadata ?? null;
85
+ if (!metadata) return undefined;
86
+ if (metadata instanceof Map) return metadata.get(key);
87
+ if (Object.prototype.hasOwnProperty.call(metadata, key)) return metadata[key];
88
+ const expected = String(key).toLowerCase();
89
+ const actualKey = Object.keys(metadata).find((entry) => String(entry).toLowerCase() === expected);
90
+ return actualKey ? metadata[actualKey] : undefined;
91
+ }
92
+
93
+ function parseCssColor(value, fallback) {
94
+ const text = String(value ?? '').trim();
95
+ const short = /^#([0-9a-f]{3})$/i.exec(text);
96
+ const long = /^#([0-9a-f]{6})$/i.exec(text);
97
+ let red;
98
+ let green;
99
+ let blue;
100
+ if (short) {
101
+ red = Number.parseInt(short[1][0] + short[1][0], 16);
102
+ green = Number.parseInt(short[1][1] + short[1][1], 16);
103
+ blue = Number.parseInt(short[1][2] + short[1][2], 16);
104
+ } else if (long) {
105
+ red = Number.parseInt(long[1].slice(0, 2), 16);
106
+ green = Number.parseInt(long[1].slice(2, 4), 16);
107
+ blue = Number.parseInt(long[1].slice(4, 6), 16);
108
+ } else {
109
+ return fallback;
110
+ }
111
+ return (((red << 24) | (green << 16) | (blue << 8) | 0xff) >>> 0);
112
+ }
113
+
114
+ function resolveNodeColor(entry) {
115
+ const model = entry?.model || null;
116
+ const contentType = String(model?.contentType ?? model?.ContentType ?? '').trim().toLowerCase();
117
+ const memoColor = readMetadata(model, 'memoColor');
118
+ return parseCssColor(memoColor, CONTENT_COLORS[contentType] ?? DEFAULT_NODE_COLOR);
119
+ }
120
+
121
+ function getNodeId(entry, fallbackId) {
122
+ return normalizeId(
123
+ entry?.model?.id ??
124
+ entry?.model?.Id ??
125
+ entry?.glObject?.userData?.nodeId ??
126
+ entry?.cssObject?.userData?.nodeId ??
127
+ fallbackId
128
+ );
129
+ }
130
+
131
+ function copyNodeGeometry(module) {
132
+ const entries = [];
133
+ module?.nodeObjectsById?.forEach?.((entry, fallbackId) => {
134
+ const id = getNodeId(entry, fallbackId);
135
+ const bounds = window.MindMapNodeBounds?.getNodeWorldBounds?.(entry, { allowDomLookup: false });
136
+ if (!id || !bounds) return;
137
+ const node = {
138
+ id,
139
+ x: finite(bounds.x, Number.NaN),
140
+ y: finite(bounds.y, Number.NaN),
141
+ z: finite(bounds.z, 0),
142
+ width: positive(bounds.width, Number.NaN),
143
+ height: positive(bounds.height, Number.NaN),
144
+ fillRgba: resolveNodeColor(entry)
145
+ };
146
+ if (!Number.isFinite(node.x) || !Number.isFinite(node.y) ||
147
+ !Number.isFinite(node.width) || !Number.isFinite(node.height)) {
148
+ throw new Error(`native-render-invalid-node-bounds:${id}`);
149
+ }
150
+ entries.push(node);
151
+ });
152
+ entries.sort((left, right) => left.id.localeCompare(right.id));
153
+ if (entries.length > MAX_NODES) throw new Error('native-render-node-budget-exceeded');
154
+ return entries;
155
+ }
156
+
157
+ function readAutomationEdges(model) {
158
+ const raw = readMetadata(model, 'AutomationOutgoingEdges');
159
+ if (!raw) return [];
160
+ if (Array.isArray(raw)) return raw;
161
+ if (typeof raw === 'object') return [raw];
162
+ try {
163
+ const parsed = JSON.parse(String(raw));
164
+ return Array.isArray(parsed) ? parsed : [];
165
+ } catch {
166
+ return [];
167
+ }
168
+ }
169
+
170
+ function buildEndpointRoute(source, target) {
171
+ const start = { x: source.x + source.width, y: source.y - source.height * 0.5 };
172
+ const end = { x: target.x, y: target.y - target.height * 0.5 };
173
+ if (end.x >= start.x + 64) {
174
+ const middleX = (start.x + end.x) * 0.5;
175
+ return [start, { x: middleX, y: start.y }, { x: middleX, y: end.y }, end];
176
+ }
177
+ const detourY = Math.max(source.y, target.y) + 64;
178
+ return [
179
+ start,
180
+ { x: start.x + 40, y: start.y },
181
+ { x: start.x + 40, y: detourY },
182
+ { x: end.x - 40, y: detourY },
183
+ { x: end.x - 40, y: end.y },
184
+ end
185
+ ];
186
+ }
187
+
188
+ function copyEdgeGeometry(module, nodesById) {
189
+ const edges = [];
190
+ const seen = new Set();
191
+ module?.nodeObjectsById?.forEach?.((entry, fallbackId) => {
192
+ const sourceId = getNodeId(entry, fallbackId);
193
+ const source = nodesById.get(sourceId);
194
+ if (!source) return;
195
+ for (const edge of readAutomationEdges(entry?.model)) {
196
+ const targetId = normalizeId(edge?.targetNodeId ?? edge?.TargetNodeId);
197
+ const target = nodesById.get(targetId);
198
+ if (!target || targetId === sourceId) continue;
199
+ const edgeId = normalizeId(edge?.id ?? edge?.Id) || `${sourceId}>${targetId}`;
200
+ if (seen.has(edgeId)) continue;
201
+ seen.add(edgeId);
202
+ edges.push({
203
+ id: edgeId,
204
+ sourceId,
205
+ targetId,
206
+ z: Math.max(source.z, target.z) + 1,
207
+ width: 2,
208
+ colorRgba: DEFAULT_EDGE_COLOR,
209
+ points: buildEndpointRoute(source, target)
210
+ });
211
+ }
212
+ });
213
+ edges.sort((left, right) => left.id.localeCompare(right.id));
214
+ if (edges.length > MAX_EDGES) throw new Error('native-render-edge-budget-exceeded');
215
+ const pointCount = edges.reduce((total, edge) => total + edge.points.length, 0);
216
+ if (pointCount > MAX_EDGE_POINTS) throw new Error('native-render-edge-point-budget-exceeded');
217
+ return edges;
218
+ }
219
+
220
+ function copyCamera(module, explicitCamera = null) {
221
+ const camera = explicitCamera || module?.camera || null;
222
+ const rendererCanvas = module?.renderer?.domElement || null;
223
+ const logicalWidth = positive(module?._lastViewportWidth || module?.container?.clientWidth, 1);
224
+ const logicalHeight = positive(module?._lastViewportHeight || module?.container?.clientHeight, 1);
225
+ const dpr = positive(module?._lastViewportDpr || window.devicePixelRatio, 1);
226
+ const viewportW = Math.max(1, Math.round(positive(rendererCanvas?.width, logicalWidth * dpr)));
227
+ const viewportH = Math.max(1, Math.round(positive(rendererCanvas?.height, logicalHeight * dpr)));
228
+ return {
229
+ x: finite(camera?.position?.x ?? camera?.x),
230
+ y: finite(camera?.position?.y ?? camera?.y),
231
+ z: positive(camera?.position?.z ?? camera?.z, 1200),
232
+ near: positive(camera?.near, 0.1),
233
+ far: positive(camera?.far, 10000000),
234
+ fov: positive(camera?.fov, 45),
235
+ viewportW,
236
+ viewportH,
237
+ zoom: positive(camera?.zoom, 1)
238
+ };
239
+ }
240
+
241
+ function isSceneSettled(module, boardId) {
242
+ const state = window.mindMap?.getActiveBoardRuntimeState?.() || null;
243
+ if (!state || normalizeId(state.activeBoardId) !== normalizeId(boardId)) return false;
244
+ return state.boardLoadComplete === true &&
245
+ state.isLoading !== true &&
246
+ module?.isPanning !== true &&
247
+ module?._panSmoothingActive !== true &&
248
+ module?.isZooming !== true &&
249
+ module?.isDraggingNode !== true &&
250
+ module?.isDraggingMultipleNodes !== true &&
251
+ module?.isWindowResizing !== true &&
252
+ module?.isResizing !== true &&
253
+ module?._businessAutomationEdgesDirty !== true &&
254
+ module?._businessAutomationEdgesDeferredForMotion !== true &&
255
+ module?._businessAutomationConnectionDraftActive !== true;
256
+ }
257
+
258
+ function emit(name, detail) {
259
+ window.MindCanvasTrace?.emit?.(name, detail);
260
+ }
261
+
262
+ function publishStatus(status) {
263
+ window.__mindExecutionNativeRenderStatus = Object.freeze({
264
+ buildId: BUILD_ID,
265
+ ...(status || {})
266
+ });
267
+ const root = document?.documentElement;
268
+ if (root) {
269
+ root.dataset.mindexecNativeRenderMode = String(status?.mode || 'off');
270
+ root.dataset.mindexecNativeRenderReady = status?.ready === true ? 'true' : 'false';
271
+ root.dataset.mindexecNativeRenderBackend = String(status?.backend || '');
272
+ }
273
+ }
274
+
275
+ class NativeRenderMirrorController {
276
+ constructor(module) {
277
+ this.module = module || null;
278
+ this.api = window.mindExecDesktop?.nativeRender || window.mindExecDesktop?.nativeRenderer || null;
279
+ this.boardId = '';
280
+ this.engineGeneration = 0;
281
+ this.boardEpoch = 0;
282
+ this.boardRevision = 0;
283
+ this.requestSequence = 0;
284
+ this.nodeHandles = new Map();
285
+ this.edgeHandles = new Map();
286
+ this.nextNodeHandle = 1;
287
+ this.nextEdgeHandle = 1;
288
+ this.attached = false;
289
+ this.nativeReady = false;
290
+ this.snapshotBlocked = false;
291
+ this.disposed = false;
292
+ this.snapshotInFlight = false;
293
+ this.snapshotPending = false;
294
+ this.cameraPending = false;
295
+ this.cameraInFlight = false;
296
+ this.retryCount = 0;
297
+ this.retryTimer = null;
298
+ this.cameraFrame = 0;
299
+ this.unsubscribeStatus = null;
300
+ this._initialize();
301
+ }
302
+
303
+ async _initialize() {
304
+ if (!this.api?.getStatus) {
305
+ publishStatus({ mode: 'off', ready: false, backend: '', lastError: '' });
306
+ return;
307
+ }
308
+ try {
309
+ this.unsubscribeStatus = this.api.onStatus?.((status) => this._adoptStatus(status)) || null;
310
+ this._adoptStatus(await this.api.getStatus());
311
+ } catch (error) {
312
+ this._fallback(error, { blockSnapshot: true });
313
+ }
314
+ }
315
+
316
+ _adoptStatus(status) {
317
+ if (this.disposed) return;
318
+ const mode = String(status?.mode || 'off');
319
+ const generation = Math.max(0, Math.floor(Number(status?.engineGeneration || 0)));
320
+ const ready = mode === 'mirror' && status?.ready === true && generation > 0;
321
+ const wasReady = this.nativeReady;
322
+ const generationChanged = ready && this.engineGeneration !== generation;
323
+ this.nativeReady = ready;
324
+ publishStatus({
325
+ ...status,
326
+ mode,
327
+ ready,
328
+ backend: String(status?.backend || ''),
329
+ lastError: String(status?.lastError || '')
330
+ });
331
+ if (!ready) {
332
+ this.attached = false;
333
+ this.snapshotBlocked = true;
334
+ return;
335
+ }
336
+ if (generationChanged) {
337
+ this.engineGeneration = generation;
338
+ this.boardEpoch = 0;
339
+ this.boardRevision = 0;
340
+ this.requestSequence = 0;
341
+ this.attached = false;
342
+ this.nodeHandles.clear();
343
+ this.edgeHandles.clear();
344
+ this.nextNodeHandle = 1;
345
+ this.nextEdgeHandle = 1;
346
+ }
347
+ if (!this.boardId) {
348
+ this.boardId = normalizeId(this.module?.activeBoardId || window.mindMap?.activeBoardId);
349
+ }
350
+ if (this.boardId && !this.boardEpoch) {
351
+ this.boardEpoch = allocateBoardEpoch(generation);
352
+ }
353
+ if (this.boardId && (generationChanged || !wasReady)) {
354
+ this.snapshotBlocked = false;
355
+ this._scheduleSnapshot('native-ready');
356
+ }
357
+ }
358
+
359
+ resetBoard(boardId, reason = 'reset-board') {
360
+ const nextBoardId = normalizeId(boardId);
361
+ if (this.attached) void this._sendDetach(reason);
362
+ this.boardId = nextBoardId;
363
+ this.boardEpoch = nextBoardId && this.engineGeneration
364
+ ? allocateBoardEpoch(this.engineGeneration)
365
+ : 0;
366
+ this.boardRevision = 0;
367
+ this.requestSequence = 0;
368
+ this.attached = false;
369
+ this.snapshotBlocked = false;
370
+ this.nodeHandles.clear();
371
+ this.edgeHandles.clear();
372
+ this.nextNodeHandle = 1;
373
+ this.nextEdgeHandle = 1;
374
+ this.retryCount = 0;
375
+ if (nextBoardId) this._scheduleSnapshot(reason);
376
+ }
377
+
378
+ geometryChanged(module = this.module, boardId = this.boardId, reason = 'geometry-changed') {
379
+ if (module) this.module = module;
380
+ const normalizedBoardId = normalizeId(boardId);
381
+ if (!normalizedBoardId) return;
382
+ if (this.boardId !== normalizedBoardId) this.resetBoard(normalizedBoardId, reason);
383
+ this.snapshotBlocked = false;
384
+ this._scheduleSnapshot(reason);
385
+ }
386
+
387
+ cameraChanged(module = this.module, camera = null) {
388
+ if (module) this.module = module;
389
+ if (!this.attached || !this.engineGeneration || !this.boardId) {
390
+ if (this.snapshotBlocked) return;
391
+ this._scheduleSnapshot('camera-before-snapshot');
392
+ return;
393
+ }
394
+ this.latestCamera = copyCamera(this.module, camera);
395
+ this.cameraPending = true;
396
+ if (this.cameraFrame) return;
397
+ const schedule = typeof requestAnimationFrame === 'function'
398
+ ? requestAnimationFrame
399
+ : (callback) => setTimeout(callback, 16);
400
+ this.cameraFrame = schedule(() => {
401
+ this.cameraFrame = 0;
402
+ void this._flushCamera();
403
+ });
404
+ }
405
+
406
+ _scheduleSnapshot(reason) {
407
+ if (this.disposed || !this.api?.sendSnapshot) return;
408
+ this.snapshotPending = true;
409
+ this.pendingReason = String(reason || 'snapshot');
410
+ if (!this.nativeReady || this.snapshotBlocked) return;
411
+ if (this.snapshotInFlight || this.retryTimer) return;
412
+ this.retryTimer = setTimeout(() => {
413
+ this.retryTimer = null;
414
+ void this._flushSnapshot();
415
+ }, 0);
416
+ }
417
+
418
+ _owner(revision, sequence) {
419
+ return {
420
+ boardId: this.boardId,
421
+ engineGeneration: this.engineGeneration,
422
+ boardEpoch: this.boardEpoch,
423
+ boardRevision: revision,
424
+ requestSequence: sequence
425
+ };
426
+ }
427
+
428
+ _assignHandle(map, id, kind) {
429
+ let handle = map.get(id);
430
+ if (handle) return handle;
431
+ if (kind === 'node') {
432
+ handle = this.nextNodeHandle++;
433
+ } else {
434
+ handle = this.nextEdgeHandle++;
435
+ }
436
+ if (!Number.isSafeInteger(handle) || handle <= 0 || handle > 0xffffffff) {
437
+ throw new Error(`native-render-${kind}-handle-exhausted`);
438
+ }
439
+ map.set(id, handle);
440
+ return handle;
441
+ }
442
+
443
+ _copySnapshot() {
444
+ const copiedNodes = copyNodeGeometry(this.module);
445
+ const nodesById = new Map(copiedNodes.map((node) => [node.id, node]));
446
+ const copiedEdges = copyEdgeGeometry(this.module, nodesById);
447
+ const nodes = copiedNodes.map((node) => ({
448
+ handle: this._assignHandle(this.nodeHandles, node.id, 'node'),
449
+ x: node.x,
450
+ y: node.y,
451
+ z: node.z,
452
+ width: node.width,
453
+ height: node.height,
454
+ fillRgba: node.fillRgba
455
+ }));
456
+ const edges = copiedEdges.map((edge) => ({
457
+ handle: this._assignHandle(this.edgeHandles, edge.id, 'edge'),
458
+ sourceHandle: this._assignHandle(this.nodeHandles, edge.sourceId, 'node'),
459
+ targetHandle: this._assignHandle(this.nodeHandles, edge.targetId, 'node'),
460
+ z: edge.z,
461
+ width: edge.width,
462
+ colorRgba: edge.colorRgba,
463
+ points: edge.points.map((point) => ({ x: point.x, y: point.y }))
464
+ }));
465
+ return { nodes, edges, camera: copyCamera(this.module) };
466
+ }
467
+
468
+ async _flushSnapshot() {
469
+ if (this.disposed || this.snapshotInFlight || !this.snapshotPending) return;
470
+ if (!this.engineGeneration || !this.boardId) return;
471
+ if (!isSceneSettled(this.module, this.boardId)) {
472
+ if (++this.retryCount <= MAX_SETTLE_RETRIES) {
473
+ this.retryTimer = setTimeout(() => {
474
+ this.retryTimer = null;
475
+ void this._flushSnapshot();
476
+ }, RETRY_DELAY_MS);
477
+ } else {
478
+ this.snapshotPending = false;
479
+ emit('native.render.snapshot.rejected', { boardId: this.boardId, reason: 'scene_not_settled' });
480
+ }
481
+ return;
482
+ }
483
+
484
+ this.retryCount = 0;
485
+ this.snapshotPending = false;
486
+ this.snapshotInFlight = true;
487
+ try {
488
+ const copied = this._copySnapshot();
489
+ const revision = ++this.boardRevision;
490
+ const sequence = ++this.requestSequence;
491
+ const owner = this._owner(revision, sequence);
492
+ const result = await this.api.sendSnapshot({ owner, ...copied, edgeRoute: 'endpoint-v1' });
493
+ if (result?.accepted !== true) {
494
+ throw new Error(result?.reason || result?.lastError || result?.lastRejected?.reason || 'native-render-snapshot-not-accepted');
495
+ }
496
+ this.attached = true;
497
+ this.snapshotBlocked = false;
498
+ publishStatus({ ...result, mode: 'mirror', ready: true });
499
+ emit('native.render.snapshot.accepted', {
500
+ boardId: owner.boardId,
501
+ engineGeneration: owner.engineGeneration,
502
+ boardEpoch: owner.boardEpoch,
503
+ boardRevision: owner.boardRevision,
504
+ requestSequence: owner.requestSequence,
505
+ nodes: copied.nodes.length,
506
+ edges: copied.edges.length,
507
+ route: 'endpoint-v1'
508
+ });
509
+ } catch (error) {
510
+ this._fallback(error, { blockSnapshot: !this.snapshotPending });
511
+ } finally {
512
+ this.snapshotInFlight = false;
513
+ if (this.snapshotPending) this._scheduleSnapshot('coalesced-geometry');
514
+ else if (this.cameraPending) void this._flushCamera();
515
+ }
516
+ }
517
+
518
+ async _flushCamera() {
519
+ if (!this.cameraPending || this.cameraInFlight || this.snapshotInFlight || !this.attached || this.disposed) return;
520
+ this.cameraPending = false;
521
+ this.cameraInFlight = true;
522
+ const owner = this._owner(this.boardRevision, ++this.requestSequence);
523
+ try {
524
+ const result = await this.api.sendCamera({ owner, camera: this.latestCamera || copyCamera(this.module) });
525
+ if (result?.accepted !== true) {
526
+ throw new Error(result?.reason || result?.lastError || result?.lastRejected?.reason || 'native-render-camera-not-accepted');
527
+ }
528
+ publishStatus({ ...result, mode: 'mirror', ready: true });
529
+ } catch (error) {
530
+ this._fallback(error, { blockSnapshot: !this.snapshotPending });
531
+ } finally {
532
+ this.cameraInFlight = false;
533
+ if (this.cameraPending && !this.disposed) {
534
+ void this._flushCamera();
535
+ }
536
+ }
537
+ }
538
+
539
+ async _sendDetach(reason) {
540
+ if (!this.api?.detach || !this.engineGeneration || !this.boardId || !this.boardEpoch) return;
541
+ const owner = this._owner(Math.max(1, this.boardRevision), ++this.requestSequence);
542
+ try {
543
+ await this.api.detach(owner);
544
+ emit('native.render.shutdown', { boardId: owner.boardId, reason: String(reason || 'detach') });
545
+ } catch {
546
+ }
547
+ }
548
+
549
+ _fallback(error, options = {}) {
550
+ const lastError = String(error?.message || error || 'native-render-failed').slice(0, 512);
551
+ this.attached = false;
552
+ if (options.blockSnapshot === true) {
553
+ this.snapshotBlocked = true;
554
+ }
555
+ publishStatus({ mode: 'mirror', ready: false, backend: '', lastError });
556
+ emit('native.render.fallback', { boardId: this.boardId, reason: lastError });
557
+ }
558
+
559
+ dispose() {
560
+ if (this.disposed) return;
561
+ this.disposed = true;
562
+ if (this.retryTimer) clearTimeout(this.retryTimer);
563
+ this.retryTimer = null;
564
+ this.unsubscribeStatus?.();
565
+ this.unsubscribeStatus = null;
566
+ if (this.attached) void this._sendDetach('logic-worker-dispose');
567
+ this.attached = false;
568
+ }
569
+ }
570
+
571
+ window.MindMapNativeRenderMirror = Object.assign(window.MindMapNativeRenderMirror || {}, {
572
+ BUILD_ID,
573
+ createController(module) {
574
+ return new NativeRenderMirrorController(module);
575
+ },
576
+ testing: {
577
+ copyNodeGeometry,
578
+ copyEdgeGeometry,
579
+ copyCamera,
580
+ buildEndpointRoute,
581
+ isSceneSettled,
582
+ parseCssColor,
583
+ allocateBoardEpoch
584
+ }
585
+ });
586
+ })();
@@ -3,11 +3,11 @@
3
3
  "abiVersion": 1,
4
4
  "engineVersion": "0.1.0",
5
5
  "status": "experimental-native-primary",
6
- "generatedUtc": "2026-08-30T07:12:19.1507322Z",
6
+ "generatedUtc": "2026-08-30T10:41:24.8262997Z",
7
7
  "sources": [
8
8
  {
9
9
  "name": "engine/CMakeLists.txt",
10
- "sha256": "80dafcd233aae1a5611af3930e22fc8e392a7cb9b13da50805c09c985c2f7a37"
10
+ "sha256": "a6c86dc750403e8251c96719721cd1cefb148218318069357ec9a018d3bee43d"
11
11
  },
12
12
  {
13
13
  "name": "engine/core/include/mindexec/core_api.h",