synabun 2.0.0 → 2.0.7

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 (507) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/NOTICE +5 -0
  3. package/README.md +2 -1
  4. package/THIRD-PARTY-LICENSES.md +59 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +153 -18
  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 +26 -3
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/child-path.js +303 -0
  14. package/lib/claude-hooks.js +2 -1
  15. package/lib/data-home-migration.js +2 -0
  16. package/lib/desktop-app.js +265 -0
  17. package/lib/external-tools.js +7 -3
  18. package/lib/log-redact.js +102 -0
  19. package/lib/packaged-runtime.js +138 -11
  20. package/lib/start-launcher.js +88 -1
  21. package/mcp-server/dist/http.d.ts +3 -1
  22. package/mcp-server/dist/http.d.ts.map +1 -1
  23. package/mcp-server/dist/http.js +15 -4
  24. package/mcp-server/dist/http.js.map +1 -1
  25. package/mcp-server/dist/index.d.ts.map +1 -1
  26. package/mcp-server/dist/index.js +9 -6
  27. package/mcp-server/dist/index.js.map +1 -1
  28. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  29. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  30. package/mcp-server/dist/services/budget-judgments.js +153 -0
  31. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  32. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  33. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  34. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  35. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  36. package/mcp-server/dist/services/identity.d.ts +11 -0
  37. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  38. package/mcp-server/dist/services/identity.js +24 -0
  39. package/mcp-server/dist/services/identity.js.map +1 -1
  40. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  41. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  42. package/mcp-server/dist/services/neural-interface.js +34 -2
  43. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  44. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  45. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  46. package/mcp-server/dist/services/typesafe-config.js +4 -1
  47. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  48. package/mcp-server/dist/tools/agents.d.ts +10 -7
  49. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  50. package/mcp-server/dist/tools/agents.js +34 -13
  51. package/mcp-server/dist/tools/agents.js.map +1 -1
  52. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  53. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  54. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  55. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  56. package/mcp-server/dist/tui.js +2 -1
  57. package/mcp-server/dist/tui.js.map +1 -1
  58. package/mcp-server/dist/version.d.ts +5 -0
  59. package/mcp-server/dist/version.d.ts.map +1 -0
  60. package/mcp-server/dist/version.js +21 -0
  61. package/mcp-server/dist/version.js.map +1 -0
  62. package/mcp-server/package-lock.json +2 -2
  63. package/mcp-server/package.json +1 -3
  64. package/mcp-server/run.mjs +15 -9
  65. package/mcp-server/src/http.ts +18 -3
  66. package/mcp-server/src/index.ts +9 -6
  67. package/mcp-server/src/services/budget-judgments.ts +183 -0
  68. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  69. package/mcp-server/src/services/identity.ts +27 -0
  70. package/mcp-server/src/services/neural-interface.ts +34 -2
  71. package/mcp-server/src/services/typesafe-config.ts +4 -1
  72. package/mcp-server/src/tools/agents.ts +33 -14
  73. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  74. package/mcp-server/src/tui.ts +2 -1
  75. package/mcp-server/src/version.ts +22 -0
  76. package/neural-interface/i18n/en.json +2126 -209
  77. package/neural-interface/i18n/pt-BR.json +2123 -207
  78. package/neural-interface/lib/account-usage.js +163 -0
  79. package/neural-interface/lib/assistant-api.js +316 -57
  80. package/neural-interface/lib/assistant-brains/claude.js +19 -2
  81. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  82. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  83. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  84. package/neural-interface/lib/assistant-budget-split.js +408 -0
  85. package/neural-interface/lib/assistant-budget.js +94 -95
  86. package/neural-interface/lib/assistant-catalog.js +179 -27
  87. package/neural-interface/lib/assistant-config.js +91 -14
  88. package/neural-interface/lib/assistant-dispatch.js +494 -138
  89. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  90. package/neural-interface/lib/assistant-jev.js +26 -3
  91. package/neural-interface/lib/assistant-memory.js +24 -10
  92. package/neural-interface/lib/assistant-persona.js +73 -12
  93. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  94. package/neural-interface/lib/assistant-pricing.js +21 -6
  95. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  96. package/neural-interface/lib/assistant-route-gate.js +19 -3
  97. package/neural-interface/lib/assistant-router.js +373 -42
  98. package/neural-interface/lib/assistant-runtime.js +488 -59
  99. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  100. package/neural-interface/lib/assistant-test-density.js +142 -0
  101. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  102. package/neural-interface/lib/assistant-user-context.js +908 -0
  103. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  104. package/neural-interface/lib/assistant-user-learn.js +404 -0
  105. package/neural-interface/lib/assistant-user-threads.js +226 -0
  106. package/neural-interface/lib/assistant-user-writer.js +183 -0
  107. package/neural-interface/lib/augmented-path.js +317 -53
  108. package/neural-interface/lib/browser-background-tab.js +134 -0
  109. package/neural-interface/lib/browser-screenshot.js +678 -61
  110. package/neural-interface/lib/claude-accounts.js +342 -31
  111. package/neural-interface/lib/claude-agent-bridge.js +123 -9
  112. package/neural-interface/lib/claude-executable.js +6 -1
  113. package/neural-interface/lib/claude-model-catalog.js +33 -5
  114. package/neural-interface/lib/claude-panel-auth.js +221 -0
  115. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  116. package/neural-interface/lib/claude-temporary.js +46 -7
  117. package/neural-interface/lib/cli-install-api.js +137 -0
  118. package/neural-interface/lib/cli-install.js +797 -0
  119. package/neural-interface/lib/cli-path.js +130 -0
  120. package/neural-interface/lib/cli-resolver.js +744 -0
  121. package/neural-interface/lib/cli-update-planner.js +143 -7
  122. package/neural-interface/lib/cloudflared/install.js +533 -0
  123. package/neural-interface/lib/cloudflared/manager.js +545 -0
  124. package/neural-interface/lib/cloudflared/pins.js +115 -0
  125. package/neural-interface/lib/cloudflared/pins.json +63 -0
  126. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  127. package/neural-interface/lib/codex-account-auth.js +178 -0
  128. package/neural-interface/lib/codex-accounts.js +489 -0
  129. package/neural-interface/lib/codex-capabilities.js +2 -1
  130. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  131. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  132. package/neural-interface/lib/codex-panel-auth.js +62 -0
  133. package/neural-interface/lib/codex-session-settings.js +1 -1
  134. package/neural-interface/lib/codex-settings-schema.js +120 -0
  135. package/neural-interface/lib/desktop/config.js +3 -0
  136. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  137. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  138. package/neural-interface/lib/editor-completion.js +714 -0
  139. package/neural-interface/lib/editor-config.js +149 -0
  140. package/neural-interface/lib/editor-files-api.js +268 -0
  141. package/neural-interface/lib/editor-git-api.js +134 -0
  142. package/neural-interface/lib/file-explorer-api.js +1039 -0
  143. package/neural-interface/lib/file-explorer-system.js +355 -0
  144. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  145. package/neural-interface/lib/file-explorer-watch.js +239 -0
  146. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  147. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  148. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  149. package/neural-interface/lib/git-bash/install.js +529 -0
  150. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  151. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  152. package/neural-interface/lib/git-bash/manager.js +268 -0
  153. package/neural-interface/lib/git-bash/pins.js +206 -0
  154. package/neural-interface/lib/git-bash/pins.json +34 -0
  155. package/neural-interface/lib/git-bash/resolver.js +194 -0
  156. package/neural-interface/lib/git-diff-api.js +103 -0
  157. package/neural-interface/lib/git-read.js +234 -0
  158. package/neural-interface/lib/greeting/api.js +89 -0
  159. package/neural-interface/lib/greeting/budgets.js +90 -0
  160. package/neural-interface/lib/greeting/engine.js +461 -0
  161. package/neural-interface/lib/greeting/facts.js +320 -0
  162. package/neural-interface/lib/greeting/sources.js +252 -0
  163. package/neural-interface/lib/greeting/writer.js +139 -0
  164. package/neural-interface/lib/http-guards.js +189 -13
  165. package/neural-interface/lib/install-details.js +145 -0
  166. package/neural-interface/lib/machine-names.js +88 -0
  167. package/neural-interface/lib/mcp-installer.js +14 -2
  168. package/neural-interface/lib/memory-api.js +31 -7
  169. package/neural-interface/lib/native-loop-providers.js +26 -8
  170. package/neural-interface/lib/native-loop-runtime.js +9 -4
  171. package/neural-interface/lib/os-path.js +319 -0
  172. package/neural-interface/lib/plugin-installer.js +4 -2
  173. package/neural-interface/lib/project-storage.js +5 -7
  174. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  175. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  176. package/neural-interface/lib/pty-host/core.js +2 -1
  177. package/neural-interface/lib/pty-host/manager.js +27 -2
  178. package/neural-interface/lib/remote-policy.js +20 -11
  179. package/neural-interface/lib/request-gate.js +589 -0
  180. package/neural-interface/lib/session-title-generator.js +4 -3
  181. package/neural-interface/lib/system-about.js +340 -0
  182. package/neural-interface/lib/system-activity.js +66 -0
  183. package/neural-interface/lib/terminal-env.js +11 -3
  184. package/neural-interface/lib/terminal-path.js +55 -0
  185. package/neural-interface/lib/ui-blob-api.js +108 -0
  186. package/neural-interface/lib/ui-blob-store.js +68 -0
  187. package/neural-interface/lib/virtual-socket.js +12 -1
  188. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  189. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  190. package/neural-interface/lib/whatsapp/commands.js +13 -14
  191. package/neural-interface/lib/whatsapp/config.js +134 -20
  192. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  193. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  194. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  195. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  196. package/neural-interface/lib/whatsapp/manager.js +92 -32
  197. package/neural-interface/lib/whatsapp/paths.js +11 -1
  198. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  199. package/neural-interface/lib/whatsapp/redact.js +63 -0
  200. package/neural-interface/lib/whatsapp/service.js +343 -91
  201. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  202. package/neural-interface/package-lock.json +701 -2
  203. package/neural-interface/package.json +13 -2
  204. package/neural-interface/public/index.html +2 -0
  205. package/neural-interface/public/index2d.html +2 -0
  206. package/neural-interface/public/install-app.html +9 -6
  207. package/neural-interface/public/offline.html +19 -3
  208. package/neural-interface/public/onboarding.html +107 -2
  209. package/neural-interface/public/shared/account-i18n.js +7 -0
  210. package/neural-interface/public/shared/account-menu.js +539 -0
  211. package/neural-interface/public/shared/account-usage.js +19 -0
  212. package/neural-interface/public/shared/account-view.js +335 -0
  213. package/neural-interface/public/shared/agent-runtime-options.js +50 -14
  214. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  215. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  216. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  217. package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
  218. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  219. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  220. package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
  221. package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
  222. package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
  223. package/neural-interface/public/shared/assistant/asst-render.js +197 -23
  224. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  225. package/neural-interface/public/shared/assistant/asst-route.js +95 -343
  226. package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
  227. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  228. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  229. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  230. package/neural-interface/public/shared/assistant/asst-state.js +186 -2
  231. package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
  232. package/neural-interface/public/shared/blob-store.js +111 -22
  233. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  234. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  235. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  236. package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
  237. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  238. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  239. package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
  240. package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
  241. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  242. package/neural-interface/public/shared/claude-account-view.js +3 -0
  243. package/neural-interface/public/shared/cli-install-view.js +392 -0
  244. package/neural-interface/public/shared/cli-status.js +282 -22
  245. package/neural-interface/public/shared/constants.js +6 -1
  246. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  247. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  248. package/neural-interface/public/shared/cp/cp-styles.js +61 -0
  249. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  250. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  251. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  252. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  253. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  254. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  255. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  256. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  257. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  258. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  259. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  260. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  261. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  262. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  263. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  264. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  265. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  266. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  267. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  268. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  269. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  270. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  271. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  272. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  273. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  274. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  275. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  276. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  277. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  278. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  279. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  280. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  281. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  282. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  283. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  284. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  285. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  286. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  287. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  288. package/neural-interface/public/shared/file-links.js +339 -0
  289. package/neural-interface/public/shared/git-bash-view.js +145 -0
  290. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  291. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  292. package/neural-interface/public/shared/html-shell.js +161 -165
  293. package/neural-interface/public/shared/i18n.js +19 -1
  294. package/neural-interface/public/shared/install-details-view.js +86 -0
  295. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  296. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  297. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
  298. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  299. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
  300. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
  301. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  302. package/neural-interface/public/shared/pwa-install-page.js +32 -2
  303. package/neural-interface/public/shared/pwa-install.css +3 -3
  304. package/neural-interface/public/shared/pwa-install.js +100 -1
  305. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  306. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  307. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  308. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  309. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  310. package/neural-interface/public/shared/settings/settings-ia.js +33 -4
  311. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  312. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  313. package/neural-interface/public/shared/start-bridge.js +238 -19
  314. package/neural-interface/public/shared/storage.js +30 -3
  315. package/neural-interface/public/shared/styles.css +1710 -770
  316. package/neural-interface/public/shared/synabun-mascot.js +68 -58
  317. package/neural-interface/public/shared/tunnel-view.js +167 -0
  318. package/neural-interface/public/shared/ui-claude-panel.js +369 -229
  319. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  320. package/neural-interface/public/shared/ui-detail.js +15 -0
  321. package/neural-interface/public/shared/ui-explorer.js +22 -0
  322. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  323. package/neural-interface/public/shared/ui-file-links.js +103 -0
  324. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  325. package/neural-interface/public/shared/ui-help.js +26 -0
  326. package/neural-interface/public/shared/ui-invite.js +126 -116
  327. package/neural-interface/public/shared/ui-judgments.js +10 -1
  328. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  329. package/neural-interface/public/shared/ui-loading.js +187 -19
  330. package/neural-interface/public/shared/ui-menubar.js +25 -33
  331. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  332. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  333. package/neural-interface/public/shared/ui-navbar.js +48 -22
  334. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  335. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  336. package/neural-interface/public/shared/ui-sessions.js +9 -3
  337. package/neural-interface/public/shared/ui-settings.js +509 -490
  338. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  339. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  340. package/neural-interface/public/shared/ui-sync.js +22 -0
  341. package/neural-interface/public/shared/ui-terminal.js +40 -5
  342. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  343. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  344. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  345. package/neural-interface/public/shared/ui-update.js +594 -5
  346. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  347. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  348. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  349. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  350. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  351. package/neural-interface/public/shared/worker-end.js +253 -0
  352. package/neural-interface/public/sw.js +2 -2
  353. package/neural-interface/public/variant/2d/main.js +10 -1
  354. package/neural-interface/public/variant/3d/main.js +10 -1
  355. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  356. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  357. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  358. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  359. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  360. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  361. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  362. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  363. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  364. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  365. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  366. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  367. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  368. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  369. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  370. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  371. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  372. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  373. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  374. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  375. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  376. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  377. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  378. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  379. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  380. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  381. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  382. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  383. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  384. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  385. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  386. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  387. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  388. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  389. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  390. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  391. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  392. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  393. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  394. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  395. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  396. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  397. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  398. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  399. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  400. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  401. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  402. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  403. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  404. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  405. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  406. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  407. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  408. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  409. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  410. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  411. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  412. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  413. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  414. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  415. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  416. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  417. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  418. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  419. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  420. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  421. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  422. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  423. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  424. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  425. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  426. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  427. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  428. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  429. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  430. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  431. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  432. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  433. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  434. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  435. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  436. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  437. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  438. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  439. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  440. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  441. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  442. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  443. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  444. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  445. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  446. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  447. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  448. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  449. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  450. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  451. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  452. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  453. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  454. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  455. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  456. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  457. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  458. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  459. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  460. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  461. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  462. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  463. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  464. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  465. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  466. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  467. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  468. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  469. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  470. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  471. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  472. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  473. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  474. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  475. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  476. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  477. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  478. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  479. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  480. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  481. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  482. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  483. package/neural-interface/scripts/build-vendor.mjs +78 -2
  484. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  485. package/neural-interface/server.js +2085 -1629
  486. package/neural-interface/templates/rulesets/README.md +1 -1
  487. package/package.json +5 -8
  488. package/setup.js +53 -15
  489. package/benchmarks/browser-live-results.json +0 -79
  490. package/benchmarks/browser-v2-results.json +0 -258
  491. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  492. package/neural-interface/public/category-logos/claude.svg +0 -1
  493. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  494. package/neural-interface/public/category-logos/openai.svg +0 -1
  495. package/neural-interface/public/category-logos/synabun.png +0 -0
  496. package/neural-interface/public/claude.svg +0 -1
  497. package/neural-interface/public/gallery.svg +0 -6
  498. package/neural-interface/public/leonardoai.svg +0 -1
  499. package/neural-interface/public/mcp.svg +0 -4
  500. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  501. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  502. package/neural-interface/public/sessionicon.svg +0 -8
  503. package/neural-interface/public/toolbar-album.svg +0 -8
  504. package/neural-interface/public/toolbar-question.svg +0 -6
  505. package/neural-interface/public/toolbar-sessions.svg +0 -6
  506. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  507. 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
