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
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — File Explorer: what decides
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// Everything of the explorer that needs no page: paths, order, what git says
|
|
6
|
+
// about a row, the trees of the filter and of "changed only", selection, what
|
|
7
|
+
// a key means, what a menu holds, how a failure is worded, what is kept per
|
|
8
|
+
// project. The modules that draw (fx-tree.js, fx-popups.js) and the one that
|
|
9
|
+
// acts (fx-ops.js) ask here. The only import is the names module, which has
|
|
10
|
+
// none: `node --test` loads this file as it is.
|
|
11
|
+
|
|
12
|
+
import { compareNames } from './fx-names.js';
|
|
13
|
+
|
|
14
|
+
// ── Paths (always with forward slashes, never ending in one) ──
|
|
15
|
+
|
|
16
|
+
export const slashes = (path) => String(path ?? '').replace(/\\/g, '/');
|
|
17
|
+
export const trimSlash = (path) => { const p = slashes(path); return p.length > 1 ? p.replace(/\/+$/, '') : p; };
|
|
18
|
+
export const baseName = (path) => { const p = trimSlash(path); return p.slice(p.lastIndexOf('/') + 1); };
|
|
19
|
+
export const dirName = (path) => { const p = trimSlash(path); const cut = p.lastIndexOf('/'); return cut <= 0 ? p.slice(0, cut + 1) : p.slice(0, cut); };
|
|
20
|
+
export const joinPath = (dir, name) => (dir.endsWith('/') ? dir + name : `${dir}/${name}`);
|
|
21
|
+
|
|
22
|
+
/** `path` below `root` ('' for the root itself), or null when it is not inside it. */
|
|
23
|
+
export function relativeTo(root, path) {
|
|
24
|
+
const r = trimSlash(root); const p = trimSlash(path);
|
|
25
|
+
if (p === r) return '';
|
|
26
|
+
return p.startsWith(`${r}/`) ? p.slice(r.length + 1) : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Whether `path` is `folder` or below it. */
|
|
30
|
+
export const isWithin = (folder, path) => relativeTo(folder, path) !== null;
|
|
31
|
+
|
|
32
|
+
/** Of several paths, the ones not inside another of them (a folder and a file in it are one thing to move). */
|
|
33
|
+
export function topLevelOf(paths) {
|
|
34
|
+
const list = [...new Set(paths.map(trimSlash))];
|
|
35
|
+
return list.filter((path) => !list.some((other) => other !== path && isWithin(other, path)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Order ──
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Folders first, then by name in natural, case-insensitive order; or files by the time they changed or by size.
|
|
42
|
+
* @param {{ name: string, type: string, size?: number, mtime?: number }[]} items
|
|
43
|
+
* @param {'name' | 'date' | 'size'} [mode]
|
|
44
|
+
*/
|
|
45
|
+
export function sortItems(items, mode = 'name') {
|
|
46
|
+
return [...items].sort((a, b) => {
|
|
47
|
+
if (a.type !== b.type) return a.type === 'dir' ? -1 : 1;
|
|
48
|
+
if (a.type === 'file' && mode === 'date' && (a.mtime || b.mtime) && a.mtime !== b.mtime) return (b.mtime || 0) - (a.mtime || 0);
|
|
49
|
+
if (a.type === 'file' && mode === 'size' && a.size !== b.size) return (b.size || 0) - (a.size || 0);
|
|
50
|
+
return compareNames(a.name, b.name);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Whether two listings show the same rows (a folder that was read again and did not change is not drawn again). */
|
|
55
|
+
export function sameListing(a, b) {
|
|
56
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
57
|
+
for (let i = 0; i < a.length; i++) {
|
|
58
|
+
const x = a[i]; const y = b[i];
|
|
59
|
+
if (x.name !== y.name || x.type !== y.type || x.size !== y.size || x.mtime !== y.mtime || !!x.link !== !!y.link || !!x.outside !== !!y.outside || !!x.broken !== !!y.broken) return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Of several readings of one thing that overlap, the one that was asked for last is the one that counts: a
|
|
66
|
+
* folder is read when it is opened and again when the watcher tells of a change, and the older answer may be the
|
|
67
|
+
* one that arrives last. `take(key)` begins a reading and answers a function that says, when its answer is
|
|
68
|
+
* there, whether that reading is still the latest one of `key`. An answer that is not is dropped.
|
|
69
|
+
* @returns {{ take: (key: string) => () => boolean, clear: () => void }}
|
|
70
|
+
*/
|
|
71
|
+
export function latestOnly() {
|
|
72
|
+
const turns = new Map(); // key → the number of the reading asked for last
|
|
73
|
+
return {
|
|
74
|
+
take(key) {
|
|
75
|
+
const turn = (turns.get(key) || 0) + 1;
|
|
76
|
+
turns.set(key, turn);
|
|
77
|
+
return () => turns.get(key) === turn;
|
|
78
|
+
},
|
|
79
|
+
clear() { turns.clear(); },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Git ──
|
|
84
|
+
|
|
85
|
+
/** The letter a row shows for its state, and the tone it is drawn in. */
|
|
86
|
+
export const GIT_MARKS = Object.freeze({
|
|
87
|
+
modified: { letter: 'M', tone: 'modified' },
|
|
88
|
+
mixed: { letter: 'M', tone: 'modified' },
|
|
89
|
+
staged: { letter: 'M', tone: 'modified' },
|
|
90
|
+
renamed: { letter: 'R', tone: 'modified' },
|
|
91
|
+
added: { letter: 'A', tone: 'new' },
|
|
92
|
+
untracked: { letter: 'U', tone: 'new' },
|
|
93
|
+
deleted: { letter: 'D', tone: 'deleted' },
|
|
94
|
+
conflict: { letter: '!', tone: 'conflict' },
|
|
95
|
+
});
|
|
96
|
+
const TONE_RANK = { new: 1, deleted: 2, modified: 2, conflict: 3 };
|
|
97
|
+
|
|
98
|
+
const NO_GIT = Object.freeze({ isGit: false, branch: '', count: 0, statusOf: () => null, toneOf: () => null, isIgnored: () => false, changed: () => [] });
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* What git says about the paths of a project, ready to be asked row by row.
|
|
102
|
+
* @param {{ isGit?: boolean, branch?: string, changes?: Record<string, string>, ignored?: string[] } | null} state
|
|
103
|
+
* what GET /api/explorer/git answered: paths below the project, a folder ends in `/`
|
|
104
|
+
* @returns {{ isGit: boolean, branch: string, count: number,
|
|
105
|
+
* statusOf: (rel: string, isDir: boolean) => string | null, toneOf: (rel: string, isDir: boolean) => string | null,
|
|
106
|
+
* isIgnored: (rel: string) => boolean, changed: () => { path: string, type: string, status: string }[] }}
|
|
107
|
+
* a file's status is its own, or `untracked` inside a new folder; a folder's tone is the strongest of what it holds
|
|
108
|
+
*/
|
|
109
|
+
export function gitIndex(state) {
|
|
110
|
+
if (!state?.isGit) return NO_GIT;
|
|
111
|
+
const files = new Map(); // file → status
|
|
112
|
+
const wholeDirs = new Map(); // folder git names as a whole (new: nothing in it is tracked) → status
|
|
113
|
+
const holds = new Map(); // folder → the strongest tone below it
|
|
114
|
+
const entries = Object.entries(state.changes || {});
|
|
115
|
+
for (const [path, status] of entries) {
|
|
116
|
+
const isDir = path.endsWith('/');
|
|
117
|
+
const clean = isDir ? path.slice(0, -1) : path;
|
|
118
|
+
if (!clean || !GIT_MARKS[status]) continue;
|
|
119
|
+
(isDir ? wholeDirs : files).set(clean, status);
|
|
120
|
+
const tone = GIT_MARKS[status].tone;
|
|
121
|
+
for (let at = clean; ;) {
|
|
122
|
+
const cut = at.lastIndexOf('/');
|
|
123
|
+
if (cut < 0) break;
|
|
124
|
+
at = at.slice(0, cut);
|
|
125
|
+
if ((TONE_RANK[holds.get(at)] || 0) < TONE_RANK[tone]) holds.set(at, tone === 'deleted' ? 'modified' : tone);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const ignoredDirs = new Set(); const ignoredFiles = new Set();
|
|
129
|
+
for (const path of state.ignored || []) (path.endsWith('/') ? ignoredDirs.add(path.slice(0, -1)) : ignoredFiles.add(path));
|
|
130
|
+
const above = (rel, table) => {
|
|
131
|
+
for (let at = rel; ;) {
|
|
132
|
+
const cut = at.lastIndexOf('/');
|
|
133
|
+
if (cut < 0) return undefined;
|
|
134
|
+
at = at.slice(0, cut);
|
|
135
|
+
if (table.has(at)) return table instanceof Map ? table.get(at) : true;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const statusOf = (rel, isDir) => {
|
|
139
|
+
if (!rel) return null;
|
|
140
|
+
if (isDir) return wholeDirs.get(rel) || above(rel, wholeDirs) || null;
|
|
141
|
+
return files.get(rel) || above(rel, wholeDirs) || null;
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
isGit: true,
|
|
145
|
+
branch: state.branch || '',
|
|
146
|
+
count: entries.length,
|
|
147
|
+
statusOf,
|
|
148
|
+
toneOf(rel, isDir) {
|
|
149
|
+
const own = statusOf(rel, isDir);
|
|
150
|
+
if (own) return GIT_MARKS[own].tone;
|
|
151
|
+
return isDir ? holds.get(rel) || null : null;
|
|
152
|
+
},
|
|
153
|
+
// git's own folder is not a file of the project either: it is dimmed with what git ignores.
|
|
154
|
+
isIgnored: (rel) => !!rel && (rel === '.git' || rel.startsWith('.git/') || ignoredFiles.has(rel) || ignoredDirs.has(rel) || !!above(rel, ignoredDirs)),
|
|
155
|
+
changed: () => entries.filter(([, status]) => GIT_MARKS[status]).map(([path, status]) => (path.endsWith('/')
|
|
156
|
+
? { path: path.slice(0, -1), type: 'dir', status }
|
|
157
|
+
: { path, type: 'file', status, ...(status === 'deleted' ? { missing: true } : {}) })),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── A tree made of paths (the filter's answer, and "changed only") ──
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The folders a list of paths spans, each with the entries to show in it.
|
|
165
|
+
* @param {{ path: string, type: 'dir' | 'file' }[]} entries paths below the project
|
|
166
|
+
* @returns {Map<string, object[]>} folder below the project ('' is the project) → its entries; an entry that was
|
|
167
|
+
* listed carries `hit: true`, a folder that is only on the way to one does not
|
|
168
|
+
*/
|
|
169
|
+
export function buildPathTree(entries) {
|
|
170
|
+
const folders = new Map();
|
|
171
|
+
const at = (dir) => { if (!folders.has(dir)) folders.set(dir, new Map()); return folders.get(dir); };
|
|
172
|
+
at('');
|
|
173
|
+
for (const entry of entries) {
|
|
174
|
+
const parts = String(entry.path || '').split('/').filter(Boolean);
|
|
175
|
+
if (!parts.length) continue;
|
|
176
|
+
let dir = '';
|
|
177
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
178
|
+
const inside = at(dir);
|
|
179
|
+
if (!inside.has(parts[i])) inside.set(parts[i], { name: parts[i], type: 'dir' });
|
|
180
|
+
else if (inside.get(parts[i]).type !== 'dir') inside.set(parts[i], { ...inside.get(parts[i]), type: 'dir' });
|
|
181
|
+
dir = dir ? `${dir}/${parts[i]}` : parts[i];
|
|
182
|
+
at(dir);
|
|
183
|
+
}
|
|
184
|
+
const name = parts[parts.length - 1];
|
|
185
|
+
const { path, ...rest } = entry;
|
|
186
|
+
at(dir).set(name, { ...(at(dir).get(name) || {}), ...rest, name, hit: true });
|
|
187
|
+
}
|
|
188
|
+
return new Map([...folders].map(([dir, inside]) => [dir, [...inside.values()]]));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Where the first word of what was typed is in a name, as `[start, end]`, or null. */
|
|
192
|
+
export function matchRange(name, query) {
|
|
193
|
+
const word = String(query || '').trim().toLowerCase().split(/\s+/)[0] || '';
|
|
194
|
+
const plain = word.includes('/') ? word.slice(word.lastIndexOf('/') + 1) : word;
|
|
195
|
+
if (!plain) return null;
|
|
196
|
+
const at = String(name).toLowerCase().indexOf(plain);
|
|
197
|
+
return at < 0 ? null : [at, at + plain.length];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Whether a name holds every word of what was typed (the same rule the server's search uses for names). */
|
|
201
|
+
export function nameMatches(name, query) {
|
|
202
|
+
const hay = String(name).toLowerCase();
|
|
203
|
+
return String(query || '').trim().toLowerCase().split(/\s+/).filter(Boolean).every((word) => hay.includes(word));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Selection and the keyboard ──
|
|
207
|
+
|
|
208
|
+
/** The rows from one to another in the order they are shown, both included. */
|
|
209
|
+
export function rangeBetween(order, from, to) {
|
|
210
|
+
const a = order.indexOf(from); const b = order.indexOf(to);
|
|
211
|
+
if (a < 0 || b < 0) return to ? [to] : [];
|
|
212
|
+
return order.slice(Math.min(a, b), Math.max(a, b) + 1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The selection after a row was picked.
|
|
217
|
+
* @param {{ selected: Iterable<string>, anchor: string | null }} state
|
|
218
|
+
* @param {string} path the row
|
|
219
|
+
* @param {{ toggle?: boolean, range?: boolean }} how `toggle`: Ctrl / Cmd; `range`: Shift
|
|
220
|
+
* @param {string[]} order the rows as they are shown
|
|
221
|
+
* @returns {{ selected: Set<string>, anchor: string | null, focus: string }}
|
|
222
|
+
*/
|
|
223
|
+
export function pickRow(state, path, { toggle = false, range = false } = {}, order = []) {
|
|
224
|
+
if (range && state.anchor && order.includes(state.anchor)) {
|
|
225
|
+
return { selected: new Set(rangeBetween(order, state.anchor, path)), anchor: state.anchor, focus: path };
|
|
226
|
+
}
|
|
227
|
+
if (toggle) {
|
|
228
|
+
const selected = new Set(state.selected);
|
|
229
|
+
if (selected.has(path)) selected.delete(path); else selected.add(path);
|
|
230
|
+
return { selected, anchor: path, focus: path };
|
|
231
|
+
}
|
|
232
|
+
return { selected: new Set([path]), anchor: path, focus: path };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** The row a movement key lands on. */
|
|
236
|
+
export function moveTarget(order, current, move, { page = 10 } = {}) {
|
|
237
|
+
if (!order.length) return null;
|
|
238
|
+
const at = order.indexOf(current);
|
|
239
|
+
if (at < 0) return move === 'end' || move === 'up' || move === 'pageUp' ? order[order.length - 1] : order[0];
|
|
240
|
+
switch (move) {
|
|
241
|
+
case 'down': return order[Math.min(order.length - 1, at + 1)];
|
|
242
|
+
case 'up': return order[Math.max(0, at - 1)];
|
|
243
|
+
case 'home': return order[0];
|
|
244
|
+
case 'end': return order[order.length - 1];
|
|
245
|
+
case 'pageDown': return order[Math.min(order.length - 1, at + page)];
|
|
246
|
+
case 'pageUp': return order[Math.max(0, at - page)];
|
|
247
|
+
default: return current;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The row typing goes to: the next one whose name begins with what was typed (one letter typed again moves on to
|
|
253
|
+
* the next such row).
|
|
254
|
+
* @param {string[]} order @param {(path: string) => string} nameOf @param {string | null} current @param {string} typed
|
|
255
|
+
*/
|
|
256
|
+
export function typeAheadTarget(order, nameOf, current, typed) {
|
|
257
|
+
const want = String(typed || '').toLowerCase();
|
|
258
|
+
if (!want || !order.length) return null;
|
|
259
|
+
const repeated = want.length > 1 && [...want].every((letter) => letter === want[0]);
|
|
260
|
+
const prefix = repeated ? want[0] : want;
|
|
261
|
+
const from = order.indexOf(current);
|
|
262
|
+
// More letters narrow down from the row the keyboard is on; one letter (or the same again) starts after it.
|
|
263
|
+
const start = prefix.length > 1 ? Math.max(0, from) : from + 1;
|
|
264
|
+
for (let i = 0; i < order.length; i++) {
|
|
265
|
+
const path = order[(start + i + order.length) % order.length];
|
|
266
|
+
if (nameOf(path).toLowerCase().startsWith(prefix)) return path;
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* What a key means in the tree.
|
|
273
|
+
* @param {{ key: string, code?: string, ctrlKey?: boolean, metaKey?: boolean, altKey?: boolean, shiftKey?: boolean }} event
|
|
274
|
+
* @param {{ mac?: boolean }} [o]
|
|
275
|
+
* @returns {{ action: string, extend?: boolean, keep?: boolean } | { type: string } | null} `extend`: Shift widens the
|
|
276
|
+
* selection; `keep`: Ctrl / Cmd moves the keyboard without selecting; `type`: a letter for type-ahead
|
|
277
|
+
*/
|
|
278
|
+
export function keyAction(event, { mac = false } = {}) {
|
|
279
|
+
const mod = mac ? !!event.metaKey : !!event.ctrlKey;
|
|
280
|
+
const otherMod = mac ? !!event.ctrlKey : !!event.metaKey;
|
|
281
|
+
const { key } = event;
|
|
282
|
+
const moves = { ArrowDown: 'down', ArrowUp: 'up', Home: 'home', End: 'end', PageDown: 'pageDown', PageUp: 'pageUp' };
|
|
283
|
+
if (moves[key] && !event.altKey) return { action: moves[key], extend: !!event.shiftKey, keep: mod };
|
|
284
|
+
if (event.altKey && mod && (event.code === 'KeyC' || key === 'c' || key === 'C' || key === 'ç')) return { action: event.shiftKey ? 'copyRelativePath' : 'copyPath' };
|
|
285
|
+
// Ctrl+Space on every system (Cmd+Space is the system's own on a Mac).
|
|
286
|
+
if (event.ctrlKey && !event.altKey && !event.metaKey && key === ' ') return { action: 'toggle' };
|
|
287
|
+
if (event.altKey || otherMod) return null;
|
|
288
|
+
if (mod) {
|
|
289
|
+
if (mac && key === 'Backspace') return { action: 'delete' };
|
|
290
|
+
switch (key.toLowerCase()) {
|
|
291
|
+
case 'c': return { action: 'copy' };
|
|
292
|
+
case 'x': return { action: 'cut' };
|
|
293
|
+
case 'v': return { action: 'paste' };
|
|
294
|
+
case 'a': return { action: 'selectAll' };
|
|
295
|
+
case 'p': return event.shiftKey ? null : { action: 'jump' };
|
|
296
|
+
default: return null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
switch (key) {
|
|
300
|
+
case 'ArrowRight': return { action: 'right' };
|
|
301
|
+
case 'ArrowLeft': return { action: 'left' };
|
|
302
|
+
case 'Enter': return { action: 'open' };
|
|
303
|
+
case ' ': return { action: 'peek' };
|
|
304
|
+
case 'F2': return { action: 'rename' };
|
|
305
|
+
case 'Delete': return { action: 'delete' };
|
|
306
|
+
case 'ContextMenu': return { action: 'menu' };
|
|
307
|
+
case 'F10': return event.shiftKey ? { action: 'menu' } : null;
|
|
308
|
+
case 'Escape': return { action: 'escape' };
|
|
309
|
+
default:
|
|
310
|
+
}
|
|
311
|
+
// A letter, a digit or a sign: the name that is being typed.
|
|
312
|
+
if (key.length === 1 && key !== ' ') return { type: key };
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Whether rows may be dropped into a folder: never into themselves or below themselves; a move, not where they already are. */
|
|
317
|
+
export function canDropInto(sources, target, { copy = false } = {}) {
|
|
318
|
+
const into = trimSlash(target);
|
|
319
|
+
const list = topLevelOf(sources);
|
|
320
|
+
if (!list.length) return false;
|
|
321
|
+
if (list.some((path) => isWithin(path, into))) return false;
|
|
322
|
+
return copy || list.some((path) => dirName(path) !== into);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── What a file is ──
|
|
326
|
+
|
|
327
|
+
const IMAGE_EXT = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'ico', 'avif', 'svg']);
|
|
328
|
+
const BINARY_KINDS = {
|
|
329
|
+
archive: ['zip', 'tar', 'gz', 'tgz', 'bz2', 'xz', '7z', 'rar', 'zst', 'jar', 'war'],
|
|
330
|
+
document: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp', 'pages', 'numbers', 'key', 'epub'],
|
|
331
|
+
audio: ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac', 'aiff', 'opus'],
|
|
332
|
+
video: ['mp4', 'mov', 'mkv', 'webm', 'avi', 'm4v', 'wmv'],
|
|
333
|
+
font: ['woff', 'woff2', 'ttf', 'otf', 'eot'],
|
|
334
|
+
database: ['db', 'sqlite', 'sqlite3', 'db-wal', 'db-shm', 'mdb'],
|
|
335
|
+
program: ['exe', 'dll', 'so', 'dylib', 'bin', 'o', 'a', 'class', 'wasm', 'node', 'pyc', 'app'],
|
|
336
|
+
diskImage: ['dmg', 'iso', 'pkg', 'deb', 'rpm', 'msi', 'apk', 'ipa'],
|
|
337
|
+
design: ['psd', 'ai', 'sketch', 'fig', 'xd', 'heic', 'tif', 'tiff', 'raw'],
|
|
338
|
+
};
|
|
339
|
+
const BINARY_BY_EXT = new Map(Object.entries(BINARY_KINDS).flatMap(([kind, list]) => list.map((ext) => [ext, kind])));
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* What a file is, by its name: text the editor opens, an image the explorer previews, or something else it names.
|
|
343
|
+
* @returns {{ kind: 'text' | 'image' | 'binary', type?: string, alsoText?: boolean }} `type`: which kind of
|
|
344
|
+
* binary (the key of its name: fileExplorer.kind.*); `alsoText`: an image that is text too (SVG)
|
|
345
|
+
*/
|
|
346
|
+
export function fileKind(name) {
|
|
347
|
+
const lower = String(name || '').toLowerCase();
|
|
348
|
+
if (lower === '.ds_store' || lower === 'thumbs.db') return { kind: 'binary', type: 'system' };
|
|
349
|
+
const dot = lower.lastIndexOf('.');
|
|
350
|
+
const ext = dot > 0 ? lower.slice(dot + 1) : '';
|
|
351
|
+
if (IMAGE_EXT.has(ext)) return { kind: 'image', ...(ext === 'svg' ? { alsoText: true } : {}) };
|
|
352
|
+
if (BINARY_BY_EXT.has(ext)) return { kind: 'binary', type: BINARY_BY_EXT.get(ext) };
|
|
353
|
+
return { kind: 'text' };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** A size in the unit that reads best. */
|
|
357
|
+
export function formatBytes(bytes) {
|
|
358
|
+
const n = Number(bytes);
|
|
359
|
+
if (!Number.isFinite(n) || n < 0) return '';
|
|
360
|
+
if (n < 1024) return `${n} B`;
|
|
361
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
362
|
+
let value = n / 1024; let unit = 0;
|
|
363
|
+
while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit++; }
|
|
364
|
+
return `${value >= 100 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ── Failures, in words ──
|
|
368
|
+
|
|
369
|
+
const NAME_REASONS = new Set(['empty', 'dots', 'separator', 'control', 'reserved', 'chars', 'trailing', 'long']);
|
|
370
|
+
const FAILURE_KEYS = {
|
|
371
|
+
exists: 'exists', gone: 'gone', denied: 'denied', root: 'root', 'into-self': 'intoSelf', 'too-large': 'tooLarge', busy: 'busy',
|
|
372
|
+
'no-space': 'noSpace', outside: 'outside', 'not-dir': 'notDir', offline: 'offline', 'no-reveal': 'noReveal', 'no-trash': 'noTrash',
|
|
373
|
+
changed: 'changed', 'other-disk': 'otherDisk',
|
|
374
|
+
unsupported: 'needsRestart', GUEST_FORBIDDEN: 'owner', BAD_HOST: 'owner', BAD_ORIGIN: 'owner', limit: 'limit', 'not-image': 'notImage',
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* The sentence for a failure, as an i18n key and its parameters.
|
|
379
|
+
* @param {{ code?: string, reason?: string, name?: string, conflicts?: string[] }} failure what a route answered
|
|
380
|
+
* (`code` 'offline' when it did not answer at all)
|
|
381
|
+
*/
|
|
382
|
+
export function failureMessage(failure = {}) {
|
|
383
|
+
const name = failure.name || failure.conflicts?.[0] || '';
|
|
384
|
+
if (failure.code === 'invalid-name') {
|
|
385
|
+
return { key: `fileExplorer.error.name.${NAME_REASONS.has(failure.reason) ? failure.reason : 'other'}`, params: { name } };
|
|
386
|
+
}
|
|
387
|
+
return { key: `fileExplorer.error.${FAILURE_KEYS[failure.code] || 'failed'}`, params: { name } };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const STOPPED_KEYS = { move: 'fileExplorer.move.stopped', copy: 'fileExplorer.copy.stopped', delete: 'fileExplorer.delete.stopped' };
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* What to say of a move, a copy or a delete that did not go through: how far it got, the entry it stopped at, and
|
|
394
|
+
* why. When nothing was done before it stopped, the reason alone.
|
|
395
|
+
* @param {'move' | 'copy' | 'delete' | 'trash'} what `trash`: a delete to the Trash
|
|
396
|
+
* @param {{ done?: number, total?: number, failure?: object, trash?: 'win32' | 'other' }} o `done` of `total` went
|
|
397
|
+
* through; `failure` is what the route answered (`failed`: the path it stopped at; `partial`: a copy that was
|
|
398
|
+
* begun under that path and is not whole)
|
|
399
|
+
* @returns {{ key: string, params: object, why: { key: string, params: object } | null, partial: { key: string, params: object } | null }}
|
|
400
|
+
* `why`, once worded, is the `{why}` of the sentence; null when the sentence is the reason itself. `partial` is
|
|
401
|
+
* a sentence of its own, said after it: what the copy stopped at is at the destination in part
|
|
402
|
+
*/
|
|
403
|
+
export function stoppedMessage(what, { done = 0, total = 0, failure = {}, trash = 'other' } = {}) {
|
|
404
|
+
const name = failure.name || (failure.failed ? baseName(failure.failed) : '') || failure.conflicts?.[0] || '';
|
|
405
|
+
const why = failureMessage({ ...failure, name });
|
|
406
|
+
const partial = typeof failure.partial === 'string' && failure.partial ? { key: 'fileExplorer.copy.partial', params: { name: baseName(failure.partial) } } : null;
|
|
407
|
+
if (!(done > 0)) return { ...why, why: null, partial };
|
|
408
|
+
const key = what === 'trash' ? `fileExplorer.delete.stoppedTrash.${trash === 'win32' ? 'win32' : 'other'}` : STOPPED_KEYS[what] || STOPPED_KEYS.move;
|
|
409
|
+
return { key, params: { done, total, name }, why, partial };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── The menu ──
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* What the context menu holds, in groups.
|
|
416
|
+
* @param {object} c
|
|
417
|
+
* @param {'file' | 'dir' | 'multi' | 'empty'} c.target what was right-clicked: one file, one folder, several rows, or
|
|
418
|
+
* the space below the rows (the project itself)
|
|
419
|
+
* @param {number} [c.count] how many rows
|
|
420
|
+
* @param {{ ops?: boolean, reveal?: boolean }} [c.caps] what the server can do; the rest is listed and says why not
|
|
421
|
+
* @param {boolean} [c.expanded] a folder that is open
|
|
422
|
+
* @param {boolean} [c.canPaste] something was cut or copied
|
|
423
|
+
* @param {'text' | 'image' | 'binary'} [c.kind] of a file
|
|
424
|
+
* @param {boolean} [c.missing] a file git lists as deleted: it is not on disk
|
|
425
|
+
* @param {boolean} [c.changedOnly] @param {string} [c.sort] the view's state, for the menu of the empty space
|
|
426
|
+
* @returns {({ separator: true } | { id: string, key?: string, danger?: boolean, disabled?: boolean, checked?: boolean, more?: boolean })[]}
|
|
427
|
+
* `key`: its shortcut in the editor's notation (`Mod-c`); `more`: it opens a second page of the menu
|
|
428
|
+
*/
|
|
429
|
+
export function menuModel({ target, count = 1, caps = {}, expanded = false, canPaste = false, kind = 'text', missing = false, changedOnly = false, sort = 'name' }) {
|
|
430
|
+
const ops = !!caps.ops;
|
|
431
|
+
const needs = (item) => (ops ? item : { ...item, disabled: true, why: 'restart' });
|
|
432
|
+
const sep = { separator: true };
|
|
433
|
+
const send = [{ id: 'send' }, { id: 'sendTo', more: true }];
|
|
434
|
+
const paths = [{ id: 'copyPath', key: 'Mod-Alt-c' }, { id: 'copyRelativePath', key: 'Mod-Shift-Alt-c' }];
|
|
435
|
+
const reveal = caps.reveal ? { id: 'reveal' } : { id: 'reveal', disabled: true, why: ops ? 'noReveal' : 'restart' };
|
|
436
|
+
if (target === 'empty') {
|
|
437
|
+
return [
|
|
438
|
+
{ id: 'newFile' }, { id: 'newFolder' }, sep,
|
|
439
|
+
needs({ id: 'paste', key: 'Mod-v', ...(canPaste ? {} : { disabled: true, why: 'nothingCopied' }) }), sep,
|
|
440
|
+
...paths.slice(0, 1), reveal, sep,
|
|
441
|
+
{ id: 'changedOnly', checked: changedOnly }, { id: 'sortBy', more: true, value: sort }, sep,
|
|
442
|
+
{ id: 'expandAll' }, { id: 'collapseAll' }, { id: 'refresh' },
|
|
443
|
+
];
|
|
444
|
+
}
|
|
445
|
+
if (missing) return [...paths, { id: 'copyName' }];
|
|
446
|
+
if (target === 'multi') {
|
|
447
|
+
return [
|
|
448
|
+
...send, sep,
|
|
449
|
+
needs({ id: 'cut', key: 'Mod-x' }), needs({ id: 'copy', key: 'Mod-c' }), sep,
|
|
450
|
+
...paths, sep,
|
|
451
|
+
{ id: 'delete', key: 'Delete', danger: true, count },
|
|
452
|
+
];
|
|
453
|
+
}
|
|
454
|
+
const edit = [needs({ id: 'cut', key: 'Mod-x' }), needs({ id: 'copy', key: 'Mod-c' })];
|
|
455
|
+
const tail = [needs({ id: 'rename', key: 'F2' }), { id: 'delete', key: 'Delete', danger: true }, sep, reveal];
|
|
456
|
+
if (target === 'dir') {
|
|
457
|
+
return [
|
|
458
|
+
{ id: 'newFile' }, { id: 'newFolder' }, sep,
|
|
459
|
+
expanded ? { id: 'collapse', key: 'ArrowLeft' } : { id: 'expand', key: 'ArrowRight' },
|
|
460
|
+
...(expanded ? [{ id: 'collapseChildren' }] : []), sep,
|
|
461
|
+
...send, sep,
|
|
462
|
+
...edit, needs({ id: 'paste', key: 'Mod-v', ...(canPaste ? {} : { disabled: true, why: 'nothingCopied' }) }), needs({ id: 'duplicate' }), sep,
|
|
463
|
+
...paths, { id: 'copyName' }, sep,
|
|
464
|
+
{ id: 'color', more: true }, sep,
|
|
465
|
+
...tail,
|
|
466
|
+
];
|
|
467
|
+
}
|
|
468
|
+
return [
|
|
469
|
+
// Each entry beside its own key: Space looks at a file in the reused tab, Enter keeps the tab.
|
|
470
|
+
kind === 'text' ? { id: 'open', key: 'Space' } : { id: 'preview', key: 'Enter' },
|
|
471
|
+
...(kind === 'text' ? [{ id: 'openKeep', key: 'Enter' }] : kind === 'image' ? [{ id: 'openAsText' }] : []), sep,
|
|
472
|
+
...send, sep,
|
|
473
|
+
...edit, needs({ id: 'duplicate' }), sep,
|
|
474
|
+
...paths, { id: 'copyName' }, sep,
|
|
475
|
+
...tail,
|
|
476
|
+
];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** The items of a menu the keyboard can stop on, in order. */
|
|
480
|
+
export const menuStops = (items) => items.filter((item) => !item.separator && !item.disabled);
|
|
481
|
+
|
|
482
|
+
// ── What is kept of the tree, per project ──
|
|
483
|
+
|
|
484
|
+
export const TREE_STATE_VERSION = 1;
|
|
485
|
+
export const MAX_KEPT_PROJECTS = 30;
|
|
486
|
+
export const MAX_KEPT_EXPANDED = 400;
|
|
487
|
+
export const MAX_KEPT_SELECTED = 50;
|
|
488
|
+
|
|
489
|
+
const strings = (list, max) => (Array.isArray(list) ? list.filter((entry) => typeof entry === 'string' && entry).slice(0, max) : []);
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* What was kept for a project, cleaned up: its open folders, its selection, the row the keyboard was on, how far
|
|
493
|
+
* it was scrolled and whether it showed only what changed.
|
|
494
|
+
* @param {any} stored the stored value (parsed JSON, or anything)
|
|
495
|
+
* @param {string} project the project's folder
|
|
496
|
+
*/
|
|
497
|
+
export function recallTreeState(stored, project) {
|
|
498
|
+
const kept = stored && stored.v === TREE_STATE_VERSION && stored.projects && typeof stored.projects === 'object' ? stored.projects[trimSlash(project)] : null;
|
|
499
|
+
return {
|
|
500
|
+
expanded: strings(kept?.expanded, MAX_KEPT_EXPANDED),
|
|
501
|
+
selected: strings(kept?.selected, MAX_KEPT_SELECTED),
|
|
502
|
+
focus: typeof kept?.focus === 'string' ? kept.focus : null,
|
|
503
|
+
top: Number.isFinite(kept?.top) && kept.top > 0 ? Math.round(kept.top) : 0,
|
|
504
|
+
changedOnly: kept?.changedOnly === true,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** The stored value with one project's state written into it; the projects not used for longest go first. */
|
|
509
|
+
export function rememberTreeState(stored, project, state, now = Date.now()) {
|
|
510
|
+
const projects = stored && stored.v === TREE_STATE_VERSION && stored.projects && typeof stored.projects === 'object' ? { ...stored.projects } : {};
|
|
511
|
+
projects[trimSlash(project)] = {
|
|
512
|
+
at: now,
|
|
513
|
+
expanded: strings([...(state.expanded || [])], MAX_KEPT_EXPANDED),
|
|
514
|
+
selected: strings([...(state.selected || [])], MAX_KEPT_SELECTED),
|
|
515
|
+
focus: typeof state.focus === 'string' ? state.focus : null,
|
|
516
|
+
top: Number.isFinite(state.top) && state.top > 0 ? Math.round(state.top) : 0,
|
|
517
|
+
changedOnly: state.changedOnly === true,
|
|
518
|
+
};
|
|
519
|
+
const names = Object.keys(projects).sort((a, b) => (projects[b].at || 0) - (projects[a].at || 0)).slice(0, MAX_KEPT_PROJECTS);
|
|
520
|
+
return { v: TREE_STATE_VERSION, projects: Object.fromEntries(names.map((name) => [name, projects[name]])) };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ── A server from before these routes ──
|
|
524
|
+
|
|
525
|
+
/** A folder as GET /api/project-files lists it, in the shape of GET /api/explorer/list, plus what it said of git. */
|
|
526
|
+
export function fromLegacyListing(data) {
|
|
527
|
+
const items = (data?.items || []).map((item) => ({ name: item.name, type: item.type === 'dir' ? 'dir' : 'file', ...(item.size !== undefined ? { size: item.size } : {}), ...(item.mtime !== undefined ? { mtime: item.mtime } : {}) }));
|
|
528
|
+
const git = {};
|
|
529
|
+
for (const item of data?.items || []) if (item.git && GIT_MARKS[item.git]) git[item.name] = item.git;
|
|
530
|
+
return { ok: true, path: trimSlash(data?.path || ''), items, total: data?.total ?? items.length, truncated: !!data?.truncated, branch: data?.branch || null, git };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** A search as GET /api/project-files answers it, in the shape of GET /api/explorer/search. */
|
|
534
|
+
export function fromLegacySearch(data) {
|
|
535
|
+
const items = (data?.items || []).filter((item) => item.relativePath).map((item) => ({ path: slashes(item.relativePath), type: item.type === 'dir' ? 'dir' : 'file' }));
|
|
536
|
+
return { ok: true, items, truncated: items.length >= 100, partial: false };
|
|
537
|
+
}
|