@mindexec/cli 0.2.465 → 0.2.466

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 (27) hide show
  1. package/README.md +1 -0
  2. package/codex-runtime.js +521 -15
  3. package/package.json +35 -33
  4. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  5. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  6. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  7. package/scripts/board-render-ownership-smoke.mjs +85 -0
  8. package/scripts/codex-sdk-image-runtime-smoke.mjs +121 -0
  9. package/server.js +17 -0
  10. package/wwwroot/_content/MindExecution.Shared/js/mind-map-board-render-ownership.js +81 -0
  11. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +85 -32
  12. package/wwwroot/_framework/MindExecution.Core.psrddqi3mi.dll +0 -0
  13. package/wwwroot/_framework/{MindExecution.Kernel.98wpeg0ntq.dll → MindExecution.Kernel.emus4f6jja.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Admin.y25y20jkiv.dll → MindExecution.Plugins.Admin.q3gz9b4wp8.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.Business.guko3zr1rb.dll → MindExecution.Plugins.Business.dr4m9mbnc5.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.Concept.qmin06apyt.dll → MindExecution.Plugins.Concept.xzrzi61p7e.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.Directory.6rw2l9utyr.dll → MindExecution.Plugins.Directory.hcbtf3n8w7.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.72ir717kho.dll → MindExecution.Plugins.PlanMaster.1w09epz56f.dll} +0 -0
  19. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.rkge49vbw6.dll → MindExecution.Plugins.YouTube.7vjbulgfo8.dll} +0 -0
  20. package/wwwroot/_framework/{MindExecution.Shared.q4dgev2e3z.dll → MindExecution.Shared.zj2n1axexl.dll} +0 -0
  21. package/wwwroot/_framework/MindExecution.Web.z1np8j1qu6.dll +0 -0
  22. package/wwwroot/_framework/blazor.boot.json +21 -21
  23. package/wwwroot/index.html +2 -1
  24. package/wwwroot/service-worker-assets.js +28 -24
  25. package/wwwroot/service-worker.js +1 -1
  26. package/wwwroot/_framework/MindExecution.Core.o29fdql6j5.dll +0 -0
  27. package/wwwroot/_framework/MindExecution.Web.ckdrqv28k2.dll +0 -0
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.465",
3
+ "version": "0.2.466",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "start-bridge.bat",
9
9
  "start-bridge.sh",
10
- "launch-bridge.cjs",
11
- "desktop-workspace-state.cjs",
12
- "server.js",
10
+ "launch-bridge.cjs",
11
+ "desktop-workspace-state.cjs",
12
+ "server.js",
13
13
  "remote-hub.js",
14
14
  "lzo1x.js",
15
15
  "mode4-atlas.js",
