noteconnection 1.7.0 → 1.8.0

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 (211) hide show
  1. package/LICENSE +674 -21
  2. package/README.md +242 -62
  3. package/dist/src/agent_workspace.contract.parity.test.js +475 -0
  4. package/dist/src/agent_workspace.frontend.test.js +9989 -0
  5. package/dist/src/agent_workspace.locale.contract.test.js +95 -0
  6. package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
  7. package/dist/src/copy.assets.contract.test.js +130 -0
  8. package/dist/src/core/PathBridge.js +28 -3
  9. package/dist/src/export/WorkspaceExportBundle.js +1130 -0
  10. package/dist/src/export/WorkspaceExportBundle.test.js +1480 -0
  11. package/dist/src/export/index.js +18 -0
  12. package/dist/src/export/types.js +2 -0
  13. package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
  14. package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
  15. package/dist/src/foundation.release.evidence.contract.test.js +406 -0
  16. package/dist/src/foundation.sqlite.runtime.contract.test.js +82 -0
  17. package/dist/src/frontend/README.md +242 -62
  18. package/dist/src/frontend/agent_workspace.js +5353 -0
  19. package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
  20. package/dist/src/frontend/analysis.js +31 -4
  21. package/dist/src/frontend/app.js +2178 -62
  22. package/dist/src/frontend/focus_mode_interactions.js +33 -0
  23. package/dist/src/frontend/godot_future_path_renderer.js +540 -0
  24. package/dist/src/frontend/godot_tree_interactions.js +178 -0
  25. package/dist/src/frontend/graph_state.mjs +105 -0
  26. package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
  27. package/dist/src/frontend/i18n.mjs +186 -0
  28. package/dist/src/frontend/index.html +641 -266
  29. package/dist/src/frontend/layout_gpu.js +12 -4
  30. package/dist/src/frontend/locales/en.json +721 -5
  31. package/dist/src/frontend/locales/zh.json +721 -5
  32. package/dist/src/frontend/main.mjs +60 -0
  33. package/dist/src/frontend/markdown_runtime.js +827 -0
  34. package/dist/src/frontend/notemd.css +49 -0
  35. package/dist/src/frontend/notemd.html +7 -1
  36. package/dist/src/frontend/notemd.js +64 -0
  37. package/dist/src/frontend/path.html +107 -0
  38. package/dist/src/frontend/path_app.js +2108 -130
  39. package/dist/src/frontend/path_layout.mjs +143 -0
  40. package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
  41. package/dist/src/frontend/path_modules_bridge.js +486 -0
  42. package/dist/src/frontend/path_state.mjs +118 -0
  43. package/dist/src/frontend/path_styles.css +146 -0
  44. package/dist/src/frontend/path_worker_bridge.mjs +85 -0
  45. package/dist/src/frontend/reader.js +393 -33
  46. package/dist/src/frontend/runtime_bridge.js +67 -54
  47. package/dist/src/frontend/runtime_bridge.mjs +279 -0
  48. package/dist/src/frontend/settings.js +130 -12
  49. package/dist/src/frontend/source_manager.js +190 -21
  50. package/dist/src/frontend/styles.css +2720 -72
  51. package/dist/src/frontend/workbench_state.mjs +101 -0
  52. package/dist/src/frontend/workspace_panes.js +10168 -0
  53. package/dist/src/frontend.locale.contract.test.js +62 -0
  54. package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
  55. package/dist/src/indexing/IndexLifecycle.js +195 -0
  56. package/dist/src/indexing/IndexLifecycle.test.js +49 -0
  57. package/dist/src/indexing/SegmentBuilder.js +64 -0
  58. package/dist/src/indexing/UnitBuilder.js +48 -0
  59. package/dist/src/indexing/types.js +2 -0
  60. package/dist/src/knowledge.api.contract.test.js +170 -0
  61. package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
  62. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
  64. package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
  65. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
  67. package/dist/src/learning/answerReleaseReview.js +4319 -0
  68. package/dist/src/learning/answerReleaseReview.test.js +2888 -0
  69. package/dist/src/learning/api.js +2 -0
  70. package/dist/src/learning/conversationComposer.js +1480 -0
  71. package/dist/src/learning/conversationComposer.test.js +1817 -0
  72. package/dist/src/learning/domains/ConversationManager.js +53 -0
  73. package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
  74. package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
  75. package/dist/src/learning/domains/MasteryEngine.js +387 -0
  76. package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
  77. package/dist/src/learning/domains/QualityEvaluator.js +307 -0
  78. package/dist/src/learning/domains/TutorRouter.js +313 -0
  79. package/dist/src/learning/domains/index.js +33 -0
  80. package/dist/src/learning/domains/types.js +7 -0
  81. package/dist/src/learning/errors.js +29 -0
  82. package/dist/src/learning/evidenceContextAssembler.js +1176 -0
  83. package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
  84. package/dist/src/learning/graphContextAssembler.js +870 -0
  85. package/dist/src/learning/graphContextAssembler.test.js +1033 -0
  86. package/dist/src/learning/index.js +28 -0
  87. package/dist/src/learning/queryBackend.js +1898 -0
  88. package/dist/src/learning/queryBackend.test.js +955 -0
  89. package/dist/src/learning/ragContextPack.js +257 -0
  90. package/dist/src/learning/ragContextPack.test.js +160 -0
  91. package/dist/src/learning/ragPublicText.js +38 -0
  92. package/dist/src/learning/ragSufficiencyJudge.js +161 -0
  93. package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
  94. package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
  95. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
  96. package/dist/src/learning/requestNormalization.js +198 -0
  97. package/dist/src/learning/runtimeCapability.js +4677 -0
  98. package/dist/src/learning/runtimeCapability.test.js +3635 -0
  99. package/dist/src/learning/store.js +1240 -0
  100. package/dist/src/learning/store.test.js +1126 -0
  101. package/dist/src/learning/tutorAdapter.js +2 -0
  102. package/dist/src/learning/types.js +2 -0
  103. package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
  104. package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
  105. package/dist/src/lfs.asset.policy.contract.test.js +153 -0
  106. package/dist/src/license.policy.contract.test.js +66 -0
  107. package/dist/src/memory/MemoryGovernance.js +74 -0
  108. package/dist/src/memory/MemoryGovernance.test.js +46 -0
  109. package/dist/src/memory/types.js +2 -0
  110. package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
  111. package/dist/src/middleware/auth.js +17 -0
  112. package/dist/src/middleware/body-parser.js +45 -0
  113. package/dist/src/middleware/cors.js +44 -0
  114. package/dist/src/middleware/index.js +21 -0
  115. package/dist/src/middleware/request-trace.js +96 -0
  116. package/dist/src/notemd/AppConfigToml.js +6 -4
  117. package/dist/src/notemd/MermaidProcessor.js +400 -50
  118. package/dist/src/notemd/NotemdService.js +498 -13
  119. package/dist/src/notemd/PromptManager.js +15 -0
  120. package/dist/src/notemd/cli/commands.js +357 -0
  121. package/dist/src/notemd/cli/dispatcher.js +225 -0
  122. package/dist/src/notemd/cli/index.js +169 -0
  123. package/dist/src/notemd/cli/parser.js +68 -0
  124. package/dist/src/notemd/cli/types.js +2 -0
  125. package/dist/src/notemd/constants.js +43 -0
  126. package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
  127. package/dist/src/notemd/diagram/diagramSpec.js +79 -0
  128. package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
  129. package/dist/src/notemd/diagram/intent.js +95 -0
  130. package/dist/src/notemd/diagram/planner.js +71 -0
  131. package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
  132. package/dist/src/notemd/diagram/types.js +18 -0
  133. package/dist/src/notemd/index.js +26 -0
  134. package/dist/src/notemd/operations/capabilityManifest.js +23 -0
  135. package/dist/src/notemd/operations/cliContracts.js +17 -0
  136. package/dist/src/notemd/operations/configProfileCommands.js +85 -0
  137. package/dist/src/notemd/operations/registry.contract.test.js +95 -0
  138. package/dist/src/notemd/operations/registry.js +991 -0
  139. package/dist/src/notemd/operations/types.js +2 -0
  140. package/dist/src/notemd/providerDiagnostics.js +220 -0
  141. package/dist/src/notemd/providerProfiles.js +42 -0
  142. package/dist/src/notemd/providerTemplates.js +231 -0
  143. package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
  144. package/dist/src/notemd/search/SearchManager.js +13 -0
  145. package/dist/src/notemd/search/SearchProvider.js +2 -0
  146. package/dist/src/notemd/search/TavilyProvider.js +44 -0
  147. package/dist/src/notemd.agent.manifest.test.js +85 -0
  148. package/dist/src/notemd.api.contract.test.js +14 -1
  149. package/dist/src/notemd.app_config_toml.test.js +2 -1
  150. package/dist/src/notemd.batch.workflow.test.js +117 -0
  151. package/dist/src/notemd.cli.e2e.test.js +136 -0
  152. package/dist/src/notemd.core.test.js +51 -0
  153. package/dist/src/notemd.diagram.pipeline.test.js +233 -0
  154. package/dist/src/notemd.providerTemplates.test.js +34 -0
  155. package/dist/src/notemd.server.integration.test.js +143 -35
  156. package/dist/src/notemd.workflow.pipeline.test.js +162 -0
  157. package/dist/src/pathbridge.handshake.contract.test.js +16 -2
  158. package/dist/src/pathmode.background.contract.test.js +69 -0
  159. package/dist/src/pathmode.settings.api.contract.test.js +9 -0
  160. package/dist/src/pkg.sidecar.contract.test.js +9 -3
  161. package/dist/src/platform/ExportProfile.js +58 -0
  162. package/dist/src/platform/PlatformCapabilities.js +45 -0
  163. package/dist/src/platform/PlatformCapabilities.test.js +30 -0
  164. package/dist/src/platform/RenderMaterializer.js +33 -0
  165. package/dist/src/platform/RenderMaterializer.test.js +32 -0
  166. package/dist/src/query_backend.external_http.integration.test.js +410 -0
  167. package/dist/src/reader_renderer.js +404 -3
  168. package/dist/src/reader_renderer.test.js +87 -0
  169. package/dist/src/release.godot.mirror.contract.test.js +73 -0
  170. package/dist/src/resources/ResourceRegistry.js +223 -0
  171. package/dist/src/resources/ResourceRegistry.test.js +61 -0
  172. package/dist/src/resources/types.js +2 -0
  173. package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
  174. package/dist/src/routes/data.js +267 -0
  175. package/dist/src/routes/diagnostics.js +51 -0
  176. package/dist/src/routes/index.js +23 -0
  177. package/dist/src/routes/knowledge.js +968 -0
  178. package/dist/src/routes/markdown.js +287 -0
  179. package/dist/src/routes/notemd.js +565 -0
  180. package/dist/src/routes/registry.contract.test.js +130 -0
  181. package/dist/src/routes/render.js +285 -0
  182. package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
  183. package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
  184. package/dist/src/routes/settings.js +6 -0
  185. package/dist/src/routes/staticFiles.js +94 -0
  186. package/dist/src/routes/types.js +2 -0
  187. package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
  188. package/dist/src/server.js +11090 -1549
  189. package/dist/src/server.migration.test.js +193 -21
  190. package/dist/src/server.port.fallback.contract.test.js +63 -0
  191. package/dist/src/session/SessionStateStore.js +81 -0
  192. package/dist/src/session/SessionStateStore.test.js +58 -0
  193. package/dist/src/session/types.js +2 -0
  194. package/dist/src/settings.runtime.contract.test.js +50 -0
  195. package/dist/src/shared/types.contract.test.js +107 -0
  196. package/dist/src/shared/types.js +22 -0
  197. package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
  198. package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
  199. package/dist/src/source_manager.loadflow.test.js +46 -0
  200. package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
  201. package/dist/src/tauri.frontend.build.contract.test.js +60 -0
  202. package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
  203. package/dist/src/utils/RuntimePaths.js +4 -13
  204. package/dist/src/utils/platform.js +153 -0
  205. package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
  206. package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
  207. package/dist/src/workflows/types.js +2 -0
  208. package/dist/src/workspace/WorkspaceRegistry.js +122 -0
  209. package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
  210. package/dist/src/workspace/types.js +2 -0
  211. package/package.json +53 -10
@@ -27,6 +27,385 @@ function readStartupPerfProfileOverride() {
27
27
  }
28
28
  }
29
29
 
