@mindexec/cli 0.2.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 (232) hide show
  1. package/README.md +275 -0
  2. package/codex-runtime.js +960 -0
  3. package/launch-bridge.cjs +162 -0
  4. package/package.json +61 -0
  5. package/port-guard.cjs +232 -0
  6. package/scripts/setup-tree-sitter-grammars.mjs +59 -0
  7. package/server.js +8422 -0
  8. package/start-bridge.bat +32 -0
  9. package/start-bridge.sh +81 -0
  10. package/tree-sitter-grammars/README.md +18 -0
  11. package/tree-sitter-grammars/tree-sitter-c_sharp.wasm +0 -0
  12. package/tree-sitter-grammars/tree-sitter-go.wasm +0 -0
  13. package/tree-sitter-grammars/tree-sitter-java.wasm +0 -0
  14. package/tree-sitter-grammars/tree-sitter-javascript.wasm +0 -0
  15. package/tree-sitter-grammars/tree-sitter-python.wasm +0 -0
  16. package/tree-sitter-grammars/tree-sitter-rust.wasm +0 -0
  17. package/tree-sitter-grammars/tree-sitter-tsx.wasm +0 -0
  18. package/tree-sitter-grammars/tree-sitter-typescript.wasm +0 -0
  19. package/wwwroot/MindExecution.Web.styles.css +3 -0
  20. package/wwwroot/_content/MindExecution.Plugins.Admin/css/admin-dashboard.css +546 -0
  21. package/wwwroot/_content/MindExecution.Plugins.Directory/MindExecution.Plugins.Directory.u7utcng611.bundle.scp.css +7 -0
  22. package/wwwroot/_content/MindExecution.Plugins.Directory/background.png +0 -0
  23. package/wwwroot/_content/MindExecution.Plugins.Directory/directory-manager.js +202 -0
  24. package/wwwroot/_content/MindExecution.Plugins.Directory/exampleJsInterop.js +6 -0
  25. package/wwwroot/_content/MindExecution.Plugins.YouTube/css/youtube-search.css +251 -0
  26. package/wwwroot/_content/MindExecution.Shared/MindExecution.Shared.wsano1j4wp.bundle.scp.css +4 -0
  27. package/wwwroot/_content/MindExecution.Shared/css/admin-dashboard.css +559 -0
  28. package/wwwroot/_content/MindExecution.Shared/css/app.css +1 -0
  29. package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +2936 -0
  30. package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Bold.ttf +0 -0
  31. package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Regular.ttf +0 -0
  32. package/wwwroot/_content/MindExecution.Shared/js/agent-visualization.js +359 -0
  33. package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1721 -0
  34. package/wwwroot/_content/MindExecution.Shared/js/code-master.js +8316 -0
  35. package/wwwroot/_content/MindExecution.Shared/js/file-system-helper.js +639 -0
  36. package/wwwroot/_content/MindExecution.Shared/js/helpers/InfiniteGridHelper.js +109 -0
  37. package/wwwroot/_content/MindExecution.Shared/js/marked.min.js +69 -0
  38. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +7982 -0
  39. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js.backup +1059 -0
  40. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +15803 -0
  41. package/wwwroot/_content/MindExecution.Shared/js/mind-map-dev-guards.js +325 -0
  42. package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js +1430 -0
  43. package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js.bak +434 -0
  44. package/wwwroot/_content/MindExecution.Shared/js/mind-map-glow-shader.js +260 -0
  45. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +7640 -0
  46. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js +160 -0
  47. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +9923 -0
  48. package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +977 -0
  49. package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +1431 -0
  50. package/wwwroot/_content/MindExecution.Shared/js/mind-map-multi-select.js +1716 -0
  51. package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +553 -0
  52. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +4541 -0
  53. package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js +489 -0
  54. package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js.backup +372 -0
  55. package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +2075 -0
  56. package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-lod-system.js +646 -0
  57. package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-overlay-v2.js +4323 -0
  58. package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +2260 -0
  59. package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js.backup +1258 -0
  60. package/wwwroot/_content/MindExecution.Shared/js/mind-map-visibility-worker.js +890 -0
  61. package/wwwroot/_content/MindExecution.Shared/js/mindmap-toolbar.js +594 -0
  62. package/wwwroot/_content/MindExecution.Shared/js/native-drop-handler.js +170 -0
  63. package/wwwroot/_content/MindExecution.Shared/js/plan-master.js +788 -0
  64. package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +50 -0
  65. package/wwwroot/_content/MindExecution.Shared/js/texture-worker-manager.js +188 -0
  66. package/wwwroot/_content/MindExecution.Shared/js/texture-worker.js +208 -0
  67. package/wwwroot/_content/MindExecution.Shared/js/three.min.js +6 -0
  68. package/wwwroot/_content/MindExecution.Shared/js/titlebar-handler.js +191 -0
  69. package/wwwroot/_content/MindExecution.Shared/js/token-manager.js +37 -0
  70. package/wwwroot/_content/MindExecution.Shared/js/token-worker.js +28 -0
  71. package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js +5626 -0
  72. package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js.map +7 -0
  73. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/css/all.min.css +9 -0
  74. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-brands-400.ttf +0 -0
  75. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-brands-400.woff2 +0 -0
  76. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-regular-400.ttf +0 -0
  77. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-regular-400.woff2 +0 -0
  78. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-solid-900.ttf +0 -0
  79. package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/webfonts/fa-solid-900.woff2 +0 -0
  80. package/wwwroot/_content/MindExecution.Shared/models/all-MiniLM-L6-v2-quantized.onnx +0 -0
  81. package/wwwroot/_content/MindExecution.Shared/models/vocab.txt +30522 -0
  82. package/wwwroot/_framework/Google.Protobuf.9h59ukbel7.dll +0 -0
  83. package/wwwroot/_framework/Markdig.d1j7v41cl1.dll +0 -0
  84. package/wwwroot/_framework/MessagePack.Annotations.l6qv48kgpt.dll +0 -0
  85. package/wwwroot/_framework/MessagePack.eqoptzx9d5.dll +0 -0
  86. package/wwwroot/_framework/Microsoft.AspNetCore.Authorization.k7dsih5y5g.dll +0 -0
  87. package/wwwroot/_framework/Microsoft.AspNetCore.Components.6nyje9sa0g.dll +0 -0
  88. package/wwwroot/_framework/Microsoft.AspNetCore.Components.Authorization.iycd6unprw.dll +0 -0
  89. package/wwwroot/_framework/Microsoft.AspNetCore.Components.Web.487u3twia4.dll +0 -0
  90. package/wwwroot/_framework/Microsoft.AspNetCore.Components.WebAssembly.d0gcnmlxxz.dll +0 -0
  91. package/wwwroot/_framework/Microsoft.AspNetCore.Metadata.h4yevl9adi.dll +0 -0
  92. package/wwwroot/_framework/Microsoft.CSharp.qrvp77qmhs.dll +0 -0
  93. package/wwwroot/_framework/Microsoft.Data.Sqlite.jdlxgv2jtg.dll +0 -0
  94. package/wwwroot/_framework/Microsoft.EntityFrameworkCore.4gjazp7kjf.dll +0 -0
  95. package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Abstractions.gocudnvz7b.dll +0 -0
  96. package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Relational.lt4rsvinuo.dll +0 -0
  97. package/wwwroot/_framework/Microsoft.EntityFrameworkCore.Sqlite.69luj0fa9r.dll +0 -0
  98. package/wwwroot/_framework/Microsoft.Extensions.Caching.Abstractions.364t4jh3zz.dll +0 -0
  99. package/wwwroot/_framework/Microsoft.Extensions.Caching.Memory.izlxhpzosu.dll +0 -0
  100. package/wwwroot/_framework/Microsoft.Extensions.Configuration.8zq7hh41o7.dll +0 -0
  101. package/wwwroot/_framework/Microsoft.Extensions.Configuration.Abstractions.8if74zs6ea.dll +0 -0
  102. package/wwwroot/_framework/Microsoft.Extensions.Configuration.Json.duvlngw8i0.dll +0 -0
  103. package/wwwroot/_framework/Microsoft.Extensions.DependencyInjection.Abstractions.t2hh9kvx0o.dll +0 -0
  104. package/wwwroot/_framework/Microsoft.Extensions.DependencyInjection.n4tg99oy8l.dll +0 -0
  105. package/wwwroot/_framework/Microsoft.Extensions.DependencyModel.h0d06ixk3e.dll +0 -0
  106. package/wwwroot/_framework/Microsoft.Extensions.Logging.Abstractions.rl32bkx2sd.dll +0 -0
  107. package/wwwroot/_framework/Microsoft.Extensions.Logging.dlht1xei0t.dll +0 -0
  108. package/wwwroot/_framework/Microsoft.Extensions.Options.qeunebioml.dll +0 -0
  109. package/wwwroot/_framework/Microsoft.Extensions.Primitives.18cr6vnuuz.dll +0 -0
  110. package/wwwroot/_framework/Microsoft.IO.RecyclableMemoryStream.r915vovvw4.dll +0 -0
  111. package/wwwroot/_framework/Microsoft.IdentityModel.Abstractions.1ejljk3erv.dll +0 -0
  112. package/wwwroot/_framework/Microsoft.IdentityModel.JsonWebTokens.1596zr8gne.dll +0 -0
  113. package/wwwroot/_framework/Microsoft.IdentityModel.Logging.229uyvpgio.dll +0 -0
  114. package/wwwroot/_framework/Microsoft.IdentityModel.Tokens.9sibtajc9f.dll +0 -0
  115. package/wwwroot/_framework/Microsoft.JSInterop.17lq4j1j7g.dll +0 -0
  116. package/wwwroot/_framework/Microsoft.JSInterop.WebAssembly.ryia5gxiad.dll +0 -0
  117. package/wwwroot/_framework/Microsoft.ML.OnnxRuntime.w9deo1m5ss.dll +0 -0
  118. package/wwwroot/_framework/Microsoft.ML.Tokenizers.cm2vuv2z61.dll +0 -0
  119. package/wwwroot/_framework/Microsoft.NET.StringTools.3qbrf4v2ki.dll +0 -0
  120. package/wwwroot/_framework/MimeMapping.og9ys58ylm.dll +0 -0
  121. package/wwwroot/_framework/MindExecution.Core.1q1trifbuu.dll +0 -0
  122. package/wwwroot/_framework/MindExecution.Kernel.gwwc40sc45.dll +0 -0
  123. package/wwwroot/_framework/MindExecution.Plugins.Admin.0jgrn1sckv.dll +0 -0
  124. package/wwwroot/_framework/MindExecution.Plugins.Business.13mme2qcag.dll +0 -0
  125. package/wwwroot/_framework/MindExecution.Plugins.Concept.dfp2mdt45q.dll +0 -0
  126. package/wwwroot/_framework/MindExecution.Plugins.Directory.3w4t6n3se0.dll +0 -0
  127. package/wwwroot/_framework/MindExecution.Plugins.PlanMaster.s0qpntz420.dll +0 -0
  128. package/wwwroot/_framework/MindExecution.Plugins.YouTube.iu11fq8d16.dll +0 -0
  129. package/wwwroot/_framework/MindExecution.Shared.7j27dcqnrc.dll +0 -0
  130. package/wwwroot/_framework/MindExecution.Web.pq1ty8ov2v.dll +0 -0
  131. package/wwwroot/_framework/Newtonsoft.Json.a56zs13vug.dll +0 -0
  132. package/wwwroot/_framework/SQLitePCLRaw.batteries_v2.rrd1nzawpp.dll +0 -0
  133. package/wwwroot/_framework/SQLitePCLRaw.core.1dxloztpfz.dll +0 -0
  134. package/wwwroot/_framework/SQLitePCLRaw.provider.e_sqlite3.oekyzl53i1.dll +0 -0
  135. package/wwwroot/_framework/Supabase.Core.s1pkj4aj0l.dll +0 -0
  136. package/wwwroot/_framework/Supabase.Functions.qz4nu782sg.dll +0 -0
  137. package/wwwroot/_framework/Supabase.Gotrue.twah27pkik.dll +0 -0
  138. package/wwwroot/_framework/Supabase.Postgrest.gmuuv369ih.dll +0 -0
  139. package/wwwroot/_framework/Supabase.Realtime.ox3kchdy3w.dll +0 -0
  140. package/wwwroot/_framework/Supabase.Storage.fnjnepaowr.dll +0 -0
  141. package/wwwroot/_framework/Supabase.azmaw5pgcz.dll +0 -0
  142. package/wwwroot/_framework/System.Collections.Concurrent.y1zmvuyipi.dll +0 -0
  143. package/wwwroot/_framework/System.Collections.Immutable.ug3j698qms.dll +0 -0
  144. package/wwwroot/_framework/System.Collections.NonGeneric.h66hj3863h.dll +0 -0
  145. package/wwwroot/_framework/System.Collections.Specialized.umr3y27ntj.dll +0 -0
  146. package/wwwroot/_framework/System.Collections.x53e19vfsj.dll +0 -0
  147. package/wwwroot/_framework/System.ComponentModel.Annotations.tz6gnt4ebt.dll +0 -0
  148. package/wwwroot/_framework/System.ComponentModel.Primitives.j7tiphu4rg.dll +0 -0
  149. package/wwwroot/_framework/System.ComponentModel.TypeConverter.ujlztox1gx.dll +0 -0
  150. package/wwwroot/_framework/System.ComponentModel.x9xz0ojfb6.dll +0 -0
  151. package/wwwroot/_framework/System.Console.ijzpqmj7ne.dll +0 -0
  152. package/wwwroot/_framework/System.Data.Common.1r0sqffq1p.dll +0 -0
  153. package/wwwroot/_framework/System.Diagnostics.DiagnosticSource.9upoqwq09o.dll +0 -0
  154. package/wwwroot/_framework/System.Diagnostics.Process.m99azzntjm.dll +0 -0
  155. package/wwwroot/_framework/System.Diagnostics.TraceSource.pl7wv26myr.dll +0 -0
  156. package/wwwroot/_framework/System.Diagnostics.Tracing.crlhfx6tut.dll +0 -0
  157. package/wwwroot/_framework/System.Drawing.Primitives.22e4y9ikq9.dll +0 -0
  158. package/wwwroot/_framework/System.Drawing.mi7d8hwowb.dll +0 -0
  159. package/wwwroot/_framework/System.Formats.Asn1.jx23sjiqnn.dll +0 -0
  160. package/wwwroot/_framework/System.IO.Compression.6fyoii3uej.dll +0 -0
  161. package/wwwroot/_framework/System.IO.Pipelines.vg77t4cd4d.dll +0 -0
  162. package/wwwroot/_framework/System.IdentityModel.Tokens.Jwt.t67es60z5b.dll +0 -0
  163. package/wwwroot/_framework/System.Linq.1bkoxlqgmq.dll +0 -0
  164. package/wwwroot/_framework/System.Linq.Expressions.24xqiypwdt.dll +0 -0
  165. package/wwwroot/_framework/System.Linq.Queryable.hvd01d6rsa.dll +0 -0
  166. package/wwwroot/_framework/System.Memory.8dx3lwgym4.dll +0 -0
  167. package/wwwroot/_framework/System.Net.Http.Json.3mhdm9l1rf.dll +0 -0
  168. package/wwwroot/_framework/System.Net.Http.eitrz660my.dll +0 -0
  169. package/wwwroot/_framework/System.Net.NetworkInformation.3pkuofcv9r.dll +0 -0
  170. package/wwwroot/_framework/System.Net.Ping.8clj5pklrp.dll +0 -0
  171. package/wwwroot/_framework/System.Net.Primitives.qrp4wcjz1p.dll +0 -0
  172. package/wwwroot/_framework/System.Net.WebSockets.Client.2u6pv01g69.dll +0 -0
  173. package/wwwroot/_framework/System.Net.WebSockets.qp6u31zvm5.dll +0 -0
  174. package/wwwroot/_framework/System.Numerics.Tensors.0c7z4mt3on.dll +0 -0
  175. package/wwwroot/_framework/System.Numerics.Vectors.kc7ufp2j4l.dll +0 -0
  176. package/wwwroot/_framework/System.ObjectModel.qv82fot1ib.dll +0 -0
  177. package/wwwroot/_framework/System.Private.CoreLib.rkafq04oma.dll +0 -0
  178. package/wwwroot/_framework/System.Private.Uri.t9542hmr6j.dll +0 -0
  179. package/wwwroot/_framework/System.Private.Xml.Linq.n8n3ptrbwu.dll +0 -0
  180. package/wwwroot/_framework/System.Private.Xml.rxd3tytisn.dll +0 -0
  181. package/wwwroot/_framework/System.Reactive.t3fuon548l.dll +0 -0
  182. package/wwwroot/_framework/System.Reflection.Emit.9tjhp6y0j3.dll +0 -0
  183. package/wwwroot/_framework/System.Reflection.Emit.ILGeneration.stxyk8zoo1.dll +0 -0
  184. package/wwwroot/_framework/System.Reflection.Emit.Lightweight.6xrd5v8vg0.dll +0 -0
  185. package/wwwroot/_framework/System.Reflection.Primitives.wgn8fpwwvv.dll +0 -0
  186. package/wwwroot/_framework/System.Runtime.InteropServices.JavaScript.sliym526xh.dll +0 -0
  187. package/wwwroot/_framework/System.Runtime.InteropServices.RuntimeInformation.oji7zut14z.dll +0 -0
  188. package/wwwroot/_framework/System.Runtime.InteropServices.te07xr2we9.dll +0 -0
  189. package/wwwroot/_framework/System.Runtime.Intrinsics.507y4h8nzq.dll +0 -0
  190. package/wwwroot/_framework/System.Runtime.Loader.v7gk4bse0k.dll +0 -0
  191. package/wwwroot/_framework/System.Runtime.Numerics.eqy5xjv3nd.dll +0 -0
  192. package/wwwroot/_framework/System.Runtime.Serialization.Formatters.zpkrub8lab.dll +0 -0
  193. package/wwwroot/_framework/System.Runtime.Serialization.Primitives.vhkpnbxjip.dll +0 -0
  194. package/wwwroot/_framework/System.Runtime.jn319d5nyg.dll +0 -0
  195. package/wwwroot/_framework/System.Security.Claims.0ztig1q9vo.dll +0 -0
  196. package/wwwroot/_framework/System.Security.Cryptography.vttizqc9ho.dll +0 -0
  197. package/wwwroot/_framework/System.Text.Encoding.Extensions.utdd47ny8f.dll +0 -0
  198. package/wwwroot/_framework/System.Text.Encodings.Web.wah8r1zoe0.dll +0 -0
  199. package/wwwroot/_framework/System.Text.Json.kxlfxj0wrs.dll +0 -0
  200. package/wwwroot/_framework/System.Text.RegularExpressions.dbqn58klox.dll +0 -0
  201. package/wwwroot/_framework/System.Threading.42ao9vi047.dll +0 -0
  202. package/wwwroot/_framework/System.Threading.Channels.hfa7j0uv2w.dll +0 -0
  203. package/wwwroot/_framework/System.Threading.Thread.caul0pdqul.dll +0 -0
  204. package/wwwroot/_framework/System.Transactions.Local.fimi2hamzo.dll +0 -0
  205. package/wwwroot/_framework/System.Web.HttpUtility.gq8yz50p2e.dll +0 -0
  206. package/wwwroot/_framework/System.Xml.Linq.kitin4zjoj.dll +0 -0
  207. package/wwwroot/_framework/System.Xml.ReaderWriter.kzvw3qgxb0.dll +0 -0
  208. package/wwwroot/_framework/System.Xml.XDocument.c539ki6cuq.dll +0 -0
  209. package/wwwroot/_framework/System.m05i39uvk9.dll +0 -0
  210. package/wwwroot/_framework/Websocket.Client.vapounvmnl.dll +0 -0
  211. package/wwwroot/_framework/blazor.boot.json +305 -0
  212. package/wwwroot/_framework/blazor.webassembly.js +1 -0
  213. package/wwwroot/_framework/dotnet.js +4 -0
  214. package/wwwroot/_framework/dotnet.native.vz0adxojrz.wasm +0 -0
  215. package/wwwroot/_framework/dotnet.native.xsn1d6x2kd.js +16 -0
  216. package/wwwroot/_framework/dotnet.runtime.dstopyvqzi.js +4 -0
  217. package/wwwroot/_framework/icudt_CJK.tjcz0u77k5.dat +0 -0
  218. package/wwwroot/_framework/icudt_EFIGS.tptq2av103.dat +0 -0
  219. package/wwwroot/_framework/icudt_no_CJK.lfu7j35m59.dat +0 -0
  220. package/wwwroot/_framework/netstandard.0xet7jg7ky.dll +0 -0
  221. package/wwwroot/_headers +40 -0
  222. package/wwwroot/_redirects +1 -0
  223. package/wwwroot/appsettings.json +71 -0
  224. package/wwwroot/icon-192.png +0 -0
  225. package/wwwroot/icon-512.png +0 -0
  226. package/wwwroot/index.html +710 -0
  227. package/wwwroot/js/marketing-tool.js +180 -0
  228. package/wwwroot/manifest.webmanifest +22 -0
  229. package/wwwroot/robots.txt +4 -0
  230. package/wwwroot/service-worker-assets.js +857 -0
  231. package/wwwroot/service-worker.js +33 -0
  232. package/wwwroot/sitemap.xml +27 -0