@@ -27,17 +27,19 @@
27
27
  "scripts": {
28
28
  "start": "node launch-bridge.cjs",
29
29
  "dev": "node launch-bridge.cjs --watch",
30
- "desktop:start": "electron electron/main.cjs",
31
- "desktop:build:win": "electron-builder --win --x64 --publish never",
32
- "desktop:inspect:update:dev": "node scripts/publish-mindexec-desktop-updates.mjs --channel dev --platform windows-x64 --dir ../artifacts/electron --allow-unsigned",
33
- "desktop:publish:update:dev": "node scripts/publish-mindexec-desktop-updates.mjs --channel dev --platform windows-x64 --dir ../artifacts/electron --allow-unsigned --publish",
34
- "desktop:deploy:update-worker": "node ../r2-auth-proxy/node_modules/wrangler/wrangler-dist/cli.js deploy --config ../workers/mindexec-desktop-updates/wrangler.jsonc",
35
- "desktop:check:update-worker": "node ../r2-auth-proxy/node_modules/wrangler/wrangler-dist/cli.js deploy --dry-run --config ../workers/mindexec-desktop-updates/wrangler.jsonc",
36
- "test:desktop:update": "node --test electron/update-manager-smoke.mjs electron/recurring-update-owner-smoke.mjs scripts/desktop-update-worker-smoke.mjs scripts/desktop-update-publisher-smoke.mjs",
37
- "test:desktop": "node electron/source-smoke.mjs && node scripts/desktop-workspace-state-smoke.mjs && npm run test:desktop:update",
30
+ "desktop:start": "electron electron/main.cjs",
31
+ "desktop:build:win": "electron-builder --win --x64 --publish never",
32
+ "desktop:inspect:update:dev": "node scripts/publish-mindexec-desktop-updates.mjs --channel dev --platform windows-x64 --dir ../artifacts/electron --allow-unsigned",
33
+ "desktop:publish:update:dev": "node scripts/publish-mindexec-desktop-updates.mjs --channel dev --platform windows-x64 --dir ../artifacts/electron --allow-unsigned --publish",
34
+ "desktop:deploy:update-worker": "node ../r2-auth-proxy/node_modules/wrangler/wrangler-dist/cli.js deploy --config ../workers/mindexec-desktop-updates/wrangler.jsonc",
35
+ "desktop:check:update-worker": "node ../r2-auth-proxy/node_modules/wrangler/wrangler-dist/cli.js deploy --dry-run --config ../workers/mindexec-desktop-updates/wrangler.jsonc",
36
+ "test:desktop:update": "node --test electron/update-manager-smoke.mjs electron/recurring-update-owner-smoke.mjs scripts/desktop-update-worker-smoke.mjs scripts/desktop-update-publisher-smoke.mjs",
37
+ "test:desktop": "node electron/source-smoke.mjs && node scripts/desktop-workspace-state-smoke.mjs && npm run test:desktop:update",
38
38
  "test:desktop:win": "node electron/windows-package-smoke.mjs",
39
- "test:syntax": "node --check server.js && node --check remote-hub.js && node --check lzo1x.js && node --check mode4-atlas.js && node --check mode4-atlas-worker.js && node --check codex-runtime.js && node --check codex-model-catalog.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check desktop-workspace-state.cjs && node --check electron/main.cjs && node --check electron/preload.cjs && node --check electron/update-manager.cjs && node --check electron/update-feed-resolver.cjs && node --check electron/recurring-update-owner.cjs && node --check electron/source-smoke.mjs && node --check electron/windows-package-smoke.mjs && node --check electron/update-manager-smoke.mjs && node --check electron/recurring-update-owner-smoke.mjs && node --check scripts/desktop-workspace-state-smoke.mjs && node --check scripts/desktop-update-worker-smoke.mjs && node --check scripts/desktop-update-publisher-smoke.mjs && node --check scripts/publish-mindexec-desktop-updates.mjs && node --check ../workers/mindexec-desktop-updates/src/index.mjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/npm-fresh-install-smoke.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/codex-model-catalog-smoke.mjs && node --check scripts/codex-sdk-runtime-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
39
+ "test:syntax": "node --check server.js && node --check remote-hub.js && node --check lzo1x.js && node --check mode4-atlas.js && node --check mode4-atlas-worker.js && node --check codex-runtime.js && node --check codex-model-catalog.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check desktop-workspace-state.cjs && node --check electron/main.cjs && node --check electron/preload.cjs && node --check electron/update-manager.cjs && node --check electron/update-feed-resolver.cjs && node --check electron/recurring-update-owner.cjs && node --check electron/source-smoke.mjs && node --check electron/windows-package-smoke.mjs && node --check electron/update-manager-smoke.mjs && node --check electron/recurring-update-owner-smoke.mjs && node --check scripts/desktop-workspace-state-smoke.mjs && node --check scripts/desktop-update-worker-smoke.mjs && node --check scripts/desktop-update-publisher-smoke.mjs && node --check scripts/board-render-ownership-smoke.mjs && node --check ../workers/mindexec-desktop-updates/src/index.mjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/npm-fresh-install-smoke.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/codex-model-catalog-smoke.mjs && node --check scripts/codex-sdk-runtime-smoke.mjs && node --check scripts/codex-sdk-image-runtime-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
40
+ "test:canvas:board-owner": "node scripts/board-render-ownership-smoke.mjs",
40
41
  "test:codex-sdk": "node scripts/codex-sdk-runtime-smoke.mjs",
42
+ "test:codex-sdk:image": "node scripts/codex-sdk-image-runtime-smoke.mjs",
41
43
  "test:codex-sdk:live": "node scripts/codex-sdk-runtime-smoke.mjs --live",
42
44
  "test:codex-sdk:live-tools": "node scripts/codex-sdk-runtime-smoke.mjs --live-tools",
43
45
  "test:codex-model-catalog": "node scripts/codex-model-catalog-smoke.mjs",
@@ -83,17 +85,17 @@
83
85
  "cors": "^2.8.5",
84
86
  "express": "^4.18.2",
85
87
  "ffmpeg-static": "^5.3.0",
86
- "multer": "^2.2.0",
87
- "electron-updater": "^6.8.9",
88
- "playwright-core": "^1.53.0",
88
+ "multer": "^2.2.0",
89
+ "electron-updater": "^6.8.9",
90
+ "playwright-core": "^1.53.0",
89
91
  "sharp": "^0.35.3",
90
92
  "web-tree-sitter": "^0.22.6",
91
93
  "ws": "^8.16.0"
92
94
  },
