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
|
@@ -2882,6 +2882,11 @@ export function isSidepanelKeyTarget(el) {
|
|
|
2882
2882
|
return !!el?.closest?.('[data-sidepanel-provider]');
|
|
2883
2883
|
}
|
|
2884
2884
|
|
|
2885
|
+
// A key pressed inside the File Explorer's code editor (shared/editor/editor-panel.js) is the editor's.
|
|
2886
|
+
export function isEditorPanelKeyTarget(el) {
|
|
2887
|
+
return !!el?.closest?.('#fe-editor-panel');
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2885
2890
|
function onKeyDown(e) {
|
|
2886
2891
|
if (!isWhiteboardActive()) return;
|
|
2887
2892
|
|
|
@@ -2897,6 +2902,13 @@ function onKeyDown(e) {
|
|
|
2897
2902
|
// Nor while it is inside a side panel: Escape there stops a turn, and Delete, Backspace or
|
|
2898
2903
|
// Ctrl+A/Z/C on one of its controls is not a board edit.
|
|
2899
2904
|
if (isSidepanelKeyTarget(e.target) || isSidepanelKeyTarget(ae)) return;
|
|
2905
|
+
// Nor while it is inside the code editor's panel: on one of its tabs, header or status-bar buttons, in one of
|
|
2906
|
+
// its menus, its palette or its popover (all attached inside the panel), M, Delete, Backspace or
|
|
2907
|
+
// Ctrl+A/Z/Y/C is the editor's key, or nobody's, and never a board edit.
|
|
2908
|
+
if (isEditorPanelKeyTarget(e.target) || isEditorPanelKeyTarget(ae)) return;
|
|
2909
|
+
// Nor while it is in the File Explorer, or in what it opens over the page (its menu, preview and questions):
|
|
2910
|
+
// Delete there deletes a file, and a letter is the name of a row being typed.
|
|
2911
|
+
if (ae?.closest?.('#file-explorer-panel, .fe-ctx-flyout, .fe-modal-overlay, .fe-git-popover')) return;
|
|
2900
2912
|
|
|
2901
2913
|
const isEditingText = !!document.activeElement?.closest('.wb-text.editing');
|
|
2902
2914
|
const editingListLi = document.activeElement?.closest('.wb-list.editing li') || null;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — how a dispatched worker's tab ends: the page side
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// What the three provider sidepanels (Claude Code, Codex, OpenCode) share for
|
|
6
|
+
// the end of a worker the Assistant dispatched: following the run until it
|
|
7
|
+
// has something to show, asking the dispatcher how it went (GET
|
|
8
|
+
// /api/assistant/runs/:runId, then its `assistant:dispatch` events), the card
|
|
9
|
+
// itself, and the way back to the Assistant session that dispatched it. What
|
|
10
|
+
// the card says is worker-end.js; where it goes in a transcript, how it looks
|
|
11
|
+
// and how its tab closes are each panel's own (every panel passes its own
|
|
12
|
+
// classes and its own close action, so the card wears that panel's styles).
|
|
13
|
+
//
|
|
14
|
+
// The way back closes the worker's tab with the close the panel's own X runs
|
|
15
|
+
// (it never deletes the run record: the Assistant still reads its result, and
|
|
16
|
+
// the agents dock still lists it) and then shows the session through
|
|
17
|
+
// `assistant:show` with the sidepanel as the host for a session that is open
|
|
18
|
+
// nowhere: the terminal tab or the sidepanel that has it keeps it. Closing the
|
|
19
|
+
// tab of a worker that finished its task completes its run instead of
|
|
20
|
+
// stopping it (endWorkerRunOnClose): the Assistant reads it as done, never as
|
|
21
|
+
// stopped by the user.
|
|
22
|
+
|
|
23
|
+
import { emit, on } from './state.js';
|
|
24
|
+
import { t } from './i18n.js';
|
|
25
|
+
import { TERMINAL_STATES, WORKER_END_CLASS, isDispatchedRun, isRunWorking, mayHaveEnded, workerEndView } from './worker-end.js';
|
|
26
|
+
|
|
27
|
+
export { WORKER_END_CLASS };
|
|
28
|
+
|
|
29
|
+
const translate = (key, params) => t(`workerEnd.${key}`, params);
|
|
30
|
+
|
|
31
|
+
// runId → { run, dispatch, settled, wasWorking, busy, apply, readKey, sig }
|
|
32
|
+
const _runs = new Map();
|
|
33
|
+
let _wired = false;
|
|
34
|
+
|
|
35
|
+
function newer(next, current) {
|
|
36
|
+
if (!current) return true;
|
|
37
|
+
const a = Number(next?.version) || 0;
|
|
38
|
+
const b = Number(current?.version) || 0;
|
|
39
|
+
if (a !== b) return a > b;
|
|
40
|
+
return String(next?.updatedAt || '') >= String(current?.updatedAt || '');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function wire() {
|
|
44
|
+
if (_wired) return;
|
|
45
|
+
_wired = true;
|
|
46
|
+
// Every change of a run the dispatcher makes (a turn, a result, the end, a
|
|
47
|
+
// failure's cause judged later) reaches the tab without another request.
|
|
48
|
+
on('sync:assistant:dispatch', (message) => {
|
|
49
|
+
const view = message?.run;
|
|
50
|
+
const entry = view?.runId ? _runs.get(view.runId) : null;
|
|
51
|
+
if (!entry || !newer(view, entry.dispatch)) return;
|
|
52
|
+
noteDispatch(entry, view);
|
|
53
|
+
paint(entry);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function noteDispatch(entry, view) {
|
|
58
|
+
entry.dispatch = view;
|
|
59
|
+
entry.settled = true;
|
|
60
|
+
// What the run was doing before it ended: a stop in the middle of a turn is a stop.
|
|
61
|
+
if (!TERMINAL_STATES.has(String(view.state || ''))) entry.wasWorking = isRunWorking(null, view);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function paint(entry) {
|
|
65
|
+
const view = workerEndView(entry.run, entry.dispatch, { t: translate, busy: entry.busy, stoppedMidTurn: entry.wasWorking });
|
|
66
|
+
// An ended run is drawn once the dispatcher has answered: never "Stopped" first and "Task complete" a moment later.
|
|
67
|
+
if (view && !entry.settled) return;
|
|
68
|
+
const sig = view?.sig || '';
|
|
69
|
+
if (sig === entry.sig) return;
|
|
70
|
+
entry.sig = sig;
|
|
71
|
+
try { entry.apply(view); } catch (error) { console.warn('[worker-end] a panel failed to draw the end of a run', error); }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function read(entry) {
|
|
75
|
+
const run = entry.run;
|
|
76
|
+
const key = `${run.status}|${run.turnState || ''}|${run.version || 0}`;
|
|
77
|
+
if (entry.readKey === key) return;
|
|
78
|
+
entry.readKey = key;
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch(`/api/assistant/runs/${encodeURIComponent(run.runId)}`);
|
|
81
|
+
const data = response.ok ? await response.json() : null;
|
|
82
|
+
if (_runs.get(run.runId) !== entry) return;
|
|
83
|
+
if (data?.run && newer(data.run, entry.dispatch)) noteDispatch(entry, data.run);
|
|
84
|
+
} catch {
|
|
85
|
+
if (_runs.get(run.runId) !== entry) return;
|
|
86
|
+
}
|
|
87
|
+
// No answer (an older server, a run the dispatcher no longer keeps): the runtime's own reading is drawn.
|
|
88
|
+
entry.settled = true;
|
|
89
|
+
paint(entry);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Follow a run attached to a provider tab. Call it on every attach (the
|
|
94
|
+
* router attaches again on each change of the run): `apply(view)` is called
|
|
95
|
+
* with the card to show, or null to take it away, only when that changes.
|
|
96
|
+
* A run the Assistant did not dispatch gets `apply(null)` and is not followed.
|
|
97
|
+
*
|
|
98
|
+
* @param {object} run the runtime descriptor the panel was attached with
|
|
99
|
+
* @param {object} o
|
|
100
|
+
* @param {(view: object | null) => void} o.apply draw the card (or remove it)
|
|
101
|
+
* @param {boolean} [o.busy] the tab sees background work of the run still going
|
|
102
|
+
*/
|
|
103
|
+
export function trackWorkerEnd(run, { apply, busy = false } = {}) {
|
|
104
|
+
if (!run?.runId || typeof apply !== 'function') return;
|
|
105
|
+
if (!isDispatchedRun(run)) {
|
|
106
|
+
_runs.delete(run.runId);
|
|
107
|
+
apply(null);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
wire();
|
|
111
|
+
let entry = _runs.get(run.runId);
|
|
112
|
+
if (!entry) {
|
|
113
|
+
entry = { run: null, dispatch: null, settled: false, wasWorking: false, busy: false, apply, readKey: '', sig: null };
|
|
114
|
+
_runs.set(run.runId, entry);
|
|
115
|
+
}
|
|
116
|
+
entry.apply = apply;
|
|
117
|
+
entry.busy = !!busy;
|
|
118
|
+
if (newer(run, entry.run)) entry.run = run;
|
|
119
|
+
if (!TERMINAL_STATES.has(String(entry.run.status || '')) && !TERMINAL_STATES.has(String(entry.dispatch?.state || ''))) {
|
|
120
|
+
entry.wasWorking = isRunWorking(entry.run, null);
|
|
121
|
+
}
|
|
122
|
+
paint(entry);
|
|
123
|
+
if (mayHaveEnded(entry.run)) read(entry);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The tab saw its background work change: the card of a warm worker waits for it. */
|
|
127
|
+
export function updateWorkerEndBusy(runId, busy) {
|
|
128
|
+
const entry = _runs.get(runId);
|
|
129
|
+
if (!entry || entry.busy === !!busy) return;
|
|
130
|
+
entry.busy = !!busy;
|
|
131
|
+
paint(entry);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The tab of the run closed (or let the run go): stop following it. */
|
|
135
|
+
export function forgetWorkerEnd(runId) {
|
|
136
|
+
if (runId) _runs.delete(runId);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The run reported its task done and waits warm for a follow-up, with no
|
|
141
|
+
* background work of its own going: its card is (or would be) a warm "Task
|
|
142
|
+
* complete". Known only for a run the tab follows, so ask before forgetWorkerEnd().
|
|
143
|
+
*/
|
|
144
|
+
export function isFinishedWarmWorker(runId, { busy = false } = {}) {
|
|
145
|
+
const entry = runId ? _runs.get(runId) : null;
|
|
146
|
+
if (!entry?.settled || !entry.run) return false;
|
|
147
|
+
const view = workerEndView(entry.run, entry.dispatch, { busy: busy || entry.busy, stoppedMidTurn: entry.wasWorking });
|
|
148
|
+
return view?.kind === 'complete' && view.warm;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* End the run of a worker tab the user closes (its X, or Back to Assistant).
|
|
153
|
+
* A worker that finished its task and waits warm for a follow-up is completed
|
|
154
|
+
* (POST /api/assistant/runs/:runId/tab-closed: completed, reason `tab_closed`,
|
|
155
|
+
* its result untouched). Any other run, a run the server finds working after
|
|
156
|
+
* all, and a server without that route get `stop()`: the panel's own stop,
|
|
157
|
+
* what its X always did. Decided when called: call it before forgetWorkerEnd().
|
|
158
|
+
*
|
|
159
|
+
* @param {string} runId
|
|
160
|
+
* @param {() => void} stop the panel's stop (POST /api/loop/stop)
|
|
161
|
+
* @param {object} [o]
|
|
162
|
+
* @param {boolean} [o.busy] the tab sees background work of the run still going
|
|
163
|
+
* @returns {Promise<'completed' | 'stopped'>}
|
|
164
|
+
*/
|
|
165
|
+
export function endWorkerRunOnClose(runId, stop, { busy = false } = {}) {
|
|
166
|
+
const stopped = () => { stop(); return 'stopped'; };
|
|
167
|
+
if (!isFinishedWarmWorker(runId, { busy })) return Promise.resolve(stopped());
|
|
168
|
+
return fetch(`/api/assistant/runs/${encodeURIComponent(runId)}/tab-closed`, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
body: '{}',
|
|
172
|
+
}).then(async (response) => {
|
|
173
|
+
const data = response.ok ? await response.json().catch(() => null) : null;
|
|
174
|
+
return data?.ok ? 'completed' : stopped();
|
|
175
|
+
}).catch(stopped);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Show the Assistant session that dispatched a run: focused where it is open
|
|
180
|
+
* (the terminal tab or the sidepanel), else opened in the Assistant sidepanel.
|
|
181
|
+
*/
|
|
182
|
+
export function backToAssistant(assistantSessionId) {
|
|
183
|
+
if (!assistantSessionId) return false;
|
|
184
|
+
emit('assistant:show', { sessionId: assistantSessionId, host: 'sidepanel' });
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The card. Every element takes the owning panel's classes, so it is drawn in
|
|
190
|
+
* that panel's type and colours; `data-tone` (ok, warn, err, off) is what the
|
|
191
|
+
* panel's styles colour the title by, and the title always says the state in
|
|
192
|
+
* words. The button is there only for a run with a dispatching session, and
|
|
193
|
+
* works once.
|
|
194
|
+
*
|
|
195
|
+
* @param {object} view a workerEndView() result
|
|
196
|
+
* @param {object} [o]
|
|
197
|
+
* @param {{ card?: string, title?: string, detail?: string, meta?: string, note?: string, actions?: string, button?: string }} [o.classes]
|
|
198
|
+
* @param {(view: object) => (void | boolean | Promise<void | boolean>)} [o.onBack] close the tab, then
|
|
199
|
+
* backToAssistant(); `false` when the tab stayed (the button works again)
|
|
200
|
+
*/
|
|
201
|
+
export function buildWorkerEndCard(view, { classes = {}, onBack = null } = {}) {
|
|
202
|
+
const card = document.createElement('div');
|
|
203
|
+
card.className = [WORKER_END_CLASS, classes.card].filter(Boolean).join(' ');
|
|
204
|
+
card.dataset.workerEnd = view.kind;
|
|
205
|
+
card.dataset.tone = view.tone;
|
|
206
|
+
card.setAttribute('role', 'status');
|
|
207
|
+
|
|
208
|
+
const title = document.createElement('div');
|
|
209
|
+
title.className = ['worker-end-title', classes.title].filter(Boolean).join(' ');
|
|
210
|
+
title.textContent = view.title;
|
|
211
|
+
card.appendChild(title);
|
|
212
|
+
|
|
213
|
+
for (const part of ['detail', 'meta', 'note']) {
|
|
214
|
+
if (!view[part]) continue;
|
|
215
|
+
const el = document.createElement('div');
|
|
216
|
+
el.className = [`worker-end-${part}`, classes[part]].filter(Boolean).join(' ');
|
|
217
|
+
el.textContent = view[part];
|
|
218
|
+
card.appendChild(el);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (view.assistantSessionId && typeof onBack === 'function') {
|
|
222
|
+
const actions = document.createElement('div');
|
|
223
|
+
actions.className = ['worker-end-actions', classes.actions].filter(Boolean).join(' ');
|
|
224
|
+
const button = document.createElement('button');
|
|
225
|
+
button.type = 'button';
|
|
226
|
+
button.className = ['worker-end-back', classes.button].filter(Boolean).join(' ');
|
|
227
|
+
button.textContent = view.backLabel;
|
|
228
|
+
button.title = view.backHint;
|
|
229
|
+
button.addEventListener('click', () => {
|
|
230
|
+
if (button.disabled) return;
|
|
231
|
+
button.disabled = true;
|
|
232
|
+
Promise.resolve()
|
|
233
|
+
.then(() => onBack(view))
|
|
234
|
+
// `false`: the panel asked first and the user kept the tab.
|
|
235
|
+
.then((done) => { if (done === false) button.disabled = false; })
|
|
236
|
+
.catch((error) => {
|
|
237
|
+
console.warn('[worker-end] going back to the Assistant failed', error);
|
|
238
|
+
button.disabled = false;
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
actions.appendChild(button);
|
|
242
|
+
card.appendChild(actions);
|
|
243
|
+
}
|
|
244
|
+
return card;
|
|
245
|
+
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Save/load full scene snapshots: open cards, graph node positions,
|
|
6
6
|
// camera state (3D), active category filters, and selection.
|
|
7
|
-
//
|
|
7
|
+
// The switcher leads the top-right dock; Snap to grid and Tile terminals are on
|
|
8
|
+
// the whiteboard rail and in View.
|
|
8
9
|
// Export/import as JSON for backup.
|
|
9
10
|
//
|
|
10
11
|
// Events emitted:
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
import { state, emit, on } from './state.js';
|
|
21
22
|
import { KEYS } from './constants.js';
|
|
22
23
|
import { storage } from './storage.js';
|
|
24
|
+
import { t } from './i18n.js';
|
|
23
25
|
import { posStore } from './node-positions-store.js';
|
|
24
26
|
import { getVariant } from './registry.js';
|
|
25
27
|
import {
|
|
@@ -525,18 +527,13 @@ function updateIndicator() {
|
|
|
525
527
|
if (!indicator || !nameEl) return;
|
|
526
528
|
|
|
527
529
|
const activeId = getActiveId();
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
nameEl.textContent = 'No workspace';
|
|
539
|
-
indicator.classList.remove('has-workspace');
|
|
530
|
+
const active = activeId ? getWorkspaces().find(w => w.id === activeId) : null;
|
|
531
|
+
nameEl.textContent = active ? active.name : 'No workspace';
|
|
532
|
+
indicator.classList.toggle('has-workspace', !!active);
|
|
533
|
+
// The name is cut short in the bar, or not shown at all where the band is narrow: the tooltip has it whole.
|
|
534
|
+
const label = active ? t('toolbar.workspaceActive', { name: active.name }) : t('toolbar.workspaceNone');
|
|
535
|
+
indicator.dataset.tooltip = label;
|
|
536
|
+
indicator.setAttribute('aria-label', label);
|
|
540
537
|
}
|
|
541
538
|
|
|
542
539
|
|
|
@@ -552,6 +549,7 @@ function openDropdown() {
|
|
|
552
549
|
_isOpen = true;
|
|
553
550
|
dropdown.style.display = '';
|
|
554
551
|
indicator?.classList.add('open');
|
|
552
|
+
indicator?.setAttribute('aria-expanded', 'true');
|
|
555
553
|
renderList();
|
|
556
554
|
|
|
557
555
|
emit('panel:close-all-dropdowns-except', 'workspace');
|
|
@@ -565,6 +563,7 @@ function closeDropdown() {
|
|
|
565
563
|
_isOpen = false;
|
|
566
564
|
dropdown.style.display = 'none';
|
|
567
565
|
indicator?.classList.remove('open');
|
|
566
|
+
indicator?.setAttribute('aria-expanded', 'false');
|
|
568
567
|
}
|
|
569
568
|
|
|
570
569
|
function toggleDropdown() {
|
|
@@ -668,25 +667,33 @@ export function initWorkspaces() {
|
|
|
668
667
|
});
|
|
669
668
|
}
|
|
670
669
|
|
|
671
|
-
// Grid snap toggle
|
|
670
|
+
// Grid snap: the whiteboard rail's toggle and View → Snap to grid (the rail shows in Focus Mode only)
|
|
672
671
|
const gridBtn = $('ws-grid-toggle');
|
|
672
|
+
const gridMenu = $('menu-toggle-grid-snap');
|
|
673
|
+
const showGridSnap = () => {
|
|
674
|
+
gridBtn?.classList.toggle('active', state.gridSnap);
|
|
675
|
+
gridBtn?.setAttribute('aria-pressed', String(state.gridSnap));
|
|
676
|
+
gridMenu?.classList.toggle('active', state.gridSnap);
|
|
677
|
+
document.body.classList.toggle('grid-active', state.gridSnap);
|
|
678
|
+
};
|
|
679
|
+
const toggleGridSnap = () => {
|
|
680
|
+
state.gridSnap = !state.gridSnap;
|
|
681
|
+
showGridSnap();
|
|
682
|
+
storage.setItem(KEYS.GRID_SNAP, state.gridSnap);
|
|
683
|
+
};
|
|
684
|
+
if (storage.getItem(KEYS.GRID_SNAP) === 'true') {
|
|
685
|
+
state.gridSnap = true;
|
|
686
|
+
showGridSnap();
|
|
687
|
+
}
|
|
673
688
|
if (gridBtn) {
|
|
674
|
-
const saved = storage.getItem(KEYS.GRID_SNAP);
|
|
675
|
-
if (saved === 'true') {
|
|
676
|
-
state.gridSnap = true;
|
|
677
|
-
gridBtn.classList.add('active');
|
|
678
|
-
document.body.classList.add('grid-active');
|
|
679
|
-
}
|
|
680
689
|
gridBtn.addEventListener('click', (e) => {
|
|
681
690
|
e.stopPropagation();
|
|
682
|
-
|
|
683
|
-
gridBtn.classList.toggle('active', state.gridSnap);
|
|
684
|
-
document.body.classList.toggle('grid-active', state.gridSnap);
|
|
685
|
-
storage.setItem(KEYS.GRID_SNAP, state.gridSnap);
|
|
691
|
+
toggleGridSnap();
|
|
686
692
|
});
|
|
687
693
|
}
|
|
694
|
+
if (gridMenu) gridMenu.addEventListener('click', toggleGridSnap);
|
|
688
695
|
|
|
689
|
-
// Tile floating terminals
|
|
696
|
+
// Tile floating terminals: the rail's button and View → Tile terminals
|
|
690
697
|
const tileBtn = $('ws-tile-btn');
|
|
691
698
|
if (tileBtn) {
|
|
692
699
|
tileBtn.addEventListener('click', (e) => {
|
|
@@ -694,12 +701,19 @@ export function initWorkspaces() {
|
|
|
694
701
|
tileFloatingTerminals();
|
|
695
702
|
});
|
|
696
703
|
}
|
|
704
|
+
const tileMenu = $('menu-tile-terminals');
|
|
705
|
+
if (tileMenu) {
|
|
706
|
+
tileMenu.addEventListener('click', () => {
|
|
707
|
+
emit('panel:close-all-dropdowns');
|
|
708
|
+
tileFloatingTerminals();
|
|
709
|
+
});
|
|
710
|
+
}
|
|
697
711
|
|
|
698
|
-
// Close dropdown on click outside
|
|
712
|
+
// Close dropdown on click outside (the session pills hang inside #topright-controls, but are not the toolbar)
|
|
699
713
|
document.addEventListener('click', (e) => {
|
|
700
714
|
if (!_isOpen) return;
|
|
701
715
|
const container = $('topright-controls') || $('workspace-overlay');
|
|
702
|
-
if (container && !container.contains(e.target)) {
|
|
716
|
+
if (container && (!container.contains(e.target) || e.target.closest('#term-minimized-tray'))) {
|
|
703
717
|
closeDropdown();
|
|
704
718
|
}
|
|
705
719
|
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — how a dispatched worker's tab ends
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// A worker the Assistant dispatched (agent_dispatch) runs as an automation tab
|
|
6
|
+
// in the Claude Code, Codex or OpenCode sidepanel. When its work is over, the
|
|
7
|
+
// tab ends on one card that says how it ended: the task is complete, it is
|
|
8
|
+
// blocked or waits for input, it failed (with the reason) or it was stopped
|
|
9
|
+
// (with who or what stopped it), and a button back to the Assistant session
|
|
10
|
+
// that dispatched it. This module decides what that card says, from two
|
|
11
|
+
// readings of the run:
|
|
12
|
+
// - `run`: the runtime descriptor the sidepanel router attaches with
|
|
13
|
+
// (lib/native-loop-runtime.js cleanDescriptor: status, turnState,
|
|
14
|
+
// stoppedReason, error, runMode, assistantSessionId), and
|
|
15
|
+
// - `dispatch`: the dispatcher's view of the same run (GET
|
|
16
|
+
// /api/assistant/runs/:runId, or an `assistant:dispatch` event: state,
|
|
17
|
+
// outcome, lastResult, completionReason, error, costUsd, turns).
|
|
18
|
+
// The runtime status alone cannot tell a finished task from a stopped one: a
|
|
19
|
+
// warm worker that reported done is released by an idle timeout, by the
|
|
20
|
+
// Assistant's agent_stop or by its tab closing, and each of those ends the
|
|
21
|
+
// runtime run as "stopped". The worker's own `## Result` (the dispatcher's
|
|
22
|
+
// `outcome`) says whether the task was done.
|
|
23
|
+
//
|
|
24
|
+
// No imports and no DOM: `node --test` loads this file as it is
|
|
25
|
+
// (tests/worker-end.test.mjs). WORKER_END_COPY is the English of every
|
|
26
|
+
// sentence; `workerEnd.*` in i18n/en.json must equal it and i18n/pt-BR.json
|
|
27
|
+
// must have the same keys (the test checks both). The page side is
|
|
28
|
+
// ui-worker-end.js.
|
|
29
|
+
|
|
30
|
+
export const WORKER_END_COPY = Object.freeze({
|
|
31
|
+
complete: 'Task complete',
|
|
32
|
+
blocked: 'Blocked',
|
|
33
|
+
needsInput: 'Needs input',
|
|
34
|
+
failed: 'Task failed',
|
|
35
|
+
stopped: 'Stopped',
|
|
36
|
+
interrupted: 'Interrupted',
|
|
37
|
+
back: 'Back to Assistant',
|
|
38
|
+
backHint: 'Close this tab and open the Assistant session that dispatched it',
|
|
39
|
+
warmNote: 'The worker is still open for follow-ups. Going back closes it.',
|
|
40
|
+
failedNoReason: 'The run ended with an error.',
|
|
41
|
+
reasonUser: 'Stopped by you.',
|
|
42
|
+
reasonAssistant: 'Stopped by the Assistant.',
|
|
43
|
+
reasonAssistantSaid: 'Stopped by the Assistant: {reason}',
|
|
44
|
+
reasonTimeCap: 'The run reached its time limit.',
|
|
45
|
+
reasonBudget: 'The run reached its budget limit.',
|
|
46
|
+
reasonMaxTurns: 'The run reached its turn limit.',
|
|
47
|
+
reasonIdle: 'The worker waited for a follow-up that did not come, and closed.',
|
|
48
|
+
reasonRestart: 'The server restarted while the run was open.',
|
|
49
|
+
reasonKillAll: 'Stopped together with every other run.',
|
|
50
|
+
reasonCleared: 'The run\'s state was removed.',
|
|
51
|
+
reasonOther: 'Stopped ({reason}).',
|
|
52
|
+
durationSeconds: '{s}s',
|
|
53
|
+
durationMinutes: '{m}m {s}s',
|
|
54
|
+
durationHours: '{h}h {m}m',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** Marks the card in a transcript: snapshot writers and restorers leave it out (it is drawn again from the run). */
|
|
58
|
+
export const WORKER_END_CLASS = 'worker-end-card';
|
|
59
|
+
|
|
60
|
+
/** The run states (runtime and dispatcher alike) after which nothing more happens. */
|
|
61
|
+
export const TERMINAL_STATES = new Set(['completed', 'failed', 'stopped', 'interrupted']);
|
|
62
|
+
const TERMINAL = TERMINAL_STATES;
|
|
63
|
+
// Dispatcher states in which a turn is in progress (or about to be): the card waits.
|
|
64
|
+
const WORKING = new Set(['queued', 'awaiting_route', 'starting', 'running', 'awaiting_permission']);
|
|
65
|
+
const SUMMARY_CHARS = 320;
|
|
66
|
+
|
|
67
|
+
/** A WORKER_END_COPY sentence with its `{params}` filled in: the translator when the page gives none. */
|
|
68
|
+
export function fillWorkerEndCopy(key, params = {}) {
|
|
69
|
+
return String(WORKER_END_COPY[key] ?? key).replace(/\{(\w+)\}/g, (whole, name) => (params[name] != null ? params[name] : whole));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A run the Assistant dispatched (task mode), as opposed to a loop or a schedule the user started. */
|
|
73
|
+
export function isDispatchedRun(run, dispatch = null) {
|
|
74
|
+
return !!(dispatch?.runId || run?.assistantSessionId || run?.runMode === 'task');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A turn of the run is in progress (or the run has not started one yet). The
|
|
79
|
+
* dispatcher's state wins over the runtime's: it is the one that knows a warm
|
|
80
|
+
* worker between turns is idle.
|
|
81
|
+
*/
|
|
82
|
+
export function isRunWorking(run, dispatch = null) {
|
|
83
|
+
const state = String(dispatch?.state || '');
|
|
84
|
+
if (TERMINAL.has(state) || TERMINAL.has(String(run?.status || ''))) return false;
|
|
85
|
+
if (state) return WORKING.has(state);
|
|
86
|
+
const status = String(run?.status || '');
|
|
87
|
+
if (status !== 'starting' && status !== 'running') return false;
|
|
88
|
+
return run?.turnState !== 'idle' && run?.turnState !== 'terminal';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The runtime descriptor says the run may have something to show: it ended, or it waits between turns. */
|
|
92
|
+
export function mayHaveEnded(run) {
|
|
93
|
+
return TERMINAL.has(String(run?.status || '')) || run?.turnState === 'idle';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The Claude Agent SDK's own words for a query SynaBun aborted: what its
|
|
98
|
+
* stream throws when the runtime disposes a worker, which happens to every run
|
|
99
|
+
* that ends, however it ended. Never a failure the user has to read.
|
|
100
|
+
*/
|
|
101
|
+
export function isEndOfRunAbort(message) {
|
|
102
|
+
const text = String(message || '').trim();
|
|
103
|
+
return /^(Claude Code process aborted by user|Connection aborted by user|Operation aborted|disposed)\.?$/i.test(text);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** "4m 12s": how long a run took. */
|
|
107
|
+
export function formatDuration(ms, t = fillWorkerEndCopy) {
|
|
108
|
+
const total = Math.max(0, Math.round((Number(ms) || 0) / 1000));
|
|
109
|
+
const h = Math.floor(total / 3600);
|
|
110
|
+
const m = Math.floor((total % 3600) / 60);
|
|
111
|
+
const s = total % 60;
|
|
112
|
+
if (h) return t('durationHours', { h, m });
|
|
113
|
+
if (m) return t('durationMinutes', { m, s });
|
|
114
|
+
return t('durationSeconds', { s });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** "$0.42"; under a cent is "<$0.01"; nothing for a run that cost nothing or was not priced. */
|
|
118
|
+
export function formatCost(usd) {
|
|
119
|
+
const value = Number(usd);
|
|
120
|
+
if (!Number.isFinite(value) || value <= 0) return '';
|
|
121
|
+
if (value < 0.01) return '<$0.01';
|
|
122
|
+
return `$${value.toFixed(2)}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** A worker's summary as one plain paragraph: no Markdown emphasis or heading marks, clipped. */
|
|
126
|
+
function plain(text, max = SUMMARY_CHARS) {
|
|
127
|
+
const flat = String(text || '')
|
|
128
|
+
.replace(/^#{1,6}\s+/gm, '')
|
|
129
|
+
.replace(/\*\*|__|`/g, '')
|
|
130
|
+
.replace(/\s+/g, ' ')
|
|
131
|
+
.trim();
|
|
132
|
+
return flat.length > max ? `${flat.slice(0, max - 1).trimEnd()}…` : flat;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function time(value) {
|
|
136
|
+
const at = Date.parse(value || '');
|
|
137
|
+
return Number.isFinite(at) ? at : null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** How long the work took: to the end of its last turn when the task got that far, else to the end of the run. */
|
|
141
|
+
function elapsedOf(run, dispatch, untilLastTurn) {
|
|
142
|
+
const startedAt = time(dispatch?.startedAt) ?? time(dispatch?.createdAt) ?? time(run?.startedAt);
|
|
143
|
+
const turns = Array.isArray(dispatch?.turns) ? dispatch.turns : [];
|
|
144
|
+
const lastTurnEnd = untilLastTurn ? time(turns.at(-1)?.endedAt) : null;
|
|
145
|
+
const end = lastTurnEnd ?? time(dispatch?.completedAt) ?? time(dispatch?.finishedAt) ?? time(run?.completedAt);
|
|
146
|
+
if (startedAt != null && end != null && end >= startedAt) return end - startedAt;
|
|
147
|
+
const ms = Number(dispatch?.elapsedMs);
|
|
148
|
+
return Number.isFinite(ms) && ms > 0 ? ms : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function stopReason(reason, t) {
|
|
152
|
+
const code = String(reason || '').trim();
|
|
153
|
+
switch (code) {
|
|
154
|
+
case 'user': return t('reasonUser');
|
|
155
|
+
case 'assistant': case 'complete': return t('reasonAssistant');
|
|
156
|
+
case 'time_cap': return t('reasonTimeCap');
|
|
157
|
+
case 'budget_cap': case 'session_budget_cap': case 'budget_unpriced': return t('reasonBudget');
|
|
158
|
+
case 'max_turns': return t('reasonMaxTurns');
|
|
159
|
+
case 'idle_timeout': return t('reasonIdle');
|
|
160
|
+
case 'server_restart': case 'server_shutdown': return t('reasonRestart');
|
|
161
|
+
case 'kill_all': return t('reasonKillAll');
|
|
162
|
+
case 'state_removed': case 'state_inactive': return t('reasonCleared');
|
|
163
|
+
case '': return '';
|
|
164
|
+
default:
|
|
165
|
+
// The Assistant's agent_stop reason is its own sentence; anything else is a code.
|
|
166
|
+
return /\s/.test(code) || code.length > 40
|
|
167
|
+
? t('reasonAssistantSaid', { reason: plain(code, 200) })
|
|
168
|
+
: t('reasonOther', { reason: code });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* What the end card of a dispatched worker's tab shows, or null when there is
|
|
174
|
+
* no card (not a dispatched run, a turn still in progress, background work
|
|
175
|
+
* still running, or a warm worker that has not reported its task done).
|
|
176
|
+
*
|
|
177
|
+
* @param {object | null} run the runtime descriptor (sidepanel router)
|
|
178
|
+
* @param {object | null} dispatch the dispatcher's view of the run, when known
|
|
179
|
+
* @param {object} [o]
|
|
180
|
+
* @param {(key: string, params?: object) => string} [o.t] a key of WORKER_END_COPY → the sentence in the page's language
|
|
181
|
+
* @param {boolean} [o.busy] background work of the run is still going (a warm worker only: an ended run has none)
|
|
182
|
+
* @param {boolean} [o.stoppedMidTurn] the run was seen working right before it was stopped: a done
|
|
183
|
+
* report from an earlier turn does not make it complete
|
|
184
|
+
* @returns {null | {
|
|
185
|
+
* kind: 'complete' | 'blocked' | 'needs_input' | 'failed' | 'stopped' | 'interrupted',
|
|
186
|
+
* tone: 'ok' | 'warn' | 'err' | 'off',
|
|
187
|
+
* title: string, detail: string, meta: string, note: string,
|
|
188
|
+
* warm: boolean,
|
|
189
|
+
* assistantSessionId: string | null, backLabel: string, backHint: string,
|
|
190
|
+
* sig: string,
|
|
191
|
+
* }}
|
|
192
|
+
*/
|
|
193
|
+
export function workerEndView(run, dispatch = null, { t = fillWorkerEndCopy, busy = false, stoppedMidTurn = false } = {}) {
|
|
194
|
+
if (!run && !dispatch) return null;
|
|
195
|
+
if (!isDispatchedRun(run, dispatch)) return null;
|
|
196
|
+
if (isRunWorking(run, dispatch)) return null;
|
|
197
|
+
|
|
198
|
+
const state = String(dispatch?.state || '');
|
|
199
|
+
const status = String(run?.status || '');
|
|
200
|
+
const endState = TERMINAL.has(state) ? state : (TERMINAL.has(status) ? status : '');
|
|
201
|
+
const result = dispatch?.lastResult && typeof dispatch.lastResult === 'object' ? dispatch.lastResult : null;
|
|
202
|
+
const outcome = String(dispatch?.outcome || result?.status || '');
|
|
203
|
+
const assistantSessionId = dispatch?.assistantSessionId || run?.assistantSessionId || null;
|
|
204
|
+
|
|
205
|
+
let kind = null;
|
|
206
|
+
let detail = '';
|
|
207
|
+
let warm = false;
|
|
208
|
+
if (!endState) {
|
|
209
|
+
// A warm worker between turns: only a task it reported done ends on a card.
|
|
210
|
+
// Background work it started may still be running (and report again).
|
|
211
|
+
if (outcome !== 'done' || busy) return null;
|
|
212
|
+
kind = 'complete';
|
|
213
|
+
warm = true;
|
|
214
|
+
} else if (endState === 'failed') {
|
|
215
|
+
kind = 'failed';
|
|
216
|
+
detail = plain(dispatch?.error || run?.error || '', 400) || t('failedNoReason');
|
|
217
|
+
} else if (outcome === 'done' && !(stoppedMidTurn && endState !== 'completed')) {
|
|
218
|
+
kind = 'complete';
|
|
219
|
+
} else if ((outcome === 'blocked' || outcome === 'needs_input') && !stoppedMidTurn) {
|
|
220
|
+
kind = outcome;
|
|
221
|
+
} else if (endState === 'completed') {
|
|
222
|
+
// Completed without a done report: the Assistant completed it (agent_complete).
|
|
223
|
+
kind = 'complete';
|
|
224
|
+
} else {
|
|
225
|
+
kind = endState === 'interrupted' ? 'interrupted' : 'stopped';
|
|
226
|
+
detail = stopReason(dispatch?.completionReason || run?.stoppedReason || '', t);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (kind === 'complete' || kind === 'blocked') detail = plain(result?.summary || '');
|
|
230
|
+
if (kind === 'needs_input') detail = plain(result?.question || result?.summary || '');
|
|
231
|
+
|
|
232
|
+
const titles = { complete: 'complete', blocked: 'blocked', needs_input: 'needsInput', failed: 'failed', stopped: 'stopped', interrupted: 'interrupted' };
|
|
233
|
+
const tones = { complete: 'ok', blocked: 'warn', needs_input: 'warn', failed: 'err', stopped: 'off', interrupted: 'off' };
|
|
234
|
+
const elapsed = elapsedOf(run, dispatch, kind === 'complete' || kind === 'blocked' || kind === 'needs_input');
|
|
235
|
+
const meta = [elapsed != null ? formatDuration(elapsed, t) : '', formatCost(dispatch?.costUsd)].filter(Boolean).join(' · ');
|
|
236
|
+
const view = {
|
|
237
|
+
kind,
|
|
238
|
+
tone: tones[kind],
|
|
239
|
+
title: t(titles[kind]),
|
|
240
|
+
detail,
|
|
241
|
+
meta,
|
|
242
|
+
note: warm && assistantSessionId ? t('warmNote') : '',
|
|
243
|
+
warm,
|
|
244
|
+
assistantSessionId,
|
|
245
|
+
backLabel: assistantSessionId ? t('back') : '',
|
|
246
|
+
backHint: assistantSessionId ? t('backHint') : '',
|
|
247
|
+
sig: '',
|
|
248
|
+
};
|
|
249
|
+
view.sig = JSON.stringify([view.kind, view.title, view.detail, view.meta, view.note, view.assistantSessionId, view.backLabel]);
|
|
250
|
+
return view;
|
|
251
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
// Bump when offline.html changes shape: the new worker drops the old cache and
|
|
2
|
+
// stores the page again (v13: a restart read from the supervisor's report).
|
|
3
|
+
const CACHE_NAME = 'synabun-offline-v13';
|
|
2
4
|
const OFFLINE_URL = '/offline.html';
|
|
3
5
|
|
|
4
6
|
self.addEventListener('install', (event) => {
|
|
@@ -21,7 +21,7 @@ import { initI18n, t } from '../../shared/i18n.js';
|
|
|
21
21
|
// ── Shared UI modules (side-effect: each registers its own event listeners) ──
|
|
22
22
|
import { initTooltip } from '../../shared/ui-tooltip.js';
|
|
23
23
|
import { initPanelSystem, initPinToggle, clampPanelsToViewport } from '../../shared/ui-panels.js';
|
|
24
|
-
import { initLoading, showLoadingError, hideLoading } from '../../shared/ui-loading.js';
|
|
24
|
+
import { initLoading, showLoadingError, hideLoading, rememberHealth } from '../../shared/ui-loading.js';
|
|
25
25
|
import { initNavbar } from '../../shared/ui-navbar.js';
|
|
26
26
|
import { initMenubar } from '../../shared/ui-menubar.js';
|
|
27
27
|
import { initSearch } from '../../shared/ui-search.js';
|
|
@@ -304,6 +304,7 @@ async function boot() {
|
|
|
304
304
|
try {
|
|
305
305
|
const healthRes = await fetch('/api/health');
|
|
306
306
|
const health = await healthRes.json();
|
|
307
|
+
rememberHealth(health);
|
|
307
308
|
if (!health.ok) {
|
|
308
309
|
const messages = {
|
|
309
310
|
db_missing: [t('loading.health.databaseUnreachable.title'), health.detail || t('loading.health.databaseUnreachable.sub')],
|