@@ -0,0 +1,1431 @@
1
+ // File: wwwroot/js/mind-map-menu-manager.js
2
+ // Single-node menu manager — screen-space DOM overlay (zoom-invariant)
3
+ (function () {
4
+ 'use strict';
5
+
6
+ let currentMenuElement = null; // DOM overlay element
7
+ let currentMenuNodeId = null;
8
+ let _module = null;
9
+ const MEMO_COLORS = [
10
+ { key: 'coral', swatch: '#ff8052', ring: 'rgba(255, 128, 82, 0.22)' },
11
+ { key: 'amber', swatch: '#f59e0b', ring: 'rgba(245, 158, 11, 0.22)' },
12
+ { key: 'sun', swatch: '#ca8a04', ring: 'rgba(202, 138, 4, 0.22)' },
13
+ { key: 'sage', swatch: '#65a30d', ring: 'rgba(101, 163, 13, 0.22)' },
14
+ { key: 'mint', swatch: '#10b981', ring: 'rgba(16, 185, 129, 0.22)' },
15
+ { key: 'sky', swatch: '#3b82f6', ring: 'rgba(59, 130, 246, 0.22)' },
16
+ { key: 'indigo', swatch: '#6366f1', ring: 'rgba(99, 102, 241, 0.22)' },
17
+ { key: 'violet', swatch: '#8b5cf6', ring: 'rgba(139, 92, 246, 0.22)' },
18
+ { key: 'rose', swatch: '#f43f5e', ring: 'rgba(244, 63, 94, 0.22)' },
19
+ { key: 'gray', swatch: '#6b7280', ring: 'rgba(107, 114, 128, 0.22)' },
20
+ { key: 'slate', swatch: '#64748b', ring: 'rgba(100, 116, 139, 0.22)' }
21
+ ];
22
+
23
+ function ensureFeedbackStyles() {
24
+ if (document.getElementById('mindmap-feedback-styles')) return;
25
+
26
+ const style = document.createElement('style');
27
+ style.id = 'mindmap-feedback-styles';
28
+ style.textContent = `
29
+ .mindmap-toast-stack {
30
+ position: fixed;
31
+ right: 22px;
32
+ bottom: 24px;
33
+ z-index: 2147483000;
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 10px;
37
+ pointer-events: none;
38
+ }
39
+
40
+ .mindmap-toast {
41
+ width: min(380px, calc(100vw - 32px));
42
+ padding: 12px 14px;
43
+ border-radius: 16px;
44
+ background: rgba(15, 23, 42, 0.94);
45
+ color: #f8fafc;
46
+ box-shadow: 0 18px 42px rgba(15, 23, 42, 0.24);
47
+ font: 600 13px/1.45 Inter, ui-sans-serif, system-ui, sans-serif;
48
+ opacity: 0;
49
+ transform: translateY(8px);
50
+ transition: opacity 180ms ease, transform 180ms ease;
51
+ pointer-events: auto;
52
+ }
53
+
54
+ .mindmap-toast.is-visible {
55
+ opacity: 1;
56
+ transform: translateY(0);
57
+ }
58
+
59
+ .mindmap-toast[data-kind="error"] {
60
+ background: rgba(127, 29, 29, 0.94);
61
+ }
62
+
63
+ .mindmap-confirm-backdrop {
64
+ position: fixed;
65
+ inset: 0;
66
+ z-index: 2147483001;
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ padding: 24px;
71
+ background: rgba(15, 23, 42, 0.20);
72
+ backdrop-filter: blur(5px);
73
+ }
74
+
75
+ .mindmap-confirm-panel {
76
+ width: min(440px, calc(100vw - 36px));
77
+ border: 1px solid rgba(203, 213, 225, 0.86);
78
+ border-radius: 22px;
79
+ background: rgba(248, 250, 252, 0.98);
80
+ box-shadow: 0 28px 72px rgba(15, 23, 42, 0.22);
81
+ padding: 20px;
82
+ color: #0f172a;
83
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
84
+ }
85
+
86
+ .mindmap-confirm-title {
87
+ margin: 0;
88
+ font-size: 15px;
89
+ font-weight: 800;
90
+ letter-spacing: 0.01em;
91
+ }
92
+
93
+ .mindmap-confirm-message {
94
+ margin: 8px 0 0;
95
+ color: #475569;
96
+ font-size: 13px;
97
+ line-height: 1.55;
98
+ }
99
+
100
+ .mindmap-confirm-actions {
101
+ display: flex;
102
+ justify-content: flex-end;
103
+ gap: 10px;
104
+ margin-top: 18px;
105
+ }
106
+
107
+ .mindmap-confirm-button {
108
+ border: 0;
109
+ border-radius: 999px;
110
+ padding: 9px 15px;
111
+ font-size: 13px;
112
+ font-weight: 800;
113
+ cursor: pointer;
114
+ transition: transform 120ms ease, background 120ms ease;
115
+ }
116
+
117
+ .mindmap-confirm-button:active {
118
+ transform: scale(0.98);
119
+ }
120
+
121
+ .mindmap-confirm-button.secondary {
122
+ background: #e0f2fe;
123
+ color: #075985;
124
+ }
125
+
126
+ .mindmap-confirm-button.primary {
127
+ background: #0f172a;
128
+ color: #ffffff;
129
+ }
130
+ `;
131
+ document.head.appendChild(style);
132
+ }
133
+
134
+ function getToastStack() {
135
+ ensureFeedbackStyles();
136
+ let stack = document.querySelector('.mindmap-toast-stack');
137
+ if (!stack) {
138
+ stack = document.createElement('div');
139
+ stack.className = 'mindmap-toast-stack';
140
+ document.body.appendChild(stack);
141
+ }
142
+ return stack;
143
+ }
144
+
145
+ function showToast(message, options = {}) {
146
+ const text = String(message || '').trim();
147
+ if (!text || !document?.body) return;
148
+
149
+ const toast = document.createElement('div');
150
+ toast.className = 'mindmap-toast';
151
+ toast.dataset.kind = options.kind || 'info';
152
+ toast.textContent = text;
153
+
154
+ getToastStack().appendChild(toast);
155
+ requestAnimationFrame(() => toast.classList.add('is-visible'));
156
+
157
+ window.setTimeout(() => {
158
+ toast.classList.remove('is-visible');
159
+ window.setTimeout(() => toast.remove(), 220);
160
+ }, options.duration || 2600);
161
+ }
162
+
163
+ function showConfirmDialog(options = {}) {
164
+ if (!document?.body) return Promise.resolve(false);
165
+
166
+ ensureFeedbackStyles();
167
+
168
+ return new Promise((resolve) => {
169
+ const backdrop = document.createElement('div');
170
+ backdrop.className = 'mindmap-confirm-backdrop';
171
+
172
+ const panel = document.createElement('div');
173
+ panel.className = 'mindmap-confirm-panel';
174
+ panel.setAttribute('role', 'dialog');
175
+ panel.setAttribute('aria-modal', 'true');
176
+
177
+ const title = document.createElement('h3');
178
+ title.className = 'mindmap-confirm-title';
179
+ title.textContent = options.title || 'Confirm action';
180
+
181
+ const message = document.createElement('p');
182
+ message.className = 'mindmap-confirm-message';
183
+ message.textContent = options.message || 'Do you want to continue?';
184
+
185
+ const actions = document.createElement('div');
186
+ actions.className = 'mindmap-confirm-actions';
187
+
188
+ const cancel = document.createElement('button');
189
+ cancel.type = 'button';
190
+ cancel.className = 'mindmap-confirm-button secondary';
191
+ cancel.textContent = options.cancelText || 'Cancel';
192
+
193
+ const confirm = document.createElement('button');
194
+ confirm.type = 'button';
195
+ confirm.className = 'mindmap-confirm-button primary';
196
+ confirm.textContent = options.confirmText || 'Confirm';
197
+
198
+ const close = (value) => {
199
+ backdrop.remove();
200
+ document.removeEventListener('keydown', handleKeyDown, true);
201
+ resolve(!!value);
202
+ };
203
+
204
+ const handleKeyDown = (event) => {
205
+ if (event.key === 'Escape') {
206
+ event.preventDefault();
207
+ close(false);
208
+ }
209
+ };
210
+
211
+ cancel.addEventListener('click', () => close(false));
212
+ confirm.addEventListener('click', () => close(true));
213
+ backdrop.addEventListener('click', (event) => {
214
+ if (event.target === backdrop) close(false);
215
+ });
216
+ document.addEventListener('keydown', handleKeyDown, true);
217
+
218
+ actions.appendChild(cancel);
219
+ actions.appendChild(confirm);
220
+ panel.appendChild(title);
221
+ panel.appendChild(message);
222
+ panel.appendChild(actions);
223
+ backdrop.appendChild(panel);
224
+ document.body.appendChild(backdrop);
225
+ confirm.focus({ preventScroll: true });
226
+ });
227
+ }
228
+
229
+ window.MindMapFeedback = window.MindMapFeedback || {};
230
+ window.MindMapFeedback.toast = showToast;
231
+ window.MindMapFeedback.confirm = showConfirmDialog;
232
+
233
+ function init(module) {
234
+ _module = module;
235
+ }
236
+
237
+ function ensureMetadata(model) {
238
+ if (!model) return {};
239
+ if (model.metadata && typeof model.metadata === 'object') return model.metadata;
240
+ if (model.Metadata && typeof model.Metadata === 'object') {
241
+ model.metadata = model.Metadata;
242
+ return model.metadata;
243
+ }
244
+ model.metadata = {};
245
+ model.Metadata = model.metadata;
246
+ return model.metadata;
247
+ }
248
+
249
+ function getNodeSemanticType(model) {
250
+ const metadata = ensureMetadata(model);
251
+ return String(metadata.SemanticType || metadata.semanticType || '').trim();
252
+ }
253
+
254
+ function isAgentStyledMemoNode(model) {
255
+ const semanticType = getNodeSemanticType(model);
256
+ return semanticType === 'MindCanvasAgent' || semanticType === 'AgentCommand';
257
+ }
258
+
259
+ function isTextFitNode(model) {
260
+ const contentType = String(model?.contentType || '').toLowerCase();
261
+ return contentType === 'text' || contentType === 'markdown' || contentType === 'note' || contentType === 'code';
262
+ }
263
+
264
+ function getNodeContentType(model) {
265
+ return String(model?.contentType || '').trim().toLowerCase();
266
+ }
267
+
268
+ function showActionSuccessIcon(button) {
269
+ const icon = button?.querySelector?.('i');
270
+ if (!icon) return;
271
+
272
+ const oldClass = icon.className;
273
+ icon.className = 'fa-solid fa-check text-green-500';
274
+ setTimeout(() => {
275
+ icon.className = oldClass;
276
+ }, 1000);
277
+ }
278
+
279
+ function getImageClipboardSourceCandidates(nodeModel) {
280
+ const assetUrls = window.MindMapNodes?.getNodeImageAssetUrls?.(nodeModel) || {};
281
+ const rawCandidates = [
282
+ assetUrls.fullResUrl,
283
+ assetUrls.originalUrl,
284
+ assetUrls.originalPath,
285
+ assetUrls.previewUrl,
286
+ assetUrls.responseUrl,
287
+ nodeModel?.response,
288
+ nodeModel?.Response
289
+ ];
290
+
291
+ const candidates = [];
292
+ rawCandidates.forEach(value => {
293
+ const normalized = typeof value === 'string' ? value.trim() : '';
294
+ if (!normalized || candidates.includes(normalized)) {
295
+ return;
296
+ }
297
+
298
+ candidates.push(normalized);
299
+ });
300
+
301
+ return candidates;
302
+ }
303
+
304
+ function shouldResolveImageClipboardSourceBeforeFetch(src) {
305
+ const normalized = typeof src === 'string' ? src.trim().toLowerCase() : '';
306
+ if (!normalized || normalized.startsWith('blob:') || normalized.startsWith('data:') || normalized.startsWith('file:')) {
307
+ return false;
308
+ }
309
+
310
+ return normalized.startsWith('/assets/')
311
+ || normalized.startsWith('assets/')
312
+ || normalized.includes('/assets/thumbs/')
313
+ || (normalized.includes('/assets/') && !/\.[a-z0-9]{2,5}(?:[?#]|$)/i.test(normalized));
314
+ }
315
+
316
+ async function resolveClipboardImageSource(nodeModel, src) {
317
+ if (!shouldResolveImageClipboardSourceBeforeFetch(src) || !_module?.dotNetHelper) {
318
+ return src;
319
+ }
320
+
321
+ const nodeId = String(nodeModel?.id || nodeModel?.Id || '').trim();
322
+ if (!nodeId) {
323
+ return src;
324
+ }
325
+
326
+ try {
327
+ const resolved = await _module.dotNetHelper.invokeMethodAsync('ResolveNodeAssetUrl', nodeId);
328
+ const normalized = typeof resolved === 'string' ? resolved.trim() : '';
329
+ return normalized || src;
330
+ } catch (error) {
331
+ console.warn('[MindMapMenuManager] Failed to resolve image clipboard source.', error);
332
+ return src;
333
+ }
334
+ }
335
+
336
+ async function fetchClipboardImageBlob(nodeModel) {
337
+ const candidates = getImageClipboardSourceCandidates(nodeModel);
338
+ let lastError = null;
339
+
340
+ for (const src of candidates) {
341
+ try {
342
+ const resolvedSrc = await resolveClipboardImageSource(nodeModel, src);
343
+ const response = await fetch(resolvedSrc, { cache: 'no-store' });
344
+ if (!response.ok) {
345
+ throw new Error(`HTTP ${response.status}`);
346
+ }
347
+
348
+ const blob = await response.blob();
349
+ if (!(blob instanceof Blob) || blob.size <= 0) {
350
+ throw new Error('Empty image blob.');
351
+ }
352
+
353
+ return { blob, src: resolvedSrc };
354
+ } catch (error) {
355
+ lastError = error;
356
+ console.warn(`[MindMapMenuManager] Failed to fetch clipboard image from ${src}`, error);
357
+ }
358
+ }
359
+
360
+ throw lastError || new Error('No image source was available for clipboard copy.');
361
+ }
362
+
363
+ async function decodeClipboardImageBlob(blob) {
364
+ if (typeof createImageBitmap === 'function') {
365
+ try {
366
+ const bitmap = await createImageBitmap(blob);
367
+ return {
368
+ width: bitmap.width,
369
+ height: bitmap.height,
370
+ draw(ctx, width, height) {
371
+ ctx.drawImage(bitmap, 0, 0, width, height);
372
+ },
373
+ dispose() {
374
+ bitmap.close?.();
375
+ }
376
+ };
377
+ } catch (error) {
378
+ console.warn('[MindMapMenuManager] createImageBitmap failed for clipboard image conversion.', error);
379
+ }
380
+ }
381
+
382
+ const objectUrl = URL.createObjectURL(blob);
383
+ try {
384
+ const image = await new Promise((resolve, reject) => {
385
+ const img = new Image();
386
+ img.decoding = 'async';
387
+ img.onload = () => resolve(img);
388
+ img.onerror = () => reject(new Error('Failed to decode image blob.'));
389
+ img.src = objectUrl;
390
+ });
391
+
392
+ return {
393
+ width: image.naturalWidth || image.width,
394
+ height: image.naturalHeight || image.height,
395
+ draw(ctx, width, height) {
396
+ ctx.drawImage(image, 0, 0, width, height);
397
+ },
398
+ dispose() {
399
+ image.remove?.();
400
+ URL.revokeObjectURL(objectUrl);
401
+ }
402
+ };
403
+ } catch (error) {
404
+ URL.revokeObjectURL(objectUrl);
405
+ throw error;
406
+ }
407
+ }
408
+
409
+ async function convertBlobToPngForClipboard(blob) {
410
+ const normalizedType = String(blob?.type || '').toLowerCase();
411
+ if (normalizedType === 'image/png' && blob.size > 0) {
412
+ return blob;
413
+ }
414
+
415
+ const decoded = await decodeClipboardImageBlob(blob);
416
+ const width = Math.max(1, Math.round(decoded?.width || 0));
417
+ const height = Math.max(1, Math.round(decoded?.height || 0));
418
+
419
+ if (!width || !height) {
420
+ decoded?.dispose?.();
421
+ throw new Error('Decoded image had no dimensions.');
422
+ }
423
+
424
+ try {
425
+ if (typeof OffscreenCanvas === 'function') {
426
+ const canvas = new OffscreenCanvas(width, height);
427
+ const ctx = canvas.getContext('2d');
428
+ if (!ctx) {
429
+ throw new Error('OffscreenCanvas 2D context is unavailable.');
430
+ }
431
+
432
+ decoded.draw(ctx, width, height);
433
+ return await canvas.convertToBlob({ type: 'image/png' });
434
+ }
435
+
436
+ const canvas = document.createElement('canvas');
437
+ canvas.width = width;
438
+ canvas.height = height;
439
+ const ctx = canvas.getContext('2d');
440
+ if (!ctx) {
441
+ throw new Error('Canvas 2D context is unavailable.');
442
+ }
443
+
444
+ decoded.draw(ctx, width, height);
445
+ return await new Promise((resolve, reject) => {
446
+ canvas.toBlob(result => {
447
+ if (result instanceof Blob && result.size > 0) {
448
+ resolve(result);
449
+ return;
450
+ }
451
+
452
+ reject(new Error('Failed to encode clipboard image as PNG.'));
453
+ }, 'image/png');
454
+ });
455
+ } finally {
456
+ decoded?.dispose?.();
457
+ }
458
+ }
459
+
460
+ async function writeImageBlobToClipboard(blob, fallbackText) {
461
+ if (navigator.clipboard?.write && typeof ClipboardItem === 'function') {
462
+ await navigator.clipboard.write([
463
+ new ClipboardItem({ 'image/png': blob })
464
+ ]);
465
+ return 'image';
466
+ }
467
+
468
+ if (navigator.clipboard?.writeText && fallbackText) {
469
+ await navigator.clipboard.writeText(fallbackText);
470
+ return 'text';
471
+ }
472
+
473
+ throw new Error('Clipboard API is unavailable.');
474
+ }
475
+
476
+ function getNodeSourceFilePath(model) {
477
+ return String(model?.sourceFilePath || model?.SourceFilePath || '').trim();
478
+ }
479
+
480
+ function getCompanyBindingValue(model, key) {
481
+ const metadata = ensureMetadata(model);
482
+ return String(metadata[key] || '').trim();
483
+ }
484
+
485
+ function isCompanyBoundNode(model) {
486
+ return getCompanyBindingValue(model, 'CompanyCore.CompanyId').length > 0;
487
+ }
488
+
489
+ function getCompanyStatus(model) {
490
+ return getCompanyBindingValue(model, 'CompanyCore.Status').toLowerCase();
491
+ }
492
+
493
+ function isPendingCompanyBinding(model) {
494
+ const companyId = getCompanyBindingValue(model, 'CompanyCore.CompanyId');
495
+ if (companyId.startsWith('pending:')) {
496
+ return true;
497
+ }
498
+
499
+ const status = getCompanyStatus(model);
500
+ const runId = getCompanyBindingValue(model, 'CompanyCore.RunId');
501
+ return !companyId && status === 'queued' && !runId;
502
+ }
503
+
504
+ function applyCompanyBindingValues(model, values) {
505
+ const metadata = ensureMetadata(model);
506
+ Object.entries(values || {}).forEach(([key, value]) => {
507
+ if (value === null || value === undefined || value === '') {
508
+ delete metadata[key];
509
+ return;
510
+ }
511
+
512
+ metadata[key] = String(value);
513
+ });
514
+ model.metadata = metadata;
515
+ model.Metadata = metadata;
516
+ }
517
+
518
+ function applyOptimisticCompanyRunState(nodeEntry, nodeId, executionMode, enableSubAgents) {
519
+ if (!nodeEntry?.model) return;
520
+
521
+ applyCompanyBindingValues(nodeEntry.model, {
522
+ 'CompanyCore.CompanyId': `pending:${nodeId}`,
523
+ 'CompanyCore.ExecutionMode': executionMode || 'Auto',
524
+ 'CompanyCore.SubAgentsEnabled': enableSubAgents ? 'true' : 'false',
525
+ 'CompanyCore.Status': 'Queued',
526
+ 'CompanyCore.LastMessage': 'Starting company from this note...',
527
+ 'CompanyCore.PendingApprovalCount': '0',
528
+ 'CompanyCore.RunningAgentCount': '0',
529
+ 'CompanyCore.WorkItemCount': '0',
530
+ 'CompanyCore.ArtifactCount': '0',
531
+ 'CompanyCore.GrowthResultCount': '0',
532
+ 'CompanyCore.RevenueResultCount': '0'
533
+ });
534
+ }
535
+
536
+ function applyCompanyRunResult(nodeEntry, result, fallbackExecutionMode, fallbackEnableSubAgents) {
537
+ if (!nodeEntry?.model) return;
538
+
539
+ const companyId = String(result?.companyId || '').trim();
540
+ const status = String(result?.status || '').trim();
541
+ if (!companyId) {
542
+ applyCompanyBindingValues(nodeEntry.model, {
543
+ 'CompanyCore.CompanyId': null,
544
+ 'CompanyCore.RunId': null,
545
+ 'CompanyCore.ExecutionMode': result?.executionMode || fallbackExecutionMode || 'Auto',
546
+ 'CompanyCore.SubAgentsEnabled': typeof result?.subAgentsEnabled === 'boolean'
547
+ ? (result.subAgentsEnabled ? 'true' : 'false')
548
+ : (fallbackEnableSubAgents ? 'true' : 'false'),
549
+ 'CompanyCore.Status': status || 'Failed',
550
+ 'CompanyCore.LastMessage': result?.lastMessage || 'Company did not start from this note.',
551
+ 'CompanyCore.PendingApprovalCount': String(result?.pendingApprovalCount ?? 0),
552
+ 'CompanyCore.RunningAgentCount': String(result?.runningAgentCount ?? 0),
553
+ 'CompanyCore.WorkItemCount': String(result?.workItemCount ?? 0),
554
+ 'CompanyCore.ArtifactCount': String(result?.artifactCount ?? 0),
555
+ 'CompanyCore.GrowthResultCount': String(result?.growthResultCount ?? 0),
556
+ 'CompanyCore.RevenueResultCount': String(result?.revenueResultCount ?? 0),
557
+ 'CompanyCore.LastSnapshotAt': result?.lastSnapshotAt || null
558
+ });
559
+ return;
560
+ }
561
+
562
+ applyCompanyBindingValues(nodeEntry.model, {
563
+ 'CompanyCore.CompanyId': companyId,
564
+ 'CompanyCore.RunId': result?.runId || null,
565
+ 'CompanyCore.ExecutionMode': result?.executionMode || fallbackExecutionMode || 'Auto',
566
+ 'CompanyCore.SubAgentsEnabled': typeof result?.subAgentsEnabled === 'boolean'
567
+ ? (result.subAgentsEnabled ? 'true' : 'false')
568
+ : (fallbackEnableSubAgents ? 'true' : 'false'),
569
+ 'CompanyCore.Status': status || 'Queued',
570
+ 'CompanyCore.PendingApprovalCount': String(result?.pendingApprovalCount ?? 0),
571
+ 'CompanyCore.RunningAgentCount': String(result?.runningAgentCount ?? 0),
572
+ 'CompanyCore.WorkItemCount': String(result?.workItemCount ?? 0),
573
+ 'CompanyCore.ArtifactCount': String(result?.artifactCount ?? 0),
574
+ 'CompanyCore.GrowthResultCount': String(result?.growthResultCount ?? 0),
575
+ 'CompanyCore.RevenueResultCount': String(result?.revenueResultCount ?? 0),
576
+ 'CompanyCore.LastSnapshotAt': result?.lastSnapshotAt || null,
577
+ 'CompanyCore.LastMessage': result?.lastMessage || 'Company is running from this note in the background.'
578
+ });
579
+ }
580
+
581
+ function clearCompanyBindingValues(nodeEntry) {
582
+ if (!nodeEntry?.model) return;
583
+
584
+ applyCompanyBindingValues(nodeEntry.model, {
585
+ 'CompanyCore.CompanyId': null,
586
+ 'CompanyCore.RunId': null,
587
+ 'CompanyCore.CoreBaseUrl': null,
588
+ 'CompanyCore.SourceNodeId': null,
589
+ 'CompanyCore.BoardId': null,
590
+ 'CompanyCore.ExecutionMode': null,
591
+ 'CompanyCore.SubAgentsEnabled': null,
592
+ 'CompanyCore.Status': null,
593
+ 'CompanyCore.LastMessage': null,
594
+ 'CompanyCore.PendingApprovalCount': null,
595
+ 'CompanyCore.RunningAgentCount': null,
596
+ 'CompanyCore.WorkItemCount': null,
597
+ 'CompanyCore.ArtifactCount': null,
598
+ 'CompanyCore.GrowthResultCount': null,
599
+ 'CompanyCore.RevenueResultCount': null,
600
+ 'CompanyCore.LastSnapshotAt': null
601
+ });
602
+ }
603
+
604
+ function syncCompanyNodeVisual(nodeEntry) {
605
+ if (!nodeEntry?.cssObject || !window.MindMapCss3DManager?.syncCss3dContent) {
606
+ return;
607
+ }
608
+
609
+ nodeEntry.isCssDirty = true;
610
+ window.MindMapCss3DManager.syncCss3dContent(nodeEntry.model, nodeEntry.cssObject);
611
+ nodeEntry.isCssDirty = false;
612
+ _module?.lodRenderer?.requestResidentPatch?.('update-node', nodeEntry.model?.id || nodeEntry.model?.Id);
613
+ }
614
+
615
+ function canRunAsCompany(model) {
616
+ if (!model) return false;
617
+ if (isCompanyBoundNode(model)) return true;
618
+
619
+ const contentType = getNodeContentType(model);
620
+ if (!contentType) return true;
621
+
622
+ if (['image', 'video', 'pdf', 'file', 'directory'].includes(contentType)) {
623
+ return false;
624
+ }
625
+
626
+ if (['note', 'text', 'markdown', 'memo', 'code', 'mask'].includes(contentType)) {
627
+ return true;
628
+ }
629
+
630
+ return getNodeSourceFilePath(model).length === 0;
631
+ }
632
+
633
+ function getMemoColorKey(model) {
634
+ const metadata = ensureMetadata(model);
635
+ const fallbackKey = isAgentStyledMemoNode(model) ? 'gray' : 'coral';
636
+ const key = String(metadata.memoColor || fallbackKey).toLowerCase();
637
+ return MEMO_COLORS.some(color => color.key === key) ? key : fallbackKey;
638
+ }
639
+
640
+ function buildMemoColorPopover(selectedKey) {
641
+ const popover = document.createElement('div');
642
+ popover.className = 'menu-color-popover';
643
+
644
+ MEMO_COLORS.forEach(color => {
645
+ const button = document.createElement('button');
646
+ button.type = 'button';
647
+ button.className = 'menu-color-swatch';
648
+ button.dataset.colorKey = color.key;
649
+ button.title = color.key;
650
+ button.style.setProperty('--swatch-color', color.swatch);
651
+ button.style.setProperty('--swatch-ring', color.ring);
652
+ button.classList.toggle('is-active', color.key === selectedKey);
653
+ popover.appendChild(button);
654
+ });
655
+
656
+ return popover;
657
+ }
658
+
659
+ function syncMemoColorPopover(popover, selectedKey) {
660
+ if (!popover) return;
661
+ popover.querySelectorAll('.menu-color-swatch').forEach(button => {
662
+ button.classList.toggle('is-active', button.dataset.colorKey === selectedKey);
663
+ });
664
+ }
665
+
666
+ function refreshMemoNode(nodeEntry) {
667
+ if (!nodeEntry) return;
668
+ nodeEntry.isCssDirty = false;
669
+ if (nodeEntry.cssObject && window.MindMapCss3DManager?.syncCss3dContent) {
670
+ window.MindMapCss3DManager.syncCss3dContent(nodeEntry.model, nodeEntry.cssObject);
671
+ }
672
+ if (_module?.lodRenderer?.requestResidentPatch) {
673
+ _module.lodRenderer.requestResidentPatch('update-node', nodeEntry.model?.id || nodeEntry.model?.Id);
674
+ }
675
+ }
676
+
677
+ // ▼▼▼ [Refactor] Screen-space DOM overlay instead of CSS3DObject ▼▼▼
678
+ function showMenu(nodeId) {
679
+ hideMenu();
680
+
681
+ if (!nodeId || !_module) return;
682
+
683
+ const nodeEntry = _module.nodeObjectsById.get(nodeId);
684
+ if (!nodeEntry) return;
685
+
686
+ const templateId = `node-${nodeId}`;
687
+ const templateNode = document.getElementById(templateId);
688
+
689
+ let menuContainerSource = templateNode ? templateNode.querySelector('.node-menu-container') : null;
690
+ if (!menuContainerSource) {
691
+ menuContainerSource = createMenuContainer(nodeEntry);
692
+ }
693
+
694
+ // Clone menu DOM
695
+ const menuClone = menuContainerSource.cloneNode(true);
696
+
697
+ // Sync AI button state
698
+ const isAiEnabled = nodeEntry.model.isAiEnabled !== false;
699
+ const aiBtn = menuClone.querySelector('.js-btn-ai');
700
+ if (aiBtn) {
701
+ aiBtn.classList.remove('active-ai', 'inactive-ai');
702
+ aiBtn.classList.add(isAiEnabled ? 'active-ai' : 'inactive-ai');
703
+
704
+ const existingOverlay = aiBtn.querySelector('.slash-overlay');
705
+ if (existingOverlay) existingOverlay.remove();
706
+
707
+ if (!isAiEnabled) {
708
+ const span = document.createElement('span');
709
+ span.className = 'slash-overlay';
710
+ span.textContent = '\\';
711
+ const icon = aiBtn.querySelector('i');
712
+ if (icon) icon.after(span);
713
+ }
714
+ }
715
+
716
+ // ★ Hide edit button for file nodes (text files and code files)
717
+ const editBtn = menuClone.querySelector('.js-btn-edit');
718
+ const prompt = nodeEntry.model.prompt || '';
719
+ const contentType = nodeEntry.model.contentType;
720
+
721
+ const textFileExtensions = ['.txt', '.md'];
722
+ const isTextFile = textFileExtensions.some(ext => prompt.toLowerCase().endsWith(ext));
723
+
724
+ const codeFileExtensions = [
725
+ '.js', '.jsx', '.ts', '.tsx', '.py', '.cs', '.java',
726
+ '.cpp', '.c', '.h', '.go', '.rs', '.rb', '.php',
727
+ '.swift', '.kt', '.json', '.xml', '.yaml', '.yml',
728
+ '.sql', '.html', '.css', '.scss', '.less', '.sh', '.bat', '.ps1', '.csv', '.razor'
729
+ ];
730
+ const isCodeFile = codeFileExtensions.some(ext => prompt.toLowerCase().endsWith(ext));
731
+
732
+ if (editBtn && (isTextFile || isCodeFile || contentType === 'code')) {
733
+ editBtn.style.display = 'none';
734
+ }
735
+
736
+ // ★ Sync word wrap button state for code nodes
737
+ const isWordWrap = nodeEntry.model.isWordWrap === true;
738
+ const wrapBtn = menuClone.querySelector('.js-btn-wordwrap');
739
+ if (wrapBtn) {
740
+ wrapBtn.classList.toggle('active-nowrap', !isWordWrap);
741
+ wrapBtn.title = isWordWrap ? 'Disable word wrap' : 'Enable word wrap';
742
+
743
+ const icon = wrapBtn.querySelector('i');
744
+ if (icon) {
745
+ icon.className = isWordWrap
746
+ ? 'fa-solid fa-align-left'
747
+ : 'fa-solid fa-arrows-left-right';
748
+ }
749
+ }
750
+
751
+ // Menu styling — screen-space overlay
752
+ Object.assign(menuClone.style, {
753
+ position: 'fixed',
754
+ pointerEvents: 'auto',
755
+ whiteSpace: 'nowrap',
756
+ zIndex: '10000',
757
+ transform: 'translate(-50%, -100%)',
758
+ overflow: 'visible'
759
+ });
760
+
761
+ // Stop event bubbling
762
+ const stopProp = (e) => {
763
+ if (e.button === 1 || e.button === 2) {
764
+ return;
765
+ }
766
+ e.stopPropagation();
767
+ };
768
+ menuClone.addEventListener('mousedown', stopProp);
769
+ menuClone.addEventListener('mouseup', stopProp);
770
+ menuClone.addEventListener('click', stopProp);
771
+ menuClone.addEventListener('dblclick', stopProp);
772
+
773
+ // Bind button actions
774
+ bindMenuEvents(menuClone, nodeId, nodeEntry);
775
+
776
+ // Append to body
777
+ document.body.appendChild(menuClone);
778
+
779
+ // Position on screen
780
+ updateMenuScreenPosition(menuClone, nodeEntry);
781
+ requestAnimationFrame(() => {
782
+ if (currentMenuElement === menuClone && currentMenuNodeId === nodeId) {
783
+ updateMenuScreenPosition(menuClone, nodeEntry);
784
+ }
785
+ });
786
+
787
+ currentMenuElement = menuClone;
788
+ currentMenuNodeId = nodeId;
789
+ }
790
+ // ▲▲▲ [Refactor] ▲▲▲
791
+
792
+ function createMenuContainer(nodeEntry) {
793
+ const contentType = nodeEntry.model.contentType;
794
+ const prompt = nodeEntry.model.prompt || '';
795
+ const isAiEnabled = nodeEntry.model.isAiEnabled !== false;
796
+ const isWordWrap = nodeEntry.model.isWordWrap === true;
797
+ const companyBound = isCompanyBoundNode(nodeEntry.model);
798
+ const companyStatus = getCompanyStatus(nodeEntry.model);
799
+
800
+ const container = document.createElement('div');
801
+ container.className = 'node-menu-container';
802
+
803
+ const addSeparator = () => {
804
+ const sep = document.createElement('div');
805
+ sep.className = 'menu-separator';
806
+ container.appendChild(sep);
807
+ };
808
+
809
+ const addButton = (className, title, iconClass, isDisabled = false) => {
810
+ const btn = document.createElement('button');
811
+ btn.className = `menu-btn ${className}${isDisabled ? ' disabled' : ''}`.trim();
812
+ btn.title = title;
813
+ const icon = document.createElement('i');
814
+ icon.className = iconClass;
815
+ btn.appendChild(icon);
816
+ return btn;
817
+ };
818
+
819
+ const appendCompanyButtons = () => {
820
+ if (!canRunAsCompany(nodeEntry.model)) {
821
+ return false;
822
+ }
823
+
824
+ if (companyBound) {
825
+ if (isPendingCompanyBinding(nodeEntry.model)) {
826
+ container.appendChild(addButton('js-btn-company-pending', 'Company is starting', 'fa-solid fa-spinner', true));
827
+ container.appendChild(addButton('js-btn-company-refresh', 'Refresh Company status', 'fa-solid fa-rotate'));
828
+ container.appendChild(addButton('js-btn-company-remove', 'Remove Company from this note', 'fa-solid fa-xmark'));
829
+ return true;
830
+ }
831
+
832
+ if (companyStatus !== 'archived') {
833
+ container.appendChild(addButton('js-btn-company-sync-goal', 'Update Company goal from this note', 'fa-solid fa-bullseye'));
834
+ }
835
+ container.appendChild(addButton('js-btn-company-graph', 'Open Company graph', 'fa-solid fa-diagram-project'));
836
+ container.appendChild(addButton('js-btn-company-refresh', 'Refresh Company status', 'fa-solid fa-rotate'));
837
+
838
+ if (companyStatus === 'archived') {
839
+ container.appendChild(addButton('js-btn-company-archived', 'Company is archived', 'fa-solid fa-box-archive', true));
840
+ } else if (companyStatus === 'paused') {
841
+ container.appendChild(addButton('js-btn-company-resume', 'Resume Company', 'fa-solid fa-play'));
842
+ } else {
843
+ container.appendChild(addButton('js-btn-company-pause', 'Pause Company', 'fa-solid fa-pause'));
844
+ }
845
+
846
+ if (companyStatus !== 'archived') {
847
+ container.appendChild(addButton('js-btn-company-archive', 'Archive Company', 'fa-solid fa-box-archive'));
848
+ }
849
+
850
+ container.appendChild(addButton('js-btn-company-remove', 'Remove Company from this note', 'fa-solid fa-xmark'));
851
+ return true;
852
+ }
853
+
854
+ container.appendChild(addButton('js-btn-company-run', 'Run as Company', 'fa-solid fa-building'));
855
+ return true;
856
+ };
857
+
858
+ // AI toggle (non-image)
859
+ let hasPrimaryActions = false;
860
+ if (contentType !== 'image' && contentType !== 'memo') {
861
+ const aiBtn = addButton(`js-btn-ai ${isAiEnabled ? 'active-ai' : 'inactive-ai'}`, isAiEnabled ? 'Disable AI reference' : 'Enable AI reference', 'fa-solid fa-brain');
862
+ if (!isAiEnabled) {
863
+ const span = document.createElement('span');
864
+ span.className = 'slash-overlay';
865
+ span.textContent = '\\';
866
+ aiBtn.appendChild(span);
867
+ }
868
+ container.appendChild(aiBtn);
869
+ hasPrimaryActions = true;
870
+ }
871
+
872
+ hasPrimaryActions = appendCompanyButtons() || hasPrimaryActions;
873
+
874
+ if (hasPrimaryActions) {
875
+ addSeparator();
876
+ }
877
+
878
+ // Type-specific buttons
879
+ switch (contentType) {
880
+ case 'text': {
881
+ if (!nodeEntry.model.sourceFilePath) {
882
+ container.appendChild(addButton('js-btn-edit', 'Edit content', 'fa-solid fa-pen-to-square'));
883
+ }
884
+ container.appendChild(addButton('js-btn-copy', 'Copy content', 'fa-regular fa-copy'));
885
+ container.appendChild(addButton('js-btn-fit-text-height', 'Fit text height', 'fa-solid fa-arrows-up-down'));
886
+ container.appendChild(addButton('disabled', 'Share (coming soon)', 'fa-solid fa-share-nodes', true));
887
+ break;
888
+ }
889
+ case 'note': {
890
+ container.appendChild(addButton('js-btn-edit', 'Edit content', 'fa-solid fa-pen-to-square'));
891
+ container.appendChild(addButton('js-btn-copy', 'Copy content', 'fa-regular fa-copy'));
892
+ container.appendChild(addButton('js-btn-fit-text-height', 'Fit text height', 'fa-solid fa-arrows-up-down'));
893
+ container.appendChild(addButton('disabled', 'Share (coming soon)', 'fa-solid fa-share-nodes', true));
894
+ break;
895
+ }
896
+ case 'memo': {
897
+ const colorBtn = addButton('js-btn-color', 'Change background color', 'fa-solid fa-palette');
898
+ colorBtn.appendChild(buildMemoColorPopover(getMemoColorKey(nodeEntry.model)));
899
+ container.appendChild(colorBtn);
900
+ container.appendChild(addButton('js-btn-copy', 'Copy memo', 'fa-regular fa-copy'));
901
+ break;
902
+ }
903
+ case 'code': {
904
+ const wrapBtn = addButton(`js-btn-wordwrap ${isWordWrap ? '' : 'active-nowrap'}`, isWordWrap ? 'Disable word wrap' : 'Enable word wrap', isWordWrap ? 'fa-solid fa-align-left' : 'fa-solid fa-arrows-left-right');
905
+ container.appendChild(wrapBtn);
906
+ container.appendChild(addButton('js-btn-edit', 'Edit content', 'fa-solid fa-pen-to-square'));
907
+ container.appendChild(addButton('js-btn-copy', 'Copy content', 'fa-regular fa-copy'));
908
+ container.appendChild(addButton('js-btn-fit-text-height', 'Fit text height', 'fa-solid fa-arrows-up-down'));
909
+ container.appendChild(addButton('disabled', 'Share (coming soon)', 'fa-solid fa-share-nodes', true));
910
+ break;
911
+ }
912
+ case 'markdown': {
913
+ container.appendChild(addButton('js-btn-copy', 'Copy content', 'fa-regular fa-copy'));
914
+ container.appendChild(addButton('js-btn-fit-text-height', 'Fit text height', 'fa-solid fa-arrows-up-down'));
915
+ container.appendChild(addButton('disabled', 'Share (coming soon)', 'fa-solid fa-share-nodes', true));
916
+ break;
917
+ }
918
+ case 'file': {
919
+ container.appendChild(addButton('js-btn-copy', 'Copy content', 'fa-regular fa-copy'));
920
+ break;
921
+ }
922
+ case 'directory': {
923
+ container.appendChild(addButton('js-btn-settings', 'Folder settings', 'fa-solid fa-gear'));
924
+ break;
925
+ }
926
+ case 'image': {
927
+ container.appendChild(addButton('js-btn-copy-image', 'Copy image to clipboard', 'fa-regular fa-copy'));
928
+ break;
929
+ }
930
+ case 'video': {
931
+ container.appendChild(addButton('js-btn-video-play', 'Play video', 'fa-solid fa-play'));
932
+ container.appendChild(addButton('js-btn-video-pause', 'Pause video', 'fa-solid fa-pause'));
933
+ break;
934
+ }
935
+ }
936
+
937
+ addSeparator();
938
+ container.appendChild(addButton('delete-btn js-btn-delete', 'Delete', 'fa-solid fa-trash'));
939
+
940
+ return container;
941
+ }
942
+
943
+ function hideMenu() {
944
+ if (currentMenuElement && currentMenuElement.parentNode) {
945
+ currentMenuElement.parentNode.removeChild(currentMenuElement);
946
+ }
947
+ currentMenuElement = null;
948
+ currentMenuNodeId = null;
949
+ }
950
+
951
+ // ▼▼▼ [New] Screen-space position using worldToScreen ▼▼▼
952
+ function worldToScreen(worldX, worldY, camera, container) {
953
+ const THREE = globalThis.THREE;
954
+ const vector = new THREE.Vector3(worldX, worldY, 0);
955
+ vector.project(camera);
956
+
957
+ const halfWidth = container.clientWidth / 2;
958
+ const halfHeight = container.clientHeight / 2;
959
+
960
+ return {
961
+ x: (vector.x * halfWidth) + halfWidth,
962
+ y: -(vector.y * halfHeight) + halfHeight
963
+ };
964
+ }
965
+
966
+ function updateMenuScreenPosition(menuEl, nodeEntry) {
967
+ if (!menuEl || !nodeEntry || !_module || !_module.camera || !_module.container) return;
968
+
969
+ const templateNode = document.getElementById(`node-${nodeEntry.model?.id || currentMenuNodeId}`);
970
+ let anchorX = null;
971
+ let anchorY = null;
972
+ const shouldPreferProjectedAnchor =
973
+ _module.isZooming === true ||
974
+ _module.isPanning === true ||
975
+ _module.isDraggingNode === true ||
976
+ _module.isDraggingMultipleNodes === true ||
977
+ _module.isResizing === true;
978
+
979
+ if (templateNode && !shouldPreferProjectedAnchor) {
980
+ const rect = templateNode.getBoundingClientRect();
981
+ if (Number.isFinite(rect.left) && Number.isFinite(rect.top) && rect.width > 0 && rect.height > 0) {
982
+ anchorX = rect.left + rect.width / 2;
983
+ anchorY = rect.top;
984
+ }
985
+ }
986
+
987
+ if (!Number.isFinite(anchorX) || !Number.isFinite(anchorY)) {
988
+ const model = nodeEntry.model || {};
989
+ const glObject = nodeEntry.glObject || null;
990
+
991
+ // Use live world position during drag to keep menu attached to moving node.
992
+ const posX = glObject ? glObject.position.x : (model.positionX || 0);
993
+ const posY = glObject ? glObject.position.y : (model.positionY || 0);
994
+
995
+ // Prefer runtime width when available (resize/drag in-progress), fallback to model width.
996
+ const liveWidth = glObject?.userData?.width;
997
+ const width = Number.isFinite(liveWidth) ? liveWidth : (model.width || 400);
998
+
999
+ // Node top-center in world coordinates
1000
+ const centerX = posX + width / 2;
1001
+ const topY = posY;
1002
+ const screenPos = worldToScreen(centerX, topY, _module.camera, _module.container);
1003
+ anchorX = screenPos.x;
1004
+ anchorY = screenPos.y;
1005
+ }
1006
+
1007
+ const menuRect = menuEl.getBoundingClientRect();
1008
+ const halfMenuWidth = menuRect.width > 0 ? menuRect.width / 2 : 0;
1009
+ const viewportMargin = 10;
1010
+ const verticalGap = 14;
1011
+
1012
+ const clampedLeft = Math.min(
1013
+ window.innerWidth - viewportMargin - halfMenuWidth,
1014
+ Math.max(viewportMargin + halfMenuWidth, anchorX)
1015
+ );
1016
+ const anchoredTop = anchorY - verticalGap;
1017
+ const minTop = viewportMargin + (menuRect.height || 0);
1018
+ const clampedTop = Math.max(minTop, anchoredTop);
1019
+
1020
+ menuEl.style.left = `${clampedLeft}px`;
1021
+ menuEl.style.top = `${clampedTop}px`;
1022
+ }
1023
+ // ▲▲▲ [New] ▲▲▲
1024
+
1025
+ function update() {
1026
+ if (currentMenuElement && currentMenuNodeId && _module) {
1027
+ const nodeEntry = _module.nodeObjectsById.get(currentMenuNodeId);
1028
+ if (nodeEntry) {
1029
+ updateMenuScreenPosition(currentMenuElement, nodeEntry);
1030
+ } else {
1031
+ hideMenu();
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ function bindMenuEvents(element, nodeId, nodeEntry) {
1037
+ const invokeMenuAction = async (button, methodName, ...args) => {
1038
+ if (!button || !_module?.dotNetHelper || button.disabled) {
1039
+ return;
1040
+ }
1041
+
1042
+ button.disabled = true;
1043
+ hideMenu();
1044
+
1045
+ try {
1046
+ await _module.dotNetHelper.invokeMethodAsync(methodName, ...args);
1047
+ } catch (error) {
1048
+ console.error(`[MindMapMenuManager] ${methodName} failed for node ${nodeId}`, error);
1049
+ } finally {
1050
+ button.disabled = false;
1051
+ }
1052
+ };
1053
+
1054
+ element.addEventListener('mousedown', (e) => {
1055
+ if (!e.target.closest('.js-btn-color')) {
1056
+ element.querySelectorAll('.menu-color-popover.is-open').forEach(popover => {
1057
+ popover.classList.remove('is-open');
1058
+ });
1059
+ }
1060
+ });
1061
+
1062
+ // AI Toggle
1063
+ const aiBtn = element.querySelector('.js-btn-ai');
1064
+ if (aiBtn) {
1065
+ aiBtn.addEventListener('click', (e) => {
1066
+ e.stopPropagation();
1067
+ nodeEntry.model.isAiEnabled = !nodeEntry.model.isAiEnabled;
1068
+ if (_module.dotNetHelper) _module.dotNetHelper.invokeMethodAsync('ToggleNodeAi', nodeId);
1069
+
1070
+ const icon = aiBtn.querySelector('span.slash-overlay');
1071
+ if (icon) icon.remove();
1072
+ else {
1073
+ const span = document.createElement('span');
1074
+ span.className = 'slash-overlay';
1075
+ span.textContent = '\\';
1076
+ aiBtn.querySelector('i').after(span);
1077
+ }
1078
+ aiBtn.classList.toggle('active-ai');
1079
+ aiBtn.classList.toggle('inactive-ai');
1080
+ });
1081
+ }
1082
+
1083
+ // Edit (convert text to note, or enter edit mode for note/code)
1084
+ const editBtn = element.querySelector('.js-btn-edit');
1085
+ if (editBtn) {
1086
+ editBtn.addEventListener('click', (e) => {
1087
+ e.stopPropagation();
1088
+ console.log(`[MindMapMenuManager] Edit button clicked for node ${nodeId}`);
1089
+
1090
+ const contentType = nodeEntry.model.contentType;
1091
+
1092
+ if (contentType === 'note' || contentType === 'code') {
1093
+ if (window.MindMapInteractions && window.MindMapInteractions.enterEditMode) {
1094
+ window.MindMapInteractions.enterEditMode(_module, nodeId);
1095
+ } else if (_module.dotNetHelper) {
1096
+ _module.dotNetHelper.invokeMethodAsync('EnterEditMode', nodeId);
1097
+ }
1098
+ } else if (contentType === 'text') {
1099
+ hideMenu();
1100
+ if (_module.dotNetHelper) {
1101
+ _module.dotNetHelper.invokeMethodAsync('ConvertToNoteNode', nodeId);
1102
+ }
1103
+ }
1104
+ });
1105
+ }
1106
+
1107
+ // Copy (text)
1108
+ const copyBtn = element.querySelector('.js-btn-copy');
1109
+ if (copyBtn) {
1110
+ copyBtn.addEventListener('click', (e) => {
1111
+ e.stopPropagation();
1112
+ const isMemo = nodeEntry.model.contentType === 'memo';
1113
+ const metadataText = isMemo
1114
+ ? [nodeEntry.model.prompt || '', nodeEntry.model.response || ''].filter(Boolean).join('\n\n')
1115
+ : (nodeEntry.model.response || '');
1116
+ const text = metadataText;
1117
+ navigator.clipboard.writeText(text).then(() => {
1118
+ showActionSuccessIcon(copyBtn);
1119
+ });
1120
+ });
1121
+ }
1122
+
1123
+ const fitTextHeightBtn = element.querySelector('.js-btn-fit-text-height');
1124
+ if (fitTextHeightBtn && isTextFitNode(nodeEntry.model)) {
1125
+ fitTextHeightBtn.addEventListener('click', async (e) => {
1126
+ e.stopPropagation();
1127
+ fitTextHeightBtn.disabled = true;
1128
+ try {
1129
+ await window.MindMapMultiSelect?.fitTextNodesToContentAndReflowY?.([nodeId]);
1130
+ const icon = fitTextHeightBtn.querySelector('i');
1131
+ if (icon) {
1132
+ const oldClass = icon.className;
1133
+ icon.className = 'fa-solid fa-check text-green-500';
1134
+ setTimeout(() => icon.className = oldClass, 1000);
1135
+ }
1136
+ requestAnimationFrame(() => update());
1137
+ } finally {
1138
+ fitTextHeightBtn.disabled = false;
1139
+ }
1140
+ });
1141
+ }
1142
+
1143
+ const colorBtn = element.querySelector('.js-btn-color');
1144
+ const colorPopover = colorBtn?.querySelector('.menu-color-popover');
1145
+ if (colorBtn && colorPopover) {
1146
+ syncMemoColorPopover(colorPopover, getMemoColorKey(nodeEntry.model));
1147
+
1148
+ colorBtn.addEventListener('click', (e) => {
1149
+ e.stopPropagation();
1150
+ colorPopover.classList.toggle('is-open');
1151
+ });
1152
+
1153
+ colorPopover.querySelectorAll('.menu-color-swatch').forEach(button => {
1154
+ button.addEventListener('click', (e) => {
1155
+ e.stopPropagation();
1156
+ const colorKey = button.dataset.colorKey || 'coral';
1157
+ const metadata = ensureMetadata(nodeEntry.model);
1158
+ metadata.memoColor = colorKey;
1159
+ metadata.memoColorIsCustom = 'true';
1160
+ nodeEntry.model.metadata = metadata;
1161
+ nodeEntry.model.Metadata = metadata;
1162
+ syncMemoColorPopover(colorPopover, colorKey);
1163
+ colorPopover.classList.remove('is-open');
1164
+ refreshMemoNode(nodeEntry);
1165
+ if (_module.dotNetHelper) {
1166
+ _module.dotNetHelper.invokeMethodAsync('UpdateNodeMetadata', nodeId, 'memoColor', colorKey);
1167
+ }
1168
+ });
1169
+ });
1170
+ }
1171
+
1172
+ // Copy image
1173
+ const copyImgBtn = element.querySelector('.js-btn-copy-image');
1174
+ if (copyImgBtn) {
1175
+ copyImgBtn.addEventListener('click', async (e) => {
1176
+ e.stopPropagation();
1177
+ const candidateSources = getImageClipboardSourceCandidates(nodeEntry.model);
1178
+ if (candidateSources.length === 0) {
1179
+ showToast('No image to copy.', { kind: 'error' });
1180
+ return;
1181
+ }
1182
+
1183
+ try {
1184
+ const { blob, src } = await fetchClipboardImageBlob(nodeEntry.model);
1185
+ const pngBlob = await convertBlobToPngForClipboard(blob);
1186
+ const copiedKind = await writeImageBlobToClipboard(pngBlob, src);
1187
+
1188
+ if (copiedKind !== 'image') {
1189
+ showToast('Image clipboard is unavailable in this browser. Copied the image URL instead.');
1190
+ return;
1191
+ }
1192
+
1193
+ console.log(`[MindMapMenuManager] Image copied to clipboard as PNG (${pngBlob.size} bytes) from ${src}`);
1194
+ showActionSuccessIcon(copyImgBtn);
1195
+ } catch (err) {
1196
+ console.error('Image copy failed:', err);
1197
+ showToast('Failed to copy image to the clipboard.', { kind: 'error' });
1198
+ }
1199
+ });
1200
+ }
1201
+
1202
+ // Delete
1203
+ const delBtn = element.querySelector('.js-btn-delete');
1204
+ if (delBtn) {
1205
+ delBtn.addEventListener('click', (e) => {
1206
+ e.stopPropagation();
1207
+ const snapshot =
1208
+ window.MindMapNodes?.captureDeleteSnapshot?.(_module, nodeId) ||
1209
+ { nodeId: String(nodeId), currentContent: nodeEntry.model.response || '' };
1210
+ hideMenu();
1211
+ if (_module.dotNetHelper) {
1212
+ _module.dotNetHelper.invokeMethodAsync('DeleteSingleNodeSnapshotFromJs', snapshot);
1213
+ }
1214
+ });
1215
+ }
1216
+
1217
+ // Settings
1218
+ const settingBtn = element.querySelector('.js-btn-settings');
1219
+ if (settingBtn) {
1220
+ settingBtn.addEventListener('click', (e) => {
1221
+ e.stopPropagation();
1222
+ if (_module.dotNetHelper) _module.dotNetHelper.invokeMethodAsync('OpenDirectorySettings', nodeId);
1223
+ });
1224
+ }
1225
+
1226
+ // Duplicate button (for single image)
1227
+ const duplicateBtn = element.querySelector('.js-btn-duplicate');
1228
+ if (duplicateBtn) {
1229
+ duplicateBtn.addEventListener('click', (e) => {
1230
+ e.stopPropagation();
1231
+ if (_module.dotNetHelper) {
1232
+ _module.dotNetHelper.invokeMethodAsync('DuplicateNodeFromJs', nodeId);
1233
+ }
1234
+ const i = duplicateBtn.querySelector('i');
1235
+ if (i) {
1236
+ const oldClass = i.className;
1237
+ i.className = 'fa-solid fa-check text-green-500';
1238
+ setTimeout(() => i.className = oldClass, 1000);
1239
+ }
1240
+ });
1241
+ }
1242
+
1243
+ const videoPlayBtn = element.querySelector('.js-btn-video-play');
1244
+ if (videoPlayBtn) {
1245
+ videoPlayBtn.addEventListener('click', (e) => {
1246
+ e.stopPropagation();
1247
+ window.MindMapCss3DManager?.setVideoNodePlayback?.(nodeId, true);
1248
+ });
1249
+ }
1250
+
1251
+ const videoPauseBtn = element.querySelector('.js-btn-video-pause');
1252
+ if (videoPauseBtn) {
1253
+ videoPauseBtn.addEventListener('click', (e) => {
1254
+ e.stopPropagation();
1255
+ window.MindMapCss3DManager?.setVideoNodePlayback?.(nodeId, false);
1256
+ });
1257
+ }
1258
+
1259
+ // ▼▼▼ [NEW] Word wrap toggle for code nodes ▼▼▼
1260
+ const wrapBtn = element.querySelector('.js-btn-wordwrap');
1261
+ if (wrapBtn) {
1262
+ wrapBtn.addEventListener('click', (e) => {
1263
+ e.stopPropagation();
1264
+ const newWrapState = !nodeEntry.model.isWordWrap;
1265
+ nodeEntry.model.isWordWrap = newWrapState;
1266
+ console.log(`[MindMapMenuManager] Word wrap toggled for ${nodeId}: ${newWrapState}`);
1267
+
1268
+ const icon = wrapBtn.querySelector('i');
1269
+ if (icon) {
1270
+ icon.className = newWrapState
1271
+ ? 'fa-solid fa-align-left'
1272
+ : 'fa-solid fa-arrows-left-right';
1273
+ }
1274
+ wrapBtn.classList.toggle('active-nowrap', !newWrapState);
1275
+ wrapBtn.title = newWrapState ? 'Disable word wrap' : 'Enable word wrap';
1276
+
1277
+ if (_module.dotNetHelper) {
1278
+ _module.dotNetHelper.invokeMethodAsync('ToggleNodeWordWrap', nodeId);
1279
+ }
1280
+
1281
+ if (window.MindMapNodes && window.MindMapNodes.updateNodeWordWrap) {
1282
+ window.MindMapNodes.updateNodeWordWrap(_module, nodeId, newWrapState);
1283
+ }
1284
+ });
1285
+ }
1286
+ // ▲▲▲ [NEW] ▲▲▲
1287
+
1288
+ const companyRunBtn = element.querySelector('.js-btn-company-run');
1289
+ if (companyRunBtn) {
1290
+ companyRunBtn.addEventListener('click', async (e) => {
1291
+ e.stopPropagation();
1292
+ if (!_module?.dotNetHelper || companyRunBtn.disabled) {
1293
+ return;
1294
+ }
1295
+
1296
+ companyRunBtn.disabled = true;
1297
+ applyOptimisticCompanyRunState(nodeEntry, nodeId, 'Auto', true);
1298
+ syncCompanyNodeVisual(nodeEntry);
1299
+ showMenu(nodeId);
1300
+
1301
+ try {
1302
+ const result = await _module.dotNetHelper.invokeMethodAsync('RunNodeAsCompanyFromJs', nodeId, 'Auto', true);
1303
+ applyCompanyRunResult(nodeEntry, result, 'Auto', true);
1304
+ } catch (error) {
1305
+ console.error(`[MindMapMenuManager] RunNodeAsCompanyFromJs failed for node ${nodeId}`, error);
1306
+ applyCompanyRunResult(nodeEntry, { status: 'Failed', lastMessage: 'Company start failed from this note.' }, 'Auto', true);
1307
+ } finally {
1308
+ syncCompanyNodeVisual(nodeEntry);
1309
+ showMenu(nodeId);
1310
+ }
1311
+ });
1312
+ }
1313
+
1314
+ const companySyncGoalBtn = element.querySelector('.js-btn-company-sync-goal');
1315
+ if (companySyncGoalBtn) {
1316
+ companySyncGoalBtn.addEventListener('click', async (e) => {
1317
+ e.stopPropagation();
1318
+ if (!_module?.dotNetHelper || companySyncGoalBtn.disabled) {
1319
+ return;
1320
+ }
1321
+
1322
+ companySyncGoalBtn.disabled = true;
1323
+ applyCompanyBindingValues(nodeEntry.model, {
1324
+ 'CompanyCore.LastMessage': 'Updating the existing company goal from this note...',
1325
+ 'CompanyCore.Status': getCompanyStatus(nodeEntry.model) || 'Running'
1326
+ });
1327
+ syncCompanyNodeVisual(nodeEntry);
1328
+ showMenu(nodeId);
1329
+
1330
+ try {
1331
+ const mode = getCompanyBindingValue(nodeEntry.model, 'CompanyCore.ExecutionMode') || 'Auto';
1332
+ const subAgentsEnabled = getCompanyBindingValue(nodeEntry.model, 'CompanyCore.SubAgentsEnabled') !== 'false';
1333
+ const result = await _module.dotNetHelper.invokeMethodAsync('SyncCompanyGoalFromJs', nodeId, mode, subAgentsEnabled);
1334
+ applyCompanyRunResult(nodeEntry, result, mode, subAgentsEnabled);
1335
+ } catch (error) {
1336
+ console.error(`[MindMapMenuManager] SyncCompanyGoalFromJs failed for node ${nodeId}`, error);
1337
+ applyCompanyBindingValues(nodeEntry.model, {
1338
+ 'CompanyCore.LastMessage': 'Updating the company goal failed.',
1339
+ 'CompanyCore.Status': 'Failed'
1340
+ });
1341
+ } finally {
1342
+ syncCompanyNodeVisual(nodeEntry);
1343
+ showMenu(nodeId);
1344
+ }
1345
+ });
1346
+ }
1347
+
1348
+ const companyGraphBtn = element.querySelector('.js-btn-company-graph');
1349
+ if (companyGraphBtn) {
1350
+ companyGraphBtn.addEventListener('click', async (e) => {
1351
+ e.stopPropagation();
1352
+ await invokeMenuAction(companyGraphBtn, 'OpenCompanyGraphFromJs', nodeId);
1353
+ });
1354
+ }
1355
+
1356
+ const companyRefreshBtn = element.querySelector('.js-btn-company-refresh');
1357
+ if (companyRefreshBtn) {
1358
+ companyRefreshBtn.addEventListener('click', async (e) => {
1359
+ e.stopPropagation();
1360
+ await invokeMenuAction(companyRefreshBtn, 'RefreshCompanyBindingFromJs', nodeId);
1361
+ });
1362
+ }
1363
+
1364
+ const companyPauseBtn = element.querySelector('.js-btn-company-pause');
1365
+ if (companyPauseBtn) {
1366
+ companyPauseBtn.addEventListener('click', async (e) => {
1367
+ e.stopPropagation();
1368
+ await invokeMenuAction(companyPauseBtn, 'PauseCompanyFromJs', nodeId);
1369
+ });
1370
+ }
1371
+
1372
+ const companyResumeBtn = element.querySelector('.js-btn-company-resume');
1373
+ if (companyResumeBtn) {
1374
+ companyResumeBtn.addEventListener('click', async (e) => {
1375
+ e.stopPropagation();
1376
+ await invokeMenuAction(companyResumeBtn, 'ResumeCompanyFromJs', nodeId);
1377
+ });
1378
+ }
1379
+
1380
+ const companyArchiveBtn = element.querySelector('.js-btn-company-archive');
1381
+ if (companyArchiveBtn) {
1382
+ companyArchiveBtn.addEventListener('click', async (e) => {
1383
+ e.stopPropagation();
1384
+ await invokeMenuAction(companyArchiveBtn, 'ArchiveCompanyFromJs', nodeId);
1385
+ });
1386
+ }
1387
+
1388
+ const companyDetachBtn = element.querySelector('.js-btn-company-detach');
1389
+ if (companyDetachBtn) {
1390
+ companyDetachBtn.addEventListener('click', async (e) => {
1391
+ e.stopPropagation();
1392
+ await invokeMenuAction(companyDetachBtn, 'DetachCompanyFromJs', nodeId);
1393
+ });
1394
+ }
1395
+
1396
+ const companyRemoveBtn = element.querySelector('.js-btn-company-remove');
1397
+ if (companyRemoveBtn) {
1398
+ companyRemoveBtn.addEventListener('click', async (e) => {
1399
+ e.stopPropagation();
1400
+ if (!_module?.dotNetHelper || companyRemoveBtn.disabled) {
1401
+ return;
1402
+ }
1403
+
1404
+ const shouldRemove = await showConfirmDialog({
1405
+ title: 'Remove company?',
1406
+ message: 'The company will be archived and detached from this node.',
1407
+ confirmText: 'Remove',
1408
+ cancelText: 'Cancel'
1409
+ });
1410
+ if (!shouldRemove) {
1411
+ return;
1412
+ }
1413
+
1414
+ companyRemoveBtn.disabled = true;
1415
+ try {
1416
+ await _module.dotNetHelper.invokeMethodAsync('RemoveCompanyFromJs', nodeId);
1417
+ clearCompanyBindingValues(nodeEntry);
1418
+ } catch (error) {
1419
+ console.error(`[MindMapMenuManager] RemoveCompanyFromJs failed for node ${nodeId}`, error);
1420
+ } finally {
1421
+ syncCompanyNodeVisual(nodeEntry);
1422
+ showMenu(nodeId);
1423
+ }
1424
+ });
1425
+ }
1426
+ }
1427
+
1428
+ window.MindMapMenuManager = { init, showMenu, hideMenu, update };
1429
+
1430
+ console.log('✅ mind-map-menu-manager.js loaded');
1431
+ })();