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.
- package/CHANGELOG.md +97 -0
- package/NOTICE +5 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +59 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +153 -18
- package/hooks/claude-code/pre-compact.mjs +11 -0
- package/hooks/claude-code/prompt-submit.mjs +60 -184
- package/hooks/claude-code/session-start.mjs +73 -16
- package/hooks/claude-code/shared.mjs +26 -3
- package/hooks/claude-code/stop.mjs +5 -5
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/packaged-runtime.js +138 -11
- package/lib/start-launcher.js +88 -1
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -7
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +34 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- package/mcp-server/dist/tui.js +2 -1
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +33 -14
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tui.ts +2 -1
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +2126 -209
- package/neural-interface/i18n/pt-BR.json +2123 -207
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/assistant-api.js +316 -57
- package/neural-interface/lib/assistant-brains/claude.js +19 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +91 -14
- package/neural-interface/lib/assistant-dispatch.js +494 -138
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +73 -12
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +488 -59
- package/neural-interface/lib/assistant-task-prompt.js +13 -3
- package/neural-interface/lib/assistant-test-density.js +142 -0
- package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
- package/neural-interface/lib/assistant-user-context.js +908 -0
- package/neural-interface/lib/assistant-user-knowledge.js +328 -0
- package/neural-interface/lib/assistant-user-learn.js +404 -0
- package/neural-interface/lib/assistant-user-threads.js +226 -0
- package/neural-interface/lib/assistant-user-writer.js +183 -0
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +123 -9
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +33 -5
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/claude-temporary.js +46 -7
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -7
- package/neural-interface/lib/cloudflared/install.js +533 -0
- package/neural-interface/lib/cloudflared/manager.js +545 -0
- package/neural-interface/lib/cloudflared/pins.js +115 -0
- package/neural-interface/lib/cloudflared/pins.json +63 -0
- package/neural-interface/lib/cloudflared/tunnel.js +265 -0
- package/neural-interface/lib/codex-account-auth.js +178 -0
- package/neural-interface/lib/codex-accounts.js +489 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +120 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/editor-completion-endpoint.js +264 -0
- package/neural-interface/lib/editor-completion-opencode.js +436 -0
- package/neural-interface/lib/editor-completion.js +714 -0
- package/neural-interface/lib/editor-config.js +149 -0
- package/neural-interface/lib/editor-files-api.js +268 -0
- package/neural-interface/lib/editor-git-api.js +134 -0
- package/neural-interface/lib/file-explorer-api.js +1039 -0
- package/neural-interface/lib/file-explorer-system.js +355 -0
- package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
- package/neural-interface/lib/file-explorer-watch.js +239 -0
- package/neural-interface/lib/git-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +234 -0
- package/neural-interface/lib/greeting/api.js +89 -0
- package/neural-interface/lib/greeting/budgets.js +90 -0
- package/neural-interface/lib/greeting/engine.js +461 -0
- package/neural-interface/lib/greeting/facts.js +320 -0
- package/neural-interface/lib/greeting/sources.js +252 -0
- package/neural-interface/lib/greeting/writer.js +139 -0
- package/neural-interface/lib/http-guards.js +189 -13
- package/neural-interface/lib/install-details.js +145 -0
- package/neural-interface/lib/machine-names.js +88 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-loop-providers.js +26 -8
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/session-title-generator.js +4 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +701 -2
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/install-app.html +9 -6
- package/neural-interface/public/offline.html +19 -3
- package/neural-interface/public/onboarding.html +107 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +539 -0
- package/neural-interface/public/shared/account-usage.js +19 -0
- package/neural-interface/public/shared/account-view.js +335 -0
- package/neural-interface/public/shared/agent-runtime-options.js +50 -14
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +32 -2
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
- package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
- package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
- package/neural-interface/public/shared/assistant/asst-render.js +197 -23
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +95 -343
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +186 -2
- package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
- package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +61 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -0
- package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
- package/neural-interface/public/shared/editor/editor-ai.js +619 -0
- package/neural-interface/public/shared/editor/editor-commands.js +212 -0
- package/neural-interface/public/shared/editor/editor-compare.js +103 -0
- package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
- package/neural-interface/public/shared/editor/editor-doc.js +257 -0
- package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
- package/neural-interface/public/shared/editor/editor-git.js +95 -0
- package/neural-interface/public/shared/editor/editor-languages.js +156 -0
- package/neural-interface/public/shared/editor/editor-lint.js +229 -0
- package/neural-interface/public/shared/editor/editor-loader.js +51 -0
- package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
- package/neural-interface/public/shared/editor/editor-menu.js +233 -0
- package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
- package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
- package/neural-interface/public/shared/editor/editor-palette.js +125 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
- package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
- package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
- package/neural-interface/public/shared/editor/editor-safety.js +500 -0
- package/neural-interface/public/shared/editor/editor-search.js +352 -0
- package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
- package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
- package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
- package/neural-interface/public/shared/editor/editor-theme.js +244 -0
- package/neural-interface/public/shared/editor/editor-view.js +332 -0
- package/neural-interface/public/shared/explorer/fx-api.js +216 -0
- package/neural-interface/public/shared/explorer/fx-links.js +204 -0
- package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
- package/neural-interface/public/shared/explorer/fx-main.js +865 -0
- package/neural-interface/public/shared/explorer/fx-names.js +87 -0
- package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
- package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
- package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
- package/neural-interface/public/shared/file-links.js +339 -0
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
- package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
- package/neural-interface/public/shared/html-shell.js +161 -165
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +32 -2
- package/neural-interface/public/shared/pwa-install.css +3 -3
- package/neural-interface/public/shared/pwa-install.js +100 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -0
- package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
- package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
- package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
- package/neural-interface/public/shared/settings/settings-ia.js +33 -4
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/showcase-privacy.js +489 -0
- package/neural-interface/public/shared/start-bridge.js +238 -19
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +1710 -770
- package/neural-interface/public/shared/synabun-mascot.js +68 -58
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +369 -229
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-detail.js +15 -0
- package/neural-interface/public/shared/ui-explorer.js +22 -0
- package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
- package/neural-interface/public/shared/ui-file-links.js +103 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-help.js +26 -0
- package/neural-interface/public/shared/ui-invite.js +126 -116
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +187 -19
- package/neural-interface/public/shared/ui-menubar.js +25 -33
- package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
- package/neural-interface/public/shared/ui-navbar.js +48 -22
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +509 -490
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +22 -0
- package/neural-interface/public/shared/ui-terminal.js +40 -5
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-tunnel.js +130 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
- package/neural-interface/public/shared/ui-update.js +594 -5
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/ui-whiteboard.js +12 -0
- package/neural-interface/public/shared/ui-worker-end.js +245 -0
- package/neural-interface/public/shared/ui-workspaces.js +41 -27
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +253 -0
- package/neural-interface/public/sw.js +2 -2
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
- package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
- package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
- package/neural-interface/scripts/build-vendor.mjs +78 -2
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +2085 -1629
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +5 -8
- package/setup.js +53 -15
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/neural-interface/lib/codex-capabilities.d.ts +0 -41
- package/neural-interface/public/category-logos/claude.svg +0 -1
- package/neural-interface/public/category-logos/gemini-color.svg +0 -1
- package/neural-interface/public/category-logos/openai.svg +0 -1
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude.svg +0 -1
- package/neural-interface/public/gallery.svg +0 -6
- package/neural-interface/public/leonardoai.svg +0 -1
- package/neural-interface/public/mcp.svg +0 -4
- package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
- package/neural-interface/public/pin-svgrepo-com.svg +0 -25
- package/neural-interface/public/sessionicon.svg +0 -8
- package/neural-interface/public/toolbar-album.svg +0 -8
- package/neural-interface/public/toolbar-question.svg +0 -6
- package/neural-interface/public/toolbar-sessions.svg +0 -6
- package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
- 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
|
+
}
|