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,372 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Context menu component
|
|
3
|
+
// One menu at a time, at the pointer or by the focused element, in the box of
|
|
4
|
+
// the menubar's dropdown and with its rows (.menu-item, .menu-shortcut,
|
|
5
|
+
// .menu-submenu-arrow, .menu-sep). Items are data (context-menu-logic.js);
|
|
6
|
+
// this module draws them, places the menu and its submenus inside the window,
|
|
7
|
+
// takes the keys while it is open and closes on a press outside, Escape,
|
|
8
|
+
// Tab, scroll, resize, window blur and after an action. Any area can open
|
|
9
|
+
// one; the app's global right-click menu is ui-context-menu.js.
|
|
10
|
+
// ═══════════════════════════════════════════
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
anchorPoint, firstIndex, lastIndex, normalizeItems, placeMenu, placeSubmenu, stepIndex, typeaheadIndex,
|
|
14
|
+
} from './context-menu-logic.js';
|
|
15
|
+
import { boundsForPoint, frameRect } from './app-frame.js';
|
|
16
|
+
|
|
17
|
+
const SUBMENU_OPEN_DELAY = 120; // hover intent before a submenu opens
|
|
18
|
+
const SUBMENU_GRACE = 300; // the pointer may cross other rows on its way into an open submenu
|
|
19
|
+
const TYPEAHEAD_RESET = 600;
|
|
20
|
+
const ARROW_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>';
|
|
21
|
+
|
|
22
|
+
let _open = null; // the open menu's handle
|
|
23
|
+
|
|
24
|
+
/** Whether a menu is open. */
|
|
25
|
+
export const isContextMenuOpen = () => !!_open;
|
|
26
|
+
|
|
27
|
+
/** Close the open menu, if there is one. `refocus: false` leaves the keyboard where it is. */
|
|
28
|
+
export function closeContextMenu(o) { _open?.close(o); }
|
|
29
|
+
|
|
30
|
+
/** The root element of the open menu, or null. */
|
|
31
|
+
export const openContextMenuElement = () => _open?.element || null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Open a menu.
|
|
35
|
+
* @param {object} o
|
|
36
|
+
* @param {import('./context-menu-logic.js').MenuItem[]} o.items
|
|
37
|
+
* @param {number} [o.x] @param {number} [o.y] where the press was (client px)
|
|
38
|
+
* @param {DOMRect | null} [o.anchorRect] opened from the keyboard: the focused element's box, used when x/y are missing
|
|
39
|
+
* @param {string} [o.label] the menu's accessible name
|
|
40
|
+
* @param {boolean} [o.keyboard] opened from the keyboard: the first row has the focus from the start
|
|
41
|
+
* @param {Element | null} [o.returnFocus] where the keyboard goes back to when the menu closes
|
|
42
|
+
* @param {() => void} [o.onClose]
|
|
43
|
+
* @returns {{ close: Function, element: HTMLElement } | null} null when no item is left to show
|
|
44
|
+
*/
|
|
45
|
+
export function openContextMenu({ items, x, y, anchorRect = null, label = '', keyboard = false,
|
|
46
|
+
returnFocus = document.activeElement, onClose } = {}) {
|
|
47
|
+
// A menu opened over another one hands the keyboard back to where the first one would have.
|
|
48
|
+
if (_open && (!returnFocus || _open.contains(returnFocus))) returnFocus = _open.returnFocus;
|
|
49
|
+
closeContextMenu({ refocus: false });
|
|
50
|
+
const list = normalizeItems(items);
|
|
51
|
+
if (!list.length) return null;
|
|
52
|
+
|
|
53
|
+
const levels = []; // [{ el, items, rows, active, parentRow }]
|
|
54
|
+
let bounds = null; // the app frame for a menu opened inside it (it and its submenus stay in it), else the window
|
|
55
|
+
let closed = false;
|
|
56
|
+
let subTimer = 0;
|
|
57
|
+
let subPending = null;
|
|
58
|
+
let typed = '';
|
|
59
|
+
let typedAt = 0;
|
|
60
|
+
|
|
61
|
+
// ── Drawing ──
|
|
62
|
+
function drawRow(item) {
|
|
63
|
+
if (item.separator) {
|
|
64
|
+
const sep = document.createElement('div');
|
|
65
|
+
sep.className = 'menu-sep';
|
|
66
|
+
sep.setAttribute('role', 'separator');
|
|
67
|
+
return sep;
|
|
68
|
+
}
|
|
69
|
+
const row = document.createElement('div');
|
|
70
|
+
row.className = 'menu-item';
|
|
71
|
+
row.tabIndex = -1;
|
|
72
|
+
const checkable = typeof item.checked === 'boolean';
|
|
73
|
+
row.setAttribute('role', checkable ? 'menuitemcheckbox' : 'menuitem');
|
|
74
|
+
if (checkable) {
|
|
75
|
+
row.setAttribute('aria-checked', String(item.checked));
|
|
76
|
+
row.classList.toggle('active', item.checked);
|
|
77
|
+
}
|
|
78
|
+
if (item.id) row.dataset.ctxId = item.id;
|
|
79
|
+
if (item.disabled) {
|
|
80
|
+
row.classList.add('disabled');
|
|
81
|
+
row.setAttribute('aria-disabled', 'true');
|
|
82
|
+
}
|
|
83
|
+
const check = document.createElement('span');
|
|
84
|
+
check.className = 'menu-check';
|
|
85
|
+
check.setAttribute('aria-hidden', 'true');
|
|
86
|
+
check.textContent = checkable ? '✓' : '';
|
|
87
|
+
const text = document.createElement('span');
|
|
88
|
+
text.className = 'menu-text';
|
|
89
|
+
if (item.icon) {
|
|
90
|
+
const icon = document.createElement('span');
|
|
91
|
+
icon.className = 'menu-icon';
|
|
92
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
93
|
+
icon.innerHTML = item.icon; // the app's own markup, never text from the page
|
|
94
|
+
text.append(icon);
|
|
95
|
+
}
|
|
96
|
+
const name = document.createElement('span');
|
|
97
|
+
name.className = 'app-ctx-label';
|
|
98
|
+
name.textContent = item.label;
|
|
99
|
+
text.append(name);
|
|
100
|
+
row.append(check, text);
|
|
101
|
+
if (item.submenu) {
|
|
102
|
+
row.setAttribute('aria-haspopup', 'menu');
|
|
103
|
+
row.setAttribute('aria-expanded', 'false');
|
|
104
|
+
const arrow = document.createElement('span');
|
|
105
|
+
arrow.className = 'menu-submenu-arrow';
|
|
106
|
+
arrow.setAttribute('aria-hidden', 'true');
|
|
107
|
+
arrow.innerHTML = ARROW_SVG;
|
|
108
|
+
row.append(arrow);
|
|
109
|
+
} else if (item.shortcut) {
|
|
110
|
+
const key = document.createElement('span');
|
|
111
|
+
key.className = 'menu-shortcut';
|
|
112
|
+
key.textContent = item.shortcut;
|
|
113
|
+
row.append(key);
|
|
114
|
+
}
|
|
115
|
+
return row;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function drawLevel(levelItems, name) {
|
|
119
|
+
const el = document.createElement('div');
|
|
120
|
+
el.className = 'app-ctx-menu';
|
|
121
|
+
el.setAttribute('role', 'menu');
|
|
122
|
+
if (name) el.setAttribute('aria-label', name);
|
|
123
|
+
el.tabIndex = -1;
|
|
124
|
+
el.style.visibility = 'hidden';
|
|
125
|
+
const rows = levelItems.map(drawRow);
|
|
126
|
+
el.append(...rows);
|
|
127
|
+
const level = { el, items: levelItems, rows, active: -1, parentRow: null };
|
|
128
|
+
el.addEventListener('pointermove', (event) => onHover(level, event));
|
|
129
|
+
el.addEventListener('click', (event) => {
|
|
130
|
+
const index = rowIndex(level, event.target);
|
|
131
|
+
if (index >= 0) activate(level, index);
|
|
132
|
+
});
|
|
133
|
+
// The press stays off the page: nothing selected there is cleared, nothing behind the menu is focused.
|
|
134
|
+
el.addEventListener('pointerdown', (event) => event.preventDefault());
|
|
135
|
+
el.addEventListener('mousedown', (event) => event.preventDefault());
|
|
136
|
+
el.addEventListener('contextmenu', (event) => event.preventDefault());
|
|
137
|
+
document.body.appendChild(el);
|
|
138
|
+
return level;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const rowIndex = (level, node) => {
|
|
142
|
+
const row = node?.closest?.('.menu-item');
|
|
143
|
+
return row ? level.rows.indexOf(row) : -1;
|
|
144
|
+
};
|
|
145
|
+
const levelOf = (index) => levels[index];
|
|
146
|
+
const levelIndex = (level) => levels.indexOf(level);
|
|
147
|
+
|
|
148
|
+
// ── The row the keyboard and the pointer are on ──
|
|
149
|
+
function setActive(level, index, { focus = true } = {}) {
|
|
150
|
+
if (level.active >= 0) level.rows[level.active]?.classList.remove('focused');
|
|
151
|
+
level.active = index;
|
|
152
|
+
const row = index >= 0 ? level.rows[index] : null;
|
|
153
|
+
if (!row) { if (focus) level.el.focus({ preventScroll: true }); return; }
|
|
154
|
+
row.classList.add('focused');
|
|
155
|
+
if (focus) row.focus({ preventScroll: true });
|
|
156
|
+
row.scrollIntoView?.({ block: 'nearest' });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// The level the keyboard is in: the deepest one with a row on, else the deepest one open.
|
|
160
|
+
function keyLevel() {
|
|
161
|
+
for (let i = levels.length - 1; i >= 0; i--) if (levels[i].active >= 0) return levels[i];
|
|
162
|
+
return levels[levels.length - 1];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Submenus ──
|
|
166
|
+
function openSubmenu(level, index, { focusFirst = false } = {}) {
|
|
167
|
+
cancelTimer();
|
|
168
|
+
const item = level.items[index];
|
|
169
|
+
const row = level.rows[index];
|
|
170
|
+
if (!item?.submenu || item.disabled) return;
|
|
171
|
+
const depth = levelIndex(level);
|
|
172
|
+
const next = levelOf(depth + 1);
|
|
173
|
+
if (next?.parentRow !== row) {
|
|
174
|
+
closeFrom(depth + 1);
|
|
175
|
+
const sub = drawLevel(item.submenu, item.label);
|
|
176
|
+
sub.parentRow = row;
|
|
177
|
+
levels.push(sub);
|
|
178
|
+
row.classList.add('submenu-open');
|
|
179
|
+
row.setAttribute('aria-expanded', 'true');
|
|
180
|
+
const menuRect = level.el.getBoundingClientRect();
|
|
181
|
+
const rowRect = row.getBoundingClientRect();
|
|
182
|
+
const box = sub.el.getBoundingClientRect();
|
|
183
|
+
const at = placeSubmenu({ menuRect, rowRect, width: box.width, height: box.height,
|
|
184
|
+
viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, bounds });
|
|
185
|
+
sub.el.style.left = `${at.left}px`;
|
|
186
|
+
sub.el.style.top = `${at.top}px`;
|
|
187
|
+
sub.el.dataset.side = at.side;
|
|
188
|
+
sub.el.style.visibility = '';
|
|
189
|
+
}
|
|
190
|
+
if (focusFirst) setActive(levelOf(depth + 1), firstIndex(item.submenu));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Close every level from `depth` down; the row that opened the first of them is no longer open.
|
|
194
|
+
function closeFrom(depth) {
|
|
195
|
+
while (levels.length > Math.max(depth, 1)) {
|
|
196
|
+
const level = levels.pop();
|
|
197
|
+
level.parentRow?.classList.remove('submenu-open');
|
|
198
|
+
level.parentRow?.setAttribute('aria-expanded', 'false');
|
|
199
|
+
level.el.remove();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function scheduleTimer(row, fn, ms) {
|
|
204
|
+
clearTimeout(subTimer);
|
|
205
|
+
subPending = row;
|
|
206
|
+
subTimer = setTimeout(() => { subTimer = 0; subPending = null; if (!closed) fn(); }, ms);
|
|
207
|
+
}
|
|
208
|
+
function cancelTimer() {
|
|
209
|
+
clearTimeout(subTimer);
|
|
210
|
+
subTimer = 0;
|
|
211
|
+
subPending = null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function onHover(level, event) {
|
|
215
|
+
const index = rowIndex(level, event.target);
|
|
216
|
+
if (index < 0) return;
|
|
217
|
+
const item = level.items[index];
|
|
218
|
+
const row = level.rows[index];
|
|
219
|
+
if (item.disabled) return;
|
|
220
|
+
if (level.active !== index) setActive(level, index);
|
|
221
|
+
// Back in a parent level: the deeper ones lose their highlight, not their place.
|
|
222
|
+
const depth = levelIndex(level);
|
|
223
|
+
for (let i = depth + 1; i < levels.length; i++) setActive(levels[i], -1, { focus: false });
|
|
224
|
+
if (row === subPending) return;
|
|
225
|
+
const next = levelOf(depth + 1);
|
|
226
|
+
if (next) {
|
|
227
|
+
if (next.parentRow === row) { cancelTimer(); return; }
|
|
228
|
+
scheduleTimer(row, () => (item.submenu ? openSubmenu(level, index) : closeFrom(depth + 1)), SUBMENU_GRACE);
|
|
229
|
+
} else if (item.submenu) {
|
|
230
|
+
scheduleTimer(row, () => openSubmenu(level, index), SUBMENU_OPEN_DELAY);
|
|
231
|
+
} else {
|
|
232
|
+
cancelTimer();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Choosing ──
|
|
237
|
+
function activate(level, index) {
|
|
238
|
+
const item = level.items[index];
|
|
239
|
+
if (!item || item.separator || item.disabled) return;
|
|
240
|
+
if (item.submenu) { openSubmenu(level, index, { focusFirst: true }); return; }
|
|
241
|
+
close({ refocus: true });
|
|
242
|
+
try {
|
|
243
|
+
const result = item.run?.();
|
|
244
|
+
if (result && typeof result.catch === 'function') result.catch((err) => console.warn('[context-menu] action failed:', err));
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.warn('[context-menu] action failed:', err);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Keys (at the window, before anything in the page; the app's single-key shortcuts must not fire) ──
|
|
251
|
+
function onKey(event) {
|
|
252
|
+
if (event.ctrlKey || event.metaKey || event.altKey) {
|
|
253
|
+
if (event.key === 'Escape') { event.preventDefault(); close({ refocus: true }); }
|
|
254
|
+
return; // the browser's and the system's keys stay theirs
|
|
255
|
+
}
|
|
256
|
+
const level = keyLevel();
|
|
257
|
+
const depth = levelIndex(level);
|
|
258
|
+
const deepest = levels.length - 1; // a submenu the pointer opened may be open without the keyboard in it
|
|
259
|
+
const { key } = event;
|
|
260
|
+
const handled = () => { event.preventDefault(); event.stopPropagation(); };
|
|
261
|
+
// Moving to another row closes a submenu that belongs to the row left.
|
|
262
|
+
const move = (index) => {
|
|
263
|
+
setActive(level, index);
|
|
264
|
+
if (levelOf(depth + 1) && levelOf(depth + 1).parentRow !== level.rows[level.active]) closeFrom(depth + 1);
|
|
265
|
+
};
|
|
266
|
+
switch (key) {
|
|
267
|
+
case 'ArrowDown': handled(); move(stepIndex(level.items, level.active, 1)); return;
|
|
268
|
+
case 'ArrowUp': handled(); move(stepIndex(level.items, level.active, -1)); return;
|
|
269
|
+
case 'Home': handled(); move(firstIndex(level.items)); return;
|
|
270
|
+
case 'End': handled(); move(lastIndex(level.items)); return;
|
|
271
|
+
case 'ArrowRight':
|
|
272
|
+
handled();
|
|
273
|
+
if (level.items[level.active]?.submenu) openSubmenu(level, level.active, { focusFirst: true });
|
|
274
|
+
return;
|
|
275
|
+
case 'ArrowLeft':
|
|
276
|
+
handled();
|
|
277
|
+
if (deepest > 0) backTo(deepest);
|
|
278
|
+
return;
|
|
279
|
+
case 'Escape':
|
|
280
|
+
handled();
|
|
281
|
+
if (deepest > 0) backTo(deepest); else close({ refocus: true });
|
|
282
|
+
return;
|
|
283
|
+
case 'Tab': handled(); close({ refocus: true }); return;
|
|
284
|
+
case 'Enter':
|
|
285
|
+
case ' ':
|
|
286
|
+
handled();
|
|
287
|
+
if (level.active >= 0) activate(level, level.active);
|
|
288
|
+
return;
|
|
289
|
+
default:
|
|
290
|
+
if (key.length !== 1) return;
|
|
291
|
+
handled();
|
|
292
|
+
{
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
typed = now - typedAt > TYPEAHEAD_RESET ? key : typed + key;
|
|
295
|
+
typedAt = now;
|
|
296
|
+
const index = typeaheadIndex(level.items, level.active, typed);
|
|
297
|
+
if (index >= 0) move(index);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// A Space released on a row must not reach it as a click either.
|
|
302
|
+
function onKeyUp(event) {
|
|
303
|
+
if (!event.ctrlKey && !event.metaKey && !event.altKey && (event.key === ' ' || event.key === 'Enter')) {
|
|
304
|
+
event.preventDefault();
|
|
305
|
+
event.stopPropagation();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Leave a submenu for the row that opened it.
|
|
310
|
+
function backTo(depth) {
|
|
311
|
+
const parent = levelOf(depth - 1);
|
|
312
|
+
const row = levelOf(depth).parentRow;
|
|
313
|
+
closeFrom(depth);
|
|
314
|
+
setActive(parent, parent.rows.indexOf(row));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ── Closing ──
|
|
318
|
+
const inside = (node) => levels.some((level) => level.el.contains(node));
|
|
319
|
+
const onOutsidePress = (event) => { if (!inside(event.target)) close({ refocus: false }); };
|
|
320
|
+
const onScroll = (event) => { if (!inside(event.target)) close({ refocus: false }); };
|
|
321
|
+
const onGone = () => close({ refocus: false });
|
|
322
|
+
|
|
323
|
+
function close({ refocus = true } = {}) {
|
|
324
|
+
if (closed) return;
|
|
325
|
+
closed = true;
|
|
326
|
+
if (_open === handle) _open = null;
|
|
327
|
+
cancelTimer();
|
|
328
|
+
window.removeEventListener('keydown', onKey, true);
|
|
329
|
+
window.removeEventListener('keyup', onKeyUp, true);
|
|
330
|
+
document.removeEventListener('pointerdown', onOutsidePress, true);
|
|
331
|
+
document.removeEventListener('scroll', onScroll, true);
|
|
332
|
+
document.removeEventListener('wheel', onScroll, true);
|
|
333
|
+
window.removeEventListener('resize', onGone);
|
|
334
|
+
window.removeEventListener('blur', onGone);
|
|
335
|
+
// The keyboard goes back where it was, unless the press outside already took it somewhere.
|
|
336
|
+
const active = document.activeElement;
|
|
337
|
+
const held = !active || active === document.body || inside(active);
|
|
338
|
+
for (const level of levels.splice(0)) level.el.remove();
|
|
339
|
+
if (refocus && held && returnFocus?.isConnected && typeof returnFocus.focus === 'function') {
|
|
340
|
+
try { returnFocus.focus({ preventScroll: true }); } catch { /* not focusable any more */ }
|
|
341
|
+
}
|
|
342
|
+
try { onClose?.(); } catch (err) { console.warn('[context-menu] onClose failed:', err); }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── Open ──
|
|
346
|
+
const root = drawLevel(list, label);
|
|
347
|
+
levels.push(root);
|
|
348
|
+
const box = root.el.getBoundingClientRect();
|
|
349
|
+
let px = x;
|
|
350
|
+
let py = y;
|
|
351
|
+
if (!Number.isFinite(px) || !Number.isFinite(py)) ({ x: px, y: py } = anchorPoint(anchorRect, window.innerWidth, window.innerHeight));
|
|
352
|
+
bounds = boundsForPoint(frameRect(), px, py);
|
|
353
|
+
const at = placeMenu({ x: px, y: py, width: box.width, height: box.height,
|
|
354
|
+
viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, bounds });
|
|
355
|
+
root.el.style.left = `${at.left}px`;
|
|
356
|
+
root.el.style.top = `${at.top}px`;
|
|
357
|
+
root.el.style.visibility = '';
|
|
358
|
+
root.el.classList.add('app-ctx-menu--root');
|
|
359
|
+
|
|
360
|
+
const handle = { close, element: root.el, returnFocus, contains: inside };
|
|
361
|
+
_open = handle;
|
|
362
|
+
window.addEventListener('keydown', onKey, true);
|
|
363
|
+
window.addEventListener('keyup', onKeyUp, true);
|
|
364
|
+
document.addEventListener('pointerdown', onOutsidePress, true);
|
|
365
|
+
document.addEventListener('scroll', onScroll, true);
|
|
366
|
+
document.addEventListener('wheel', onScroll, { capture: true, passive: true });
|
|
367
|
+
window.addEventListener('resize', onGone);
|
|
368
|
+
window.addEventListener('blur', onGone);
|
|
369
|
+
if (keyboard) setActive(root, firstIndex(list));
|
|
370
|
+
else root.el.focus({ preventScroll: true });
|
|
371
|
+
return handle;
|
|
372
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { cpCtx } from './cp-ctx.js';
|
|
14
14
|
import { contextReading, contextMenuModel, isUnset } from './cp-context-model.js';
|
|
15
|
+
import { frameRect } from '../app-frame.js';
|
|
15
16
|
|
|
16
17
|
const POP_ID = 'cp-context-popover';
|
|
17
18
|
const LABEL = 'Context settings';
|
|
@@ -149,24 +150,24 @@ function tipClipped() {
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
// Below the cog, its right edge on the cog's; above it when there is more room
|
|
152
|
-
// there; always inside the
|
|
153
|
+
// there; always inside the app frame, scrolling inside when it is taller.
|
|
153
154
|
function place() {
|
|
154
155
|
const $cog = cog();
|
|
155
156
|
if (!_open || !_pop || !$cog) return;
|
|
156
157
|
const r = $cog.getBoundingClientRect();
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const width = Math.max(0, Math.min(WIDTH,
|
|
158
|
+
// Inside the app frame (app-frame.js; the whole window on a page without it)
|
|
159
|
+
const f = frameRect();
|
|
160
|
+
const width = Math.max(0, Math.min(WIDTH, f.width - MARGIN * 2));
|
|
160
161
|
_pop.style.width = `${width}px`;
|
|
161
|
-
const below =
|
|
162
|
-
const above = r.top - GAP - MARGIN;
|
|
162
|
+
const below = f.bottom - r.bottom - GAP - MARGIN;
|
|
163
|
+
const above = r.top - f.top - GAP - MARGIN;
|
|
163
164
|
const up = below < 220 && above > below;
|
|
164
165
|
_pop.style.maxHeight = `${Math.max(120, up ? above : below)}px`;
|
|
165
166
|
const height = _pop.offsetHeight;
|
|
166
|
-
const left = Math.max(MARGIN, Math.min(r.right - width,
|
|
167
|
+
const left = Math.max(f.left + MARGIN, Math.min(r.right - width, f.right - width - MARGIN));
|
|
167
168
|
const top = up
|
|
168
|
-
? Math.max(MARGIN, r.top - GAP - height)
|
|
169
|
-
: Math.max(MARGIN, Math.min(r.bottom + GAP,
|
|
169
|
+
? Math.max(f.top + MARGIN, r.top - GAP - height)
|
|
170
|
+
: Math.max(f.top + MARGIN, Math.min(r.bottom + GAP, f.bottom - MARGIN - height));
|
|
170
171
|
_pop.style.left = `${Math.round(left)}px`;
|
|
171
172
|
_pop.style.top = `${Math.round(top)}px`;
|
|
172
173
|
// Once it has its width: only now is a cut value known.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — the notice after an automatic restart: what it says
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// When SynaBun's server crashes after it was up, its supervisor starts it again
|
|
6
|
+
// and leaves the new server a record of the crash (lib/server-crash-record.js,
|
|
7
|
+
// GET /api/system/crash-notice). The owner's pages say so until it is dismissed:
|
|
8
|
+
// that the server stopped unexpectedly and was restarted, when and how it ended,
|
|
9
|
+
// and what the restart ended. This module decides the words; ui-crash-notice.js
|
|
10
|
+
// asks the server and draws them.
|
|
11
|
+
//
|
|
12
|
+
// What the restart ends is what the code does when the server process dies and
|
|
13
|
+
// a new one starts, nothing more:
|
|
14
|
+
// - terminals: the pty host ends with the server and kills its shells
|
|
15
|
+
// (lib/pty-host/host.js on 'disconnect'); a new host has none of them
|
|
16
|
+
// - runs: dispatched worker runs and native loops that were running are marked
|
|
17
|
+
// interrupted at boot (lib/assistant-dispatch.js, lib/native-loop-runtime.js);
|
|
18
|
+
// Quick Timers, schedule timers set for later and the schedule queue live in
|
|
19
|
+
// memory; saved schedules keep firing. The program a run had started is not
|
|
20
|
+
// always ended with the server: on macOS and Linux a CLI whose input is not
|
|
21
|
+
// the server's (`claude -p`, `codex exec`) ends at its next write to the
|
|
22
|
+
// server, and on Windows one started through a shell is not in the server's
|
|
23
|
+
// job: hence "a step already under way may still finish"
|
|
24
|
+
// - replies: a Claude Code, Codex or Assistant turn in progress is gone, the
|
|
25
|
+
// conversation stays and the next message resumes it (an OpenCode serve that
|
|
26
|
+
// outlived the server may be adopted and go on: hence "may have been")
|
|
27
|
+
// - browser: the sessions SynaBun drove live in memory; their profiles stay
|
|
28
|
+
// - public address: cloudflared is not started again, a new start gets a new
|
|
29
|
+
// one. Windows ends it with the server (its job); on macOS and Linux it
|
|
30
|
+
// outlives a crash, and the new server ends it by its record at start
|
|
31
|
+
// (server.js sweepLeftoverTunnels). One it could not end is `tunnel:
|
|
32
|
+
// 'unknown'` in the notice, and the notice says the address may still be open
|
|
33
|
+
//
|
|
34
|
+
// No imports and no DOM: `node --test` loads this file as it is
|
|
35
|
+
// (tests/crash-notice-view.test.mjs). CRASH_NOTICE_COPY is the English of every
|
|
36
|
+
// sentence; `crashNotice.*` in i18n/en.json must equal it and i18n/pt-BR.json
|
|
37
|
+
// must have the same keys.
|
|
38
|
+
|
|
39
|
+
export const CRASH_NOTICE_COPY = Object.freeze({
|
|
40
|
+
title: 'SynaBun\'s server stopped unexpectedly and was restarted automatically.',
|
|
41
|
+
whenToday: 'It happened at {time}.',
|
|
42
|
+
whenEarlier: 'It happened on {date} at {time}.',
|
|
43
|
+
whenManyToday: 'It happened {count} times, the last at {time}.',
|
|
44
|
+
whenManyEarlier: 'It happened {count} times, the last on {date} at {time}.',
|
|
45
|
+
how: 'How it ended: {exit}.',
|
|
46
|
+
lostTitle: 'What the restart ended',
|
|
47
|
+
lostTerminals: 'Open terminals were closed.',
|
|
48
|
+
lostRuns: 'Agent runs and loops that were running were interrupted, though a step already under way may still finish on its own. Timers and queued runs were cleared. Saved schedules keep running.',
|
|
49
|
+
lostReplies: 'Replies being written in the side panels or the Assistant may have been cut off. The conversations are kept: send again what did not finish.',
|
|
50
|
+
lostBrowser: 'Browser sessions SynaBun was driving ended. Their saved sign-ins are kept.',
|
|
51
|
+
lostTunnel: 'The public address, if it was on, is off. Turning it on again gives a new address.',
|
|
52
|
+
lostTunnelUnknown: 'The public address, if it was on, may still be open: SynaBun could not end the cloudflared that served it. Ending that process, or restarting the computer, closes it.',
|
|
53
|
+
openLogs: 'Open logs',
|
|
54
|
+
openLogsFailed: 'The logs folder could not be opened.',
|
|
55
|
+
dismiss: 'Dismiss',
|
|
56
|
+
label: 'Server restarted after a crash',
|
|
57
|
+
exitCode: 'exit code {code}',
|
|
58
|
+
exitText: '{status}, {kind}',
|
|
59
|
+
kindNativeCrash: 'native crash',
|
|
60
|
+
kindOutOfMemory: 'out of memory',
|
|
61
|
+
kindNodeFatal: 'Node.js fatal error',
|
|
62
|
+
kindError: 'stopped by an error',
|
|
63
|
+
kindKilled: 'ended by force',
|
|
64
|
+
kindInterrupted: 'interrupted',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/** The order the notice lists what the restart ended (the last: lostTunnelUnknown for a notice whose `tunnel` is 'unknown'). */
|
|
68
|
+
export const CRASH_NOTICE_LOST = Object.freeze(['lostTerminals', 'lostRuns', 'lostReplies', 'lostBrowser', 'lostTunnel']);
|
|
69
|
+
|
|
70
|
+
/** The word for each kind lib/server-exit.js names; a kind without one shows the status alone. */
|
|
71
|
+
export const CRASH_KIND_KEYS = Object.freeze({
|
|
72
|
+
'native-crash': 'kindNativeCrash',
|
|
73
|
+
'out-of-memory': 'kindOutOfMemory',
|
|
74
|
+
'node-fatal': 'kindNodeFatal',
|
|
75
|
+
error: 'kindError',
|
|
76
|
+
killed: 'kindKilled',
|
|
77
|
+
interrupted: 'kindInterrupted',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/** A sentence of CRASH_NOTICE_COPY in English with its {values} filled; an unknown key is itself. */
|
|
81
|
+
export function fillCrashNoticeCopy(key, params = {}) {
|
|
82
|
+
const text = Object.prototype.hasOwnProperty.call(CRASH_NOTICE_COPY, key) ? CRASH_NOTICE_COPY[key] : key;
|
|
83
|
+
return text.replace(/\{(\w+)\}/g, (whole, name) => (params[name] != null ? String(params[name]) : whole));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ID = /^[0-9a-f]{16}$/;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The notice in an answer of GET /api/system/crash-notice (or a `crash-notice:changed`
|
|
90
|
+
* message: both carry `notice`), checked field by field; null when there is none.
|
|
91
|
+
* `tunnel: 'unknown'` is there only when the server could not end the crashed
|
|
92
|
+
* server's cloudflared.
|
|
93
|
+
*/
|
|
94
|
+
export function readCrashNotice(answer) {
|
|
95
|
+
const notice = answer && typeof answer === 'object' ? answer.notice : null;
|
|
96
|
+
if (!notice || typeof notice !== 'object' || Array.isArray(notice)) return null;
|
|
97
|
+
if (typeof notice.id !== 'string' || !ID.test(notice.id)) return null;
|
|
98
|
+
const at = Number(notice.at);
|
|
99
|
+
if (!Number.isFinite(at) || at <= 0) return null;
|
|
100
|
+
const exit = notice.exit && typeof notice.exit === 'object' && !Array.isArray(notice.exit) ? notice.exit : {};
|
|
101
|
+
return {
|
|
102
|
+
id: notice.id,
|
|
103
|
+
at,
|
|
104
|
+
count: Number.isInteger(notice.count) && notice.count > 0 ? Math.min(notice.count, 999) : 1,
|
|
105
|
+
exit: {
|
|
106
|
+
hex: typeof exit.hex === 'string' && /^0x[0-9a-f]{8}$/i.test(exit.hex) ? `0x${exit.hex.slice(2).toUpperCase()}` : '',
|
|
107
|
+
signal: typeof exit.signal === 'string' && /^SIG[A-Z0-9]{1,10}$/.test(exit.signal) ? exit.signal : '',
|
|
108
|
+
code: Number.isInteger(exit.code) ? exit.code : null,
|
|
109
|
+
kind: typeof exit.kind === 'string' && /^[a-z][a-z-]{0,23}$/.test(exit.kind) ? exit.kind : '',
|
|
110
|
+
},
|
|
111
|
+
...(notice.tunnel === 'unknown' ? { tunnel: 'unknown' } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** How it ended in the reader's words ("0xC0000409, native crash"), or '' when the record names nothing. */
|
|
116
|
+
export function crashExitWords(exit, t = fillCrashNoticeCopy) {
|
|
117
|
+
if (!exit || typeof exit !== 'object') return '';
|
|
118
|
+
const status = exit.hex || exit.signal || (Number.isInteger(exit.code) ? t('exitCode', { code: exit.code }) : '');
|
|
119
|
+
if (!status) return '';
|
|
120
|
+
const key = CRASH_KIND_KEYS[exit.kind];
|
|
121
|
+
return key ? t('exitText', { status, kind: t(key) }) : status;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** When it happened, in the reader's locale: { today, date, time } (the date is for another day). */
|
|
125
|
+
export function crashTimeParts(at, { now = Date.now(), locale = undefined } = {}) {
|
|
126
|
+
const when = new Date(at);
|
|
127
|
+
if (!Number.isFinite(when.getTime())) return null;
|
|
128
|
+
const day = new Date(now);
|
|
129
|
+
const today = when.getFullYear() === day.getFullYear() && when.getMonth() === day.getMonth() && when.getDate() === day.getDate();
|
|
130
|
+
const format = (fn, options, fallback) => { try { return fn.call(when, locale, options); } catch { return fallback; } };
|
|
131
|
+
return {
|
|
132
|
+
today,
|
|
133
|
+
date: format(Date.prototype.toLocaleDateString, { dateStyle: 'medium' }, when.toISOString().slice(0, 10)),
|
|
134
|
+
time: format(Date.prototype.toLocaleTimeString, { hour: '2-digit', minute: '2-digit' }, when.toISOString().slice(11, 16)),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* What the notice shows, or null: { id, label, title, when, how, lostTitle, lost[], actions[] }.
|
|
140
|
+
* `t(key, params)` is a sentence of CRASH_NOTICE_COPY in the page's language; `canOpenLogs`
|
|
141
|
+
* says whether this page can open the logs folder (the desktop app, or a page on this computer).
|
|
142
|
+
*/
|
|
143
|
+
export function crashNoticeView(notice, { t = fillCrashNoticeCopy, now = Date.now(), locale = undefined, canOpenLogs = false } = {}) {
|
|
144
|
+
if (!notice || typeof notice !== 'object' || !notice.id) return null;
|
|
145
|
+
const parts = crashTimeParts(notice.at, { now, locale }) || { today: true, date: '', time: '' };
|
|
146
|
+
const many = notice.count > 1;
|
|
147
|
+
const whenKey = `${many ? 'whenMany' : 'when'}${parts.today ? 'Today' : 'Earlier'}`;
|
|
148
|
+
const exit = crashExitWords(notice.exit, t);
|
|
149
|
+
return {
|
|
150
|
+
id: notice.id,
|
|
151
|
+
label: t('label'),
|
|
152
|
+
title: t('title'),
|
|
153
|
+
when: t(whenKey, { count: notice.count, date: parts.date, time: parts.time }),
|
|
154
|
+
how: exit ? t('how', { exit }) : '',
|
|
155
|
+
lostTitle: t('lostTitle'),
|
|
156
|
+
lost: CRASH_NOTICE_LOST.map((key) => t(key === 'lostTunnel' && notice.tunnel === 'unknown' ? 'lostTunnelUnknown' : key)),
|
|
157
|
+
actions: [
|
|
158
|
+
...(canOpenLogs ? [{ id: 'logs', label: t('openLogs') }] : []),
|
|
159
|
+
{ id: 'dismiss', label: t('dismiss') },
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** True for a page served from this computer's loopback address: the only one the logs folder opens for. */
|
|
165
|
+
export function isLoopbackHost(hostname) {
|
|
166
|
+
const host = String(hostname || '').toLowerCase().replace(/^\[|\]$/g, '');
|
|
167
|
+
return host === 'localhost' || host === '::1' || /^127(?:\.\d{1,3}){3}$/.test(host);
|
|
168
|
+
}
|
|
@@ -76,6 +76,10 @@ export const DESKTOP_COPY = Object.freeze({
|
|
|
76
76
|
metaReadyOff: 'Ready to install',
|
|
77
77
|
metaCountdown: 'Restarting to install it',
|
|
78
78
|
metaPostponed: 'Postponed: installs on quit or later',
|
|
79
|
+
// The button at the top right while a downloaded update waits for a restart, as Discord's green one.
|
|
80
|
+
restartButton: 'Restart to update',
|
|
81
|
+
restartTooltip: 'SynaBun v{latest} is ready: restart to update',
|
|
82
|
+
restartSoon: 'Restarting soon',
|
|
79
83
|
}),
|
|
80
84
|
// What an update waits for (the app's server names it, GET /api/system/activity).
|
|
81
85
|
busy: Object.freeze({
|
|
@@ -422,6 +426,22 @@ export function desktopUpdateTooltip(state, { t = fillDesktopCopy } = {}) {
|
|
|
422
426
|
return t('update.tooltip', params);
|
|
423
427
|
}
|
|
424
428
|
|
|
429
|
+
/**
|
|
430
|
+
* The top right button while a downloaded update waits for a restart into it, or null: { label, tooltip, asked }.
|
|
431
|
+
* Only an update this copy installs by restarting (not one to move to Applications or to install by hand). After
|
|
432
|
+
* Restart to update, while something still runs, it says that the restart is coming (`asked`).
|
|
433
|
+
*/
|
|
434
|
+
export function desktopRestartButton(state, { t = fillDesktopCopy } = {}) {
|
|
435
|
+
const s = readDesktopUpdateState(state);
|
|
436
|
+
if (desktopUpdateStep(s) !== 'downloaded' || s.install !== 'restart' || s.manualFile) return null;
|
|
437
|
+
const asked = s.auto?.now === true;
|
|
438
|
+
return {
|
|
439
|
+
label: t(asked ? 'update.restartSoon' : 'update.restartButton'),
|
|
440
|
+
tooltip: t('update.restartTooltip', { latest: s.latest || '', current: s.current }),
|
|
441
|
+
asked,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
425
445
|
/**
|
|
426
446
|
* The notice at the bottom right, or null:
|
|
427
447
|
* countdown SynaBun is idle and restarts into the update: the seconds left, and Later
|
|
@@ -80,9 +80,9 @@ export function matchesExpectedMarkdown(content, expected) {
|
|
|
80
80
|
return String(content).replace(/\r\n/g, '\n') === expected.replace(/\r\n/g, '\n');
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
/** Whether a path is the user's OpenCode configuration file. */
|
|
83
|
+
/** Whether a path is the user's OpenCode configuration file (OpenCode 2's opencode.json or opencode.jsonc). */
|
|
84
84
|
export function isOpencodeConfigPath(path) {
|
|
85
|
-
return String(path || '').replace(/\\/g, '/')
|
|
85
|
+
return /\/\.config\/opencode\/opencode\.jsonc?$/.test(String(path || '').replace(/\\/g, '/'));
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const OUTSIDE_ROOTS = 'Path outside registered project roots';
|