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,103 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — File links in agent messages: the page side
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // One click handler for the links in the messages of the four side panels
6
+ // (Assistant, Claude, Codex, OpenCode). A link that points at a file the code
7
+ // editor can open opens it there (`open-file-editor`), at its line and column;
8
+ // every other link keeps what its panel did with it. What a target means and
9
+ // what a click does are decided in file-links.js; this module gives it the
10
+ // page: the bus, the toast, and the server. Whether a file can be opened is
11
+ // the editor's own answer: GET /api/file-content, with the path checks that
12
+ // route applies.
13
+
14
+ import { emit } from './state.js';
15
+ import { t } from './i18n.js';
16
+ import { createFileLinkHandlers, fillFileLinkCopy } from './file-links.js';
17
+
18
+ /** A `fileLinks.*` sentence in the active locale; in English where i18n never loads (a detached session). */
19
+ export function fileLinkText(key, params) {
20
+ const id = `fileLinks.${key}`;
21
+ let text = id;
22
+ try { text = t(id, params); } catch { /* i18n is not there: the English below */ }
23
+ return text && text !== id ? text : fillFileLinkCopy(key, params);
24
+ }
25
+
26
+ /** A short message over the page: the toast the editor uses. */
27
+ export function showFileLinkNotice(message) {
28
+ let toast = document.getElementById('fe-toast');
29
+ if (!toast) {
30
+ toast = document.createElement('div');
31
+ toast.id = 'fe-toast';
32
+ toast.style.cssText = 'position:fixed;bottom:60px;left:50%;transform:translateX(-50%);background:rgba(30,30,34,0.95);color:#e0e0e0;padding:6px 16px;border-radius:6px;font-size:12px;font-family:var(--ff-mono);z-index:2147483647;pointer-events:none;opacity:0;transition:opacity 0.2s;border:1px solid rgba(255,255,255,0.1);';
33
+ document.body.appendChild(toast);
34
+ }
35
+ toast.textContent = message;
36
+ toast.style.opacity = '1';
37
+ clearTimeout(toast._timer);
38
+ toast._timer = setTimeout(() => { toast.style.opacity = '0'; }, 3500);
39
+ }
40
+
41
+ let _home = null;
42
+
43
+ // The home folder, for `~`: the folder picker's route answers with it when no path is asked for.
44
+ function homeFolder() {
45
+ _home ??= fetch('/api/browse-directory')
46
+ .then((res) => (res.ok ? res.json() : null))
47
+ .then((data) => (typeof data?.current === 'string' ? data.current : ''))
48
+ .catch(() => '')
49
+ .then((home) => { if (!home) _home = null; return home; });
50
+ return _home;
51
+ }
52
+
53
+ async function probeFile(path) {
54
+ const res = await fetch(`/api/file-content?path=${encodeURIComponent(path)}`);
55
+ // Only the answer matters: the editor reads the file itself when it opens it.
56
+ try { await res.body?.cancel(); } catch { /* already read or closed */ }
57
+ return res.status;
58
+ }
59
+
60
+ const handlers = createFileLinkHandlers({
61
+ emit,
62
+ probe: probeFile,
63
+ home: homeFolder,
64
+ origin: () => window.location.origin,
65
+ say: fileLinkText,
66
+ notify: showFileLinkNotice,
67
+ follow: (anchor) => {
68
+ if (anchor.target === '_blank') window.open(anchor.href, '_blank', 'noopener,noreferrer');
69
+ else window.location.assign(anchor.href);
70
+ },
71
+ });
72
+
73
+ /**
74
+ * Take a click on a link in an agent's message when the link points at a file (see createFileLinkHandlers).
75
+ * @returns {boolean} true when the click was taken: the browser no longer follows the link
76
+ */
77
+ export function handleFileLinkClick(event, options = {}) {
78
+ const taken = handlers.handleClick(event, options);
79
+ if (!taken) return false;
80
+ taken.catch((error) => console.warn('[file-links] click failed', error));
81
+ return true;
82
+ }
83
+
84
+ /**
85
+ * Give the links of a panel's messages their file behaviour. One listener on the container covers messages
86
+ * that are streaming, finished, or put back from history.
87
+ * @param {Element} container the element every message of the panel is rendered into
88
+ * @param {{ cwd?: (anchor: Element) => string, textLinks?: string, copyPath?: (anchor: Element, path: string) => any,
89
+ * notify?: (message: string) => void }} [options] see createFileLinkHandlers().handleClick
90
+ * @returns {() => void} removes the listeners
91
+ */
92
+ export function wireFileLinks(container, options = {}) {
93
+ if (!container) return () => {};
94
+ const own = { ...options, within: container };
95
+ const onClick = (event) => { handleFileLinkClick(event, own); };
96
+ const onOver = (event) => handlers.label(event, own);
97
+ container.addEventListener('click', onClick);
98
+ container.addEventListener('mouseover', onOver);
99
+ return () => {
100
+ container.removeEventListener('click', onClick);
101
+ container.removeEventListener('mouseover', onOver);
102
+ };
103
+ }
@@ -5,6 +5,10 @@
5
5
 
