synabun 2.0.0-beta → 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 (374) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +51 -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/launcher.mjs +40 -0
  14. package/lib/claude-hooks.js +61 -8
  15. package/lib/client-config-repair.js +4 -3
  16. package/lib/data-home-migration.js +4 -0
  17. package/lib/desktop-pwa.js +206 -0
  18. package/lib/external-tools.js +221 -0
  19. package/lib/first-launch-protection.js +5 -0
  20. package/lib/packaged-runtime.js +110 -0
  21. package/lib/start-launcher.js +1396 -0
  22. package/mcp-server/package-lock.json +2 -2
  23. package/mcp-server/package.json +1 -3
  24. package/neural-interface/i18n/en.json +1540 -154
  25. package/neural-interface/i18n/pt-BR.json +1538 -153
  26. package/neural-interface/lib/assistant-api.js +62 -6
  27. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  28. package/neural-interface/lib/assistant-catalog.js +179 -27
  29. package/neural-interface/lib/assistant-config.js +31 -2
  30. package/neural-interface/lib/assistant-dispatch.js +62 -13
  31. package/neural-interface/lib/assistant-envelope.js +9 -6
  32. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  33. package/neural-interface/lib/assistant-memory.js +24 -10
  34. package/neural-interface/lib/assistant-persona.js +6 -2
  35. package/neural-interface/lib/assistant-pricing.js +21 -6
  36. package/neural-interface/lib/assistant-runtime.js +181 -12
  37. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  38. package/neural-interface/lib/assistant-test-density.js +142 -0
  39. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  40. package/neural-interface/lib/assistant-user-context.js +908 -0
  41. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  42. package/neural-interface/lib/assistant-user-learn.js +404 -0
  43. package/neural-interface/lib/assistant-user-threads.js +226 -0
  44. package/neural-interface/lib/assistant-user-writer.js +181 -0
  45. package/neural-interface/lib/augmented-path.js +23 -3
  46. package/neural-interface/lib/browser-background-tab.js +134 -0
  47. package/neural-interface/lib/browser-language.js +83 -0
  48. package/neural-interface/lib/browser-screenshot.js +678 -61
  49. package/neural-interface/lib/claude-agent-bridge.js +87 -153
  50. package/neural-interface/lib/claude-executable.js +241 -0
  51. package/neural-interface/lib/claude-model-catalog.js +82 -93
  52. package/neural-interface/lib/claude-temporary.js +46 -7
  53. package/neural-interface/lib/cloudflared/install.js +533 -0
  54. package/neural-interface/lib/cloudflared/manager.js +545 -0
  55. package/neural-interface/lib/cloudflared/pins.js +115 -0
  56. package/neural-interface/lib/cloudflared/pins.json +63 -0
  57. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  58. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  59. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  60. package/neural-interface/lib/editor-completion.js +714 -0
  61. package/neural-interface/lib/editor-config.js +149 -0
  62. package/neural-interface/lib/editor-files-api.js +268 -0
  63. package/neural-interface/lib/editor-git-api.js +134 -0
  64. package/neural-interface/lib/file-explorer-api.js +1039 -0
  65. package/neural-interface/lib/file-explorer-system.js +355 -0
  66. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  67. package/neural-interface/lib/file-explorer-watch.js +239 -0
  68. package/neural-interface/lib/git-diff-api.js +103 -0
  69. package/neural-interface/lib/git-read.js +202 -0
  70. package/neural-interface/lib/greeting/api.js +89 -0
  71. package/neural-interface/lib/greeting/budgets.js +90 -0
  72. package/neural-interface/lib/greeting/engine.js +461 -0
  73. package/neural-interface/lib/greeting/facts.js +320 -0
  74. package/neural-interface/lib/greeting/sources.js +252 -0
  75. package/neural-interface/lib/greeting/writer.js +139 -0
  76. package/neural-interface/lib/http-guards.js +189 -13
  77. package/neural-interface/lib/machine-names.js +88 -0
  78. package/neural-interface/lib/memory-api.js +31 -7
  79. package/neural-interface/lib/native-binary-runtime.js +69 -337
  80. package/neural-interface/lib/native-loop-providers.js +40 -49
  81. package/neural-interface/lib/offline-page.js +54 -0
  82. package/neural-interface/lib/plugin-installer.js +4 -2
  83. package/neural-interface/lib/project-storage.js +5 -7
  84. package/neural-interface/lib/request-gate.js +589 -0
  85. package/neural-interface/lib/session-title-generator.js +5 -0
  86. package/neural-interface/lib/synabun-update-plan.js +19 -0
  87. package/neural-interface/lib/ui-blob-api.js +108 -0
  88. package/neural-interface/lib/ui-blob-store.js +68 -0
  89. package/neural-interface/package-lock.json +701 -253
  90. package/neural-interface/package.json +13 -2
  91. package/neural-interface/public/install-app.html +34 -0
  92. package/neural-interface/public/manifest.json +2 -0
  93. package/neural-interface/public/offline.html +402 -66
  94. package/neural-interface/public/onboarding.html +264 -147
  95. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  96. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  97. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  98. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  99. package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
  100. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  101. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  102. package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
  103. package/neural-interface/public/shared/assistant/asst-render.js +219 -67
  104. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  105. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  106. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  107. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  108. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  109. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  110. package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
  111. package/neural-interface/public/shared/blob-store.js +111 -22
  112. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  113. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  114. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  115. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  116. package/neural-interface/public/shared/command-runner-model.js +30 -0
  117. package/neural-interface/public/shared/constants.js +6 -1
  118. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  119. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  120. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  121. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  122. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  123. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  124. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  125. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  126. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  127. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  128. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  129. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  130. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  131. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  132. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  133. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  134. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  135. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  136. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  137. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  138. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  139. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  140. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  141. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  142. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  143. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  144. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  145. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  146. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  147. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  148. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  149. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  150. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  151. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  152. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  153. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  154. package/neural-interface/public/shared/file-links.js +339 -0
  155. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  156. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  157. package/neural-interface/public/shared/html-shell.js +169 -170
  158. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  159. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  160. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  161. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  162. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  163. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  164. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  165. package/neural-interface/public/shared/pwa-install-page.js +62 -0
  166. package/neural-interface/public/shared/pwa-install.css +12 -0
  167. package/neural-interface/public/shared/pwa-install.js +127 -0
  168. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  169. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  170. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  171. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  172. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  173. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  174. package/neural-interface/public/shared/start-bridge.js +802 -0
  175. package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
  176. package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
  177. package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
  178. package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
  179. package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
  180. package/neural-interface/public/shared/styles.css +2219 -1117
  181. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  182. package/neural-interface/public/shared/term-ready.js +307 -0
  183. package/neural-interface/public/shared/tunnel-view.js +167 -0
  184. package/neural-interface/public/shared/ui-claude-panel.js +160 -19
  185. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  186. package/neural-interface/public/shared/ui-detail.js +15 -0
  187. package/neural-interface/public/shared/ui-explorer.js +22 -0
  188. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  189. package/neural-interface/public/shared/ui-file-links.js +103 -0
  190. package/neural-interface/public/shared/ui-help.js +26 -0
  191. package/neural-interface/public/shared/ui-invite.js +126 -116
  192. package/neural-interface/public/shared/ui-judgments.js +1 -1
  193. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  194. package/neural-interface/public/shared/ui-loading.js +367 -12
  195. package/neural-interface/public/shared/ui-menubar.js +21 -55
  196. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  197. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  198. package/neural-interface/public/shared/ui-navbar.js +45 -21
  199. package/neural-interface/public/shared/ui-settings.js +491 -547
  200. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  201. package/neural-interface/public/shared/ui-sync.js +16 -0
  202. package/neural-interface/public/shared/ui-terminal.js +153 -149
  203. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  204. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  205. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  206. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  207. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  208. package/neural-interface/public/shared/worker-end.js +251 -0
  209. package/neural-interface/public/sw.js +3 -1
  210. package/neural-interface/public/variant/2d/main.js +2 -1
  211. package/neural-interface/public/variant/3d/main.js +2 -1
  212. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  309. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  310. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  311. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  312. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  313. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  314. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  315. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  316. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  317. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  318. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  319. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  320. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  321. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  322. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  323. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  324. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  325. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  326. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  327. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  328. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  329. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  330. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  331. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  332. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  333. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  334. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  335. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  336. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  337. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  338. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  339. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  340. package/neural-interface/scripts/build-vendor.mjs +78 -2
  341. package/neural-interface/scripts/rebuild-pty.js +15 -12
  342. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  343. package/neural-interface/server.js +1092 -1212
  344. package/neural-interface/templates/rulesets/README.md +1 -1
  345. package/package.json +13 -7
  346. package/postinstall.js +11 -81
  347. package/setup.js +111 -6
  348. package/benchmarks/browser-live-results.json +0 -79
  349. package/benchmarks/browser-v2-results.json +0 -258
  350. package/docs/browser-v2.md +0 -109
  351. package/docs/hook-integrity.md +0 -89
  352. package/docs/judgments.md +0 -182
  353. package/docs/style-guide.md +0 -246
  354. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  355. package/neural-interface/public/category-logos/claude.svg +0 -1
  356. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  357. package/neural-interface/public/category-logos/openai.svg +0 -1
  358. package/neural-interface/public/category-logos/synabun.png +0 -0
  359. package/neural-interface/public/claude.svg +0 -1
  360. package/neural-interface/public/gallery.svg +0 -6
  361. package/neural-interface/public/leonardoai.svg +0 -1
  362. package/neural-interface/public/mcp.svg +0 -4
  363. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  364. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  365. package/neural-interface/public/sessionicon.svg +0 -8
  366. package/neural-interface/public/toolbar-album.svg +0 -8
  367. package/neural-interface/public/toolbar-question.svg +0 -6
  368. package/neural-interface/public/toolbar-sessions.svg +0 -6
  369. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  370. package/scripts/audit-claude-hook-usage.mjs +0 -12
  371. package/scripts/audit-memory-duplicates.mjs +0 -44
  372. package/scripts/measure-claude-hook-overhead.mjs +0 -77
  373. package/scripts/recover-user-config.mjs +0 -254
  374. 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; }
