noteconnection 1.6.8 → 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 (212) hide show
  1. package/LICENSE +674 -21
  2. package/README.md +258 -64
  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 +258 -64
  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 +3597 -141
  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 +2189 -150
  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 +522 -27
  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/simulationWorker.js +241 -6
  50. package/dist/src/frontend/source_manager.js +190 -21
  51. package/dist/src/frontend/styles.css +2853 -72
  52. package/dist/src/frontend/workbench_state.mjs +101 -0
  53. package/dist/src/frontend/workspace_panes.js +10168 -0
  54. package/dist/src/frontend.locale.contract.test.js +62 -0
  55. package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
  56. package/dist/src/indexing/IndexLifecycle.js +195 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +49 -0
  58. package/dist/src/indexing/SegmentBuilder.js +64 -0
  59. package/dist/src/indexing/UnitBuilder.js +48 -0
  60. package/dist/src/indexing/types.js +2 -0
  61. package/dist/src/knowledge.api.contract.test.js +170 -0
  62. package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
  64. package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
  68. package/dist/src/learning/answerReleaseReview.js +4319 -0
  69. package/dist/src/learning/answerReleaseReview.test.js +2888 -0
  70. package/dist/src/learning/api.js +2 -0
  71. package/dist/src/learning/conversationComposer.js +1480 -0
  72. package/dist/src/learning/conversationComposer.test.js +1817 -0
  73. package/dist/src/learning/domains/ConversationManager.js +53 -0
  74. package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
  75. package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
  76. package/dist/src/learning/domains/MasteryEngine.js +387 -0
  77. package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
  78. package/dist/src/learning/domains/QualityEvaluator.js +307 -0
  79. package/dist/src/learning/domains/TutorRouter.js +313 -0
  80. package/dist/src/learning/domains/index.js +33 -0
  81. package/dist/src/learning/domains/types.js +7 -0
  82. package/dist/src/learning/errors.js +29 -0
  83. package/dist/src/learning/evidenceContextAssembler.js +1176 -0
  84. package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
  85. package/dist/src/learning/graphContextAssembler.js +870 -0
  86. package/dist/src/learning/graphContextAssembler.test.js +1033 -0
  87. package/dist/src/learning/index.js +28 -0
  88. package/dist/src/learning/queryBackend.js +1898 -0
  89. package/dist/src/learning/queryBackend.test.js +955 -0
  90. package/dist/src/learning/ragContextPack.js +257 -0
  91. package/dist/src/learning/ragContextPack.test.js +160 -0
  92. package/dist/src/learning/ragPublicText.js +38 -0
  93. package/dist/src/learning/ragSufficiencyJudge.js +161 -0
  94. package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
  95. package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
  96. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
  97. package/dist/src/learning/requestNormalization.js +198 -0
  98. package/dist/src/learning/runtimeCapability.js +4677 -0
  99. package/dist/src/learning/runtimeCapability.test.js +3635 -0
  100. package/dist/src/learning/store.js +1240 -0
  101. package/dist/src/learning/store.test.js +1126 -0
  102. package/dist/src/learning/tutorAdapter.js +2 -0
  103. package/dist/src/learning/types.js +2 -0
  104. package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
  105. package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
  106. package/dist/src/lfs.asset.policy.contract.test.js +153 -0
  107. package/dist/src/license.policy.contract.test.js +66 -0
  108. package/dist/src/memory/MemoryGovernance.js +74 -0
  109. package/dist/src/memory/MemoryGovernance.test.js +46 -0
  110. package/dist/src/memory/types.js +2 -0
  111. package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
  112. package/dist/src/middleware/auth.js +17 -0
  113. package/dist/src/middleware/body-parser.js +45 -0
  114. package/dist/src/middleware/cors.js +44 -0
  115. package/dist/src/middleware/index.js +21 -0
  116. package/dist/src/middleware/request-trace.js +96 -0
  117. package/dist/src/notemd/AppConfigToml.js +6 -4
  118. package/dist/src/notemd/MermaidProcessor.js +400 -50
  119. package/dist/src/notemd/NotemdService.js +498 -13
  120. package/dist/src/notemd/PromptManager.js +15 -0
  121. package/dist/src/notemd/cli/commands.js +357 -0
  122. package/dist/src/notemd/cli/dispatcher.js +225 -0
  123. package/dist/src/notemd/cli/index.js +169 -0
  124. package/dist/src/notemd/cli/parser.js +68 -0
  125. package/dist/src/notemd/cli/types.js +2 -0
  126. package/dist/src/notemd/constants.js +43 -0
  127. package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
  128. package/dist/src/notemd/diagram/diagramSpec.js +79 -0
  129. package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
  130. package/dist/src/notemd/diagram/intent.js +95 -0
  131. package/dist/src/notemd/diagram/planner.js +71 -0
  132. package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
  133. package/dist/src/notemd/diagram/types.js +18 -0
  134. package/dist/src/notemd/index.js +26 -0
  135. package/dist/src/notemd/operations/capabilityManifest.js +23 -0
  136. package/dist/src/notemd/operations/cliContracts.js +17 -0
  137. package/dist/src/notemd/operations/configProfileCommands.js +85 -0
  138. package/dist/src/notemd/operations/registry.contract.test.js +95 -0
  139. package/dist/src/notemd/operations/registry.js +991 -0
  140. package/dist/src/notemd/operations/types.js +2 -0
  141. package/dist/src/notemd/providerDiagnostics.js +220 -0
  142. package/dist/src/notemd/providerProfiles.js +42 -0
  143. package/dist/src/notemd/providerTemplates.js +231 -0
  144. package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
  145. package/dist/src/notemd/search/SearchManager.js +13 -0
  146. package/dist/src/notemd/search/SearchProvider.js +2 -0
  147. package/dist/src/notemd/search/TavilyProvider.js +44 -0
  148. package/dist/src/notemd.agent.manifest.test.js +85 -0
  149. package/dist/src/notemd.api.contract.test.js +14 -1
  150. package/dist/src/notemd.app_config_toml.test.js +2 -1
  151. package/dist/src/notemd.batch.workflow.test.js +117 -0
  152. package/dist/src/notemd.cli.e2e.test.js +136 -0
  153. package/dist/src/notemd.core.test.js +51 -0
  154. package/dist/src/notemd.diagram.pipeline.test.js +233 -0
  155. package/dist/src/notemd.providerTemplates.test.js +34 -0
  156. package/dist/src/notemd.server.integration.test.js +143 -35
  157. package/dist/src/notemd.workflow.pipeline.test.js +162 -0
  158. package/dist/src/pathbridge.handshake.contract.test.js +16 -2
  159. package/dist/src/pathmode.background.contract.test.js +69 -0
  160. package/dist/src/pathmode.settings.api.contract.test.js +9 -0
  161. package/dist/src/pkg.sidecar.contract.test.js +9 -3
  162. package/dist/src/platform/ExportProfile.js +58 -0
  163. package/dist/src/platform/PlatformCapabilities.js +45 -0
  164. package/dist/src/platform/PlatformCapabilities.test.js +30 -0
  165. package/dist/src/platform/RenderMaterializer.js +33 -0
  166. package/dist/src/platform/RenderMaterializer.test.js +32 -0
  167. package/dist/src/query_backend.external_http.integration.test.js +410 -0
  168. package/dist/src/reader_renderer.js +404 -3
  169. package/dist/src/reader_renderer.test.js +87 -0
  170. package/dist/src/release.godot.mirror.contract.test.js +73 -0
  171. package/dist/src/resources/ResourceRegistry.js +223 -0
  172. package/dist/src/resources/ResourceRegistry.test.js +61 -0
  173. package/dist/src/resources/types.js +2 -0
  174. package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
  175. package/dist/src/routes/data.js +267 -0
  176. package/dist/src/routes/diagnostics.js +51 -0
  177. package/dist/src/routes/index.js +23 -0
  178. package/dist/src/routes/knowledge.js +968 -0
  179. package/dist/src/routes/markdown.js +287 -0
  180. package/dist/src/routes/notemd.js +565 -0
  181. package/dist/src/routes/registry.contract.test.js +130 -0
  182. package/dist/src/routes/render.js +285 -0
  183. package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
  184. package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
  185. package/dist/src/routes/settings.js +6 -0
  186. package/dist/src/routes/staticFiles.js +94 -0
  187. package/dist/src/routes/types.js +2 -0
  188. package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
  189. package/dist/src/server.js +11090 -1549
  190. package/dist/src/server.migration.test.js +193 -21
  191. package/dist/src/server.port.fallback.contract.test.js +63 -0
  192. package/dist/src/session/SessionStateStore.js +81 -0
  193. package/dist/src/session/SessionStateStore.test.js +58 -0
  194. package/dist/src/session/types.js +2 -0
  195. package/dist/src/settings.runtime.contract.test.js +50 -0
  196. package/dist/src/shared/types.contract.test.js +107 -0
  197. package/dist/src/shared/types.js +22 -0
  198. package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
  199. package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
  200. package/dist/src/source_manager.loadflow.test.js +46 -0
  201. package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
  202. package/dist/src/tauri.frontend.build.contract.test.js +60 -0
  203. package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
  204. package/dist/src/utils/RuntimePaths.js +4 -13
  205. package/dist/src/utils/platform.js +153 -0
  206. package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
  207. package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
  208. package/dist/src/workflows/types.js +2 -0
  209. package/dist/src/workspace/WorkspaceRegistry.js +122 -0
  210. package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
  211. package/dist/src/workspace/types.js +2 -0
  212. package/package.json +61 -10