+ }
@@ -0,0 +1,124 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — the Git Bash row of Settings
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The page side of git-bash-view.js: the row of Settings → AI connections →
6
+ // Executable paths that shows the Git Bash every Claude Code SynaBun starts
7
+ // gets on Windows, with Download / Try again and Re-check. It asks
8
+ // GET /api/cli/git-bash, follows a download while it runs (a few times a
9
+ // second, until the row has left the page), and posts what the buttons ask
10
+ // with the guards of SynaBun's other local routes (a JSON body, X-SynaBun-UI).
11
+ // A server that has no such route (an older one), and every macOS or Linux
12
+ // server, leaves the row hidden.
13
+
14
+ import { gitBashText, gitBashView } from './git-bash-view.js';
15
+
16
+ const POST = Object.freeze({ method: 'POST', headers: { 'Content-Type': 'application/json', 'X-SynaBun-UI': '1' }, body: '{}' });
17
+
18
+ const ICON = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 4h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zm1 2v12h16V6H4zm2.3 2.3 3.7 3.7-3.7 3.7-1.4-1.4L7.2 12 4.9 9.7l1.4-1.4zM11 15h6v2h-6v-2z"/></svg>';
19
+
20
+ const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
21
+
22
+ /** The row's markup: hidden until the server says there is something to show (Windows). */
23
+ export function gitBashRowHtml() {
24
+ return `
25
+ <div class="cli-path-row" id="cli-git-bash" data-git-bash hidden>
26
+ <div class="cli-path-icon" style="color:var(--t-secondary)">${ICON}</div>
27
+ <div class="cli-path-field">
28
+ <span class="cli-path-label" id="cli-git-bash-label">${escapeHtml(gitBashText('label'))}</span>
29
+ <div class="cli-path-input-row">
30
+ <span class="stg-help" data-git-bash-line role="status" aria-live="polite" aria-labelledby="cli-git-bash-label" style="flex:1;min-width:0;margin:0"></span>
31
+ <button type="button" class="cli-detect-btn" data-git-bash-action hidden></button>
32
+ <button type="button" class="cli-detect-btn" data-git-bash-recheck hidden>${escapeHtml(gitBashText('recheck'))}</button>
33
+ <span class="cli-path-status" data-git-bash-badge></span>
34
+ </div>
35
+ <div class="stg-help" data-git-bash-note hidden></div>
36
+ <div class="stg-help">${escapeHtml(gitBashText('help'))}</div>
37
+ </div>
38
+ </div>`;
39
+ }
40
+
41
+ /**
42
+ * Fill the row and keep it current. `fetchImpl`, `schedule` and `cancel` are the page's own unless a test gives
43
+ * others. Returns `{ refresh, stop }`; the row stops following a download by itself once it is off the page.
44
+ */
45
+ export function wireGitBashRow(row, { fetchImpl = (...args) => fetch(...args), pollMs = 1500, schedule = (fn, ms) => setTimeout(fn, ms), cancel = (id) => clearTimeout(id) } = {}) {
46
+ if (!row) return { refresh: async () => null, stop: () => {} };
47
+ const badge = row.querySelector('[data-git-bash-badge]');
48
+ const line = row.querySelector('[data-git-bash-line]');
49
+ const note = row.querySelector('[data-git-bash-note]');
50
+ const action = row.querySelector('[data-git-bash-action]');
51
+ const recheck = row.querySelector('[data-git-bash-recheck]');
52
+ let timer = null;
53
+ let stopped = false;
54
+
55
+ const clearPoll = () => { if (timer !== null) { cancel(timer); timer = null; } };
56
+ const stop = () => { stopped = true; clearPoll(); };
57
+
58
+ function draw(status) {
59
+ const view = gitBashView(status);
60
+ row.hidden = !view.visible;
61
+ clearPoll();
62
+ if (!view.visible) return view;
63
+ if (badge) {
64
+ badge.textContent = view.badge?.text || '';
65
+ badge.className = `cli-path-status${view.badge?.tone ? ` ${view.badge.tone}` : ''}`;
66
+ }
67
+ if (line) line.textContent = view.line;
68
+ if (note) { note.textContent = view.note || ''; note.hidden = !view.note; }
69
+ if (action) {
70
+ action.hidden = !view.action;
71
+ action.disabled = false;
72
+ action.textContent = view.action ? gitBashText(view.action) : '';
73
+ }
74
+ if (recheck) {
75
+ recheck.hidden = !view.recheck;
76
+ recheck.disabled = false;
77
+ recheck.textContent = gitBashText('recheck');
78
+ }
79
+ if (view.busy && !stopped) {
80
+ timer = schedule(() => {
81
+ timer = null;
82
+ if (row.isConnected === false) { stop(); return; }
83
+ refresh();
84
+ }, pollMs);
85
+ }
86
+ return view;
87
+ }
88
+
89
+ async function ask(path, init) {
90
+ const res = await fetchImpl(path, init);
91
+ let body = null;
92
+ try { body = await res.json(); } catch { body = null; }
93
+ // A refusal that still says how things are (422: nothing to fetch here) is drawn as it is.
94
+ if (!res.ok && !(body && typeof body.state === 'string')) throw new Error(body?.error || body?.code || `HTTP ${res.status}`);
95
+ return body;
96
+ }
97
+
98
+ const said = (error) => { if (line) line.textContent = gitBashText('errorRequest', { message: error?.message || String(error) }); };
99
+
100
+ async function refresh() {
101
+ try { return draw(await ask('/api/cli/git-bash')); } catch {
102
+ // An older server, or none: there is no row to show.
103
+ row.hidden = true;
104
+ return null;
105
+ }
106
+ }
107
+
108
+ action?.addEventListener('click', async () => {
109
+ action.disabled = true;
110
+ try { draw(await ask('/api/cli/git-bash/install', POST)); } catch (error) { said(error); action.disabled = false; }
111
+ });
112
+ recheck?.addEventListener('click', async () => {
113
+ recheck.disabled = true;
114
+ recheck.textContent = gitBashText('checking');
115
+ try { draw(await ask('/api/cli/git-bash/recheck', POST)); } catch (error) {
116
+ said(error);
117
+ recheck.disabled = false;
118
+ recheck.textContent = gitBashText('recheck');
119
+ }
120
+ });
121
+
122
+ refresh();
123
+ return { refresh, stop };
124
+ }
@@ -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 || '') ? '&#8984;' : '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) => {
@@ -862,7 +862,7 @@ export function wireJudgmentsTab(overlay, { toast = () => {}, initial = null } =
862
862
  const outcome = row.outcome && typeof row.outcome === 'object' ? row.outcome : null;
863
863
  const summary = `${clock(row.created_at)} · <code>${escapeHtml(row.surface)}</code>${riders.map(x => ` <span style="${CHIP}">+${escapeHtml(x)}</span>`).join('')} ${kit.th('settings.redesign.judgments.qMs', { origin: escapeHtml(row.origin), model: escapeHtml(row.model || ''), num: num(row.question_count), num2: num(row.latency_ms), num3: num(row.input_tokens) })}${num(row.output_tokens)}${row.cached ? ' · <span style="color:var(--accent)">' + kit.te('settings.redesign.judgments.cached') + '</span>' : ''}${row.error ? ` · <span style="color:#f66">${escapeHtml(row.error)}</span>` : ''}${row.entity_id ? ` · ${memLink(row.entity_id)}` : ''}${row.session_id ? ` · <a href="#" class="jv-session-link" data-session="${attr(row.session_id)}" title="${attr(row.session_id)}${kit.te('settings.redesign.judgments.thisSessionSJudgments')}" style="${LINK};font-family:monospace">${kit.te('settings.redesign.judgments.session')} ${escapeHtml(shortId(row.session_id))}</a>` : ''}${row.project ? ` · ${escapeHtml(row.project)}` : ''}`;
864
864
  return `<details class="jv-log-row" data-id="${attr(row.id)}"${state.openLogIds.has(String(row.id)) ? ' open' : ''} style="font-size:12px;border:1px solid var(--s-medium);border-radius:6px;padding:4px 8px;background:var(--s-darker)">
865
- <summary style="cursor:pointer;list-style:none" title="${attr(row.created_at)}">${summary}</summary>
865
+ <summary style="cursor:pointer;list-style:none" title="${attr(row.created_at)}">${summary}${row.surface === 'budget-split' && budgetSplitLine(row, row.answers || {}).line ? ' · ' + budgetSplitLine(row, row.answers || {}).line : ''}</summary>
866
866
  <div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.state')}</div><pre style="margin:0;white-space:pre-wrap;font-size:11px;max-height:160px;overflow:auto">${escapeHtml(row.state_preview || '')}</pre>
867
867
  <div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.answers')}</div><pre style="margin:0;white-space:pre-wrap;font-size:11px;max-height:200px;overflow:auto">${escapeHtml(answers || '—')}</pre>
868
868
  ${outcome ? `<div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.outcome')}</div><pre style="${PRE}">${escapeHtml(JSON.stringify(outcome, null, 2))}</pre>` : ''}
@@ -1029,7 +1029,16 @@ export function wireJudgmentsTab(overlay, { toast = () => {}, initial = null } =
1029
1029
  const p = prob(a.goal_met);
1030
1030
  return { line: p == null ? '' : flagged(kit.tx('settings.redesign.judgments.pGoalMet', { pct: pct(p) }), p >= knob('loop-goal', 'minConfidence', 0.8)) };
1031
1031
  }
1032
+ function budgetSplitLine(row, answers) {
1033
+ const names = { share_claude_code: 'Claude Code', share_codex: 'Codex', share_opencode: 'OpenCode' };
1034
+ const parts = Object.entries(names).filter(([key]) => answers[key]?.type === 'score').map(([key, label]) => {
1035
+ const a = answers[key];
1036
+ return escapeHtml(kit.tx('settings.redesign.judgments.budgetShareScore', { provider: label, score: num(a.score ?? a.value), confidence: pct(a.confidence) }));
1037
+ });
1038
+ return { line: parts.join(' · ') };
1039
+ }
1032
1040
  const TIMELINE = {
1041
+ 'budget-split': budgetSplitLine,
1033
1042
  'prompt-urgency': urgencyLine, 'rerank': rankLine, 'brief-rank': rankLine,
1034
1043
  'agent-message': stopLine, 'turn-worth': stopLine, 'claim-check': stopLine,
1035
1044
  'edit-stale': staleLine, 'stale-check': staleLine, 'plan-conflict': planLine, 'compact-digest': digestLine, 'loop-goal': loopLine,
@@ -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