diff --git a/.changeset/fix-secondary-sidebar-nav-bar.md b/.changeset/fix-secondary-sidebar-nav-bar.md index 070b89d5f8..7905945a1e 100644 --- a/.changeset/fix-secondary-sidebar-nav-bar.md +++ b/.changeset/fix-secondary-sidebar-nav-bar.md @@ -2,4 +2,4 @@ "kilo-code": patch --- -Fix the sidebar navigation bar (New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, Settings) disappearing when the Kilo Code view is docked in the Secondary Side Bar. The navigation is now rendered inside the webview itself, so it stays visible regardless of where the view is docked. +Fix the sidebar navigation bar (New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, Settings) disappearing in Cursor when the Kilo Code view is docked in the Secondary Side Bar. Cursor now renders the navigation inside the webview itself so it stays visible regardless of dock location. VS Code is unaffected — it continues to use its native title bar toolbar, which already worked correctly everywhere. diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index d1aed14c8d..99d3b5a23f 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -141,6 +141,41 @@ "category": "Kilo Code", "icon": "$(settings-gear)" }, + { + "command": "kilo-code.new.sidebarTitle.plusButtonClicked", + "title": "New Task", + "icon": "$(add)" + }, + { + "command": "kilo-code.new.sidebarTitle.agentManagerOpen", + "title": "Agent Manager", + "icon": "$(organization)" + }, + { + "command": "kilo-code.new.sidebarTitle.kiloClawOpen", + "title": "KiloClaw", + "icon": "$(comment-discussion)" + }, + { + "command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked", + "title": "Marketplace", + "icon": "$(extensions)" + }, + { + "command": "kilo-code.new.sidebarTitle.historyButtonClicked", + "title": "History", + "icon": "$(history)" + }, + { + "command": "kilo-code.new.sidebarTitle.profileButtonClicked", + "title": "Profile", + "icon": "$(account)" + }, + { + "command": "kilo-code.new.sidebarTitle.settingsButtonClicked", + "title": "Settings", + "icon": "$(settings-gear)" + }, { "command": "kilo-code.new.openInTab", "title": "Open in Tab", @@ -426,12 +461,76 @@ ], "menus": { "commandPalette": [ + { + "command": "kilo-code.new.sidebarTitle.plusButtonClicked", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.historyButtonClicked", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.agentManagerOpen", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.kiloClawOpen", + "when": "false" + }, { "command": "kilo-code.new.agentManager.advancedWorktree", "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.profileButtonClicked", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.settingsButtonClicked", + "when": "false" + } + ], + "view/title": [ + { + "command": "kilo-code.new.sidebarTitle.plusButtonClicked", + "group": "navigation@0", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.historyButtonClicked", + "group": "navigation@1", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.agentManagerOpen", + "group": "navigation@2", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.kiloClawOpen", + "group": "navigation@3", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked", + "group": "navigation@4", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.profileButtonClicked", + "group": "navigation@5", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.settingsButtonClicked", + "group": "navigation@6", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" } ], - "view/title": [], "scm/title": [ { "command": "kilo-code.new.generateCommitMessage", @@ -451,6 +550,26 @@ "command": "kilo-code.new.openInTab", "group": "navigation", "when": "true" + }, + { + "command": "kilo-code.new.plusButtonClicked", + "group": "navigation@0", + "when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.historyButtonClicked", + "group": "navigation@1", + "when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.profileButtonClicked", + "group": "navigation@2", + "when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.settingsButtonClicked", + "group": "navigation@3", + "when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor" } ], "editor/context": [ diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 4de5a31101..4e6f22e97d 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -17,7 +17,7 @@ import type { EditorContext, IndexingStatus } from "./services/cli-backend/types import { FileIgnoreController } from "./services/autocomplete/shims/FileIgnoreController" import { ChatTextAreaAutocomplete } from "./services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete" import { notebookUri } from "./services/autocomplete/continuedev/core/autocomplete/notebook" -import { buildWebviewHtml, getWebviewFontSize } from "./utils" +import { buildWebviewHtml, getWebviewFontSize, isCursorHost } from "./utils" import { saveImage } from "./kilo-provider/save-image" import { handleEditorAction } from "./kilo-provider/editor-actions" import { exportTranscript } from "./kilo-provider/export-transcript" @@ -4905,7 +4905,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper title: "Kilo Code", port: this.connectionService.getServerInfo()?.port, extraStyles: `.container { height: 100vh; }`, - topBar: this.opts.hideTopBar !== true, + // Dedicated single-purpose panels (Settings, Profile, Sub-Agent Viewer) + // never show the bar. Sidebar and "Open in Tab" only need it in Cursor — + // VS Code's native toolbar (restored in package.json) works everywhere. + topBar: this.opts.hideTopBar !== true && isCursorHost(), topBarSurface: this.opts.topBarSurface === "tab" ? "tab_title" : "sidebar_title", }) } diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 6e6e991a3d..e592626b97 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -17,7 +17,7 @@ import { ensureBackendForAutocomplete } from "./services/autocomplete/ensure-bac import { AutocompleteServiceManager } from "./services/autocomplete/AutocompleteServiceManager" import { AttentionService } from "./services/attention" import { BrowserAutomationService } from "./services/browser-automation" -import { TelemetryProxy } from "./services/telemetry" +import { TelemetryEventName, TelemetryProxy } from "./services/telemetry" import { registerCommitMessageService } from "./services/commit-message" import { registerCodeActions, registerTerminalActions, KiloCodeActionProvider } from "./services/code-actions" import { registerToggleAutoApprove } from "./commands/toggle-auto-approve" @@ -26,6 +26,7 @@ import { RemoteStatusService } from "./services/RemoteStatusService" import { markWorkspace } from "./util/spotlight" import { createNotebookBridge } from "./services/notebook" import { createGitExecutable } from "./util/git-executable" +import { isCursorHost } from "./utils" let agentManager: AgentManagerProvider | undefined let shuttingDown = false @@ -48,6 +49,10 @@ export function activate(context: vscode.ExtensionContext) { console.log("Kilo Code extension is now active") shuttingDown = false + // Drives the "!kilo-code.new.isCursor" guards on the native view/title and + // editor/title menu contributions — see isCursorHost() for why. + void vscode.commands.executeCommand("setContext", "kilo-code.new.isCursor", isCursorHost()) + const telemetry = TelemetryProxy.getInstance() // Create shared connection service (one server for all webviews) @@ -334,8 +339,39 @@ export function activate(context: vscode.ExtensionContext) { }), ) + // Sidebar menus use wrapper commands so this event measures real title button presses, + // not programmatic opens, shortcuts, or editor title commands. + const track = (button: string, command: string) => { + TelemetryProxy.capture(TelemetryEventName.TITLE_BUTTON_CLICKED, { + button, + surface: "sidebar_title", + }) + void vscode.commands.executeCommand(command) + } + // Register toolbar button command handlers context.subscriptions.push( + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.plusButtonClicked", () => { + track("new_task", "kilo-code.new.plusButtonClicked") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.historyButtonClicked", () => { + track("history", "kilo-code.new.historyButtonClicked") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.agentManagerOpen", () => { + track("agent_manager", "kilo-code.new.agentManagerOpen") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.kiloClawOpen", () => { + track("kiloclaw", "kilo-code.new.kiloClawOpen") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.marketplaceButtonClicked", () => { + track("marketplace", "kilo-code.new.marketplaceButtonClicked") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.profileButtonClicked", () => { + track("profile", "kilo-code.new.profileButtonClicked") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.settingsButtonClicked", () => { + track("settings", "kilo-code.new.settingsButtonClicked") + }), vscode.commands.registerCommand("kilo-code.new.plusButtonClicked", () => { const tab = activeTabProvider() if (tab) tab.postMessage({ type: "action", action: "plusButtonClicked" }) diff --git a/packages/kilo-vscode/src/utils.ts b/packages/kilo-vscode/src/utils.ts index 23479043e8..6e3b6b49c0 100644 --- a/packages/kilo-vscode/src/utils.ts +++ b/packages/kilo-vscode/src/utils.ts @@ -18,6 +18,27 @@ export function getWebviewFontSize(): number { return clamp(raw) } +/** + * True when running inside Cursor rather than real VS Code (or another + * fork). Cursor's Secondary Side Bar support is known to be unreliable for + * extension-contributed `view/title` toolbars (see + * https://github.com/anthropics/claude-code/issues/31375 for the same class + * of bug in a different extension), so Cursor falls back to an in-webview + * navigation bar instead of the native toolbar that VS Code renders fine + * everywhere. + * + * This is a per-host choice, not a per-dock-location one: `WebviewView` (and + * the rest of the public API, checked against @types/vscode) exposes no way + * to ask "is my view currently in the primary or secondary side bar", so the + * webview fallback bar is Cursor's only option in both locations. Accepted + * trade-off: Cursor's primary side bar loses the single-line native look it + * had before this existed, in exchange for the Secondary Side Bar actually + * working, with zero guessing about dock position anywhere. + */ +export function isCursorHost(): boolean { + return vscode.env.appName.toLowerCase().includes("cursor") +} + function fontStyle(): string { const base = getWebviewFontSize() const vars = SIZES.map((size) => `--kilo-font-size-${size}: ${(base * size) / 13}px;`).join("\n ") diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index 6dc0e4da1c..e8dafa01c5 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -311,8 +311,11 @@ const AppContent: Component = () => { // Set synchronously in the webview HTML by KiloProvider so it's available // before this component ever mounts (see buildWebviewHtml/_getHtmlForWebview). - // Dedicated single-purpose panels (Settings, Profile, Sub-Agent Viewer) set - // KILO_TOP_BAR = false since navigating away from them makes no sense. + // False for dedicated single-purpose panels (Settings, Profile, Sub-Agent + // Viewer) always, and for the Sidebar/"Open in Tab" outside Cursor — real + // VS Code's native title bar toolbar already covers those. Defaults to + // true only when unset entirely (e.g. Storybook, which doesn't render the + // real page HTML). const host = window as { KILO_TOP_BAR?: boolean; KILO_TOP_BAR_SURFACE?: string } const showTopBar = host.KILO_TOP_BAR !== false const topBarSurface = host.KILO_TOP_BAR_SURFACE ?? "sidebar_title" diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/SidebarTopBar.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/SidebarTopBar.tsx index c031882e4d..0a0de70c92 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/SidebarTopBar.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/SidebarTopBar.tsx @@ -1,8 +1,10 @@ /** * Renders New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, and - * Settings inside the webview. VS Code's native `view/title` toolbar renders - * outside the webview DOM and disappears in the Secondary Side Bar with no way - * to detect or work around that — this bar guarantees the actions stay visible. + * Settings inside the webview, as a fallback for Cursor only (see isCursorHost() + * in src/utils.ts). Cursor's Secondary Side Bar support is unreliable for + * extension-contributed `view/title` toolbars, which render outside the webview + * DOM with no API to detect or work around the failure. Real VS Code renders the + * native toolbar fine everywhere, so it keeps using that instead of this bar. */ import { Component, For } from "solid-js" @@ -45,13 +47,12 @@ export const SidebarTopBar: Component = (props) => { type: "openAgentManager" | "openKiloClaw" | "openMarketplacePanel" | "openProfilePanel" | "openSettingsPanel", ) => vscode.postMessage({ type }) - const actions: (Action | "spacer")[] = [ + const actions: Action[] = [ { key: "newTask", codicon: "add", button: "new_task", run: () => props.onNewTask() }, { key: "history", codicon: "history", button: "history", run: () => props.onHistory() }, { key: "agentManager", codicon: "organization", button: "agent_manager", run: () => open("openAgentManager") }, { key: "kiloClaw", codicon: "comment-discussion", button: "kiloclaw", run: () => open("openKiloClaw") }, { key: "marketplace", codicon: "extensions", button: "marketplace", run: () => open("openMarketplacePanel") }, - "spacer", { key: "profile", codicon: "account", button: "profile", run: () => open("openProfilePanel") }, { key: "settings", codicon: "settings-gear", button: "settings", run: () => open("openSettingsPanel") }, ] @@ -60,7 +61,6 @@ export const SidebarTopBar: Component = (props) => {