93
- "overrides": {
94
- "body-parser": "1.20.6",
95
- "js-yaml": "4.3.1"
96
- },
95
+ "overrides": {
96
+ "body-parser": "1.20.6",
97
+ "js-yaml": "4.3.1"
98
+ },
97
99
  "bin": {
98
100
  "mindexec": "launch-bridge.cjs",
99
101
  "mind-bridge": "launch-bridge.cjs"
@@ -108,9 +110,9 @@
108
110
  "productName": "MindExec",
109
111
  "copyright": "Copyright © 2026 lovecrdm77",
110
112
  "asar": false,
111
- "extraMetadata": {
112
- "main": "electron/main.cjs",
113
- "desktopUpdateChannel": "dev"
113
+ "extraMetadata": {
114
+ "main": "electron/main.cjs",
115
+ "desktopUpdateChannel": "dev"
114
116
  },
115
117
  "directories": {
116
118
  "output": "../artifacts/electron"
@@ -119,7 +121,7 @@
119
121
  "*.js",
120
122
  "*.cjs",
121
123
  "*.json",
122
- "electron/**/*",
124
+ "electron/**/*",
123
125
  "remote-fast/**/*",
124
126
  "scripts/**/*",
125
127
  "tree-sitter-grammars/**/*",
@@ -144,8 +146,8 @@
144
146
  }
145
147
  ]
146
148
  },
147
- "nsis": {
148
- "artifactName": "MindExec-${version}-dev-${arch}.${ext}",
149
+ "nsis": {
150
+ "artifactName": "MindExec-${version}-dev-${arch}.${ext}",
149
151
  "oneClick": false,
150
152
  "perMachine": false,
151
153
  "allowToChangeInstallationDirectory": true,
@@ -153,12 +155,12 @@
153
155
  "createStartMenuShortcut": true,
154
156
  "shortcutName": "MindExec"
155
157
  },
156
- "portable": {
157
- "artifactName": "MindExec-Portable-${version}-${arch}.${ext}"
158
- },
159
- "publish": {
160
- "provider": "generic",
161
- "url": "https://mindexec.lovecrdm.workers.dev/dev/windows/x64"
162
- }
158
+ "portable": {
159
+ "artifactName": "MindExec-Portable-${version}-${arch}.${ext}"
160
+ },
161
+ "publish": {
162
+ "provider": "generic",
163
+ "url": "https://mindexec.lovecrdm.workers.dev/dev/windows/x64"
164
+ }
163
165
  }
164
166
  }
