synabun 2.0.0 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/NOTICE +5 -0
  3. package/README.md +2 -1
  4. package/THIRD-PARTY-LICENSES.md +59 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +153 -18
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +26 -3
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/child-path.js +303 -0
  14. package/lib/claude-hooks.js +2 -1
  15. package/lib/data-home-migration.js +2 -0
  16. package/lib/desktop-app.js +265 -0
  17. package/lib/external-tools.js +7 -3
  18. package/lib/log-redact.js +102 -0
  19. package/lib/packaged-runtime.js +138 -11
  20. package/lib/start-launcher.js +88 -1
  21. package/mcp-server/dist/http.d.ts +3 -1
  22. package/mcp-server/dist/http.d.ts.map +1 -1
  23. package/mcp-server/dist/http.js +15 -4
  24. package/mcp-server/dist/http.js.map +1 -1
  25. package/mcp-server/dist/index.d.ts.map +1 -1
  26. package/mcp-server/dist/index.js +9 -6
  27. package/mcp-server/dist/index.js.map +1 -1
  28. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  29. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  30. package/mcp-server/dist/services/budget-judgments.js +153 -0
  31. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  32. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  33. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  34. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  35. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  36. package/mcp-server/dist/services/identity.d.ts +11 -0
  37. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  38. package/mcp-server/dist/services/identity.js +24 -0
  39. package/mcp-server/dist/services/identity.js.map +1 -1
  40. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  41. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  42. package/mcp-server/dist/services/neural-interface.js +34 -2
  43. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  44. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  45. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  46. package/mcp-server/dist/services/typesafe-config.js +4 -1
  47. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  48. package/mcp-server/dist/tools/agents.d.ts +10 -7
  49. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  50. package/mcp-server/dist/tools/agents.js +34 -13
  51. package/mcp-server/dist/tools/agents.js.map +1 -1
  52. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  53. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  54. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  55. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  56. package/mcp-server/dist/tui.js +2 -1
  57. package/mcp-server/dist/tui.js.map +1 -1
  58. package/mcp-server/dist/version.d.ts +5 -0
  59. package/mcp-server/dist/version.d.ts.map +1 -0
  60. package/mcp-server/dist/version.js +21 -0
  61. package/mcp-server/dist/version.js.map +1 -0
  62. package/mcp-server/package-lock.json +2 -2
  63. package/mcp-server/package.json +1 -3
  64. package/mcp-server/run.mjs +15 -9
  65. package/mcp-server/src/http.ts +18 -3
  66. package/mcp-server/src/index.ts +9 -6
  67. package/mcp-server/src/services/budget-judgments.ts +183 -0
  68. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  69. package/mcp-server/src/services/identity.ts +27 -0
  70. package/mcp-server/src/services/neural-interface.ts +34 -2
  71. package/mcp-server/src/services/typesafe-config.ts +4 -1
  72. package/mcp-server/src/tools/agents.ts +33 -14
  73. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  74. package/mcp-server/src/tui.ts +2 -1
  75. package/mcp-server/src/version.ts +22 -0
  76. package/neural-interface/i18n/en.json +2126 -209
  77. package/neural-interface/i18n/pt-BR.json +2123 -207
  78. package/neural-interface/lib/account-usage.js +163 -0
  79. package/neural-interface/lib/assistant-api.js +316 -57
  80. package/neural-interface/lib/assistant-brains/claude.js +19 -2
  81. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  82. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  83. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  84. package/neural-interface/lib/assistant-budget-split.js +408 -0
  85. package/neural-interface/lib/assistant-budget.js +94 -95
  86. package/neural-interface/lib/assistant-catalog.js +179 -27
  87. package/neural-interface/lib/assistant-config.js +91 -14
  88. package/neural-interface/lib/assistant-dispatch.js +494 -138
  89. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  90. package/neural-interface/lib/assistant-jev.js +26 -3
  91. package/neural-interface/lib/assistant-memory.js +24 -10
  92. package/neural-interface/lib/assistant-persona.js +73 -12
  93. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  94. package/neural-interface/lib/assistant-pricing.js +21 -6
  95. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  96. package/neural-interface/lib/assistant-route-gate.js +19 -3
  97. package/neural-interface/lib/assistant-router.js +373 -42
  98. package/neural-interface/lib/assistant-runtime.js +488 -59
  99. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  100. package/neural-interface/lib/assistant-test-density.js +142 -0
  101. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  102. package/neural-interface/lib/assistant-user-context.js +908 -0
  103. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  104. package/neural-interface/lib/assistant-user-learn.js +404 -0
  105. package/neural-interface/lib/assistant-user-threads.js +226 -0
  106. package/neural-interface/lib/assistant-user-writer.js +183 -0
  107. package/neural-interface/lib/augmented-path.js +317 -53
  108. package/neural-interface/lib/browser-background-tab.js +134 -0
  109. package/neural-interface/lib/browser-screenshot.js +678 -61
  110. package/neural-interface/lib/claude-accounts.js +342 -31
  111. package/neural-interface/lib/claude-agent-bridge.js +123 -9
  112. package/neural-interface/lib/claude-executable.js +6 -1
  113. package/neural-interface/lib/claude-model-catalog.js +33 -5
  114. package/neural-interface/lib/claude-panel-auth.js +221 -0
  115. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  116. package/neural-interface/lib/claude-temporary.js +46 -7
  117. package/neural-interface/lib/cli-install-api.js +137 -0
  118. package/neural-interface/lib/cli-install.js +797 -0
  119. package/neural-interface/lib/cli-path.js +130 -0
  120. package/neural-interface/lib/cli-resolver.js +744 -0
  121. package/neural-interface/lib/cli-update-planner.js +143 -7
  122. package/neural-interface/lib/cloudflared/install.js +533 -0
  123. package/neural-interface/lib/cloudflared/manager.js +545 -0
  124. package/neural-interface/lib/cloudflared/pins.js +115 -0
  125. package/neural-interface/lib/cloudflared/pins.json +63 -0
  126. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  127. package/neural-interface/lib/codex-account-auth.js +178 -0
  128. package/neural-interface/lib/codex-accounts.js +489 -0
  129. package/neural-interface/lib/codex-capabilities.js +2 -1
  130. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  131. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  132. package/neural-interface/lib/codex-panel-auth.js +62 -0
  133. package/neural-interface/lib/codex-session-settings.js +1 -1
  134. package/neural-interface/lib/codex-settings-schema.js +120 -0
  135. package/neural-interface/lib/desktop/config.js +3 -0
  136. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  137. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  138. package/neural-interface/lib/editor-completion.js +714 -0
  139. package/neural-interface/lib/editor-config.js +149 -0
  140. package/neural-interface/lib/editor-files-api.js +268 -0
  141. package/neural-interface/lib/editor-git-api.js +134 -0
  142. package/neural-interface/lib/file-explorer-api.js +1039 -0
  143. package/neural-interface/lib/file-explorer-system.js +355 -0
  144. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  145. package/neural-interface/lib/file-explorer-watch.js +239 -0
  146. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  147. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  148. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  149. package/neural-interface/lib/git-bash/install.js +529 -0
  150. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  151. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  152. package/neural-interface/lib/git-bash/manager.js +268 -0
  153. package/neural-interface/lib/git-bash/pins.js +206 -0
  154. package/neural-interface/lib/git-bash/pins.json +34 -0
  155. package/neural-interface/lib/git-bash/resolver.js +194 -0
  156. package/neural-interface/lib/git-diff-api.js +103 -0
  157. package/neural-interface/lib/git-read.js +234 -0
  158. package/neural-interface/lib/greeting/api.js +89 -0
  159. package/neural-interface/lib/greeting/budgets.js +90 -0
  160. package/neural-interface/lib/greeting/engine.js +461 -0
  161. package/neural-interface/lib/greeting/facts.js +320 -0
  162. package/neural-interface/lib/greeting/sources.js +252 -0
  163. package/neural-interface/lib/greeting/writer.js +139 -0
  164. package/neural-interface/lib/http-guards.js +189 -13
  165. package/neural-interface/lib/install-details.js +145 -0
  166. package/neural-interface/lib/machine-names.js +88 -0
  167. package/neural-interface/lib/mcp-installer.js +14 -2
  168. package/neural-interface/lib/memory-api.js +31 -7
  169. package/neural-interface/lib/native-loop-providers.js +26 -8
  170. package/neural-interface/lib/native-loop-runtime.js +9 -4
  171. package/neural-interface/lib/os-path.js +319 -0
  172. package/neural-interface/lib/plugin-installer.js +4 -2
  173. package/neural-interface/lib/project-storage.js +5 -7
  174. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  175. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  176. package/neural-interface/lib/pty-host/core.js +2 -1
  177. package/neural-interface/lib/pty-host/manager.js +27 -2
  178. package/neural-interface/lib/remote-policy.js +20 -11
  179. package/neural-interface/lib/request-gate.js +589 -0
  180. package/neural-interface/lib/session-title-generator.js +4 -3
  181. package/neural-interface/lib/system-about.js +340 -0
  182. package/neural-interface/lib/system-activity.js +66 -0
  183. package/neural-interface/lib/terminal-env.js +11 -3
  184. package/neural-interface/lib/terminal-path.js +55 -0
  185. package/neural-interface/lib/ui-blob-api.js +108 -0
  186. package/neural-interface/lib/ui-blob-store.js +68 -0
  187. package/neural-interface/lib/virtual-socket.js +12 -1
  188. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  189. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  190. package/neural-interface/lib/whatsapp/commands.js +13 -14
  191. package/neural-interface/lib/whatsapp/config.js +134 -20
  192. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  193. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  194. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  195. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  196. package/neural-interface/lib/whatsapp/manager.js +92 -32
  197. package/neural-interface/lib/whatsapp/paths.js +11 -1
  198. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  199. package/neural-interface/lib/whatsapp/redact.js +63 -0
  200. package/neural-interface/lib/whatsapp/service.js +343 -91
  201. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  202. package/neural-interface/package-lock.json +701 -2
  203. package/neural-interface/package.json +13 -2
  204. package/neural-interface/public/index.html +2 -0
  205. package/neural-interface/public/index2d.html +2 -0
  206. package/neural-interface/public/install-app.html +9 -6
  207. package/neural-interface/public/offline.html +19 -3
  208. package/neural-interface/public/onboarding.html +107 -2
  209. package/neural-interface/public/shared/account-i18n.js +7 -0
  210. package/neural-interface/public/shared/account-menu.js +539 -0
  211. package/neural-interface/public/shared/account-usage.js +19 -0
  212. package/neural-interface/public/shared/account-view.js +335 -0
  213. package/neural-interface/public/shared/agent-runtime-options.js +50 -14
  214. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  215. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  216. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  217. package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
  218. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  219. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  220. package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
  221. package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
  222. package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
  223. package/neural-interface/public/shared/assistant/asst-render.js +197 -23
  224. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  225. package/neural-interface/public/shared/assistant/asst-route.js +95 -343
  226. package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
  227. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  228. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  229. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  230. package/neural-interface/public/shared/assistant/asst-state.js +186 -2
  231. package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
  232. package/neural-interface/public/shared/blob-store.js +111 -22
  233. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  234. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  235. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  236. package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
  237. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  238. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  239. package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
  240. package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
  241. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  242. package/neural-interface/public/shared/claude-account-view.js +3 -0
  243. package/neural-interface/public/shared/cli-install-view.js +392 -0
  244. package/neural-interface/public/shared/cli-status.js +282 -22
  245. package/neural-interface/public/shared/constants.js +6 -1
  246. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  247. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  248. package/neural-interface/public/shared/cp/cp-styles.js +61 -0
  249. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  250. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  251. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  252. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  253. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  254. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  255. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  256. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  257. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  258. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  259. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  260. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  261. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  262. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  263. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  264. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  265. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  266. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  267. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  268. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  269. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  270. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  271. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  272. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  273. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  274. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  275. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  276. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  277. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  278. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  279. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  280. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  281. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  282. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  283. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  284. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  285. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  286. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  287. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  288. package/neural-interface/public/shared/file-links.js +339 -0
  289. package/neural-interface/public/shared/git-bash-view.js +145 -0
  290. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  291. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  292. package/neural-interface/public/shared/html-shell.js +161 -165
  293. package/neural-interface/public/shared/i18n.js +19 -1
  294. package/neural-interface/public/shared/install-details-view.js +86 -0
  295. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  296. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  297. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
  298. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  299. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
  300. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
  301. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  302. package/neural-interface/public/shared/pwa-install-page.js +32 -2
  303. package/neural-interface/public/shared/pwa-install.css +3 -3
  304. package/neural-interface/public/shared/pwa-install.js +100 -1
  305. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  306. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  307. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  308. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  309. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  310. package/neural-interface/public/shared/settings/settings-ia.js +33 -4
  311. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  312. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  313. package/neural-interface/public/shared/start-bridge.js +238 -19
  314. package/neural-interface/public/shared/storage.js +30 -3
  315. package/neural-interface/public/shared/styles.css +1710 -770
  316. package/neural-interface/public/shared/synabun-mascot.js +68 -58
  317. package/neural-interface/public/shared/tunnel-view.js +167 -0
  318. package/neural-interface/public/shared/ui-claude-panel.js +369 -229
  319. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  320. package/neural-interface/public/shared/ui-detail.js +15 -0
  321. package/neural-interface/public/shared/ui-explorer.js +22 -0
  322. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  323. package/neural-interface/public/shared/ui-file-links.js +103 -0
  324. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  325. package/neural-interface/public/shared/ui-help.js +26 -0
  326. package/neural-interface/public/shared/ui-invite.js +126 -116
  327. package/neural-interface/public/shared/ui-judgments.js +10 -1
  328. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  329. package/neural-interface/public/shared/ui-loading.js +187 -19
  330. package/neural-interface/public/shared/ui-menubar.js +25 -33
  331. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  332. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  333. package/neural-interface/public/shared/ui-navbar.js +48 -22
  334. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  335. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  336. package/neural-interface/public/shared/ui-sessions.js +9 -3
  337. package/neural-interface/public/shared/ui-settings.js +509 -490
  338. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  339. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  340. package/neural-interface/public/shared/ui-sync.js +22 -0
  341. package/neural-interface/public/shared/ui-terminal.js +40 -5
  342. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  343. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  344. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  345. package/neural-interface/public/shared/ui-update.js +594 -5
  346. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  347. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  348. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  349. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  350. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  351. package/neural-interface/public/shared/worker-end.js +253 -0
  352. package/neural-interface/public/sw.js +2 -2
  353. package/neural-interface/public/variant/2d/main.js +10 -1
  354. package/neural-interface/public/variant/3d/main.js +10 -1
  355. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  356. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  357. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  358. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  359. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  360. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  361. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  362. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  363. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  364. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  365. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  366. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  367. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  368. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  369. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  370. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  371. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  372. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  373. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  374. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  375. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  376. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  377. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  378. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  379. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  380. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  381. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  382. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  383. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  384. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  385. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  386. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  387. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  388. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  389. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  390. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  391. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  392. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  393. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  394. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  395. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  396. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  397. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  398. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  399. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  400. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  401. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  402. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  403. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  404. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  405. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  406. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  407. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  408. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  409. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  410. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  411. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  412. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  413. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  414. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  415. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  416. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  417. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  418. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  419. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  420. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  421. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  422. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  423. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  424. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  425. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  426. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  427. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  428. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  429. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  430. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  431. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  432. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  433. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  434. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  435. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  436. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  437. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  438. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  439. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  440. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  441. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  442. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  443. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  444. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  445. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  446. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  447. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  448. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  449. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  450. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  451. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  452. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  453. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  454. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  455. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  456. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  457. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  458. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  459. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  460. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  461. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  462. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  463. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  464. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  465. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  466. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  467. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  468. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  469. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  470. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  471. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  472. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  473. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  474. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  475. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  476. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  477. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  478. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  479. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  480. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  481. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  482. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  483. package/neural-interface/scripts/build-vendor.mjs +78 -2
  484. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  485. package/neural-interface/server.js +2085 -1629
  486. package/neural-interface/templates/rulesets/README.md +1 -1
  487. package/package.json +5 -8
  488. package/setup.js +53 -15
  489. package/benchmarks/browser-live-results.json +0 -79
  490. package/benchmarks/browser-v2-results.json +0 -258
  491. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  492. package/neural-interface/public/category-logos/claude.svg +0 -1
  493. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  494. package/neural-interface/public/category-logos/openai.svg +0 -1
  495. package/neural-interface/public/category-logos/synabun.png +0 -0
  496. package/neural-interface/public/claude.svg +0 -1
  497. package/neural-interface/public/gallery.svg +0 -6
  498. package/neural-interface/public/leonardoai.svg +0 -1
  499. package/neural-interface/public/mcp.svg +0 -4
  500. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  501. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  502. package/neural-interface/public/sessionicon.svg +0 -8
  503. package/neural-interface/public/toolbar-album.svg +0 -8
  504. package/neural-interface/public/toolbar-question.svg +0 -6
  505. package/neural-interface/public/toolbar-sessions.svg +0 -6
  506. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  507. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,154 @@
