synabun 2.0.0-beta → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +51 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +2 -2
- 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 +20 -0
- package/hooks/claude-code/stop.mjs +5 -5
- package/launcher.mjs +40 -0
- package/lib/claude-hooks.js +61 -8
- package/lib/client-config-repair.js +4 -3
- package/lib/data-home-migration.js +4 -0
- package/lib/desktop-pwa.js +206 -0
- package/lib/external-tools.js +221 -0
- package/lib/first-launch-protection.js +5 -0
- package/lib/packaged-runtime.js +110 -0
- package/lib/start-launcher.js +1396 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/neural-interface/i18n/en.json +1540 -154
- package/neural-interface/i18n/pt-BR.json +1538 -153
- package/neural-interface/lib/assistant-api.js +62 -6
- package/neural-interface/lib/assistant-brains/claude.js +4 -0
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +31 -2
- package/neural-interface/lib/assistant-dispatch.js +62 -13
- package/neural-interface/lib/assistant-envelope.js +9 -6
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +6 -2
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-runtime.js +181 -12
- 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 +181 -0
- package/neural-interface/lib/augmented-path.js +23 -3
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-language.js +83 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-agent-bridge.js +87 -153
- package/neural-interface/lib/claude-executable.js +241 -0
- package/neural-interface/lib/claude-model-catalog.js +82 -93
- package/neural-interface/lib/claude-temporary.js +46 -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/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-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +202 -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/machine-names.js +88 -0
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-binary-runtime.js +69 -337
- package/neural-interface/lib/native-loop-providers.js +40 -49
- package/neural-interface/lib/offline-page.js +54 -0
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/session-title-generator.js +5 -0
- package/neural-interface/lib/synabun-update-plan.js +19 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/package-lock.json +701 -253
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/install-app.html +34 -0
- package/neural-interface/public/manifest.json +2 -0
- package/neural-interface/public/offline.html +402 -66
- package/neural-interface/public/onboarding.html +264 -147
- package/neural-interface/public/shared/agent-runtime-options.js +22 -6
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
- package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
- package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
- package/neural-interface/public/shared/assistant/asst-render.js +219 -67
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +23 -329
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -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-state.js +185 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
- package/neural-interface/public/shared/command-runner-model.js +30 -0
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +15 -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/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 +169 -170
- 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 +99 -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 +5 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +62 -0
- package/neural-interface/public/shared/pwa-install.css +12 -0
- package/neural-interface/public/shared/pwa-install.js +127 -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 +18 -4
- package/neural-interface/public/shared/settings/settings-rules.js +160 -0
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/start-bridge.js +802 -0
- package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
- package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
- package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
- package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
- package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
- package/neural-interface/public/shared/styles.css +2219 -1117
- package/neural-interface/public/shared/synabun-mascot.js +20 -56
- package/neural-interface/public/shared/term-ready.js +307 -0
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +160 -19
- package/neural-interface/public/shared/ui-command-runner.js +251 -468
- 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-help.js +26 -0
- package/neural-interface/public/shared/ui-invite.js +126 -116
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +367 -12
- package/neural-interface/public/shared/ui-menubar.js +21 -55
- 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 +45 -21
- package/neural-interface/public/shared/ui-settings.js +491 -547
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sync.js +16 -0
- package/neural-interface/public/shared/ui-terminal.js +153 -149
- 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-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/worker-end.js +251 -0
- package/neural-interface/public/sw.js +3 -1
- package/neural-interface/public/variant/2d/main.js +2 -1
- package/neural-interface/public/variant/3d/main.js +2 -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/rebuild-pty.js +15 -12
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +1092 -1212
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +13 -7
- package/postinstall.js +11 -81
- package/setup.js +111 -6
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/docs/browser-v2.md +0 -109
- package/docs/hook-integrity.md +0 -89
- package/docs/judgments.md +0 -182
- package/docs/style-guide.md +0 -246
- 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/scripts/audit-claude-hook-usage.mjs +0 -12
- package/scripts/audit-memory-duplicates.mjs +0 -44
- package/scripts/measure-claude-hook-overhead.mjs +0 -77
- package/scripts/recover-user-config.mjs +0 -254
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -11,15 +11,16 @@
|
|
|
11
11
|
// Replay runs the same handleEvent path with { silent: true }: live minus
|
|
12
12
|
// motion, durations from the journal's `at`. Every row is inserted before the
|
|
13
13
|
// "Working…" indicator so it always stays last.
|
|
14
|
-
// The empty state's character (the hero) outlives a send
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
14
|
+
// The empty state's character (the hero) outlives a send only for its
|
|
15
|
+
// hand-off: the "Thinking" row's character flies in from the hero's box at
|
|
16
|
+
// once, so nothing sits on the prompt, and with no such row the hero fades
|
|
17
|
+
// out. mascot() names the character on screen for the panel's director
|
|
18
|
+
// (asst-mascot.js), which may hold its pose; stills are painted SVGs, never
|
|
19
|
+
// rigs.
|
|
20
20
|
|
|
21
21
|
import { assistantEventKey, fmtCost, fmtElapsed, isRemovableRun, isRunIdle, isTerminalRunStatus, modelShortName, providerShortLabel, routeTargetLabel, runTone, summarizeMailbox } from './asst-state.js';
|
|
22
22
|
import { createRouteLine } from './asst-route.js';
|
|
23
|
+
import { closeMenu, isMenuOpen } from './asst-menu.js';
|
|
23
24
|
import { computerToolName, describeComputerAction, frameUrls, markerPercent, openFrameLightbox, parseComputerResult, swapImage } from './asst-computer.js';
|
|
24
25
|
import * as mascotLib from '../synabun-mascot.js';
|
|
25
26
|
import { isReducedMotion, subscribe } from '../synabun-ticker.js';
|
|
@@ -27,6 +28,7 @@ import { KIND_SPECS, callOutcome, commandOf, editDiff, editPaths, fmtDuration, g
|
|
|
27
28
|
import { injectRackStyles } from './asst-rack-styles.js';
|
|
28
29
|
import { formatRunTokens } from './asst-usage.js';
|
|
29
30
|
import { sanitizeHtmlString, sanitizeInto } from './asst-sanitize.js';
|
|
31
|
+
import { automationView } from './asst-automation.js';
|
|
30
32
|
|
|
31
33
|
// ── Markdown (lazy marked, same CDN build as the sidepanels) ───────────────
|
|
32
34
|
let _marked = null;
|
|
@@ -227,7 +229,7 @@ export function updateRunCard(card, run, opts = {}) {
|
|
|
227
229
|
const routeBits = [r.taskClassLabel || r.taskClass || t('assistant.routes.task', 'task')];
|
|
228
230
|
if (r.status === 'pending') routeBits.push(t('assistant.routes.state.pending', 'waiting for your choice'));
|
|
229
231
|
else if (r.decidedBy === 'user') routeBits.push(t('assistant.routes.youChose', 'you chose'));
|
|
230
|
-
else if (r.remembered || r.decidedBy === 'remembered') routeBits.push(t('assistant.routes.remembered', '
|
|
232
|
+
else if (r.remembered || r.decidedBy === 'remembered') routeBits.push(t('assistant.routes.remembered', 'your saved choice'));
|
|
231
233
|
else if (Number.isFinite(Number(r.confidence))) routeBits.push(`${Math.round(Number(r.confidence) <= 1 ? Number(r.confidence) * 100 : Number(r.confidence))}%`);
|
|
232
234
|
const tip = r.target ? `${t('assistant.routes.routedTo', 'Routed to')} ${routeTargetLabel(r.target, { effort: true })}` : t('assistant.routes.title', 'Model routing');
|
|
233
235
|
bits.push(`<span class="asst-run-route" data-tooltip="${esc(tip)}">⇄ ${esc(routeBits.join(', '))}</span>`);
|
|
@@ -408,8 +410,8 @@ const BURST_MS = 800; // 3+ calls started within this window read "newest ×N"
|
|
|
408
410
|
const STATUS_GAP_MS = 1500; // screen-reader announcements at most this often
|
|
409
411
|
const FOLD_MS = 180; // narration collapsing into the stage caption
|
|
410
412
|
const COLLAPSE_MS = 260; // the stage folding into the receipt
|
|
411
|
-
const FLIP_MS = 320; // the hero flying into the
|
|
412
|
-
const HERO_LEAVE_MS = 140; // a parked hero
|
|
413
|
+
const FLIP_MS = 320; // the hero flying into the "Thinking" row (--asst-dur-slow)
|
|
414
|
+
const HERO_LEAVE_MS = 140; // a parked hero the "Thinking" row did not take over fades out
|
|
413
415
|
const MAX_TICKS = 60;
|
|
414
416
|
const NARROW_TICKS = 12;
|
|
415
417
|
const PREVIEW_LINES = 5;
|
|
@@ -434,12 +436,12 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
434
436
|
const scopes = new Map(); // parent_tool_use_id → { container, stream, key }
|
|
435
437
|
const runCards = new Map(); // runId → card element
|
|
436
438
|
const routeLines = new Map(); // routeId → line handle
|
|
439
|
+
const autoRows = new Map(); // traceId → the turn's automation trace row
|
|
437
440
|
let working = null;
|
|
438
441
|
let workingFace = null; // the "Thinking" row's character: { still, rig?, el, target? } (syncWorkingFace)
|
|
439
442
|
let followTail = true;
|
|
440
443
|
let emptyEl = null;
|
|
441
444
|
let hero = null; // the empty state's character: { rig, el, box, parked, leaving, held, dock, egg, target }
|
|
442
|
-
let rigsOn = true; // this panel's rigs may animate (asst-mascot.js lets one panel at a time)
|
|
443
445
|
const mcpWarned = new Set();
|
|
444
446
|
let lastErrorText = ''; // a synthetic API-error message's text, so the result does not repeat it
|
|
445
447
|
let turnFrames = []; // computer cards with inline thumbnails in the current turn
|
|
@@ -535,14 +537,21 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
535
537
|
return scope;
|
|
536
538
|
}
|
|
537
539
|
|
|
538
|
-
/** The empty state goes; `park` (
|
|
540
|
+
/** The empty state goes; `park` (a turn starts) hands its character over to the "Thinking" row. */
|
|
539
541
|
function hideEmpty({ park = false } = {}) {
|
|
540
542
|
if (!emptyEl) return;
|
|
541
543
|
if (!(park && parkHero())) dropHero();
|
|
544
|
+
closeEmptyMenu();
|
|
542
545
|
emptyEl.remove();
|
|
543
546
|
emptyEl = null;
|
|
544
547
|
}
|
|
545
548
|
|
|
549
|
+
/** A menu opened from the empty state's project picker does not outlive it. */
|
|
550
|
+
function closeEmptyMenu() {
|
|
551
|
+
const btn = emptyEl?.querySelector('.asst-empty-project-btn');
|
|
552
|
+
if (btn && isMenuOpen(btn)) closeMenu();
|
|
553
|
+
}
|
|
554
|
+
|
|
546
555
|
// ── The hero ──
|
|
547
556
|
const cssEase = (name, fallback) => {
|
|
548
557
|
try { return getComputedStyle($msgs).getPropertyValue(name).trim() || fallback; } catch { return fallback; }
|
|
@@ -561,9 +570,11 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
561
570
|
|
|
562
571
|
/**
|
|
563
572
|
* Park the hero where it stands, in an overlay of the transcript's parent,
|
|
564
|
-
* so it outlives the empty state
|
|
565
|
-
*
|
|
566
|
-
*
|
|
573
|
+
* so it outlives the empty state for its hand-off. It stays there only
|
|
574
|
+
* until the rows of this task are in (the prompt, "Thinking"): settleHero()
|
|
575
|
+
* then gives it to the "Thinking" row, so it is never drawn over the
|
|
576
|
+
* prompt that took the empty state's place. Not on replay, under reduced
|
|
577
|
+
* motion or while the UI is dragged, and not when it is off screen.
|
|
567
578
|
*/
|
|
568
579
|
function parkHero() {
|
|
569
580
|
const h = hero;
|
|
@@ -585,10 +596,33 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
585
596
|
h.dock = dock;
|
|
586
597
|
h.parked = true;
|
|
587
598
|
if (!h.held) h.rig.setPose('think');
|
|
599
|
+
queueMicrotask(() => settleHero(h));
|
|
588
600
|
mascotChanged();
|
|
589
601
|
return true;
|
|
590
602
|
}
|
|
591
603
|
|
|
604
|
+
/**
|
|
605
|
+
* The parked hero's hand-off, once the task that parked it has put its rows
|
|
606
|
+
* in: the "Thinking" row's character flies in from the hero's box and the
|
|
607
|
+
* hero leaves in the same frame. With no such row to take it over (a slash
|
|
608
|
+
* command's echo, a rack already live) it fades out.
|
|
609
|
+
*/
|
|
610
|
+
function settleHero(h) {
|
|
611
|
+
if (hero !== h || !h.parked || h.leaving) return;
|
|
612
|
+
if (!workingFree()) { dismissHero(); return; }
|
|
613
|
+
const from = h.el.getBoundingClientRect();
|
|
614
|
+
dropHero(); // the "Thinking" row wears the character now (syncWorkingFace)
|
|
615
|
+
const face = workingFace;
|
|
616
|
+
if (!face?.rig) return;
|
|
617
|
+
const row = working;
|
|
618
|
+
row.classList.add('is-flip');
|
|
619
|
+
const anim = flipFrom(face.el, from);
|
|
620
|
+
const done = () => row.classList.remove('is-flip');
|
|
621
|
+
if (!anim) { done(); return; }
|
|
622
|
+
anim.onfinish = done;
|
|
623
|
+
anim.oncancel = done;
|
|
624
|
+
}
|
|
625
|
+
|
|
592
626
|
function dropHero() {
|
|
593
627
|
const h = hero;
|
|
594
628
|
if (!h) return;
|
|
@@ -600,20 +634,15 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
600
634
|
syncWorkingFace(); // the "Thinking" row may wear the character now
|
|
601
635
|
}
|
|
602
636
|
|
|
603
|
-
/** A parked hero
|
|
604
|
-
function dismissHero(
|
|
637
|
+
/** A parked hero the "Thinking" row did not take over leaves: it fades out (at once when calm). */
|
|
638
|
+
function dismissHero() {
|
|
605
639
|
const h = hero;
|
|
606
640
|
if (!h?.parked || h.leaving) return;
|
|
607
641
|
h.leaving = true;
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
anim.onfinish = () => { if (hero === h) dropHero(); };
|
|
613
|
-
h.leaveTimer = setTimeout(() => { if (hero === h) dropHero(); }, HERO_LEAVE_MS + 120);
|
|
614
|
-
};
|
|
615
|
-
if (delay > 0) h.leaveTimer = setTimeout(go, delay);
|
|
616
|
-
else go();
|
|
642
|
+
if (calm() || typeof h.dock.animate !== 'function') { dropHero(); return; }
|
|
643
|
+
const anim = h.dock.animate([{ opacity: 1 }, { opacity: 0 }], { duration: HERO_LEAVE_MS, easing: cssEase('--asst-ease-exit', 'cubic-bezier(.2,0,1,.9)'), fill: 'forwards' });
|
|
644
|
+
anim.onfinish = () => { if (hero === h) dropHero(); };
|
|
645
|
+
h.leaveTimer = setTimeout(() => { if (hero === h) dropHero(); }, HERO_LEAVE_MS + 120);
|
|
617
646
|
}
|
|
618
647
|
|
|
619
648
|
/**
|
|
@@ -635,19 +664,6 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
635
664
|
], { duration: FLIP_MS, easing: cssEase('--asst-ease-emphasized', 'cubic-bezier(.4,.14,.3,1)') });
|
|
636
665
|
}
|
|
637
666
|
|
|
638
|
-
/** The hand-off: the first stage's face flies in from the parked hero, which leaves in the same frame. */
|
|
639
|
-
function flipIntoStage(rack) {
|
|
640
|
-
const h = hero;
|
|
641
|
-
const from = h.el.getBoundingClientRect();
|
|
642
|
-
dropHero();
|
|
643
|
-
rack.row.classList.add('is-flip');
|
|
644
|
-
const anim = flipFrom(rack.stage.face, from);
|
|
645
|
-
const done = () => rack.row.classList.remove('is-flip');
|
|
646
|
-
if (!anim) { done(); return; }
|
|
647
|
-
anim.onfinish = done;
|
|
648
|
-
anim.oncancel = done;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
667
|
/** Triple-click the character: its eyes part and re-form, once per mount. Silent. */
|
|
652
668
|
function wireEgg(h) {
|
|
653
669
|
const hit = (e) => {
|
|
@@ -691,13 +707,6 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
691
707
|
try { hooks.onMascotChange?.(); } catch { /* the director is gone */ }
|
|
692
708
|
}
|
|
693
709
|
|
|
694
|
-
/** Whether this panel's rigs animate (another panel may lead); still frames otherwise. */
|
|
695
|
-
function setRigsEnabled(on) {
|
|
696
|
-
if (rigsOn === !!on) return;
|
|
697
|
-
rigsOn = !!on;
|
|
698
|
-
activateRigs();
|
|
699
|
-
}
|
|
700
|
-
|
|
701
710
|
function prune() {
|
|
702
711
|
const rows = $msgs.children;
|
|
703
712
|
while (rows.length > MAX_MESSAGES && rows[0] !== working) rows[0].remove();
|
|
@@ -709,7 +718,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
709
718
|
if (scope.container === $msgs && working && working.parentNode === $msgs) $msgs.insertBefore(node, working);
|
|
710
719
|
else scope.container.appendChild(node);
|
|
711
720
|
if (scope === rootScope) prune();
|
|
712
|
-
// Text, a card, a notice…: the parked hero would sit on it.
|
|
721
|
+
// Text, a card, a notice…: the parked hero would sit on it. The prompt and a rack leave it to settleHero().
|
|
713
722
|
if (hero?.parked && scope === rootScope && !node._rack && !node.classList.contains('msg-user')) dismissHero();
|
|
714
723
|
if (racks.size) syncRacks();
|
|
715
724
|
scrollEnd();
|
|
@@ -750,8 +759,8 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
750
759
|
const a = el('a', 'file-link', match[0]);
|
|
751
760
|
a.href = '#';
|
|
752
761
|
a.title = t('assistant.copyPath', 'Click to copy path');
|
|
753
|
-
|
|
754
|
-
|
|
762
|
+
// The click is taken on the transcript (asst-panel.js → ui-file-links.js): the code editor when it
|
|
763
|
+
// can open the path, else the copy a click has always made.
|
|
755
764
|
frag.appendChild(a);
|
|
756
765
|
last = match.index + match[0].length;
|
|
757
766
|
}
|
|
@@ -1495,13 +1504,10 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1495
1504
|
rack.clearTimer = 0;
|
|
1496
1505
|
rack.row.classList.add('is-staged');
|
|
1497
1506
|
if (!rack.rig) {
|
|
1498
|
-
|
|
1499
|
-
const heir = hero?.parked && !hero.leaving && !calm() ? hero : null;
|
|
1500
|
-
try { rack.rig = mascotLib.createMascot(rack.stage.face, { width: 72, height: 36, rangeX: 10, rangeY: 7, active: rigsOn, pose: heir ? heir.rig.pose : 'idle' }); } catch { rack.rig = null; }
|
|
1507
|
+
try { rack.rig = mascotLib.createMascot(rack.stage.face, { width: 72, height: 36, rangeX: 10, rangeY: 7, pose: 'idle' }); } catch { rack.rig = null; }
|
|
1501
1508
|
rack.pose = null;
|
|
1502
1509
|
rack.target = rack.rig ? stageTarget(rack) : null;
|
|
1503
|
-
if (
|
|
1504
|
-
else if (hero?.parked) dropHero();
|
|
1510
|
+
if (hero?.parked) dropHero(); // the stage is the character now: a hero still on its way out goes at once
|
|
1505
1511
|
}
|
|
1506
1512
|
mascotChanged();
|
|
1507
1513
|
return;
|
|
@@ -1560,12 +1566,16 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
1560
1566
|
rack.bubble = { step: null, since: 0, key: '', open: false };
|
|
1561
1567
|
}
|
|
1562
1568
|
|
|
1563
|
-
/**
|
|
1569
|
+
/**
|
|
1570
|
+
* One rig of this transcript animates: the hero, else the newest stage, else
|
|
1571
|
+
* the "Thinking" row's. The rule ends at this renderer: every other panel
|
|
1572
|
+
* runs its own, so N live sessions show N moving characters.
|
|
1573
|
+
*/
|
|
1564
1574
|
function activateRigs() {
|
|
1565
1575
|
const newest = hero?.rig ? null : newestStage();
|
|
1566
|
-
for (const rack of racks) if (rack.rig) rack.rig.setActive?.(
|
|
1567
|
-
hero?.rig?.setActive?.(
|
|
1568
|
-
workingFace?.rig?.setActive?.(
|
|
1576
|
+
for (const rack of racks) if (rack.rig) rack.rig.setActive?.(rack === newest);
|
|
1577
|
+
hero?.rig?.setActive?.(true);
|
|
1578
|
+
workingFace?.rig?.setActive?.(!hero?.rig && !newest);
|
|
1569
1579
|
}
|
|
1570
1580
|
|
|
1571
1581
|
/** The bubble's call: the awake one, else the newest; it holds 900 ms (3 s for thinking) before the next wins. */
|
|
@@ -2605,18 +2615,23 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2605
2615
|
|
|
2606
2616
|
// ── The "Thinking" row's character ──
|
|
2607
2617
|
// The row wears the mascot, thinking, only when no other character is on
|
|
2608
|
-
// screen: no
|
|
2609
|
-
//
|
|
2610
|
-
//
|
|
2618
|
+
// screen: no hero, no stage (a live rack right above it hides the row
|
|
2619
|
+
// altogether). A rig, so the director can dress it; a painted still when
|
|
2620
|
+
// calm. A send hands the hero over to it (settleHero).
|
|
2611
2621
|
function workingCovered() {
|
|
2612
2622
|
let prev = working?.previousElementSibling || null;
|
|
2613
2623
|
while (prev && prev.classList.contains('asst-folding')) prev = prev.previousElementSibling;
|
|
2614
2624
|
return !!prev?._rack?.live;
|
|
2615
2625
|
}
|
|
2616
2626
|
|
|
2627
|
+
/** The row is up and neither a stage nor a live rack stands in for it. */
|
|
2628
|
+
function workingFree() {
|
|
2629
|
+
return !!working?.isConnected && !newestStage() && !workingCovered();
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2617
2632
|
function syncWorkingFace() {
|
|
2618
2633
|
const host = working?.querySelector('.asst-working-face');
|
|
2619
|
-
const wanted = !!host &&
|
|
2634
|
+
const wanted = !!host && !hero && workingFree();
|
|
2620
2635
|
if (!wanted) { dropWorkingFace(); return; }
|
|
2621
2636
|
if (workingFace && workingFace.still === calm()) return;
|
|
2622
2637
|
dropWorkingFace();
|
|
@@ -2625,7 +2640,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2625
2640
|
return;
|
|
2626
2641
|
}
|
|
2627
2642
|
try {
|
|
2628
|
-
const rig = mascotLib.createMascot(host, { width: 56, height: 28,
|
|
2643
|
+
const rig = mascotLib.createMascot(host, { width: 56, height: 28, pose: 'think' });
|
|
2629
2644
|
const face = { still: false, rig, el: rig.el, held: false };
|
|
2630
2645
|
face.target = { kind: 'working', rig, el: rig.el, hold(on) { face.held = !!on; }, restore() { if (workingFace === face) rig.setPose('think'); } };
|
|
2631
2646
|
workingFace = face;
|
|
@@ -2638,6 +2653,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2638
2653
|
if (!face) return;
|
|
2639
2654
|
workingFace = null;
|
|
2640
2655
|
try { face.rig?.destroy(); } catch { /* gone */ }
|
|
2656
|
+
working?.classList.remove('is-flip');
|
|
2641
2657
|
working?.querySelector('.asst-working-face')?.replaceChildren();
|
|
2642
2658
|
if (face.rig) mascotChanged();
|
|
2643
2659
|
}
|
|
@@ -2751,6 +2767,115 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2751
2767
|
if (line) { line.el.remove(); routeLines.delete(routeId); }
|
|
2752
2768
|
}
|
|
2753
2769
|
|
|
2770
|
+
// ── The automation trace ──
|
|
2771
|
+
// What the harness did for a turn (`synabun.automation`, asst-automation.js):
|
|
2772
|
+
// one disclosure row per turn, upserted by its traceId. It appears where the
|
|
2773
|
+
// turn was sent, moves under the reply when the turn ends and updates in
|
|
2774
|
+
// place when learning finishes. Drawn from the event alone, so the live row
|
|
2775
|
+
// and the replayed one are the same. Opening one keeps the next ones open.
|
|
2776
|
+
let autoUid = 0;
|
|
2777
|
+
const TRACE_OPEN_KEY = 'synabun-asst-trace-open';
|
|
2778
|
+
const traceOpenPref = () => { try { return localStorage.getItem(TRACE_OPEN_KEY) === '1'; } catch { return false; } };
|
|
2779
|
+
|
|
2780
|
+
function buildAutoRow() {
|
|
2781
|
+
const node = el('div', 'asst-route-line asst-auto');
|
|
2782
|
+
const detailId = `asst-auto-detail-${++autoUid}`;
|
|
2783
|
+
node.innerHTML = `
|
|
2784
|
+
<button type="button" class="asst-route-line-head asst-auto-head" aria-expanded="false" aria-controls="${detailId}">
|
|
2785
|
+
<span class="asst-route-glyph asst-icon" aria-hidden="true">${ICON_MEMORY}</span>
|
|
2786
|
+
<span class="asst-route-class asst-auto-title"></span>
|
|
2787
|
+
<span class="asst-route-meta asst-auto-summary"></span>
|
|
2788
|
+
<span class="asst-route-state asst-auto-state"></span>
|
|
2789
|
+
<span class="asst-route-chev" aria-hidden="true">›</span>
|
|
2790
|
+
</button>
|
|
2791
|
+
<div class="asst-route-detail asst-auto-detail" id="${detailId}" hidden></div>`;
|
|
2792
|
+
const head = node.querySelector('.asst-auto-head');
|
|
2793
|
+
const detail = node.querySelector('.asst-auto-detail');
|
|
2794
|
+
const setOpen = (open) => {
|
|
2795
|
+
head.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
2796
|
+
detail.hidden = !open;
|
|
2797
|
+
node.classList.toggle('open', open);
|
|
2798
|
+
};
|
|
2799
|
+
head.addEventListener('click', () => {
|
|
2800
|
+
const open = head.getAttribute('aria-expanded') !== 'true';
|
|
2801
|
+
setOpen(open);
|
|
2802
|
+
try { localStorage.setItem(TRACE_OPEN_KEY, open ? '1' : '0'); } catch { /* no storage: this row only */ }
|
|
2803
|
+
});
|
|
2804
|
+
setOpen(traceOpenPref());
|
|
2805
|
+
return { el: node, detail, atEnd: false };
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
function paintAutoRow(row, ev) {
|
|
2809
|
+
const view = automationView(ev, { t: hooks.t });
|
|
2810
|
+
row.el.dataset.traceId = view.traceId;
|
|
2811
|
+
row.el.dataset.phase = view.phase;
|
|
2812
|
+
row.el.classList.toggle('busy', view.tone === 'busy');
|
|
2813
|
+
row.el.classList.toggle('learned', view.tone === 'learned');
|
|
2814
|
+
row.el.querySelector('.asst-auto-title').textContent = view.title;
|
|
2815
|
+
row.el.querySelector('.asst-auto-summary').textContent = view.summary;
|
|
2816
|
+
const state = row.el.querySelector('.asst-auto-state');
|
|
2817
|
+
state.textContent = view.state;
|
|
2818
|
+
state.hidden = !view.state;
|
|
2819
|
+
row.detail.replaceChildren();
|
|
2820
|
+
for (const group of view.groups) {
|
|
2821
|
+
const box = el('div', 'asst-auto-group');
|
|
2822
|
+
box.dataset.group = group.key;
|
|
2823
|
+
box.appendChild(el('div', 'asst-auto-h', group.title));
|
|
2824
|
+
for (const item of group.rows) {
|
|
2825
|
+
const line = el('div', `asst-auto-row${item.tone ? ` ${item.tone}` : ''}`);
|
|
2826
|
+
if (item.label || item.meta) {
|
|
2827
|
+
const lead = el('div', 'asst-auto-row-head');
|
|
2828
|
+
if (item.label) lead.appendChild(el('span', 'asst-auto-tier', item.label));
|
|
2829
|
+
if (item.meta) lead.appendChild(el('span', 'asst-auto-meta', item.meta));
|
|
2830
|
+
line.appendChild(lead);
|
|
2831
|
+
}
|
|
2832
|
+
// Memory text and the user's own words: always as text, never as markup.
|
|
2833
|
+
if (item.text) line.appendChild(el(item.pre ? 'pre' : 'div', 'asst-auto-text', item.text));
|
|
2834
|
+
if (item.ids.length) {
|
|
2835
|
+
const ids = el('div', 'asst-auto-ids');
|
|
2836
|
+
for (const id of item.ids) {
|
|
2837
|
+
const link = el('button', 'asst-link', String(id).slice(0, 8));
|
|
2838
|
+
link.type = 'button';
|
|
2839
|
+
link.dataset.memoryId = id;
|
|
2840
|
+
link.title = `${t('assistant.automation.open', 'Open memory')} ${id}`;
|
|
2841
|
+
link.setAttribute('aria-label', link.title);
|
|
2842
|
+
link.addEventListener('click', () => hooks.onMemoryClick?.(id, { id }));
|
|
2843
|
+
ids.appendChild(link);
|
|
2844
|
+
}
|
|
2845
|
+
line.appendChild(ids);
|
|
2846
|
+
}
|
|
2847
|
+
box.appendChild(line);
|
|
2848
|
+
}
|
|
2849
|
+
row.detail.appendChild(box);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
/** Create or update the trace row for `ev.traceId`. */
|
|
2854
|
+
function upsertAutomation(ev) {
|
|
2855
|
+
const id = String(ev?.traceId || '');
|
|
2856
|
+
if (!id) return null;
|
|
2857
|
+
let row = autoRows.get(id);
|
|
2858
|
+
const fresh = !row || !row.el.isConnected;
|
|
2859
|
+
if (fresh) {
|
|
2860
|
+
row = buildAutoRow();
|
|
2861
|
+
autoRows.set(id, row);
|
|
2862
|
+
while (autoRows.size > 200) autoRows.delete(autoRows.keys().next().value);
|
|
2863
|
+
}
|
|
2864
|
+
paintAutoRow(row, ev);
|
|
2865
|
+
// Under the reply once the turn has ended; a later snapshot (learning) never moves it again.
|
|
2866
|
+
const ended = ev.phase !== 'sent';
|
|
2867
|
+
if (fresh || (ended && !row.atEnd)) {
|
|
2868
|
+
// Placed like any row (before "Working…"), but a status line of the harness never dismisses the hero.
|
|
2869
|
+
if (working && working.parentNode === $msgs) $msgs.insertBefore(row.el, working);
|
|
2870
|
+
else $msgs.appendChild(row.el);
|
|
2871
|
+
if (ended) row.atEnd = true;
|
|
2872
|
+
prune();
|
|
2873
|
+
if (racks.size) syncRacks();
|
|
2874
|
+
scrollEnd();
|
|
2875
|
+
}
|
|
2876
|
+
return row.el;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2754
2879
|
// ── Empty state ──
|
|
2755
2880
|
function showEmpty() {
|
|
2756
2881
|
if (emptyEl || $msgs.querySelector(':scope > :not(.asst-working)')) return;
|
|
@@ -2759,14 +2884,22 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2759
2884
|
<div class="asst-empty-mascot" aria-hidden="true"></div>
|
|
2760
2885
|
<div class="asst-empty-title" role="heading" aria-level="2">${esc(t('assistant.empty.heading', 'What should we do?'))}</div>
|
|
2761
2886
|
<div class="asst-empty-body">${esc(t('assistant.empty.lead', 'I can use your Mac, look things up on the web, edit files and run commands, and remember or schedule things.'))}</div>
|
|
2887
|
+
<div class="asst-empty-greet"></div>
|
|
2888
|
+
<div class="asst-empty-project" hidden></div>
|
|
2762
2889
|
<div class="asst-suggestions" role="list"></div>`;
|
|
2763
2890
|
const box = emptyEl.querySelector('.asst-empty-mascot');
|
|
2764
2891
|
try {
|
|
2765
|
-
const rig = mascotLib.createMascot(box, { width: 120, height: 60
|
|
2892
|
+
const rig = mascotLib.createMascot(box, { width: 120, height: 60 });
|
|
2766
2893
|
hero = { rig, el: rig.el, box, parked: false, leaving: false, held: false, dock: null, egg: false, leaveTimer: 0 };
|
|
2767
2894
|
hero.target = heroTarget(hero);
|
|
2768
2895
|
wireEgg(hero);
|
|
2769
2896
|
} catch { hero = null; }
|
|
2897
|
+
// The project the conversation works in, under the lead: the host draws the picker (the brain
|
|
2898
|
+
// picker's project field), so it and the toolbar's Working project are one control.
|
|
2899
|
+
const projectSlot = emptyEl.querySelector('.asst-empty-project');
|
|
2900
|
+
let picker = null;
|
|
2901
|
+
try { picker = typeof hooks.mountEmptyProject === 'function' ? hooks.mountEmptyProject(projectSlot) : null; } catch { picker = null; }
|
|
2902
|
+
projectSlot.hidden = !picker;
|
|
2770
2903
|
renderSuggestions();
|
|
2771
2904
|
$msgs.appendChild(emptyEl);
|
|
2772
2905
|
// The host's "Starting…" character flies into place (not on replay or reduced motion).
|
|
@@ -2776,6 +2909,20 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
2776
2909
|
mascotChanged();
|
|
2777
2910
|
}
|
|
2778
2911
|
|
|
2912
|
+
/**
|
|
2913
|
+
* The greeting SynaBun prepared for this return (public/shared/greeting/) takes the lead's place.
|
|
2914
|
+
* The panel calls it once it knows the conversation is new: a restored one is never greeted.
|
|
2915
|
+
*/
|
|
2916
|
+
function offerGreeting() {
|
|
2917
|
+
const owner = emptyEl;
|
|
2918
|
+
if (!owner || owner.dataset.greetingAsked === '1' || typeof hooks.greeting !== 'function') return;
|
|
2919
|
+
owner.dataset.greetingAsked = '1';
|
|
2920
|
+
const slot = owner.querySelector('.asst-empty-greet');
|
|
2921
|
+
Promise.resolve().then(() => hooks.greeting(slot))
|
|
2922
|
+
.then((greeting) => { if (greeting && emptyEl === owner) owner.classList.add('has-greeting'); })
|
|
2923
|
+
.catch(() => { /* no greeting: the lead stays */ });
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2779
2926
|
function renderSuggestions() {
|
|
2780
2927
|
if (!emptyEl) return;
|
|
2781
2928
|
const host = emptyEl.querySelector('.asst-suggestions');
|
|
@@ -3101,6 +3248,9 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3101
3248
|
// The card itself arrives as a control_request; its outcome is a line (the panel's card shows it live).
|
|
3102
3249
|
if (ev.phase !== 'card') appendStatus(clarifyLine(ev), 'info');
|
|
3103
3250
|
return true;
|
|
3251
|
+
case 'synabun.automation':
|
|
3252
|
+
upsertAutomation(ev);
|
|
3253
|
+
return true;
|
|
3104
3254
|
default:
|
|
3105
3255
|
return false;
|
|
3106
3256
|
}
|
|
@@ -3131,8 +3281,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3131
3281
|
|
|
3132
3282
|
/**
|
|
3133
3283
|
* The turn ended. `holdMs`: the panel plays a one-shot (success, error) on
|
|
3134
|
-
* the character on screen, so its stage stays up that long before it folds
|
|
3135
|
-
* and a parked hero waits as long before it fades.
|
|
3284
|
+
* the character on screen, so its stage stays up that long before it folds.
|
|
3136
3285
|
*/
|
|
3137
3286
|
function finishTurn({ holdMs = 0 } = {}) {
|
|
3138
3287
|
flushStream(rootScope);
|
|
@@ -3154,7 +3303,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3154
3303
|
rack.closed = true;
|
|
3155
3304
|
if (rack.live || tail || rack === stage) renderRack(rack);
|
|
3156
3305
|
}
|
|
3157
|
-
if (hero?.parked) dismissHero(
|
|
3306
|
+
if (hero?.parked) dismissHero();
|
|
3158
3307
|
}
|
|
3159
3308
|
|
|
3160
3309
|
/**
|
|
@@ -3224,6 +3373,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3224
3373
|
scopes.clear();
|
|
3225
3374
|
runCards.clear();
|
|
3226
3375
|
routeLines.clear();
|
|
3376
|
+
autoRows.clear();
|
|
3227
3377
|
turnFrames = [];
|
|
3228
3378
|
rootScope.stream = null;
|
|
3229
3379
|
rootScope.kept = null;
|
|
@@ -3240,6 +3390,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3240
3390
|
seen.clear();
|
|
3241
3391
|
snapshot = null;
|
|
3242
3392
|
waiting = null;
|
|
3393
|
+
closeEmptyMenu();
|
|
3243
3394
|
$msgs.innerHTML = '';
|
|
3244
3395
|
emptyEl = null;
|
|
3245
3396
|
followTail = true;
|
|
@@ -3264,6 +3415,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3264
3415
|
registerRouteLine,
|
|
3265
3416
|
removeRouteLine,
|
|
3266
3417
|
getRouteLine: (routeId) => routeLines.get(routeId) || null,
|
|
3418
|
+
upsertAutomation,
|
|
3267
3419
|
getRunCard: (runId) => runCards.get(runId) || null,
|
|
3268
3420
|
handleEvent,
|
|
3269
3421
|
/** The live gate for an event (false: already drawn); registers what it lets through. */
|
|
@@ -3280,6 +3432,7 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3280
3432
|
hideWorking,
|
|
3281
3433
|
isWorkingShown: () => !!working?.isConnected,
|
|
3282
3434
|
showEmpty,
|
|
3435
|
+
offerGreeting,
|
|
3283
3436
|
refreshEmpty: renderSuggestions,
|
|
3284
3437
|
isEmpty: () => !!emptyEl,
|
|
3285
3438
|
scrollEnd,
|
|
@@ -3287,7 +3440,6 @@ export function createRenderer(transcriptEl, hooks = {}) {
|
|
|
3287
3440
|
md,
|
|
3288
3441
|
/** The character on screen, for the panel's director: { kind, rig, el, hold(on), restore() } or null. */
|
|
3289
3442
|
mascot: visibleMascot,
|
|
3290
|
-
setRigsEnabled,
|
|
3291
3443
|
/** What this turn did: real calls (not the prompt-time recall), whether the last one failed, a turn error. */
|
|
3292
3444
|
turnStats: () => ({ calls: turn.calls, lastFailed: turn.last?.state === 'error', errored: turn.errored }),
|
|
3293
3445
|
/** The text of the turn's last message at the root ('' when it folded into a rack or there was none). */
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Assistant — Model routes editor: where its model lists come from
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
// The data side of the Routes editor (asst-route.js `openRoutesEditor`), kept
|
|
5
|
+
// apart from its markup. No DOM is built here.
|
|
6
|
+
//
|
|
7
|
+
// One list per provider, the one every Assistant model menu shows:
|
|
8
|
+
// loadModelCatalog() of asst-brain-picker.js, which is GET /api/assistant/catalog,
|
|
9
|
+
// itself built from the lists the Claude, Codex and OpenCode panels' own
|
|
10
|
+
// selectors read. Nothing here, and nothing in the editor, may keep a second
|
|
11
|
+
// list of models: a label, a tier, a price or an effort level is read from the
|
|
12
|
+
// catalog row each time it is drawn.
|
|
13
|
+
|
|
14
|
+
import { catalogModelRow, loadModelCatalog, modelCatalogStatus, onModelCatalogChange } from './asst-brain-picker.js';
|
|
15
|
+
import { routeTargetLabel } from './asst-state.js';
|
|
16
|
+
|
|
17
|
+
/** The catalog as the editor opens: a new build, never the copy another menu loaded a minute ago. */
|
|
18
|
+
export function loadRouteModels() {
|
|
19
|
+
return loadModelCatalog({ force: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Loading/partial state belongs to the same model source, without changing the catalog shape. */
|
|
23
|
+
export function routeModelsStatus() { return modelCatalogStatus(); }
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The name shown for a saved route's model. The catalog's label for that id wins:
|
|
27
|
+
* a route keeps the label it was saved with, and an alias moves ("haiku" was
|
|
28
|
+
* Haiku 4.5, then Haiku 5.5). A model the catalog no longer lists (removed by its
|
|
29
|
+
* provider, hidden, or its provider is not answering) keeps the saved label,
|
|
30
|
+
* then its id, then the provider's name: never an empty name.
|
|
31
|
+
*/
|
|
32
|
+
export function routeModelLabel(pref, catalog) {
|
|
33
|
+
if (!pref || typeof pref !== 'object') return '';
|
|
34
|
+
const row = pref.provider && pref.model ? catalogModelRow(pref.provider, pref.model, catalog) : null;
|
|
35
|
+
const live = row?.label ? String(row.label).trim() : '';
|
|
36
|
+
return live || routeTargetLabel(pref);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** What the editor draws from a catalog: when this is unchanged, there is nothing to redraw. */
|
|
40
|
+
export function catalogSignature(catalog) {
|
|
41
|
+
if (!catalog || typeof catalog !== 'object') return '';
|
|
42
|
+
return JSON.stringify(Object.keys(catalog).sort().map(provider => [
|
|
43
|
+
provider,
|
|
44
|
+
(Array.isArray(catalog[provider]) ? catalog[provider] : []).map(m => [
|
|
45
|
+
m?.id, m?.label, m?.status ?? null, m?.vision ?? null, m?.outputs?.image ?? null, m?.outputs?.video ?? null,
|
|
46
|
+
(m?.efforts || []).join(','), m?.defaultEffort || '', m?.isDefault === true,
|
|
47
|
+
]),
|
|
48
|
+
]));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Keeps an open editor on the current lists. `apply(catalog)` is called with a
|
|
53
|
+
* newly loaded catalog whenever it differs from the last one applied:
|
|
54
|
+
* - after a change event (a model hidden or shown, a provider listing other
|
|
55
|
+
* models, an OpenCode provider connected or removed): a new build is asked;
|
|
56
|
+
* - when the window is shown again: the catalog is read again, and reloaded
|
|
57
|
+
* once its copy has aged out (no event tells a window that slept).
|
|
58
|
+
* Returns { seed(catalog), stop() }: seed with the catalog the editor loaded
|
|
59
|
+
* itself, stop when it closes. No timer runs while nothing changes.
|
|
60
|
+
*/
|
|
61
|
+
export function watchRouteModels(apply, {
|
|
62
|
+
load = loadModelCatalog,
|
|
63
|
+
subscribe = onModelCatalogChange,
|
|
64
|
+
doc = typeof document !== 'undefined' ? document : null,
|
|
65
|
+
delayMs = 150,
|
|
66
|
+
setTimer = setTimeout,
|
|
67
|
+
clearTimer = clearTimeout,
|
|
68
|
+
} = {}) {
|
|
69
|
+
let stopped = false;
|
|
70
|
+
let timer = null;
|
|
71
|
+
let last = null;
|
|
72
|
+
let run = 0;
|
|
73
|
+
|
|
74
|
+
async function reload(force) {
|
|
75
|
+
const mine = ++run;
|
|
76
|
+
let catalog = null;
|
|
77
|
+
try { catalog = await load({ force }); } catch { return; }
|
|
78
|
+
if (stopped || mine !== run || !catalog) return;
|
|
79
|
+
const signature = catalogSignature(catalog);
|
|
80
|
+
if (signature === last) return;
|
|
81
|
+
last = signature;
|
|
82
|
+
try { apply(catalog); } catch { /* the editor's own failure */ }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Several events for one change (a hide broadcasts twice) become one reload.
|
|
86
|
+
let pendingForce = false;
|
|
87
|
+
function schedule(force) {
|
|
88
|
+
if (stopped) return;
|
|
89
|
+
pendingForce = pendingForce || force;
|
|
90
|
+
if (timer != null) clearTimer(timer);
|
|
91
|
+
timer = setTimer(() => { timer = null; const f = pendingForce; pendingForce = false; reload(f); }, delayMs);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const unsubscribe = subscribe(() => schedule(true));
|
|
95
|
+
const onVisible = () => { if (doc?.visibilityState !== 'hidden') schedule(false); };
|
|
96
|
+
doc?.addEventListener?.('visibilitychange', onVisible);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
seed(catalog) { last = catalogSignature(catalog); },
|
|
100
|
+
stop() {
|
|
101
|
+
stopped = true;
|
|
102
|
+
if (timer != null) clearTimer(timer);
|
|
103
|
+
timer = null;
|
|
104
|
+
try { unsubscribe?.(); } catch { /* ignore */ }
|
|
105
|
+
doc?.removeEventListener?.('visibilitychange', onVisible);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|