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
@@ -26,7 +26,7 @@ import {
26
26
  } from '../ui-assistant.js';
27
27
  import { mountAssistant } from './asst-panel.js';
28
28
  import { openModelsManager } from './asst-route.js';
29
- import { brainLabel, readStoredBrain } from './asst-state.js';
29
+ import { brainLabel, readStoredBrain, restoredSessionName } from './asst-state.js';
30
30
  import { closeMenu, isMenuOpen, openMenu } from './asst-menu.js';
31
31
  import { injectAssistantSidepanelStyles } from './asst-sidepanel-styles.js';
32
32
  import { createMascot, mascotCameoSvg } from '../synabun-mascot.js';
@@ -50,7 +50,7 @@ let _initialized = false;
50
50
  let _starting = false;
51
51
  let _startError = ''; // the last message the host showed while starting (the failed state repeats it)
52
52
  let _placeholderMascot = null; // the character while starting: the empty state's, same size; it flies to the empty state's spot
53
- let _pendingPath = '';
53
+ let _pendingPaths = []; // file paths sent while no session was open: attached, in order, when one is
54
54
  let _toastTimer = null;
55
55
  let _renaming = false;
56
56
  const _tabs = []; // { sessionId, viewport, toolbar, ctl, pill, label, userRenamed, status, brain }