30
+ const mermaidErrorGuardState = {
31
+ observer: null,
32
+ toastHost: null,
33
+ seenAtBySignature: new Map(),
34
+ renderGuardInstalled: false,
35
+ periodicSweepHandle: null,
36
+ suppressedArtifacts: [],
37
+ };
38
+
39
+ function isMermaidErrorArtifactText(text) {
40
+ const normalized = String(text || '').toLowerCase();
41
+ if (!normalized) {
42
+ return false;
43
+ }
44
+ return (
45
+ normalized.includes('syntax error in text') ||
46
+ normalized.includes('lexical error on line') ||
47
+ normalized.includes('parse error on line') ||
48
+ normalized.includes('mermaid version') ||
49
+ normalized.includes('diagram syntax error')
50
+ );
51
+ }
52
+
53
+ function normalizeMermaidErrorArtifactText(text) {
54
+ return String(text || '')
55
+ .replace(/\s+/g, ' ')
56
+ .trim()
57
+ .slice(0, 240);
58
+ }
59
+
60
+ function registerMermaidSuppressedArtifact(detail, context = {}) {
61
+ const signature = normalizeMermaidErrorArtifactText(detail);
62
+ if (!signature) {
63
+ return;
64
+ }
65
+ const entry = {
66
+ signature,
67
+ detectedAt: new Date().toISOString(),
68
+ context,
69
+ };
70
+ mermaidErrorGuardState.suppressedArtifacts.push(entry);
71
+ if (mermaidErrorGuardState.suppressedArtifacts.length > 20) {
72
+ mermaidErrorGuardState.suppressedArtifacts.shift();
73
+ }
74
+ }
75
+
76
+ function ensureMermaidErrorToastHost() {
77
+ if (mermaidErrorGuardState.toastHost && mermaidErrorGuardState.toastHost.isConnected) {
78
+ return mermaidErrorGuardState.toastHost;
79
+ }
80
+ const host = document.createElement('div');
81
+ host.id = 'mermaid-error-toast-stack';
82
+ host.className = 'mermaid-error-toast-stack';
83
+ document.body.appendChild(host);
84
+ mermaidErrorGuardState.toastHost = host;
85
+ return host;
86
+ }
87
+
88
+ function emitMermaidErrorToast(detail) {
89
+ const signature = normalizeMermaidErrorArtifactText(detail);
90
+ if (!signature) {
91
+ return;
92
+ }
93
+ const now = Date.now();
94
+ const lastSeenAt = mermaidErrorGuardState.seenAtBySignature.get(signature) || 0;
95
+ if ((now - lastSeenAt) < 8000) {
96
+ return;
97
+ }
98
+ mermaidErrorGuardState.seenAtBySignature.set(signature, now);
99
+
100
+ const host = ensureMermaidErrorToastHost();
101
+ const toast = document.createElement('div');
102
+ toast.className = 'mermaid-error-toast';
103
+
104
+ const title = document.createElement('div');
105
+ title.className = 'mermaid-error-toast-title';
106
+ title.textContent = 'Mermaid diagram skipped';
107
+ toast.appendChild(title);
108
+
109
+ const summary = document.createElement('div');
110
+ summary.className = 'mermaid-error-toast-summary';
111
+ summary.textContent = 'A malformed Mermaid block was suppressed to keep the workspace readable.';
112
+ toast.appendChild(summary);
113
+
114
+ const detailLine = document.createElement('div');
115
+ detailLine.className = 'mermaid-error-toast-detail';
116
+ detailLine.textContent = signature;
117
+ toast.appendChild(detailLine);
118
+
119
+ host.appendChild(toast);
120
+ while (host.childElementCount > 3) {
121
+ host.removeChild(host.firstElementChild);
122
+ }
123
+
124
+ window.setTimeout(() => {
125
+ if (toast.parentNode) {
126
+ toast.parentNode.removeChild(toast);
127
+ }
128
+ }, 6500);
129
+ }
130
+
131
+ function createInlineMermaidErrorNotice(detail) {
132
+ const wrapper = document.createElement('div');
133
+ wrapper.className = 'mermaid-inline-guard';
134
+
135
+ const title = document.createElement('div');
136
+ title.className = 'mermaid-inline-guard-title';
137
+ title.textContent = 'Mermaid diagram unavailable';
138
+ wrapper.appendChild(title);
139
+
140
+ const summary = document.createElement('div');
141
+ summary.className = 'mermaid-inline-guard-summary';
142
+ summary.textContent = 'Malformed Mermaid content was suppressed in this view.';
143
+ wrapper.appendChild(summary);
144
+
145
+ if (detail) {
146
+ const detailLine = document.createElement('div');
147
+ detailLine.className = 'mermaid-inline-guard-detail';
148
+ detailLine.textContent = normalizeMermaidErrorArtifactText(detail);
149
+ wrapper.appendChild(detailLine);
150
+ }
151
+
152
+ return wrapper;
153
+ }
154
+
155
+ function resolveMermaidErrorArtifactHost(node) {
156
+ if (!node || typeof node.closest !== 'function') {
157
+ return node;
158
+ }
159
+ return (
160
+ node.closest('.mermaid-render-host-offscreen') ||
161
+ node.closest('.mermaid-render-failed') ||
162
+ node.closest('.mermaid') ||
163
+ node.closest('.reader-block') ||
164
+ node.closest('svg') ||
165
+ node
166
+ );
167
+ }
168
+
169
+ function isProtectedMermaidSuppressionHost(host) {
170
+ if (!host || host.nodeType !== Node.ELEMENT_NODE) {
171
+ return true;
172
+ }
173
+ return (
174
+ host === document.body ||
175
+ host === document.documentElement ||
176
+ host === document.head ||
177
+ host.id === 'graph-wrapper' ||
178
+ host.id === 'path-container' ||
179
+ host.id === 'reading-window' ||
180
+ host.id === 'reading-content-box' ||
181
+ host.id === 'reading-body'
182
+ );
183
+ }
184
+
185
+ function collectMermaidErrorArtifactCandidates(root) {
186
+ const hosts = [];
187
+ const hostSet = new Set();
188
+ const candidates = [];
189
+
190
+ if (!root) {
191
+ return hosts;
192
+ }
193
+ if (
194
+ root.nodeType === Node.ELEMENT_NODE &&
195
+ root !== document.body &&
196
+ root !== document.documentElement &&
197
+ root !== document.head
198
+ ) {
199
+ candidates.push(root);
200
+ }
201
+
202
+ const queryRoot = root.nodeType === Node.ELEMENT_NODE ? root : document.body;
203
+ if (queryRoot && typeof queryRoot.querySelectorAll === 'function') {
204
+ queryRoot.querySelectorAll('svg, .mermaid, div, section, article, aside, img, foreignObject').forEach((node) => candidates.push(node));
205
+ }
206
+
207
+ candidates.forEach((candidate) => {
208
+ if (!candidate || candidate.nodeType !== Node.ELEMENT_NODE) {
209
+ return;
210
+ }
211
+ const element = candidate;
212
+ const text = normalizeMermaidErrorArtifactText(
213
+ element.textContent ||
214
+ element.getAttribute?.('alt') ||
215
+ element.getAttribute?.('aria-label') ||
216
+ ''
217
+ );
218
+ if (!isMermaidErrorArtifactText(text)) {
219
+ return;
220
+ }
221
+ const host = resolveMermaidErrorArtifactHost(element);
222
+ if (!host || hostSet.has(host) || isProtectedMermaidSuppressionHost(host)) {
223
+ return;
224
+ }
225
+ hostSet.add(host);
226
+ hosts.push(host);
227
+ });
228
+
229
+ return hosts;
230
+ }
231
+
232
+ function suppressMermaidErrorArtifacts(root, context = {}) {
233
+ if (!root) {
234
+ return;
235
+ }
236
+
237
+ const candidates = collectMermaidErrorArtifactCandidates(root);
238
+ candidates.forEach((candidate) => {
239
+ if (!candidate || candidate.dataset?.mermaidErrorSuppressed === '1') {
240
+ return;
241
+ }
242
+ const text = normalizeMermaidErrorArtifactText(candidate.textContent || '');
243
+ if (!isMermaidErrorArtifactText(text)) {
244
+ return;
245
+ }
246
+
247
+ const host = resolveMermaidErrorArtifactHost(candidate);
248
+ if (!host || host.dataset?.mermaidErrorSuppressed === '1') {
249
+ return;
250
+ }
251
+ if (host.dataset) {
252
+ host.dataset.mermaidErrorSuppressed = '1';
253
+ }
254
+ registerMermaidSuppressedArtifact(text, {
255
+ hostTag: host.tagName || '',
256
+ hostId: host.id || '',
257
+ hostClass: host.className || '',
258
+ ...context,
259
+ });
260
+
261
+ const shouldKeepInline = Boolean(
262
+ host.closest('#reading-window') ||
263
+ host.closest('#reading-body') ||
264
+ host.closest('.notemd-embed-shell')
265
+ );
266
+
267
+ if (shouldKeepInline) {
268
+ const inlineNotice = createInlineMermaidErrorNotice(text);
269
+ if (host.parentNode) {
270
+ host.parentNode.replaceChild(inlineNotice, host);
271
+ }
272
+ } else {
273
+ emitMermaidErrorToast(text);
274
+ if (host.parentNode) {
275
+ host.parentNode.removeChild(host);
276
+ } else {
277
+ host.style.display = 'none';
278
+ }
279
+ }
280
+ });
281
+ }
282
+
283
+ function installMermaidRuntimeGuards() {
284
+ const mermaid = window.mermaid;
285
+ if (!mermaid || mermaidErrorGuardState.renderGuardInstalled === true || mermaid.__noteConnectionErrorGuardInstalled === true) {
286
+ return;
287
+ }
288
+
289
+ if (typeof mermaid.render === 'function') {
290
+ const originalRender = mermaid.render.bind(mermaid);
291
+ mermaid.render = async function(...args) {
292
+ const result = await originalRender(...args);
293
+ if (result && typeof result.svg === 'string' && isMermaidErrorArtifactText(result.svg)) {
294
+ registerMermaidSuppressedArtifact(result.svg, {
295
+ source: 'mermaid.render',
296
+ });
297
+ throw new Error('Suppressed Mermaid error SVG before it could be mounted.');
298
+ }
299
+ return result;
300
+ };
301
+ }
302
+
303
+ if (typeof mermaid.run === 'function') {
304
+ const originalRun = mermaid.run.bind(mermaid);
305
+ mermaid.run = async function(options) {
306
+ const result = await originalRun(options);
307
+ try {
308
+ if (options && Array.isArray(options.nodes) && options.nodes.length > 0) {
309
+ options.nodes.forEach((node) => suppressMermaidErrorArtifacts(node, {
310
+ source: 'mermaid.run.nodes',
311
+ }));
312
+ } else {
313
+ suppressMermaidErrorArtifacts(document.body, {
314
+ source: 'mermaid.run.document',
315
+ });
316
+ }
317
+ } catch (_error) {
318
+ // Guard should never break main rendering.
319
+ }
320
+ return result;
321
+ };
322
+ }
323
+
324
+ mermaid.__noteConnectionErrorGuardInstalled = true;
325
+ mermaidErrorGuardState.renderGuardInstalled = true;
326
+ }
327
+
328
+ function exposeMermaidDebugInterface() {
329
+ const existing = (window.__NC_DEBUG__ && typeof window.__NC_DEBUG__ === 'object')
330
+ ? window.__NC_DEBUG__
331
+ : {};
332
+ window.__NC_DEBUG__ = {
333
+ ...existing,
334
+ scanMermaidErrorArtifacts: () => collectMermaidErrorArtifactCandidates(document.body).map((host) => ({
335
+ tag: host.tagName || '',
336
+ id: host.id || '',
337
+ className: host.className || '',
338
+ text: normalizeMermaidErrorArtifactText(host.textContent || ''),
339
+ })),
340
+ suppressMermaidErrorArtifactsNow: () => {
341
+ suppressMermaidErrorArtifacts(document.body, { source: 'manual-debug-scan' });
342
+ return mermaidErrorGuardState.suppressedArtifacts.slice();
343
+ },
344
+ getMermaidGuardState: () => ({
345
+ renderGuardInstalled: mermaidErrorGuardState.renderGuardInstalled,
346
+ suppressedArtifacts: mermaidErrorGuardState.suppressedArtifacts.slice(),
347
+ }),
348
+ captureRuntimeState: () => ({
349
+ url: window.location.href,
350
+ title: document.title,
351
+ mermaidGuardState: {
352
+ renderGuardInstalled: mermaidErrorGuardState.renderGuardInstalled,
353
+ suppressedArtifacts: mermaidErrorGuardState.suppressedArtifacts.slice(),
354
+ },
355
+ activeMermaidErrors: collectMermaidErrorArtifactCandidates(document.body).map((host) => ({
356
+ tag: host.tagName || '',
357
+ id: host.id || '',
358
+ className: host.className || '',
359
+ text: normalizeMermaidErrorArtifactText(host.textContent || ''),
360
+ })),
361
+ }),
362
+ };
363
+ }
364
+
365
+ function installMermaidErrorArtifactObserver() {
366
+ installMermaidRuntimeGuards();
367
+ exposeMermaidDebugInterface();
368
+
369
+ if (mermaidErrorGuardState.observer || typeof MutationObserver === 'undefined') {
370
+ suppressMermaidErrorArtifacts(document.body, { source: 'observer-reuse' });
371
+ return;
372
+ }
373
+
374
+ mermaidErrorGuardState.observer = new MutationObserver((mutations) => {
375
+ mutations.forEach((mutation) => {
376
+ mutation.addedNodes.forEach((node) => {
377
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) {
378
+ return;
379
+ }
380
+ suppressMermaidErrorArtifacts(node, { source: 'mutation-observer' });
381
+ });
382
+ });
383
+ });
384
+
385
+ mermaidErrorGuardState.observer.observe(document.body, {
386
+ childList: true,
387
+ subtree: true,
388
+ });
389
+
390
+ suppressMermaidErrorArtifacts(document.body, { source: 'observer-install' });
391
+ window.setTimeout(() => {
392
+ installMermaidRuntimeGuards();
393
+ suppressMermaidErrorArtifacts(document.body, { source: 'delayed-sweep-1200' });
394
+ }, 1200);
395
+ window.setTimeout(() => {
396
+ installMermaidRuntimeGuards();
397
+ suppressMermaidErrorArtifacts(document.body, { source: 'delayed-sweep-3200' });
398
+ }, 3200);
399
+ if (mermaidErrorGuardState.periodicSweepHandle === null) {
400
+ mermaidErrorGuardState.periodicSweepHandle = window.setInterval(() => {
401
+ installMermaidRuntimeGuards();
402
+ suppressMermaidErrorArtifacts(document.body, { source: 'periodic-sweep' });
403
+ }, 750);
404
+ }
405
+ }
406
+
407
+ installMermaidErrorArtifactObserver();
408
+
30
409
  function resolveRuntimePlatform(runtimeCaps) {
31
410
  const rawPlatform = runtimeCaps && typeof runtimeCaps.platform === 'string'
32
411
  ? runtimeCaps.platform.trim().toLowerCase()
@@ -285,7 +664,9 @@ const startupLayoutSnapshotState = {
285
664
  pendingRecord: null,
286
665
  restorePromise: null,
287
666
  saveHandle: null,
288
- lastSaveAtMs: 0
667
+ lastSaveAtMs: 0,
668
+ sourceLayoutSummary: null,
669
+ sourceLayoutById: null
289
670
  };
290
671
 
291
672
  function hashFNV1a32(input) {
@@ -332,6 +713,177 @@ function buildStartupGraphFingerprint(nodeList, linkList, graphPayload) {
332
713
  return `nc-layout-v${STARTUP_LAYOUT_SNAPSHOT_VERSION}-${hashFNV1a32(signature)}`;
333
714
  }
334
715
 
716
+ function summarizeLayoutPositions(positionItems) {
717
+ const safeItems = Array.isArray(positionItems) ? positionItems : [];
718
+ const finitePositions = safeItems.filter((item) => (
719
+ item &&
720
+ Number.isFinite(Number(item.x)) &&
721
+ Number.isFinite(Number(item.y))
722
+ ));
723
+
724
+ if (finitePositions.length === 0) {
725
+ return {
726
+ finiteCount: 0,
727
+ minX: 0,
728
+ maxX: 0,
729
+ minY: 0,
730
+ maxY: 0,
731
+ spanX: 0,
732
+ spanY: 0,
733
+ uniqueRatio: 0
734
+ };
735
+ }
736
+
737
+ let minX = Infinity;
738
+ let maxX = -Infinity;
739
+ let minY = Infinity;
740
+ let maxY = -Infinity;
741
+ const uniqueBuckets = new Set();
742
+
743
+ for (let index = 0; index < finitePositions.length; index += 1) {
744
+ const item = finitePositions[index];
745
+ const x = Number(item.x);
746
+ const y = Number(item.y);
747
+ if (x < minX) minX = x;
748
+ if (x > maxX) maxX = x;
749
+ if (y < minY) minY = y;
750
+ if (y > maxY) maxY = y;
751
+ uniqueBuckets.add(`${Math.round(x)}:${Math.round(y)}`);
752
+ }
753
+
754
+ return {
755
+ finiteCount: finitePositions.length,
756
+ minX,
757
+ maxX,
758
+ minY,
759
+ maxY,
760
+ spanX: maxX - minX,
761
+ spanY: maxY - minY,
762
+ uniqueRatio: uniqueBuckets.size / Math.max(1, finitePositions.length)
763
+ };
764
+ }
765
+
766
+ function isDegenerateLayoutSummary(summary) {
767
+ return Boolean(
768
+ summary &&
769
+ summary.finiteCount > 0 &&
770
+ ((summary.spanX < 48 && summary.spanY < 48) || summary.uniqueRatio < 0.12)
771
+ );
772
+ }
773
+
774
+ function isSnapshotLayoutCollapsedVsSource(summary, sourceSummary) {
775
+ if (!summary || !sourceSummary || sourceSummary.finiteCount < 10) {
776
+ return false;
777
+ }
778
+
779
+ const sourceWideX = sourceSummary.spanX >= 320;
780
+ const sourceWideY = sourceSummary.spanY >= 320;
781
+ if (!sourceWideX && !sourceWideY) {
782
+ return false;
783
+ }
784
+
785
+ const spanXTooSmall = sourceWideX && summary.spanX < Math.max(72, sourceSummary.spanX * 0.08);
786
+ const spanYTooSmall = sourceWideY && summary.spanY < Math.max(72, sourceSummary.spanY * 0.08);
787
+ return spanXTooSmall && spanYTooSmall;
788
+ }
789
+
790
+ function buildSourceLayoutById(nodeList) {
791
+ const map = new Map();
792
+ const safeNodes = Array.isArray(nodeList) ? nodeList : [];
793
+ for (let index = 0; index < safeNodes.length; index += 1) {
794
+ const node = safeNodes[index];
795
+ if (!node || node.id === undefined || node.id === null) {
796
+ continue;
797
+ }
798
+ const x = Number(node.x);
799
+ const y = Number(node.y);
800
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
801
+ continue;
802
+ }
803
+ map.set(node.id, { x, y });
804
+ }
805
+ return map;
806
+ }
807
+
808
+ function restoreSourceLayoutOrJitterNodes(nodeList, viewportWidth, viewportHeight, reason = '') {
809
+ const safeNodes = Array.isArray(nodeList) ? nodeList : [];
810
+ if (safeNodes.length === 0) {
811
+ return false;
812
+ }
813
+
814
+ const currentSummary = summarizeLayoutPositions(safeNodes);
815
+ const sourceSummary = startupLayoutSnapshotState.sourceLayoutSummary;
816
+ const sourceLayoutById = startupLayoutSnapshotState.sourceLayoutById;
817
+ const shouldRestoreSource = Boolean(
818
+ sourceLayoutById instanceof Map &&
819
+ sourceLayoutById.size > 0 &&
820
+ isSnapshotLayoutCollapsedVsSource(currentSummary, sourceSummary)
821
+ );
822
+
823
+ if (!isDegenerateLayoutSummary(currentSummary) && !shouldRestoreSource) {
824
+ return false;
825
+ }
826
+
827
+ if (shouldRestoreSource) {
828
+ let restoredCount = 0;
829
+ for (let index = 0; index < safeNodes.length; index += 1) {
830
+ const node = safeNodes[index];
831
+ const sourcePos = sourceLayoutById.get(node.id);
832
+ if (!sourcePos) {
833
+ continue;
834
+ }
835
+ node.x = sourcePos.x;
836
+ node.y = sourcePos.y;
837
+ node.fx = null;
838
+ node.fy = null;
839
+ if (currentPositions && typeof currentPositions.set === 'function') {
840
+ currentPositions.set(node.id, { x: node.x, y: node.y });
841
+ }
842
+ restoredCount += 1;
843
+ }
844
+ console.warn('[Startup Warm Snapshot] Restored source layout because active positions collapsed relative to source.', {
845
+ reason,
846
+ restoredCount,
847
+ currentSpanX: Number(currentSummary.spanX.toFixed(2)),
848
+ currentSpanY: Number(currentSummary.spanY.toFixed(2)),
849
+ sourceSpanX: sourceSummary ? Number(sourceSummary.spanX.toFixed(2)) : null,
850
+ sourceSpanY: sourceSummary ? Number(sourceSummary.spanY.toFixed(2)) : null
851
+ });
852
+ return restoredCount > 0;
853
+ }
854
+
855
+ const centerX = Number.isFinite(Number(viewportWidth)) && Number(viewportWidth) > 0
856
+ ? Number(viewportWidth) / 2
857
+ : 400;
858
+ const centerY = Number.isFinite(Number(viewportHeight)) && Number(viewportHeight) > 0
859
+ ? Number(viewportHeight) / 2
860
+ : 300;
861
+ const goldenAngle = Math.PI * (3 - Math.sqrt(5));
862
+
863
+ for (let index = 0; index < safeNodes.length; index += 1) {
864
+ const node = safeNodes[index];
865
+ const rankOffset = Number.isFinite(Number(node.rank)) ? Number(node.rank) * 4 : 0;
866
+ const radius = 28 + (Math.sqrt(index + 1) * 18) + rankOffset;
867
+ const angle = (index * goldenAngle) + (rankOffset * 0.017);
868
+ node.x = centerX + (Math.cos(angle) * radius);
869
+ node.y = centerY + (Math.sin(angle) * radius);
870
+ node.fx = null;
871
+ node.fy = null;
872
+ if (currentPositions && typeof currentPositions.set === 'function') {
873
+ currentPositions.set(node.id, { x: node.x, y: node.y });
874
+ }
875
+ }
876
+
877
+ console.warn('[Startup Warm Snapshot] Re-seeded degenerate initial layout before simulation bootstrap.', {
878
+ reason,
879
+ nodeCount: safeNodes.length,
880
+ spanX: Number(currentSummary.spanX.toFixed(2)),
881
+ spanY: Number(currentSummary.spanY.toFixed(2)),
882
+ uniqueRatio: Number(currentSummary.uniqueRatio.toFixed(4))
883
+ });
884
+ return true;
885
+ }
886
+
335
887
  function openStartupLayoutSnapshotDb() {
336
888
  return new Promise((resolve, reject) => {
337
889
  if (typeof window === 'undefined' || !window.indexedDB) {
@@ -457,6 +1009,36 @@ async function persistStartupLayoutSnapshotRecord(record) {
457
1009
  db.close();
458
1010
  }
459
1011
 
1012
+ async function deleteStartupLayoutSnapshotRecord(fingerprint) {
1013
+ if (!fingerprint) {
1014
+ return;
1015
+ }
1016
+
1017
+ if (typeof localStorage !== 'undefined') {
1018
+ try {
1019
+ localStorage.removeItem(`${STARTUP_LAYOUT_SNAPSHOT_LS_PREFIX}${fingerprint}`);
1020
+ } catch (_err) {
1021
+ // Ignore localStorage cleanup failures.
1022
+ }
1023
+ }
1024
+
1025
+ const db = await openStartupLayoutSnapshotDb();
1026
+ if (!db) {
1027
+ return;
1028
+ }
1029
+
1030
+ await new Promise((resolve, reject) => {
1031
+ const tx = db.transaction(STARTUP_LAYOUT_SNAPSHOT_STORE_NAME, 'readwrite');
1032
+ const store = tx.objectStore(STARTUP_LAYOUT_SNAPSHOT_STORE_NAME);
1033
+ store.delete(fingerprint);
1034
+ tx.oncomplete = () => resolve();
1035
+ tx.onerror = () => reject(tx.error || new Error('indexedDB delete failed'));
1036
+ tx.onabort = () => reject(tx.error || new Error('indexedDB delete aborted'));
1037
+ });
1038
+
1039
+ db.close();
1040
+ }
1041
+
460
1042
  function collectStartupLayoutSnapshotRecord(reason = '') {
461
1043
  if (!startupLayoutSnapshotState.fingerprint || !Array.isArray(nodes) || nodes.length === 0) {
462
1044
  return null;
@@ -469,8 +1051,8 @@ function collectStartupLayoutSnapshotRecord(reason = '') {
469
1051
  id: n.id,
470
1052
  x: Number.isFinite(Number(n.x)) ? Number(n.x) : 0,
471
1053
  y: Number.isFinite(Number(n.y)) ? Number(n.y) : 0,
472
- fx: Number.isFinite(Number(n.fx)) ? Number(n.fx) : null,
473
- fy: Number.isFinite(Number(n.fy)) ? Number(n.fy) : null
1054
+ fx: null,
1055
+ fy: null
474
1056
  };
475
1057
  }
476
1058
 
@@ -515,6 +1097,37 @@ function validateStartupLayoutSnapshotRecord(record) {
515
1097
  return { ok: false, reason: 'position-coverage-low', coverage: Number(coverage.toFixed(4)) };
516
1098
  }
517
1099
 
1100
+ const positionSummary = summarizeLayoutPositions(record.positions);
1101
+ if (expectedNodeCount >= 10 && positionSummary.finiteCount > 0) {
1102
+ if (isDegenerateLayoutSummary(positionSummary)) {
1103
+ return {
1104
+ ok: false,
1105
+ reason: 'degenerate-layout',
1106
+ purge: true,
1107
+ spanX: Number(positionSummary.spanX.toFixed(2)),
1108
+ spanY: Number(positionSummary.spanY.toFixed(2)),
1109
+ uniqueRatio: Number(positionSummary.uniqueRatio.toFixed(4)),
1110
+ };
1111
+ }
1112
+
1113
+ if (isSnapshotLayoutCollapsedVsSource(positionSummary, startupLayoutSnapshotState.sourceLayoutSummary)) {
1114
+ return {
1115
+ ok: false,
1116
+ reason: 'degenerate-layout-vs-source',
1117
+ purge: true,
1118
+ spanX: Number(positionSummary.spanX.toFixed(2)),
1119
+ spanY: Number(positionSummary.spanY.toFixed(2)),
1120
+ uniqueRatio: Number(positionSummary.uniqueRatio.toFixed(4)),
1121
+ sourceSpanX: startupLayoutSnapshotState.sourceLayoutSummary
1122
+ ? Number(startupLayoutSnapshotState.sourceLayoutSummary.spanX.toFixed(2))
1123
+ : null,
1124
+ sourceSpanY: startupLayoutSnapshotState.sourceLayoutSummary
1125
+ ? Number(startupLayoutSnapshotState.sourceLayoutSummary.spanY.toFixed(2))
1126
+ : null
1127
+ };
1128
+ }
1129
+ }
1130
+
518
1131
  return {
519
1132
  ok: true,
520
1133
  coverage: Number(coverage.toFixed(4)),
@@ -539,8 +1152,8 @@ function applyStartupLayoutSnapshotRecord(record) {
539
1152
  }
540
1153
  n.x = Number.isFinite(saved.x) ? saved.x : n.x;
541
1154
  n.y = Number.isFinite(saved.y) ? saved.y : n.y;
542
- n.fx = Number.isFinite(saved.fx) ? saved.fx : null;
543
- n.fy = Number.isFinite(saved.fy) ? saved.fy : null;
1155
+ n.fx = null;
1156
+ n.fy = null;
544
1157
  if (currentPositions && typeof currentPositions.set === 'function') {
545
1158
  currentPositions.set(n.id, { x: n.x, y: n.y });
546
1159
  }
@@ -568,8 +1181,16 @@ function maybeApplyStartupWarmSnapshot(trigger = '') {
568
1181
  recordFingerprint: record.fingerprint || null,
569
1182
  recordNodeCount: record.nodeCount || 0,
570
1183
  recordEdgeCount: record.edgeCount || 0,
571
- positionCount: Array.isArray(record.positions) ? record.positions.length : 0
1184
+ positionCount: Array.isArray(record.positions) ? record.positions.length : 0,
1185
+ spanX: validation.spanX,
1186
+ spanY: validation.spanY,
1187
+ uniqueRatio: validation.uniqueRatio,
572
1188
  });
1189
+ if (validation.purge === true && record.fingerprint) {
1190
+ deleteStartupLayoutSnapshotRecord(record.fingerprint).catch((error) => {
1191
+ console.warn('[Startup Warm Snapshot] Failed to purge invalid snapshot record:', error && error.message ? error.message : String(error));
1192
+ });
1193
+ }
573
1194
  startupLayoutSnapshotState.pendingRecord = null;
574
1195
  return false;
575
1196
  }
@@ -1094,6 +1715,17 @@ markStartupCheckpoint('T1 graph_preprocessed', {
1094
1715
  preprocessMs: Number(graphPreprocessElapsedMs.toFixed(2))
1095
1716
  });
1096
1717
 
1718
+ startupLayoutSnapshotState.sourceLayoutById = buildSourceLayoutById(nodes);
1719
+ startupLayoutSnapshotState.sourceLayoutSummary = summarizeLayoutPositions(nodes);
1720
+ if (startupLayoutSnapshotState.sourceLayoutSummary.finiteCount > 0) {
1721
+ console.log('[Startup Warm Snapshot] Source layout summary prepared.', {
1722
+ finiteCount: startupLayoutSnapshotState.sourceLayoutSummary.finiteCount,
1723
+ spanX: Number(startupLayoutSnapshotState.sourceLayoutSummary.spanX.toFixed(2)),
1724
+ spanY: Number(startupLayoutSnapshotState.sourceLayoutSummary.spanY.toFixed(2)),
1725
+ uniqueRatio: Number(startupLayoutSnapshotState.sourceLayoutSummary.uniqueRatio.toFixed(4))
1726
+ });
1727
+ }
1728
+
1097
1729
  startupLayoutSnapshotState.fingerprint = buildStartupGraphFingerprint(nodes, links, runtimeGraphData);
1098
1730
  console.log('[Startup Warm Snapshot] Fingerprint prepared.', {
1099
1731
  fingerprint: startupLayoutSnapshotState.fingerprint,
@@ -1491,6 +2123,7 @@ const simulation = {
1491
2123
  const workerPayloadBuildStartTs = (typeof performance !== 'undefined' && typeof performance.now === 'function')
1492
2124
  ? performance.now()
1493
2125
  : Date.now();
2126
+ restoreSourceLayoutOrJitterNodes(nodes, width, height, 'worker-init');
1494
2127
  const workerNodes = new Array(nodes.length);
1495
2128
  for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex += 1) {
1496
2129
  const node = nodes[nodeIndex];
@@ -2480,11 +3113,23 @@ function handleDoubleClick(event, d) {
2480
3113
  // v0.9.20 Enhancement: Auto-clear selection state when entering focus mode
2481
3114
  // v0.9.20 增强:进入专注模式时自动清除选择状态
2482
3115
 
2483
- if (focusNode && focusNode.id === d.id) {
3116
+ const focusInteractions = window.NoteConnectionFocusModeInteractions;
3117
+ const focusAction = focusInteractions && typeof focusInteractions.resolveDoubleClickAction === 'function'
3118
+ ? focusInteractions.resolveDoubleClickAction({
3119
+ currentAnchorId: focusNode && focusNode.id,
3120
+ clickedNodeId: d && d.id,
3121
+ })
3122
+ : {
3123
+ action: focusNode && focusNode.id === d.id ? 'open-reader' : 'switch-focus',
3124
+ clickedNodeId: d && d.id,
3125
+ currentAnchorId: focusNode && focusNode.id,
3126
+ };
3127
+
3128
+ if (focusAction.action === 'open-reader') {
2484
3129
  // Already focused on same node -> Open Reader
2485
3130
  // 已经专注于同一节点 -> 打开阅读器
2486
3131
  if (window.reader) window.reader.open(d);
2487
- } else {
3132
+ } else if (focusAction.action === 'switch-focus') {
2488
3133
  // Clear any existing selection/highlight state before entering focus mode
2489
3134
  // 在进入专注模式前清除任何现有的选择/高亮状态
2490
3135
  if (window.highlightManager) {
@@ -3727,43 +4372,595 @@ window.focusOnNode = function(id) {
3727
4372
  }
3728
4373
  };
3729
4374
 
4375
+ function normalizeGraphViewText(value) {
4376
+ return String(value || '').replace(/\s+/g, ' ').trim();
4377
+ }
3730
4378
 
4379
+ function normalizeGraphViewLookupKey(value) {
4380
+ return normalizeGraphViewText(value).toLowerCase();
4381
+ }
3731
4382
 
3732
- // --- Query History Implementation (v0.9.77) ---
3733
- window.focusHistory = [];
3734
- const MAX_HISTORY = 10;
4383
+ function resolveGraphViewSourceBasename(sourcePath) {
4384
+ const normalized = String(sourcePath || '').replace(/\\/g, '/').trim();
4385
+ if (!normalized) {
4386
+ return '';
4387
+ }
4388
+ const fileName = normalized.split('/').filter(Boolean).pop() || normalized;
4389
+ return fileName.replace(/\.[^/.]+$/, '').trim();
4390
+ }
3735
4391
 
3736
- function updateFocusHistory(newNode) {
3737
- // Avoid duplicates: Remove if exists, then add to top
3738
- // Requirement: "pin the corresponding node to the top"
3739
- window.focusHistory = window.focusHistory.filter(n => n.id !== newNode.id);
3740
-
3741
- // Add to specific history list
3742
- window.focusHistory.unshift(newNode);
3743
- if (window.focusHistory.length > MAX_HISTORY) window.focusHistory.pop();
3744
-
3745
- renderFocusHistory();
4392
+ function getGraphViewNodeLabel(node) {
4393
+ return normalizeGraphViewText(node && (node.label || node.title || node.name || node.id));
3746
4394
  }
3747
4395
 
3748
- function renderFocusHistory() {
3749
- const container = document.getElementById('focus-history-list');
3750
- if (!container) return; // Should be created by init
3751
-
3752
- container.innerHTML = '';
3753
-
3754
- if (window.focusHistory.length === 0) {
3755
- container.innerHTML = '<div style="padding:5px; color:#aaa; font-style:italic">No history</div>';
3756
- return;
4396
+ function getGraphViewLinkNodeId(endpoint) {
4397
+ if (endpoint && typeof endpoint === 'object') {
4398
+ return normalizeGraphViewText(endpoint.id || endpoint.nodeId || endpoint.key);
3757
4399
  }
4400
+ return normalizeGraphViewText(endpoint);
4401
+ }
3758
4402
 
3759
- window.focusHistory.forEach(node => {
3760
- const item = document.createElement('div');
3761
- item.style.padding = '4px 8px';
3762
- item.style.cursor = 'pointer';
3763
- item.style.borderBottom = '1px solid #444';
3764
- item.style.fontSize = '0.8rem';
3765
- item.style.color = '#eee';
3766
- item.innerText = node.label;
4403
+ function collectGraphViewKnowledgePointCandidates(payload) {
4404
+ const candidates = [];
4405
+ const seen = new Set();
4406
+ const appendCandidate = (value) => {
4407
+ const normalized = normalizeGraphViewText(value);
4408
+ if (!normalized || seen.has(normalized)) {
4409
+ return;
4410
+ }
4411
+ seen.add(normalized);
4412
+ candidates.push(normalized);
4413
+ };
4414
+ appendCandidate(payload && payload.graphNodeId);
4415
+ appendCandidate(payload && payload.graphTargetId);
4416
+ appendCandidate(payload && payload.nodeId);
4417
+ appendCandidate(payload && payload.documentId);
4418
+ appendCandidate(payload && payload.title);
4419
+ appendCandidate(payload && payload.label);
4420
+ appendCandidate(payload && payload.sourceBasename);
4421
+ appendCandidate(resolveGraphViewSourceBasename(payload && payload.sourcePath));
4422
+ appendCandidate(payload && payload.atomId);
4423
+ if (Array.isArray(payload && payload.atomIds)) {
4424
+ payload.atomIds.forEach(appendCandidate);
4425
+ }
4426
+ if (Array.isArray(payload && payload.nodeIds)) {
4427
+ payload.nodeIds.forEach(appendCandidate);
4428
+ }
4429
+ if (Array.isArray(payload && payload.matchedSpans)) {
4430
+ payload.matchedSpans.forEach((span) => {
4431
+ appendCandidate(span && span.title);
4432
+ appendCandidate(span && span.atomId);
4433
+ appendCandidate(resolveGraphViewSourceBasename(span && span.sourcePath));
4434
+ });
4435
+ }
4436
+ return candidates;
4437
+ }
4438
+
4439
+ function resolveGraphViewNodeByIdOrLabel(candidate) {
4440
+ const normalized = normalizeGraphViewText(candidate);
4441
+ if (!normalized || !Array.isArray(nodes)) {
4442
+ return null;
4443
+ }
4444
+ const exactId = nodes.find((node) => normalizeGraphViewText(node && node.id) === normalized);
4445
+ if (exactId) {
4446
+ return exactId;
4447
+ }
4448
+ const lookupKey = normalizeGraphViewLookupKey(normalized);
4449
+ const exactLabelMatches = nodes.filter((node) => normalizeGraphViewLookupKey(getGraphViewNodeLabel(node)) === lookupKey);
4450
+ if (exactLabelMatches.length === 1) {
4451
+ return exactLabelMatches[0];
4452
+ }
4453
+ const sourceMatches = nodes.filter((node) => {
4454
+ const metadata = node && typeof node.metadata === 'object' ? node.metadata : {};
4455
+ return normalizeGraphViewLookupKey(resolveGraphViewSourceBasename(node && (node.sourcePath || node.filepath || metadata.filepath || metadata.sourcePath))) === lookupKey;
4456
+ });
4457
+ return sourceMatches.length === 1 ? sourceMatches[0] : null;
4458
+ }
4459
+
4460
+ function resolveGraphViewNodeByKnowledgePoint(payload) {
4461
+ const candidates = collectGraphViewKnowledgePointCandidates(payload);
4462
+ for (const candidate of candidates) {
4463
+ const node = resolveGraphViewNodeByIdOrLabel(candidate);
4464
+ if (node) {
4465
+ return node;
4466
+ }
4467
+ }
4468
+ return null;
4469
+ }
4470
+
4471
+ function buildGraphViewFocusModeSnapshot(nodeId) {
4472
+ const focusD = resolveGraphViewNodeByIdOrLabel(nodeId);
4473
+ if (!focusD) {
4474
+ return null;
4475
+ }
4476
+ const incoming = [];
4477
+ const outgoing = [];
4478
+ const linkList = Array.isArray(links) ? links : [];
4479
+ linkList.forEach((linkItem) => {
4480
+ const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
4481
+ const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
4482
+ if (sourceId === focusD.id) {
4483
+ const targetNode = resolveGraphViewNodeByIdOrLabel(targetId);
4484
+ if (targetNode) {
4485
+ outgoing.push(targetNode);
4486
+ }
4487
+ } else if (targetId === focusD.id) {
4488
+ const sourceNode = resolveGraphViewNodeByIdOrLabel(sourceId);
4489
+ if (sourceNode) {
4490
+ incoming.push(sourceNode);
4491
+ }
4492
+ }
4493
+ });
4494
+
4495
+ const uniqueById = (items) => {
4496
+ const seen = new Set();
4497
+ const unique = [];
4498
+ items.forEach((item) => {
4499
+ const id = normalizeGraphViewText(item && item.id);
4500
+ if (!id || seen.has(id)) {
4501
+ return;
4502
+ }
4503
+ seen.add(id);
4504
+ unique.push(item);
4505
+ });
4506
+ return unique;
4507
+ };
4508
+ const incomingNodes = uniqueById(incoming).slice(0, 5);
4509
+ const outgoingNodes = uniqueById(outgoing).slice(0, 5);
4510
+ const snapshotNodes = [];
4511
+ const addSnapshotNode = (node, role, x, y) => {
4512
+ snapshotNodes.push({
4513
+ id: normalizeGraphViewText(node && node.id),
4514
+ label: getGraphViewNodeLabel(node),
4515
+ role,
4516
+ x,
4517
+ y,
4518
+ });
4519
+ };
4520
+ const placeLane = (nodeList, role, x) => {
4521
+ const step = 80 / (nodeList.length + 1);
4522
+ nodeList.forEach((node, index) => {
4523
+ addSnapshotNode(node, role, x, 10 + step * (index + 1));
4524
+ });
4525
+ };
4526
+ placeLane(incomingNodes, 'incoming', 22);
4527
+ addSnapshotNode(focusD, 'anchor', 50, 50);
4528
+ placeLane(outgoingNodes, 'outgoing', 78);
4529
+
4530
+ const visibleIds = new Set(snapshotNodes.map((node) => node.id));
4531
+ const snapshotEdges = linkList
4532
+ .map((linkItem) => {
4533
+ const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
4534
+ const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
4535
+ if (!sourceId || !targetId || !visibleIds.has(sourceId) || !visibleIds.has(targetId)) {
4536
+ return null;
4537
+ }
4538
+ return {
4539
+ sourceId,
4540
+ targetId,
4541
+ relationKind: normalizeGraphViewText(linkItem && (linkItem.type || linkItem.relationKind || linkItem.kind)) || 'related',
4542
+ confidence: Number(linkItem && (linkItem.confidence || linkItem.weight)),
4543
+ };
4544
+ })
4545
+ .filter(Boolean);
4546
+
4547
+ return {
4548
+ anchorId: focusD.id,
4549
+ anchorLabel: getGraphViewNodeLabel(focusD),
4550
+ nodes: snapshotNodes,
4551
+ edges: snapshotEdges,
4552
+ };
4553
+ }
4554
+
4555
+ function buildGraphViewFocusModeProjection(nodeId, options = {}) {
4556
+ const focusD = resolveGraphViewNodeByIdOrLabel(nodeId);
4557
+ if (!focusD) {
4558
+ return null;
4559
+ }
4560
+ const linkList = Array.isArray(links) ? links : [];
4561
+ const uniqueById = (items) => {
4562
+ const seen = new Set();
4563
+ const unique = [];
4564
+ items.forEach((item) => {
4565
+ const id = normalizeGraphViewText(item && item.id);
4566
+ if (!id || seen.has(id)) {
4567
+ return;
4568
+ }
4569
+ seen.add(id);
4570
+ unique.push(item);
4571
+ });
4572
+ return unique;
4573
+ };
4574
+ const getEndpointNode = (endpoint) => {
4575
+ if (endpoint && typeof endpoint === 'object') {
4576
+ const endpointId = normalizeGraphViewText(endpoint.id || endpoint.nodeId || endpoint.key);
4577
+ return endpointId ? resolveGraphViewNodeByIdOrLabel(endpointId) || endpoint : null;
4578
+ }
4579
+ return resolveGraphViewNodeByIdOrLabel(endpoint);
4580
+ };
4581
+ const getLinkWeight = (linkItem) => {
4582
+ const numericWeight = Number(linkItem && (linkItem.weight || linkItem.confidence));
4583
+ return Number.isFinite(numericWeight) ? numericWeight : 0.5;
4584
+ };
4585
+ const getRelationKind = (linkItem) => (
4586
+ normalizeGraphViewText(linkItem && (linkItem.type || linkItem.relationKind || linkItem.kind)) || 'related'
4587
+ );
4588
+ const outgoing = [];
4589
+ const incoming = [];
4590
+ const focusEdgeMap = new Map();
4591
+ linkList.forEach((linkItem) => {
4592
+ const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
4593
+ const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
4594
+ if (!sourceId || !targetId) {
4595
+ return;
4596
+ }
4597
+ if (sourceId === focusD.id || targetId === focusD.id) {
4598
+ focusEdgeMap.set(`${sourceId}-${targetId}`, linkItem);
4599
+ }
4600
+ if (sourceId === focusD.id) {
4601
+ const targetNode = getEndpointNode(linkItem && linkItem.target);
4602
+ if (targetNode) {
4603
+ outgoing.push(targetNode);
4604
+ }
4605
+ return;
4606
+ }
4607
+ if (targetId === focusD.id) {
4608
+ const sourceNode = getEndpointNode(linkItem && linkItem.source);
4609
+ if (sourceNode) {
4610
+ incoming.push(sourceNode);
4611
+ }
4612
+ }
4613
+ });
4614
+ const scoreNode = (node) => {
4615
+ const key1 = `${focusD.id}-${node.id}`;
4616
+ const key2 = `${node.id}-${focusD.id}`;
4617
+ const edge = focusEdgeMap.get(key1) || focusEdgeMap.get(key2);
4618
+ const weight = getLinkWeight(edge);
4619
+ const degreeRatio = (Number(node && node.outDegree) || 0) / ((Number(node && node.inDegree) || 0) + 1);
4620
+ const normalizedDegreeRatio = Math.min(degreeRatio, 5) / 5;
4621
+ return (weight * 0.7) + (normalizedDegreeRatio * 0.3);
4622
+ };
4623
+ const sortByFocusScore = (left, right) => scoreNode(right) - scoreNode(left);
4624
+ const incomingNodes = uniqueById(incoming).sort(sortByFocusScore);
4625
+ const outgoingNodes = uniqueById(outgoing).sort(sortByFocusScore);
4626
+ const activeIdSet = new Set([focusD.id].concat(
4627
+ incomingNodes.map((node) => node.id),
4628
+ outgoingNodes.map((node) => node.id)
4629
+ ));
4630
+ const associatedNodes = uniqueById(linkList
4631
+ .map((linkItem) => {
4632
+ const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
4633
+ const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
4634
+ if ((sourceId !== focusD.id && targetId !== focusD.id) || getLinkWeight(linkItem) <= 0.6) {
4635
+ return null;
4636
+ }
4637
+ const otherId = sourceId === focusD.id ? targetId : sourceId;
4638
+ if (!otherId || activeIdSet.has(otherId)) {
4639
+ return null;
4640
+ }
4641
+ return getEndpointNode(sourceId === focusD.id ? linkItem.target : linkItem.source);
4642
+ })
4643
+ .filter(Boolean))
4644
+ .sort(sortByFocusScore);
4645
+ associatedNodes.forEach((node) => activeIdSet.add(node.id));
4646
+
4647
+ const currentWidth = Number.isFinite(Number(width)) ? Number(width) : 1200;
4648
+ const currentHeight = Number.isFinite(Number(height)) ? Number(height) : 800;
4649
+ const cx = Number.isFinite(Number(focusD.x)) ? Number(focusD.x) : currentWidth / 2;
4650
+ const cy = Number.isFinite(Number(focusD.y)) ? Number(focusD.y) : currentHeight / 2;
4651
+ const selectedLayoutType = normalizeGraphViewText(options.layoutType)
4652
+ || (document.getElementById('focus-layout-select') ? document.getElementById('focus-layout-select').value : '')
4653
+ || 'horizontal';
4654
+ const layoutType = selectedLayoutType === 'vertical' ? 'vertical' : 'horizontal';
4655
+ const defaultLayerGap = focusSpacingSettings && focusSpacingSettings[layoutType]
4656
+ ? Number(focusSpacingSettings[layoutType].layer)
4657
+ : (layoutType === 'horizontal' ? 125 : 250);
4658
+ const defaultNodeGap = focusSpacingSettings && focusSpacingSettings[layoutType]
4659
+ ? Number(focusSpacingSettings[layoutType].node)
4660
+ : (layoutType === 'horizontal' ? 80 : 20);
4661
+ const layerGap = Number.isFinite(Number(options.layerGap)) ? Number(options.layerGap) : defaultLayerGap;
4662
+ const nodeGap = Number.isFinite(Number(options.nodeGap)) ? Number(options.nodeGap) : defaultNodeGap;
4663
+ const projectionNodes = [];
4664
+ const projectionLabels = [];
4665
+ const points = new Map();
4666
+ const addProjectionNode = (node, role, x, y, labelDy, labelDx) => {
4667
+ const id = normalizeGraphViewText(node && node.id);
4668
+ if (!id || points.has(id)) {
4669
+ return;
4670
+ }
4671
+ points.set(id, { x, y });
4672
+ projectionNodes.push({
4673
+ id,
4674
+ label: getGraphViewNodeLabel(node) || id,
4675
+ role,
4676
+ x,
4677
+ y,
4678
+ score: Number(scoreNode(node).toFixed(4)),
4679
+ inDegree: Number(node && node.inDegree) || 0,
4680
+ outDegree: Number(node && node.outDegree) || 0,
4681
+ sourcePath: normalizeGraphViewText(node && (
4682
+ node.sourcePath
4683
+ || node.filepath
4684
+ || (node.metadata && (node.metadata.sourcePath || node.metadata.filepath))
4685
+ )),
4686
+ radius: role === 'anchor' ? 25 : 8,
4687
+ labelDy,
4688
+ labelDx,
4689
+ });
4690
+ };
4691
+ addProjectionNode(focusD, 'anchor', cx, cy, 35, layoutType === 'vertical' ? 25 : 29);
4692
+ if (layoutType === 'vertical') {
4693
+ const spreadVertical = (nodeList, baselineX, role) => {
4694
+ const totalHeight = (nodeList.length - 1) * nodeGap;
4695
+ const startY = cy - totalHeight / 2;
4696
+ nodeList.forEach((node, index) => {
4697
+ addProjectionNode(node, role, baselineX, startY + (index * nodeGap), 25, 25);
4698
+ });
4699
+ };
4700
+ spreadVertical(incomingNodes, cx - layerGap, 'incoming');
4701
+ spreadVertical(outgoingNodes, cx + layerGap, 'outgoing');
4702
+ projectionLabels.push({
4703
+ text: typeof t === 'function' ? t('focus_inbound') : 'Inbound',
4704
+ x: cx - layerGap,
4705
+ y: cy - (incomingNodes.length * nodeGap / 2) - 40,
4706
+ align: 'middle',
4707
+ role: 'incoming',
4708
+ });
4709
+ projectionLabels.push({
4710
+ text: typeof t === 'function' ? t('focus_outbound') : 'Outbound',
4711
+ x: cx + layerGap,
4712
+ y: cy - (outgoingNodes.length * nodeGap / 2) - 40,
4713
+ align: 'middle',
4714
+ role: 'outgoing',
4715
+ });
4716
+ } else {
4717
+ const spreadHorizontal = (nodeList, baselineY, role) => {
4718
+ const totalWidth = (nodeList.length - 1) * nodeGap;
4719
+ const startX = cx - totalWidth / 2;
4720
+ nodeList.forEach((node, index) => {
4721
+ const nodeScore = scoreNode(node);
4722
+ const stagger = (index % 2 === 0 ? -1 : 1) * 20;
4723
+ const nodeY = baselineY + stagger + (nodeScore * 20);
4724
+ addProjectionNode(node, role, startX + (index * nodeGap), nodeY, nodeY < baselineY ? -15 : 25, 12);
4725
+ });
4726
+ };
4727
+ spreadHorizontal(outgoingNodes, cy - layerGap, 'outgoing');
4728
+ spreadHorizontal(incomingNodes, cy + layerGap, 'incoming');
4729
+ projectionLabels.push({
4730
+ text: typeof t === 'function' ? t('focus_outbound') : 'Outbound',
4731
+ x: cx,
4732
+ y: cy - layerGap - 60,
4733
+ align: 'middle',
4734
+ role: 'outgoing',
4735
+ });
4736
+ projectionLabels.push({
4737
+ text: typeof t === 'function' ? t('focus_inbound') : 'Inbound',
4738
+ x: cx,
4739
+ y: cy + layerGap + 80,
4740
+ align: 'middle',
4741
+ role: 'incoming',
4742
+ });
4743
+ }
4744
+ if (associatedNodes.length > 0) {
4745
+ const left = [];
4746
+ const right = [];
4747
+ associatedNodes.forEach((node, index) => {
4748
+ if (index % 2 === 0) {
4749
+ left.push(node);
4750
+ } else {
4751
+ right.push(node);
4752
+ }
4753
+ });
4754
+ const sideGap = layerGap * 1.2;
4755
+ const placeSide = (nodeList, direction) => {
4756
+ nodeList.forEach((node, index) => {
4757
+ addProjectionNode(
4758
+ node,
4759
+ 'associated',
4760
+ cx + (direction * sideGap),
4761
+ cy + (index * 60) - (nodeList.length * 30),
4762
+ 25,
4763
+ 12
4764
+ );
4765
+ });
4766
+ };
4767
+ placeSide(left, -1);
4768
+ placeSide(right, 1);
4769
+ }
4770
+
4771
+ const visibleIds = new Set(projectionNodes.map((node) => node.id));
4772
+ const projectionEdges = linkList
4773
+ .map((linkItem) => {
4774
+ const sourceId = getGraphViewLinkNodeId(linkItem && linkItem.source);
4775
+ const targetId = getGraphViewLinkNodeId(linkItem && linkItem.target);
4776
+ if (!sourceId || !targetId || !visibleIds.has(sourceId) || !visibleIds.has(targetId)) {
4777
+ return null;
4778
+ }
4779
+ return {
4780
+ sourceId,
4781
+ targetId,
4782
+ relationKind: getRelationKind(linkItem),
4783
+ confidence: getLinkWeight(linkItem),
4784
+ role: sourceId === focusD.id
4785
+ ? 'outgoing'
4786
+ : targetId === focusD.id
4787
+ ? 'incoming'
4788
+ : 'associated',
4789
+ };
4790
+ })
4791
+ .filter(Boolean);
4792
+ const maxContextNodes = Number.isFinite(Number(options.maxContextNodes))
4793
+ ? Math.max(0, Math.min(500, Math.trunc(Number(options.maxContextNodes))))
4794
+ : 220;
4795
+ const contextNodes = maxContextNodes > 0
4796
+ ? (Array.isArray(nodes) ? nodes : [])
4797
+ .map((node) => {
4798
+ const id = normalizeGraphViewText(node && node.id);
4799
+ const x = Number(node && node.x);
4800
+ const y = Number(node && node.y);
4801
+ if (!id || visibleIds.has(id) || !Number.isFinite(x) || !Number.isFinite(y)) {
4802
+ return null;
4803
+ }
4804
+ const dx = x - cx;
4805
+ const dy = y - cy;
4806
+ const distance = Math.sqrt((dx * dx) + (dy * dy));
4807
+ const degree = (Number(node && node.inDegree) || 0) + (Number(node && node.outDegree) || 0);
4808
+ const degreeScore = Math.min(degree, 40) / 40;
4809
+ const proximityScore = 1 / (1 + (distance / Math.max(layerGap, nodeGap, 1)));
4810
+ return {
4811
+ id,
4812
+ label: getGraphViewNodeLabel(node) || id,
4813
+ x,
4814
+ y,
4815
+ inDegree: Number(node && node.inDegree) || 0,
4816
+ outDegree: Number(node && node.outDegree) || 0,
4817
+ score: Number(((proximityScore * 0.72) + (degreeScore * 0.28)).toFixed(4)),
4818
+ };
4819
+ })
4820
+ .filter(Boolean)
4821
+ .sort((left, right) => right.score - left.score)
4822
+ .slice(0, maxContextNodes)
4823
+ : [];
4824
+ const boundsPoints = projectionNodes
4825
+ .map((node) => ({ x: node.x, y: node.y }))
4826
+ .concat(projectionLabels.map((label) => ({ x: label.x, y: label.y })));
4827
+ const contextBoundsPoints = contextNodes.length > 0
4828
+ ? contextNodes.map((node) => ({ x: node.x, y: node.y }))
4829
+ : boundsPoints;
4830
+ const minX = Math.min(...boundsPoints.map((point) => point.x));
4831
+ const maxX = Math.max(...boundsPoints.map((point) => point.x));
4832
+ const minY = Math.min(...boundsPoints.map((point) => point.y));
4833
+ const maxY = Math.max(...boundsPoints.map((point) => point.y));
4834
+ const contextMinX = Math.min(...contextBoundsPoints.map((point) => point.x));
4835
+ const contextMaxX = Math.max(...contextBoundsPoints.map((point) => point.x));
4836
+ const contextMinY = Math.min(...contextBoundsPoints.map((point) => point.y));
4837
+ const contextMaxY = Math.max(...contextBoundsPoints.map((point) => point.y));
4838
+ return {
4839
+ anchorId: focusD.id,
4840
+ anchorLabel: getGraphViewNodeLabel(focusD) || focusD.id,
4841
+ layoutType,
4842
+ layerGap,
4843
+ nodeGap,
4844
+ stats: {
4845
+ inDegree: Number(focusD.inDegree) || incomingNodes.length,
4846
+ outDegree: Number(focusD.outDegree) || outgoingNodes.length,
4847
+ incomingCount: incomingNodes.length,
4848
+ outgoingCount: outgoingNodes.length,
4849
+ associatedCount: associatedNodes.length,
4850
+ contextCount: contextNodes.length,
4851
+ },
4852
+ controls: {
4853
+ layoutType,
4854
+ layerGap,
4855
+ nodeGap,
4856
+ rendererMode: document.querySelector('input[name="rendererMode"]:checked')
4857
+ ? document.querySelector('input[name="rendererMode"]:checked').value
4858
+ : '',
4859
+ },
4860
+ labels: projectionLabels,
4861
+ contextNodes,
4862
+ nodes: projectionNodes,
4863
+ edges: projectionEdges,
4864
+ bounds: {
4865
+ minX,
4866
+ maxX,
4867
+ minY,
4868
+ maxY,
4869
+ width: Math.max(1, maxX - minX),
4870
+ height: Math.max(1, maxY - minY),
4871
+ },
4872
+ contextBounds: {
4873
+ minX: contextMinX,
4874
+ maxX: contextMaxX,
4875
+ minY: contextMinY,
4876
+ maxY: contextMaxY,
4877
+ width: Math.max(1, contextMaxX - contextMinX),
4878
+ height: Math.max(1, contextMaxY - contextMinY),
4879
+ },
4880
+ };
4881
+ }
4882
+
4883
+ // Compatibility API for agent workspace graph-focus capabilities.
4884
+ // Keeps agent runtime decoupled from historical global helper names.
4885
+ window.NoteConnectionGraphView = {
4886
+ resolveNodeById: function(nodeId) {
4887
+ const id = String(nodeId || '').trim();
4888
+ if (!id) {
4889
+ return null;
4890
+ }
4891
+ return resolveGraphViewNodeByIdOrLabel(id);
4892
+ },
4893
+ resolveNodeByKnowledgePoint: function(payload) {
4894
+ return resolveGraphViewNodeByKnowledgePoint(payload);
4895
+ },
4896
+ openFocusModeById: function(nodeId) {
4897
+ const node = this.resolveNodeById(nodeId);
4898
+ if (!node) {
4899
+ return false;
4900
+ }
4901
+ if (window.highlightManager && typeof window.highlightManager.unhighlight === 'function') {
4902
+ window.highlightManager.unhighlight({ force: true });
4903
+ }
4904
+ const popup = document.getElementById('node-stats-popup');
4905
+ if (popup) {
4906
+ popup.style.display = 'none';
4907
+ }
4908
+ enterFocusMode(node);
4909
+ return true;
4910
+ },
4911
+ getFocusNode: function() {
4912
+ return focusNode && typeof focusNode === 'object'
4913
+ ? focusNode
4914
+ : null;
4915
+ },
4916
+ getFocusModeSnapshot: function(nodeId) {
4917
+ return buildGraphViewFocusModeSnapshot(nodeId || (focusNode && focusNode.id));
4918
+ },
4919
+ getFocusModeProjection: function(nodeId, options) {
4920
+ return buildGraphViewFocusModeProjection(nodeId || (focusNode && focusNode.id), options || {});
4921
+ },
4922
+ getNodeCount: function() {
4923
+ return Array.isArray(nodes) ? nodes.length : 0;
4924
+ },
4925
+ };
4926
+
4927
+
4928
+
4929
+ // --- Query History Implementation (v0.9.77) ---
4930
+ window.focusHistory = [];
4931
+ const MAX_HISTORY = 10;
4932
+
4933
+ function updateFocusHistory(newNode) {
4934
+ // Avoid duplicates: Remove if exists, then add to top
4935
+ // Requirement: "pin the corresponding node to the top"
4936
+ window.focusHistory = window.focusHistory.filter(n => n.id !== newNode.id);
4937
+
4938
+ // Add to specific history list
4939
+ window.focusHistory.unshift(newNode);
4940
+ if (window.focusHistory.length > MAX_HISTORY) window.focusHistory.pop();
4941
+
4942
+ renderFocusHistory();
4943
+ }
4944
+
4945
+ function renderFocusHistory() {
4946
+ const container = document.getElementById('focus-history-list');
4947
+ if (!container) return; // Should be created by init
4948
+
4949
+ container.innerHTML = '';
4950
+
4951
+ if (window.focusHistory.length === 0) {
4952
+ container.innerHTML = '<div style="padding:5px; color:#aaa; font-style:italic">No history</div>';
4953
+ return;
4954
+ }
4955
+
4956
+ window.focusHistory.forEach(node => {
4957
+ const item = document.createElement('div');
4958
+ item.style.padding = '4px 8px';
4959
+ item.style.cursor = 'pointer';
4960
+ item.style.borderBottom = '1px solid #444';
4961
+ item.style.fontSize = '0.8rem';
4962
+ item.style.color = '#eee';
4963
+ item.innerText = node.label;
3767
4964
  item.title = `Cluster: ${node.clusterId || '-'}`;
3768
4965
 
3769
4966
  item.addEventListener('mouseenter', () => item.style.background = '#444');
@@ -4366,12 +5563,18 @@ const workersSlider = document.getElementById('set-workers-slider');
4366
5563
  // --- Settings Integration ---
4367
5564
  function initSettingsUI() {
4368
5565
  const modal = document.getElementById('settings-modal');
5566
+ const agentModal = document.getElementById('agent-settings-modal');
4369
5567
  const openBtn = document.getElementById('btn-open-settings');
4370
- const closeBtns = document.querySelectorAll('.modal-close');
5568
+ const mainCloseBtns = modal ? modal.querySelectorAll('[data-settings-close="main"]') : [];
5569
+ const agentCloseBtns = agentModal ? agentModal.querySelectorAll('[data-settings-close="agent"]') : [];
5570
+ const openAgentSettingsBtn = document.getElementById('btn-open-agent-settings');
5571
+ const agentSettingsBackBtn = document.getElementById('btn-agent-settings-back');
5572
+ const agentSettingsBackFooterBtn = document.getElementById('btn-agent-settings-back-footer');
4371
5573
  const resetBtn = document.getElementById('btn-reset-settings');
4372
5574
 
4373
- // v0.9.41: Track modal state to freeze simulation
4374
- let isSettingsModalOpen = false;
5575
+ // Track settings page state so simulation only resumes after every settings
5576
+ // surface is actually closed.
5577
+ let activeSettingsPage = null;
4375
5578
 
4376
5579
  // Controls
4377
5580
  const inputs = {
@@ -4392,6 +5595,7 @@ function initSettingsUI() {
4392
5595
  const workersSlider = document.getElementById('set-workers-slider');
4393
5596
  const workersInput = document.getElementById('set-workers-input');
4394
5597
  const gpuCheckbox = document.getElementById('set-gpu');
5598
+ const staticModeCheckbox = document.getElementById('set-static-mode');
4395
5599
  const gpuRenderingCheckbox = document.getElementById('set-gpu-rendering');
4396
5600
  const memorySavingCheckbox = document.getElementById('set-memory-saving');
4397
5601
  const compactModeCheckbox = document.getElementById('set-compact-mode');
@@ -4400,6 +5604,665 @@ function initSettingsUI() {
4400
5604
  // Reader Settings
4401
5605
  const inputReadingMode = document.getElementById('set-reading-mode');
4402
5606
 
5607
+ // Simplified NoteMD Provider Settings
5608
+ const providerTemplateSelect = document.getElementById('set-notemd-provider-template');
5609
+ const providerTemplateHint = document.getElementById('set-notemd-provider-template-hint');
5610
+ const providerNameSelect = document.getElementById('set-notemd-provider-name');
5611
+ const providerBaseUrlInput = document.getElementById('set-notemd-provider-base-url');
5612
+ const providerModelInput = document.getElementById('set-notemd-provider-model');
5613
+ const providerApiKeyInput = document.getElementById('set-notemd-provider-api-key');
5614
+ const providerApiVersionInput = document.getElementById('set-notemd-provider-api-version');
5615
+ const providerApiVersionRow = document.getElementById('set-notemd-provider-api-version-row');
5616
+ const providerApiVersionHint = document.getElementById('set-notemd-provider-api-version-hint');
5617
+ const providerStatus = document.getElementById('set-notemd-provider-status');
5618
+ const providerConfigPath = document.getElementById('set-notemd-provider-config-path');
5619
+ const applyProviderTemplateBtn = document.getElementById('btn-apply-notemd-provider-template');
5620
+ const testProviderBtn = document.getElementById('btn-test-notemd-provider');
5621
+ const saveProviderBtn = document.getElementById('btn-save-notemd-provider');
5622
+ const materializeProviderTemplatesBtn = document.getElementById('btn-materialize-notemd-provider-templates');
5623
+ const NOTEMD_PROVIDER_DRAFT_STORAGE_KEY = 'nc.notemdProviderDraft.v1';
5624
+ const NOTEMD_PROVIDER_AUTOSAVE_DEBOUNCE_MS = 900;
5625
+
5626
+ const notemdProviderModalState = {
5627
+ settings: null,
5628
+ templates: [],
5629
+ configPath: '',
5630
+ loading: false,
5631
+ autosaveTimer: null,
5632
+ lastSavedFingerprint: '',
5633
+ applyingDraft: false,
5634
+ };
5635
+
5636
+ const t = (key, fallback, params) => {
5637
+ if (window.i18n && typeof window.i18n.t === 'function') {
5638
+ const translated = window.i18n.t(key, params);
5639
+ if (translated && translated !== key) {
5640
+ return translated;
5641
+ }
5642
+ }
5643
+ return fallback;
5644
+ };
5645
+
5646
+ const buildRuntimeUrl = (resourcePath) => {
5647
+ if (window.NoteConnectionRuntime && typeof window.NoteConnectionRuntime.buildUrl === 'function') {
5648
+ return window.NoteConnectionRuntime.buildUrl(resourcePath.replace(/^\/+/, ''));
5649
+ }
5650
+ return resourcePath;
5651
+ };
5652
+
5653
+ const buildRuntimeFetchOptions = (init) => {
5654
+ if (window.NoteConnectionRuntime && typeof window.NoteConnectionRuntime.buildFetchOptions === 'function') {
5655
+ return window.NoteConnectionRuntime.buildFetchOptions(init);
5656
+ }
5657
+ return init;
5658
+ };
5659
+
5660
+ const cloneJson = (value) => JSON.parse(JSON.stringify(value));
5661
+
5662
+ const clearProviderAutosaveTimer = () => {
5663
+ if (notemdProviderModalState.autosaveTimer) {
5664
+ clearTimeout(notemdProviderModalState.autosaveTimer);
5665
+ notemdProviderModalState.autosaveTimer = null;
5666
+ }
5667
+ };
5668
+
5669
+ const readProviderDraftStore = () => {
5670
+ try {
5671
+ const raw = localStorage.getItem(NOTEMD_PROVIDER_DRAFT_STORAGE_KEY);
5672
+ const parsed = raw ? JSON.parse(raw) : {};
5673
+ return parsed && typeof parsed === 'object' ? parsed : {};
5674
+ } catch (_error) {
5675
+ return {};
5676
+ }
5677
+ };
5678
+
5679
+ const writeProviderDraftStore = (draftStore) => {
5680
+ try {
5681
+ localStorage.setItem(NOTEMD_PROVIDER_DRAFT_STORAGE_KEY, JSON.stringify(draftStore || {}));
5682
+ } catch (_error) {
5683
+ // Best effort only.
5684
+ }
5685
+ };
5686
+
5687
+ const getProviderDraft = (providerName) => {
5688
+ const draftStore = readProviderDraftStore();
5689
+ const key = String(providerName || '').trim();
5690
+ if (!key || !draftStore[key] || typeof draftStore[key] !== 'object') {
5691
+ return null;
5692
+ }
5693
+ return draftStore[key];
5694
+ };
5695
+
5696
+ const updateProviderDraft = (providerName, draftPatch) => {
5697
+ const key = String(providerName || '').trim();
5698
+ if (!key) {
5699
+ return;
5700
+ }
5701
+ const draftStore = readProviderDraftStore();
5702
+ const current = draftStore[key] && typeof draftStore[key] === 'object' ? draftStore[key] : {};
5703
+ draftStore[key] = {
5704
+ ...current,
5705
+ ...draftPatch,
5706
+ updatedAt: new Date().toISOString(),
5707
+ };
5708
+ writeProviderDraftStore(draftStore);
5709
+ };
5710
+
5711
+ const removeProviderDraft = (providerName) => {
5712
+ const key = String(providerName || '').trim();
5713
+ if (!key) {
5714
+ return;
5715
+ }
5716
+ const draftStore = readProviderDraftStore();
5717
+ if (Object.prototype.hasOwnProperty.call(draftStore, key)) {
5718
+ delete draftStore[key];
5719
+ writeProviderDraftStore(draftStore);
5720
+ }
5721
+ };
5722
+
5723
+ const setProviderModalStatus = (message, tone = 'muted') => {
5724
+ if (!providerStatus) {
5725
+ return;
5726
+ }
5727
+ providerStatus.textContent = String(message || '');
5728
+ providerStatus.style.color =
5729
+ tone === 'error'
5730
+ ? '#fca5a5'
5731
+ : tone === 'success'
5732
+ ? '#93c5fd'
5733
+ : '#aeb7c3';
5734
+ };
5735
+
5736
+ const setProviderModalBusy = (busy) => {
5737
+ notemdProviderModalState.loading = busy === true;
5738
+ const disabled = notemdProviderModalState.loading;
5739
+ [
5740
+ providerTemplateSelect,
5741
+ providerNameSelect,
5742
+ providerBaseUrlInput,
5743
+ providerModelInput,
5744
+ providerApiKeyInput,
5745
+ providerApiVersionInput,
5746
+ applyProviderTemplateBtn,
5747
+ testProviderBtn,
5748
+ saveProviderBtn,
5749
+ materializeProviderTemplatesBtn,
5750
+ ].forEach((element) => {
5751
+ if (element) {
5752
+ element.disabled = disabled;
5753
+ }
5754
+ });
5755
+ };
5756
+
5757
+ const ensureRuntimeBridgeReady = async () => {
5758
+ if (
5759
+ window.NoteConnectionRuntime
5760
+ && typeof window.NoteConnectionRuntime.whenReady === 'function'
5761
+ ) {
5762
+ await window.NoteConnectionRuntime.whenReady();
5763
+ }
5764
+ };
5765
+
5766
+ const refreshRuntimeBridgeFromTauri = async () => {
5767
+ if (
5768
+ !window.NoteConnectionRuntime
5769
+ || typeof window.NoteConnectionRuntime.refreshFromTauri !== 'function'
5770
+ ) {
5771
+ return false;
5772
+ }
5773
+ try {
5774
+ await window.NoteConnectionRuntime.refreshFromTauri();
5775
+ return true;
5776
+ } catch (error) {
5777
+ console.warn('[Settings] Runtime bridge refresh failed before retrying agent settings request.', error);
5778
+ return false;
5779
+ }
5780
+ };
5781
+
5782
+ const fetchRuntimeJsonOnce = async (resourcePath, init) => {
5783
+ await ensureRuntimeBridgeReady();
5784
+ const response = await fetch(buildRuntimeUrl(resourcePath), buildRuntimeFetchOptions(init));
5785
+ const payload = await response.json().catch(() => null);
5786
+ return { response, payload };
5787
+ };
5788
+
5789
+ const isSuccessfulRuntimeJsonResponse = ({ response, payload }) => (
5790
+ response.ok && payload && payload.success === true
5791
+ );
5792
+
5793
+ const buildRuntimeJsonError = (resourcePath, { response, payload }) => {
5794
+ const message = payload && payload.error
5795
+ ? String(payload.error)
5796
+ : `Request failed (${resourcePath} ${response.status})`;
5797
+ return new Error(message);
5798
+ };
5799
+
5800
+ const requestRuntimeJson = async (resourcePath, init = {}) => {
5801
+ const firstAttempt = await fetchRuntimeJsonOnce(resourcePath, init);
5802
+ if (isSuccessfulRuntimeJsonResponse(firstAttempt)) {
5803
+ return firstAttempt.payload;
5804
+ }
5805
+
5806
+ if (firstAttempt.response.status === 401 && await refreshRuntimeBridgeFromTauri()) {
5807
+ const secondAttempt = await fetchRuntimeJsonOnce(resourcePath, init);
5808
+ if (isSuccessfulRuntimeJsonResponse(secondAttempt)) {
5809
+ return secondAttempt.payload;
5810
+ }
5811
+ throw buildRuntimeJsonError(resourcePath, secondAttempt);
5812
+ }
5813
+
5814
+ throw buildRuntimeJsonError(resourcePath, firstAttempt);
5815
+ };
5816
+
5817
+ const isAnySettingsPanelOpen = () => activeSettingsPage !== null;
5818
+
5819
+ const resumeSimulationIfAllowed = () => {
5820
+ const isFrozen = document.getElementById('freeze-layout')
5821
+ ? document.getElementById('freeze-layout').checked
5822
+ : false;
5823
+ if (!isFrozen) {
5824
+ simulation.alpha(0.3).restart();
5825
+ }
5826
+ };
5827
+
5828
+ const showMainSettingsPage = () => {
5829
+ if (modal) {
5830
+ modal.style.display = 'flex';
5831
+ const body = modal.querySelector('.settings-modal-body');
5832
+ if (body) {
5833
+ body.scrollTop = 0;
5834
+ }
5835
+ }
5836
+ if (agentModal) {
5837
+ agentModal.style.display = 'none';
5838
+ }
5839
+ activeSettingsPage = 'main';
5840
+ simulation.stop();
5841
+ };
5842
+
5843
+ const showAgentSettingsPage = async () => {
5844
+ if (modal) {
5845
+ modal.style.display = 'none';
5846
+ }
5847
+ if (agentModal) {
5848
+ agentModal.style.display = 'flex';
5849
+ const body = agentModal.querySelector('.settings-modal-body');
5850
+ if (body) {
5851
+ body.scrollTop = 0;
5852
+ }
5853
+ }
5854
+ activeSettingsPage = 'agent';
5855
+ simulation.stop();
5856
+ await loadNotemdProviderModalState();
5857
+ };
5858
+
5859
+ const closeAllSettingsPages = () => {
5860
+ clearProviderAutosaveTimer();
5861
+ const nextFingerprint = getCurrentProviderFingerprint();
5862
+ if (
5863
+ activeSettingsPage === 'agent'
5864
+ && nextFingerprint
5865
+ && nextFingerprint !== notemdProviderModalState.lastSavedFingerprint
5866
+ && !notemdProviderModalState.loading
5867
+ ) {
5868
+ void saveNotemdProviderModalState({ auto: true }).catch((error) => {
5869
+ setProviderModalStatus(
5870
+ t('notemd_provider_autosave_failed', 'Auto-save failed: {error}', {
5871
+ error: error && error.message ? error.message : String(error),
5872
+ }),
5873
+ 'error'
5874
+ );
5875
+ });
5876
+ }
5877
+ if (modal) {
5878
+ modal.style.display = 'none';
5879
+ }
5880
+ if (agentModal) {
5881
+ agentModal.style.display = 'none';
5882
+ }
5883
+ activeSettingsPage = null;
5884
+ resumeSimulationIfAllowed();
5885
+ };
5886
+
5887
+ const getCurrentProviderFromModalState = () => {
5888
+ if (!notemdProviderModalState.settings || !providerNameSelect) {
5889
+ return null;
5890
+ }
5891
+ const providerName = String(providerNameSelect.value || '').trim();
5892
+ return notemdProviderModalState.settings.providers.find((provider) => provider.name === providerName) || null;
5893
+ };
5894
+
5895
+ const getCurrentProviderFingerprint = () => {
5896
+ const providerName = String(providerNameSelect ? providerNameSelect.value : '').trim();
5897
+ if (!providerName) {
5898
+ return '';
5899
+ }
5900
+ return JSON.stringify({
5901
+ providerName,
5902
+ templateId: String(providerTemplateSelect ? providerTemplateSelect.value : '').trim(),
5903
+ baseUrl: String(providerBaseUrlInput ? providerBaseUrlInput.value : '').trim(),
5904
+ model: String(providerModelInput ? providerModelInput.value : '').trim(),
5905
+ apiKey: String(providerApiKeyInput ? providerApiKeyInput.value : ''),
5906
+ apiVersion: String(providerApiVersionInput ? providerApiVersionInput.value : '').trim(),
5907
+ });
5908
+ };
5909
+
5910
+ const buildCurrentProviderRequestPayload = () => {
5911
+ const providerName = String(providerNameSelect ? providerNameSelect.value : '').trim();
5912
+ const persistedProvider = getCurrentProviderFromModalState();
5913
+ return {
5914
+ provider: {
5915
+ name: providerName,
5916
+ baseUrl: String(providerBaseUrlInput ? providerBaseUrlInput.value : '').trim(),
5917
+ model: String(providerModelInput ? providerModelInput.value : '').trim(),
5918
+ apiKey: String(providerApiKeyInput ? providerApiKeyInput.value : ''),
5919
+ apiVersion: String(providerApiVersionInput ? providerApiVersionInput.value : '').trim(),
5920
+ temperature: Number.isFinite(Number(persistedProvider && persistedProvider.temperature))
5921
+ ? Number(persistedProvider.temperature)
5922
+ : 0.5,
5923
+ },
5924
+ providerName,
5925
+ };
5926
+ };
5927
+
5928
+ const syncCurrentProviderDraft = () => {
5929
+ const providerName = String(providerNameSelect ? providerNameSelect.value : '').trim();
5930
+ if (!providerName || notemdProviderModalState.applyingDraft) {
5931
+ return;
5932
+ }
5933
+ updateProviderDraft(providerName, {
5934
+ templateId: String(providerTemplateSelect ? providerTemplateSelect.value : '').trim(),
5935
+ baseUrl: String(providerBaseUrlInput ? providerBaseUrlInput.value : '').trim(),
5936
+ model: String(providerModelInput ? providerModelInput.value : '').trim(),
5937
+ apiKey: String(providerApiKeyInput ? providerApiKeyInput.value : ''),
5938
+ apiVersion: String(providerApiVersionInput ? providerApiVersionInput.value : '').trim(),
5939
+ });
5940
+ };
5941
+
5942
+ const findTemplateForProvider = (providerName) => {
5943
+ const templates = Array.isArray(notemdProviderModalState.templates) ? notemdProviderModalState.templates : [];
5944
+ return templates.find((template) => template.providerName === providerName) || null;
5945
+ };
5946
+
5947
+ const updateProviderApiVersionVisibility = (provider) => {
5948
+ if (!providerApiVersionRow) {
5949
+ return;
5950
+ }
5951
+ const isAzure = provider && String(provider.name || '').trim() === 'Azure OpenAI';
5952
+ providerApiVersionRow.style.display = isAzure ? '' : 'none';
5953
+ if (providerApiVersionInput) {
5954
+ providerApiVersionInput.placeholder = isAzure ? '2025-01-01-preview' : '';
5955
+ }
5956
+ if (providerApiVersionHint) {
5957
+ providerApiVersionHint.textContent = isAzure
5958
+ ? t('notemd_provider_api_version_required', 'Required for Azure OpenAI. Example: 2025-01-01-preview.')
5959
+ : t('notemd_provider_api_version_optional', 'Optional. Leave blank for standard OpenAI-compatible endpoints. This is mainly used by Azure OpenAI.');
5960
+ }
5961
+ };
5962
+
5963
+ const applyProviderDraftToFields = (providerName) => {
5964
+ const draft = getProviderDraft(providerName);
5965
+ if (!draft) {
5966
+ return;
5967
+ }
5968
+ notemdProviderModalState.applyingDraft = true;
5969
+ try {
5970
+ if (providerTemplateSelect && typeof draft.templateId === 'string' && draft.templateId) {
5971
+ providerTemplateSelect.value = draft.templateId;
5972
+ renderProviderTemplateHint();
5973
+ }
5974
+ if (providerBaseUrlInput && typeof draft.baseUrl === 'string') {
5975
+ providerBaseUrlInput.value = draft.baseUrl;
5976
+ }
5977
+ if (providerModelInput && typeof draft.model === 'string') {
5978
+ providerModelInput.value = draft.model;
5979
+ }
5980
+ if (providerApiKeyInput && typeof draft.apiKey === 'string') {
5981
+ providerApiKeyInput.value = draft.apiKey;
5982
+ }
5983
+ if (providerApiVersionInput && typeof draft.apiVersion === 'string') {
5984
+ providerApiVersionInput.value = draft.apiVersion;
5985
+ }
5986
+ } finally {
5987
+ notemdProviderModalState.applyingDraft = false;
5988
+ }
5989
+ };
5990
+
5991
+ const renderProviderTemplateHint = () => {
5992
+ if (!providerTemplateHint) {
5993
+ return;
5994
+ }
5995
+ const templateId = providerTemplateSelect ? String(providerTemplateSelect.value || '').trim() : '';
5996
+ const templates = Array.isArray(notemdProviderModalState.templates) ? notemdProviderModalState.templates : [];
5997
+ const template = templates.find((item) => item.id === templateId) || null;
5998
+ if (!template) {
5999
+ providerTemplateHint.textContent = '';
6000
+ return;
6001
+ }
6002
+ const noteSummary = Array.isArray(template.notes) ? template.notes.slice(0, 2).join(' ') : '';
6003
+ providerTemplateHint.textContent = `${template.label} · ${template.category} · ${template.transport}. ${template.recommendedFor} ${template.hostHint} ${noteSummary}`.trim();
6004
+ };
6005
+
6006
+ const renderSelectedProviderFields = () => {
6007
+ const provider = getCurrentProviderFromModalState();
6008
+ if (!provider) {
6009
+ if (providerBaseUrlInput) providerBaseUrlInput.value = '';
6010
+ if (providerModelInput) providerModelInput.value = '';
6011
+ if (providerApiKeyInput) providerApiKeyInput.value = '';
6012
+ if (providerApiVersionInput) providerApiVersionInput.value = '';
6013
+ updateProviderApiVersionVisibility(null);
6014
+ return;
6015
+ }
6016
+ if (providerBaseUrlInput) providerBaseUrlInput.value = String(provider.baseUrl || '');
6017
+ if (providerModelInput) providerModelInput.value = String(provider.model || '');
6018
+ if (providerApiKeyInput) providerApiKeyInput.value = String(provider.apiKey || '');
6019
+ if (providerApiVersionInput) providerApiVersionInput.value = String(provider.apiVersion || '');
6020
+ updateProviderApiVersionVisibility(provider);
6021
+
6022
+ const matchedTemplate = findTemplateForProvider(provider.name);
6023
+ if (providerTemplateSelect && matchedTemplate) {
6024
+ providerTemplateSelect.value = matchedTemplate.id;
6025
+ renderProviderTemplateHint();
6026
+ }
6027
+ applyProviderDraftToFields(provider.name);
6028
+ };
6029
+
6030
+ const populateProviderTemplateSelect = () => {
6031
+ if (!providerTemplateSelect) {
6032
+ return;
6033
+ }
6034
+ providerTemplateSelect.innerHTML = '';
6035
+ const placeholder = document.createElement('option');
6036
+ placeholder.value = '';
6037
+ placeholder.textContent = t('notemd_provider_template_select', 'Choose a preset template');
6038
+ providerTemplateSelect.appendChild(placeholder);
6039
+ const templates = Array.isArray(notemdProviderModalState.templates) ? notemdProviderModalState.templates : [];
6040
+ templates.forEach((template) => {
6041
+ const option = document.createElement('option');
6042
+ option.value = template.id;
6043
+ option.textContent = `${template.label} (${template.category})`;
6044
+ providerTemplateSelect.appendChild(option);
6045
+ });
6046
+ };
6047
+
6048
+ const populateProviderSelect = () => {
6049
+ if (!providerNameSelect) {
6050
+ return;
6051
+ }
6052
+ providerNameSelect.innerHTML = '';
6053
+ const settings = notemdProviderModalState.settings;
6054
+ const providers = settings && Array.isArray(settings.providers) ? settings.providers : [];
6055
+ providers.forEach((provider) => {
6056
+ const option = document.createElement('option');
6057
+ option.value = provider.name;
6058
+ option.textContent = provider.name;
6059
+ providerNameSelect.appendChild(option);
6060
+ });
6061
+ if (settings && settings.activeProvider) {
6062
+ providerNameSelect.value = settings.activeProvider;
6063
+ }
6064
+ renderSelectedProviderFields();
6065
+ };
6066
+
6067
+ const loadNotemdProviderModalState = async (options = {}) => {
6068
+ if (notemdProviderModalState.loading) {
6069
+ return;
6070
+ }
6071
+ setProviderModalBusy(true);
6072
+ setProviderModalStatus(t('notemd_provider_loading', 'Loading provider settings...'));
6073
+ try {
6074
+ const templatesPayload = await requestRuntimeJson('/api/notemd/provider-templates?persist=1');
6075
+ const settingsPayload = await requestRuntimeJson('/api/notemd/settings');
6076
+ notemdProviderModalState.templates = Array.isArray(templatesPayload.templates)
6077
+ ? cloneJson(templatesPayload.templates)
6078
+ : [];
6079
+ notemdProviderModalState.configPath = String(templatesPayload.configPath || '');
6080
+ notemdProviderModalState.settings = cloneJson(settingsPayload.settings || {});
6081
+ populateProviderTemplateSelect();
6082
+ populateProviderSelect();
6083
+ if (providerConfigPath) {
6084
+ providerConfigPath.textContent = notemdProviderModalState.configPath
6085
+ ? `${t('notemd_provider_config_path', 'TOML path')}: ${notemdProviderModalState.configPath}`
6086
+ : '';
6087
+ }
6088
+ notemdProviderModalState.lastSavedFingerprint = getCurrentProviderFingerprint();
6089
+ setProviderModalStatus(
6090
+ t('notemd_provider_loaded', 'Provider presets and TOML templates are ready.'),
6091
+ 'success'
6092
+ );
6093
+ } catch (error) {
6094
+ setProviderModalStatus(
6095
+ t('notemd_provider_load_failed', 'Provider settings load failed: {error}', {
6096
+ error: error && error.message ? error.message : String(error),
6097
+ }),
6098
+ 'error'
6099
+ );
6100
+ } finally {
6101
+ setProviderModalBusy(false);
6102
+ }
6103
+ };
6104
+
6105
+ const saveNotemdProviderModalState = async (options = {}) => {
6106
+ if (!notemdProviderModalState.settings || !providerNameSelect) {
6107
+ return;
6108
+ }
6109
+ const auto = options.auto === true;
6110
+ const nextSettings = cloneJson(notemdProviderModalState.settings);
6111
+ const providerName = String(providerNameSelect.value || '').trim();
6112
+ const nextProvider = nextSettings.providers.find((provider) => provider.name === providerName);
6113
+ if (!nextProvider) {
6114
+ throw new Error(`Unsupported provider: ${providerName}`);
6115
+ }
6116
+ nextSettings.activeProvider = providerName;
6117
+ nextProvider.baseUrl = String(providerBaseUrlInput ? providerBaseUrlInput.value : '').trim();
6118
+ nextProvider.model = String(providerModelInput ? providerModelInput.value : '').trim();
6119
+ nextProvider.apiKey = String(providerApiKeyInput ? providerApiKeyInput.value : '');
6120
+ nextProvider.apiVersion = String(providerApiVersionInput ? providerApiVersionInput.value : '').trim();
6121
+ setProviderModalBusy(true);
6122
+ setProviderModalStatus(
6123
+ auto
6124
+ ? t('notemd_provider_autosaving', 'Auto-saving provider settings...')
6125
+ : t('notemd_provider_saving', 'Saving provider settings...')
6126
+ );
6127
+ try {
6128
+ const payload = await requestRuntimeJson('/api/notemd/settings', {
6129
+ method: 'POST',
6130
+ headers: { 'Content-Type': 'application/json' },
6131
+ body: JSON.stringify({ settings: nextSettings }),
6132
+ });
6133
+ notemdProviderModalState.settings = cloneJson(payload.settings || nextSettings);
6134
+ populateProviderSelect();
6135
+ notemdProviderModalState.lastSavedFingerprint = getCurrentProviderFingerprint();
6136
+ removeProviderDraft(providerName);
6137
+ setProviderModalStatus(
6138
+ auto
6139
+ ? t('notemd_provider_autosaved', 'Provider settings auto-saved.')
6140
+ : t('notemd_provider_saved', 'Provider settings saved to app_config.toml.'),
6141
+ 'success'
6142
+ );
6143
+ } finally {
6144
+ setProviderModalBusy(false);
6145
+ }
6146
+ };
6147
+
6148
+ const scheduleProviderAutosave = () => {
6149
+ if (notemdProviderModalState.loading || notemdProviderModalState.applyingDraft) {
6150
+ return;
6151
+ }
6152
+ const providerName = String(providerNameSelect ? providerNameSelect.value : '').trim();
6153
+ if (!providerName || !notemdProviderModalState.settings) {
6154
+ return;
6155
+ }
6156
+ const nextFingerprint = getCurrentProviderFingerprint();
6157
+ if (!nextFingerprint || nextFingerprint === notemdProviderModalState.lastSavedFingerprint) {
6158
+ return;
6159
+ }
6160
+ clearProviderAutosaveTimer();
6161
+ notemdProviderModalState.autosaveTimer = setTimeout(() => {
6162
+ notemdProviderModalState.autosaveTimer = null;
6163
+ void saveNotemdProviderModalState({ auto: true }).catch((error) => {
6164
+ setProviderModalStatus(
6165
+ t('notemd_provider_autosave_failed', 'Auto-save failed: {error}', {
6166
+ error: error && error.message ? error.message : String(error),
6167
+ }),
6168
+ 'error'
6169
+ );
6170
+ });
6171
+ }, NOTEMD_PROVIDER_AUTOSAVE_DEBOUNCE_MS);
6172
+ };
6173
+
6174
+ const applyNotemdProviderTemplate = async () => {
6175
+ if (!providerTemplateSelect) {
6176
+ return;
6177
+ }
6178
+ const templateId = String(providerTemplateSelect.value || '').trim();
6179
+ if (!templateId) {
6180
+ setProviderModalStatus(t('notemd_provider_template_required', 'Choose a preset template first.'), 'error');
6181
+ return;
6182
+ }
6183
+ setProviderModalBusy(true);
6184
+ setProviderModalStatus(t('notemd_provider_template_applying', 'Applying provider preset...'));
6185
+ try {
6186
+ const payload = await requestRuntimeJson('/api/notemd/provider-templates/apply', {
6187
+ method: 'POST',
6188
+ headers: { 'Content-Type': 'application/json' },
6189
+ body: JSON.stringify({ templateId }),
6190
+ });
6191
+ notemdProviderModalState.settings = cloneJson(payload.settings || {});
6192
+ if (payload.configPath) {
6193
+ notemdProviderModalState.configPath = String(payload.configPath);
6194
+ }
6195
+ populateProviderSelect();
6196
+ if (providerConfigPath) {
6197
+ providerConfigPath.textContent = notemdProviderModalState.configPath
6198
+ ? `${t('notemd_provider_config_path', 'TOML path')}: ${notemdProviderModalState.configPath}`
6199
+ : '';
6200
+ }
6201
+ notemdProviderModalState.lastSavedFingerprint = getCurrentProviderFingerprint();
6202
+ removeProviderDraft(String(providerNameSelect ? providerNameSelect.value : '').trim());
6203
+ setProviderModalStatus(
6204
+ t('notemd_provider_template_applied', 'Preset applied and saved to app_config.toml.'),
6205
+ 'success'
6206
+ );
6207
+ } finally {
6208
+ setProviderModalBusy(false);
6209
+ }
6210
+ };
6211
+
6212
+ const materializeNotemdProviderTemplates = async () => {
6213
+ setProviderModalBusy(true);
6214
+ setProviderModalStatus(t('notemd_provider_templates_writing', 'Writing TOML provider templates...'));
6215
+ try {
6216
+ const payload = await requestRuntimeJson('/api/notemd/provider-templates?persist=1');
6217
+ notemdProviderModalState.templates = Array.isArray(payload.templates)
6218
+ ? cloneJson(payload.templates)
6219
+ : [];
6220
+ notemdProviderModalState.configPath = String(payload.configPath || notemdProviderModalState.configPath || '');
6221
+ populateProviderTemplateSelect();
6222
+ renderProviderTemplateHint();
6223
+ if (providerConfigPath) {
6224
+ providerConfigPath.textContent = notemdProviderModalState.configPath
6225
+ ? `${t('notemd_provider_config_path', 'TOML path')}: ${notemdProviderModalState.configPath}`
6226
+ : '';
6227
+ }
6228
+ setProviderModalStatus(
6229
+ payload.persisted
6230
+ ? t('notemd_provider_templates_written', 'Provider templates were written to app_config.toml.')
6231
+ : t('notemd_provider_templates_present', 'Provider templates are already present in app_config.toml.'),
6232
+ 'success'
6233
+ );
6234
+ } finally {
6235
+ setProviderModalBusy(false);
6236
+ }
6237
+ };
6238
+
6239
+ const testNotemdProviderConnection = async () => {
6240
+ const payload = buildCurrentProviderRequestPayload();
6241
+ if (!payload.providerName) {
6242
+ throw new Error('Provider name is required.');
6243
+ }
6244
+
6245
+ setProviderModalBusy(true);
6246
+ setProviderModalStatus(
6247
+ t('btn_test_provider_connection', 'Test Connection') + '...',
6248
+ 'muted'
6249
+ );
6250
+ try {
6251
+ const response = await requestRuntimeJson('/api/notemd/test-llm', {
6252
+ method: 'POST',
6253
+ headers: { 'Content-Type': 'application/json' },
6254
+ body: JSON.stringify(payload),
6255
+ });
6256
+ const result = response.result || {};
6257
+ setProviderModalStatus(
6258
+ String(result.message || `Connected to ${payload.providerName}.`),
6259
+ result.success === false ? 'error' : 'success'
6260
+ );
6261
+ } finally {
6262
+ setProviderModalBusy(false);
6263
+ }
6264
+ };
6265
+
4403
6266
  // Load initial values
4404
6267
  const updateUIFromSettings = (settings) => {
4405
6268
  const mode = document.querySelector('input[name="layoutMode"]:checked') ? document.querySelector('input[name="layoutMode"]:checked').value : 'force';
@@ -4441,6 +6304,9 @@ function initSettingsUI() {
4441
6304
  if (settings.performance.enableGPU !== undefined) {
4442
6305
  if (gpuCheckbox) gpuCheckbox.checked = settings.performance.enableGPU;
4443
6306
  }
6307
+ if (settings.performance.staticMode !== undefined) {
6308
+ if (staticModeCheckbox) staticModeCheckbox.checked = settings.performance.staticMode;
6309
+ }
4444
6310
  if (settings.performance.gpuRendering !== undefined) {
4445
6311
  if (gpuRenderingCheckbox) gpuRenderingCheckbox.checked = settings.performance.gpuRendering;
4446
6312
  }
@@ -4503,6 +6369,12 @@ function initSettingsUI() {
4503
6369
  });
4504
6370
  }
4505
6371
 
6372
+ if (staticModeCheckbox) {
6373
+ staticModeCheckbox.addEventListener('change', (e) => {
6374
+ settingsManager.set('performance', 'staticMode', e.target.checked);
6375
+ });
6376
+ }
6377
+
4506
6378
  if (gpuRenderingCheckbox) {
4507
6379
  gpuRenderingCheckbox.addEventListener('change', (e) => {
4508
6380
  settingsManager.set('performance', 'gpuRendering', e.target.checked);
@@ -4548,33 +6420,132 @@ function initSettingsUI() {
4548
6420
  settingsManager.set('reading', 'mode', e.target.value);
4549
6421
  });
4550
6422
 
4551
- // Helper to close settings
4552
- const closeSettings = () => {
4553
- modal.style.display = 'none';
4554
- isSettingsModalOpen = false;
4555
- // Resume if not globally frozen
4556
- const isFrozen = document.getElementById('freeze-layout') ? document.getElementById('freeze-layout').checked : false;
4557
- if (!isFrozen) {
4558
- simulation.alpha(0.3).restart();
6423
+ if (providerTemplateSelect) {
6424
+ providerTemplateSelect.addEventListener('change', () => {
6425
+ renderProviderTemplateHint();
6426
+ syncCurrentProviderDraft();
6427
+ scheduleProviderAutosave();
6428
+ });
6429
+ }
6430
+
6431
+ if (providerNameSelect) {
6432
+ providerNameSelect.addEventListener('change', () => {
6433
+ renderSelectedProviderFields();
6434
+ syncCurrentProviderDraft();
6435
+ scheduleProviderAutosave();
6436
+ });
6437
+ }
6438
+
6439
+ [
6440
+ providerBaseUrlInput,
6441
+ providerModelInput,
6442
+ providerApiKeyInput,
6443
+ providerApiVersionInput,
6444
+ ].forEach((input) => {
6445
+ if (!input) {
6446
+ return;
4559
6447
  }
4560
- };
6448
+ input.addEventListener('input', () => {
6449
+ syncCurrentProviderDraft();
6450
+ scheduleProviderAutosave();
6451
+ });
6452
+ input.addEventListener('change', () => {
6453
+ syncCurrentProviderDraft();
6454
+ scheduleProviderAutosave();
6455
+ });
6456
+ });
6457
+
6458
+ if (applyProviderTemplateBtn) {
6459
+ applyProviderTemplateBtn.addEventListener('click', () => {
6460
+ void applyNotemdProviderTemplate().catch((error) => {
6461
+ setProviderModalStatus(
6462
+ t('notemd_provider_template_apply_failed', 'Provider preset apply failed: {error}', {
6463
+ error: error && error.message ? error.message : String(error),
6464
+ }),
6465
+ 'error'
6466
+ );
6467
+ });
6468
+ });
6469
+ }
6470
+
6471
+ if (testProviderBtn) {
6472
+ testProviderBtn.addEventListener('click', () => {
6473
+ void testNotemdProviderConnection().catch((error) => {
6474
+ setProviderModalStatus(
6475
+ error && error.message ? error.message : String(error),
6476
+ 'error'
6477
+ );
6478
+ });
6479
+ });
6480
+ }
6481
+
6482
+ if (saveProviderBtn) {
6483
+ saveProviderBtn.addEventListener('click', () => {
6484
+ void saveNotemdProviderModalState().catch((error) => {
6485
+ setProviderModalStatus(
6486
+ t('notemd_provider_save_failed', 'Provider save failed: {error}', {
6487
+ error: error && error.message ? error.message : String(error),
6488
+ }),
6489
+ 'error'
6490
+ );
6491
+ });
6492
+ });
6493
+ }
6494
+
6495
+ if (materializeProviderTemplatesBtn) {
6496
+ materializeProviderTemplatesBtn.addEventListener('click', () => {
6497
+ void materializeNotemdProviderTemplates().catch((error) => {
6498
+ setProviderModalStatus(
6499
+ t('notemd_provider_templates_write_failed', 'Template write failed: {error}', {
6500
+ error: error && error.message ? error.message : String(error),
6501
+ }),
6502
+ 'error'
6503
+ );
6504
+ });
6505
+ });
6506
+ }
4561
6507
 
4562
6508
  // Modal Actions
4563
- // v0.9.42: When opening settings, update UI to reflect current mode's values
4564
6509
  openBtn.addEventListener('click', () => {
4565
6510
  updateUIFromSettings(settingsManager.settings);
4566
- modal.style.display = 'flex';
4567
- isSettingsModalOpen = true;
4568
- simulation.stop(); // v0.9.41: Force freeze to save resources
4569
- });
4570
-
4571
- closeBtns.forEach(btn => btn.addEventListener('click', closeSettings));
4572
-
4573
- // Close on click outside
4574
- modal.addEventListener('click', (e) => {
4575
- if (e.target === modal) closeSettings();
6511
+ showMainSettingsPage();
4576
6512
  });
4577
6513
 
6514
+ if (openAgentSettingsBtn) {
6515
+ openAgentSettingsBtn.addEventListener('click', () => {
6516
+ void showAgentSettingsPage().catch((error) => {
6517
+ console.warn('[Settings] Failed to open agent settings.', error);
6518
+ });
6519
+ });
6520
+ }
6521
+
6522
+ if (agentSettingsBackBtn) {
6523
+ agentSettingsBackBtn.addEventListener('click', showMainSettingsPage);
6524
+ }
6525
+
6526
+ if (agentSettingsBackFooterBtn) {
6527
+ agentSettingsBackFooterBtn.addEventListener('click', showMainSettingsPage);
6528
+ }
6529
+
6530
+ mainCloseBtns.forEach((btn) => btn.addEventListener('click', closeAllSettingsPages));
6531
+ agentCloseBtns.forEach((btn) => btn.addEventListener('click', closeAllSettingsPages));
6532
+
6533
+ if (modal) {
6534
+ modal.addEventListener('click', (e) => {
6535
+ if (e.target === modal) {
6536
+ closeAllSettingsPages();
6537
+ }
6538
+ });
6539
+ }
6540
+
6541
+ if (agentModal) {
6542
+ agentModal.addEventListener('click', (e) => {
6543
+ if (e.target === agentModal) {
6544
+ closeAllSettingsPages();
6545
+ }
6546
+ });
6547
+ }
6548
+
4578
6549
  resetBtn.addEventListener('click', () => {
4579
6550
  settingsManager.reset();
4580
6551
  updateUIFromSettings(settingsManager.settings);
@@ -4588,7 +6559,7 @@ function initSettingsUI() {
4588
6559
 
4589
6560
  // v0.9.40: Check Freeze Layout State before restarting
4590
6561
  const globalFreeze = document.getElementById('freeze-layout') ? document.getElementById('freeze-layout').checked : false;
4591
- const isFrozen = globalFreeze || isSettingsModalOpen;
6562
+ const isFrozen = globalFreeze || isAnySettingsPanelOpen();
4592
6563
 
4593
6564
  if (!isFrozen) {
4594
6565
  simulation.alpha(0.3).restart();
@@ -4799,6 +6770,151 @@ function getTauriDialogApi() {
4799
6770
  return null;
4800
6771
  }
4801
6772
 
6773
+ function normalizePathModeRuntimeText(value) {
6774
+ return String(value || '').replace(/\s+/g, ' ').trim();
6775
+ }
6776
+
6777
+ function normalizePathModeLanguage(value) {
6778
+ const raw = normalizePathModeRuntimeText(value).toLowerCase();
6779
+ return raw.startsWith('zh') ? 'zh' : 'en';
6780
+ }
6781
+
6782
+ function resolvePathModeRuntimeTarget(targetId) {
6783
+ const normalizedTargetId = normalizePathModeRuntimeText(targetId);
6784
+ if (!normalizedTargetId) {
6785
+ return null;
6786
+ }
6787
+ const node = resolveGraphViewNodeByIdOrLabel(normalizedTargetId);
6788
+ if (!node) {
6789
+ return {
6790
+ id: normalizedTargetId,
6791
+ label: normalizedTargetId,
6792
+ resolved: false,
6793
+ };
6794
+ }
6795
+ return {
6796
+ id: node.id,
6797
+ label: getGraphViewNodeLabel(node) || node.id,
6798
+ resolved: true,
6799
+ };
6800
+ }
6801
+
6802
+ function buildGodotFuturePathRuntimeConfig(targetId, options = {}) {
6803
+ const runtimeTarget = resolvePathModeRuntimeTarget(targetId);
6804
+ if (!runtimeTarget || !runtimeTarget.id) {
6805
+ return null;
6806
+ }
6807
+ const existingConfig = options.config && typeof options.config === 'object'
6808
+ ? options.config
6809
+ : {};
6810
+ const targetIds = [];
6811
+ const seen = new Set();
6812
+ const appendTargetId = function(value) {
6813
+ const normalized = normalizePathModeRuntimeText(value);
6814
+ if (!normalized || seen.has(normalized)) {
6815
+ return;
6816
+ }
6817
+ seen.add(normalized);
6818
+ targetIds.push(normalized);
6819
+ };
6820
+ appendTargetId(runtimeTarget.id);
6821
+ if (Array.isArray(existingConfig.targetIds)) {
6822
+ existingConfig.targetIds.forEach(appendTargetId);
6823
+ }
6824
+ return {
6825
+ ...existingConfig,
6826
+ mode: 'diffusion',
6827
+ strategy: 'core',
6828
+ layout: 'orbital',
6829
+ targetId: runtimeTarget.id,
6830
+ target_id: runtimeTarget.id,
6831
+ targetIds,
6832
+ focus_mode: true,
6833
+ language: normalizePathModeLanguage(
6834
+ existingConfig.language
6835
+ || window.i18n && window.i18n.currentLanguage
6836
+ || document.documentElement && document.documentElement.lang
6837
+ || 'en'
6838
+ ),
6839
+ };
6840
+ }
6841
+
6842
+ async function openGodotFuturePathById(targetId, options = {}) {
6843
+ const config = buildGodotFuturePathRuntimeConfig(targetId, options);
6844
+ if (!config) {
6845
+ throw new Error('Missing Godot Future Path target node.');
6846
+ }
6847
+ window.__NC_LAST_GODOT_FUTURE_PATH_REQUEST = { ...config };
6848
+
6849
+ const pathApp = window.pathApp;
6850
+ if (pathApp && typeof pathApp === 'object') {
6851
+ if (!window.__NC_AGENT_GODOT_FUTURE_PATH_INITIALIZED && typeof pathApp.init === 'function') {
6852
+ pathApp.init(config.targetId);
6853
+ window.__NC_AGENT_GODOT_FUTURE_PATH_INITIALIZED = true;
6854
+ }
6855
+ if (pathApp.runtimeConfig && typeof pathApp.runtimeConfig === 'object') {
6856
+ pathApp.runtimeConfig.mode = 'diffusion';
6857
+ pathApp.runtimeConfig.strategy = 'core';
6858
+ pathApp.runtimeConfig.layout = 'orbital';
6859
+ pathApp.runtimeConfig.targetId = config.targetId;
6860
+ pathApp.runtimeConfig.targetIds = config.targetIds.slice();
6861
+ }
6862
+ pathApp.currentTargetId = config.targetId;
6863
+ pathApp.currentTargetIds = config.targetIds.slice();
6864
+ pathApp.centralNodeId = config.targetId;
6865
+ if (typeof pathApp.applyRemoteConfigure === 'function') {
6866
+ pathApp.applyRemoteConfigure(config);
6867
+ }
6868
+ if (typeof pathApp._sendBridgeMessage === 'function') {
6869
+ pathApp._sendBridgeMessage('configure', config);
6870
+ }
6871
+ if (typeof pathApp.triggerUpdate === 'function') {
6872
+ pathApp.triggerUpdate();
6873
+ }
6874
+ if (typeof pathApp.requestBridgeWindowVisibility === 'function') {
6875
+ const bridgeReady = await pathApp.requestBridgeWindowVisibility(true, {
6876
+ waitMs: 1800,
6877
+ reason: options.source || 'open-godot-future-path',
6878
+ });
6879
+ if (bridgeReady && typeof pathApp._sendBridgeMessage === 'function') {
6880
+ pathApp._sendBridgeMessage('configure', config);
6881
+ }
6882
+ }
6883
+ }
6884
+
6885
+ const invoke = getTauriCoreInvoke();
6886
+ if (invoke) {
6887
+ const caps = window.__NC_RUNTIME_CAPS || {};
6888
+ if (caps.platform === 'android' && caps.supports_native_pathmode === true) {
6889
+ await invoke('open_native_pathmode', {
6890
+ request: {
6891
+ mode: 'diffusion',
6892
+ strategy: 'core',
6893
+ targetId: config.targetId,
6894
+ },
6895
+ });
6896
+ } else {
6897
+ await invoke('toggle_pathmode_window', { showGodot: true });
6898
+ }
6899
+ }
6900
+
6901
+ return {
6902
+ opened: true,
6903
+ targetId: config.targetId,
6904
+ targetIds: config.targetIds.slice(),
6905
+ strategy: config.strategy,
6906
+ mode: config.mode,
6907
+ };
6908
+ }
6909
+
6910
+ window.NoteConnectionPathMode = {
6911
+ ...(window.NoteConnectionPathMode && typeof window.NoteConnectionPathMode === 'object'
6912
+ ? window.NoteConnectionPathMode
6913
+ : {}),
6914
+ buildGodotFuturePathRuntimeConfig,
6915
+ openGodotFuturePathById,
6916
+ };
6917
+
4802
6918
  function normalizeNotemdPickerPayload(payload) {
4803
6919
  const safePayload = payload && typeof payload === 'object' ? { ...payload } : {};
4804
6920
  const initialPath = typeof safePayload.initialPath === 'string'