@@ -9,12 +9,19 @@ class Reader {
9
9
  this.contentBox = document.getElementById('reading-content-box');
10
10
  this.body = document.getElementById('reading-body');
11
11
  this.title = document.getElementById('reading-title');
12
+ this._touchZoomBoundBody = null;
12
13
 
13
14
  this.isLocked = true;
14
15
  this.currentZoom = 0.5;
15
16
  this._protocolRenderState = null;
16
17
  this._protocolScrollHandler = null;
17
18
  this._mermaidInitialized = false;
19
+ this._mermaidRenderStats = {
20
+ frontendRender: 0,
21
+ frontendRun: 0,
22
+ backendPng: 0,
23
+ failed: 0,
24
+ };
18
25
 
19
26
  this.init();
20
27
  }
@@ -45,14 +52,50 @@ class Reader {
45
52
  }
46
53
 
47
54
  // Touch Gestures (Pinch to Zoom)
48
- this.initTouchZoom();
55
+ this.bindTouchZoomSurface();
56
+ }
57
+
58
+ refreshDomRefs() {
59
+ this.window = document.getElementById('reading-window');
60
+ this.contentBox = document.getElementById('reading-content-box');
61
+ this.body = document.getElementById('reading-body');
62
+ this.title = document.getElementById('reading-title');
63
+ }
64
+
65
+ ensureReaderStructure() {
66
+ this.refreshDomRefs();
67
+ if (!this.window || !this.contentBox || !this.title) {
68
+ throw new Error('Reader shell structure is unavailable in the current document.');
69
+ }
70
+
71
+ const existingBody = document.getElementById('reading-body');
72
+ const bodyDetached = !!(existingBody && !this.contentBox.contains(existingBody));
73
+ if (!existingBody || bodyDetached) {
74
+ const replacementBody = document.createElement('div');
75
+ replacementBody.id = 'reading-body';
76
+ replacementBody.className = `reading-body ${this.isLocked ? 'locked' : 'unlocked'}`;
77
+ replacementBody.style.fontSize = `${this.currentZoom}rem`;
78
+ this.contentBox.appendChild(replacementBody);
79
+ this.body = replacementBody;
80
+ } else {
81
+ this.body = existingBody;
82
+ }
83
+
84
+ this.bindTouchZoomSurface();
85
+ return this.body;
49
86
  }