@@ -446,6 +454,8 @@ export function injectAssistantStyles() {
446
454
  .asst-working-face { display: inline-flex; flex-shrink: 0; color: var(--asst-text); line-height: 0; }
447
455
  .asst-working-face:empty { display: none; }
448
456
  .asst-working-face svg { display: block; width: 56px; height: 28px; }
457
+ /* The hero's hand-off (asst-render.js settleHero): the face flies in from the empty state, over the prompt. */
458
+ .asst-working.is-flip .asst-working-face { position: relative; z-index: 3; }
449
459
 
450
460
  /* ── Tool rows ──────────────────────────────────────────────────── */
451
461
  .asst-tool { min-width: 0; font-size: 12.5px; }
@@ -608,6 +618,39 @@ export function injectAssistantStyles() {
608
618
  color: var(--asst-text-3);
609
619
  font-size: 12px;
610
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; }
611
654
  .asst-link { padding: 0; border: 0; background: transparent; color: var(--asst-focus); font: inherit; text-align: left; cursor: pointer; }
612
655
  .asst-link:hover { text-decoration: underline; }
613
656
  .asst-link:disabled { color: var(--asst-text-4); cursor: default; text-decoration: none; }
@@ -1389,6 +1432,8 @@ export function injectAssistantStyles() {
1389
1432
  .asst-dd-item-main { display: flex; flex: 1; flex-direction: column; gap: 1px; min-width: 0; }
1390
1433
  .asst-dd-item-label, .asst-dd-item-desc { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
1391
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; }
1392
1437
  .asst-dd-item-tag { flex-shrink: 0; margin-left: auto; color: var(--asst-text-3); font-size: 11px; }
1393
1438
  .asst-dd-kbd { flex-shrink: 0; margin-left: auto; color: var(--asst-text-3); font-family: var(--asst-mono); font-size: 10.5px; }
1394
1439
  .asst-dd-sep { flex-shrink: 0; height: 1px; margin: 4px 6px; background: var(--asst-line); }
@@ -1544,6 +1589,109 @@ export function injectAssistantStyles() {
1544
1589
  .asst-routes-row { flex-wrap: wrap; }
1545
1590
  }
1546
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
+
1547
1695
  /* ── Frame lightbox ─────────────────────────────────────────────── */
1548
1696
  .asst-lightbox { position: fixed; inset: 0; z-index: 100040; display: flex; align-items: center; justify-content: center; background: var(--asst-scrim-strong); }
1549
1697
  .asst-lightbox-figure { display: flex; flex-direction: column; align-items: center; gap: 10px; margin: 0; }
@@ -1575,8 +1723,8 @@ export function injectAssistantStyles() {
1575
1723
 
1576
1724
  /* ── Empty state character (shared/synabun-mascot.js, the onboarding eyes) ──
1577
1725
  The rig moves by itself (idle, blinks, the cursor); nothing floats it.
1578
- After a send it is parked where it stood (.asst-hero-dock, positioned
1579
- by asst-render.js) until the first stage of the turn takes it over. */
1726
+ A send hands it over to the "Thinking" row at once; with no such row it
1727
+ fades out where it stood (.asst-hero-dock, positioned by asst-render.js). */
1580
1728
  .asst-empty-mascot { display: flex; justify-content: center; margin-bottom: 2px; color: var(--asst-text); pointer-events: none; user-select: none; }
1581
1729
  :is(.asst-empty-mascot, .asst-hero-dock) svg { display: block; }
1582
1730
  .asst-hero-dock { position: absolute; z-index: 4; display: flex; align-items: center; justify-content: center; color: var(--asst-text); pointer-events: none; user-select: none; }
@@ -1597,7 +1745,8 @@ export function injectAssistantStyles() {
1597
1745
  /* ── Narrow / short containers (the tab or float window) ──────────
1598
1746
  Toolbar priority, highest first: route mode · Computer · project ·
1599
1747
  MCP. The composer row (attach · model · effort · permission mode ·
1600
- 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
1601
1750
  room drops its label half first and stays a glyph (its words remain in
1602
1751
  aria-label and the tooltip): route mode and Computer ≤960px, the mode
1603
1752
  ≤540px, every composer field but the model ≤440px. Model, history, new
@@ -1618,6 +1767,9 @@ export function injectAssistantStyles() {
1618
1767
  }
1619
1768
  @container asst (max-width: 720px) {
1620
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; }
1621
1773
  .asst-route-keys { display: none; }
1622
1774
  }
1623
1775
  @container asst (max-width: 540px) {
@@ -1637,6 +1789,9 @@ export function injectAssistantStyles() {
1637
1789
  :is(.asst-cost, .asst-bar-group[data-group="context"]) { display: none !important; }
1638
1790
  .asst-suggestions { flex-direction: column; align-items: stretch; width: 100%; }
1639
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; }
1640
1795
  .asst-msg.msg-user .asst-msg-content { max-width: 92%; }
1641
1796
  .asst-dd-model .asst-dd-label { max-width: 9ch; }
1642
1797
  .asst-route-opt-sub { display: none; }
@@ -1647,12 +1802,13 @@ export function injectAssistantStyles() {
1647
1802
  .asst-bar-group[data-group="computer"] { display: none !important; }
1648
1803
  /* The model keeps a readable name (at least 5ch): the composer row wraps instead of truncating it. */
1649
1804
  .asst-dd-model .asst-dd-label { min-width: 5ch; max-width: 14ch; }
1650
- .asst-composer-actions { flex-wrap: wrap; row-gap: 6px; }
1651
- .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; }
1652
1808
  }
1653
1809
  @container asst (max-height: 260px) {
1654
1810
  /* the toolbar stays: it carries the controls */
1655
- :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; }
1656
1812
  .asst-empty { gap: 4px; padding: 4px; }
1657
1813
  .asst-empty-title { font-size: 14px; }
1658
1814
  .asst-bottom { margin-top: 6px; }