@@ -260,7 +260,7 @@ function showPanelToast(text) {
260
260
 
261
261
  // ── Tabs ─────────────────────────────────────────────────────────────────────
262
262
 
263
- function mountTab({ sessionId, meta = null, brain = null, label = '', userRenamed = false, prompt = '', show = true } = {}) {
263
+ function mountTab({ sessionId, meta = null, brain = null, label = '', userRenamed = false, prompt = '', show = true, restoring = false } = {}) {
264
264
  if (!sessionId) return null;
265
265
  ensurePanel();
266
266
  const existing = findTab(sessionId);
@@ -280,9 +280,13 @@ function mountTab({ sessionId, meta = null, brain = null, label = '', userRename
280
280
  toolbar.className = 'asp-tab-bar';
281
281
  toolbar.dataset.sessionId = sessionId;
282
282
  panelEl('.asp-bar-slot').appendChild(toolbar);
283
- const renamed = !!(userRenamed && label);
283
+ // A renamed session comes back under its name: the session record's (a rename made in either
284
+ // host, the one the Sessions menu lists), else the rename saved with this tab. A record whose
285
+ // name was given up says so: the label and the renamed flag saved with a tab are stale.
286
+ const name = restoredSessionName(meta, userRenamed ? label : '');
287
+ const renamed = !!name;
284
288
  // The record exists before mountAssistant: the host hooks fire during mount.
285
- const tab = { sessionId, viewport, toolbar, ctl: null, pill: null, label: renamed ? label : defaultLabel(), userRenamed: renamed, status: 'idle', brain: brain || meta?.brain || null };
289
+ const tab = { sessionId, viewport, toolbar, ctl: null, pill: null, label: renamed ? name : defaultLabel(), userRenamed: renamed, status: 'idle', brain: brain || meta?.brain || null };
286
290
  _tabs.push(tab);
287
291
  const host = {
288
292
  id: HOST_ID,
@@ -295,6 +299,16 @@ function mountTab({ sessionId, meta = null, brain = null, label = '', userRename
295
299
  renderPills();
296
300
  saveTabs();
297
301
  },
302
+ // A rename the panel could not save: the tab and its saved label go back to the record's name.
303
+ setName(text, renamed) {
304
+ const next = String(text || '').trim();
305
+ if (!next) return;
306
+ tab.label = next;
307
+ tab.userRenamed = !!renamed;
308
+ renderHeader();
309
+ renderPills();
310
+ saveTabs();
311
+ },
298
312
  setStatus(status) {
299
313
  tab.status = status || 'idle';
300
314
  if (tab.status === 'done' && isTabVisible(tab)) tab.status = 'idle';
@@ -311,7 +325,7 @@ function mountTab({ sessionId, meta = null, brain = null, label = '', userRename
311
325
  },
312
326
  };
313
327
  tab.ctl = mountAssistant(viewport, { sessionId, brain: tab.brain, host, session: meta });
314
- if (tab.userRenamed) tab.ctl.setTitle(tab.label);
328
+ if (tab.userRenamed) tab.ctl.setTitle(tab.label, { restored: true });
315
329
  if (show) {
316
330
  switchTo(_tabs.length - 1);
317
331
  if (!_visible) setVisible(true);
@@ -324,7 +338,8 @@ function mountTab({ sessionId, meta = null, brain = null, label = '', userRename
324
338
  renderPills();
325
339
  saveTabs();
326
340
  if (prompt) setTimeout(() => { if (findTab(sessionId)) tab.ctl?.send(prompt); }, 50);
327
- if (_pendingPath && tab === activeTab()) { const path = _pendingPath; _pendingPath = ''; tab.ctl.attachPath(path); }
341
+ // (Not while the saved tabs are coming back: the first of them is not the one that was showing.)
342
+ if (_pendingPaths.length && !restoring && tab === activeTab()) { const paths = _pendingPaths; _pendingPaths = []; for (const path of paths) tab.ctl.attachPath(path); }
328
343
  return tab;
329
344
  }
330
345
 
@@ -635,12 +650,14 @@ export async function initAssistantPanel() {
635
650
  if (!_visible) setVisible(true);
636
651
  tab.ctl?.attachImageDataUrl(dataUrl, 'whiteboard');
637
652
  });
638
- on('assistant:attach-path', ({ path } = {}) => {
653
+ on('assistant:attach-path', (request = {}) => {
654
+ const { path } = request;
639
655
  if (!path || assistantBlocked()) return;
640
656
  const tab = activeTab();
641
657
  if (!_visible) setVisible(true);
642
658
  if (tab?.ctl) tab.ctl.attachPath(path);
643
- else _pendingPath = String(path);
659
+ else _pendingPaths.push(String(path));
660
+ request.taken = true; // the sender says "sent" only of what was taken
644
661
  });
645
662
  // The server renamed a conversation (first prompt): refresh tooltips after the component applied it.
646
663
  on('sync:assistant:session-updated', (msg) => {
@@ -663,6 +680,7 @@ export async function initAssistantPanel() {
663
680
  label: entry.label || '',
664
681
  userRenamed: !!entry.userRenamed,
665
682
  show: false,
683
+ restoring: true,
666
684
  });
667
685
  }
668
686
  if (_tabs.length) {
@@ -670,6 +688,10 @@ export async function initAssistantPanel() {
670
688
  markActive();
671
689
  renderHeader();
672
690
  renderPills();
691
+ // Paths that were sent while the sessions were being asked about go to the one that is showing again.
692
+ const paths = _pendingPaths;
693
+ _pendingPaths = [];
694
+ for (const path of paths) activeTab()?.ctl?.attachPath(path);
673
695
  }
674
696
  saveTabs();
675
697
  }
@@ -231,6 +231,56 @@ export function modelShortName(model) {
231
231
  return tail.replace(/:\d+$/, '');
232
232
  }
233
233
 
234
+ // ── Projects ────────────────────────────────────────────────────────────────
235
+
236
+ /** A folder path without trailing separators ("/a/b/" is "/a/b"); the root stays itself. */
237
+ function folderPath(path) {
238
+ const value = str(path);
239
+ return value.replace(/[\\/]+$/, '') || value;
240
+ }
241
+
242
+ /**
243
+ * What a working folder is called: its label among SynaBun's registered
244
+ * projects (GET /api/projects: [{ path, label }]), else the folder's own name;
245
+ * '' for none. The toolbar's Working project, the empty state's picker and the
246
+ * "Fix … in {project}" suggestion all read it, so they name it the same way.
247
+ */
248
+ export function projectLabel(path, projects = []) {
249
+ const want = folderPath(path);
250
+ if (!want) return '';
251
+ const row = (Array.isArray(projects) ? projects : []).find(p => p && folderPath(p.path) === want);
252
+ return str(row?.label) || want.split(/[\\/]/).filter(Boolean).pop() || want;
253
+ }
254
+
255
+ const fillParams = (text, params) => (params ? String(text).replace(/\{(\w+)\}/g, (_, k) => (params[k] != null ? params[k] : `{${k}}`)) : String(text));
256
+
257
+ /**
258
+ * The empty state's project picker: { chosen, key, label, title, aria }. With
259
+ * no project the brain works in the home directory and the picker asks
260
+ * "Choose a project"; with one it reads "Project Synabun", the path in its
261
+ * tooltip. `tr(key, fallback, params)` translates.
262
+ */
263
+ export function emptyProjectView(project, projects = [], tr = (_key, fallback, params) => fillParams(fallback, params)) {
264
+ const path = str(project);
265
+ const name = projectLabel(path, projects);
266
+ if (!name) {
267
+ return {
268
+ chosen: false,
269
+ key: '',
270
+ label: tr('assistant.empty.chooseProject', 'Choose a project'),
271
+ title: tr('assistant.brain.projectNoneTip', 'Working project: home directory'),
272
+ aria: tr('assistant.empty.projectAria', 'Choose a project (now: {project})', { project: tr('assistant.brain.projectNone', 'Home directory') }),
273
+ };
274
+ }
275
+ return {
276
+ chosen: true,
277
+ key: tr('assistant.empty.project', 'Project'),
278
+ label: name,
279
+ title: path,
280
+ aria: tr('assistant.empty.projectAria', 'Choose a project (now: {project})', { project: name === path ? name : `${name}, ${path}` }),
281
+ };
282
+ }
283
+
234
284
  /** Does switching from `a` to `b` require a fresh server session (switch_brain)? */
235
285
  export function brainRequiresSwitch(a, b) {
236
286
  const x = normalizeBrain(a);
@@ -453,6 +503,22 @@ export function normalizeRouteMode(value, fallback = null) {
453
503
  return ROUTE_MODE_ALIASES[key] || fallback;
454
504
  }
455
505
 
506
+ // Test density: how much testing SynaBun asks of dispatched workers (server: lib/assistant-test-density.js).
507
+ // `null` means "server default" in a tab's own state; a missing or unknown value reads as the default level.
508
+ export const TEST_DENSITIES = ['none', 'light', 'standard', 'thorough'];
509
+ export const DEFAULT_TEST_DENSITY = 'light';
510
+
511
+ /** A known level, else `fallback` (the default level; pass null to tell "not set" apart). */
512
+ export function normalizeTestDensity(value, fallback = DEFAULT_TEST_DENSITY) {
513
+ const key = str(value).toLowerCase();
514
+ return TEST_DENSITIES.includes(key) ? key : fallback;
515
+ }
516
+
517
+ /** Bars of the composer's gauge that are filled at a level: none 0 · light 1 · standard 2 · thorough 3. */
518
+ export function testDensityBars(level) {
519
+ return TEST_DENSITIES.indexOf(normalizeTestDensity(level));
520
+ }
521
+
456
522
  const COMPUTER_ON = new Set(['on', 'true', '1', 'yes', 'enable', 'enabled']);
457
523
  const COMPUTER_OFF = new Set(['off', 'false', '0', 'no', 'disable', 'disabled']);
458
524
 
@@ -529,6 +595,121 @@ export function tabStorageKey(sessionId) {
529
595
  return `${KEYS.ASSISTANT_TAB_PREFIX}${sessionId}`;
530
596
  }
531
597
 
598
+ /**
599
+ * The name the user gave a session, '' when they never renamed it. It lives on
600
+ * the server's session record (`title` + `userRenamed`, written by a rename in
601
+ * either host): the Sessions menu and a reopened tab read the same name, and a
602
+ * host's saved tab label is only its copy.
603
+ */
604
+ export function userSessionTitle(meta) {
605
+ if (!meta || typeof meta !== 'object' || meta.userRenamed !== true) return '';
606
+ return str(meta.title);
607
+ }
608
+
609
+ /** What a tab is called until the user renames it (the server's DEFAULT_SESSION_TITLE). */
610
+ export const DEFAULT_SESSION_TITLE = 'SynaBun';
611
+
612
+ /**
613
+ * The title to write to the session record for a tab's name, null when there is
614
+ * nothing to write. `recordName` is userSessionTitle() of the record,
615
+ * `recordTitle` its title, `keepsRenames` whether the server marks renames
616
+ * (features.rename; one not restarted yet does not, and holds a name once its
617
+ * title says it), `defaultTitle` the default tab name as this locale shows it.
618
+ * The default name is no name: it is only written to take a rename back.
619
+ */
620
+ export function renameToSave(name, { recordName = '', recordTitle = '', keepsRenames = false, defaultTitle = DEFAULT_SESSION_TITLE } = {}) {
621
+ const next = str(name);
622
+ if (!next) return null;
623
+ if (next === DEFAULT_SESSION_TITLE || next === str(defaultTitle)) return keepsRenames && str(recordName) ? DEFAULT_SESSION_TITLE : null;
624
+ return next === (keepsRenames ? str(recordName) : str(recordTitle)) ? null : next;
625
+ }
626
+
627
+ /**
628
+ * The name a host gives the tab it mounts for a session: the record's
629
+ * (userSessionTitle), else the label saved with the tab when the record says
630
+ * nothing about a name (a rename made before the record kept names). A record
631
+ * with `userRenamed: false` had its name given up: a saved label is stale.
632
+ */
633
+ export function restoredSessionName(meta, savedLabel = '') {
634
+ return userSessionTitle(meta) || (sessionNameGivenUp(meta) ? '' : str(savedLabel));
635
+ }
636
+
637
+ /** The record says its name was taken back ("SynaBun" again): no saved label or renamed flag stands for it. */
638
+ export function sessionNameGivenUp(meta) {
639
+ return !!meta && typeof meta === 'object' && meta.userRenamed === false;
640
+ }
641
+
642
+ /**
643
+ * Every rename on its way to a session record, one write at a time per session,
644
+ * so the name asked for last is the one the record ends with: after each answer
645
+ * the newest name is compared with what the record holds now, and what still
646
+ * differs (taking a rename back included) is written. One writer for the page,
647
+ * not one per tab: a write goes on after its tab closed, and a session list
648
+ * waits for `settled()`. `patch(sessionId, { title })` is the request.
649
+ *
650
+ * save(sessionId, name, opts): `recordName` / `recordTitle` / `keepsRenames` /
651
+ * `defaultTitle` are renameToSave()'s, as the caller knows the record (a write
652
+ * under way hears the record from its answer instead); `from` is the name the
653
+ * tab had before the first of them. `onSaved(session)` gets each answer,
654
+ * `onFailed({ name, back })` a write that did not get through: `name` was not
655
+ * saved, `back` is the name the record answers to (the last one it confirmed,
656
+ * else `from`). The latest call's callbacks are the ones used. The promise
657
+ * never rejects.
658
+ */
659
+ export function createRenameWriter({ patch } = {}) {
660
+ const jobs = new Map(); // sessionId → { name, back, record, keepsRenames, defaultTitle, onSaved, onFailed, done }
661
+
662
+ const toWrite = (job) => renameToSave(job.name, { recordName: job.record.name, recordTitle: job.record.title, keepsRenames: job.keepsRenames, defaultTitle: job.defaultTitle });
663
+
664
+ async function run(sessionId, job) {
665
+ // The title of the last write the server answered. It may keep a name cut or tidied, which
666
+ // never equals the tab's: the same title is not sent twice in a row.
667
+ let written = null;
668
+ for (;;) {
669
+ const name = job.name;
670
+ const title = toWrite(job);
671
+ if (!title || title === written) break;
672
+ let session = null;
673
+ try { session = (await patch(sessionId, { title }))?.session || null; } catch {
674
+ jobs.delete(sessionId);
675
+ try { job.onFailed?.({ name: job.name, back: job.back }); } catch { /* the caller's */ }
676
+ return;
677
+ }
678
+ written = title;
679
+ job.back = name;
680
+ job.record = session?.title
681
+ ? { name: userSessionTitle(session), title: str(session.title) }
682
+ : { name: job.keepsRenames && title !== DEFAULT_SESSION_TITLE ? title : '', title };
683
+ try { job.onSaved?.(session); } catch { /* the caller's */ }
684
+ }
685
+ jobs.delete(sessionId);
686
+ }
687
+
688
+ function save(sessionId, name, { from = '', recordName = '', recordTitle = '', keepsRenames = false, defaultTitle = DEFAULT_SESSION_TITLE, onSaved = null, onFailed = null } = {}) {
689
+ const next = str(name);
690
+ if (!sessionId || !next) return Promise.resolve();
691
+ const waiting = jobs.get(sessionId);
692
+ if (waiting) {
693
+ // A write is under way: this name is the one compared with its answer.
694
+ Object.assign(waiting, { name: next, keepsRenames, defaultTitle, onSaved, onFailed });
695
+ return waiting.done;
696
+ }
697
+ const job = { name: next, back: str(from), record: { name: str(recordName), title: str(recordTitle) }, keepsRenames, defaultTitle, onSaved, onFailed, done: null };
698
+ if (!toWrite(job)) return Promise.resolve();
699
+ jobs.set(sessionId, job);
700
+ job.done = run(sessionId, job);
701
+ return job.done;
702
+ }
703
+
704
+ return {
705
+ save,
706
+ /** A rename of this session is on its way. */
707
+ pending: (sessionId) => jobs.has(sessionId),
708
+ /** Resolves once every rename that was on its way has its answer, whatever tab asked for it. */
709
+ settled: () => Promise.all([...jobs.values()].map((job) => job.done)).then(() => {}),
710
+ };
711
+ }
712
+
532
713
  export function createTabState(sessionId, brain, extra = {}) {
533
714
  return {
534
715
  sessionId,
@@ -548,6 +729,7 @@ export function createTabState(sessionId, brain, extra = {}) {
548
729
  draft: '',
549
730
  routeMode: null, // explicit per-session route mode; null = server default
550
731
  computerUse: null, // explicit per-session computer use; null = server default
732
+ testDensity: null, // explicit per-session test density; null = server default
551
733
  usageView: null,
552
734
  limit: null, // the provider's usage limit notice (applyLimit); never persisted, the server re-sends it on attach
553
735
  ...extra,
@@ -568,12 +750,13 @@ export function serializeTabState(state) {
568
750
  costUsd: Number.isFinite(Number(state.costUsd)) ? Number(state.costUsd) : 0,
569
751
  routeMode: normalizeRouteMode(state.routeMode, null),
570
752
  computerUse: normalizeComputerMode(state.computerUse, null),
753
+ testDensity: normalizeTestDensity(state.testDensity, null),
571
754
  usageView: compactUsageView(state.usageView),
572
755
  updatedAt: Date.now(),
573
756
  };
574
757
  }
575
758
 
576
- /** Tolerant of older payloads: missing routeMode / computerUse read as null (server default). */
759
+ /** Tolerant of older payloads: missing routeMode / computerUse / testDensity read as null (server default). */
577
760
  export function deserializeTabState(raw) {
578
761
  let data = raw;
579
762
  if (typeof raw === 'string') {
@@ -590,6 +773,7 @@ export function deserializeTabState(raw) {
590
773
  costUsd: Number.isFinite(Number(data.costUsd)) ? Number(data.costUsd) : 0,
591
774
  routeMode: normalizeRouteMode(data.routeMode, null),
592
775
  computerUse: normalizeComputerMode(data.computerUse, null),
776
+ testDensity: normalizeTestDensity(data.testDensity, null),
593
777
  usageView: compactUsageView(data.usageView),
594
778
  };
595
779
  }
@@ -359,11 +359,19 @@ export function injectAssistantStyles() {
359
359
  }
360
360
  .asst-empty-title { color: var(--asst-text); font-family: var(--ff-heading, inherit); font-size: 20px; font-weight: 600; letter-spacing: -.01em; }
361
361
  .asst-empty-body { max-width: 440px; color: var(--asst-text-3); font-size: 13px; line-height: 1.55; }
362
+ /* The greeting (public/shared/greeting/) takes the lead's place when there is one. */
363
+ .asst-empty-greet { display: contents; }
364
+ .asst-empty.has-greeting .asst-empty-body { display: none; }
362
365
  .asst-suggestions { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; max-width: 560px; margin-top: 6px; }
363
366
  .asst-chip.asst-suggestion { --cap-h: var(--asst-pill-h-md); --cap-r: var(--asst-pill-r-md); --cap-px: var(--asst-pill-px-md); --cap-fs: 12px; justify-content: flex-start; text-align: left; }
364
367
  .asst-chip.asst-suggestion > .asst-icon svg { width: 20px; height: 14px; }
365
368
  .asst-chip.asst-suggestion > .asst-icon { color: var(--asst-text-3); }
366
369
  .asst-chip.asst-suggestion:is(:hover, :focus-visible) > .asst-icon { color: var(--asst-text); }
370
+ /* "Choose a project": the toolbar's Working project field, centred under the lead (asst-brain-picker.js mountEmptyProject). An md capsule like the suggestions. */
371
+ .asst-empty-project { display: flex; justify-content: center; max-width: 100%; }
372
+ .asst-dd.asst-empty-project-btn { max-width: min(100%, 360px); }
373
+ .asst-empty-project-btn .asst-dd-label { max-width: 30ch; }
374
+ .asst-empty-project-key { flex-shrink: 0; color: var(--asst-text-3); font-weight: 400; }
367
375
 
368
376
  /* ── Messages ───────────────────────────────────────────────────── */
369
377
  .asst-msg { display: flex; flex-direction: column; min-width: 0; }
@@ -610,6 +618,39 @@ export function injectAssistantStyles() {
610
618
  color: var(--asst-text-3);
611
619
  font-size: 12px;
612
620
  }
621
+ /* ── Automation trace (one row per turn: asst-automation.js) ─────── */
622
+ .asst-transcript > .asst-auto + .asst-route-line,
623
+ .asst-transcript > .asst-route-line + .asst-auto,
624
+ .asst-transcript > .asst-msg.msg-user + .asst-auto,
625
+ .asst-transcript > .asst-msg.msg-assistant + .asst-auto { margin-top: 4px; }
626
+ .asst-auto .asst-auto-head { color: var(--asst-text-3); }
627
+ .asst-auto .asst-route-glyph { display: inline-flex; color: var(--asst-text-4); }
628
+ .asst-auto .asst-route-glyph svg { width: 12px; height: 12px; }
629
+ .asst-auto.learned .asst-route-glyph { color: var(--asst-brand); }
630
+ .asst-auto-title { flex-shrink: 0; color: var(--asst-text-2); white-space: nowrap; }
631
+ /* The summary is what gives way in a narrow panel: one line, ellipsized. */
632
+ .asst-auto .asst-auto-summary { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
633
+ .asst-auto.learned .asst-auto-state { color: var(--asst-brand); }
634
+ .asst-auto-detail { gap: 10px; }
635
+ .asst-auto-group { display: flex; flex-direction: column; gap: 4px; }
636
+ .asst-auto-h { color: var(--asst-text-3); font-size: 11px; font-weight: 500; letter-spacing: .02em; }
637
+ .asst-auto-row { display: flex; flex-direction: column; gap: 2px; color: var(--asst-text-2); }
638
+ .asst-auto-row.muted { color: var(--asst-text-3); }
639
+ .asst-auto-row.learned .asst-auto-text { color: var(--asst-text); }
640
+ .asst-auto-row-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
641
+ .asst-auto-tier { color: var(--asst-text-2); font-weight: 500; }
642
+ .asst-auto-meta { color: var(--asst-text-3); font-size: 11.5px; font-variant-numeric: tabular-nums; }
643
+ .asst-auto-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; }
644
+ pre.asst-auto-text {
645
+ padding: 6px 8px;
646
+ border-radius: var(--asst-r-ctl);
647
+ background: var(--asst-fill-1);
648
+ color: var(--asst-text-2);
649
+ font-family: var(--asst-mono);
650
+ font-size: 11.5px;
651
+ line-height: 1.45;
652
+ }
653
+ .asst-auto-ids { display: flex; flex-wrap: wrap; gap: 2px 10px; font-family: var(--asst-mono); font-size: 11px; }
613
654
  .asst-link { padding: 0; border: 0; background: transparent; color: var(--asst-focus); font: inherit; text-align: left; cursor: pointer; }
614
655
  .asst-link:hover { text-decoration: underline; }
615
656
  .asst-link:disabled { color: var(--asst-text-4); cursor: default; text-decoration: none; }
@@ -1391,6 +1432,8 @@ export function injectAssistantStyles() {
1391
1432
  .asst-dd-item-main { display: flex; flex: 1; flex-direction: column; gap: 1px; min-width: 0; }
1392
1433
  .asst-dd-item-label, .asst-dd-item-desc { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
1393
1434
  .asst-dd-item-desc { color: var(--asst-text-3); font-size: 11px; }
1435
+ /* Test density: each level's description is a sentence, so it wraps instead of truncating. */
1436
+ .asst-tests-menu .asst-dd-item-desc { white-space: normal; }
1394
1437
  .asst-dd-item-tag { flex-shrink: 0; margin-left: auto; color: var(--asst-text-3); font-size: 11px; }
1395
1438
  .asst-dd-kbd { flex-shrink: 0; margin-left: auto; color: var(--asst-text-3); font-family: var(--asst-mono); font-size: 10.5px; }
1396
1439
  .asst-dd-sep { flex-shrink: 0; height: 1px; margin: 4px 6px; background: var(--asst-line); }
@@ -1546,6 +1589,109 @@ export function injectAssistantStyles() {
1546
1589
  .asst-routes-row { flex-wrap: wrap; }
1547
1590
  }
1548
1591
 
1592
+ /* Model routes editor: Settings density on a desktop pointer. Scoped here
1593
+ so route cards, the model manager and other Assistant menus keep their UI. */
1594
+ .asst-routes-overlay { font-family: var(--stg-font, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Inter, sans-serif); }
1595
+ .asst-routes-overlay .asst-routes-modal { width: min(800px, 100%); max-height: calc(100dvh - 32px); border-radius: var(--asst-r-card); line-height: 1.4; }
1596
+ .asst-routes-modal :is(.asst-btn, .asst-dd, .asst-iconbtn) { --cap-h: var(--asst-pill-h-sm); --cap-r: var(--asst-pill-r-xs); --cap-px: var(--asst-pill-px-sm); --cap-icon: 12px; --cap-gap: 4px; }
1597
+ .asst-routes-modal .asst-modal-head { min-height: 40px; padding: 8px 14px 8px 22px; font-size: 13px; }
1598
+ .asst-routes-modal .asst-modal-head .asst-icon { color: var(--asst-text-2); }
1599
+ .asst-routes-modal .asst-modal-close { width: var(--cap-h); height: var(--cap-h); }
1600
+ .asst-routes-modal .asst-modal-body { gap: 12px; padding: 12px 22px; scroll-padding: 28px 0; }
1601
+ .asst-routes-modal .asst-modal-foot { padding: 10px 22px; }
1602
+ .asst-routes-modal .asst-btn { height: var(--cap-h); min-height: var(--cap-h); padding: 0 10px; }
1603
+ .asst-routes-modal .asst-routes-section { flex-shrink: 0; gap: 6px; margin: 0; min-width: 0; }
1604
+ .asst-routes-modal .asst-routes-section-title { margin: 0; color: var(--asst-text); font-size: 13px; font-weight: 600; }
1605
+ .asst-routes-modal .asst-routes-modes { gap: 0; padding: 0; border-radius: var(--asst-pill-r-xs); background: var(--asst-fill-1); }
1606
+ .asst-routes-modal .asst-routes-mode { position: relative; flex-direction: row; align-items: center; justify-content: center; height: var(--asst-pill-h-sm); padding: 0 8px; gap: 0; min-width: 0; border-radius: var(--asst-pill-r-xs); box-shadow: none; color: var(--asst-text-2); }
1607
+ .asst-routes-mode input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
1608
+ .asst-routes-modal .asst-routes-mode:has(input:checked) { background: var(--asst-fill-3); color: var(--asst-text); box-shadow: inset 0 0 0 1px var(--asst-line-2); }
1609
+ .asst-routes-modal .asst-routes-mode-label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 12px; font-weight: 500; }
1610
+ .asst-routes-modal .asst-routes-mode-desc, .asst-routes-session-note { margin: 0; color: var(--asst-text-2); font-size: 12px; line-height: 1.45; overflow-wrap: anywhere; }
1611
+ .asst-routes-session-note { color: var(--asst-text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1612
+ .asst-routes-mode:has(:focus-visible) { outline: 2px solid var(--asst-focus); outline-offset: 2px; z-index: 1; }
1613
+ .asst-routes-section-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
1614
+ .asst-routes-list-actions { display: flex; flex-wrap: wrap; gap: 8px; }
1615
+ .asst-routes-modal .asst-routes-list-actions .asst-btn { background: transparent; box-shadow: none; padding: 0 2px; color: var(--asst-text-2); font-weight: 400; }
1616
+ .asst-routes-modal .asst-routes-list-actions .asst-btn:hover:not(:disabled) { color: var(--asst-text); background: var(--asst-fill-1); }
1617
+ /* Reserve the widths the current model names, effort words and destinations
1618
+ need; give all remaining space to task descriptions instead of a share. */
1619
+ .asst-routes-modal .asst-routes-columns, .asst-routes-modal .asst-routes-row { display: grid; grid-template-columns: minmax(0, 1fr) 208px 84px 128px var(--asst-pill-h-sm); column-gap: 6px; }
1620
+ .asst-routes-columns { position: sticky; top: -14px; z-index: 1; padding: 4px 8px; color: var(--asst-text-3); font-size: 11.5px; background: var(--asst-popover); }
1621
+ .asst-routes-columns span:last-child { visibility: hidden; }
1622
+ .asst-routes-modal .asst-routes-list { padding: 0 8px; border: 1px solid var(--asst-line); border-radius: var(--radius-group, 9px); background: var(--asst-field); }
1623
+ .asst-routes-modal .asst-routes-row { align-items: center; row-gap: 0; padding: 2px 0; border-top: 1px solid var(--asst-line); }
1624
+ .asst-routes-modal .asst-routes-row:first-child { border-top: 0; }
1625
+ .asst-routes-modal .asst-routes-row-main { min-width: 0; }
1626
+ .asst-routes-modal .asst-routes-row-label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 13px; font-weight: 400; line-height: 1.3; }
1627
+ .asst-routes-modal :is(.asst-routes-row-desc, .asst-routes-row-note, .asst-routes-row-warning, .asst-routes-row-context) { margin: 0; padding: 0; border: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--asst-text-3); font-size: 11.5px; line-height: 1.3; }
1628
+ .asst-routes-modal :is(.asst-routes-row-note, .asst-routes-row-warning) { color: var(--asst-warn); }
1629
+ .asst-routes-modal .asst-routes-row > :is(.asst-routes-row-note, .asst-routes-row-warning, .asst-routes-row-context) { grid-column: 1 / -1; white-space: normal; overflow: visible; overflow-wrap: anywhere; }
1630
+ .asst-routes-inactive { margin-inline-start: 6px; color: var(--asst-text-3); font-size: 11px; }
1631
+ .asst-routes-cell { display: flex; min-width: 0; }
1632
+ .asst-routes-cell-label { display: none; }
1633
+ .asst-routes-modal .asst-dd { width: 100%; max-width: none; height: var(--cap-h); min-height: var(--cap-h); padding: 0 6px; background: var(--asst-fill-2); box-shadow: inset 0 0 0 1px var(--asst-line); color: var(--asst-text); }
1634
+ .asst-routes-modal .asst-dd .asst-dd-label { flex: 1; max-width: none; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; line-height: 1; font-size: 12px; text-align: start; }
1635
+ .asst-routes-modal .asst-dd > .asst-icon { margin: 0; padding: 0; box-shadow: none; }
1636
+ .asst-routes-modal .asst-dd:disabled { opacity: 1; cursor: default; background: transparent; box-shadow: none; color: var(--asst-text-3); }
1637
+ .asst-routes-modal .asst-routes-source { display: inline-flex; flex-shrink: 0; min-width: 0; width: 12px; height: 12px; padding: 0; border-radius: 0; background: none; box-shadow: none; color: var(--asst-text-3); }
1638
+ .asst-routes-source svg { width: 12px; height: 12px; }
1639
+ .asst-routes-modal .asst-routes-source[data-edited="true"] { color: var(--asst-text); }
1640
+ .asst-routes-modal .asst-routes-source:is([data-source="disabled"], [data-source="incapable"]) { color: var(--asst-warn); text-decoration: none; }
1641
+ .asst-routes-modal .asst-routes-reset { width: var(--cap-h); min-width: var(--cap-h); padding: 0; color: var(--asst-text-3); }
1642
+ .asst-routes-draft { flex: 1; min-width: 0; color: var(--asst-text-3); font-size: 12px; }
1643
+ .asst-routes-draft[data-dirty="true"] { color: var(--asst-brand); }
1644
+ .asst-routes-scroll-cue { display: none; flex-shrink: 0; min-height: 44px; padding: 8px 14px; border: 0; border-top: 1px solid var(--asst-line); background: transparent; color: var(--asst-text-2); font: inherit; font-size: 12px; cursor: pointer; }
1645
+ .asst-routes-confirm { display: flex; flex-shrink: 0; align-items: center; flex-wrap: wrap; gap: 8px; padding: 10px 22px; border-top: 1px solid var(--asst-line); }
1646
+ .asst-routes-confirm-copy { display: flex; flex: 1 1 240px; flex-direction: column; gap: 4px; min-width: 0; font-size: 12px; }
1647
+ .asst-routes-confirm-copy span { color: var(--asst-text-2); }
1648
+ .asst-routes-confirm-action { box-shadow: inset 0 0 0 1px var(--asst-warn); }
1649
+ .asst-routes-modal .asst-routes-notice { box-shadow: none; padding: 0; color: var(--asst-text-2); }
1650
+ .asst-routes-catalog-note { padding: 8px; color: var(--asst-warn); font-size: 12px; line-height: 1.45; background: var(--asst-fill-1); border-radius: var(--asst-pill-r-xs); }
1651
+ .asst-routes-modal .asst-routes-error { flex-wrap: wrap; }
1652
+ .asst-routes-modal .asst-routes-error span { min-width: 0; overflow-wrap: anywhere; }
1653
+ .asst-routes-overlay :is(.asst-route-model-menu, .asst-route-field-menu) { max-width: min(440px, calc(100vw - 16px)); font-family: inherit; font-size: 12px; }
1654
+ .asst-routes-overlay :is(.asst-route-model-menu, .asst-route-field-menu) .asst-dd-item { height: var(--asst-pill-h-sm); min-height: var(--asst-pill-h-sm); padding: 0 8px; border-radius: var(--asst-pill-r-xs); }
1655
+ .asst-routes-overlay :is(.asst-route-model-menu, .asst-route-field-menu) .asst-dd-item-main { flex-direction: row; align-items: center; gap: 8px; }
1656
+ .asst-routes-overlay :is(.asst-route-model-menu, .asst-route-field-menu) .asst-dd-item-label { flex-shrink: 0; max-width: 60%; }
1657
+ .asst-routes-overlay :is(.asst-route-model-menu, .asst-route-field-menu) .asst-dd-item-desc { flex: 1; text-align: end; }
1658
+ .asst-routes-overlay .asst-route-model-menu .asst-dd-input { height: var(--asst-pill-h-sm); min-height: var(--asst-pill-h-sm); font-size: 12px; border-radius: var(--asst-pill-r-xs); }
1659
+ .asst-routes-overlay .asst-route-model-menu .asst-dd-input:focus-visible { outline: 2px solid var(--asst-focus); outline-offset: -2px; }
1660
+ .asst-route-menu-foot { flex-shrink: 0; display: flex; flex-wrap: wrap; align-items: center; border-top: 1px solid var(--asst-line-2); padding-top: 4px; }
1661
+ .asst-route-menu-foot .asst-dd-info { flex: 1 0 100%; font-size: 12px; color: var(--asst-text-2); }
1662
+ .asst-route-menu-foot .asst-dd-item { flex: 1; width: auto; }
1663
+ /* Large targets belong to a coarse pointer or the narrow stacked layout. */
1664
+ @media (pointer: coarse), (max-width: 600px) {
1665
+ .asst-routes-modal :is(.asst-btn, .asst-dd, .asst-iconbtn) { --cap-h: 44px; }
1666
+ .asst-routes-modal .asst-routes-mode { height: auto; min-height: 44px; }
1667
+ .asst-routes-modal .asst-routes-mode-label { white-space: normal; }
1668
+ .asst-routes-modal .asst-routes-columns, .asst-routes-modal .asst-routes-row { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.55fr) minmax(0, .7fr) minmax(0, .9fr) 44px; }
1669
+ .asst-routes-overlay :is(.asst-route-model-menu, .asst-route-field-menu) :is(.asst-dd-item, .asst-dd-input) { min-height: 44px; height: auto; padding: 8px; }
1670
+ .asst-routes-overlay :is(.asst-route-model-menu, .asst-route-field-menu) .asst-dd-item-main { flex-direction: column; align-items: stretch; gap: 2px; }
1671
+ .asst-routes-overlay :is(.asst-route-model-menu, .asst-route-field-menu) :is(.asst-dd-item-label, .asst-dd-item-desc) { white-space: normal; max-width: none; text-align: start; overflow-wrap: anywhere; }
1672
+ }
1673
+ @media (max-width: 600px) {
1674
+ .asst-routes-overlay { padding: 8px; }
1675
+ .asst-routes-overlay .asst-routes-modal { max-height: calc(100dvh - 16px); }
1676
+ .asst-routes-modal .asst-modal-body { padding: 14px; gap: 16px; }
1677
+ .asst-routes-modal .asst-modal-head { padding-inline: 14px 8px; }
1678
+ .asst-routes-modal .asst-routes-modes { grid-template-columns: 1fr; }
1679
+ .asst-routes-modal .asst-routes-columns { display: none; }
1680
+ .asst-routes-modal .asst-routes-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 44px; gap: 8px; padding: 12px 0; }
1681
+ .asst-routes-modal .asst-routes-row-main, .asst-routes-modal .asst-routes-model-cell { grid-column: 1 / -1; }
1682
+ .asst-routes-cell { flex-direction: column; gap: 4px; }
1683
+ .asst-routes-cell-label { display: block; color: var(--asst-text-3); font-size: 11.5px; }
1684
+ .asst-routes-reset-cell { align-self: end; }
1685
+ .asst-routes-reset-cell:empty { display: none; }
1686
+ .asst-routes-modal .asst-modal-foot { display: grid; grid-template-columns: 1fr auto auto; padding: 10px 14px; }
1687
+ .asst-routes-draft { grid-column: 1 / -1; grid-row: 1; }
1688
+ .asst-routes-reset-all { grid-column: 1; grid-row: 2; justify-self: start; }
1689
+ .asst-routes-cancel { grid-column: 2; grid-row: 2; }
1690
+ .asst-routes-save { grid-column: 3; grid-row: 2; }
1691
+ .asst-routes-scroll-cue { display: block; }
1692
+ .asst-routes-confirm { padding: 10px 14px; }
1693
+ }
1694
+
1549
1695
  /* ── Frame lightbox ─────────────────────────────────────────────── */
1550
1696
  .asst-lightbox { position: fixed; inset: 0; z-index: 100040; display: flex; align-items: center; justify-content: center; background: var(--asst-scrim-strong); }
1551
1697
  .asst-lightbox-figure { display: flex; flex-direction: column; align-items: center; gap: 10px; margin: 0; }
@@ -1599,7 +1745,8 @@ export function injectAssistantStyles() {
1599
1745
  /* ── Narrow / short containers (the tab or float window) ──────────
1600
1746
  Toolbar priority, highest first: route mode · Computer · project ·
1601
1747
  MCP. The composer row (attach · model · effort · permission mode ·
1602
- account) keeps model; account hides ≤720px. A capsule that runs out of
1748
+ test density · account) keeps model; account hides ≤720px, where test
1749
+ density is its gauge alone. A capsule that runs out of
1603
1750
  room drops its label half first and stays a glyph (its words remain in
1604
1751
  aria-label and the tooltip): route mode and Computer ≤960px, the mode
1605
1752
  ≤540px, every composer field but the model ≤440px. Model, history, new
@@ -1620,6 +1767,9 @@ export function injectAssistantStyles() {
1620
1767
  }
1621
1768
  @container asst (max-width: 720px) {
1622
1769
  :is(.asst-dd-mcp, .asst-dd-account, .asst-dd-project, .asst-bar-group[data-group="context"]) { display: none !important; }
1770
+ .asst-composer-brain .asst-dd.asst-dd-tests { display: grid; place-content: center; place-items: center; padding: 0; }
1771
+ .asst-composer-brain .asst-dd.asst-dd-tests > :is(.asst-dd-label, .asst-dd-caret) { display: none; }
1772
+ .asst-composer-brain .asst-dd.asst-dd-tests > .asst-icon:not([hidden]) { margin: 0; padding: 0 var(--cap-glyph-pad); box-shadow: none; }
1623
1773
  .asst-route-keys { display: none; }
1624
1774
  }
1625
1775
  @container asst (max-width: 540px) {
@@ -1639,6 +1789,9 @@ export function injectAssistantStyles() {
1639
1789
  :is(.asst-cost, .asst-bar-group[data-group="context"]) { display: none !important; }
1640
1790
  .asst-suggestions { flex-direction: column; align-items: stretch; width: 100%; }
1641
1791
  .asst-chip.asst-suggestion { justify-content: flex-start; max-width: none; }
1792
+ .asst-empty-project { width: 100%; }
1793
+ .asst-dd.asst-empty-project-btn { flex: 1 1 auto; justify-content: flex-start; max-width: none; }
1794
+ .asst-empty-project-btn .asst-dd-label { flex: 1 1 auto; max-width: none; text-align: start; }
1642
1795
  .asst-msg.msg-user .asst-msg-content { max-width: 92%; }
1643
1796
  .asst-dd-model .asst-dd-label { max-width: 9ch; }
1644
1797
  .asst-route-opt-sub { display: none; }
@@ -1649,12 +1802,13 @@ export function injectAssistantStyles() {
1649
1802
  .asst-bar-group[data-group="computer"] { display: none !important; }
1650
1803
  /* The model keeps a readable name (at least 5ch): the composer row wraps instead of truncating it. */
1651
1804
  .asst-dd-model .asst-dd-label { min-width: 5ch; max-width: 14ch; }
1652
- .asst-composer-actions { flex-wrap: wrap; row-gap: 6px; }
1653
- .asst-composer-brain { flex-wrap: wrap; row-gap: 6px; }
1805
+ /* A tighter gap keeps attach · model · effort · variant · mode · tests · send on one row at 320px. */
1806
+ .asst-composer-actions { flex-wrap: wrap; gap: 6px 4px; }
1807
+ .asst-composer-brain { flex-wrap: wrap; gap: 6px 4px; }
1654
1808
  }
1655
1809
  @container asst (max-height: 260px) {
1656
1810
  /* the toolbar stays: it carries the controls */
1657
- :is(.asst-empty-body, .asst-empty-mascot, .asst-suggestions) { display: none !important; }
1811
+ :is(.asst-empty-body, .asst-empty-mascot, .asst-empty-project, .asst-suggestions, .asst-empty-greet) { display: none !important; }
1658
1812
  .asst-empty { gap: 4px; padding: 4px; }
1659
1813
  .asst-empty-title { font-size: 14px; }
1660
1814
  .asst-bottom { margin-top: 6px; }