6
6
  import { getHelpSections, getVariant } from './registry.js';
7
7
  import { registerAction, getDisplayKey } from './ui-keybinds.js';
8
+ import { t } from './i18n.js';
9
+
10
+ // The modifier of the File Explorer's keys, as this system shows it.
11
+ const MOD_KEY = /Mac|iPhone|iPad/.test(globalThis.navigator?.platform || '') ? '⌘' : 'Ctrl';
8
12
 
9
13
  // Shared help sections that appear in all variants
10
14
  // Keyboard section uses dynamic keys from central keybinds system
@@ -46,6 +50,28 @@ function getSharedHelpSections() {
46
50
  <div class="help-row"><div class="help-keys"><span class="help-key">Pin</span><span class="help-key">&#x1F4CC;</span></div><span class="help-desc">Lock panel position &amp; size</span></div>
47
51
  </div>`
48
52
  },
53
+ {
54
+ // The File Explorer's tree, while it has the keyboard (shared/explorer/, docs/file-explorer.md)
55
+ order: 85,
56
+ html: `<div class="help-section">
57
+ <div class="help-section-title">${t('fileExplorer.help.title')}</div>
58
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('toggle-file-explorer')}</span></div><span class="help-desc">${t('fileExplorer.help.toggle')}</span></div>
59
+ <div class="help-row"><div class="help-keys"><span class="help-key">&uarr;</span><span class="help-key">&darr;</span><span class="help-key">Home</span><span class="help-key">End</span></div><span class="help-desc">${t('fileExplorer.help.move')}</span></div>
60
+ <div class="help-row"><div class="help-keys"><span class="help-key">&rarr;</span><span class="help-key">&larr;</span></div><span class="help-desc">${t('fileExplorer.help.fold')}</span></div>
61
+ <div class="help-row"><div class="help-keys"><span class="help-key">Enter</span></div><span class="help-desc">${t('fileExplorer.help.open')}</span></div>
62
+ <div class="help-row"><div class="help-keys"><span class="help-key">Space</span></div><span class="help-desc">${t('fileExplorer.help.peek')}</span></div>
63
+ <div class="help-row"><div class="help-keys"><span class="help-key">Shift</span><span class="help-key">&uarr; &darr;</span></div><span class="help-desc">${t('fileExplorer.help.range')}</span></div>
64
+ <div class="help-row"><div class="help-keys"><span class="help-key">A&ndash;Z</span></div><span class="help-desc">${t('fileExplorer.help.typeAhead')}</span></div>
65
+ <div class="help-row"><div class="help-keys"><span class="help-key">&darr;</span><span class="help-key">Enter</span></div><span class="help-desc">${t('fileExplorer.help.offers')}</span></div>
66
+ <div class="help-row"><div class="help-keys"><span class="help-key">F2</span></div><span class="help-desc">${t('fileExplorer.help.rename')}</span></div>
67
+ <div class="help-row"><div class="help-keys"><span class="help-key">Del</span></div><span class="help-desc">${t('fileExplorer.help.delete')}</span></div>
68
+ <div class="help-row"><div class="help-keys"><span class="help-key">${MOD_KEY}</span><span class="help-key">X / C / V</span></div><span class="help-desc">${t('fileExplorer.help.clipboard')}</span></div>
69
+ <div class="help-row"><div class="help-keys"><span class="help-key">${MOD_KEY}</span><span class="help-key">Alt</span><span class="help-key">C</span></div><span class="help-desc">${t('fileExplorer.help.copyPath')}</span></div>
70
+ <div class="help-row"><div class="help-keys"><span class="help-key">${MOD_KEY}</span><span class="help-key">P</span></div><span class="help-desc">${t('fileExplorer.help.goToFile')}</span></div>
71
+ <div class="help-row"><div class="help-keys"><span class="help-key">Shift</span><span class="help-key">F10</span></div><span class="help-desc">${t('fileExplorer.help.menu')}</span></div>
72
+ <div class="help-row"><div class="help-keys"><span class="help-key">Esc</span></div><span class="help-desc">${t('fileExplorer.help.escape')}</span></div>
73
+ </div>`
74
+ },
49
75
  {
50
76
  order: 90,
51
77
  html: `<div class="help-section">
