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,229 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Code editor: problems in the file
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// A JSON file that does not parse gets the place of the error underlined, a
|
|
6
|
+
// mark in the gutter and the parser's message (on hover, and with F8). The
|
|
7
|
+
// check is CodeMirror's lint (@codemirror/lint, a chunk of the vendored bundle
|
|
8
|
+
// fetched with the first JSON file) run with the JSON language's own linter.
|
|
9
|
+
// Files that are JSON with comments by convention (tsconfig.json, .vscode/…)
|
|
10
|
+
// are checked with comments and trailing commas allowed; files that are not
|
|
11
|
+
// one JSON value (JSON Lines, JSON5) are not checked.
|
|
12
|
+
// Takes the vendored bundle as an argument: no imports. The helpers at the top
|
|
13
|
+
// need no page: `node --test` loads them as they are.
|
|
14
|
+
|
|
15
|
+
/** The check runs this long after the last edit. */
|
|
16
|
+
export const LINT_DELAY_MS = 400;
|
|
17
|
+
|
|
18
|
+
// JSON with comments, by the name tools gave the file.
|
|
19
|
+
const JSONC_NAMES = /^(tsconfig|jsconfig)(\..+)?\.json$|^(\.?devcontainer|\.eslintrc|\.babelrc|deno|turbo|\.luarc|api-extractor|typedoc)\.json$|^(\.babelrc|\.eslintrc|\.prettierrc|\.stylelintrc|\.swcrc|bun\.lock)$/;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Which check a file gets.
|
|
23
|
+
* @param {string} path the file's path or name
|
|
24
|
+
* @param {string} languageName the language it is shown in, as the status bar names it
|
|
25
|
+
* @returns {'json' | 'jsonc' | null} `json`: strict; `jsonc`: comments and trailing commas are allowed; null: none
|
|
26
|
+
*/
|
|
27
|
+
export function lintKind(path, languageName) {
|
|
28
|
+
if (languageName !== 'JSON') return null;
|
|
29
|
+
const full = String(path ?? '').replace(/\\/g, '/');
|
|
30
|
+
const name = full.slice(full.lastIndexOf('/') + 1).toLowerCase();
|
|
31
|
+
const ext = name.lastIndexOf('.') > 0 ? name.slice(name.lastIndexOf('.') + 1) : '';
|
|
32
|
+
if (ext === 'jsonl' || ext === 'ndjson' || ext === 'json5') return null; // several values, or another syntax
|
|
33
|
+
if (ext === 'jsonc' || ext === 'code-workspace' || /\/\.vscode\/[^/]+$/.test(full) || JSONC_NAMES.test(name)) return 'jsonc';
|
|
34
|
+
return 'json';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* `text` with its comments and trailing commas turned into spaces, every other character where it was: what is
|
|
39
|
+
* left is JSON when the text was JSON with comments, and an error in it is at the same place in the text.
|
|
40
|
+
*/
|
|
41
|
+
export function blankJsonComments(text) {
|
|
42
|
+
const s = String(text ?? '');
|
|
43
|
+
const n = s.length;
|
|
44
|
+
const out = new Uint16Array(n);
|
|
45
|
+
let comma = -1; // a comma that nothing but blanks and comments has followed yet
|
|
46
|
+
let i = 0;
|
|
47
|
+
while (i < n) {
|
|
48
|
+
const c = s.charCodeAt(i);
|
|
49
|
+
if (c === 34) { // a string: copied as it is, to its closing quote or the end of its line
|
|
50
|
+
out[i++] = c;
|
|
51
|
+
while (i < n) {
|
|
52
|
+
const d = s.charCodeAt(i);
|
|
53
|
+
out[i++] = d;
|
|
54
|
+
if (d === 92 && i < n) out[i] = s.charCodeAt(i++);
|
|
55
|
+
else if (d === 34 || d === 10) break;
|
|
56
|
+
}
|
|
57
|
+
comma = -1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (c === 47 && s.charCodeAt(i + 1) === 47) {
|
|
61
|
+
while (i < n && s.charCodeAt(i) !== 10) out[i++] = 32;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (c === 47 && s.charCodeAt(i + 1) === 42) {
|
|
65
|
+
const end = s.indexOf('*/', i + 2);
|
|
66
|
+
const stop = end === -1 ? n : end + 2;
|
|
67
|
+
for (; i < stop; i++) out[i] = s.charCodeAt(i) === 10 ? 10 : 32;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
out[i] = c;
|
|
71
|
+
if (c === 44) comma = i;
|
|
72
|
+
else if (c === 125 || c === 93) { if (comma >= 0) out[comma] = 32; comma = -1; }
|
|
73
|
+
else if (c !== 32 && c !== 9 && c !== 10 && c !== 13) comma = -1;
|
|
74
|
+
i++;
|
|
75
|
+
}
|
|
76
|
+
let result = '';
|
|
77
|
+
for (let at = 0; at < n; at += 8192) result += String.fromCharCode.apply(null, out.subarray(at, Math.min(n, at + 8192)));
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Where a parser's message says the error is, as an offset into the text. Null when the message names no place
|
|
83
|
+
* (some engines' do not).
|
|
84
|
+
*/
|
|
85
|
+
export function parseErrorOffset(message, text) {
|
|
86
|
+
const words = String(message ?? '');
|
|
87
|
+
const s = String(text ?? '');
|
|
88
|
+
let m = /\bat position (\d+)/.exec(words);
|
|
89
|
+
if (m) return Math.min(Number(m[1]), s.length);
|
|
90
|
+
m = /\bat line (\d+) column (\d+)/.exec(words);
|
|
91
|
+
if (m) {
|
|
92
|
+
let at = 0;
|
|
93
|
+
for (let line = 1; line < Number(m[1]); line++) {
|
|
94
|
+
const next = s.indexOf('\n', at);
|
|
95
|
+
if (next === -1) return s.length;
|
|
96
|
+
at = next + 1;
|
|
97
|
+
}
|
|
98
|
+
return Math.min(at + Math.max(0, Number(m[2]) - 1), s.length);
|
|
99
|
+
}
|
|
100
|
+
if (/end of (JSON )?(input|data)/i.test(words)) return s.length;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const isWordChar = (c) => (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95 || c === 36 || c === 45 || c === 43 || c === 46 || c > 127;
|
|
105
|
+
const isBlank = (c) => c === 32 || c === 9 || c === 10 || c === 13;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The stretch of text to underline for an error at `offset`: the word there, or the one character; at a blank
|
|
109
|
+
* or past the end, the last thing before it. Never empty unless the text is, and never across a line break.
|
|
110
|
+
* @returns {{ from: number, to: number }}
|
|
111
|
+
*/
|
|
112
|
+
export function problemSpan(text, offset) {
|
|
113
|
+
const s = String(text ?? '');
|
|
114
|
+
if (!s.length) return { from: 0, to: 0 };
|
|
115
|
+
let at = Math.max(0, Math.min(Number(offset) || 0, s.length - 1));
|
|
116
|
+
while (at > 0 && isBlank(s.charCodeAt(at))) at--;
|
|
117
|
+
if (isBlank(s.charCodeAt(at))) return { from: at, to: at + 1 === s.length || s.charCodeAt(at) === 10 ? at : at + 1 };
|
|
118
|
+
if (!isWordChar(s.charCodeAt(at))) return { from: at, to: at + 1 };
|
|
119
|
+
let from = at; let to = at + 1;
|
|
120
|
+
while (from > 0 && isWordChar(s.charCodeAt(from - 1))) from--;
|
|
121
|
+
while (to < s.length && isWordChar(s.charCodeAt(to))) to++;
|
|
122
|
+
return { from, to };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The problems of a JSON text.
|
|
127
|
+
* @param {string} text
|
|
128
|
+
* @param {{ relaxed?: boolean }} [o] `relaxed`: comments and trailing commas are not problems
|
|
129
|
+
* @returns {{ from: number, to: number, message: string, placed: boolean }[]} none, or the first one the parser
|
|
130
|
+
* met; `placed` is false when the parser's message named no place (`from` and `to` are then 0)
|
|
131
|
+
*/
|
|
132
|
+
export function jsonProblems(text, { relaxed = false } = {}) {
|
|
133
|
+
const s = String(text ?? '');
|
|
134
|
+
if (!s.trim()) return []; // an empty file is not a broken one
|
|
135
|
+
let checked = s;
|
|
136
|
+
try { JSON.parse(s); return []; } catch (strict) {
|
|
137
|
+
let error = strict;
|
|
138
|
+
if (relaxed) {
|
|
139
|
+
checked = blankJsonComments(s);
|
|
140
|
+
try { JSON.parse(checked); return []; } catch (inner) { error = inner; }
|
|
141
|
+
}
|
|
142
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
143
|
+
const offset = parseErrorOffset(error.message, checked);
|
|
144
|
+
if (offset === null) return [{ from: 0, to: 0, message: error.message, placed: false }];
|
|
145
|
+
return [{ ...problemSpan(s, offset), message: error.message, placed: true }];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {object} cm the vendored CodeMirror bundle (editor-loader.js)
|
|
151
|
+
*/
|
|
152
|
+
export function editorLint(cm) {
|
|
153
|
+
let lint = null; // @codemirror/lint, once loaded
|
|
154
|
+
let strictLinter = null; // the JSON language's own linter
|
|
155
|
+
let loading = null;
|
|
156
|
+
const built = new Map(); // kind → its extension, the same one for every document
|
|
157
|
+
|
|
158
|
+
function load() {
|
|
159
|
+
if (!loading) {
|
|
160
|
+
loading = Promise.all([cm.loadLint(), typeof cm.loadJsonLinter === 'function' ? cm.loadJsonLinter() : null]).then(([module, jsonLinter]) => {
|
|
161
|
+
lint = module;
|
|
162
|
+
strictLinter = typeof jsonLinter === 'function' ? jsonLinter() : null;
|
|
163
|
+
});
|
|
164
|
+
loading.catch(() => { loading = null; });
|
|
165
|
+
}
|
|
166
|
+
return loading;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// A problem the parser's message did not place is put at the first node the editor's own parser could not read.
|
|
170
|
+
function firstErrorNode(state) {
|
|
171
|
+
let at = -1;
|
|
172
|
+
cm.syntaxTree(state).iterate({ enter: (node) => { if (at < 0 && node.type.isError) at = node.from; return at < 0; } });
|
|
173
|
+
return at;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function source(kind) {
|
|
177
|
+
return (view) => {
|
|
178
|
+
const text = view.state.doc.toString();
|
|
179
|
+
let found;
|
|
180
|
+
if (kind === 'json' && strictLinter) {
|
|
181
|
+
// The JSON language's own linter says whether the file parses and with which message; where the
|
|
182
|
+
// underline goes is worked out here, so it is a stretch of text instead of a point.
|
|
183
|
+
found = text.trim() ? strictLinter(view).map((d) => {
|
|
184
|
+
const offset = parseErrorOffset(d.message, text);
|
|
185
|
+
return offset === null ? { from: 0, to: 0, message: d.message, placed: false } : { ...problemSpan(text, offset), message: d.message, placed: true };
|
|
186
|
+
}) : [];
|
|
187
|
+
} else {
|
|
188
|
+
found = jsonProblems(text, { relaxed: kind === 'jsonc' });
|
|
189
|
+
}
|
|
190
|
+
return found.map((problem) => {
|
|
191
|
+
let { from, to } = problem;
|
|
192
|
+
if (!problem.placed) {
|
|
193
|
+
const at = firstErrorNode(view.state);
|
|
194
|
+
({ from, to } = problemSpan(text, at < 0 ? 0 : at));
|
|
195
|
+
}
|
|
196
|
+
return { from, to, severity: 'error', message: problem.message };
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function build(kind) {
|
|
202
|
+
if (!built.has(kind)) built.set(kind, [lint.linter(source(kind), { delay: LINT_DELAY_MS }), lint.lintGutter()]);
|
|
203
|
+
return built.get(kind);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
/** The extension that checks a document of this kind, when the lint chunk is already there; else null. */
|
|
208
|
+
ready: (kind) => (lint && kind ? build(kind) : null),
|
|
209
|
+
|
|
210
|
+
/** The same, fetching the chunk first. Resolves to null for no kind; rejects when the chunk cannot be loaded. */
|
|
211
|
+
async extension(kind) {
|
|
212
|
+
if (!kind) return null;
|
|
213
|
+
await load();
|
|
214
|
+
return build(kind);
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/** Go to the next / the previous problem. False when there is none (or nothing is checked). */
|
|
218
|
+
next: (view) => (lint ? lint.nextDiagnostic(view) : false),
|
|
219
|
+
previous: (view) => (lint ? lint.previousDiagnostic(view) : false),
|
|
220
|
+
|
|
221
|
+
/** How many problems a state shows. */
|
|
222
|
+
count: (state) => (lint ? lint.diagnosticCount(state) : 0),
|
|
223
|
+
|
|
224
|
+
/** Call `use(from, to)` for each problem of a state (the minimap draws them). */
|
|
225
|
+
each(state, use) {
|
|
226
|
+
if (lint) lint.forEachDiagnostic(state, (_diagnostic, from, to) => use(from, to));
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Code editor: loading CodeMirror
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// CodeMirror is vendored (public/vendor/codemirror/, built by
|
|
6
|
+
// scripts/build-vendor.mjs): nothing comes from a CDN. The manifest names the
|
|
7
|
+
// entry file of the current build; it is imported once per page, the first
|
|
8
|
+
// time a file is opened. A failed load is reported to the caller, which shows
|
|
9
|
+
// it with a Retry; the next call tries again.
|
|
10
|
+
|
|
11
|
+
export const CODEMIRROR_BASE = '/vendor/codemirror/';
|
|
12
|
+
const ENTRY_NAME = /^codemirror-[A-Za-z0-9._-]+\.min\.js$/;
|
|
13
|
+
|
|
14
|
+
let _loading = null;
|
|
15
|
+
let _attempt = 0;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The vendored CodeMirror bundle.
|
|
19
|
+
* @param {{ fetchImpl?: typeof fetch, importModule?: (url: string) => Promise<object> }} [o] for tests
|
|
20
|
+
* @returns {Promise<{ cm: object, manifest: { version: string, entry: string, packages: object } }>}
|
|
21
|
+
* Rejects with an Error when the manifest or the bundle cannot be loaded.
|
|
22
|
+
*/
|
|
23
|
+
export function loadCodeMirror({ fetchImpl = (...args) => fetch(...args), importModule = (url) => import(url) } = {}) {
|
|
24
|
+
if (_loading) return _loading;
|
|
25
|
+
const attempt = ++_attempt;
|
|
26
|
+
const loading = (async () => {
|
|
27
|
+
const res = await fetchImpl(`${CODEMIRROR_BASE}manifest.json`, { cache: 'no-cache' });
|
|
28
|
+
if (!res.ok) throw new Error(`CodeMirror manifest: HTTP ${res.status}`);
|
|
29
|
+
const manifest = await res.json();
|
|
30
|
+
if (typeof manifest?.entry !== 'string' || !ENTRY_NAME.test(manifest.entry)) {
|
|
31
|
+
throw new Error('CodeMirror manifest names no entry file');
|
|
32
|
+
}
|
|
33
|
+
// A module that failed to load can stay failed under its address for the rest of the page:
|
|
34
|
+
// a retry asks for it under another one.
|
|
35
|
+
const retry = attempt > 1 ? `?retry=${attempt}` : '';
|
|
36
|
+
const cm = await importModule(`${CODEMIRROR_BASE}${manifest.entry}${retry}`);
|
|
37
|
+
if (typeof cm?.EditorView !== 'function' || typeof cm?.EditorState !== 'function') {
|
|
38
|
+
throw new Error('CodeMirror bundle is incomplete');
|
|
39
|
+
}
|
|
40
|
+
return { cm, manifest };
|
|
41
|
+
})();
|
|
42
|
+
_loading = loading;
|
|
43
|
+
loading.catch(() => { if (_loading === loading) _loading = null; });
|
|
44
|
+
return loading;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Forget a load (tests). */
|
|
48
|
+
export function resetCodeMirrorLoader() {
|
|
49
|
+
_loading = null;
|
|
50
|
+
_attempt = 0;
|
|
51
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Code editor: Markdown preview
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// Markdown → the HTML the editor's Preview shows. The text is a file from disk,
|
|
6
|
+
// so it is untrusted: every character that means something in HTML is escaped
|
|
7
|
+
// before any tag is written, and the only attribute that carries a value from
|
|
8
|
+
// the text, a link's href, is limited to http:, https:, mailto: and relative
|
|
9
|
+
// targets. No DOM and no imports: loaded by `node --test` as it is.
|
|
10
|
+
|
|
11
|
+
const ESCAPES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
12
|
+
|
|
13
|
+
/** `text` with & < > " ' as HTML entities: safe as element content and inside a quoted attribute. */
|
|
14
|
+
export function escapeHtml(text) {
|
|
15
|
+
return String(text ?? '').replace(/[&<>"']/g, (c) => ESCAPES[c]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// What a browser drops or ignores when it reads a URL's scheme: C0 controls, spaces of every kind, and the
|
|
19
|
+
// invisible format characters. Without them "java\tscript:" is seen for what it is.
|
|
20
|
+
const URL_NOISE = /[\u0000-\u0020\u007F-\u00A0\u1680\u2000-\u200F\u2028-\u202F\u205F-\u2064\u3000\uFEFF]/g;
|
|
21
|
+
const ALLOWED_SCHEMES = new Set(['http', 'https', 'mailto']);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A link target that is safe to open, or null. Relative targets (a path, `#anchor`, `?query`, `//host/path`)
|
|
25
|
+
* pass; a target with a scheme passes only for http, https and mailto.
|
|
26
|
+
* @param {string} target as written in the Markdown, or already HTML-escaped (escaping adds no scheme)
|
|
27
|
+
*/
|
|
28
|
+
export function safeLinkTarget(target) {
|
|
29
|
+
const raw = String(target ?? '').trim();
|
|
30
|
+
if (!raw) return null;
|
|
31
|
+
const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(raw.replace(URL_NOISE, ''));
|
|
32
|
+
if (scheme && !ALLOWED_SCHEMES.has(scheme[1].toLowerCase())) return null;
|
|
33
|
+
return raw;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const HOLD = '\u0000';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render Markdown for the Preview pane: headings, emphasis, inline and fenced code, links, block quotes,
|
|
40
|
+
* tables, lists and paragraphs. Front matter is left out. Raw HTML in the text is shown as text.
|
|
41
|
+
* @returns {string} HTML
|
|
42
|
+
*/
|
|
43
|
+
export function renderMarkdown(markdown) {
|
|
44
|
+
let text = String(markdown ?? '').replace(/\r\n?/g, '\n').split(HOLD).join('');
|
|
45
|
+
text = text.replace(/^---\n[\s\S]*?\n---\n?/, '');
|
|
46
|
+
text = escapeHtml(text);
|
|
47
|
+
|
|
48
|
+
// Code and links are set aside while the rest is formatted, so nothing inside them is read as Markdown and
|
|
49
|
+
// nothing is formatted into an attribute.
|
|
50
|
+
const held = [];
|
|
51
|
+
const hold = (html) => `${HOLD}${held.push(html) - 1}${HOLD}`;
|
|
52
|
+
text = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => hold(`<pre><code class="lang-${lang}">${code.trimEnd()}</code></pre>`));
|
|
53
|
+
text = text.replace(/`([^`\n]+)`/g, (_, code) => hold(`<code>${code}</code>`));
|
|
54
|
+
text = text.replace(/\[([^\]\n]+)\]\(([^)\n]+)\)/g, (whole, label, target) => {
|
|
55
|
+
if (target.includes(HOLD)) return whole;
|
|
56
|
+
const href = safeLinkTarget(target);
|
|
57
|
+
return href === null ? label : `${hold(`<a href="${href}" target="_blank" rel="noopener noreferrer">`)}${label}${hold('</a>')}`;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
text = text.replace(/^######\s+(.+)$/gm, '<h6>$1</h6>');
|
|
61
|
+
text = text.replace(/^#####\s+(.+)$/gm, '<h5>$1</h5>');
|
|
62
|
+
text = text.replace(/^####\s+(.+)$/gm, '<h4>$1</h4>');
|
|
63
|
+
text = text.replace(/^###\s+(.+)$/gm, '<h3>$1</h3>');
|
|
64
|
+
text = text.replace(/^##\s+(.+)$/gm, '<h2>$1</h2>');
|
|
65
|
+
text = text.replace(/^#\s+(.+)$/gm, '<h1>$1</h1>');
|
|
66
|
+
text = text.replace(/^---+$/gm, '<hr>');
|
|
67
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
68
|
+
text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
69
|
+
text = text.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
70
|
+
text = text.replace(/~~(.+?)~~/g, '<del>$1</del>');
|
|
71
|
+
text = text.replace(/^>\s?(.*)$/gm, '<blockquote>$1</blockquote>');
|
|
72
|
+
text = text.replace(/<\/blockquote>\n<blockquote>/g, '\n');
|
|
73
|
+
text = text.replace(/^(\|.+\|)\n(\|[-| :]+\|)\n((?:\|.+\|\n?)+)/gm, (_, header, _sep, body) => {
|
|
74
|
+
const ths = header.split('|').filter((c) => c.trim()).map((c) => `<th>${c.trim()}</th>`).join('');
|
|
75
|
+
const rows = body.trim().split('\n').map((row) => {
|
|
76
|
+
const tds = row.split('|').filter((c) => c.trim()).map((c) => `<td>${c.trim()}</td>`).join('');
|
|
77
|
+
return `<tr>${tds}</tr>`;
|
|
78
|
+
}).join('');
|
|
79
|
+
return `<table><thead><tr>${ths}</tr></thead><tbody>${rows}</tbody></table>`;
|
|
80
|
+
});
|
|
81
|
+
text = text.replace(/^[-*]\s+(.+)$/gm, '<li>$1</li>');
|
|
82
|
+
text = text.replace(/((?:<li>.*<\/li>\n?)+)/g, '<ul>$1</ul>');
|
|
83
|
+
text = text.replace(/^\d+\.\s+(.+)$/gm, '<oli>$1</oli>');
|
|
84
|
+
text = text.replace(/((?:<oli>.*<\/oli>\n?)+)/g, (match) => `<ol>${match.replace(/<\/?oli>/g, (tag) => tag.replace('oli', 'li'))}</ol>`);
|
|
85
|
+
// A line that does not start with a tag (or with code or a link, which are held) becomes a paragraph.
|
|
86
|
+
text = text.replace(/^(?![<\u0000])(.+)$/gm, '<p>$1</p>');
|
|
87
|
+
text = text.replace(/<p>\s*<\/p>/g, '');
|
|
88
|
+
|
|
89
|
+
return text.replace(new RegExp(`${HOLD}(\\d+)${HOLD}`, 'g'), (_, i) => held[Number(i)]);
|
|
90
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Code editor: menus
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The one popup of the editor panel: a tab's menu, the status bar's lists
|
|
6
|
+
// (language, indentation, line ending), the View menu, the command palette and
|
|
7
|
+
// go-to-file. A list of actions, optionally under a search field; arrows,
|
|
8
|
+
// Home / End, Enter and Escape work it, and its keys never reach the app's
|
|
9
|
+
// shortcuts. They are taken at the window, before anything in the page sees
|
|
10
|
+
// them: other parts of the app listen for keys at the document in the capture
|
|
11
|
+
// phase (the whiteboard takes Escape there whenever the keyboard is not in a
|
|
12
|
+
// text field), and a listener on the menu itself would come after those.
|
|
13
|
+
// One is open at a time; closed with the keyboard, it gives the keyboard back
|
|
14
|
+
// to where it was. Its look is `.fe-editor-menu` in styles.css (the menu
|
|
15
|
+
// treatment of DESIGN.md).
|
|
16
|
+
// No imports: the caller hands in the words.
|
|
17
|
+
|
|
18
|
+
let _current = null;
|
|
19
|
+
const KEY_EVENTS = ['keydown', 'keyup', 'keypress'];
|
|
20
|
+
|
|
21
|
+
/** Close the open menu, if there is one. */
|
|
22
|
+
export function closeEditorMenu() {
|
|
23
|
+
_current?.close();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} EditorMenuItem
|
|
28
|
+
* @property {string} [id]
|
|
29
|
+
* @property {string} [label]
|
|
30
|
+
* @property {string} [hint] muted words after the label
|
|
31
|
+
* @property {boolean} [checked] the one in force (shown with a check)
|
|
32
|
+
* @property {boolean} [toggle] `checked` is a switch of its own, not one choice among the others
|
|
33
|
+
* @property {boolean} [disabled]
|
|
34
|
+
* @property {boolean} [separator] a line between groups; takes no other field
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Open a menu.
|
|
39
|
+
* @param {object} o
|
|
40
|
+
* @param {HTMLElement} o.host the element it is added to (the editor panel: its variables and its focus ring apply)
|
|
41
|
+
* @param {HTMLElement} [o.anchor] the control it belongs to: it opens against it and gives the keyboard back to it
|
|
42
|
+
* @param {{ x: number, y: number }} [o.at] or a point of the page (a right click): the keyboard then goes back to
|
|
43
|
+
* whatever had it when the menu opened
|
|
44
|
+
* @param {string} o.label its accessible name
|
|
45
|
+
* @param {EditorMenuItem[]} o.items
|
|
46
|
+
* @param {{ label: string, placeholder: string, empty: string | (() => string), filter: (query: string) => EditorMenuItem[] }} [o.search]
|
|
47
|
+
* a field above the list: `filter` gives the items for what was typed; `empty` is said when there are none
|
|
48
|
+
* @param {string} [o.className] a class of its own, next to `fe-editor-menu` (the palette is wider)
|
|
49
|
+
* @param {string} [o.activeId] the item the keyboard starts on (else the checked one, else the first)
|
|
50
|
+
* @param {(id: string) => void} o.onSelect
|
|
51
|
+
* @param {() => void} [o.onClose]
|
|
52
|
+
* @returns {{ close: (o?: { refocus?: boolean }) => void, setItems: (items: EditorMenuItem[]) => void, element: HTMLElement }}
|
|
53
|
+
* `setItems`: show other items in the open menu (a search whose answer arrived)
|
|
54
|
+
*/
|
|
55
|
+
export function openEditorMenu({ host, anchor = null, at = null, label, items, search = null, className = '', activeId = null, onSelect, onClose = () => {} }) {
|
|
56
|
+
closeEditorMenu();
|
|
57
|
+
|
|
58
|
+
const menu = document.createElement('div');
|
|
59
|
+
menu.className = className ? `fe-editor-menu ${className}` : 'fe-editor-menu';
|
|
60
|
+
menu.setAttribute('role', search ? 'dialog' : 'menu');
|
|
61
|
+
menu.setAttribute('aria-label', label);
|
|
62
|
+
menu.tabIndex = -1;
|
|
63
|
+
const list = document.createElement('div');
|
|
64
|
+
list.className = 'fe-editor-menu-list';
|
|
65
|
+
list.id = 'fe-editor-menu-list';
|
|
66
|
+
list.setAttribute('role', search ? 'listbox' : 'none');
|
|
67
|
+
let input = null;
|
|
68
|
+
if (search) {
|
|
69
|
+
input = document.createElement('input');
|
|
70
|
+
input.type = 'text';
|
|
71
|
+
input.className = 'fe-editor-menu-search';
|
|
72
|
+
input.placeholder = search.placeholder;
|
|
73
|
+
input.spellcheck = false;
|
|
74
|
+
input.autocomplete = 'off';
|
|
75
|
+
input.setAttribute('role', 'combobox');
|
|
76
|
+
input.setAttribute('aria-label', search.label);
|
|
77
|
+
input.setAttribute('aria-expanded', 'true');
|
|
78
|
+
input.setAttribute('aria-controls', list.id);
|
|
79
|
+
input.setAttribute('aria-autocomplete', 'list');
|
|
80
|
+
menu.append(input);
|
|
81
|
+
}
|
|
82
|
+
menu.append(list);
|
|
83
|
+
|
|
84
|
+
let shown = []; // the items that can be chosen, in order
|
|
85
|
+
let activeAt = -1;
|
|
86
|
+
let open = true;
|
|
87
|
+
// What had the keyboard when the menu opened: where it goes back to, for a menu that belongs to no control.
|
|
88
|
+
const opener = document.activeElement instanceof HTMLElement && document.activeElement !== document.body ? document.activeElement : null;
|
|
89
|
+
|
|
90
|
+
function setActive(index, { scroll = true } = {}) {
|
|
91
|
+
activeAt = shown.length ? Math.max(0, Math.min(shown.length - 1, index)) : -1;
|
|
92
|
+
shown.forEach((entry, i) => entry.node.classList.toggle('active', i === activeAt));
|
|
93
|
+
const node = shown[activeAt]?.node || null;
|
|
94
|
+
(input || menu).setAttribute('aria-activedescendant', node ? node.id : '');
|
|
95
|
+
if (node && scroll) node.scrollIntoView({ block: 'nearest' });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function render(entries) {
|
|
99
|
+
list.textContent = '';
|
|
100
|
+
shown = [];
|
|
101
|
+
let seq = 0;
|
|
102
|
+
for (const item of entries) {
|
|
103
|
+
if (item.separator) {
|
|
104
|
+
const line = document.createElement('div');
|
|
105
|
+
line.className = 'fe-editor-menu-sep';
|
|
106
|
+
line.setAttribute('role', 'separator');
|
|
107
|
+
list.append(line);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const node = document.createElement('div');
|
|
111
|
+
node.className = 'fe-editor-menu-item';
|
|
112
|
+
node.id = `fe-editor-menu-item-${seq++}`;
|
|
113
|
+
node.dataset.id = item.id;
|
|
114
|
+
node.setAttribute('role', search ? 'option' : (item.checked === undefined ? 'menuitem' : (item.toggle ? 'menuitemcheckbox' : 'menuitemradio')));
|
|
115
|
+
if (item.checked !== undefined) node.setAttribute(search ? 'aria-selected' : 'aria-checked', String(!!item.checked));
|
|
116
|
+
if (item.disabled) node.setAttribute('aria-disabled', 'true');
|
|
117
|
+
const check = document.createElement('span');
|
|
118
|
+
check.className = 'fe-editor-menu-check';
|
|
119
|
+
check.setAttribute('aria-hidden', 'true');
|
|
120
|
+
if (item.checked) check.textContent = '✓';
|
|
121
|
+
const text = document.createElement('span');
|
|
122
|
+
text.className = 'fe-editor-menu-label';
|
|
123
|
+
text.textContent = item.label;
|
|
124
|
+
node.append(check, text);
|
|
125
|
+
if (item.hint) {
|
|
126
|
+
const hint = document.createElement('span');
|
|
127
|
+
hint.className = 'fe-editor-menu-hint';
|
|
128
|
+
hint.textContent = item.hint;
|
|
129
|
+
node.append(hint);
|
|
130
|
+
}
|
|
131
|
+
list.append(node);
|
|
132
|
+
if (!item.disabled) shown.push({ item, node });
|
|
133
|
+
}
|
|
134
|
+
if (!shown.length && search) {
|
|
135
|
+
const none = document.createElement('div');
|
|
136
|
+
none.className = 'fe-editor-menu-empty';
|
|
137
|
+
none.textContent = typeof search.empty === 'function' ? search.empty() : search.empty;
|
|
138
|
+
list.append(none);
|
|
139
|
+
}
|
|
140
|
+
const wanted = activeId === null ? -1 : shown.findIndex((entry) => entry.item.id === activeId);
|
|
141
|
+
const checked = shown.findIndex((entry) => entry.item.checked && !entry.item.toggle);
|
|
142
|
+
setActive(wanted >= 0 ? wanted : (checked >= 0 ? checked : 0));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function close({ refocus = false } = {}) {
|
|
146
|
+
if (!open) return;
|
|
147
|
+
open = false;
|
|
148
|
+
if (_current === handle) _current = null;
|
|
149
|
+
document.removeEventListener('mousedown', onOutside, true);
|
|
150
|
+
window.removeEventListener('resize', onResize);
|
|
151
|
+
for (const type of KEY_EVENTS) window.removeEventListener(type, onKey, true);
|
|
152
|
+
menu.remove();
|
|
153
|
+
anchor?.setAttribute('aria-expanded', 'false');
|
|
154
|
+
if (refocus) {
|
|
155
|
+
const back = anchor || (opener?.isConnected ? opener : null);
|
|
156
|
+
back?.focus();
|
|
157
|
+
}
|
|
158
|
+
onClose();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function choose(entry) {
|
|
162
|
+
if (!entry) return;
|
|
163
|
+
close({ refocus: true });
|
|
164
|
+
onSelect(entry.item.id);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const onOutside = (event) => { if (!menu.contains(event.target)) close(); };
|
|
168
|
+
const onResize = () => close();
|
|
169
|
+
|
|
170
|
+
menu.addEventListener('click', (event) => {
|
|
171
|
+
const node = event.target.closest?.('.fe-editor-menu-item');
|
|
172
|
+
if (node) choose(shown.find((entry) => entry.node === node));
|
|
173
|
+
});
|
|
174
|
+
menu.addEventListener('mousemove', (event) => {
|
|
175
|
+
const node = event.target.closest?.('.fe-editor-menu-item');
|
|
176
|
+
const index = node ? shown.findIndex((entry) => entry.node === node) : -1;
|
|
177
|
+
if (index >= 0 && index !== activeAt) setActive(index, { scroll: false });
|
|
178
|
+
});
|
|
179
|
+
// The keys pressed in the menu are its own. Taken at the window in the capture phase, so that they are the
|
|
180
|
+
// menu's before a listener at the document can take them for a shortcut of its own, and go no further: what a
|
|
181
|
+
// key types into the search field is not held back by that.
|
|
182
|
+
function onKey(event) {
|
|
183
|
+
if (!open || !menu.contains(event.target)) return;
|
|
184
|
+
event.stopPropagation();
|
|
185
|
+
if (event.type !== 'keydown') return;
|
|
186
|
+
switch (event.key) {
|
|
187
|
+
case 'ArrowDown': event.preventDefault(); setActive(activeAt + 1 >= shown.length ? 0 : activeAt + 1); break;
|
|
188
|
+
case 'ArrowUp': event.preventDefault(); setActive(activeAt - 1 < 0 ? shown.length - 1 : activeAt - 1); break;
|
|
189
|
+
case 'Home': if (!input) { event.preventDefault(); setActive(0); } break;
|
|
190
|
+
case 'End': if (!input) { event.preventDefault(); setActive(shown.length - 1); } break;
|
|
191
|
+
case 'PageDown': event.preventDefault(); setActive(activeAt + 8); break;
|
|
192
|
+
case 'PageUp': event.preventDefault(); setActive(activeAt - 8); break;
|
|
193
|
+
case 'Enter': if (!event.isComposing) { event.preventDefault(); choose(shown[activeAt]); } break;
|
|
194
|
+
case ' ': if (!input) { event.preventDefault(); choose(shown[activeAt]); } break;
|
|
195
|
+
case 'Escape': event.preventDefault(); close({ refocus: true }); break;
|
|
196
|
+
case 'Tab': event.preventDefault(); close({ refocus: true }); break;
|
|
197
|
+
default:
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (input) input.addEventListener('input', () => render(search.filter(input.value)));
|
|
201
|
+
|
|
202
|
+
render(search ? search.filter('') : items);
|
|
203
|
+
host.append(menu);
|
|
204
|
+
|
|
205
|
+
// Against the anchor (above it when there is no room below), or at the point; always inside the host.
|
|
206
|
+
const frame = host.getBoundingClientRect();
|
|
207
|
+
const size = menu.getBoundingClientRect();
|
|
208
|
+
const gap = 6;
|
|
209
|
+
let left; let top;
|
|
210
|
+
if (anchor) {
|
|
211
|
+
const box = anchor.getBoundingClientRect();
|
|
212
|
+
left = box.left;
|
|
213
|
+
top = box.bottom + gap + size.height <= frame.bottom ? box.bottom + gap : box.top - gap - size.height;
|
|
214
|
+
} else {
|
|
215
|
+
left = at?.x ?? frame.left;
|
|
216
|
+
top = at?.y ?? frame.top;
|
|
217
|
+
}
|
|
218
|
+
left = Math.max(frame.left + 8, Math.min(left, frame.right - size.width - 8));
|
|
219
|
+
top = Math.max(frame.top + 8, Math.min(top, frame.bottom - size.height - 8));
|
|
220
|
+
menu.style.left = `${Math.round(left - frame.left)}px`;
|
|
221
|
+
menu.style.top = `${Math.round(top - frame.top)}px`;
|
|
222
|
+
|
|
223
|
+
anchor?.setAttribute('aria-expanded', 'true');
|
|
224
|
+
document.addEventListener('mousedown', onOutside, true);
|
|
225
|
+
window.addEventListener('resize', onResize);
|
|
226
|
+
for (const type of KEY_EVENTS) window.addEventListener(type, onKey, true);
|
|
227
|
+
(input || menu).focus();
|
|
228
|
+
setActive(activeAt);
|
|
229
|
+
|
|
230
|
+
const handle = { close, setItems: (entries) => { if (open) render(entries); }, element: menu };
|
|
231
|
+
_current = handle;
|
|
232
|
+
return handle;
|
|
233
|
+
}
|