1
+ // ═══════════════════════════════════════════
2
+ // SETTINGS — the Assistant's user context, in words
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // What `GET /api/assistant/user-context` (lib/assistant-api.js, the engine in lib/assistant-user-context.js)
6
+ // says about Settings → Automations → Automation behavior: whether the three switches can be used at all,
7
+ // what each one is set to, what stands in the way of a layer, and whether the digest is in use. Pure: no DOM
8
+ // and no import, so node runs it (tests/settings-user-context-view.test.mjs).
9
+ //
10
+ // readUserContextResponse({ status, body, error }) what the server answered → a reading
11
+ // describeUserContext(reading, { tx }) the section as ui-settings.js draws it
12
+ // describeUserContextSaveFailure(input, { tx }) the sentence for a switch that could not be saved
13
+ //
14
+ // `tx(key, params)` is the caller's translate function over the settings.redesign.* strings (the kit's `tx`).
15
+ //
16
+ // The section never shows a healthy-looking control for something that is not happening: a server without
17
+ // the engine gets disabled rows and says why, a switched-off layer says what stops, and the digest is shown
18
+ // as told to a new conversation only while "Knowledge of you" is on (the engine's preview is built either way).
19
+
20
+ const K = 'settings.redesign.hooks.uc';
21
+
22
+ /** The three layers, in the order the section lists them. */
23
+ export const USER_CONTEXT_LAYERS = Object.freeze(['knowledge', 'nudges', 'trace']);
24
+
25
+ const clean = (value, max = 200) => {
26
+ const text = String(value ?? '').replace(/\s+/g, ' ').trim();
27
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
28
+ };
29
+
30
+ /**
31
+ * What the server answered.
32
+ *
33
+ * ready the engine is there: `payload` is { settings, capabilities, preview }
34
+ * old-server 404: the running server was started before the engine existed (public/ reloads at any
35
+ * time, lib/ only on a restart)
36
+ * failed 503 USER_CONTEXT_UNAVAILABLE: the engine did not start at boot; `reason` is the error
37
+ * unreadable anything else (no answer, a refusal, a body that is not the payload); `reason` says what
38
+ *
39
+ * @param {{ status?: number, body?: object|null, error?: string|null }} response
40
+ * @returns {{ state: 'ready'|'old-server'|'failed'|'unreadable', payload: object|null, reason: string|null }}
41
+ */
42
+ export function readUserContextResponse({ status = 0, body = null, error = null } = {}) {
43
+ if (status === 200 && body?.ok === true && body.settings && typeof body.settings === 'object') return { state: 'ready', payload: body, reason: null };
44
+ if (status === 404) return { state: 'old-server', payload: null, reason: null };
45
+ if (status === 503 && body?.code === 'USER_CONTEXT_UNAVAILABLE') return { state: 'failed', payload: null, reason: clean(body.reason) || null };
46
+ return { state: 'unreadable', payload: null, reason: clean(body?.error || error) || (status ? `HTTP ${status}` : null) };
47
+ }
48
+
49
+ /** The settings as booleans, the way the engine reads them: anything that is not exactly `false` is on. */
50
+ const layersOf = (settings) => Object.fromEntries(USER_CONTEXT_LAYERS.map((key) => [key, settings?.[key] !== false]));
51
+
52
+ // Why learning cannot run → the sentence, and whether Settings → AI decisions is where to fix it.
53
+ const LEARNING_REASONS = {
54
+ 'kill-switch': { key: 'learn.killSwitch', link: false },
55
+ 'master-off': { key: 'learn.masterOff', link: 'jv-sec-master' },
56
+ 'no-key': { key: 'learn.noKey', link: 'jv-sec-connection' },
57
+ 'surface-off': { key: 'learn.surfaceOff', link: 'jv-sec-surfaces' },
58
+ waiting: { key: 'learn.waiting', link: false },
59
+ };
60
+
61
+ /**
62
+ * The section as it is drawn.
63
+ *
64
+ * `note`: one line above the switches when they cannot be used (and why), else null.
65
+ * `layers[key]`: `on` (the stored value; false while it cannot be read), `disabled`, and `notes`: what stands
66
+ * in the way of that layer right now, each `{ text, tone: 'warn'|'info', link }` where `link` is
67
+ * `{ label, target }` (a Settings deep link) or null.
68
+ * `applies`: the line that says when a change takes effect, or ''.
69
+ * `digest`: `shown` (false without the engine), `state` 'in-use' | 'empty' | 'off', the `count` text, the `rows`
70
+ * to list (none while off), the `message` shown in place of rows (or ''), and the `omitted` line (or '').
71
+ *
72
+ * @param {{ state: string, payload: object|null, reason: string|null }} reading
73
+ * @param {{ tx: (key: string, params?: object) => string }} deps
74
+ */
75
+ export function describeUserContext(reading, { tx }) {
76
+ const state = reading?.state || 'unreadable';
77
+ const ready = state === 'ready';
78
+ const page = tx('settings.redesign.page.ai_decisions.title');
79
+ const view = {
80
+ state, ready, note: null, applies: ready ? tx(`${K}.applies`) : '',
81
+ layers: Object.fromEntries(USER_CONTEXT_LAYERS.map((key) => [key, { on: false, disabled: !ready, notes: [] }])),
82
+ digest: { shown: false, state: 'off', count: '', rows: [], message: '', omitted: '' },
83
+ };
84
+
85
+ if (!ready) {
86
+ const reason = clean(reading?.reason);
87
+ if (state === 'old-server') view.note = { text: tx(`${K}.restart`), tone: 'warn' };
88
+ else if (state === 'failed') view.note = { text: reason ? tx(`${K}.failed`, { reason }) : tx(`${K}.failedNoReason`), tone: 'warn' };
89
+ else view.note = { text: reason ? tx(`${K}.unreadable`, { reason }) : tx(`${K}.unreadableNoReason`), tone: 'warn' };
90
+ return view;
91
+ }
92
+
93
+ const { settings, capabilities, preview } = reading.payload;
94
+ const on = layersOf(settings);
95
+ const caps = capabilities && typeof capabilities === 'object' ? capabilities : {};
96
+ for (const key of USER_CONTEXT_LAYERS) view.layers[key].on = on[key];
97
+
98
+ // Without the memory store nothing is read, learned or recorded: both layers that live in it say so.
99
+ const noStore = caps.store === false;
100
+ if (noStore) {
101
+ if (on.knowledge) view.layers.knowledge.notes.push({ text: tx(`${K}.noStore`), tone: 'warn', link: null });
102
+ if (on.nudges) view.layers.nudges.notes.push({ text: tx(`${K}.noStore`), tone: 'warn', link: null });
103
+ }
104
+ // Learning needs Jev on the `user-learning` surface; the digest does not.
105
+ if (on.knowledge && !noStore && caps.jev === false) {
106
+ const known = LEARNING_REASONS[caps.jevReason] || { key: 'learn.unavailable', link: false };
107
+ view.layers.knowledge.notes.push({
108
+ text: tx(`${K}.${known.key}`, { page }), tone: 'warn',
109
+ link: known.link ? { label: tx(`${K}.openPage`, { page }), target: { tab: 'ai-decisions', scrollTo: known.link } } : null,
110
+ });
111
+ }
112
+ // The statement writer: without it (or when its last one-shot failed) a statement stays in the user's own words.
113
+ if (on.knowledge && !noStore && caps.jev !== false) {
114
+ if (caps.writer === false) view.layers.knowledge.notes.push({ text: tx(`${K}.writer.missing`), tone: 'info', link: null });
115
+ else if (caps.writerLast && caps.writerLast.ok === false) {
116
+ const reason = clean(caps.writerLast.reason, 120);
117
+ view.layers.knowledge.notes.push({ text: reason ? tx(`${K}.writer.failed`, { reason }) : tx(`${K}.writer.failedNoReason`), tone: 'info', link: null });
118
+ }
119
+ }
120
+ // The trace is a line about the two layers: with both off the engine emits nothing.
121
+ if (on.trace && !on.knowledge && !on.nudges) view.layers.trace.notes.push({ text: tx(`${K}.trace.nothing`), tone: 'info', link: null });
122
+
123
+ // The digest: what a new conversation is told, which is nothing while the layer is off.
124
+ const rows = Array.isArray(preview?.rows) ? preview.rows.filter((row) => row && row.id && row.statement) : [];
125
+ const omitted = Number(preview?.omitted) > 0 ? Math.round(Number(preview.omitted)) : 0;
126
+ view.digest.shown = true;
127
+ if (!on.knowledge) {
128
+ view.digest.state = 'off';
129
+ view.digest.count = tx('settings.redesign.common.off');
130
+ view.digest.message = tx(`${K}.off.note`);
131
+ } else {
132
+ view.digest.state = rows.length ? 'in-use' : 'empty';
133
+ view.digest.count = tx(`${K}.tokens`, { tokens: Math.max(0, Math.round(Number(preview?.tokens) || 0)), max: Math.round(Number(preview?.maxTokens) || 300) });
134
+ view.digest.rows = rows.map((row) => ({ id: String(row.id), statement: String(row.statement), facet: String(row.facet || ''), seed: row.seed === true }));
135
+ view.digest.message = rows.length ? '' : tx(`${K}.empty`);
136
+ view.digest.omitted = omitted > 0 ? tx(`${K}.omitted`, { n: omitted }) : '';
137
+ }
138
+ return view;
139
+ }
140
+
141
+ /**
142
+ * A switch that could not be saved: it goes back to where it was, and this is the sentence that says so.
143
+ *
144
+ * @param {{ name: string, wanted: boolean, status?: number, body?: object|null, error?: string|null }} input
145
+ * `name` is the switch's label, `wanted` what the click asked for
146
+ * @returns {string}
147
+ */
148
+ export function describeUserContextSaveFailure({ name, wanted, status = 0, body = null, error = null } = {}, { tx }) {
149
+ let reason = clean(body?.error || body?.message, 160);
150
+ if (!reason && status === 404) reason = tx(`${K}.save.oldServer`);
151
+ if (!reason && status) reason = `HTTP ${status}`;
152
+ if (!reason) reason = clean(error, 160) || tx(`${K}.save.noAnswer`);
153
+ return tx(`${K}.save.${wanted ? 'on' : 'off'}`, { name, reason });
154
+ }
@@ -0,0 +1,489 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Showcase privacy
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // Loaded only in showcase mode (`?showcase`, storage.js isShowcaseMode) by the
6
+ // main entry points, for product screenshots: the feature stays sharp, the
7
+ // user's own content is blurred (a CSS filter; no text is removed or rewritten).
8
+ //
9
+ // - Wholesale, by selector: the surfaces that always hold the user's content
10
+ // (memory text, session titles, transcripts, terminal output, Resume results,
11
+ // account names, toast text). Pure CSS, so a node is blurred the moment it
12
+ // is drawn.
13
+ // - By what it says: any element whose own visible text, value or placeholder
14
+ // matches a private pattern (emails, home paths, ids, keys, phone numbers,
15
+ // the names of the user's other projects, their OS user name...). A
16
+ // debounced MutationObserver looks only at added or changed nodes.
17
+ // - Same-origin frames (detached side-panel sessions) get the same treatment.
18
+ //
19
+ // Canvas text cannot be blurred: the 3D map's labels (#map-labels) and the 2D
20
+ // map (#canvas-main) are drawn on canvases; blur("#map-labels") blurs all of
21
+ // them at once.
22
+ //
23
+ // window.__synabunShowcase = { scan, blur, unblur, stats }.
24
+
25
+ const BLUR_CLASS = 'sb-showcase-blur';
26
+ const MANUAL_CLASS = 'sb-showcase-manual';
27
+ const CLEAR_ATTR = 'data-sb-showcase-clear';
28
+ const STYLE_ID = 'sb-showcase-style';
29
+ const BLUR = 'blur(7px)';
30
+ const DEBOUNCE_MS = 120;
31
+ // Private text split over a few spans (a path in pieces): a parent holding at
32
+ // most this much text is read as one, up to this many levels up.
33
+ const SPAN_MAX = 160;
34
+ const SPAN_LEVELS = 3;
35
+
36
+ /** Surfaces that always hold the user's own content (class names from the shared UI modules). */
37
+ export const WHOLESALE_SELECTORS = [
38
+ // Memories: detail card, map hover title, Memory Explorer rows, bookmarks, trash
39
+ '.detail-card [data-role="content"]', '.detail-card .md-textarea', '.detail-card .tag-text',
40
+ '.detail-card [data-role="files-content"]', '.detail-card [data-role="meta-content"] .detail-meta-row > span:last-child',
41
+ '#tooltip-preview', '.explorer-memory .explorer-label', '.dropdown-item-title',
42
+ '.tw-item-preview', '.tw-detail-body', '.tw-detail-tags', '.tw-detail-file',
43
+ // Session pills, windows and tabs, with their titles
44
+ '.term-minimized-pill-label', '.sp-session-title', '.asp-title',
45
+ '.cp-session-label', '.cp-sess-prompt', '.cp-sess-meta',
46
+ '#cxp-session-label', '.cxp-sess-prompt', '.cxp-sess-meta',
47
+ '.ocpv2-session-label', '.ocpv2-session-item[data-tooltip] .ocpv2-session-item-label', '.ocpv2-session-item-meta', '.ocpv2-session-share-url',
48
+ '.term-tab-label', '.term-float-tab-title', '.peek-tab-label', '.sm-card-project', '.sm-card-val',
49
+ // Claude Code side panel transcript
50
+ '.msg-user .msg-bubble', '.msg-assistant .msg-body', '.msg-thinking-content', '.tool-detail', '.tool-section', '.tool-result-content',
51
+ '.cp-bash-cmd', '.cp-bash-desc', '.cp-bash-out', '.cp-queue-text', '.cp-todo-txt', '.cp-task-title',
52
+ // Codex side panel transcript
53
+ '.cxp-msg-user-text', '.cxp-msg-body', '.cxp-reasoning-body', '.cxp-think-detail',
54
+ '.cxp-card-subtitle', '.cxp-card-body', '.cxp-fold-body', '.cxp-queue-item-text',
55
+ // OpenCode side panel transcript
56
+ '.ocpv2-part-text', '.ocpv2-reasoning-body', '.ocpv2-tc-summary', '.ocpv2-tc-meta', '.ocpv2-tc-body',
57
+ '.ocpv2-tool-input', '.ocpv2-tool-output', '.ocpv2-sb-summary',
58
+ // Assistant transcript, run and worker cards
59
+ '.asst-msg-text', '.asst-md', '.asst-thinking-content', '.asst-run-title', '.asst-run-summary', '.asst-run-escalate-reason',
60
+ '.asst-tool-detail', '.asst-tool-body', '.asst-tool-section', '.asst-tl-said', '.asst-tl-subject', '.asst-bubble-text',
61
+ '.asst-station-sub', '.asst-station-log', '.asst-auto-title', '.asst-auto-summary', '.asst-auto-detail',
62
+ // Composers, once they hold text (an empty one keeps its placeholder sharp)
63
+ '#cp-input:not(:placeholder-shown)', '#cxp-input:not(:placeholder-shown)', '.ocpv2-compose-input:not(:placeholder-shown)',
64
+ '.asst-input:not(:placeholder-shown)',
65
+ // Terminal output (the WebGL canvas and the DOM renderer alike) and the terminal's browser tab
66
+ '.term-viewport .xterm-screen', '.term-float-tab-body .xterm', '.browser-canvas', '.browser-url-input', '.browser-title-label',
67
+ // Resume results
68
+ '.resume-session-prompt', '.resume-session-snippet', '.resume-continue-prompt', '.resume-project-name',
69
+ '.resume-project-badge', '.resume-branch',
70
+ // Account emails and names
71
+ '.acct-menu-trigger-name', '.acct-menu-item-name', '.acct-menu-item-id', '.acct-menu-avatar', '.acct-menu-note', '.acct-menu-v',
72
+ '.asst-account-label', '.asst-account-sub', '.asst-account-cred', '.asst-model-account', '.as-launch-account-name',
73
+ '.invite-key-box', '.invite-url-text', '.invite-key-status',
74
+ // Toast text
75
+ '.notif-toast-msg', '.term-toast', '#fe-toast', '.as-toast', '.ss-toast', '.cc-toast', '.asp-toast',
76
+ // Workspace names, generated image names
77
+ '.ws-row-name', '.ws-row-meta', '#ws-active-name', '.ig-card-name', '.ig-lb-name',
78
+ ];
79
+ const WHOLESALE = WHOLESALE_SELECTORS.join(',');
80
+
81
+ // The shared tooltip element shows a memory or session title for some rows and
82
+ // a control's name for the rest: it is blurred when what it describes is private.
83
+ const TOOLTIP_TEXT = '.ui-tooltip-text';
84
+
85
+ // ── Patterns ──────────────────────────────────────────────────────
86
+
87
+ const PHONE_PATTERNS = [
88
+ /(?:^|[^\w+])\+\d{1,3}[\s.-]?\(?\d{1,4}\)?(?:[\s.-]?\d{2,5}){2,4}\b/, // +55 11 9####-####, +1 415 555 0100
89
+ /\(\d{2,3}\)\s?\d{4,5}[\s.-]\d{4}\b/, // (11) 91234-5678
90
+ /\b\d{2}\s9\d{4}[\s-]\d{4}\b/, // 11 91234-5678
91
+ /\b\d{3}[\s.-]\d{3}[\s.-]\d{4}\b/, // 415-555-0100
92
+ ];
93
+
94
+ /**
95
+ * Patterns that name a checkout's own private work stay out of the published code: a checkout
96
+ * may list them in showcase-private-patterns.json beside this file, as [reason, source, flags,
97
+ * ownOnly] rows. A release has none, and the page's one request for it answers 404.
98
+ */
99
+ export function patternsFromRows(rows) {
100
+ return (Array.isArray(rows) ? rows : []).flatMap((row) => {
101
+ if (!Array.isArray(row) || typeof row[0] !== 'string' || typeof row[1] !== 'string') return [];
102
+ try { return [[row[0], new RegExp(row[1], typeof row[2] === 'string' ? row[2] : ''), row[3] === true]]; } catch { return []; }
103
+ });
104
+ }
105
+ const LOCAL_PATTERNS = typeof window === 'undefined' ? [] : await fetch(new URL('./showcase-private-patterns.json', import.meta.url))
106
+ .then((res) => (res.ok ? res.json() : []))
107
+ .then(patternsFromRows, () => []);
108
+
109
+ /**
110
+ * What the matcher looks for, besides the names it is given: [reason, test, ownOnly].
111
+ * ownOnly tests are skipped on the joined text of a few spans, where labels and numbers
112
+ * that sit side by side would read as one long key or a phone number.
113
+ */
114
+ export const PRIVATE_PATTERNS = [
115
+ ['email', /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}/],
116
+ ['home-path', /(?:^|[^\w.-])\/(?:Users|home)\/[^/\s]+|\b[A-Za-z]:\\Users\\[^\\\s]+/],
117
+ ['uuid', /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i],
118
+ ['secret', /\b(?:sk|pk|rk)[-_][A-Za-z0-9_-]{12,}|\bgh[pousr]_[A-Za-z0-9]{16,}|\bgithub_pat_[A-Za-z0-9_]{20,}|\bxox[a-z]-[A-Za-z0-9-]{8,}|\bAIza[0-9A-Za-z_-]{20,}/],
119
+ ['secret', (text) => (text.match(/[A-Za-z0-9+=]{24,}/g) || []).some((run) => (run.match(/\d/g) || []).length >= 3 && (run.match(/[A-Za-z]/g) || []).length >= 3), true],
120
+ ['phone', (text) => PHONE_PATTERNS.some((re) => re.test(text)), true],
121
+ ...LOCAL_PATTERNS,
122
+ ];
123
+
124
+ const SELF_PROJECT = 'synabun';
125
+ const GENERIC_USER_NAMES = new Set(['user', 'users', 'admin', 'root', 'shared', 'home', 'guest', 'test', 'public']);
126
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
127
+ const squash = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9]/g, '');
128
+
129
+ /** A path's last folder name ('/a/b/' → 'b', 'C:\\x\\y' → 'y'). */
130
+ function baseName(path) {
131
+ const parts = String(path || '').split(/[\\/]+/).filter(Boolean);
132
+ return parts.length ? parts[parts.length - 1] : '';
133
+ }
134
+
135
+ /**
136
+ * The names a registered-project list gives away: every project's label and folder
137
+ * name except SynaBun's own, and the OS user name read from the home folder in the paths.
138
+ * @param {Array<{path?: string, label?: string}>} projects GET /api/projects → projects
139
+ */
140
+ export function namesFromProjects(projects = []) {
141
+ const projectNames = new Set();
142
+ const userNames = new Set();
143
+ for (const p of Array.isArray(projects) ? projects : []) {
144
+ const path = String(p?.path || '');
145
+ const label = String(p?.label || '');
146
+ for (const name of [/[\\/]/.test(label) ? baseName(label) : label, baseName(path)]) {
147
+ const key = squash(name);
148
+ if (key.length >= 4 && key !== SELF_PROJECT) projectNames.add(name.trim());
149
+ }
150
+ const home = path.match(/^\/(?:Users|home)\/([^/]+)/) || path.match(/^[A-Za-z]:\\Users\\([^\\]+)/i);
151
+ if (home && home[1].length >= 3 && !GENERIC_USER_NAMES.has(home[1].toLowerCase())) userNames.add(home[1]);
152
+ }
153
+ return { projectNames: [...projectNames], userNames: [...userNames] };
154
+ }
155
+
156
+ /**
157
+ * A name as a pattern: its words (camelCase humps, '-', '_', '.', spaces) may be joined by
158
+ * '-', '_' or '.' or nothing ('SynabunWeb' finds "synabun-web"), never inside a longer word.
159
+ * A plain one-word name ('Downloads') must match its case, so ordinary prose stays clear.
160
+ */
161
+ function namePattern(name) {
162
+ const words = String(name).trim().split(/[\s_.-]+|(?<=[a-z0-9])(?=[A-Z])/).filter(Boolean);
163
+ if (!words.length) return null;
164
+ const plainWord = words.length === 1 && !/[A-Z].*[A-Z]/.test(name.slice(1));
165
+ const body = words.map(escapeRe).join('[_.-]?');
166
+ return new RegExp(`(?<![A-Za-z0-9])${body}(?![A-Za-z0-9])`, plainWord ? '' : 'i');
167
+ }
168
+
169
+ /**
170
+ * The private-text test. Returns the reason ('email', 'project-name'...) or null.
171
+ * @param {{projectNames?: string[], userNames?: string[], extraPatterns?: Array}} names extraPatterns: rows of patternsFromRows (the page's own are already in PRIVATE_PATTERNS)
172
+ */
173
+ export function createPrivacyMatcher({ projectNames = [], userNames = [], extraPatterns = [] } = {}) {
174
+ const named = [
175
+ ...projectNames.filter((n) => squash(n) !== SELF_PROJECT).map((n) => ['project-name', namePattern(n)]),
176
+ ...userNames.map((n) => ['user-name', new RegExp(`(?<![A-Za-z0-9])${escapeRe(n)}(?![A-Za-z0-9])`, 'i')]),
177
+ ].filter(([, re]) => re);
178
+ const checks = [...PRIVATE_PATTERNS, ...extraPatterns, ...named];
179
+ /** @param {{ joined?: boolean }} [options] joined: the text of several spans run together */
180
+ return function privateReason(text, { joined = false } = {}) {
181
+ const value = String(text ?? '');
182
+ if (value.trim().length < 3) return null;
183
+ for (const [reason, test, ownOnly] of checks) {
184
+ if (joined && ownOnly) continue;
185
+ if (typeof test === 'function' ? test(value) : test.test(value)) return reason;
186
+ }
187
+ return null;
188
+ };
189
+ }
190
+
191
+ // ── What an element shows ─────────────────────────────────────────
192
+
193
+ const SKIP_TAGS = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE', 'HTML', 'HEAD', 'BODY', 'IFRAME', 'CANVAS']);
194
+ const TEXTLESS_INPUTS = new Set(['hidden', 'checkbox', 'radio', 'range', 'color', 'file', 'image', 'submit', 'reset', 'button']);
195
+
196
+ /** The text of an element's own text nodes (not its children's). */
197
+ export function ownText(el) {
198
+ let text = '';
199
+ for (const node of el?.childNodes || []) if (node.nodeType === 3) text += node.data;
200
+ return text;
201
+ }
202
+
203
+ /** What a form field shows: its value (never a password's) and placeholder; a select's chosen option. */
204
+ export function fieldTexts(el) {
205
+ const tag = el?.tagName;
206
+ if (tag === 'SELECT') return [...(el.selectedOptions || [])].map((o) => o.textContent || '');
207
+ if (tag !== 'INPUT' && tag !== 'TEXTAREA') return [];
208
+ const type = String(el.type || 'text').toLowerCase();
209
+ if (tag === 'INPUT' && TEXTLESS_INPUTS.has(type)) return [];
210
+ return [type === 'password' ? '' : String(el.value || ''), String(el.placeholder || '')];
211
+ }
212
+
213
+ /** Why an element's own text, value or placeholder is private, or null. */
214
+ export function elementReason(el, matcher) {
215
+ if (!el || el.nodeType !== 1 || SKIP_TAGS.has(el.tagName)) return null;
216
+ return matcher([ownText(el), ...fieldTexts(el)].filter(Boolean).join('\n'));
217
+ }
218
+
219
+ // ── The page side ─────────────────────────────────────────────────
220
+
221
+ function styleText() {
222
+ // One rule per selector: a selector one engine rejects drops only its own rule.
223
+ return [...WHOLESALE_SELECTORS, `.${BLUR_CLASS}`, `.${MANUAL_CLASS}`]
224
+ .map((sel) => `${sel} { filter: ${BLUR} !important; }`).join('\n');
225
+ }
226
+
227
+ /**
228
+ * Turn showcase privacy on for this window (and its same-origin frames). Idempotent.
229
+ * @param {{ win?: Window, fetchProjects?: () => Promise<Array<{path:string,label:string}>> }} [options]
230
+ */
231
+ export function installShowcasePrivacy({ win = globalThis.window, fetchProjects } = {}) {
232
+ if (win.__synabunShowcase) return win.__synabunShowcase;
233
+ const docs = new Set();
234
+ let matcher = createPrivacyMatcher();
235
+ let namesLoaded = false;
236
+ let nameCount = 0;
237
+ let scans = 0;
238
+ let lastScanMs = 0;
239
+
240
+ const isCleared = (el) => el.hasAttribute(CLEAR_ATTR);
241
+ const setBlur = (el, on) => { if (el.classList.contains(BLUR_CLASS) !== on) el.classList.toggle(BLUR_CLASS, on); };
242
+
243
+ function hoverIsPrivate(doc, state) {
244
+ const hovered = state.lastHover;
245
+ if (!hovered || !hovered.isConnected) return false;
246
+ try {
247
+ if (hovered.closest(WHOLESALE) || hovered.querySelector(WHOLESALE)) return true;
248
+ } catch { /* a selector this engine rejects */ }
249
+ return !!matcher(hovered.getAttribute('data-tooltip') || '');
250
+ }
251
+
252
+ /** Is this element private by what it shows (or, for the tooltip, by what it describes)? */
253
+ function isPrivate(el, doc, state) {
254
+ if (isCleared(el)) return false;
255
+ if (elementReason(el, matcher)) return true;
256
+ return el.matches?.(TOOLTIP_TEXT) && hoverIsPrivate(doc, state);
257
+ }
258
+
259
+ // A small element's whole text (null for a container): a parent with many children is a list, not a split label.
260
+ function smallText(node, cache) {
261
+ if (cache?.has(node)) return cache.get(node);
262
+ let text = null;
263
+ if (node.childElementCount <= 12 && !SKIP_TAGS.has(node.tagName)) {
264
+ text = node.textContent || '';
265
+ if (text.length > SPAN_MAX) text = null;
266
+ }
267
+ cache?.set(node, text);
268
+ return text;
269
+ }
270
+
271
+ // Private text split over spans: the nearest small ancestor whose whole text is private.
272
+ function spanningAncestor(el, cache) {
273
+ let node = el?.parentElement;
274
+ for (let level = 0; node && level < SPAN_LEVELS; level++, node = node.parentElement) {
275
+ if (isCleared(node)) return null;
276
+ const text = smallText(node, cache);
277
+ if (text === null) return null;
278
+ if (matcher(text, { joined: true })) return node;
279
+ }
280
+ return null;
281
+ }
282
+
283
+ /** Still private, for an element that carries the blur class: by its own text or as a span hit. */
284
+ function stillPrivate(el, doc, state) {
285
+ if (isPrivate(el, doc, state)) return true;
286
+ const text = isCleared(el) ? null : smallText(el);
287
+ return text !== null && !!matcher(text, { joined: true });
288
+ }
289
+
290
+ /** The elements that should carry the blur class: root alone, or root and everything under it. */
291
+ function collect(root, doc, state, into, { walk = true } = {}) {
292
+ const cache = new Map();
293
+ const consider = (el) => {
294
+ if (!el || el.nodeType !== 1 || isCleared(el)) return;
295
+ if (isPrivate(el, doc, state)) { into.add(el); return; }
296
+ const up = ownText(el).trim() ? spanningAncestor(el, cache) : null;
297
+ if (up) into.add(up);
298
+ };
299
+ if (root.nodeType === 3) { consider(root.parentElement); return into; }
300
+ if (root.nodeType !== 1 && root.nodeType !== 9) return into;
301
+ const start = root.nodeType === 9 ? root.body : root;
302
+ if (!start) return into;
303
+ if (start.nodeType === 1) consider(start);
304
+ if (!walk) return into;
305
+ const walker = doc.createTreeWalker(start, 1 /* SHOW_ELEMENT */);
306
+ for (let el = walker.nextNode(); el; el = walker.nextNode()) consider(el);
307
+ return into;
308
+ }
309
+
310
+ function attachFrames(root) {
311
+ const frames = root.nodeType === 1 && root.tagName === 'IFRAME' ? [root] : [...(root.querySelectorAll?.('iframe') || [])];
312
+ for (const frame of frames) {
313
+ if (frame.__sbShowcase) continue;
314
+ frame.__sbShowcase = true;
315
+ const tryAttach = () => {
316
+ try {
317
+ const doc = frame.contentDocument;
318
+ if (doc && doc.readyState !== 'uninitialized' && doc.location?.href !== 'about:blank') attachDocument(doc);
319
+ } catch { /* another origin: nothing to reach */ }
320
+ };
321
+ frame.addEventListener('load', tryAttach);
322
+ tryAttach();
323
+ }
324
+ }
325
+
326
+ function attachDocument(doc) {
327
+ if (!doc || doc.__sbShowcaseState) return;
328
+ // pending: added nodes (walked); touched: elements whose own content changed (checked alone).
329
+ const state = { doc, pending: new Set(), touched: new Set(), timer: null, lastHover: null, observer: null };
330
+ doc.__sbShowcaseState = state;
331
+ docs.add(doc);
332
+ const style = doc.createElement('style');
333
+ style.id = STYLE_ID;
334
+ style.textContent = styleText();
335
+ (doc.head || doc.documentElement).appendChild(style);
336
+
337
+ const flush = () => {
338
+ state.timer = null;
339
+ const added = [...state.pending];
340
+ const touched = [...state.touched];
341
+ state.pending.clear();
342
+ state.touched.clear();
343
+ const hits = new Set();
344
+ const seen = new Set();
345
+ const note = (node) => {
346
+ // What lost its private text loses the blur: the node and the small ancestors a span hit may sit on.
347
+ let el = node.nodeType === 1 ? node : node.parentElement;
348
+ for (let level = 0; el && level <= SPAN_LEVELS; level++, el = el.parentElement) seen.add(el);
349
+ };
350
+ for (const node of added) {
351
+ if (!node.isConnected) continue;
352
+ collect(node, doc, state, hits);
353
+ note(node);
354
+ if (node.nodeType === 1) {
355
+ attachFrames(node);
356
+ for (const marked of node.querySelectorAll(`.${BLUR_CLASS}`)) seen.add(marked);
357
+ }
358
+ }
359
+ for (const node of touched) {
360
+ if (!node.isConnected) continue;
361
+ collect(node, doc, state, hits, { walk: false });
362
+ note(node);
363
+ }
364
+ for (const el of seen) if (el.classList?.contains(BLUR_CLASS) && !hits.has(el) && !stillPrivate(el, doc, state)) setBlur(el, false);
365
+ for (const el of hits) setBlur(el, true);
366
+ };
367
+ const schedule = () => { if (!state.timer) state.timer = doc.defaultView.setTimeout(flush, DEBOUNCE_MS); };
368
+ const queue = (node) => { if (node) { state.pending.add(node); schedule(); } };
369
+ const touch = (node) => { if (node) { state.touched.add(node); schedule(); } };
370
+
371
+ state.observer = new doc.defaultView.MutationObserver((records) => {
372
+ for (const r of records) {
373
+ if (r.type === 'childList') {
374
+ for (const n of r.addedNodes) queue(n);
375
+ if (r.removedNodes.length) touch(r.target);
376
+ } else {
377
+ touch(r.target);
378
+ }
379
+ }
380
+ });
381
+ state.observer.observe(doc.documentElement, {
382
+ childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['value', 'placeholder'],
383
+ });
384
+ const onField = (e) => { if (e.target?.nodeType === 1) touch(e.target); };
385
+ doc.addEventListener('input', onField, true);
386
+ doc.addEventListener('change', onField, true);
387
+ doc.addEventListener('mouseover', (e) => {
388
+ state.lastHover = e.target?.closest?.('[data-tooltip]') || null;
389
+ }, true);
390
+
391
+ scanDocument(doc);
392
+ attachFrames(doc);
393
+ }
394
+
395
+ function liveDocs() {
396
+ for (const doc of docs) if (!doc.defaultView || !doc.documentElement?.isConnected) docs.delete(doc);
397
+ return [...docs];
398
+ }
399
+
400
+ function scanDocument(doc) {
401
+ const state = doc.__sbShowcaseState;
402
+ const hits = collect(doc, doc, state, new Set());
403
+ for (const el of doc.querySelectorAll(`.${BLUR_CLASS}`)) if (!hits.has(el)) setBlur(el, false);
404
+ for (const el of hits) setBlur(el, true);
405
+ }
406
+
407
+ function blurredIn(doc) {
408
+ const out = new Set();
409
+ for (const sel of [...WHOLESALE_SELECTORS, `.${BLUR_CLASS}`, `.${MANUAL_CLASS}`]) {
410
+ try { for (const el of doc.querySelectorAll(sel)) if (!isCleared(el)) out.add(el); } catch { /* rejected selector */ }
411
+ }
412
+ return out;
413
+ }
414
+
415
+ function each(selector, fn) {
416
+ if (typeof selector !== 'string' || !selector.trim()) throw new Error('showcase: a CSS selector is needed');
417
+ let count = 0;
418
+ for (const doc of liveDocs()) {
419
+ let found;
420
+ try { found = doc.querySelectorAll(selector); } catch { throw new Error(`showcase: invalid selector ${selector}`); }
421
+ for (const el of found) { fn(el); count++; }
422
+ }
423
+ return count;
424
+ }
425
+
426
+ const api = Object.freeze({
427
+ /** Scan every page and frame again from scratch; returns how many elements are blurred. */
428
+ scan() {
429
+ const started = Date.now();
430
+ for (const doc of liveDocs()) attachFrames(doc);
431
+ let total = 0;
432
+ for (const doc of liveDocs()) { scanDocument(doc); total += blurredIn(doc).size; }
433
+ scans++;
434
+ lastScanMs = Date.now() - started;
435
+ return total;
436
+ },
437
+ /** Blur what the selector matches (also undoes an unblur); returns how many. */
438
+ blur(selector) {
439
+ return each(selector, (el) => {
440
+ el.removeAttribute(CLEAR_ATTR);
441
+ el.style.removeProperty('filter');
442
+ el.classList.add(MANUAL_CLASS);
443
+ });
444
+ },
445
+ /** Keep what the selector matches sharp, whatever the rules say; returns how many. */
446
+ unblur(selector) {
447
+ return each(selector, (el) => {
448
+ el.classList.remove(BLUR_CLASS, MANUAL_CLASS);
449
+ el.setAttribute(CLEAR_ATTR, '');
450
+ el.style.setProperty('filter', 'none', 'important');
451
+ });
452
+ },
453
+ /** Counts only: never the private text or names themselves. */
454
+ stats() {
455
+ const all = liveDocs();
456
+ const count = (sel) => all.reduce((n, doc) => n + doc.querySelectorAll(sel).length, 0);
457
+ return {
458
+ documents: all.length,
459
+ blurred: all.reduce((n, doc) => n + blurredIn(doc).size, 0),
460
+ matched: count(`.${BLUR_CLASS}`),
461
+ manual: count(`.${MANUAL_CLASS}`),
462
+ cleared: count(`[${CLEAR_ATTR}]`),
463
+ namesLoaded,
464
+ names: nameCount,
465
+ scans,
466
+ lastScanMs,
467
+ };
468
+ },
469
+ });
470
+ win.__synabunShowcase = api;
471
+
472
+ attachDocument(win.document);
473
+
474
+ // The registered projects name the user's other work and, by their paths, the OS user.
475
+ const load = fetchProjects || (async () => {
476
+ const res = await win.fetch('/api/projects');
477
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
478
+ return (await res.json())?.projects || [];
479
+ });
480
+ Promise.resolve().then(load).then((projects) => {
481
+ const names = namesFromProjects(projects);
482
+ matcher = createPrivacyMatcher(names);
483
+ nameCount = names.projectNames.length + names.userNames.length;
484
+ namesLoaded = true;
485
+ api.scan();
486
+ }).catch((err) => console.warn('[showcase] project names unavailable, pattern blur only:', err?.message || err));
487
+
488
+ return api;
489
+ }