@@ -7,9 +7,6 @@
7
7
  import { emit, on } from './state.js';
8
8
  import {
9
9
  fetchInviteStatus,
10
- fetchTunnelStatus,
11
- startTunnel,
12
- stopTunnel,
13
10
  generateInviteKey,
14
11
  revokeInviteKey,
15
12
  revokeAllInviteSessions,
@@ -18,15 +15,25 @@ import {
18
15
  saveInvitePermissions,
19
16
  } from './api.js';
20
17
  import { isGuest } from './ui-sync.js';
18
+ import { createTunnelCard, drawInto } from './ui-tunnel.js';
21
19
 
22
20
  const $ = (id) => document.getElementById(id);
23
21
 
24
22
  let _isOpen = false;
25
23
  let _data = { hasKey: false, maskedKey: null, activeSessions: 0, proxyConfig: { useProxy: false, proxyUrl: '' } };
26
- let _tunnelUrl = null;
27
- let _tunnelAvailable = false;
28
- let _tunnelStarting = false;
29
- let _tunnelPollTimer = null;
24
+ // The Cloudflare tunnel: what the server says of it, read by tunnel-view.js; asked again by itself while a
25
+ // download or a start is on its way, for as long as the dropdown is open (ui-tunnel.js).
26
+ let _tunnelKind = null;
27
+ const _tunnel = createTunnelCard({
28
+ alive: () => _isOpen,
29
+ onChange(view) {
30
+ if (!_isOpen) return;
31
+ // The address coming or going changes the other sections too (the invite link); anything else only this block.
32
+ const whole = view.kind !== _tunnelKind && (view.kind === 'running' || _tunnelKind === 'running');
33
+ _tunnelKind = view.kind;
34
+ if (whole) render(); else drawTunnel();
35
+ },
36
+ });
30
37
  let _keyMode = 'auto'; // 'auto' | 'custom'
31
38
  let _revealedKey = null; // shown only right after generation
32
39
  let _permissions = { terminal: false, whiteboard: false, memories: true, skills: false, cards: true, browser: false };
@@ -60,15 +67,12 @@ function toggleDropdown() {
60
67
 
61
68
  async function loadAndRender() {
62
69
  try {
63
- const [inviteRes, tunnelRes, permRes] = await Promise.all([
70
+ const [inviteRes, , permRes] = await Promise.all([
64
71
  fetchInviteStatus(),
65
- fetchTunnelStatus(),
72
+ _tunnel.refresh(),
66
73
  fetchInvitePermissions(),
67
74
  ]);
68
75
  _data = inviteRes;
69
- _tunnelUrl = tunnelRes.url || null;
70
- _tunnelAvailable = tunnelRes.available;
71
- _tunnelStarting = tunnelRes.starting || false;
72
76
  if (permRes?.permissions) _permissions = permRes.permissions;
73
77
  } catch (err) {
74
78
  console.warn('[invite] Failed to load status:', err);
@@ -76,38 +80,13 @@ async function loadAndRender() {
76
80
  render();
77
81
  }
78
82
 
79
- // ─── Tunnel polling ─────────────────────
80
-
81
- function pollForTunnelUrl() {
82
- if (_tunnelPollTimer) clearInterval(_tunnelPollTimer);
83
- let attempts = 0;
84
- _tunnelPollTimer = setInterval(async () => {
85
- attempts++;
86
- try {
87
- const res = await fetchTunnelStatus();
88
- if (res.url) {
89
- clearInterval(_tunnelPollTimer);
90
- _tunnelPollTimer = null;
91
- _tunnelUrl = res.url;
92
- _tunnelStarting = false;
93
- if (_isOpen) render();
94
- } else if (attempts >= 30) {
95
- clearInterval(_tunnelPollTimer);
96
- _tunnelPollTimer = null;
97
- _tunnelStarting = false;
98
- if (_isOpen) render();
99
- }
100
- } catch {}
101
- }, 1000);
102
- }
103
-
104
83
  // ─── URL helpers ────────────────────────
105
84
 
106
85
  function getBaseUrl() {
107
86
  if (_data.proxyConfig?.useProxy && _data.proxyConfig?.proxyUrl) {
108
87
  return _data.proxyConfig.proxyUrl.replace(/\/+$/, '');
109
88
  }
110
- return _tunnelUrl || null;
89
+ return _tunnel.view().url || null;
111
90
  }
112
91
 
113
92
  function getInviteUrl() {
@@ -136,9 +115,7 @@ function render() {
136
115
  const dd = $('invite-dropdown');
137
116
  if (!dd) return;
138
117
 
139
- const baseUrl = getBaseUrl();
140
118
  const inviteUrl = getInviteUrl();
141
- const hasUrl = !!baseUrl;
142
119
  const useProxy = _data.proxyConfig?.useProxy || false;
143
120
 
144
121
  dd.innerHTML = `
@@ -153,39 +130,7 @@ function render() {
153
130
  <button class="invite-alt-link" id="invite-mode-tunnel">Use Cloudflare tunnel</button>
154
131
  </div>
155
132
  ` : `
156
- ${hasUrl ? `
157
- <div class="invite-status-row">
158
- <span class="invite-status-dot active"></span>
159
- <span class="invite-url-text">${baseUrl}</span>
160
- <button class="invite-copy-icon" id="invite-copy-base-url" data-tooltip="Copy"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
161
- <span class="invite-copied" id="invite-copied-url">Copied</span>
162
- </div>
163
- <div class="invite-action-row">
164
- <button class="invite-btn" id="invite-tunnel-stop">Stop</button>
165
- <button class="invite-alt-link" id="invite-mode-proxy">Use custom URL</button>
166
- </div>
167
- ` : `
168
- ${_tunnelAvailable ? `
169
- ${_tunnelStarting ? `
170
- <div class="invite-action-row">
171
- <button class="invite-btn" disabled style="opacity:0.5;cursor:default">
172
- <svg viewBox="0 0 24 24" class="invite-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
173
- Starting...
174
- </button>
175
- </div>
176
- ` : `
177
- <div class="invite-action-row">
178
- <button class="invite-btn primary" id="invite-tunnel-start">Start Tunnel</button>
179
- <button class="invite-alt-link" id="invite-mode-proxy">Use custom URL</button>
180
- </div>
181
- `}
182
- ` : `
183
- <div class="invite-action-row">
184
- <span style="font-size:10px;color:var(--t-dimmed)">cloudflared not available</span>
185
- <button class="invite-alt-link" id="invite-mode-proxy">Use custom URL</button>
186
- </div>
187
- `}
188
- `}
133
+ <div id="invite-tunnel"></div>
189
134
  `}
190
135
  </div>
191
136
 
@@ -242,12 +187,118 @@ function render() {
242
187
  ` : ''}
243
188
  `;
244
189
 
190
+ wireTunnel();
191
+ drawTunnel();
245
192
  wireEvents();
246
193
  updateBadge();
247
194
  }
248
195
 
196
+ // ─── The tunnel block ───────────────────
197
+ // Drawn by itself: while cloudflared is being downloaded the progress changes every second, and the rest of the
198
+ // dropdown (a password being typed, a key just shown) must not be drawn over for that.
199
+
200
+ const COPY_ICON = '<svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
201
+ const SPINNER = '<svg viewBox="0 0 24 24" class="invite-spin invite-tunnel-spinner" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>';
202
+ const USE_CUSTOM = '<button class="invite-alt-link" id="invite-mode-proxy">Use custom URL</button>';
203
+
204
+ /** The markup of the tunnel block for a reading of tunnel-view.js. */
205
+ function tunnelBlock(view) {
206
+ const label = escHtml(view.label);
207
+ const detail = view.detail ? `<div class="invite-tunnel-detail">${escHtml(view.detail)}</div>` : '';
208
+ if (view.kind === 'running') {
209
+ return `
210
+ <div class="invite-status-row" role="status">
211
+ <span class="invite-status-dot active" aria-hidden="true"></span>
212
+ <span class="invite-url-text" title="${label}">${escHtml(view.url)}</span>
213
+ <button class="invite-copy-icon" id="invite-copy-base-url" data-tooltip="Copy" aria-label="Copy">${COPY_ICON}</button>
214
+ <span class="invite-copied" id="invite-copied-url">Copied</span>
215
+ </div>
216
+ ${view.copy ? `<div class="invite-tunnel-detail invite-tunnel-copy">${escHtml(view.copy)}${view.copyNote ? ` ${escHtml(view.copyNote)}` : ''}</div>` : ''}
217
+ <div class="invite-action-row">
218
+ <button class="invite-btn" id="invite-tunnel-stop">${escHtml(view.actionLabel)}</button>
219
+ ${USE_CUSTOM}
220
+ </div>`;
221
+ }
222
+ if (view.kind === 'downloading' || view.kind === 'starting') {
223
+ return `
224
+ <div class="invite-status-row" role="status">
225
+ ${SPINNER}
226
+ <span class="invite-tunnel-state">${label}</span>
227
+ ${view.progress ? `<span class="invite-tunnel-percent">${escHtml(view.progress.text)}</span>` : ''}
228
+ </div>
229
+ ${view.progress ? `<div class="invite-progress" role="progressbar" aria-label="${label}" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${view.progress.percent}"><span style="width:${view.progress.percent}%"></span></div>` : ''}
230
+ ${detail}
231
+ <div class="invite-action-row">
232
+ <button class="invite-btn" id="invite-tunnel-stop">${escHtml(view.actionLabel)}</button>
233
+ ${USE_CUSTOM}
234
+ </div>`;
235
+ }
236
+ if (view.kind === 'failed') {
237
+ return `
238
+ <div class="invite-status-row" role="alert">
239
+ <span class="invite-status-dot error" aria-hidden="true"></span>
240
+ <span class="invite-tunnel-state error">${label}</span>
241
+ </div>
242
+ ${detail}
243
+ <div class="invite-action-row">
244
+ <button class="invite-btn primary" id="invite-tunnel-start">${escHtml(view.actionLabel)}</button>
245
+ ${USE_CUSTOM}
246
+ </div>`;
247
+ }
248
+ if (view.kind === 'idle') {
249
+ return `
250
+ ${detail}
251
+ <div class="invite-action-row">
252
+ <button class="invite-btn primary" id="invite-tunnel-start">${escHtml(view.actionLabel)}</button>
253
+ ${USE_CUSTOM}
254
+ </div>`;
255
+ }
256
+ // No tunnel can be started from here (the server needs a restart first, or this system has no tunnel
257
+ // program): the state, why, and the other way to an address.
258
+ return `
259
+ <div class="invite-status-row" role="status">
260
+ <span class="invite-status-dot${view.tone === 'warn' ? ' warn' : ''}" aria-hidden="true"></span>
261
+ <span class="invite-tunnel-state">${label}</span>
262
+ </div>
263
+ ${detail}
264
+ <div class="invite-action-row">
265
+ ${USE_CUSTOM}
266
+ </div>`;
267
+ }
268
+
269
+ function drawTunnel() {
270
+ const box = $('invite-tunnel');
271
+ if (box) drawInto(box, tunnelBlock(_tunnel.view()));
272
+ }
273
+
274
+ /** One listener for the block: its buttons come and go with the state. */
275
+ function wireTunnel() {
276
+ const box = $('invite-tunnel');
277
+ if (!box) return;
278
+ box.addEventListener('click', async (e) => {
279
+ const button = e.target.closest('button');
280
+ if (!button || !box.contains(button)) return;
281
+ e.stopPropagation();
282
+ if (button.id === 'invite-tunnel-start') {
283
+ // Shown at once: the server answers immediately too, and then says what it is doing.
284
+ _tunnel.show({ ...(_tunnel.status() || {}), state: 'starting', starting: true, url: null, error: null });
285
+ await _tunnel.start();
286
+ } else if (button.id === 'invite-tunnel-stop') {
287
+ await _tunnel.stop();
288
+ } else if (button.id === 'invite-copy-base-url') {
289
+ const url = getBaseUrl();
290
+ if (url) copyText(url, 'invite-copied-url');
291
+ } else if (button.id === 'invite-mode-proxy') {
292
+ await saveInviteProxy({ useProxy: true, proxyUrl: _data.proxyConfig?.proxyUrl || '' });
293
+ _data.proxyConfig = { useProxy: true, proxyUrl: _data.proxyConfig?.proxyUrl || '' };
294
+ _revealedKey = null;
295
+ render();
296
+ }
297
+ });
298
+ }
299
+
249
300
  function escHtml(s) {
250
- return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
301
+ return String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
251
302
  }
252
303
 
253
304
  const PERM_ICONS = {
@@ -282,48 +333,7 @@ function wireEvents() {
282
333
  render();
283
334
  });
284
335
 
285
- $('invite-mode-proxy')?.addEventListener('click', async (e) => {
286
- e.stopPropagation();
287
- await saveInviteProxy({ useProxy: true, proxyUrl: _data.proxyConfig?.proxyUrl || '' });
288
- _data.proxyConfig = { useProxy: true, proxyUrl: _data.proxyConfig?.proxyUrl || '' };
289
- _revealedKey = null;
290
- render();
291
- });
292
-
293
- // Copy base URL
294
- $('invite-copy-base-url')?.addEventListener('click', (e) => {
295
- e.stopPropagation();
296
- const url = getBaseUrl();
297
- if (url) copyText(url, 'invite-copied-url');
298
- });
299
-
300
- // Tunnel start/stop
301
- $('invite-tunnel-start')?.addEventListener('click', async (e) => {
302
- e.stopPropagation();
303
- try {
304
- _tunnelStarting = true;
305
- render();
306
- await startTunnel();
307
- pollForTunnelUrl();
308
- } catch (err) {
309
- console.error('[invite] Tunnel start failed:', err);
310
- _tunnelStarting = false;
311
- render();
312
- }
313
- });
314
-
315
- $('invite-tunnel-stop')?.addEventListener('click', async (e) => {
316
- e.stopPropagation();
317
- try {
318
- await stopTunnel();
319
- _tunnelUrl = null;
320
- _tunnelStarting = false;
321
- if (_tunnelPollTimer) { clearInterval(_tunnelPollTimer); _tunnelPollTimer = null; }
322
- render();
323
- } catch (err) {
324
- console.error('[invite] Tunnel stop failed:', err);
325
- }
326
- });
336
+ // (The tunnel block's own buttons, "Use custom URL" among them, are wired in wireTunnel.)
327
337
 
328
338
  // Proxy save
329
339
  $('invite-proxy-save')?.addEventListener('click', async (e) => {
@@ -25,6 +25,8 @@ let _reverseMap = new Map(); // combo string → actionId (for O(1) dispatc
25
25
  let _recording = null; // { actionId, originalCombo } when recording
26
26
  let _pendingBindings = null; // working copy during modal editing
27
27
  let _modalOpen = false;
28
+ // Actions that were removed ("toggle-toolbar" was "Hide all toolbars"): a saved key for one is dropped on load.
29
+ const RETIRED_ACTIONS = ['toggle-toolbar'];
28
30
 
29
31
  // ── Combo building ──
30
32
 
@@ -426,6 +428,8 @@ export async function initKeybinds() {
426
428
  } catch {
427
429
  _bindings = { ...DEFAULT_KEYBINDS };
428
430
  }
431
+ // A key saved for an action that is gone does nothing and must not hold its combo (or show up as a conflict).
432
+ for (const actionId of RETIRED_ACTIONS) delete _bindings[actionId];
429
433
 
430
434
  _reverseMap = buildReverseMap(_bindings);
431
435