synabun 2.0.10 → 2.1.0
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 +86 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +37 -4
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +228 -0
- package/lib/virustotal.mjs +192 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +16 -10
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +20 -62
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +639 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3463 -200
- package/neural-interface/i18n/pt-BR.json +3411 -151
- package/neural-interface/lib/assistant-api.js +127 -28
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +120 -54
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
- package/neural-interface/lib/assistant-catalog.js +6 -7
- package/neural-interface/lib/assistant-dispatch.js +871 -156
- package/neural-interface/lib/assistant-envelope.js +3 -1
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +13 -8
- package/neural-interface/lib/assistant-runtime.js +56 -18
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-worker-app-server.js +172 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +68 -68
- package/neural-interface/lib/exec-loop-command.js +4 -8
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +547 -47
- package/neural-interface/lib/native-loop-runtime.js +268 -41
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +17 -34
- package/neural-interface/lib/opencode-history.js +48 -218
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +44 -0
- package/neural-interface/lib/opencode-protocol.js +249 -112
- package/neural-interface/lib/opencode-runtime-config.js +32 -22
- package/neural-interface/lib/opencode-serve-records.js +39 -21
- package/neural-interface/lib/opencode-v2-client.js +48 -153
- package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +73 -37
- package/neural-interface/lib/opencode2-proxy.js +23 -63
- package/neural-interface/lib/opencode2-translate.js +51 -2
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/peer-identity.js +763 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +17 -30
- package/neural-interface/lib/session-title-generator.js +58 -36
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +294 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +200 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +18 -12
- package/neural-interface/package.json +2 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +35 -19
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
- package/neural-interface/public/shared/assistant/asst-control.js +157 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +401 -77
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +11 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +89 -19
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +366 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-update-view.js +20 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/html-shell.js +9 -11
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +195 -61
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +3 -1
- package/neural-interface/public/shared/settings/settings-ia.js +6 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1525 -1779
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-automation-studio.js +91 -128
- package/neural-interface/public/shared/ui-claude-panel.js +77 -5
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +15 -3
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-menubar.js +8 -18
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
- package/neural-interface/public/shared/ui-sessions.js +5 -3
- package/neural-interface/public/shared/ui-settings.js +594 -462
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- package/neural-interface/public/shared/ui-skills.js +33 -2830
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +15 -5
- package/neural-interface/public/shared/ui-terminal.js +124 -180
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- package/neural-interface/public/shared/ui-trash.js +6 -4
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -509
- package/neural-interface/public/shared/ui-update.js +38 -2
- package/neural-interface/public/shared/ui-whiteboard.js +311 -58
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +32 -1
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +69 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +32 -0
- package/neural-interface/server.js +3934 -2454
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +4 -4
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Context menu: what decides
|
|
3
|
+
// No DOM and no imports: the component (context-menu.js) and the app's
|
|
4
|
+
// global registration (ui-context-menu.js) draw what these functions say,
|
|
5
|
+
// and node --test checks them (tests/context-menu-logic.test.mjs).
|
|
6
|
+
// ═══════════════════════════════════════════
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} MenuItem
|
|
10
|
+
* @property {string} [id]
|
|
11
|
+
* @property {string} [label]
|
|
12
|
+
* @property {string} [icon] trusted SVG markup (the app's own icons, never page text)
|
|
13
|
+
* @property {string} [shortcut] the key as menus show it ('' when the action has none)
|
|
14
|
+
* @property {boolean} [disabled]
|
|
15
|
+
* @property {boolean} [checked] a toggle: drawn with a check, role menuitemcheckbox
|
|
16
|
+
* @property {boolean} [hidden] left out of the menu
|
|
17
|
+
* @property {MenuItem[]} [submenu]
|
|
18
|
+
* @property {() => unknown} [run]
|
|
19
|
+
* @property {boolean} [separator]
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const VIEWPORT_MARGIN = 8;
|
|
23
|
+
export const SUBMENU_GAP = 2;
|
|
24
|
+
// What a submenu's first row sits under: the menu's padding plus the row's margin.
|
|
25
|
+
export const SUBMENU_LEAD = 6;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Items as the menu draws them: hidden ones and ones without a label left out, a submenu with nothing in it left
|
|
29
|
+
* out, no separator at either end or next to another.
|
|
30
|
+
* @param {(MenuItem | null | undefined)[]} items
|
|
31
|
+
* @returns {MenuItem[]}
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeItems(items) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const item of items || []) {
|
|
36
|
+
if (!item || item.hidden) continue;
|
|
37
|
+
if (item.separator) {
|
|
38
|
+
if (out.length && !out[out.length - 1].separator) out.push({ separator: true });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!item.label) continue;
|
|
42
|
+
if (item.submenu) {
|
|
43
|
+
const submenu = normalizeItems(item.submenu);
|
|
44
|
+
if (!submenu.length) continue;
|
|
45
|
+
out.push({ ...item, submenu });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
out.push(item);
|
|
49
|
+
}
|
|
50
|
+
while (out.length && out[out.length - 1].separator) out.pop();
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sections in order, one separator between two that have something in them.
|
|
56
|
+
* @param {(MenuItem[] | null | undefined)[]} sections
|
|
57
|
+
* @returns {MenuItem[]}
|
|
58
|
+
*/
|
|
59
|
+
export function joinSections(sections) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const section of sections || []) {
|
|
62
|
+
const items = normalizeItems(section);
|
|
63
|
+
if (!items.length) continue;
|
|
64
|
+
if (out.length) out.push({ separator: true });
|
|
65
|
+
out.push(...items);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** How many rows a menu has at its top level (separators do not count). */
|
|
71
|
+
export function countRows(items) {
|
|
72
|
+
return (items || []).filter((item) => item && !item.separator).length;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Where a menu of `width` × `height` goes for a press at (x, y): down and to the right of it, flipped up or left
|
|
77
|
+
* when that side has no room, and kept inside the window either way. `bounds` ({ left, top, right, bottom }) is the
|
|
78
|
+
* area it is kept in instead of the window: the app frame (app-frame.js) for a press inside it.
|
|
79
|
+
* @returns {{ left: number, top: number }}
|
|
80
|
+
*/
|
|
81
|
+
export function placeMenu({ x, y, width, height, viewportWidth, viewportHeight, margin = VIEWPORT_MARGIN, bounds = null }) {
|
|
82
|
+
const b = bounds || { left: 0, top: 0, right: viewportWidth, bottom: viewportHeight };
|
|
83
|
+
const fit = (at, size, lo, hi) => {
|
|
84
|
+
const first = lo + margin; // the first place it may start
|
|
85
|
+
const furthest = hi - margin - size; // the last place it may start and still end inside the margin
|
|
86
|
+
if (furthest < first) return first; // larger than the area: from the margin (it scrolls)
|
|
87
|
+
if (at + size <= hi - margin) return Math.max(first, at);
|
|
88
|
+
if (at - size >= first) return Math.min(at - size, furthest);
|
|
89
|
+
return furthest;
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
left: Math.round(fit(x, width, b.left, b.right)),
|
|
93
|
+
top: Math.round(fit(y, height, b.top, b.bottom)),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Where a submenu goes: beside the menu it opens from, its first row level with the row that opened it; on the left
|
|
99
|
+
* when the window (or `bounds`, as for placeMenu) has no room on the right, and kept inside it.
|
|
100
|
+
* @param {{ left: number, right: number }} o.menuRect the menu it opens from
|
|
101
|
+
* @param {{ top: number }} o.rowRect the row that opens it
|
|
102
|
+
* @returns {{ left: number, top: number, side: 'right' | 'left' }}
|
|
103
|
+
*/
|
|
104
|
+
export function placeSubmenu({ menuRect, rowRect, width, height, viewportWidth, viewportHeight,
|
|
105
|
+
margin = VIEWPORT_MARGIN, gap = SUBMENU_GAP, lead = SUBMENU_LEAD, bounds = null }) {
|
|
106
|
+
const b = bounds || { left: 0, top: 0, right: viewportWidth, bottom: viewportHeight };
|
|
107
|
+
let side = 'right';
|
|
108
|
+
let left = menuRect.right + gap;
|
|
109
|
+
if (left + width > b.right - margin) {
|
|
110
|
+
const leftSide = menuRect.left - gap - width;
|
|
111
|
+
if (leftSide >= b.left + margin) { left = leftSide; side = 'left'; }
|
|
112
|
+
else left = Math.max(b.left + margin, b.right - margin - width);
|
|
113
|
+
}
|
|
114
|
+
const top = Math.max(b.top + margin, Math.min(rowRect.top - lead, b.bottom - margin - height));
|
|
115
|
+
return { left: Math.round(left), top: Math.round(top), side };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Where a menu opened from the keyboard points: inside the focused element near its top-left corner, or at its
|
|
120
|
+
* middle when it fills most of the window (the memory map).
|
|
121
|
+
*/
|
|
122
|
+
export function anchorPoint(rect, viewportWidth, viewportHeight) {
|
|
123
|
+
if (!rect) return { x: Math.round(viewportWidth / 2), y: Math.round(viewportHeight / 3) };
|
|
124
|
+
if (rect.width > viewportWidth * 0.6 && rect.height > viewportHeight * 0.6) {
|
|
125
|
+
return { x: Math.round(rect.left + rect.width / 2), y: Math.round(rect.top + rect.height / 2) };
|
|
126
|
+
}
|
|
127
|
+
return { x: Math.round(rect.left + Math.min(rect.width / 2, 16)), y: Math.round(rect.top + Math.min(rect.height / 2, 16)) };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const selectable = (item) => !!item && !item.separator && !item.disabled;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The next row the keyboard can land on from `from` in direction `dir` (1 down, -1 up), wrapping; -1 when none can.
|
|
134
|
+
* From -1, down is the first row and up the last.
|
|
135
|
+
*/
|
|
136
|
+
export function stepIndex(items, from, dir) {
|
|
137
|
+
const n = items?.length || 0;
|
|
138
|
+
if (!n) return -1;
|
|
139
|
+
let i = from;
|
|
140
|
+
for (let k = 0; k < n; k++) {
|
|
141
|
+
i = i < 0 ? (dir > 0 ? 0 : n - 1) : (i + dir + n) % n;
|
|
142
|
+
if (selectable(items[i])) return i;
|
|
143
|
+
}
|
|
144
|
+
return -1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The first and the last row the keyboard can land on. */
|
|
148
|
+
export const firstIndex = (items) => stepIndex(items, -1, 1);
|
|
149
|
+
export const lastIndex = (items) => stepIndex(items, -1, -1);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The row typed letters go to: the next one whose label starts with them, from the row after `from` (a first
|
|
153
|
+
* letter) or from `from` itself (more letters of the same word). -1 when none does.
|
|
154
|
+
*/
|
|
155
|
+
export function typeaheadIndex(items, from, typed) {
|
|
156
|
+
const n = items?.length || 0;
|
|
157
|
+
const want = String(typed || '').toLowerCase();
|
|
158
|
+
if (!n || !want) return -1;
|
|
159
|
+
const start = want.length > 1 ? Math.max(0, from) : from + 1;
|
|
160
|
+
for (let k = 0; k < n; k++) {
|
|
161
|
+
const i = (((start + k) % n) + n) % n;
|
|
162
|
+
if (selectable(items[i]) && String(items[i].label || '').toLowerCase().startsWith(want)) return i;
|
|
163
|
+
}
|
|
164
|
+
return -1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const TEXT_INPUT_TYPES = new Set(['', 'text', 'search', 'url', 'tel', 'email', 'password', 'number',
|
|
168
|
+
'date', 'datetime-local', 'month', 'time', 'week']);
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Whether an element is a field the browser's own menu belongs to (spelling, paste): a text input, a textarea, a
|
|
172
|
+
* native select or anything editable. Checkboxes, buttons and the like are not.
|
|
173
|
+
* @param {{ tagName?: string, type?: string, isContentEditable?: boolean, closest?: Function } | null} el
|
|
174
|
+
*/
|
|
175
|
+
export function isEditableTarget(el) {
|
|
176
|
+
if (!el) return false;
|
|
177
|
+
if (el.isContentEditable) return true;
|
|
178
|
+
const tag = String(el.tagName || '').toUpperCase();
|
|
179
|
+
if (tag === 'TEXTAREA' || tag === 'SELECT') return true;
|
|
180
|
+
if (tag === 'INPUT') return TEXT_INPUT_TYPES.has(String(el.type || '').toLowerCase());
|
|
181
|
+
return !!el.closest?.('[contenteditable=""], [contenteditable="true"], [contenteditable="plaintext-only"]');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Why the app's menu stays out of a right-click, or null when it opens. In order: another handler took the event
|
|
186
|
+
* (it called preventDefault: an area with a menu of its own), the press is on the app's menu itself, Shift is held
|
|
187
|
+
* (the escape hatch to the browser's menu), the target is a field, or it is in an area that opted out.
|
|
188
|
+
* @param {{ defaultPrevented?: boolean, shiftKey?: boolean }} event
|
|
189
|
+
* @param {{ inOwnMenu?: boolean, editable?: boolean, optedOut?: boolean }} target
|
|
190
|
+
* @returns {null | 'handled' | 'own-menu' | 'shift' | 'editable' | 'opt-out'}
|
|
191
|
+
*/
|
|
192
|
+
export function nativeMenuReason(event, target = {}) {
|
|
193
|
+
if (event?.defaultPrevented) return 'handled';
|
|
194
|
+
if (target.inOwnMenu) return 'own-menu';
|
|
195
|
+
if (event?.shiftKey) return 'shift';
|
|
196
|
+
if (target.editable) return 'editable';
|
|
197
|
+
if (target.optedOut) return 'opt-out';
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Whether a key press asks for a context menu: the ContextMenu key, or Shift+F10, with no other modifier. */
|
|
202
|
+
export function isMenuKey(event) {
|
|
203
|
+
if (!event || event.ctrlKey || event.metaKey || event.altKey) return false;
|
|
204
|
+
return event.key === 'ContextMenu' || (event.key === 'F10' && !!event.shiftKey);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* A link the menu can offer: http(s) only, and not one that only points into the page (`#`, `#…`) or runs script.
|
|
209
|
+
* @param {string | null} raw the href as written
|
|
210
|
+
* @param {string | null} resolved the href the browser resolved
|
|
211
|
+
* @returns {string | null} the address to open or copy
|
|
212
|
+
*/
|
|
213
|
+
export function usableLink(raw, resolved) {
|
|
214
|
+
const written = String(raw ?? '').trim();
|
|
215
|
+
if (!written || written.startsWith('#')) return null;
|
|
216
|
+
if (/^\s*javascript:/i.test(written)) return null;
|
|
217
|
+
return /^https?:\/\//i.test(String(resolved || '')) ? String(resolved) : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* An image the menu can offer. A data: or blob: picture can be copied by address; only an http(s) one opens in a
|
|
222
|
+
* new tab (browsers refuse to open a data: address there).
|
|
223
|
+
* @returns {{ src: string, canOpen: boolean } | null}
|
|
224
|
+
*/
|
|
225
|
+
export function usableImage(src) {
|
|
226
|
+
const value = String(src || '');
|
|
227
|
+
if (/^https?:\/\//i.test(value)) return { src: value, canOpen: true };
|
|
228
|
+
if (/^(data|blob):/i.test(value)) return { src: value, canOpen: false };
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** A selection as a menu label shows it: one line, at most `max` characters, an ellipsis when cut. */
|
|
233
|
+
export function quoteSelection(text, max = 24) {
|
|
234
|
+
const flat = String(text || '').replace(/\s+/g, ' ').trim();
|
|
235
|
+
return flat.length > max ? `${flat.slice(0, max - 1).trimEnd()}…` : flat;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The selected text an action takes: trimmed, and capped so a whole page selected is not sent anywhere at once. */
|
|
239
|
+
export const SELECTION_MAX = 4000;
|
|
240
|
+
export function selectionPayload(text, max = SELECTION_MAX) {
|
|
241
|
+
const value = String(text || '').trim();
|
|
242
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
243
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — The app's context menu: what is in it
|
|
3
|
+
// DOM-free. buildAppMenu(ctx, env) turns what was right-clicked into menu
|
|
4
|
+
// items. ui-context-menu.js supplies `env`: the strings (t), the keys as the
|
|
5
|
+
// central keybind registry has them now, what the page can do at this moment
|
|
6
|
+
// and the actions, which are the app's own functions (registered keybind
|
|
7
|
+
// actions, the event bus, the panels' buttons, the whiteboard's toolbar).
|
|
8
|
+
// Nothing here runs an action itself, so tests/context-menu-model.test.mjs
|
|
9
|
+
// can check every context.
|
|
10
|
+
// ═══════════════════════════════════════════
|
|
11
|
+
|
|
12
|
+
import { joinSections, quoteSelection } from './context-menu-logic.js';
|
|
13
|
+
import { resolveViewVariant } from './view-variant.js';
|
|
14
|
+
|
|
15
|
+
const svg = (body) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
|
|
16
|
+
|
|
17
|
+
// The stroke icons of the menubar's dropdowns (same viewBox, stroke and caps).
|
|
18
|
+
export const ICONS = {
|
|
19
|
+
back: svg('<line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/>'),
|
|
20
|
+
forward: svg('<line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/>'),
|
|
21
|
+
reload: svg('<path d="M23 4v6h-6"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>'),
|
|
22
|
+
copy: svg('<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>'),
|
|
23
|
+
link: svg('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>'),
|
|
24
|
+
external: svg('<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>'),
|
|
25
|
+
image: svg('<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>'),
|
|
26
|
+
search: svg('<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>'),
|
|
27
|
+
fullscreen: svg('<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>'),
|
|
28
|
+
exitFullscreen: svg('<path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>'),
|
|
29
|
+
send: svg('<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>'),
|
|
30
|
+
memory: svg('<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/>'),
|
|
31
|
+
// The memory card's Linked (focus) button
|
|
32
|
+
linked: svg('<circle cx="12" cy="12" r="3"/><path d="M12 2v4m0 12v4m-7.07-14.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83"/>'),
|
|
33
|
+
hash: svg('<line x1="4" y1="9" x2="20" y2="9"/><line x1="4" y1="15" x2="20" y2="15"/><line x1="10" y1="3" x2="8" y2="21"/><line x1="16" y1="3" x2="14" y2="21"/>'),
|
|
34
|
+
select: svg('<polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>'),
|
|
35
|
+
// Apps › Map
|
|
36
|
+
map: svg('<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/>'),
|
|
37
|
+
target: svg('<circle cx="12" cy="12" r="10"/><line x1="22" y1="12" x2="18" y2="12"/><line x1="6" y1="12" x2="2" y2="12"/><line x1="12" y1="6" x2="12" y2="2"/><line x1="12" y1="22" x2="12" y2="18"/>'),
|
|
38
|
+
frame: svg('<path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/>'),
|
|
39
|
+
sliders: svg('<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>'),
|
|
40
|
+
layers: svg('<polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/>'),
|
|
41
|
+
clear: svg('<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>'),
|
|
42
|
+
// Apps › Browser apps
|
|
43
|
+
grid: svg('<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>'),
|
|
44
|
+
keyboard: svg('<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M6 12h.01M10 12h.01M14 12h.01M18 12h.01M7 16h10"/>'),
|
|
45
|
+
settings: svg('<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>'),
|
|
46
|
+
page: svg('<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/>'),
|
|
47
|
+
panel: svg('<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="15" y1="3" x2="15" y2="21"/>'),
|
|
48
|
+
terminal: svg('<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>'),
|
|
49
|
+
folder: svg('<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>'),
|
|
50
|
+
list: svg('<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>'),
|
|
51
|
+
tag: svg('<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>'),
|
|
52
|
+
pen: svg('<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>'),
|
|
53
|
+
star: svg('<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>'),
|
|
54
|
+
zap: svg('<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>'),
|
|
55
|
+
clock: svg('<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>'),
|
|
56
|
+
play: svg('<polygon points="6 3 20 12 6 21 6 3"/>'),
|
|
57
|
+
bell: svg('<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>'),
|
|
58
|
+
help: svg('<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/>'),
|
|
59
|
+
// The focus-mode whiteboard's toolbar (html-shell.js #wb-toolbar)
|
|
60
|
+
undo: svg('<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>'),
|
|
61
|
+
redo: svg('<polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>'),
|
|
62
|
+
pointer: svg('<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/>'),
|
|
63
|
+
// The navbar's focus-mode button (#titlebar-viz-toggle)
|
|
64
|
+
exitFocus: svg('<circle cx="12" cy="12" r="4"/><circle cx="12" cy="12" r="1" fill="currentColor" stroke="none"/><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/>'),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The whiteboard's tools, in the order of its toolbar (`data-tool` of #wb-toolbar). The whiteboard has no rebindable
|
|
69
|
+
* keys for them; its own fixed keys (ui-whiteboard.js onKeyDown) are WHITEBOARD_KEYS.
|
|
70
|
+
*/
|
|
71
|
+
export const WHITEBOARD_TOOLS = ['select', 'text', 'list', 'arrow', 'shape', 'pen', 'section'];
|
|
72
|
+
|
|
73
|
+
/** The whiteboard's fixed keys, written as the keybind registry writes a key (Ctrl stands for Cmd on a Mac too). */
|
|
74
|
+
export const WHITEBOARD_KEYS = { multi: 'M', undo: 'Ctrl + Z', redo: 'Ctrl + Shift + Z', selectAll: 'Ctrl + A' };
|
|
75
|
+
|
|
76
|
+
/** Map options. The deprecated 2D definitions remain for the archived source; current contexts resolve to 3D. */
|
|
77
|
+
export const MAP_OPTIONS = { '3d': ['neighbors', 'recency', 'grid'], '2d': ['minimap', 'regionGlow'] };
|
|
78
|
+
// A switch with a registered key shows it.
|
|
79
|
+
const MAP_OPTION_ACTIONS = { minimap: 'toggle-minimap' };
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The Open › submenu: the app's panels, windows and tools, each through the path its own button or key uses.
|
|
83
|
+
* `action`: a registered keybind action (its key is shown); `press`: the button that opens it; `event`: the bus
|
|
84
|
+
* event the menubar emits. `perm`: what a guest needs (the menubar's MENU_PERM_MAP). `active`: the element whose
|
|
85
|
+
* `.active` says it is open now (a `!` in front: open while that element is NOT active). `appIcon`: the Apps
|
|
86
|
+
* menu row whose icon it shows. `null` is a separator.
|
|
87
|
+
*/
|
|
88
|
+
export const OPEN_ENTRIES = [
|
|
89
|
+
{ id: 'open-assistant', key: 'assistant', action: 'launch-assistant', appIcon: 'menu-terminal-assistant', icon: 'panel' },
|
|
90
|
+
{ id: 'open-claude-panel', key: 'claudePanel', action: 'toggle-claude-panel', active: 'topright-claude-panel-btn', appIcon: 'menu-terminal-claude', icon: 'panel' },
|
|
91
|
+
{ id: 'open-codex-panel', key: 'codexPanel', press: 'topright-codex-panel-btn', active: 'topright-codex-panel-btn', appIcon: 'menu-terminal-codex', icon: 'panel' },
|
|
92
|
+
{ id: 'open-opencode-panel', key: 'opencodePanel', press: 'topright-opencode-panel-btn', active: 'topright-opencode-panel-btn', appIcon: 'menu-terminal-opencode', icon: 'panel' },
|
|
93
|
+
null,
|
|
94
|
+
{ id: 'open-terminal', key: 'terminal', event: ['terminal:open', { profile: 'shell' }], perm: 'terminal', appIcon: 'menu-terminal-shell', icon: 'terminal' },
|
|
95
|
+
{ id: 'open-claude-terminal', key: 'claudeTerminal', action: 'launch-claude', perm: 'terminal', icon: 'terminal' },
|
|
96
|
+
{ id: 'open-browser', key: 'browser', action: 'launch-browser', perm: 'browser', appIcon: 'menu-terminal-browser', icon: 'page' },
|
|
97
|
+
null,
|
|
98
|
+
{ id: 'open-file-explorer', key: 'fileExplorer', action: 'toggle-file-explorer', active: 'menu-toggle-file-explorer', icon: 'folder' },
|
|
99
|
+
{ id: 'open-memory-explorer', key: 'memoryExplorer', action: 'toggle-explorer', active: 'menu-toggle-explorer', icon: 'list' },
|
|
100
|
+
{ id: 'open-categories', key: 'categories', action: 'toggle-categories', active: 'menu-toggle-categories', icon: 'tag' },
|
|
101
|
+
{ id: 'open-focus-mode', key: 'focusMode', action: 'toggle-focus-mode', active: '!titlebar-viz-toggle', perm: 'whiteboard', icon: 'pen' },
|
|
102
|
+
null,
|
|
103
|
+
{ id: 'open-skills', key: 'skills', action: 'open-skills', perm: 'skills', icon: 'star' },
|
|
104
|
+
{ id: 'open-automations', key: 'automations', action: 'open-automations', perm: 'automations', icon: 'zap' },
|
|
105
|
+
{ id: 'open-schedules', key: 'schedules', event: ['schedules:toggle'], active: 'menubar-schedules-btn', perm: 'automations', icon: 'clock' },
|
|
106
|
+
{ id: 'open-style-guide', key: 'styleGuide', event: ['styleguide:open'], perm: 'styleGuide', appIcon: 'menu-open-styleguide', icon: 'grid' },
|
|
107
|
+
{ id: 'open-command-runner', key: 'commandRunner', event: ['command-runner:open'], appIcon: 'menu-command-runner', icon: 'play' },
|
|
108
|
+
{ id: 'open-image-gallery', key: 'imageGallery', action: 'toggle-image-gallery', active: 'titlebar-gallery-btn', icon: 'image' },
|
|
109
|
+
{ id: 'open-notifications', key: 'notifications', action: 'toggle-session-monitor', active: 'titlebar-sessions-btn', icon: 'bell' },
|
|
110
|
+
{ id: 'open-help', key: 'help', action: 'toggle-help', icon: 'help' },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @typedef {object} MenuEnv
|
|
115
|
+
* @property {(key: string, params?: object) => string} t
|
|
116
|
+
* @property {(actionId: string) => string} shortcut the key as menus show it, '' while unbound
|
|
117
|
+
* @property {(actionId: string) => boolean} hasAction a registered keybind action
|
|
118
|
+
* @property {(id: string) => boolean} hasElement a button on this page
|
|
119
|
+
* @property {(id: string) => boolean} isActive that element has `.active`
|
|
120
|
+
* @property {(perm: string) => boolean} allowed false for a guest without that permission
|
|
121
|
+
* @property {(id: string) => string} [appIcon] the icon markup an Apps menu row shows, '' when none
|
|
122
|
+
* @property {{ url: string, canGoBack: boolean, canGoForward: boolean, fullscreen: boolean, fullscreenEnabled: boolean }} page
|
|
123
|
+
* @property {Record<string, Function>} act the actions (see ui-context-menu.js)
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* What a right-click is on.
|
|
128
|
+
* @typedef {object} MenuContext
|
|
129
|
+
* @property {string} [selection] the selected text it is over ('' or missing: none)
|
|
130
|
+
* @property {string | null} [link] an http(s) link under it
|
|
131
|
+
* @property {{ src: string, canOpen: boolean } | null} [image]
|
|
132
|
+
* @property {{ kind: 'memory', memory: MemoryTarget, act?: object } | { kind: 'map', map: MapTarget, act?: object }
|
|
133
|
+
* | { kind: 'focus', focus: FocusTarget, act?: object } | null} [area]
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @typedef {object} MemoryTarget
|
|
138
|
+
* @property {string} id
|
|
139
|
+
* @property {boolean} [card] a memory card: it is open already, so Close card instead of Open
|
|
140
|
+
* @property {boolean} [onMap] a point on the memory map: it can join the multi-selection and fly to its island
|
|
141
|
+
* @property {boolean} [selected] in the multi-selection now
|
|
142
|
+
* @property {string} [island] its category (the island it is on)
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @typedef {object} MapTarget
|
|
147
|
+
* @property {Record<string, boolean>} [options] the switches of MAP_OPTIONS[variant], as they are now
|
|
148
|
+
* @property {boolean} [searching] a search is dimming the map
|
|
149
|
+
* @property {boolean} [selected] a memory is selected on it (3D) or memories are in the multi-selection (2D)
|
|
150
|
+
* @property {{ name: string, type: string } | null} [label] an island or continent name under the pointer
|
|
151
|
+
* @property {'2d' | '3d'} [variant]
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The focus-mode screen (the whiteboard), as it is now.
|
|
156
|
+
* @typedef {object} FocusTarget
|
|
157
|
+
* @property {string} [tool] the whiteboard's active tool (WHITEBOARD_TOOLS)
|
|
158
|
+
* @property {boolean} [multi] multi-select mode is on
|
|
159
|
+
* @property {boolean} [canUndo]
|
|
160
|
+
* @property {boolean} [canRedo]
|
|
161
|
+
* @property {boolean} [hasElements] something is on the board (Select all)
|
|
162
|
+
* @property {boolean} [canEdit] false for a guest without the whiteboard permission: only Select is left
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/** The top level stays short: about 12 rows at most (tests check every context against it). */
|
|
166
|
+
export const MAX_TOP_LEVEL = 12;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The menu for a right-click.
|
|
170
|
+
* @param {MenuContext} ctx
|
|
171
|
+
* @param {MenuEnv} env
|
|
172
|
+
*/
|
|
173
|
+
export function buildAppMenu(ctx = {}, env) {
|
|
174
|
+
const act = { ...env.act, ...(ctx.area?.act || {}) };
|
|
175
|
+
const selection = ctx.selection || '';
|
|
176
|
+
const targeted = !!(selection || ctx.link || ctx.image || ctx.area);
|
|
177
|
+
const sections = [];
|
|
178
|
+
if (!targeted) sections.push(pageNavigation(env, act), pageTools(env, act, { withCopy: true }));
|
|
179
|
+
if (selection) sections.push(selectionItems(selection, env, act));
|
|
180
|
+
if (ctx.link) sections.push(linkItems(ctx.link, env, act));
|
|
181
|
+
if (ctx.image) sections.push(imageItems(ctx.image, env, act));
|
|
182
|
+
if (ctx.area?.kind === 'memory') sections.push(memoryItems(ctx.area.memory, env, act));
|
|
183
|
+
if (ctx.area?.kind === 'map') sections.push(mapItems(ctx.area.map, env, act));
|
|
184
|
+
if (ctx.area?.kind === 'focus') sections.push(focusItems(ctx.area.focus, env, act));
|
|
185
|
+
sections.push(appItems(env, act, { search: !selection }));
|
|
186
|
+
if (targeted) sections.push([pageSubmenu(env, act)]);
|
|
187
|
+
return joinSections(sections);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Page ──
|
|
191
|
+
|
|
192
|
+
function pageNavigation({ t, page }, act) {
|
|
193
|
+
return [
|
|
194
|
+
{ id: 'back', label: t('contextMenu.page.back'), icon: ICONS.back, disabled: !page.canGoBack, run: () => act.back() },
|
|
195
|
+
{ id: 'forward', label: t('contextMenu.page.forward'), icon: ICONS.forward, disabled: !page.canGoForward, run: () => act.forward() },
|
|
196
|
+
{ id: 'reload', label: t('contextMenu.page.reload'), icon: ICONS.reload, run: () => act.reload() },
|
|
197
|
+
];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function pageTools({ t, page }, act, { withCopy = false } = {}) {
|
|
201
|
+
return [
|
|
202
|
+
// Nothing is selected where the menu opened: Copy is there, and off.
|
|
203
|
+
withCopy && { id: 'copy', label: t('contextMenu.selection.copy'), icon: ICONS.copy, disabled: true },
|
|
204
|
+
{ id: 'copy-url', label: t('contextMenu.page.copyUrl'), icon: ICONS.link, run: () => act.copyText(page.url) },
|
|
205
|
+
{
|
|
206
|
+
id: 'fullscreen',
|
|
207
|
+
label: t(page.fullscreen ? 'contextMenu.page.exitFullscreen' : 'contextMenu.page.fullscreen'),
|
|
208
|
+
icon: page.fullscreen ? ICONS.exitFullscreen : ICONS.fullscreen,
|
|
209
|
+
disabled: !page.fullscreenEnabled,
|
|
210
|
+
run: () => act.toggleFullscreen(),
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function pageSubmenu(env, act) {
|
|
216
|
+
return {
|
|
217
|
+
id: 'page',
|
|
218
|
+
label: env.t('contextMenu.page.submenu'),
|
|
219
|
+
icon: ICONS.page,
|
|
220
|
+
submenu: [...pageNavigation(env, act), { separator: true }, ...pageTools(env, act)],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Selection, link, image ──
|
|
225
|
+
|
|
226
|
+
function selectionItems(text, { t }, act) {
|
|
227
|
+
return [
|
|
228
|
+
{ id: 'copy', label: t('contextMenu.selection.copy'), icon: ICONS.copy, run: () => act.copyText(text) },
|
|
229
|
+
{ id: 'search-selection', label: t('contextMenu.selection.search', { text: quoteSelection(text) }), icon: ICONS.search, run: () => act.searchMemories(text) },
|
|
230
|
+
{ id: 'send-selection', label: t('contextMenu.selection.sendToAi'), icon: ICONS.send, run: () => act.sendText(text) },
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function linkItems(href, { t }, act) {
|
|
235
|
+
return [
|
|
236
|
+
{ id: 'open-link', label: t('contextMenu.link.open'), icon: ICONS.external, run: () => act.openInNewTab(href) },
|
|
237
|
+
{ id: 'copy-link', label: t('contextMenu.link.copy'), icon: ICONS.link, run: () => act.copyText(href) },
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function imageItems(image, { t }, act) {
|
|
242
|
+
return [
|
|
243
|
+
{ id: 'open-image', label: t('contextMenu.image.open'), icon: ICONS.image, disabled: !image.canOpen, run: () => act.openInNewTab(image.src) },
|
|
244
|
+
{ id: 'copy-image', label: t('contextMenu.image.copy'), icon: ICONS.copy, run: () => act.copyText(image.src) },
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── A memory (a point on the map, an Explorer row, a memory card) ──
|
|
249
|
+
|
|
250
|
+
function memoryItems(memory, { t }, act) {
|
|
251
|
+
const { id } = memory;
|
|
252
|
+
return [
|
|
253
|
+
!memory.card && { id: 'memory-open', label: t('contextMenu.memory.open'), icon: ICONS.memory, run: () => act.openMemory(id) },
|
|
254
|
+
{ id: 'memory-linked', label: t('contextMenu.memory.linked'), icon: ICONS.linked, run: () => act.showLinked(id) },
|
|
255
|
+
{ id: 'memory-copy-id', label: t('contextMenu.memory.copyId'), icon: ICONS.hash, run: () => act.copyText(id) },
|
|
256
|
+
{ id: 'memory-send', label: t('contextMenu.memory.sendToAi'), icon: ICONS.send, run: () => act.sendMemory(id) },
|
|
257
|
+
memory.onMap && {
|
|
258
|
+
id: 'memory-select',
|
|
259
|
+
label: t(memory.selected ? 'contextMenu.memory.deselect' : 'contextMenu.memory.select'),
|
|
260
|
+
icon: ICONS.select,
|
|
261
|
+
run: () => act.toggleSelect(id),
|
|
262
|
+
},
|
|
263
|
+
memory.onMap && memory.island && {
|
|
264
|
+
id: 'memory-island',
|
|
265
|
+
label: t('contextMenu.memory.goToIsland', { name: memory.island }),
|
|
266
|
+
icon: ICONS.target,
|
|
267
|
+
run: () => act.flyToIsland(memory.island),
|
|
268
|
+
},
|
|
269
|
+
memory.card && { id: 'memory-close-card', label: t('contextMenu.memory.closeCard'), icon: ICONS.clear, run: () => act.closeCard(id) },
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── The memory map's empty space or a name on it ──
|
|
274
|
+
|
|
275
|
+
function mapItems(map, { t, shortcut }, act) {
|
|
276
|
+
const options = map.options || {};
|
|
277
|
+
const variant = resolveViewVariant(map.variant);
|
|
278
|
+
const toggle = (key) => ({
|
|
279
|
+
id: `map-${key}`,
|
|
280
|
+
label: t(`contextMenu.map.${key}`),
|
|
281
|
+
checked: !!options[key],
|
|
282
|
+
shortcut: MAP_OPTION_ACTIONS[key] ? shortcut(MAP_OPTION_ACTIONS[key]) : '',
|
|
283
|
+
run: () => act.toggleOption(key),
|
|
284
|
+
});
|
|
285
|
+
return [
|
|
286
|
+
map.label && { id: 'map-go-to', label: t('contextMenu.map.goTo', { name: map.label.name }), icon: ICONS.target, run: () => act.flyToLabel() },
|
|
287
|
+
{ id: 'map-frame-all', label: t('contextMenu.map.frameAll'), icon: ICONS.frame, shortcut: shortcut('map-frame-all'), run: () => act.frameAll() },
|
|
288
|
+
{ id: 'map-options', label: t('contextMenu.map.options'), icon: ICONS.sliders, submenu: MAP_OPTIONS[variant].map(toggle) },
|
|
289
|
+
map.searching && { id: 'map-clear-search', label: t('contextMenu.map.clearSearch'), icon: ICONS.clear, run: () => act.clearSearch() },
|
|
290
|
+
map.selected && { id: 'map-clear-selection', label: t('contextMenu.map.clearSelection'), icon: ICONS.clear, run: () => act.clearSelection() },
|
|
291
|
+
];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ── The focus-mode screen: the whiteboard's own tools, and the way out ──
|
|
295
|
+
|
|
296
|
+
function focusItems(focus, { t, shortcut }, act) {
|
|
297
|
+
const tool = focus.tool || 'select';
|
|
298
|
+
const canEdit = focus.canEdit !== false;
|
|
299
|
+
const tools = WHITEBOARD_TOOLS.map((key) => ({
|
|
300
|
+
id: `focus-tool-${key}`,
|
|
301
|
+
label: t(`contextMenu.focus.tools.${key}`),
|
|
302
|
+
checked: !focus.multi && tool === key,
|
|
303
|
+
disabled: !canEdit && key !== 'select',
|
|
304
|
+
run: () => act.setTool(key),
|
|
305
|
+
}));
|
|
306
|
+
return [
|
|
307
|
+
{
|
|
308
|
+
id: 'focus-tool',
|
|
309
|
+
label: t('contextMenu.focus.tool'),
|
|
310
|
+
icon: ICONS.pointer,
|
|
311
|
+
submenu: [
|
|
312
|
+
...tools,
|
|
313
|
+
{ separator: true },
|
|
314
|
+
{ id: 'focus-multi', label: t('contextMenu.focus.multiSelect'), checked: !!focus.multi, disabled: !canEdit, shortcut: WHITEBOARD_KEYS.multi, run: () => act.toggleMulti() },
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
{ id: 'focus-undo', label: t('contextMenu.focus.undo'), icon: ICONS.undo, shortcut: WHITEBOARD_KEYS.undo, disabled: !canEdit || !focus.canUndo, run: () => act.undo() },
|
|
318
|
+
{ id: 'focus-redo', label: t('contextMenu.focus.redo'), icon: ICONS.redo, shortcut: WHITEBOARD_KEYS.redo, disabled: !canEdit || !focus.canRedo, run: () => act.redo() },
|
|
319
|
+
{ id: 'focus-select-all', label: t('contextMenu.focus.selectAll'), icon: ICONS.select, shortcut: WHITEBOARD_KEYS.selectAll, disabled: !focus.hasElements, run: () => act.selectAll() },
|
|
320
|
+
{ id: 'focus-exit', label: t('contextMenu.focus.exit'), icon: ICONS.exitFocus, shortcut: shortcut('toggle-focus-mode'), run: () => act.exitFocus() },
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ── The app ──
|
|
325
|
+
|
|
326
|
+
function openItems(env, act) {
|
|
327
|
+
const { t, shortcut, hasAction, hasElement, isActive, allowed } = env;
|
|
328
|
+
return OPEN_ENTRIES.map((entry) => {
|
|
329
|
+
if (!entry) return { separator: true };
|
|
330
|
+
const item = {
|
|
331
|
+
id: entry.id,
|
|
332
|
+
label: t(`contextMenu.open.${entry.key}`),
|
|
333
|
+
icon: env.appIcon?.(entry.appIcon) || ICONS[entry.icon] || '',
|
|
334
|
+
disabled: entry.perm ? !allowed(entry.perm) : false,
|
|
335
|
+
};
|
|
336
|
+
if (entry.action) {
|
|
337
|
+
item.hidden = !hasAction(entry.action);
|
|
338
|
+
item.shortcut = shortcut(entry.action);
|
|
339
|
+
item.run = () => act.runAction(entry.action);
|
|
340
|
+
} else if (entry.press) {
|
|
341
|
+
item.hidden = !hasElement(entry.press);
|
|
342
|
+
item.run = () => act.press(entry.press);
|
|
343
|
+
} else {
|
|
344
|
+
item.run = () => act.emit(...entry.event);
|
|
345
|
+
}
|
|
346
|
+
if (entry.active) {
|
|
347
|
+
const negate = entry.active.startsWith('!');
|
|
348
|
+
const id = negate ? entry.active.slice(1) : entry.active;
|
|
349
|
+
if (hasElement(id)) item.checked = negate ? !isActive(id) : isActive(id);
|
|
350
|
+
}
|
|
351
|
+
return item;
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function appItems(env, act, { search = true } = {}) {
|
|
356
|
+
const { t, shortcut, hasAction } = env;
|
|
357
|
+
const registered = (id, actionId, key, icon) => ({
|
|
358
|
+
id, label: t(key), icon, shortcut: shortcut(actionId), hidden: !hasAction(actionId), run: () => act.runAction(actionId),
|
|
359
|
+
});
|
|
360
|
+
return [
|
|
361
|
+
{ id: 'open', label: t('contextMenu.app.open'), icon: ICONS.grid, submenu: openItems(env, act) },
|
|
362
|
+
search && registered('search', 'focus-search', 'contextMenu.app.search', ICONS.search),
|
|
363
|
+
registered('shortcuts', 'open-keybinds', 'contextMenu.app.shortcuts', ICONS.keyboard),
|
|
364
|
+
registered('settings', 'open-settings', 'contextMenu.app.settings', ICONS.settings),
|
|
365
|
+
];
|
|
366
|
+
}
|