50
-
51
- initTouchZoom() {
87
+
88
+ bindTouchZoomSurface() {
89
+ const body = this.ensureReaderStructureBodyOnly();
90
+ if (!body || this._touchZoomBoundBody === body) {
91
+ return;
92
+ }
93
+ this._touchZoomBoundBody = body;
94
+
52
95
  let initialDistance = 0;
53
96
  let initialZoom = 1.0;
54
-
55
- this.body.addEventListener('touchstart', (e) => {
97
+
98
+ body.addEventListener('touchstart', (e) => {
56
99
  if (e.touches.length === 2) {
57
100
  initialDistance = Math.hypot(
58
101
  e.touches[0].pageX - e.touches[1].pageX,
@@ -63,7 +106,7 @@ class Reader {
63
106
  }
64
107
  }, { passive: false });
65
108
 
66
- this.body.addEventListener('touchmove', (e) => {
109
+ body.addEventListener('touchmove', (e) => {
67
110
  if (e.touches.length === 2) {
68
111
  const currentDistance = Math.hypot(
69
112
  e.touches[0].pageX - e.touches[1].pageX,
@@ -83,14 +126,158 @@ class Reader {
83
126
  }
84
127
  }, { passive: false });
85
128
 
86
- this.body.addEventListener('touchend', (e) => {
129
+ body.addEventListener('touchend', (e) => {
87
130
  if (e.touches.length < 2) {
88
131
  initialDistance = 0;
89
132
  }
90
133
  });
91
134
  }
92
135
 
136
+ ensureReaderStructureBodyOnly() {
137
+ this.refreshDomRefs();
138
+ return this.body || null;
139
+ }
140
+
141
+ isMermaidErrorArtifactText(text) {
142
+ const normalized = String(text || '').toLowerCase();
143
+ if (!normalized) {
144
+ return false;
145
+ }
146
+ return (
147
+ normalized.includes('syntax error in text') ||
148
+ normalized.includes('lexical error on line') ||
149
+ normalized.includes('parse error on line') ||
150
+ normalized.includes('mermaid version') ||
151
+ normalized.includes('diagram syntax error')
152
+ );
153
+ }
154
+
155
+ 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') ||
162
+ node.closest('.mermaid-render-failed') ||
163
+ node.closest('.reader-block') ||
164
+ node.closest('svg') ||
165
+ node
166
+ );
167
+ }
168
+
169
+ 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
+ collectMermaidErrorArtifactHosts(root) {
186
+ const hosts = [];
187
+ const hostSet = new Set();
188
+ const candidates = [];
189
+ if (!root) {
190
+ return hosts;
191
+ }
192
+ if (
193
+ root.nodeType === Node.ELEMENT_NODE &&
194
+ root !== document.body &&
195
+ root !== document.documentElement &&
196
+ root !== document.head
197
+ ) {
198
+ candidates.push(root);
199
+ }
200
+ if (typeof root.querySelectorAll === 'function') {
201
+ root.querySelectorAll('svg, .mermaid, div, section, article, aside, img, foreignObject').forEach((node) => {
202
+ candidates.push(node);
203
+ });
204
+ }
205
+ candidates.forEach((candidate) => {
206
+ if (!candidate || candidate.nodeType !== Node.ELEMENT_NODE) {
207
+ return;
208
+ }
209
+ const text = String(
210
+ candidate.textContent ||
211
+ candidate.getAttribute?.('alt') ||
212
+ candidate.getAttribute?.('aria-label') ||
213
+ ''
214
+ ).trim();
215
+ const hasErrorClass = Boolean(
216
+ candidate.classList?.contains('error-icon') ||
217
+ candidate.querySelector?.('.error-icon')
218
+ );
219
+ if (!hasErrorClass && !this.isMermaidErrorArtifactText(text)) {
220
+ return;
221
+ }
222
+ const host = this.resolveMermaidErrorArtifactHost(candidate);
223
+ if (!host || hostSet.has(host) || this.isProtectedMermaidSuppressionHost(host)) {
224
+ return;
225
+ }
226
+ hostSet.add(host);
227
+ hosts.push(host);
228
+ });
229
+ return hosts;
230
+ }
231
+
232
+ suppressLeakedMermaidErrorArtifacts(options = {}) {
233
+ const root = options.root || document.body;
234
+ const preserveNode = options.preserveNode || null;
235
+ const hosts = this.collectMermaidErrorArtifactHosts(root);
236
+ hosts.forEach((host) => {
237
+ if (!host || host.closest?.('.mermaid-render-failed')) {
238
+ return;
239
+ }
240
+ if (preserveNode && (host === preserveNode || preserveNode.contains(host) || host.contains(preserveNode))) {
241
+ return;
242
+ }
243
+ if (host.parentNode) {
244
+ host.parentNode.removeChild(host);
245
+ } else if (host.style) {
246
+ host.style.display = 'none';
247
+ }
248
+ });
249
+ return hosts.length;
250
+ }
251
+
252
+ createOffscreenMermaidRenderHost(widthHint) {
253
+ const hostWidth = Math.max(480, Number(widthHint) || 960);
254
+ const host = document.createElement('div');
255
+ host.className = 'mermaid-render-host-offscreen';
256
+ host.style.position = 'fixed';
257
+ host.style.left = '-20000px';
258
+ host.style.top = '0';
259
+ host.style.width = String(hostWidth) + 'px';
260
+ host.style.minWidth = String(hostWidth) + 'px';
261
+ host.style.height = 'auto';
262
+ host.style.overflow = 'visible';
263
+ host.style.opacity = '0';
264
+ host.style.pointerEvents = 'none';
265
+ host.style.background = 'transparent';
266
+ host.style.fontFamily = '"Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", "Segoe UI", sans-serif';
267
+ const mountRoot = (
268
+ (this.window && this.window.ownerDocument && this.window.ownerDocument.body) ||
269
+ document.body ||
270
+ document.documentElement
271
+ );
272
+ if (!mountRoot || typeof mountRoot.appendChild !== 'function') {
273
+ throw new Error('Reader Mermaid offscreen host mount root is unavailable.');
274
+ }
275
+ mountRoot.appendChild(host);
276
+ return host;
277
+ }
278
+
93
279
  async open(node) {
280
+ this.ensureReaderStructure();
94
281
  const nodeLike = this.normalizeNodeInput(node);
95
282
  this.title.innerText = nodeLike.label;
96
283
 
@@ -119,6 +306,13 @@ class Reader {
119
306
  let rendered = false;
120
307
  if (filePath) {
121
308
  rendered = await this.tryRenderViaMarkdownProtocol(nodeLike, filePath, resolvedTarget, sessionId);
309
+ if (rendered && this.isReaderBodyVisiblyEmpty()) {
310
+ console.warn('[Reader] Markdown protocol path completed without visible content. Falling back to raw markdown render.', {
311
+ nodeId: nodeLike.id,
312
+ filePath,
313
+ });
314
+ rendered = false;
315
+ }
122
316
  }
123
317
  if (!rendered) {
124
318
  const rawContent = await this.loadRawContentFallback(nodeLike);
@@ -134,7 +328,7 @@ class Reader {
134
328
 
135
329
  normalizeNodeInput(nodeLike) {
136
330
  if (typeof nodeLike === 'string') {
137
- return {
331
+ nodeLike = {
138
332
  id: nodeLike,
139
333
  label: nodeLike,
140
334
  content: '',
@@ -143,15 +337,41 @@ class Reader {
143
337
  }
144
338
  const safeNode = (nodeLike && typeof nodeLike === 'object') ? nodeLike : {};
145
339
  const nodeId = String(safeNode.id || safeNode.label || 'unknown-node');
340
+ const hydratedNode = this.lookupNodeDataById(nodeId);
341
+ const hydratedMetadata = (hydratedNode && hydratedNode.metadata && typeof hydratedNode.metadata === 'object')
342
+ ? hydratedNode.metadata
343
+ : {};
344
+ const safeMetadata = (safeNode.metadata && typeof safeNode.metadata === 'object') ? safeNode.metadata : {};
146
345
  return {
346
+ ...(hydratedNode && typeof hydratedNode === 'object' ? hydratedNode : {}),
147
347
  ...safeNode,
148
348
  id: nodeId,
149
- label: String(safeNode.label || nodeId),
349
+ label: String(safeNode.label || hydratedNode?.label || nodeId),
150
350
  content: typeof safeNode.content === 'string' ? safeNode.content : '',
151
- metadata: (safeNode.metadata && typeof safeNode.metadata === 'object') ? safeNode.metadata : {},
351
+ metadata: {
352
+ ...hydratedMetadata,
353
+ ...safeMetadata,
354
+ },
152
355
  };
153
356
  }
154
357
 
358
+ lookupNodeDataById(nodeId) {
359
+ const normalizedNodeId = String(nodeId || '').trim();
360
+ if (!normalizedNodeId) {
361
+ return null;
362
+ }
363
+ const sourceData = (typeof graphData !== 'undefined') ? graphData : window.graphData;
364
+ if (!sourceData || !Array.isArray(sourceData.nodes)) {
365
+ return null;
366
+ }
367
+ const matchedNode = sourceData.nodes.find((item) => (
368
+ item &&
369
+ (String(item.id || '').trim() === normalizedNodeId ||
370
+ String(item.label || '').trim() === normalizedNodeId)
371
+ ));
372
+ return matchedNode && typeof matchedNode === 'object' ? matchedNode : null;
373
+ }
374
+
155
375
  extractNodeFilePath(nodeLike) {
156
376
  if (!nodeLike || typeof nodeLike !== 'object') return '';
157
377
  const metadata = (nodeLike.metadata && typeof nodeLike.metadata === 'object') ? nodeLike.metadata : {};
@@ -160,6 +380,16 @@ class Reader {
160
380
  return String(nodeLike.filepath || nodeLike.filePath || '').trim();
161
381
  }
162
382
 
383
+ isReaderBodyVisiblyEmpty() {
384
+ if (!this.body) {
385
+ return true;
386
+ }
387
+ return (
388
+ this.body.children.length === 0 &&
389
+ String(this.body.textContent || '').trim().length === 0
390
+ );
391
+ }
392
+
163
393
  getRuntimeBaseUrl() {
164
394
  if (window.NoteConnectionRuntime && typeof window.NoteConnectionRuntime.getBaseUrl === 'function') {
165
395
  return window.NoteConnectionRuntime.getBaseUrl();
@@ -225,6 +455,89 @@ class Reader {
225
455
  });
226
456
  }
227
457
 
458
+ normalizeMermaidDefinition(source) {
459
+ if (window.pathModules && window.pathModules.utils && typeof window.pathModules.utils.normalizeBridgeMermaidDefinition === 'function') {
460
+ return window.pathModules.utils.normalizeBridgeMermaidDefinition(source);
461
+ }
462
+ return String(source || '').trim();
463
+ }
464
+
465
+ getMermaidDefinitionCandidates(source) {
466
+ const candidates = [];
467
+ const normalized = this.normalizeMermaidDefinition(source);
468
+ const raw = String(source || '').replace(/\r\n?/g, '\n').trim();
469
+ [normalized, raw].forEach((candidate) => {
470
+ const next = String(candidate || '').trim();
471
+ if (!next || candidates.includes(next)) {
472
+ return;
473
+ }
474
+ candidates.push(next);
475
+ });
476
+ return candidates;
477
+ }
478
+
479
+ async canParseMermaidDefinition(definition) {
480
+ if (!window.mermaid || typeof mermaid.parse !== 'function') {
481
+ return true;
482
+ }
483
+ try {
484
+ await mermaid.parse(definition);
485
+ return true;
486
+ } catch (_error) {
487
+ return false;
488
+ }
489
+ }
490
+
491
+ isMermaidErrorSvgMarkup(markup) {
492
+ const text = String(markup || '').toLowerCase();
493
+ if (!text) {
494
+ return false;
495
+ }
496
+ return (
497
+ text.includes('syntax error in text') ||
498
+ text.includes('lexical error on line') ||
499
+ text.includes('parse error on line') ||
500
+ text.includes('mermaid version') ||
501
+ text.includes('class="error-icon"') ||
502
+ text.includes('id="error-icon"')
503
+ );
504
+ }
505
+
506
+ renderCompactMermaidFailure(wrapper, error, source) {
507
+ wrapper.classList.add('mermaid-render-failed');
508
+ wrapper.innerHTML = '';
509
+
510
+ const notice = document.createElement('div');
511
+ notice.className = 'mermaid-render-notice';
512
+
513
+ const title = document.createElement('div');
514
+ title.className = 'mermaid-render-notice-title';
515
+ title.textContent = 'Mermaid diagram unavailable.';
516
+ notice.appendChild(title);
517
+
518
+ const summary = document.createElement('div');
519
+ summary.className = 'mermaid-render-notice-summary';
520
+ summary.textContent = 'Rendering failed in the current runtime. Details are available on demand.';
521
+ notice.appendChild(summary);
522
+
523
+ const details = document.createElement('details');
524
+ details.className = 'mermaid-render-notice-details';
525
+
526
+ const detailsSummary = document.createElement('summary');
527
+ detailsSummary.textContent = 'Render details';
528
+ details.appendChild(detailsSummary);
529
+
530
+ const detailText = document.createElement('pre');
531
+ detailText.className = 'mermaid-render-notice-code';
532
+ const errorMessage = String(error && error.message ? error.message : error || 'Unknown error').trim();
533
+ const sourceSnippet = String(source || '').trim().slice(0, 320);
534
+ detailText.textContent = errorMessage + (sourceSnippet ? `\n\n${sourceSnippet}` : '');
535
+ details.appendChild(detailText);
536
+
537
+ notice.appendChild(details);
538
+ wrapper.appendChild(notice);
539
+ }
540
+
228
541
  async tryRenderViaMarkdownProtocol(nodeLike, filePath, resolvedTarget, sessionId) {
229
542
  const config = this.getReadingProtocolConfig();
230
543
  if (String(config.markdownEngine || 'auto').toLowerCase() === 'legacy') {
@@ -306,13 +619,14 @@ class Reader {
306
619
 
307
620
  cleanupProtocolState() {
308
621
  this._protocolRenderState = null;
309
- if (this._protocolScrollHandler) {
622
+ if (this._protocolScrollHandler && this.body) {
310
623
  this.body.removeEventListener('scroll', this._protocolScrollHandler);
311
624
  this._protocolScrollHandler = null;
312
625
  }
313
626
  }
314
627
 
315
628
  async renderProtocolBlocks(blocks, position, currentFilePath, sessionId) {
629
+ this.ensureReaderStructure();
316
630
  if (!Array.isArray(blocks) || blocks.length === 0) {
317
631
  return;
318
632
  }
@@ -509,8 +823,21 @@ class Reader {
509
823
  this.body.addEventListener('scroll', this._protocolScrollHandler, { passive: true });
510
824
  }
511
825
 
826
+ autoFixInlineMermaidFenceAfterBlockMath(markdownText) {
827
+ const source = String(markdownText || '');
828
+ if (!source) {
829
+ return source;
830
+ }
831
+ if (!source.includes('```mermaid') || !source.includes('$$')) {
832
+ return source;
833
+ }
834
+ return source.replace(/\$\$[ \t]*```mermaid\b/g, '$$\n```mermaid');
835
+ }
836
+
512
837
  normalizeProtocolBlock(block) {
513
- const source = String(block && block.text ? block.text : '');
838
+ const source = this.autoFixInlineMermaidFenceAfterBlockMath(
839
+ String(block && block.text ? block.text : '')
840
+ );
514
841
  const type = String(block && block.type ? block.type : '').trim().toLowerCase();
515
842
  const normalizedType = type || 'paragraph';
516
843
  if (!source) {
@@ -712,7 +1039,11 @@ class Reader {
712
1039
  }
713
1040
 
714
1041
  async renderRawMarkdown(rawContent, currentFilePath, sessionId) {
715
- const markdownText = this.transformWikiLinks(String(rawContent || '*No content available.*'));
1042
+ this.ensureReaderStructure();
1043
+ const sanitizedMarkdown = this.autoFixInlineMermaidFenceAfterBlockMath(
1044
+ String(rawContent || '*No content available.*')
1045
+ );
1046
+ const markdownText = this.transformWikiLinks(sanitizedMarkdown);
716
1047
  this.body.innerHTML = marked.parse(markdownText);
717
1048
  this.bindWikiLinks(this.body, currentFilePath || this.getRuntimeBaseUrl());
718
1049
  this.renderMathInContainer(this.body);
@@ -734,10 +1065,8 @@ class Reader {
734
1065
  }
735
1066
 
736
1067
  async renderMermaidInContainer(container) {
737
- if (!window.mermaid) {
738
- return;
739
- }
740
- if (!this._mermaidInitialized) {
1068
+ const hasFrontendMermaid = Boolean(window.mermaid);
1069
+ if (hasFrontendMermaid && !this._mermaidInitialized) {
741
1070
  mermaid.initialize({
742
1071
  startOnLoad: false,
743
1072
  theme: 'dark',
@@ -747,6 +1076,8 @@ class Reader {
747
1076
  this._mermaidInitialized = true;
748
1077
  }
749
1078
 
1079
+ this.suppressLeakedMermaidErrorArtifacts({ root: document.body });
1080
+
750
1081
  let mermaidBlocks = Array.from(container.querySelectorAll('pre code.language-mermaid, pre code.lang-mermaid'));
751
1082
  if (mermaidBlocks.length === 0 && container.dataset && container.dataset.codeLanguage === 'mermaid') {
752
1083
  const fallbackBlock = container.querySelector('pre code');
@@ -758,7 +1089,8 @@ class Reader {
758
1089
  for (const block of mermaidBlocks) {
759
1090
  const txt = document.createElement('textarea');
760
1091
  txt.innerHTML = block.innerHTML;
761
- const graphDefinition = txt.value;
1092
+ const candidateDefinitions = this.getMermaidDefinitionCandidates(txt.value);
1093
+ const graphDefinition = candidateDefinitions[0] || '';
762
1094
  const parentPre = block.parentElement;
763
1095
  if (!parentPre || !parentPre.parentNode) continue;
764
1096
 
@@ -766,25 +1098,165 @@ class Reader {
766
1098
  const renderId = `reader-mermaid-${this._mermaidRenderCounter}`;
767
1099
  const wrapper = document.createElement('div');
768
1100
  wrapper.className = 'mermaid';
769
- wrapper.id = renderId;
770
1101
  parentPre.parentNode.replaceChild(wrapper, parentPre);
771
- try {
772
- const rendered = await mermaid.render(renderId, graphDefinition);
773
- wrapper.innerHTML = rendered.svg;
774
- } catch (error) {
775
- wrapper.innerText = `Mermaid render error: ${String(error && error.message ? error.message : error)}`;
776
- console.error('Mermaid error:', error);
1102
+ let rendered = false;
1103
+ const renderErrors = [];
1104
+ let renderSource = '';
1105
+ const section = wrapper.closest('.reader-block');
1106
+ const blockId = section && section.dataset ? String(section.dataset.blockId || '').trim() : '';
1107
+
1108
+ if (hasFrontendMermaid) {
1109
+ for (let candidateIndex = 0; candidateIndex < candidateDefinitions.length; candidateIndex += 1) {
1110
+ const candidate = candidateDefinitions[candidateIndex];
1111
+ const offscreenHost = this.createOffscreenMermaidRenderHost(
1112
+ wrapper.clientWidth || parentPre.clientWidth || container.clientWidth || 960
1113
+ );
1114
+ try {
1115
+ wrapper.innerHTML = '';
1116
+ const renderedResult = await mermaid.render(`${renderId}-${candidateIndex}`, candidate, offscreenHost);
1117
+ if (renderedResult && typeof renderedResult.svg === 'string' && renderedResult.svg.trim()) {
1118
+ if (this.isMermaidErrorSvgMarkup(renderedResult.svg)) {
1119
+ throw new Error('Mermaid frontend renderer returned an error SVG instead of a diagram.');
1120
+ }
1121
+ wrapper.innerHTML = renderedResult.svg;
1122
+ }
1123
+ if (/<svg[\s>]/i.test(String(wrapper.innerHTML || ''))) {
1124
+ rendered = true;
1125
+ renderSource = 'frontend-render';
1126
+ this._mermaidRenderStats.frontendRender += 1;
1127
+ break;
1128
+ }
1129
+ } catch (error) {
1130
+ renderErrors.push(error);
1131
+ wrapper.innerHTML = '';
1132
+ this.suppressLeakedMermaidErrorArtifacts({
1133
+ root: document.body,
1134
+ preserveNode: wrapper,
1135
+ });
1136
+ } finally {
1137
+ offscreenHost.remove();
1138
+ }
1139
+ }
1140
+ } else {
1141
+ renderErrors.push(new Error('Mermaid runtime is unavailable in current webview.'));
1142
+ }
1143
+
1144
+ if (!rendered && hasFrontendMermaid) {
1145
+ for (let candidateIndex = 0; candidateIndex < candidateDefinitions.length; candidateIndex += 1) {
1146
+ const candidate = candidateDefinitions[candidateIndex];
1147
+ const canParse = await this.canParseMermaidDefinition(candidate);
1148
+ if (!canParse) {
1149
+ continue;
1150
+ }
1151
+ try {
1152
+ wrapper.innerHTML = '';
1153
+ wrapper.textContent = candidate;
1154
+ await mermaid.run({ nodes: [wrapper] });
1155
+ rendered = /<svg[\s>]/i.test(String(wrapper.innerHTML || '')) &&
1156
+ !this.isMermaidErrorSvgMarkup(String(wrapper.innerHTML || ''));
1157
+ if (rendered) {
1158
+ renderSource = 'frontend-run';
1159
+ this._mermaidRenderStats.frontendRun += 1;
1160
+ break;
1161
+ }
1162
+ } catch (error) {
1163
+ renderErrors.push(error);
1164
+ wrapper.innerHTML = '';
1165
+ this.suppressLeakedMermaidErrorArtifacts({
1166
+ root: document.body,
1167
+ preserveNode: wrapper,
1168
+ });
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ if (!rendered) {
1174
+ wrapper.innerHTML = '';
1175
+ const backendRendered = await this.renderMermaidViaBackend(graphDefinition);
1176
+ if (backendRendered && backendRendered.pngBase64) {
1177
+ const altRenderer = backendRendered.renderer ? ` (${backendRendered.renderer})` : '';
1178
+ wrapper.innerHTML = `<img class="mermaid-fallback-image" src="data:image/png;base64,${backendRendered.pngBase64}" alt="Mermaid diagram${altRenderer}" />`;
1179
+ rendered = true;
1180
+ renderSource = backendRendered.renderer ? `backend-${backendRendered.renderer}` : 'backend-png';
1181
+ this._mermaidRenderStats.backendPng += 1;
1182
+ }
1183
+ }
1184
+
1185
+ if (!rendered) {
1186
+ const lastError = renderErrors.length > 0 ? renderErrors[renderErrors.length - 1] : null;
1187
+ this.renderCompactMermaidFailure(wrapper, lastError, graphDefinition);
1188
+ if (lastError) {
1189
+ console.error('Mermaid error:', lastError);
1190
+ }
1191
+ renderSource = 'failed';
1192
+ this._mermaidRenderStats.failed += 1;
1193
+ }
1194
+
1195
+ this.suppressLeakedMermaidErrorArtifacts({
1196
+ root: document.body,
1197
+ preserveNode: wrapper,
1198
+ });
1199
+
1200
+ if (renderSource) {
1201
+ wrapper.dataset.renderSource = renderSource;
1202
+ const blockSuffix = blockId ? ` block=${blockId}` : '';
1203
+ console.info(`[Reader] Mermaid render source: ${renderSource}${blockSuffix}`);
777
1204
  }
778
1205
  }
779
1206
  if (mermaidBlocks.length > 0) {
1207
+ console.info(
1208
+ `[Reader] Mermaid render stats: frontend-render=${this._mermaidRenderStats.frontendRender}, frontend-run=${this._mermaidRenderStats.frontendRun}, backend-png=${this._mermaidRenderStats.backendPng}, failed=${this._mermaidRenderStats.failed}`
1209
+ );
780
1210
  this.initMermaidZoom();
781
1211
  }
782
1212
  }
783
1213
 
1214
+ async renderMermaidViaBackend(graphDefinition) {
1215
+ const source = this.normalizeMermaidDefinition(graphDefinition || '');
1216
+ if (!source) {
1217
+ return null;
1218
+ }
1219
+ try {
1220
+ const response = await fetch(
1221
+ this.buildRuntimeUrl('/api/render/mermaid'),
1222
+ this.buildRuntimeFetchOptions({
1223
+ method: 'POST',
1224
+ headers: {
1225
+ 'Content-Type': 'application/json',
1226
+ },
1227
+ body: JSON.stringify({
1228
+ source,
1229
+ renderer: 'auto',
1230
+ }),
1231
+ })
1232
+ );
1233
+ const payload = await response.json().catch(() => null);
1234
+ if (!response.ok || !payload || typeof payload.pngBase64 !== 'string' || !payload.pngBase64.trim()) {
1235
+ return null;
1236
+ }
1237
+ return {
1238
+ pngBase64: payload.pngBase64.trim(),
1239
+ renderer: typeof payload.renderer === 'string' ? payload.renderer.trim() : '',
1240
+ };
1241
+ } catch (error) {
1242
+ console.warn('[Reader] Mermaid backend fallback failed:', error);
1243
+ return null;
1244
+ }
1245
+ }
1246
+
784
1247
  initMermaidZoom() {
1248
+ if (!this.body) {
1249
+ return;
1250
+ }
785
1251
  const mermaidDivs = this.body.querySelectorAll('.mermaid');
786
1252
  mermaidDivs.forEach(div => {
1253
+ if (div.dataset && div.dataset.readerZoomBound === '1') {
1254
+ return;
1255
+ }
787
1256
  div.style.cursor = 'zoom-in';
1257
+ if (div.dataset) {
1258
+ div.dataset.readerZoomBound = '1';
1259
+ }
788
1260
  div.addEventListener('click', (e) => {
789
1261
  e.stopPropagation(); // Prevent Reader close
790
1262
  this.openMermaidOverlay(div.innerHTML);
@@ -793,6 +1265,11 @@ class Reader {
793
1265
  }
794
1266
 
795
1267
  openMermaidOverlay(svgContent) {
1268
+ const existingOverlay = document.getElementById('mermaid-zoom-overlay');
1269
+ if (existingOverlay && existingOverlay.parentNode) {
1270
+ existingOverlay.parentNode.removeChild(existingOverlay);
1271
+ }
1272
+
796
1273
  // Create Overlay
797
1274
  const overlay = document.createElement('div');
798
1275
  overlay.id = 'mermaid-zoom-overlay';
@@ -802,15 +1279,29 @@ class Reader {
802
1279
  const container = document.createElement('div');
803
1280
  container.className = 'mermaid-zoom-container';
804
1281
  container.innerHTML = svgContent;
1282
+ container.addEventListener('click', (event) => {
1283
+ event.stopPropagation();
1284
+ });
805
1285
  overlay.appendChild(container);
806
1286
 
807
1287
  // Close Button
808
1288
  const closeBtn = document.createElement('button');
809
1289
  closeBtn.innerText = '×';
810
1290
  closeBtn.className = 'mermaid-close-btn';
811
- closeBtn.onclick = () => document.body.removeChild(overlay);
1291
+ closeBtn.onclick = (event) => {
1292
+ event.stopPropagation();
1293
+ if (overlay.parentNode) {
1294
+ overlay.parentNode.removeChild(overlay);
1295
+ }
1296
+ };
812
1297
  overlay.appendChild(closeBtn);
813
1298
 
1299
+ overlay.addEventListener('click', () => {
1300
+ if (overlay.parentNode) {
1301
+ overlay.parentNode.removeChild(overlay);
1302
+ }
1303
+ });
1304
+
814
1305
  document.body.appendChild(overlay);
815
1306
 
816
1307
  // Pan/Zoom State
@@ -931,7 +1422,9 @@ class Reader {
931
1422
  if (!this._sessionId) this._sessionId = 0;
932
1423
  this._sessionId += 1;
933
1424
  this.cleanupProtocolState();
934
- this.window.style.display = 'none';
1425
+ if (this.window) {
1426
+ this.window.style.display = 'none';
1427
+ }
935
1428
  }
936
1429
 
937
1430
  toggleLock() {
@@ -940,6 +1433,7 @@ class Reader {
940
1433
  }
941
1434
 
942
1435
  updateLockState() {
1436
+ this.ensureReaderStructure();
943
1437
  const btn = document.getElementById('btn-reader-lock');
944
1438
  const zoomBtns = document.querySelectorAll('#btn-reader-zoom-in, #btn-reader-zoom-out');
945
1439
 
@@ -965,6 +1459,7 @@ class Reader {
965
1459
  }
966
1460
 
967
1461
  updateZoom() {
1462
+ this.ensureReaderStructure();
968
1463
  this.body.style.fontSize = `${this.currentZoom}rem`;
969
1464
  // Scale images if needed, but CSS 'resize' handles explicit image resizing in unlocked mode.
970
1465
  // Font size scaling handles text content scaling.