synabun 2.0.0 → 2.0.5

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 (334) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +46 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/start-launcher.js +88 -1
  14. package/mcp-server/package-lock.json +2 -2
  15. package/mcp-server/package.json +1 -3
  16. package/neural-interface/i18n/en.json +1500 -153
  17. package/neural-interface/i18n/pt-BR.json +1498 -152
  18. package/neural-interface/lib/assistant-api.js +62 -6
  19. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  20. package/neural-interface/lib/assistant-catalog.js +179 -27
  21. package/neural-interface/lib/assistant-config.js +31 -2
  22. package/neural-interface/lib/assistant-dispatch.js +62 -13
  23. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  24. package/neural-interface/lib/assistant-memory.js +24 -10
  25. package/neural-interface/lib/assistant-persona.js +6 -2
  26. package/neural-interface/lib/assistant-pricing.js +21 -6
  27. package/neural-interface/lib/assistant-runtime.js +181 -12
  28. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  29. package/neural-interface/lib/assistant-test-density.js +142 -0
  30. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  31. package/neural-interface/lib/assistant-user-context.js +908 -0
  32. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  33. package/neural-interface/lib/assistant-user-learn.js +404 -0
  34. package/neural-interface/lib/assistant-user-threads.js +226 -0
  35. package/neural-interface/lib/assistant-user-writer.js +181 -0
  36. package/neural-interface/lib/browser-background-tab.js +134 -0
  37. package/neural-interface/lib/browser-screenshot.js +678 -61
  38. package/neural-interface/lib/claude-agent-bridge.js +33 -6
  39. package/neural-interface/lib/claude-model-catalog.js +30 -2
  40. package/neural-interface/lib/claude-temporary.js +46 -7
  41. package/neural-interface/lib/cloudflared/install.js +533 -0
  42. package/neural-interface/lib/cloudflared/manager.js +545 -0
  43. package/neural-interface/lib/cloudflared/pins.js +115 -0
  44. package/neural-interface/lib/cloudflared/pins.json +63 -0
  45. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  46. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  47. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  48. package/neural-interface/lib/editor-completion.js +714 -0
  49. package/neural-interface/lib/editor-config.js +149 -0
  50. package/neural-interface/lib/editor-files-api.js +268 -0
  51. package/neural-interface/lib/editor-git-api.js +134 -0
  52. package/neural-interface/lib/file-explorer-api.js +1039 -0
  53. package/neural-interface/lib/file-explorer-system.js +355 -0
  54. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  55. package/neural-interface/lib/file-explorer-watch.js +239 -0
  56. package/neural-interface/lib/git-diff-api.js +103 -0
  57. package/neural-interface/lib/git-read.js +202 -0
  58. package/neural-interface/lib/greeting/api.js +89 -0
  59. package/neural-interface/lib/greeting/budgets.js +90 -0
  60. package/neural-interface/lib/greeting/engine.js +461 -0
  61. package/neural-interface/lib/greeting/facts.js +320 -0
  62. package/neural-interface/lib/greeting/sources.js +252 -0
  63. package/neural-interface/lib/greeting/writer.js +139 -0
  64. package/neural-interface/lib/http-guards.js +189 -13
  65. package/neural-interface/lib/machine-names.js +88 -0
  66. package/neural-interface/lib/memory-api.js +31 -7
  67. package/neural-interface/lib/native-loop-providers.js +10 -5
  68. package/neural-interface/lib/plugin-installer.js +4 -2
  69. package/neural-interface/lib/project-storage.js +5 -7
  70. package/neural-interface/lib/request-gate.js +589 -0
  71. package/neural-interface/lib/ui-blob-api.js +108 -0
  72. package/neural-interface/lib/ui-blob-store.js +68 -0
  73. package/neural-interface/package-lock.json +701 -2
  74. package/neural-interface/package.json +13 -2
  75. package/neural-interface/public/install-app.html +9 -6
  76. package/neural-interface/public/offline.html +3 -2
  77. package/neural-interface/public/onboarding.html +12 -1
  78. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  79. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  80. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  81. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  82. package/neural-interface/public/shared/assistant/asst-mascot.js +18 -58
  83. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  84. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  85. package/neural-interface/public/shared/assistant/asst-render.js +162 -19
  86. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  87. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  88. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  89. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  90. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  91. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  92. package/neural-interface/public/shared/assistant/asst-styles.js +158 -4
  93. package/neural-interface/public/shared/blob-store.js +111 -22
  94. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  95. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  96. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  97. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  98. package/neural-interface/public/shared/constants.js +6 -1
  99. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  100. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  101. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  102. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  103. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  104. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  105. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  106. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  107. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  108. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  109. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  110. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  111. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  112. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  113. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  114. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  115. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  116. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  117. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  118. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  119. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  120. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  121. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  122. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  123. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  124. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  125. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  126. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  127. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  128. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  129. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  130. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  131. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  132. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  133. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  134. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  135. package/neural-interface/public/shared/file-links.js +339 -0
  136. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  137. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  138. package/neural-interface/public/shared/html-shell.js +160 -165
  139. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  140. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  141. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  142. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  143. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  144. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  145. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  146. package/neural-interface/public/shared/pwa-install-page.js +29 -1
  147. package/neural-interface/public/shared/pwa-install.css +3 -3
  148. package/neural-interface/public/shared/pwa-install.js +100 -1
  149. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  150. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  151. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  152. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  153. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  154. package/neural-interface/public/shared/start-bridge.js +238 -19
  155. package/neural-interface/public/shared/styles.css +1414 -721
  156. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  157. package/neural-interface/public/shared/tunnel-view.js +167 -0
  158. package/neural-interface/public/shared/ui-claude-panel.js +157 -16
  159. package/neural-interface/public/shared/ui-detail.js +15 -0
  160. package/neural-interface/public/shared/ui-explorer.js +22 -0
  161. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  162. package/neural-interface/public/shared/ui-file-links.js +103 -0
  163. package/neural-interface/public/shared/ui-help.js +26 -0
  164. package/neural-interface/public/shared/ui-invite.js +126 -116
  165. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  166. package/neural-interface/public/shared/ui-loading.js +159 -15
  167. package/neural-interface/public/shared/ui-menubar.js +12 -30
  168. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  169. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  170. package/neural-interface/public/shared/ui-navbar.js +45 -21
  171. package/neural-interface/public/shared/ui-settings.js +455 -488
  172. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  173. package/neural-interface/public/shared/ui-sync.js +16 -0
  174. package/neural-interface/public/shared/ui-terminal.js +23 -5
  175. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  176. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  177. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  178. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  179. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  180. package/neural-interface/public/shared/worker-end.js +251 -0
  181. package/neural-interface/public/sw.js +2 -2
  182. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  183. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  184. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  185. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  186. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  187. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  188. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  189. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  190. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  191. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  192. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  193. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  194. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  195. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  196. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  197. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  198. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  199. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  200. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  201. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  202. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  203. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  204. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  205. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  206. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  207. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  208. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  209. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  210. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  211. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  212. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  309. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  310. package/neural-interface/scripts/build-vendor.mjs +78 -2
  311. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  312. package/neural-interface/server.js +873 -1028
  313. package/neural-interface/templates/rulesets/README.md +1 -1
  314. package/package.json +2 -8
  315. package/setup.js +44 -7
  316. package/benchmarks/browser-live-results.json +0 -79
  317. package/benchmarks/browser-v2-results.json +0 -258
  318. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  319. package/neural-interface/public/category-logos/claude.svg +0 -1
  320. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  321. package/neural-interface/public/category-logos/openai.svg +0 -1
  322. package/neural-interface/public/category-logos/synabun.png +0 -0
  323. package/neural-interface/public/claude.svg +0 -1
  324. package/neural-interface/public/gallery.svg +0 -6
  325. package/neural-interface/public/leonardoai.svg +0 -1
  326. package/neural-interface/public/mcp.svg +0 -4
  327. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  328. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  329. package/neural-interface/public/sessionicon.svg +0 -8
  330. package/neural-interface/public/toolbar-album.svg +0 -8
  331. package/neural-interface/public/toolbar-question.svg +0 -6
  332. package/neural-interface/public/toolbar-sessions.svg +0 -6
  333. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  334. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,371 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Code editor: the minimap
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // An overview of the document at the right edge of the editor: every line a
6
+ // row of small blocks in the colours of its tokens, a box over the part that
7
+ // is in view, and marks for what the editor knows about a line: changed
8
+ // against the last commit (a strip on the left), a search match and a problem
9
+ // (a tick on the right). A click goes to a line; dragging scrolls.
10
+ //
11
+ // It draws only the rows that fit in it, from the editor's own syntax tree, at
12
+ // most once per frame: nothing here is done per document on a keystroke. Of a
13
+ // line it looks at the characters it can draw and no further, so a line of a
14
+ // megabyte (a minified file) costs what any line costs. It is a part of the
15
+ // scroller (so the text is laid out beside it, never under it) that stays in
16
+ // view while the text scrolls.
17
+ // Takes the vendored bundle as an argument: no imports. The helpers at the top
18
+ // need no page: `node --test` loads them as they are.
19
+
20
+ /** Sizes in CSS pixels: the whole width, a row, the block in it, a character, the git strip, the gap after it, a tick. */
21
+ export const MINIMAP = Object.freeze({ width: 64, row: 3, block: 2, char: 0.75, strip: 3, gap: 3, tick: 4 });
22
+ /** Characters of a line that are looked at, at most: what lies beyond is outside the minimap anyway. */
23
+ const LINE_SCAN = 400;
24
+ // @lezer/common's IterMode.IncludeAnonymous: a walk that sees the nodes a parser adds to keep a long run of
25
+ // siblings balanced. Through them a place in the tree is found by its position, not by passing every sibling.
26
+ const INCLUDE_ANONYMOUS = 2;
27
+ /** Search matches marked at most, and how long after an edit they are looked for again. */
28
+ export const MATCH_CAP = 5000;
29
+ const MATCH_DELAY_MS = 200;
30
+
31
+ /**
32
+ * Which lines the minimap shows.
33
+ * @param {{ lines: number, height: number, row?: number, scrollTop: number, scrollMax: number }} o `height`: the
34
+ * minimap's; `scrollTop` / `scrollMax`: where the editor is scrolled to, and the furthest it can be
35
+ * @returns {{ first: number, count: number }} the first line (1-based) and how many: all of them when they fit,
36
+ * else the stretch that is as far through the document as the editor is scrolled
37
+ */
38
+ export function minimapWindow({ lines, height, row = MINIMAP.row, scrollTop, scrollMax }) {
39
+ const total = Math.max(1, Math.floor(lines) || 1);
40
+ const fit = Math.max(1, Math.floor(height / row));
41
+ if (total <= fit) return { first: 1, count: total };
42
+ const ratio = scrollMax > 0 ? Math.min(1, Math.max(0, scrollTop / scrollMax)) : 0;
43
+ return { first: 1 + Math.round(ratio * (total - fit)), count: fit };
44
+ }
45
+
46
+ /**
47
+ * The box over the lines that are in view.
48
+ * @param {{ first: number, count: number, topLine: number, bottomLine: number, row?: number }} o `first` / `count`:
49
+ * what minimapWindow() returned; `topLine` / `bottomLine`: the first and the last line in view
50
+ * @returns {{ top: number, height: number }} in pixels from the minimap's top; never outside the rows it shows
51
+ */
52
+ export function minimapBox({ first, count, topLine, bottomLine, row = MINIMAP.row }) {
53
+ const last = first + count - 1;
54
+ const from = Math.min(Math.max(topLine, first), last);
55
+ const to = Math.min(Math.max(bottomLine, from), last);
56
+ return { top: (from - first) * row, height: (to - from + 1) * row };
57
+ }
58
+
59
+ /** The line under a point of the minimap, `y` pixels from its top. */
60
+ export function minimapLineAt({ y, first, count, row = MINIMAP.row }) {
61
+ return first + Math.min(count - 1, Math.max(0, Math.floor(y / row)));
62
+ }
63
+
64
+ /**
65
+ * The blocks of one line: each run of characters that are not blank and share a colour.
66
+ * @param {string} text the line (or its start)
67
+ * @param {{ tabSize?: number, maxColumns?: number, colorAt?: (index: number) => any }} [o] `colorAt`: the colour
68
+ * of the character at an index of `text`
69
+ * @returns {{ column: number, width: number, color: any }[]} `column`: where the run starts, tabs counted as
70
+ * the columns they take
71
+ */
72
+ export function lineRuns(text, { tabSize = 4, maxColumns = Infinity, colorAt = () => null } = {}) {
73
+ const runs = [];
74
+ let column = 0;
75
+ let open = null;
76
+ for (let i = 0; i < text.length && column < maxColumns; i++) {
77
+ const c = text.charCodeAt(i);
78
+ if (c === 32 || c === 9) {
79
+ open = null;
80
+ column += c === 9 ? tabSize - (column % tabSize) : 1;
81
+ continue;
82
+ }
83
+ const color = colorAt(i);
84
+ if (open && open.color === color) open.width++;
85
+ else { open = { column, width: 1, color }; runs.push(open); }
86
+ column++;
87
+ }
88
+ return runs;
89
+ }
90
+
91
+ /**
92
+ * The stretches of the document a redraw needs the colours of: from each line shown, the characters that can be
93
+ * drawn. Lines that are no longer than `limit` are joined into one stretch; of a longer line only its start is
94
+ * taken, and the next stretch begins on the line after it.
95
+ * @param {{ from: number, lengths: number[], limit: number }} o `from`: where the first line shown starts;
96
+ * `lengths`: the length of each line shown, in order; `limit`: how many characters of a line are looked at
97
+ * @returns {number[]} pairs of document positions: start, end, start, end, …, in order, none of them empty
98
+ */
99
+ export function highlightRanges({ from, lengths, limit }) {
100
+ const ranges = [];
101
+ let at = from; // where the line being looked at starts
102
+ let start = from; // where the stretch that is open starts
103
+ for (const length of lengths) {
104
+ if (length > limit) {
105
+ if (at + limit > start) ranges.push(start, at + limit);
106
+ start = at + length + 1;
107
+ }
108
+ at += length + 1;
109
+ }
110
+ const end = at - 1; // the end of the last line
111
+ if (end > start) ranges.push(start, end);
112
+ return ranges;
113
+ }
114
+
115
+ /**
116
+ * The token colours of what a redraw shows: `[start, end, colour, start, end, colour, …]` in document order.
117
+ * Asked stretch by stretch (highlightRanges), each found in the tree by its position: the cost follows what is
118
+ * drawn, whatever the length of the lines and wherever in the document they are.
119
+ * @param {object} cm the vendored CodeMirror bundle
120
+ * @param {{ tree: object, highlighter: object, ranges: number[], colorOf: (name: string) => any }} o `tree`: the
121
+ * document's syntax tree; `highlighter`: a tag highlighter whose classes `colorOf` turns into colours
122
+ */
123
+ export function tokenSpans(cm, { tree, highlighter, ranges, colorOf }) {
124
+ const spans = [];
125
+ const byPosition = { cursor: () => tree.cursor(INCLUDE_ANONYMOUS), length: tree.length };
126
+ const put = (start, stop, cls) => {
127
+ const name = cls.indexOf(' ') < 0 ? cls : cls.slice(0, cls.indexOf(' '));
128
+ spans.push(start, stop, colorOf(name));
129
+ };
130
+ for (let i = 0; i < ranges.length; i += 2) cm.highlightTree(byPosition, highlighter, put, ranges[i], ranges[i + 1]);
131
+ return spans;
132
+ }
133
+
134
+ /**
135
+ * @param {object} cm the vendored CodeMirror bundle (editor-loader.js)
136
+ * @param {object} o
137
+ * @param {{ tag: any, color?: string }[]} o.highlight the theme's tag → colour table (editor-theme.js: highlightSpecs)
138
+ * @param {{ between: (state: object, from: number, to: number, use: (at: number, kind: string) => void) => void }} [o.git]
139
+ * editor-git.js: the marks of a stretch of the document
140
+ * @param {{ each: (state: object, use: (from: number, to: number) => void) => void }} [o.problems] editor-lint.js
141
+ */
142
+ export function editorMinimap(cm, { highlight, git = null, problems = null }) {
143
+ const { EditorView, ViewPlugin } = cm;
144
+ // The theme's colours are variables: a token's class here is the name of its variable, read off the editor once.
145
+ const variable = (color) => /var\((--[\w-]+)/.exec(String(color || ''))?.[1] || null;
146
+ const highlighter = cm.tagHighlighter(highlight.filter((spec) => variable(spec.color)).map((spec) => ({ tag: spec.tag, class: variable(spec.color) })));
147
+
148
+ const plugin = ViewPlugin.fromClass(class {
149
+ constructor(view) {
150
+ this.view = view;
151
+ this.colors = new Map();
152
+ this.window = { first: 1, count: 1 };
153
+ this.box = { top: 0, height: 0, shown: false };
154
+ this.matches = [];
155
+ this.matchTimer = 0;
156
+ this.drag = null;
157
+
158
+ this.dom = document.createElement('div');
159
+ this.dom.className = 'cm-minimap';
160
+ // An overview for the pointer: everything it shows is in the text, where the keyboard and a screen reader are.
161
+ this.dom.setAttribute('aria-hidden', 'true');
162
+ this.canvas = document.createElement('canvas');
163
+ this.slider = document.createElement('div');
164
+ this.slider.className = 'cm-minimap-box';
165
+ this.dom.append(this.canvas, this.slider);
166
+ view.scrollDOM.insertBefore(this.dom, view.contentDOM.nextSibling);
167
+
168
+ this.onScroll = () => this.schedule();
169
+ view.scrollDOM.addEventListener('scroll', this.onScroll, { passive: true });
170
+ this.onDown = (event) => this.pointerDown(event);
171
+ this.onMove = (event) => this.pointerMove(event);
172
+ this.onUp = (event) => this.pointerUp(event);
173
+ this.dom.addEventListener('pointerdown', this.onDown);
174
+ this.dom.addEventListener('pointermove', this.onMove);
175
+ this.dom.addEventListener('pointerup', this.onUp);
176
+ this.dom.addEventListener('pointercancel', this.onUp);
177
+
178
+ this.measure = { key: this, read: (v) => this.read(v), write: (geometry) => this.draw(geometry) };
179
+ this.findMatches();
180
+ this.schedule();
181
+ }
182
+
183
+ schedule() { this.view.requestMeasure(this.measure); }
184
+
185
+ update(update) {
186
+ const searching = cm.searchPanelOpen(update.state);
187
+ const queryChanged = searching !== cm.searchPanelOpen(update.startState)
188
+ || update.transactions.some((tr) => tr.effects.some((effect) => effect.is(cm.setSearchQuery)));
189
+ if (queryChanged || (update.docChanged && searching)) {
190
+ clearTimeout(this.matchTimer);
191
+ this.matchTimer = setTimeout(() => { this.findMatches(); this.schedule(); }, queryChanged ? 0 : MATCH_DELAY_MS);
192
+ }
193
+ // A caret that only moves changes nothing here. Anything else may: text, size, the parse reaching further,
194
+ // marks that arrived with an effect.
195
+ if (update.docChanged || update.geometryChanged || update.viewportChanged || update.transactions.some((tr) => tr.effects.length)
196
+ || cm.syntaxTree(update.state) !== cm.syntaxTree(update.startState)) this.schedule();
197
+ }
198
+
199
+ // The lines that hold a match of the find bar's query, in order.
200
+ findMatches() {
201
+ this.matchTimer = 0;
202
+ const { state } = this.view;
203
+ const lines = [];
204
+ if (cm.searchPanelOpen(state)) {
205
+ const query = cm.getSearchQuery(state);
206
+ if (query.search && query.valid) {
207
+ const cursor = query.getCursor(state);
208
+ let seen = 0; let last = 0;
209
+ for (let step = cursor.next(); !step.done && seen < MATCH_CAP; step = cursor.next(), seen++) {
210
+ const line = state.doc.lineAt(step.value.from).number;
211
+ if (line !== last) { lines.push(line); last = line; }
212
+ }
213
+ }
214
+ }
215
+ this.matches = lines;
216
+ }
217
+
218
+ color(name) {
219
+ if (!this.colors.has(name)) this.colors.set(name, getComputedStyle(this.view.dom).getPropertyValue(name).trim() || '#888');
220
+ return this.colors.get(name);
221
+ }
222
+
223
+ read(view) {
224
+ const scroller = view.scrollDOM;
225
+ const { doc } = view.state;
226
+ const top = scroller.getBoundingClientRect().top - view.documentTop;
227
+ return {
228
+ height: this.dom.clientHeight,
229
+ scrollTop: scroller.scrollTop,
230
+ scrollMax: scroller.scrollHeight - scroller.clientHeight,
231
+ topLine: doc.lineAt(view.lineBlockAtHeight(top).from).number,
232
+ bottomLine: doc.lineAt(view.lineBlockAtHeight(top + scroller.clientHeight).from).number,
233
+ };
234
+ }
235
+
236
+ draw(geometry) {
237
+ const { view, canvas } = this;
238
+ const { state } = view;
239
+ const { doc } = state;
240
+ const { width, row, block, char, strip, gap, tick } = MINIMAP;
241
+ const height = geometry.height;
242
+ if (height <= 0) return;
243
+ const ratio = Math.max(1, window.devicePixelRatio || 1);
244
+ const w = Math.round(width * ratio); const h = Math.round(height * ratio);
245
+ if (canvas.width !== w || canvas.height !== h) { canvas.width = w; canvas.height = h; }
246
+ const ctx = canvas.getContext('2d');
247
+ if (!ctx) return;
248
+ ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
249
+ ctx.clearRect(0, 0, width, height);
250
+
251
+ const shown = minimapWindow({ lines: doc.lines, height, scrollTop: geometry.scrollTop, scrollMax: geometry.scrollMax });
252
+ this.window = shown;
253
+ const end = shown.first + shown.count; // not included
254
+ const from = doc.line(shown.first).from;
255
+ const to = doc.line(end - 1).to;
256
+
257
+ const left = strip + gap;
258
+ const maxColumns = Math.floor((width - left - tick) / char);
259
+ // A character takes a column at least: no more of a line than there are columns is ever drawn.
260
+ const scan = Math.min(LINE_SCAN, maxColumns);
261
+ const texts = [];
262
+ const lengths = [];
263
+ for (const text of doc.iterLines(shown.first, end)) {
264
+ lengths.push(text.length);
265
+ texts.push(text.length > scan ? text.slice(0, scan) : text);
266
+ }
267
+ // The token colours of what is drawn, from the tree the editor has parsed so far.
268
+ const spans = tokenSpans(cm, { tree: cm.syntaxTree(state), highlighter, ranges: highlightRanges({ from, lengths, limit: scan }), colorOf: (name) => this.color(name) });
269
+ const plain = this.color('--ed-fg');
270
+
271
+ // What is known about its lines.
272
+ const changed = new Map();
273
+ git?.between(state, from, to, (at, kind) => changed.set(doc.lineAt(at).number, kind));
274
+ const broken = new Set();
275
+ problems?.each(state, (start, stop) => {
276
+ if (stop < from || start > to) return;
277
+ const lastLine = doc.lineAt(Math.min(stop, to)).number;
278
+ for (let n = doc.lineAt(Math.max(start, from)).number; n <= lastLine; n++) broken.add(n);
279
+ });
280
+ const matched = new Set();
281
+ for (const line of this.matches) { if (line >= end) break; if (line >= shown.first) matched.add(line); }
282
+
283
+ const tabSize = state.tabSize;
284
+ const gitColor = { added: this.color('--ed-add'), modified: this.color('--ed-warn'), deleted: this.color('--ed-del') };
285
+ let at = from; // the document position of the line being drawn
286
+ let span = 0; // the first span that may reach into it
287
+ for (let n = shown.first; n < end; n++) {
288
+ const text = texts[n - shown.first] || '';
289
+ const y = (n - shown.first) * row;
290
+ ctx.globalAlpha = 1;
291
+ const kind = changed.get(n);
292
+ if (kind) { ctx.fillStyle = gitColor[kind]; ctx.fillRect(0, y, strip, kind === 'deleted' ? 1 : row); }
293
+ if (broken.has(n)) { ctx.fillStyle = this.color('--ed-error'); ctx.fillRect(width - tick, y, tick, block); }
294
+ else if (matched.has(n)) { ctx.fillStyle = this.color('--ed-minimap-match'); ctx.fillRect(width - tick, y, tick, block); }
295
+ if (text) {
296
+ const start = at;
297
+ const colorAt = (index) => {
298
+ const position = start + index;
299
+ while (span < spans.length && spans[span + 1] <= position) span += 3;
300
+ return span < spans.length && spans[span] <= position ? spans[span + 2] : plain;
301
+ };
302
+ ctx.globalAlpha = 0.65;
303
+ for (const run of lineRuns(text, { tabSize, maxColumns, colorAt })) {
304
+ ctx.fillStyle = run.color;
305
+ ctx.fillRect(left + run.column * char, y, Math.min(run.width, maxColumns - run.column) * char, block);
306
+ }
307
+ }
308
+ at += (lengths[n - shown.first] || 0) + 1;
309
+ }
310
+ ctx.globalAlpha = 1;
311
+
312
+ // The part that is in view. With nothing to scroll there is no part to point out.
313
+ const box = minimapBox({ ...shown, topLine: geometry.topLine, bottomLine: geometry.bottomLine });
314
+ this.box = { ...box, shown: geometry.scrollMax > 0 };
315
+ this.slider.style.display = this.box.shown ? '' : 'none';
316
+ this.slider.style.top = `${box.top}px`;
317
+ this.slider.style.height = `${box.height}px`;
318
+ this.extent = Math.min(height, shown.count * row);
319
+ }
320
+
321
+ // The pointer `y` pixels down the minimap, the box held `grab` pixels from its top: scroll so it is there.
322
+ follow(y, grab) {
323
+ const scroller = this.view.scrollDOM;
324
+ const room = (this.extent || 0) - this.box.height;
325
+ const max = scroller.scrollHeight - scroller.clientHeight;
326
+ if (max <= 0) return;
327
+ const ratio = room > 0 ? Math.min(1, Math.max(0, (y - grab) / room)) : 0;
328
+ scroller.scrollTop = ratio * max;
329
+ }
330
+
331
+ pointerDown(event) {
332
+ if (event.button !== 0) return;
333
+ event.preventDefault(); // the keyboard stays in the text
334
+ const y = event.clientY - this.dom.getBoundingClientRect().top;
335
+ const onBox = this.box.shown && y >= this.box.top && y <= this.box.top + this.box.height;
336
+ if (!onBox) {
337
+ // A click beside the box: that line goes to the middle of the view.
338
+ const line = minimapLineAt({ y, ...this.window });
339
+ const { doc } = this.view.state;
340
+ this.view.dispatch({ effects: EditorView.scrollIntoView(doc.line(Math.min(line, doc.lines)).from, { y: 'center' }) });
341
+ }
342
+ this.drag = { grab: onBox ? y - this.box.top : this.box.height / 2, moved: false };
343
+ this.dom.classList.add('cm-minimap-dragging');
344
+ try { this.dom.setPointerCapture(event.pointerId); } catch { /* the pointer is gone already */ }
345
+ }
346
+
347
+ pointerMove(event) {
348
+ if (!this.drag) return;
349
+ this.drag.moved = true;
350
+ this.follow(event.clientY - this.dom.getBoundingClientRect().top, this.drag.grab);
351
+ }
352
+
353
+ pointerUp(event) {
354
+ if (!this.drag) return;
355
+ this.drag = null;
356
+ this.dom.classList.remove('cm-minimap-dragging');
357
+ try { this.dom.releasePointerCapture(event.pointerId); } catch { /* not captured */ }
358
+ }
359
+
360
+ destroy() {
361
+ clearTimeout(this.matchTimer);
362
+ this.view.scrollDOM.removeEventListener('scroll', this.onScroll);
363
+ this.dom.remove();
364
+ }
365
+ });
366
+
367
+ return {
368
+ /** The minimap, for the view's minimap compartment. */
369
+ extension: [plugin, EditorView.scrollMargins.of(() => ({ right: MINIMAP.width }))],
370
+ };
371
+ }
@@ -0,0 +1,192 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Code editor: what the palette lists
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The decisions behind the command palette and go-to-file (editor-palette.js
6
+ // opens them): which commands are listed, how what was typed is matched
7
+ // against a name, in which order the matches come, and what a file search
8
+ // asks the server and makes of its answer. No DOM and no imports: loaded by
9
+ // `node --test` as it is.
10
+
11
+ /** Rows a list shows at most. */
12
+ export const PALETTE_LIMIT = 50;
13
+ /** A file search waits this long after the last key before it asks the server. */
14
+ export const FILE_SEARCH_DELAY_MS = 120;
15
+
16
+ const isUpper = (c) => c >= 65 && c <= 90;
17
+ const isLower = (c) => c >= 97 && c <= 122;
18
+ const isAlnum = (c) => isUpper(c) || isLower(c) || (c >= 48 && c <= 57) || c > 127;
19
+
20
+ /**
21
+ * How well `query` matches `text`: its characters in order, anywhere in the text (case does not count). A match
22
+ * at the start of a word, and characters that follow one another, count for more; so does a text with little
23
+ * else in it. Null when the characters are not all there.
24
+ * @returns {number | null}
25
+ */
26
+ export function fuzzyScore(text, query) {
27
+ const hay = String(text ?? '');
28
+ const needle = String(query ?? '').toLowerCase();
29
+ if (!needle) return 0;
30
+ const lower = hay.toLowerCase();
31
+ let score = 0;
32
+ let from = 0;
33
+ let previous = -2;
34
+ for (let i = 0; i < needle.length; i++) {
35
+ const wanted = needle[i];
36
+ // The next place this character stands, a word start before any other.
37
+ let at = -1;
38
+ for (let j = lower.indexOf(wanted, from); j !== -1; j = lower.indexOf(wanted, j + 1)) {
39
+ if (j === previous + 1) { at = j; break; } // straight after the last one: the run goes on
40
+ const before = j ? hay.charCodeAt(j - 1) : 0;
41
+ const here = hay.charCodeAt(j);
42
+ if (j === 0 || !isAlnum(before) || (isLower(before) && isUpper(here))) { at = j; break; }
43
+ if (at === -1) at = j;
44
+ }
45
+ if (at === -1) return null;
46
+ const before = at ? hay.charCodeAt(at - 1) : 0;
47
+ const wordStart = at === 0 || !isAlnum(before) || (isLower(before) && isUpper(hay.charCodeAt(at)));
48
+ score += 10 + (at === previous + 1 ? 14 : 0) + (wordStart ? 12 : 0) - Math.min(8, Math.max(0, at - from));
49
+ previous = at;
50
+ from = at + 1;
51
+ }
52
+ if (lower.startsWith(needle)) score += 20;
53
+ return score - Math.min(20, Math.floor((hay.length - needle.length) / 4));
54
+ }
55
+
56
+ /** The words of what was typed. */
57
+ export const queryTerms = (query) => String(query ?? '').trim().split(/\s+/).filter(Boolean);
58
+
59
+ /** Every word must match; the scores add up. Null when one does not. */
60
+ function scoreTerms(text, terms) {
61
+ let total = 0;
62
+ for (const term of terms) {
63
+ const score = fuzzyScore(text, term);
64
+ if (score === null) return null;
65
+ total += score;
66
+ }
67
+ return total;
68
+ }
69
+
70
+ /**
71
+ * The commands a palette lists: the ones that can be run from a list (a command with only a mouse gesture, or
72
+ * one marked `palette: false` because it acts on something a list takes the keyboard from, is not), of the
73
+ * features this server has.
74
+ * @param {object[]} commands editor-commands.js
75
+ * @param {{ features?: string[] }} [o]
76
+ * @returns {{ id: string, titleKey: string, key: string | null }[]}
77
+ */
78
+ export function paletteCommands(commands, { features = [] } = {}) {
79
+ const seen = new Set();
80
+ const rows = [];
81
+ for (const command of commands) {
82
+ if (typeof command.run !== 'function' || command.palette === false) continue;
83
+ if (command.feature && !features.includes(command.feature)) continue;
84
+ if (seen.has(command.id)) continue;
85
+ seen.add(command.id);
86
+ rows.push({ id: command.id, titleKey: command.titleKey, key: command.keys?.[0] || null });
87
+ }
88
+ return rows;
89
+ }
90
+
91
+ /**
92
+ * Rows in the order a list shows them for what was typed: the best match first; rows that match equally keep
93
+ * the order they came in. Nothing typed: every row, as it came.
94
+ * @param {{ label: string, detail?: string }[]} rows `detail`: more words that may be matched (a folder), for less
95
+ * @param {string} query
96
+ * @param {{ limit?: number }} [o]
97
+ */
98
+ export function rankRows(rows, query, { limit = PALETTE_LIMIT } = {}) {
99
+ const terms = queryTerms(query);
100
+ if (!terms.length) return rows.slice(0, limit);
101
+ const scored = [];
102
+ rows.forEach((row, index) => {
103
+ const own = scoreTerms(row.label, terms);
104
+ const wider = row.detail ? scoreTerms(`${row.detail}/${row.label}`, terms) : null;
105
+ // A match in the name itself comes before one that needs the folder.
106
+ const score = own !== null ? own + 1000 : wider;
107
+ if (score !== null) scored.push({ row, score, index });
108
+ });
109
+ scored.sort((a, b) => b.score - a.score || a.index - b.index);
110
+ return scored.slice(0, limit).map((entry) => entry.row);
111
+ }
112
+
113
+ /**
114
+ * What a file search asks GET /api/project-files for. The server looks for one run of characters in a path: the
115
+ * longest word that was typed is sent, and the other words are matched here.
116
+ * @param {string} project the project's folder
117
+ * @param {string} query
118
+ * @returns {string | null} the query string, or null when nothing was typed (nothing is asked then)
119
+ */
120
+ export function fileSearchParams(project, query) {
121
+ const terms = queryTerms(query);
122
+ if (!project || !terms.length) return null;
123
+ const longest = terms.reduce((best, term) => (term.length > best.length ? term : best), '');
124
+ return new URLSearchParams({ path: project, search: longest, recursive: '1', filesOnly: '1' }).toString();
125
+ }
126
+
127
+ const slashes = (path) => String(path ?? '').replace(/\\/g, '/');
128
+
129
+ /**
130
+ * The registered project a file is in: the one whose folder holds it, the innermost when projects are nested.
131
+ * Go-to-file searches it when no project is selected in the File Explorer (a file opened from a link in a chat).
132
+ * @param {string} path a file's path
133
+ * @param {{ path?: string }[]} projects the registered projects, as GET /api/projects lists them
134
+ * @returns {string | null} the project's folder
135
+ */
136
+ export function projectFor(path, projects) {
137
+ const file = slashes(path);
138
+ let best = null;
139
+ for (const project of Array.isArray(projects) ? projects : []) {
140
+ const root = slashes(project?.path).replace(/\/+$/, '');
141
+ if (root && file.startsWith(`${root}/`) && (!best || root.length > best.length)) best = root;
142
+ }
143
+ return best;
144
+ }
145
+
146
+ /**
147
+ * The files of a search answer, as rows.
148
+ * @param {{ path?: string, items?: object[] } | null} body what GET /api/project-files answered
149
+ * @returns {{ id: string, label: string, detail: string }[]} `id`: the file's full path; `detail`: its folder
150
+ * inside the project ('' at the top)
151
+ */
152
+ export function fileRows(body) {
153
+ const folder = slashes(body?.path).replace(/\/+$/, '');
154
+ if (!folder || !Array.isArray(body?.items)) return [];
155
+ const rows = [];
156
+ for (const item of body.items) {
157
+ if (!item || item.type !== 'file' || typeof item.name !== 'string') continue;
158
+ const inside = slashes(typeof item.relativePath === 'string' && item.relativePath ? item.relativePath : item.name).replace(/^\/+/, '');
159
+ const cut = inside.lastIndexOf('/');
160
+ rows.push({ id: `${folder}/${inside}`, label: item.name, detail: cut > 0 ? inside.slice(0, cut) : '' });
161
+ }
162
+ return rows;
163
+ }
164
+
165
+ /**
166
+ * The open tabs as rows, for a go-to-file list in which nothing was typed yet: the tab used last first.
167
+ * @param {{ path: string, name: string }[]} tabs in the order they were last used
168
+ * @param {string | null} project the project's folder: a tab inside it shows its folder from there
169
+ */
170
+ export function tabRows(tabs, project) {
171
+ const root = slashes(project).replace(/\/+$/, '');
172
+ return tabs.filter((tab) => tab?.path).map((tab) => {
173
+ const path = slashes(tab.path);
174
+ const inside = root && path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
175
+ const cut = inside.lastIndexOf('/');
176
+ return { id: path, label: tab.name || inside.slice(cut + 1), detail: cut > 0 ? inside.slice(0, cut) : '' };
177
+ });
178
+ }
179
+
180
+ /**
181
+ * What go-to-file says in place of a list that is empty (an i18n key). An answer that is not in yet is not
182
+ * "no file": while a search is waiting to be sent or on its way, it says so.
183
+ * @param {{ rootKnown: boolean, root: string | null, failed: boolean, pending: boolean, typed: string }} s
184
+ * `rootKnown`: the folder to search was asked for and answered (`root`: it, or null when there is none);
185
+ * `pending`: a search for what is typed has not answered yet; `failed`: the last one did not answer
186
+ */
187
+ export function fileListEmpty({ rootKnown, root, failed, pending, typed } = {}) {
188
+ if (rootKnown && !root) return 'editor.palette.noProject';
189
+ if (!queryTerms(typed).length) return 'editor.palette.typeToSearch';
190
+ if (pending || !rootKnown) return 'editor.palette.searching';
191
+ return failed ? 'editor.palette.searchFailed' : 'editor.palette.noFile';
192
+ }