@@ -0,0 +1,85 @@
1
+ import assert from 'node:assert/strict';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import vm from 'node:vm';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
8
+ const sourcePath = path.resolve(
9
+ scriptDir,
10
+ '..',
11
+ '..',
12
+ 'MindExecution.Shared',
13
+ 'wwwroot',
14
+ 'js',
15
+ 'mind-map-board-render-ownership.js');
16
+ const source = fs.readFileSync(sourcePath, 'utf8');
17
+ const traces = [];
18
+ const window = {
19
+ MindCanvasTrace: { emit: (type, data) => traces.push({ type, data }) },
20
+ MindCanvasBuildInfo: { registerPart: () => { } }
21
+ };
22
+
23
+ vm.runInNewContext(source, { window, Map, Object, Number, String, Math });
24
+
25
+ const makeMesh = (count = 8) => ({
26
+ visible: true,
27
+ geometry: {
28
+ instanceCount: count,
29
+ drawRange: { start: 0, count: count },
30
+ setDrawRange(start, nextCount) {
31
+ this.drawRange = { start, count: nextCount };
32
+ }
33
+ }
34
+ });
35
+ const renderer = {
36
+ resetCalls: 0,
37
+ resetState() { this.resetCalls++; },
38
+ pendingNodes: new Map([['stale-node', {}]]),
39
+ instancedHaloMesh: makeMesh(),
40
+ instancedMesh: makeMesh(),
41
+ instancedLineMesh: makeMesh(),
42
+ instancedBorderLineMesh: makeMesh(),
43
+ instancedConsoleMesh: makeMesh(),
44
+ instancedImageMesh: makeMesh(),
45
+ requestResidentPatch(reason) { this.patchReason = reason; }
46
+ };
47
+ const module = { _forceUpdateFrames: 0 };
48
+
49
+ assert.equal(window.MindMapBoardRenderOwnership.retireResidentBoard(renderer, module, {
50
+ fromBoardId: 'BOARD-B',
51
+ toBoardId: 'BOARD-A'
52
+ }), true);
53
+ assert.equal(renderer.resetCalls, 1);
54
+ assert.equal(renderer.pendingNodes.size, 0);
55
+ assert.equal(renderer._activeBoardRenderId, 'board-a');
56
+ assert.equal(renderer._residentBuilt, false);
57
+ assert.equal(renderer._residentBuildPhase, 'board-switch');
58
+ for (const mesh of [
59
+ renderer.instancedHaloMesh,
60
+ renderer.instancedMesh,
61
+ renderer.instancedLineMesh,
62
+ renderer.instancedBorderLineMesh,
63
+ renderer.instancedConsoleMesh,
64
+ renderer.instancedImageMesh
65
+ ]) {
66
+ assert.equal(mesh.visible, false);
67
+ assert.equal(mesh.geometry.instanceCount, 0);
68
+ }
69
+ assert.equal(renderer.instancedBorderLineMesh.geometry.drawRange.count, 0);
70
+ assert.equal(traces.at(-1)?.type, 'board.resident.retired');
71
+
72
+ const lineDataCache = new Map([['node-a', [1, 2, 3]]]);
73
+ assert.equal(window.MindMapBoardRenderOwnership.prepareCachedBoard(renderer, module, {
74
+ boardId: 'BOARD-A',
75
+ lineDataCache
76
+ }), true);
77
+ assert.equal(renderer.lineDataCache, lineDataCache);
78
+ assert.equal(renderer.patchReason, 'board-reload');
79
+
80
+ console.log(JSON.stringify({
81
+ ok: true,
82
+ retiredResidentCount: 0,
83
+ activeBoardRenderId: renderer._activeBoardRenderId,
84
+ patchReason: renderer.patchReason
85
+ }));
@@ -0,0 +1,121 @@
1
+ import assert from 'node:assert/strict';
2
+ import { promises as fs } from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createCodexRuntime } from '../codex-runtime.js';
7
+
8
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
+ const packageRoot = path.resolve(scriptDir, '..');
10
+ const workspaceRoot = path.resolve(packageRoot, '..');
11
+ const runtimeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'mindexec-codex-sdk-image-'));
12
+ const tinyPngBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jX8kAAAAASUVORK5CYII=';
13
+
14
+ function imageEvents() {
15
+ return (async function* generateEvents() {
16
+ yield { type: 'thread.started', thread_id: 'thread-image-smoke' };
17
+ yield {
18
+ type: 'event_msg',
19
+ payload: {
20
+ type: 'image_generation_end',
21
+ result: `data:image/png;base64,${tinyPngBase64}`
22
+ }
23
+ };
24
+ yield { type: 'turn.completed' };
25
+ })();
26
+ }
27
+
28
+ function createFakeSdk(runStreamed, captures) {
29
+ return {
30
+ Codex: class FakeCodex {
31
+ constructor(options) {
32
+ captures.codexOptions.push(options);
33
+ }
34
+
35
+ startThread(options) {
36
+ captures.threadOptions.push(options);
37
+ return {
38
+ runStreamed: async input => {
39
+ captures.inputs.push(input);
40
+ return { events: runStreamed() };
41
+ }
42
+ };
43
+ }
44
+ }
45
+ };
46
+ }
47
+
48
+ function createRuntime(fakeSdk, loginCodex) {
49
+ return createCodexRuntime({
50
+ packageRoot,
51
+ resolveWorkingDirectory: async () => workspaceRoot,
52
+ getCurrentRuntime: () => ({}),
53
+ getModelCatalog: async () => ({ models: [], source: 'smoke', updatedAt: new Date().toISOString() }),
54
+ loadCodexSdk: async () => fakeSdk,
55
+ ensureCodexRuntimeHome: async () => {
56
+ await fs.mkdir(path.join(runtimeHome, 'generated_images'), { recursive: true });
57
+ return runtimeHome;
58
+ },
59
+ buildCodexChildEnv: () => ({ CODEX_HOME: runtimeHome }),
60
+ loginCodex,
61
+ log: () => {}
62
+ });
63
+ }
64
+
65
+ try {
66
+ const captures = { codexOptions: [], threadOptions: [], inputs: [] };
67
+ const runtime = createRuntime(createFakeSdk(imageEvents, captures), async () => {
68
+ throw new Error('Login must not run for an authenticated image request.');
69
+ });
70
+ const generated = await runtime.runImage({
71
+ prompt: 'Create a blue product icon.',
72
+ model: 'gpt-5.5',
73
+ size: '1024x1024',
74
+ quality: 'high',
75
+ referenceImages: [{ base64: tinyPngBase64, mimeType: 'image/png', fileName: 'source.png' }]
76
+ });
77
+
78
+ assert.equal(generated.success, true, generated.error);
79
+ assert.equal(generated.providerKind, 'TypeScriptSdk');
80
+ assert.equal(generated.imageBase64, tinyPngBase64);
81
+ assert.equal(generated.mimeType, 'image/png');
82
+ assert.equal(captures.codexOptions[0].config.features.image_generation, true);
83
+ assert.equal(captures.threadOptions[0].sandboxMode, 'read-only');
84
+ assert.equal(captures.inputs[0][0].type, 'text');
85
+ assert.equal(captures.inputs[0][1].type, 'local_image');
86
+ assert.equal(await fs.stat(captures.inputs[0][1].path).then(() => true).catch(() => false), false);
87
+
88
+ let attempts = 0;
89
+ let loginCount = 0;
90
+ const authCaptures = { codexOptions: [], threadOptions: [], inputs: [] };
91
+ const authSdk = createFakeSdk(() => {
92
+ attempts += 1;
93
+ if (attempts === 1) {
94
+ return (async function* invalidAuth() {
95
+ throw new Error('401 Unauthorized: invalid_refresh_token. Please log out and sign in again.');
96
+ })();
97
+ }
98
+ return imageEvents();
99
+ }, authCaptures);
100
+ const authRuntime = createRuntime(authSdk, async () => {
101
+ loginCount += 1;
102
+ return { success: true, exitCode: 0 };
103
+ });
104
+ const retried = await authRuntime.runImage({ prompt: 'Create one test image.' });
105
+
106
+ assert.equal(loginCount, 1);
107
+ assert.equal(attempts, 2);
108
+ assert.equal(retried.success, true, retried.error);
109
+ assert.equal(retried.loginOpened, true);
110
+ assert.equal(retried.authenticationRequired, false);
111
+ assert.equal(retried.providerKind, 'TypeScriptSdk');
112
+
113
+ console.log(JSON.stringify({
114
+ ok: true,
115
+ providerKind: generated.providerKind,
116
+ referenceAttachment: true,
117
+ browserLoginRetry: true
118
+ }));
119
+ } finally {
120
+ await fs.rm(runtimeHome, { recursive: true, force: true });
121
+ }
package/server.js CHANGED
@@ -13022,6 +13022,23 @@ app.post('/api/codex/thread/run', async (req, res) => {
13022
13022
  }
13023
13023
  });
