synabun 2.1.0 → 2.2.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 +121 -0
- package/README.md +1 -1
- package/docs/whatsapp.md +11 -11
- package/lib/client-config-repair.js +13 -9
- package/lib/external-tools.js +6 -5
- package/lib/update-snapshot.js +77 -3
- package/lib/virustotal.mjs +12 -1
- package/mcp-server/dist/index.js +1 -1
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
- package/mcp-server/dist/services/budget-judgments.js +5 -3
- package/mcp-server/dist/services/budget-judgments.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +2 -2
- package/mcp-server/dist/services/identity.js +2 -2
- package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-sessions.js +28 -2
- package/mcp-server/dist/services/memory-sessions.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +2 -1
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +2 -1
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.js +1 -1
- package/mcp-server/dist/services/tool-catalog.js.map +1 -1
- package/mcp-server/dist/services/tool-list.d.ts +1 -1
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
- package/mcp-server/dist/services/tool-runtime.js +1 -1
- package/mcp-server/dist/services/tool-runtime.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +1 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +8 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +10 -9
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +6 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +18 -13
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +2 -2
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +9 -6
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/skill-tools.js +11 -8
- package/mcp-server/dist/tools/skill-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/index.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +5 -3
- package/mcp-server/src/services/identity.ts +2 -2
- package/mcp-server/src/services/memory-sessions.ts +24 -3
- package/mcp-server/src/services/neural-interface.ts +2 -1
- package/mcp-server/src/services/server-instructions.ts +2 -1
- package/mcp-server/src/services/tool-catalog.ts +1 -1
- package/mcp-server/src/services/tool-list.ts +1 -1
- package/mcp-server/src/services/tool-runtime.ts +2 -2
- package/mcp-server/src/services/typesafe-config.ts +1 -1
- package/mcp-server/src/tools/agents.ts +10 -9
- package/mcp-server/src/tools/choice.ts +17 -12
- package/mcp-server/src/tools/git-tools.ts +2 -2
- package/mcp-server/src/tools/morelogin.ts +6 -4
- package/mcp-server/src/tools/skill-tools.ts +11 -8
- package/neural-interface/i18n/en.json +501 -39
- package/neural-interface/i18n/pt-BR.json +501 -39
- package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
- package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
- package/neural-interface/lib/antigravity/acp-connection.js +299 -0
- package/neural-interface/lib/antigravity/bypass.js +100 -0
- package/neural-interface/lib/antigravity/catalog.js +273 -0
- package/neural-interface/lib/antigravity/home.js +260 -0
- package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
- package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
- package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
- package/neural-interface/lib/antigravity/policy.js +74 -0
- package/neural-interface/lib/antigravity/tool-call.js +178 -0
- package/neural-interface/lib/antigravity/transcripts.js +252 -0
- package/neural-interface/lib/antigravity/usage.js +43 -0
- package/neural-interface/lib/assistant-api.js +1 -0
- package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
- package/neural-interface/lib/assistant-budget-planner.js +7 -3
- package/neural-interface/lib/assistant-budget-split.js +42 -15
- package/neural-interface/lib/assistant-catalog.js +76 -9
- package/neural-interface/lib/assistant-config.js +6 -3
- package/neural-interface/lib/assistant-dispatch.js +207 -49
- package/neural-interface/lib/assistant-envelope.js +153 -1
- package/neural-interface/lib/assistant-evidence.js +80 -0
- package/neural-interface/lib/assistant-hidden-models.js +7 -5
- package/neural-interface/lib/assistant-persona.js +16 -11
- package/neural-interface/lib/assistant-plan-permissions.js +15 -0
- package/neural-interface/lib/assistant-router.js +237 -35
- package/neural-interface/lib/assistant-runtime.js +158 -12
- package/neural-interface/lib/assistant-task-prompt.js +10 -8
- package/neural-interface/lib/assistant-tools.js +2 -2
- package/neural-interface/lib/assistant-usage.js +29 -0
- package/neural-interface/lib/backup-service.js +6 -1
- package/neural-interface/lib/browser-guard-cdp.js +5 -4
- package/neural-interface/lib/browser-network-guard.js +274 -82
- package/neural-interface/lib/browser-tool-policy.js +12 -7
- package/neural-interface/lib/claude-agent-bridge.js +7 -0
- package/neural-interface/lib/claude-history-cache.js +210 -0
- package/neural-interface/lib/claude-model-catalog.js +5 -1
- package/neural-interface/lib/cli-install-api.js +1 -1
- package/neural-interface/lib/cli-install.js +160 -31
- package/neural-interface/lib/cli-resolver.js +9 -2
- package/neural-interface/lib/cli-update-planner.js +25 -15
- package/neural-interface/lib/codex-rollout-reader.js +52 -0
- package/neural-interface/lib/codex-worker-app-server.js +25 -6
- package/neural-interface/lib/editor-git-api.js +3 -20
- package/neural-interface/lib/exec-loop-command.js +9 -2
- package/neural-interface/lib/file-explorer-api.js +74 -40
- package/neural-interface/lib/file-explorer-watch.js +9 -3
- package/neural-interface/lib/git-bash/install.js +52 -6
- package/neural-interface/lib/git-bash/manager.js +59 -17
- package/neural-interface/lib/git-bash/pins.js +12 -0
- package/neural-interface/lib/git-read.js +2 -2
- package/neural-interface/lib/git-status-runner.js +390 -0
- package/neural-interface/lib/git-status-watchdog.js +40 -0
- package/neural-interface/lib/greeting/budgets.js +1 -0
- package/neural-interface/lib/greeting/engine.js +2 -2
- package/neural-interface/lib/greeting/sources.js +10 -12
- package/neural-interface/lib/greeting/writer.js +1 -1
- package/neural-interface/lib/mcp-client-config.js +37 -6
- package/neural-interface/lib/mcp-external-servers.js +13 -4
- package/neural-interface/lib/mcp-installer.js +2 -1
- package/neural-interface/lib/memory-session-capture.js +17 -3
- package/neural-interface/lib/morelogin.js +147 -25
- package/neural-interface/lib/native-loop-providers.js +420 -21
- package/neural-interface/lib/native-loop-runtime.js +23 -2
- package/neural-interface/lib/opencode-native-boundary.js +11 -2
- package/neural-interface/lib/owner-authority.js +1 -1
- package/neural-interface/lib/path-confine.js +12 -9
- package/neural-interface/lib/peer-identity.js +32 -2
- package/neural-interface/lib/pty-host/core.js +28 -1
- package/neural-interface/lib/pty-host/lifetime.js +12 -0
- package/neural-interface/lib/pty-host/manager.js +12 -1
- package/neural-interface/lib/remote-policy.js +1 -1
- package/neural-interface/lib/request-gate.js +3 -0
- package/neural-interface/lib/rulesets/api.js +6 -5
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +89 -10
- package/neural-interface/lib/rulesets/render.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +64 -1
- package/neural-interface/lib/skills/catalog.js +5 -1
- package/neural-interface/lib/skills/creation-brief.js +1 -1
- package/neural-interface/lib/skills/creation-sources.js +19 -1
- package/neural-interface/lib/skills/deploy.js +9 -4
- package/neural-interface/lib/skills/draft-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-confinement.js +3 -1
- package/neural-interface/lib/skills/eval-trace.js +16 -0
- package/neural-interface/lib/skills/export.js +5 -2
- package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
- package/neural-interface/lib/skills/ownership.js +1 -1
- package/neural-interface/lib/skills/roots.js +10 -3
- package/neural-interface/lib/skills/runtime-checks.js +6 -3
- package/neural-interface/lib/skills/service.js +15 -7
- package/neural-interface/lib/skills/telemetry.js +6 -0
- package/neural-interface/lib/synabun-version.js +1 -1
- package/neural-interface/lib/system-activity.js +1 -1
- package/neural-interface/lib/whatsapp/brain.js +7 -3
- package/neural-interface/lib/whatsapp/cards.js +22 -9
- package/neural-interface/lib/whatsapp/config.js +1 -1
- package/neural-interface/lib/worker-confinement.js +77 -1
- package/neural-interface/lib/worker-question.js +74 -0
- package/neural-interface/lib/worker-resume.js +13 -4
- package/neural-interface/lib/worker-sandbox.js +46 -16
- package/neural-interface/package-lock.json +18 -2
- package/neural-interface/package.json +2 -1
- package/neural-interface/public/onboarding.html +1 -1
- package/neural-interface/public/shared/agent-runtime-options.js +89 -11
- package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
- package/neural-interface/public/shared/agy/agy-client.js +266 -0
- package/neural-interface/public/shared/agy/agy-commands.js +120 -0
- package/neural-interface/public/shared/agy/agy-copy.js +326 -0
- package/neural-interface/public/shared/agy/agy-diff.js +161 -0
- package/neural-interface/public/shared/agy/agy-events.js +469 -0
- package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
- package/neural-interface/public/shared/agy/agy-options.js +232 -0
- package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
- package/neural-interface/public/shared/agy/agy-render.js +392 -0
- package/neural-interface/public/shared/agy/agy-setup.js +149 -0
- package/neural-interface/public/shared/agy/agy-styles.js +604 -0
- package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
- package/neural-interface/public/shared/api.js +4 -4
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
- package/neural-interface/public/shared/assistant/asst-api.js +1 -1
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
- package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
- package/neural-interface/public/shared/assistant/asst-control.js +48 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
- package/neural-interface/public/shared/assistant/asst-panel.js +45 -8
- package/neural-interface/public/shared/assistant/asst-render.js +51 -12
- package/neural-interface/public/shared/assistant/asst-route.js +237 -57
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
- package/neural-interface/public/shared/assistant/asst-scenes.js +54 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
- package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
- package/neural-interface/public/shared/assistant/asst-state.js +76 -9
- package/neural-interface/public/shared/assistant/asst-styles.js +34 -0
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
- package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
- package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
- package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
- package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
- package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
- package/neural-interface/public/shared/cli-install-controller.js +218 -0
- package/neural-interface/public/shared/cli-install-view.js +7 -4
- package/neural-interface/public/shared/cli-status.js +9 -8
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/context-menu-model.js +1 -0
- package/neural-interface/public/shared/cp/cp-restore.js +162 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
- package/neural-interface/public/shared/desktop-bridge.js +17 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
- package/neural-interface/public/shared/desktop-titlebar.js +13 -4
- package/neural-interface/public/shared/desktop-update-view.js +1 -0
- package/neural-interface/public/shared/explorer/fx-api.js +4 -4
- package/neural-interface/public/shared/explorer/fx-links.js +3 -3
- package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +48 -16
- package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
- package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
- package/neural-interface/public/shared/html-shell.js +21 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +27 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
- package/neural-interface/public/shared/provider-icons.js +4 -2
- package/neural-interface/public/shared/settings/settings-rules.js +2 -2
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
- package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
- package/neural-interface/public/shared/showcase-privacy.js +6 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
- package/neural-interface/public/shared/skills/ss-creation.js +6 -3
- package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
- package/neural-interface/public/shared/skills/ss-lint.js +9 -1
- package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
- package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
- package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
- package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
- package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
- package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
- package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
- package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
- package/neural-interface/public/shared/state.js +1 -1
- package/neural-interface/public/shared/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styles.css +354 -34
- package/neural-interface/public/shared/text-cache.js +29 -0
- package/neural-interface/public/shared/tool-list-picker.js +2 -2
- package/neural-interface/public/shared/tool-list-view.js +2 -2
- package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
- package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
- package/neural-interface/public/shared/ui-assistant.js +2 -0
- package/neural-interface/public/shared/ui-automation-studio.js +20 -12
- package/neural-interface/public/shared/ui-claude-panel.js +299 -77
- package/neural-interface/public/shared/ui-cli-banner.js +23 -167
- package/neural-interface/public/shared/ui-file-explorer.js +48 -6
- package/neural-interface/public/shared/ui-file-links.js +2 -2
- package/neural-interface/public/shared/ui-games.js +2 -2
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +8 -2
- package/neural-interface/public/shared/ui-link.js +3 -3
- package/neural-interface/public/shared/ui-menubar.js +2 -2
- package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
- package/neural-interface/public/shared/ui-navbar.js +45 -0
- package/neural-interface/public/shared/ui-notifications.js +1 -0
- package/neural-interface/public/shared/ui-resume.js +9 -7
- package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
- package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
- package/neural-interface/public/shared/ui-sessions.js +22 -12
- package/neural-interface/public/shared/ui-settings.js +90 -65
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
- package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
- package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
- package/neural-interface/public/shared/ui-terminal.js +146 -10
- package/neural-interface/public/shared/ui-tool-updates.js +3 -3
- package/neural-interface/public/shared/ui-tooltip.js +71 -46
- package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
- package/neural-interface/public/shared/ui-whiteboard.js +1 -1
- package/neural-interface/public/shared/ui-worker-end.js +1 -1
- package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
- package/neural-interface/public/shared/worker-questions.js +34 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- package/neural-interface/server.js +644 -426
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
- package/neural-interface/templates/rulesets/manifest.json +1 -1
- package/npm-shrinkwrap.json +18 -2
- package/package.json +2 -1
- package/skills/skill-forge/SKILL.md +4 -4
- package/skills/synabun/SKILL.md +1 -1
- package/skills/synabun/modules/schedule.md +3 -3
- /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
// (OpenCode); the panel element gets `<prefix>-cli-blocked` while sending is
|
|
13
13
|
// off.
|
|
14
14
|
//
|
|
15
|
-
// What it says is cli-install-view.js (DOM-free, tested under node); the
|
|
16
|
-
//
|
|
15
|
+
// What it says is cli-install-view.js (DOM-free, tested under node); the state
|
|
16
|
+
// (the answer, the install, Re-check, Add to PATH) is cli-install-controller.js,
|
|
17
|
+
// which the Antigravity panel's setup card uses too. A panel mounts one banner:
|
|
17
18
|
//
|
|
18
19
|
// const cli = mountCliBanner({ toolKey: 'codex', prefix: 'cxp',
|
|
19
20
|
// panel: () => panelEl, anchor: () => messagesContainer,
|
|
@@ -23,16 +24,12 @@
|
|
|
23
24
|
// cli.nudge(); a send that was held back
|
|
24
25
|
// cli.flagFailure(text); a session could not start the CLI
|
|
25
26
|
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
27
|
+
import { cliInstallText, setCliInstallTranslator } from './cli-install-view.js';
|
|
28
|
+
import { createCliInstallController, DONE_NOTICE_MS } from './cli-install-controller.js';
|
|
28
29
|
import { t } from './i18n.js';
|
|
29
30
|
|
|
30
31
|
setCliInstallTranslator((key, params) => t(`cliInstall.${key}`, params));
|
|
31
32
|
|
|
32
|
-
/** How long the note about where an install put the CLI stays, when it has one. */
|
|
33
|
-
const DONE_NOTICE_MS = 12000;
|
|
34
|
-
const NUDGE_MS = 700;
|
|
35
|
-
|
|
36
33
|
/**
|
|
37
34
|
* @param {object} o
|
|
38
35
|
* @param {'claude-code' | 'codex' | 'opencode'} o.toolKey
|
|
@@ -44,29 +41,10 @@ const NUDGE_MS = 700;
|
|
|
44
41
|
*/
|
|
45
42
|
export function mountCliBanner({ toolKey, prefix, panel, anchor, onBlockedChange = () => {}, doneNoticeMs = DONE_NOTICE_MS } = {}) {
|
|
46
43
|
const p = String(prefix || 'cp');
|
|
47
|
-
let info = null; // this CLI in the latest answer of /api/system/tool-versions (null: none yet)
|
|
48
|
-
let forced = null; // { reason }: a session could not start it, and the server was not asked again yet
|
|
49
|
-
let job = null; // the install under way or just ended (cli-install-view.js reduceInstallJob)
|
|
50
|
-
let installing = false;
|
|
51
|
-
let addingPath = false;
|
|
52
|
-
let pathError = '';
|
|
53
|
-
let looked = false; // asked whether the server is already installing it since it went missing
|
|
54
44
|
let el = null;
|
|
55
|
-
let doneTimer = null;
|
|
56
|
-
let nudgeTimer = null;
|
|
57
45
|
let lastBlocked = null;
|
|
58
46
|
let destroyed = false;
|
|
59
|
-
|
|
60
|
-
const view = () => cliBannerView({
|
|
61
|
-
toolKey,
|
|
62
|
-
label: getCliLabel(toolKey),
|
|
63
|
-
info,
|
|
64
|
-
forced,
|
|
65
|
-
job,
|
|
66
|
-
addingPath, pathError,
|
|
67
|
-
guideUrl: getCliDocUrl(toolKey),
|
|
68
|
-
installCommand: getCliInstallCommand(toolKey),
|
|
69
|
-
}, { t: cliInstallText });
|
|
47
|
+
let controller = null;
|
|
70
48
|
|
|
71
49
|
const cls = (name) => `${p}-cli-banner-${name}`;
|
|
72
50
|
|
|
@@ -93,10 +71,10 @@ export function mountCliBanner({ toolKey, prefix, panel, anchor, onBlockedChange
|
|
|
93
71
|
</div>
|
|
94
72
|
</div>
|
|
95
73
|
`;
|
|
96
|
-
root.querySelector(`.${cls('install')}`).addEventListener('click', () => {
|
|
97
|
-
root.querySelector(`.${cls('recheck')}`).addEventListener('click', () => { recheck(); });
|
|
98
|
-
root.querySelector(`.${cls('path')}`).addEventListener('click', () => { addPath(); });
|
|
99
|
-
root.querySelector(`.${cls('dismiss')}`).addEventListener('click', () => { clearDone(); });
|
|
74
|
+
root.querySelector(`.${cls('install')}`).addEventListener('click', () => { controller?.install(); });
|
|
75
|
+
root.querySelector(`.${cls('recheck')}`).addEventListener('click', () => { controller?.recheck(); });
|
|
76
|
+
root.querySelector(`.${cls('path')}`).addEventListener('click', () => { controller?.addPath(); });
|
|
77
|
+
root.querySelector(`.${cls('dismiss')}`).addEventListener('click', () => { controller?.clearDone(); });
|
|
100
78
|
root.querySelector(`.${cls('copy')}`).addEventListener('click', async (event) => {
|
|
101
79
|
const button = event.currentTarget;
|
|
102
80
|
const command = root.querySelector(`.${cls('command')} code`)?.textContent || '';
|
|
@@ -117,7 +95,7 @@ export function mountCliBanner({ toolKey, prefix, panel, anchor, onBlockedChange
|
|
|
117
95
|
}
|
|
118
96
|
|
|
119
97
|
function paint(v) {
|
|
120
|
-
el.className = `${p}-cli-banner ${p}-cli-banner--${v.kind}${
|
|
98
|
+
el.className = `${p}-cli-banner ${p}-cli-banner--${v.kind}${v.nudging ? ` ${p}-cli-banner--nudge` : ''}`;
|
|
121
99
|
el.setAttribute('role', v.live === 'assertive' ? 'alert' : 'status');
|
|
122
100
|
el.setAttribute('aria-live', v.live);
|
|
123
101
|
el.querySelector(`.${cls('icon')}`).textContent = v.icon;
|
|
@@ -180,7 +158,8 @@ export function mountCliBanner({ toolKey, prefix, panel, anchor, onBlockedChange
|
|
|
180
158
|
else link.removeAttribute('href');
|
|
181
159
|
const recheckButton = el.querySelector(`.${cls('recheck')}`);
|
|
182
160
|
recheckButton.hidden = !v.recheck;
|
|
183
|
-
|
|
161
|
+
recheckButton.disabled = !!v.checking;
|
|
162
|
+
recheckButton.textContent = cliInstallText(v.checking ? 'checking' : 'recheck');
|
|
184
163
|
const pathButton = el.querySelector(`.${cls('path')}`);
|
|
185
164
|
pathButton.hidden = !v.addPath;
|
|
186
165
|
pathButton.textContent = v.addPath?.label || '';
|
|
@@ -191,8 +170,8 @@ export function mountCliBanner({ toolKey, prefix, panel, anchor, onBlockedChange
|
|
|
191
170
|
}
|
|
192
171
|
|
|
193
172
|
function render() {
|
|
194
|
-
if (destroyed) return;
|
|
195
|
-
const v = view();
|
|
173
|
+
if (destroyed || !controller) return;
|
|
174
|
+
const v = controller.view();
|
|
196
175
|
const panelEl = panel?.();
|
|
197
176
|
if (panelEl) panelEl.classList.toggle(`${p}-cli-blocked`, v.blocked);
|
|
198
177
|
if (v.show && place()) paint(v);
|
|
@@ -203,151 +182,28 @@ export function mountCliBanner({ toolKey, prefix, panel, anchor, onBlockedChange
|
|
|
203
182
|
}
|
|
204
183
|
}
|
|
205
184
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
doneTimer = null;
|
|
209
|
-
if (job?.phase === 'done') job = null;
|
|
210
|
-
render();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function apply(event) {
|
|
214
|
-
if (!job && event?.type !== 'start') job = reduceInstallJob(null, { type: 'start' });
|
|
215
|
-
job = reduceInstallJob(job, event);
|
|
216
|
-
if (event?.type === 'done' && event.ok && event.status && typeof event.status === 'object') {
|
|
217
|
-
info = event.status;
|
|
218
|
-
forced = null;
|
|
219
|
-
}
|
|
220
|
-
render();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function settle(final) {
|
|
224
|
-
if (job?.phase === 'running' && final) job = reduceInstallJob(job, final);
|
|
225
|
-
if (job?.phase === 'done') {
|
|
226
|
-
clearTimeout(doneTimer);
|
|
227
|
-
const where = job.path;
|
|
228
|
-
if (where && (where.onPath === false || where.added === true)) doneTimer = setTimeout(clearDone, doneNoticeMs);
|
|
229
|
-
else job = null;
|
|
230
|
-
}
|
|
231
|
-
render();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function startInstall() {
|
|
235
|
-
if (installing || !INSTALLABLE_CLIS.includes(toolKey)) return;
|
|
236
|
-
installing = true;
|
|
237
|
-
clearTimeout(doneTimer);
|
|
238
|
-
job = reduceInstallJob(null, { type: 'start' });
|
|
239
|
-
render();
|
|
240
|
-
let final = null;
|
|
241
|
-
try {
|
|
242
|
-
final = await installCli(toolKey, { onEvent: apply });
|
|
243
|
-
} catch (error) {
|
|
244
|
-
final = { type: 'done', ok: false, code: 'internal', error: error?.message || null };
|
|
245
|
-
} finally {
|
|
246
|
-
installing = false;
|
|
247
|
-
settle(final);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async function addPath() {
|
|
252
|
-
if (addingPath) return;
|
|
253
|
-
addingPath = true; pathError = ''; render();
|
|
254
|
-
try {
|
|
255
|
-
const result = await addCliToPath(toolKey);
|
|
256
|
-
info = { ...info, pathStatus: { dir: result.dir, onPath: true, canAdd: false } };
|
|
257
|
-
job = reduceInstallJob(null, { type: 'done', ok: true, status: info, path: { ...result, added: true } });
|
|
258
|
-
settle();
|
|
259
|
-
} catch (error) { pathError = error.message; }
|
|
260
|
-
finally { addingPath = false; render(); }
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// An install the server is already running (another window started it, or this page was reloaded during
|
|
264
|
-
// one): followed here as if this banner had started it.
|
|
265
|
-
async function followRunning() {
|
|
266
|
-
if (installing) return;
|
|
267
|
-
const jobs = await getCliInstallJobs();
|
|
268
|
-
if (destroyed || installing || jobs?.[toolKey]?.state !== 'running') return;
|
|
269
|
-
installing = true;
|
|
270
|
-
job = reduceInstallJob(null, { type: 'start' });
|
|
271
|
-
render();
|
|
272
|
-
let final = null;
|
|
273
|
-
try {
|
|
274
|
-
final = await followCliInstall(toolKey, { onEvent: apply });
|
|
275
|
-
} catch (error) {
|
|
276
|
-
final = { type: 'done', ok: false, code: 'connection', error: error?.message || null };
|
|
277
|
-
} finally {
|
|
278
|
-
installing = false;
|
|
279
|
-
settle(final);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async function recheck() {
|
|
284
|
-
const button = el?.querySelector(`.${cls('recheck')}`);
|
|
285
|
-
if (button) { button.disabled = true; button.textContent = cliInstallText('checking'); }
|
|
286
|
-
// Asking again starts over: an earlier failure is not repeated over a fresh answer.
|
|
287
|
-
if (job?.phase === 'failed') job = null;
|
|
288
|
-
try {
|
|
289
|
-
const next = await recheckCliStatus(toolKey);
|
|
290
|
-
info = next || info;
|
|
291
|
-
if (cliStateOf(info) === 'installed' || cliStateOf(info) === 'broken') forced = null;
|
|
292
|
-
} catch { /* the banner stays as it is */ }
|
|
293
|
-
finally {
|
|
294
|
-
if (button?.isConnected) { button.disabled = false; button.textContent = cliInstallText('recheck'); }
|
|
295
|
-
render();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const unsubscribe = subscribeCliStatus(toolKey, (next) => {
|
|
300
|
-
info = next || null;
|
|
301
|
-
const state = cliStateOf(info);
|
|
302
|
-
if (state === 'installed') {
|
|
303
|
-
forced = null;
|
|
304
|
-
if (job?.phase === 'failed') job = null;
|
|
305
|
-
}
|
|
306
|
-
if (state === 'missing') {
|
|
307
|
-
if (!looked) { looked = true; followRunning().catch(() => {}); }
|
|
308
|
-
} else {
|
|
309
|
-
looked = false;
|
|
310
|
-
}
|
|
311
|
-
render();
|
|
312
|
-
});
|
|
185
|
+
controller = createCliInstallController({ toolKey, doneNoticeMs, onChange: () => render() });
|
|
186
|
+
render();
|
|
313
187
|
|
|
314
188
|
return {
|
|
315
189
|
/** Draw again (the panel was shown, or rebuilt). */
|
|
316
190
|
refresh: render,
|
|
317
191
|
/** Sending is held back: the CLI is missing, broken, failed to install, or being installed. */
|
|
318
|
-
blocked: () =>
|
|
192
|
+
blocked: () => controller.blocked(),
|
|
319
193
|
/** What the banner shows now (cli-install-view.js cliBannerView). */
|
|
320
|
-
view,
|
|
194
|
+
view: () => controller.view(),
|
|
321
195
|
/** A send was held back: draw attention to the banner for a moment. */
|
|
322
|
-
nudge()
|
|
323
|
-
if (!view().show) return;
|
|
324
|
-
clearTimeout(nudgeTimer);
|
|
325
|
-
nudgeTimer = setTimeout(() => { nudgeTimer = null; render(); }, NUDGE_MS);
|
|
326
|
-
render();
|
|
327
|
-
},
|
|
196
|
+
nudge: () => controller.nudge(),
|
|
328
197
|
/**
|
|
329
198
|
* A session could not start the CLI ("Claude CLI not found", a spawn error): show "failed to start" with its
|
|
330
199
|
* words at once, then ask the server again; its answer decides what stays.
|
|
331
200
|
*/
|
|
332
|
-
flagFailure(reason = null)
|
|
333
|
-
forced = { reason: reason ? String(reason).slice(0, 300) : null };
|
|
334
|
-
render();
|
|
335
|
-
recheckCliStatus(toolKey)
|
|
336
|
-
.then((next) => {
|
|
337
|
-
info = next || info;
|
|
338
|
-
const state = cliStateOf(info);
|
|
339
|
-
if (state === 'installed' || state === 'broken') forced = null;
|
|
340
|
-
render();
|
|
341
|
-
})
|
|
342
|
-
.catch(() => {});
|
|
343
|
-
},
|
|
201
|
+
flagFailure: (reason = null) => controller.flagFailure(reason),
|
|
344
202
|
/** Install from the banner's own button (for a panel's own entry point). */
|
|
345
|
-
install:
|
|
203
|
+
install: () => controller.install(),
|
|
346
204
|
destroy() {
|
|
347
205
|
destroyed = true;
|
|
348
|
-
|
|
349
|
-
clearTimeout(doneTimer);
|
|
350
|
-
clearTimeout(nudgeTimer);
|
|
206
|
+
controller.destroy();
|
|
351
207
|
el?.remove();
|
|
352
208
|
el = null;
|
|
353
209
|
},
|
|
@@ -18,6 +18,7 @@ import { registerAction } from './ui-keybinds.js';
|
|
|
18
18
|
import { storage } from './storage.js';
|
|
19
19
|
import { sendToPanel as sendToClaudePanel } from './ui-claude-panel.js';
|
|
20
20
|
import { attachPathToOpencode } from './ui-opencode-panel-v2.js';
|
|
21
|
+
import { attachPathToAntigravity, isAntigravityPanelOpen, toggleAntigravityPanel } from './ui-antigravity-panel.js';
|
|
21
22
|
import { isCodexPanelOpen, toggleCodexPanel } from './cdx/cdx-panel.js';
|
|
22
23
|
import { addPathChip as addCodexPathChip } from './cdx/cdx-tabs.js';
|
|
23
24
|
import { on, emit, state } from './state.js';
|
|
@@ -35,6 +36,7 @@ const SEND = {
|
|
|
35
36
|
claude: (path) => sendToClaudePanel(path, { asFile: true }),
|
|
36
37
|
codex: async (path) => { if (!isCodexPanelOpen()) await toggleCodexPanel(); addCodexPathChip(path); },
|
|
37
38
|
opencode: (path) => attachPathToOpencode(path),
|
|
39
|
+
antigravity: async (path) => { if (!isAntigravityPanelOpen()) await toggleAntigravityPanel(); return attachPathToAntigravity(path); },
|
|
38
40
|
assistant: (path) => attachToAssistant('assistant', path),
|
|
39
41
|
};
|
|
40
42
|
function attachToAssistant(target, filePath) {
|
|
@@ -289,6 +291,13 @@ async function showTree() {
|
|
|
289
291
|
}
|
|
290
292
|
}
|
|
291
293
|
|
|
294
|
+
// What each scan state says, in words. The header never shows them while a branch is on screen (that would
|
|
295
|
+
// move the row): there they are a dot on the branch button, its tooltip and label, and a line of the git popover.
|
|
296
|
+
const SCAN_STATE_TEXT = { scanning: 'fileExplorer.branch.scanning', 'timed-out': 'fileExplorer.branch.timedOut', failed: 'fileExplorer.branch.scanFailed' };
|
|
297
|
+
function scanStateText(git) {
|
|
298
|
+
return git && SCAN_STATE_TEXT[git.scanState] ? tr(SCAN_STATE_TEXT[git.scanState]) : '';
|
|
299
|
+
}
|
|
300
|
+
|
|
292
301
|
function updateBranchLabel(git) {
|
|
293
302
|
_git = git;
|
|
294
303
|
const btn = $('fe-branch-btn');
|
|
@@ -296,15 +305,33 @@ function updateBranchLabel(git) {
|
|
|
296
305
|
const badge = $('fe-branch-btn-badge');
|
|
297
306
|
if (!btn) return;
|
|
298
307
|
|
|
299
|
-
|
|
308
|
+
const state = SCAN_STATE_TEXT[git.scanState] ? git.scanState : '';
|
|
309
|
+
const message = scanStateText(git);
|
|
310
|
+
const hasBranch = !!(git.isGit && git.branch);
|
|
311
|
+
// Nothing on screen yet (the project's first scan): the state, in one truncated line, where the branch will be.
|
|
312
|
+
const placeholder = !hasBranch && !git.shown && !!message;
|
|
313
|
+
let dot = btn.querySelector('.fe-branch-btn-state');
|
|
314
|
+
if (!dot) { dot = document.createElement('span'); dot.className = 'fe-branch-btn-state'; dot.setAttribute('aria-hidden', 'true'); btn.appendChild(dot); }
|
|
315
|
+
if (state) btn.dataset.scanState = state; else delete btn.dataset.scanState;
|
|
316
|
+
btn.setAttribute('aria-busy', String(state === 'scanning'));
|
|
317
|
+
// Read out, never drawn: only what the owner is waiting for (a first scan, a Refresh), or how one ended.
|
|
318
|
+
const live = $('fe-git-scan-live');
|
|
319
|
+
// A background scan clears it (that is not read out) rather than leave an old sentence there.
|
|
320
|
+
if (live) { const said = git.loud ? message : ''; if (live.textContent !== said) live.textContent = said; }
|
|
321
|
+
|
|
322
|
+
if (hasBranch || placeholder) {
|
|
300
323
|
btn.style.display = '';
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
324
|
+
btn.classList.toggle('fe-branch-btn--placeholder', placeholder);
|
|
325
|
+
if (nameEl) nameEl.textContent = hasBranch ? git.branch : message;
|
|
326
|
+
if (badge) badge.textContent = hasBranch && git.count > 0 ? git.count : '';
|
|
327
|
+
const label = hasBranch ? tr('fileExplorer.branch.label', { branch: git.branch, count: git.count }) : '';
|
|
328
|
+
const full = label && message ? tr('fileExplorer.branch.withState', { label, state: message }) : label || message;
|
|
329
|
+
btn.setAttribute('aria-label', full);
|
|
330
|
+
btn.dataset.tooltip = full;
|
|
305
331
|
if (_gitPopoverOpen) renderGitSummary();
|
|
306
332
|
} else {
|
|
307
333
|
btn.style.display = 'none';
|
|
334
|
+
btn.classList.remove('fe-branch-btn--placeholder');
|
|
308
335
|
closeGitPopover();
|
|
309
336
|
}
|
|
310
337
|
}
|
|
@@ -358,7 +385,8 @@ function openGitPopover() {
|
|
|
358
385
|
requestAnimationFrame(() => pop.classList.add('visible'));
|
|
359
386
|
loadGitBranches();
|
|
360
387
|
renderGitSummary();
|
|
361
|
-
|
|
388
|
+
// Fresh counts for the popover, without a word in the header: nobody pressed Refresh.
|
|
389
|
+
_fx?.refresh({ quiet: true });
|
|
362
390
|
}
|
|
363
391
|
|
|
364
392
|
function closeGitPopover() {
|
|
@@ -379,6 +407,7 @@ function toggleGitPopover() {
|
|
|
379
407
|
|
|
380
408
|
// What changed, in words and counts: from the git state the tree already holds.
|
|
381
409
|
function renderGitSummary() {
|
|
410
|
+
renderGitScanState();
|
|
382
411
|
const summary = $('fe-git-summary');
|
|
383
412
|
if (!summary) return;
|
|
384
413
|
const changes = _fx?.state.git.changed() || [];
|
|
@@ -405,6 +434,18 @@ function renderGitSummary() {
|
|
|
405
434
|
}
|
|
406
435
|
}
|
|
407
436
|
|
|
437
|
+
// How the last scan went, in the popover (the header has no room for words): a timeout or a failure always,
|
|
438
|
+
// a scan under way only when the owner is waiting for it (the first one, a Refresh).
|
|
439
|
+
function renderGitScanState() {
|
|
440
|
+
const line = $('fe-git-scan-state');
|
|
441
|
+
if (!line) return;
|
|
442
|
+
const state = _git?.scanState;
|
|
443
|
+
const message = state === 'scanning' && !_git.loud ? '' : scanStateText(_git);
|
|
444
|
+
line.textContent = message;
|
|
445
|
+
line.hidden = !message;
|
|
446
|
+
if (message) line.dataset.tone = state; else delete line.dataset.tone;
|
|
447
|
+
}
|
|
448
|
+
|
|
408
449
|
async function loadGitBranches() {
|
|
409
450
|
if (!_selectedProject) return;
|
|
410
451
|
const sel = $('fe-git-branch-select');
|
|
@@ -561,6 +602,7 @@ function createGitPopover() {
|
|
|
561
602
|
</div>
|
|
562
603
|
<div class="fe-git-pop-sep"></div>
|
|
563
604
|
<div class="fe-git-pop-section">
|
|
605
|
+
<div class="fe-git-scan-state" id="fe-git-scan-state" hidden></div>
|
|
564
606
|
<div class="fe-git-summary" id="fe-git-summary"></div>
|
|
565
607
|
</div>
|
|
566
608
|
</div>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// SynaBun Neural Interface — File links in agent messages: the page side
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
4
|
//
|
|
5
|
-
// One click handler for the links in the messages of the
|
|
6
|
-
// (Assistant, Claude, Codex, OpenCode). A link that points at a file the code
|
|
5
|
+
// One click handler for the links in the messages of the five side panels
|
|
6
|
+
// (Assistant, Claude, Codex, OpenCode, Antigravity). A link that points at a file the code
|
|
7
7
|
// editor can open opens it there (`open-file-editor`), at its line and column;
|
|
8
8
|
// every other link keeps what its panel did with it. What a target means and
|
|
9
9
|
// what a click does are decided in file-links.js; this module gives it the
|
|
@@ -197,12 +197,12 @@ function detectActiveGame() {
|
|
|
197
197
|
// ── SVG icons for opponent picker ──
|
|
198
198
|
const ICON_CLAUDE = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"/></svg>';
|
|
199
199
|
const ICON_OPENAI = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"/></svg>';
|
|
200
|
-
const
|
|
200
|
+
const ICON_ANTIGRAVITY = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 20 12 4l7.5 16"/><path d="M8.2 20a3.8 3.8 0 0 1 7.6 0"/></svg>';
|
|
201
201
|
|
|
202
202
|
const OPPONENTS = [
|
|
203
203
|
{ id: 'claude-code', icon: ICON_CLAUDE, label: 'Claude' },
|
|
204
204
|
{ id: 'codex', icon: ICON_OPENAI, label: 'OpenAI' },
|
|
205
|
-
{ id: '
|
|
205
|
+
{ id: 'antigravity', icon: ICON_ANTIGRAVITY, label: 'Antigravity' },
|
|
206
206
|
];
|
|
207
207
|
|
|
208
208
|
// ── Opponent picker overlay ──
|
|
@@ -1030,7 +1030,7 @@ export function wireJudgmentsTab(overlay, { toast = () => {}, initial = null } =
|
|
|
1030
1030
|
return { line: p == null ? '' : flagged(kit.tx('settings.redesign.judgments.pGoalMet', { pct: pct(p) }), p >= knob('loop-goal', 'minConfidence', 0.8)) };
|
|
1031
1031
|
}
|
|
1032
1032
|
function budgetSplitLine(row, answers) {
|
|
1033
|
-
const names = { share_claude_code: 'Claude Code', share_codex: 'Codex', share_opencode: 'OpenCode' };
|
|
1033
|
+
const names = { share_claude_code: 'Claude Code', share_codex: 'Codex', share_opencode: 'OpenCode', share_antigravity: 'Antigravity' };
|
|
1034
1034
|
const parts = Object.entries(names).filter(([key]) => answers[key]?.type === 'score').map(([key, label]) => {
|
|
1035
1035
|
const a = answers[key];
|
|
1036
1036
|
return escapeHtml(kit.tx('settings.redesign.judgments.budgetShareScore', { provider: label, score: num(a.score ?? a.value), confidence: pct(a.confidence) }));
|
|
@@ -12,7 +12,7 @@ const CLI_ICONS = {
|
|
|
12
12
|
assistant: '<svg viewBox="0 0 24 24" fill="currentColor"><rect x="8" y="5.5" width="11" height="5" rx="2.5" transform="rotate(-12 13.5 8)"/><rect x="4.5" y="12.5" width="11" height="5" rx="2.5" transform="rotate(-12 10 15)"/></svg>',
|
|
13
13
|
claude: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"/></svg>',
|
|
14
14
|
codex: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"/></svg>',
|
|
15
|
-
|
|
15
|
+
antigravity: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 20 12 4l7.5 16"/><path d="M8.2 20a3.8 3.8 0 0 1 7.6 0"/></svg>',
|
|
16
16
|
browser: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><ellipse cx="12" cy="12" rx="4" ry="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M4.5 7h15M4.5 17h15" stroke-width="1"/></svg>',
|
|
17
17
|
youtube: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>',
|
|
18
18
|
};
|
|
@@ -26,7 +26,8 @@ let _recording = null; // { actionId, originalCombo } when recording
|
|
|
26
26
|
let _pendingBindings = null; // working copy during modal editing
|
|
27
27
|
let _modalOpen = false;
|
|
28
28
|
// Actions that were removed ("toggle-toolbar" was "Hide all toolbars"): a saved key for one is dropped on load.
|
|
29
|
-
|
|
29
|
+
// "launch-gemini" became "launch-antigravity" (Antigravity replaced the Gemini CLI): its key carries over first.
|
|
30
|
+
const RETIRED_ACTIONS = ['toggle-toolbar', 'toggle-minimap', 'launch-gemini'];
|
|
30
31
|
|
|
31
32
|
// ── Combo building ──
|
|
32
33
|
|
|
@@ -447,6 +448,11 @@ export async function initKeybinds() {
|
|
|
447
448
|
const data = await fetchKeybinds();
|
|
448
449
|
if (data && data.bindings) {
|
|
449
450
|
_bindings = { ...DEFAULT_KEYBINDS, ...data.bindings };
|
|
451
|
+
// A key saved for the Gemini CLI launches Antigravity, unless Antigravity has one of its own
|
|
452
|
+
// (lib/antigravity/migrate-gemini.js migrateKeybinds does the same to the saved file).
|
|
453
|
+
if (Object.hasOwn(data.bindings, 'launch-gemini') && !Object.hasOwn(data.bindings, 'launch-antigravity')) {
|
|
454
|
+
_bindings['launch-antigravity'] = data.bindings['launch-gemini'];
|
|
455
|
+
}
|
|
450
456
|
} else {
|
|
451
457
|
_bindings = { ...DEFAULT_KEYBINDS };
|
|
452
458
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
4
|
//
|
|
5
5
|
// Floating Link Panel that mediates turn-based conversations
|
|
6
|
-
// between AI agents (Claude Code, Codex,
|
|
6
|
+
// between AI agents (Claude Code, Codex, Antigravity, etc.) via linked
|
|
7
7
|
// terminal sessions. Users can inject messages, control auto-relay,
|
|
8
8
|
// and watch agents collaborate in real-time.
|
|
9
9
|
|
|
@@ -31,7 +31,7 @@ const SVG_COPY = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" str
|
|
|
31
31
|
const PROFILE_META = {
|
|
32
32
|
'claude-code': { label: 'Claude Code', color: '#D4A27F' },
|
|
33
33
|
'codex': { label: 'Codex CLI', color: '#74c7a5' },
|
|
34
|
-
'
|
|
34
|
+
'antigravity': { label: 'Antigravity CLI', color: '#669DF6' },
|
|
35
35
|
'shell': { label: 'Shell', color: '#aaaaaa' },
|
|
36
36
|
'browser': { label: 'Browser', color: '#4fc3f7' },
|
|
37
37
|
};
|
|
@@ -245,7 +245,7 @@ async function _showSetupView() {
|
|
|
245
245
|
<div class="link-panel-empty">
|
|
246
246
|
<div class="link-panel-empty-icon">${SVG_UNLINK}</div>
|
|
247
247
|
<p>Open at least 2 terminal sessions to create a link.</p>
|
|
248
|
-
<p style="opacity:0.5;font-size:12px;">Use the terminal panel to launch Claude Code, Codex, or
|
|
248
|
+
<p style="opacity:0.5;font-size:12px;">Use the terminal panel to launch Claude Code, Codex, or Antigravity.</p>
|
|
249
249
|
</div>
|
|
250
250
|
`;
|
|
251
251
|
return;
|
|
@@ -160,7 +160,7 @@ export function initMenubar() {
|
|
|
160
160
|
'menu-terminal-assistant': 'terminal',
|
|
161
161
|
'menu-terminal-claude': 'terminal',
|
|
162
162
|
'menu-terminal-codex': 'terminal',
|
|
163
|
-
'menu-terminal-
|
|
163
|
+
'menu-terminal-antigravity': 'terminal',
|
|
164
164
|
'menu-terminal-opencode': 'terminal',
|
|
165
165
|
'menu-terminal-shell': 'terminal',
|
|
166
166
|
'menu-terminal-browser': 'browser',
|
|
@@ -572,7 +572,7 @@ function wireTerminalMenu() {
|
|
|
572
572
|
'menu-terminal-assistant': () => emit('assistant:open'),
|
|
573
573
|
'menu-terminal-claude': () => emit('terminal:open', { profile: 'claude-code' }),
|
|
574
574
|
'menu-terminal-codex': () => emit('terminal:open', { profile: 'codex' }),
|
|
575
|
-
'menu-terminal-
|
|
575
|
+
'menu-terminal-antigravity': () => emit('terminal:open', { profile: 'antigravity' }),
|
|
576
576
|
'menu-terminal-opencode': () => emit('terminal:open', { profile: 'opencode' }),
|
|
577
577
|
'menu-check-cli-updates': async () => {
|
|
578
578
|
const [tools, syn] = await Promise.all([
|
|
@@ -2,6 +2,7 @@ import { on } from './state.js';
|
|
|
2
2
|
import { attachClaudeAutomation, detachClaudeAutomation, handleClaudeAutomationEvent } from './ui-claude-panel.js';
|
|
3
3
|
import { attachCodexAutomation, detachCodexAutomation, handleCodexAutomationEvent } from './ui-codex-panel.js';
|
|
4
4
|
import { attachOpenCodeAutomation, detachOpenCodeAutomation } from './ui-opencode-panel-v2.js';
|
|
5
|
+
import { attachAntigravityAutomation, detachAntigravityAutomation, handleAntigravityAutomationEvent } from './ui-antigravity-panel.js';
|
|
5
6
|
import { nativeLoopWindowId } from './ui-native-window-id.js';
|
|
6
7
|
import {
|
|
7
8
|
isNewerRunDescriptor,
|
|
@@ -161,6 +162,7 @@ async function attach(run, focus) {
|
|
|
161
162
|
}
|
|
162
163
|
if (provider === 'codex') return attachCodexAutomation(run, options);
|
|
163
164
|
if (provider === 'opencode') return attachOpenCodeAutomation(run, options);
|
|
165
|
+
if (provider === 'antigravity') return attachAntigravityAutomation(run, options);
|
|
164
166
|
return { ok: false, reason: 'unsupported_provider' };
|
|
165
167
|
}
|
|
166
168
|
|
|
@@ -169,6 +171,7 @@ async function detachProviderRun(runId, provider) {
|
|
|
169
171
|
if (provider === 'claude-code' || provider === 'claude') return await detachClaudeAutomation(runId);
|
|
170
172
|
if (provider === 'codex') return await detachCodexAutomation(runId);
|
|
171
173
|
if (provider === 'opencode') return await detachOpenCodeAutomation(runId);
|
|
174
|
+
if (provider === 'antigravity') return await detachAntigravityAutomation(runId);
|
|
172
175
|
} catch {}
|
|
173
176
|
return false;
|
|
174
177
|
}
|
|
@@ -189,6 +192,7 @@ function deliverProviderEvent(message) {
|
|
|
189
192
|
return handleClaudeAutomationEvent(run.runId, payload);
|
|
190
193
|
}
|
|
191
194
|
if (payload.provider === 'codex') return handleCodexAutomationEvent(run.runId, payload);
|
|
195
|
+
if (payload.provider === 'antigravity') return handleAntigravityAutomationEvent(run.runId, payload);
|
|
192
196
|
} catch {}
|
|
193
197
|
return false;
|
|
194
198
|
}
|
|
@@ -15,6 +15,7 @@ import { startTutorial } from './ui-tutorial.js';
|
|
|
15
15
|
import { toggleClaudePanel, isClaudePanelOpen } from './ui-claude-panel.js';
|
|
16
16
|
import { toggleCodexPanel, isCodexPanelOpen } from './ui-codex-panel.js';
|
|
17
17
|
import { toggleOpencodePanel, isOpencodePanelOpen } from './ui-opencode-panel-v2.js';
|
|
18
|
+
import { toggleAntigravityPanel, isAntigravityPanelOpen } from './ui-antigravity-panel.js';
|
|
18
19
|
import { toggleAssistantPanel, isAssistantPanelOpen, initAssistantPanel } from './ui-assistant-panel.js';
|
|
19
20
|
import { initAssistant } from './ui-assistant.js';
|
|
20
21
|
import { toggleSessionMonitor, fetchLoopsForBadge, fetchNotificationsForBadge } from './ui-sessions.js';
|
|
@@ -25,6 +26,7 @@ import { initToolUpdates } from './ui-tool-updates.js';
|
|
|
25
26
|
import { initCrashNotice } from './ui-crash-notice.js';
|
|
26
27
|
import { initSidepanelTrayPlaceholders } from './ui-sidepanel-tray.js';
|
|
27
28
|
import { initDesktopTitleBar } from './desktop-titlebar.js';
|
|
29
|
+
import { initToprightOverflow } from './ui-topright-overflow.js';
|
|
28
30
|
|
|
29
31
|
const $ = (id) => document.getElementById(id);
|
|
30
32
|
|
|
@@ -35,6 +37,7 @@ export function initNavbar() {
|
|
|
35
37
|
const claudePanelBtn = $('topright-claude-panel-btn');
|
|
36
38
|
const codexPanelBtn = $('topright-codex-panel-btn');
|
|
37
39
|
const opencodePanelBtn = $('topright-opencode-panel-btn');
|
|
40
|
+
const antigravityPanelBtn = $('topright-antigravity-panel-btn');
|
|
38
41
|
initSidepanelTrayPlaceholders();
|
|
39
42
|
// The Assistant: shared host wiring (terminal tab + sidepanel), then the
|
|
40
43
|
// sidepanel host, which restores its saved tabs as tray pills.
|
|
@@ -62,6 +65,11 @@ export function initNavbar() {
|
|
|
62
65
|
toggle: toggleOpencodePanel,
|
|
63
66
|
button: opencodePanelBtn,
|
|
64
67
|
},
|
|
68
|
+
antigravity: {
|
|
69
|
+
isOpen: isAntigravityPanelOpen,
|
|
70
|
+
toggle: toggleAntigravityPanel,
|
|
71
|
+
button: antigravityPanelBtn,
|
|
72
|
+
},
|
|
65
73
|
};
|
|
66
74
|
|
|
67
75
|
function syncAgentPanelButtons() {
|
|
@@ -113,6 +121,16 @@ export function initNavbar() {
|
|
|
113
121
|
syncAgentPanelButtons();
|
|
114
122
|
});
|
|
115
123
|
}
|
|
124
|
+
if (antigravityPanelBtn) {
|
|
125
|
+
antigravityPanelBtn.addEventListener('click', async (e) => {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
await toggleAgentPanel('antigravity');
|
|
128
|
+
});
|
|
129
|
+
on('antigravity-panel:visibility', (visible) => {
|
|
130
|
+
antigravityPanelBtn.classList.toggle('active', !!visible);
|
|
131
|
+
syncAgentPanelButtons();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
116
134
|
on('claude-panel:visibility', () => syncAgentPanelButtons());
|
|
117
135
|
on('opencode-child:visibility', () => syncAgentPanelButtons());
|
|
118
136
|
|
|
@@ -328,6 +346,33 @@ export function initNavbar() {
|
|
|
328
346
|
});
|
|
329
347
|
}
|
|
330
348
|
|
|
349
|
+
// ── The whiteboard rail toggle (#wb-hide-btn ↔ #toolbar-show-btn) ──
|
|
350
|
+
// Hides the whiteboard tool rail (#wb-toolbar, Focus Mode) behind a SynaBun logo button above the console button
|
|
351
|
+
// (#term-peek-dock). The workspace toolbar and the session pills (#term-minimized-tray) stay. Remembered across reloads.
|
|
352
|
+
const wbRail = $('wb-toolbar');
|
|
353
|
+
const hideBtn = $('wb-hide-btn');
|
|
354
|
+
const showBtn = $('toolbar-show-btn');
|
|
355
|
+
if (wbRail && hideBtn && showBtn) {
|
|
356
|
+
const setRailHidden = (hidden) => {
|
|
357
|
+
wbRail.classList.toggle('wb-rail-hidden', hidden);
|
|
358
|
+
wbRail.toggleAttribute('inert', hidden);
|
|
359
|
+
hideBtn.setAttribute('aria-pressed', String(hidden));
|
|
360
|
+
showBtn.setAttribute('aria-pressed', String(!hidden));
|
|
361
|
+
showBtn.hidden = !hidden;
|
|
362
|
+
};
|
|
363
|
+
setRailHidden(storage.getItem(KEYS.WHITEBOARD_TOOLBAR_HIDDEN) === 'true');
|
|
364
|
+
const toggle = (hidden) => {
|
|
365
|
+
setRailHidden(hidden);
|
|
366
|
+
storage.setItem(KEYS.WHITEBOARD_TOOLBAR_HIDDEN, String(hidden));
|
|
367
|
+
(hidden ? showBtn : hideBtn).focus({ preventScroll: true });
|
|
368
|
+
};
|
|
369
|
+
hideBtn.addEventListener('click', () => toggle(true));
|
|
370
|
+
showBtn.addEventListener('click', () => toggle(false));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ── One row, always: what does not fit the band goes into the ⋯ menu ──
|
|
374
|
+
initToprightOverflow();
|
|
375
|
+
|
|
331
376
|
// ── Session pills in the row: the whole name is the tooltip ──
|
|
332
377
|
// Laid out in a row, a pill keeps its width on hover (styles.css): widening, it would push the others onto another
|
|
333
378
|
// line under the pointer. A name cut short is read from the tooltip instead; a pill that brings a tooltip of its own
|