13024
13024
 
13025
+ app.post('/api/codex/image/run', async (req, res) => {
13026
+ try {
13027
+ res.json(await getCodexRuntime().runImage(req.body || {}));
13028
+ } catch (err) {
13029
+ console.error('[Codex/ImageRun] Error:', err);
13030
+ res.status(err.statusCode || 500).json({
13031
+ success: false,
13032
+ providerKind: 'Unavailable',
13033
+ imageBase64: '',
13034
+ mimeType: '',
13035
+ error: err?.message || String(err),
13036
+ authenticationRequired: false,
13037
+ loginOpened: false
13038
+ });
13039
+ }
13040
+ });
13041
+
13025
13042
  app.post('/api/codex/thread/resume', async (req, res) => {
13026
13043
  try {
13027
13044
  res.json(await getCodexRuntime().resumeThread(req.body || {}));
@@ -0,0 +1,81 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const BUILD_ID = '20260819-board-render-owner-v1';
5
+
6
+ function normalizeBoardId(value) {
7
+ return String(value || '').trim().toLowerCase();
8
+ }
9
+
10
+ function clearResidentGeometry(mesh, useDrawRange = false) {
11
+ if (!mesh) return;
12
+
13
+ mesh.visible = false;
14
+ const geometry = mesh.geometry;
15
+ if (!geometry) return;
16
+
17
+ if ('instanceCount' in geometry) {
18
+ geometry.instanceCount = 0;
19
+ }
20
+ if (useDrawRange && typeof geometry.setDrawRange === 'function') {
21
+ geometry.setDrawRange(0, 0);
22
+ }
23
+ }
24
+
25
+ function retireResidentBoard(renderer, module, details = {}) {
26
+ if (!renderer) return false;
27
+
28
+ renderer.resetState?.();
29
+ renderer.pendingNodes?.clear?.();
30
+
31
+ clearResidentGeometry(renderer.instancedHaloMesh);
32
+ clearResidentGeometry(renderer.instancedMesh);
33
+ clearResidentGeometry(renderer.instancedLineMesh);
34
+ clearResidentGeometry(renderer.instancedBorderLineMesh, true);
35
+ clearResidentGeometry(renderer.instancedConsoleMesh);
36
+ clearResidentGeometry(renderer.instancedImageMesh);
37
+
38
+ renderer._activeBoardRenderId = normalizeBoardId(details.toBoardId);
39
+ renderer._boardRenderEpoch = Math.max(0, Number(renderer._boardRenderEpoch || 0)) + 1;
40
+ renderer._residentBuilt = false;
41
+ renderer._residentBuildPhase = 'board-switch';
42
+
43
+ if (module) {
44
+ module._forceUpdateFrames = Math.max(Number(module._forceUpdateFrames || 0), 2);
45
+ }
46
+
47
+ window.MindCanvasTrace?.emit?.('board.resident.retired', {
48
+ fromBoardId: details.fromBoardId || null,
49
+ toBoardId: details.toBoardId || null,
50
+ reason: details.reason || 'board-switch',
51
+ epoch: renderer._boardRenderEpoch
52
+ });
53
+ return true;
54
+ }
55
+
56
+ function prepareCachedBoard(renderer, module, details = {}) {
57
+ if (!renderer) return false;
58
+
59
+ const boardId = normalizeBoardId(details.boardId);
60
+ renderer._activeBoardRenderId = boardId;
61
+ if (details.lineDataCache instanceof Map) {
62
+ renderer.lineDataCache = details.lineDataCache;
63
+ }
64
+ renderer.requestResidentPatch?.('board-reload');
65
+
66
+ if (module) {
67
+ module._forceUpdateFrames = Math.max(Number(module._forceUpdateFrames || 0), 2);
68
+ }
69
+ return true;
70
+ }
71
+
72
+ window.MindMapBoardRenderOwnership = Object.freeze({
73
+ BUILD_ID,
74
+ normalizeBoardId,
75
+ retireResidentBoard,
76
+ prepareCachedBoard
77
+ });
78
+ window.MindCanvasBuildInfo?.registerPart?.('boardRenderOwnership', BUILD_ID, {
79
+ file: 'mind-map-board-render-ownership.js'
80
+ });
81
+ })();
@@ -2837,7 +2837,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2837
2837
  /**
2838
2838
  * Detach the current board's scene objects (without destroying them) and store in cache.
2839
2839
  */
2840
- function _detachCurrentBoard(boardId) {
2840
+ function _detachCurrentBoard(boardId, nextBoardId = null) {
2841
2841
  if (!moduleInstance || !boardId) return;
2842
2842
  const t0 = performance.now();
2843
2843
 
@@ -2892,6 +2892,16 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2892
2892
  window.MindMapCss3DManager.clearTextOverlays(moduleInstance);
2893
2893
  }
2894
2894
 
2895
+ window.MindMapBoardRenderOwnership?.retireResidentBoard?.(
2896
+ moduleInstance.lodRenderer,
2897
+ moduleInstance,
2898
+ {
2899
+ fromBoardId: boardId,
2900
+ toBoardId: nextBoardId,
2901
+ reason: 'board-cache-detach'
2902
+ }
2903
+ );
2904
+
2895
2905
  _touchBoardCache(boardId, snapshot);
2896
2906
  _enforceBoardCacheLimit();
2897
2907
 
@@ -2911,15 +2921,6 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2911
2921
  totalTextureMemoryBytes: 0
2912
2922
  });
2913
2923
 
2914
- // Reset LOD renderer state
2915
- if (moduleInstance.lodRenderer) {
2916
- moduleInstance.lodRenderer._instancesDirty = true;
2917
- moduleInstance.lodRenderer._isNodeArrayDirty = true;
2918
- moduleInstance.lodRenderer._nodeIdToIndex.clear();
2919
- moduleInstance.lodRenderer.lineDataCache = new Map();
2920
- moduleInstance.lodRenderer._cachedNodeArray = [];
2921
- }
2922
-
2923
2924
  console.log(`[BoardCache] ??Detached board ${boardId} (${snapshot.nodeObjectsById.size} nodes) in ${(performance.now() - t0).toFixed(1)}ms`);
2924
2925
  }
2925
2926
 
@@ -2939,16 +2940,17 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2939
2940
  snapshot.nodeObjectsById.forEach((entry, nodeId) => {
2940
2941
  try {
2941
2942
  if (entry.glObject && moduleInstance.scene) {
2943
+ entry.glObject.visible = false;
2942
2944
  moduleInstance.scene.add(entry.glObject);
2943
2945
  }
2944
2946
  if (entry.cssObject) {
2945
2947
  const cssScene = moduleInstance.cssScene || moduleInstance.scene;
2946
2948
  if (cssScene) cssScene.add(entry.cssObject);
2947
- // Re-show CSS element
2949
+ entry.cssObject.visible = false;
2950
+ // Keep the cached surface neutral until the target board's
2951
+ // camera band becomes the active renderer owner.
2948
2952
  if (entry.cssObject.element) {
2949
- entry.cssObject.element.style.display = '';
2950
- entry.cssObject.element.style.visibility = '';
2951
- entry.cssObject.element.style.opacity = '';
2953
+ entry.cssObject.element.style.setProperty('display', 'none', 'important');
2952
2954
  }
2953
2955
  }
2954
2956
  } catch (e) {
@@ -2974,10 +2976,14 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2974
2976
  });
2975
2977
 
2976
2978
  // Restore LOD renderer
2977
- if (moduleInstance.lodRenderer) {
2978
- moduleInstance.lodRenderer.lineDataCache = snapshot.lodLineDataCache;
2979
- moduleInstance.lodRenderer.requestResidentPatch?.('board-reload');
2980
- }
2979
+ window.MindMapBoardRenderOwnership?.prepareCachedBoard?.(
2980
+ moduleInstance.lodRenderer,
2981
+ moduleInstance,
2982
+ {
2983
+ boardId,
2984
+ lineDataCache: snapshot.lodLineDataCache
2985
+ }
2986
+ );
2981
2987
  moduleInstance.logicWorkerBridge?.resetBoard?.(boardId, 'cache-attach');
2982
2988
  moduleInstance.logicWorkerBridge?.rebuildNodeTables?.(moduleInstance, {
2983
2989
  boardId,
@@ -3859,6 +3865,53 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
3859
3865
  return true;
3860
3866
  }
3861
3867
 
3868
+ _applyLocalSnapGridMotionStyle(layer, smallStepPx, majorStepPx, opacity) {
3869
+ if (!layer) return false;
3870
+
3871
+ const safeSmallStep = Math.max(
3872
+ LOCAL_SNAP_GRID_RASTER_MIN_STEP_PX,
3873
+ Number(smallStepPx) || LOCAL_SNAP_GRID_DEFAULT_STEP_PX
3874
+ );
3875
+ const safeMajorStep = Math.max(
3876
+ safeSmallStep,
3877
+ Math.min(
3878
+ LOCAL_SNAP_GRID_RASTER_MAX_PERIOD_PX,
3879
+ Number(majorStepPx) || LOCAL_SNAP_GRID_DEFAULT_MAJOR_STEP_PX
3880
+ )
3881
+ );
3882
+ const safeOpacity = Math.max(0, Math.min(0.68, Number(opacity) || 0));
3883
+ const smallStepValue = safeSmallStep.toFixed(3);
3884
+ const majorStepValue = safeMajorStep.toFixed(3);
3885
+ const backgroundSizeValue = [
3886
+ `${smallStepValue}px ${smallStepValue}px`,
3887
+ `${smallStepValue}px ${smallStepValue}px`,
3888
+ `${majorStepValue}px ${majorStepValue}px`,
3889
+ `${majorStepValue}px ${majorStepValue}px`
3890
+ ].join(', ');
3891
+ const backgroundImage = [
3892
+ 'linear-gradient(to right, rgba(51, 65, 85, 0.24) 0 1px, transparent 1px)',
3893
+ 'linear-gradient(to bottom, rgba(51, 65, 85, 0.24) 0 1px, transparent 1px)',
3894
+ 'linear-gradient(to right, rgba(15, 23, 42, 0.42) 0 1px, transparent 1px)',
3895
+ 'linear-gradient(to bottom, rgba(15, 23, 42, 0.42) 0 1px, transparent 1px)'
3896
+ ].join(', ');
3897
+ const motionKey = `motion-line|${smallStepValue}|${majorStepValue}|${safeOpacity.toFixed(3)}`;
3898
+
3899
+ if (motionKey === this._lastLocalSnapGridKey) return false;
3900
+
3901
+ this._lastLocalSnapGridKey = motionKey;
3902
+ this._lastLocalSnapGridPatternPeriodPx = safeMajorStep;
3903
+ this._localSnapGridRasterPeriodPx = safeMajorStep;
3904
+ this._lastLocalSnapGridBackgroundSizeKey = backgroundSizeValue;
3905
+ this._lastLocalSnapGridOpacity = safeOpacity.toFixed(3);
3906
+ this._lastLocalSnapGridTransformKey = '';
3907
+ if (layer.style.backgroundImage !== backgroundImage) layer.style.backgroundImage = backgroundImage;
3908
+ if (layer.style.backgroundSize !== backgroundSizeValue) layer.style.backgroundSize = backgroundSizeValue;
3909
+ if (layer.style.opacity !== this._lastLocalSnapGridOpacity) {
3910
+ layer.style.opacity = this._lastLocalSnapGridOpacity;
3911
+ }
3912
+ return true;
3913
+ }
3914
+
3862
3915
  _ensureLocalSnapGridBaseStyle(layer = null) {
3863
3916
  const targetLayer = layer || this._ensureLocalSnapGridLayer();
3864
3917
  if (!targetLayer) {
@@ -3973,16 +4026,6 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
3973
4026
  return false;
3974
4027
  }
3975
4028
 
3976
- if (this.isZooming === true &&
3977
- !!this._lastLocalSnapGridKey &&
3978
- this._getLocalSnapGridStyleKind() !== 'dot') {
3979
- this._syncLocalSnapGridSpotlightPosition(width, height, left, top);
3980
- this._lastLocalSnapGridPositionRevision = pointerRevision;
3981
- this._localSnapGridStyleDeferredForZoom = true;
3982
- this._setLocalSnapGridVisible(true, opacity.toFixed(3));
3983
- return true;
3984
- }
3985
-
3986
4029
  const vfov = (Number(camera.fov || 45) * Math.PI) / 180;
3987
4030
  const viewHeight = 2 * Math.tan(vfov / 2) * cameraZ;
3988
4031
  const pxPerWorld = height / Math.max(1, viewHeight);
@@ -4007,6 +4050,12 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4007
4050
  opacity
4008
4051
  );
4009
4052
  } else if (shouldDeferLocalGridStyleForZoom === true) {
4053
+ this._applyLocalSnapGridMotionStyle(
4054
+ layer,
4055
+ smallStepPx,
4056
+ majorStepPx,
4057
+ opacity
4058
+ );
4010
4059
  this._localSnapGridStyleDeferredForZoom = true;
4011
4060
  }
4012
4061
 
@@ -4062,13 +4111,17 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4062
4111
  this._applyLocalSnapGridStyle(
4063
4112
  layer, styleKind, projection.smallStepPx, projection.majorStepPx, opacity);
4064
4113
  this._localSnapGridStyleDeferredForZoom = false;
4114
+ } else if (this.isZooming === true) {
4115
+ const vfov = (Number(this.camera?.fov || 45) * Math.PI) / 180;
4116
+ const pxPerWorld = height / Math.max(1, 2 * Math.tan(vfov / 2) * cameraZ);
4117
+ const projection = this._resolveLocalSnapGridWorldStep(pxPerWorld);
4118
+ this._applyLocalSnapGridMotionStyle(
4119
+ layer, projection.smallStepPx, projection.majorStepPx, opacity);
4120
+ this._localSnapGridStyleDeferredForZoom = true;
4065
4121
  }
4066
4122
  this._syncLocalSnapGridSpotlightPosition(width, height, left, top);
4067
4123
  this._lastLocalSnapGridPositionRevision = pointerRevision;
4068
4124
 
4069
- if (this.isZooming === true && styleKind !== 'dot') {
4070
- this._localSnapGridStyleDeferredForZoom = true;
4071
- }
4072
4125
  this._setLocalSnapGridVisible(true, opacity.toFixed(3));
4073
4126
  return true;
4074
4127
  }
@@ -9905,7 +9958,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
9905
9958
 
9906
9959
  // Detach current board (save to JS cache)
9907
9960
  if (boardToDetach) {
9908
- _detachCurrentBoard(boardToDetach);
9961
+ _detachCurrentBoard(boardToDetach, targetBoardId);
9909
9962
  }
9910
9963
  moduleInstance.setCanvasPhase?.(CanvasPhase.BoardSwitching, 'swapBoard', {
9911
9964
  fromBoardId: boardToDetach || null,