From 775a87e8c89d08311c32d2426c882329828010e2 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 16 Mar 2026 12:35:18 +0100 Subject: [PATCH 1/2] fix(vscode): restore settings view after webview reload The SettingsEditorProvider disposed its webviewReady listener after the first fire, so when VS Code's 'Reload Webviews' command re-created the JS context, no navigate message was re-sent and the panel fell back to the default newTask view despite still being titled 'Kilo Settings'. Keep the listener alive for the panel's lifetime so every webviewReady (including reloads) re-sends the navigate message to the correct view. --- packages/kilo-vscode/src/SettingsEditorProvider.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/src/SettingsEditorProvider.ts b/packages/kilo-vscode/src/SettingsEditorProvider.ts index 8a12dd94708..f54d4de1581 100644 --- a/packages/kilo-vscode/src/SettingsEditorProvider.ts +++ b/packages/kilo-vscode/src/SettingsEditorProvider.ts @@ -57,14 +57,14 @@ export class SettingsEditorProvider implements vscode.Disposable { } }) - // Once the webview signals ready, navigate to the target view. + // Navigate to the target view on every webviewReady (including after + // "Developer: Reload Webviews" which re-creates the JS context). const readyDisposable = panel.webview.onDidReceiveMessage((msg) => { if (msg.type === "webviewReady") { // Small delay to let KiloProvider's own webviewReady handler finish first setTimeout(() => { provider.postMessage({ type: "navigate", view }) }, 50) - readyDisposable.dispose() } }) @@ -74,6 +74,7 @@ export class SettingsEditorProvider implements vscode.Disposable { panel.onDidDispose(() => { console.log(`[Kilo New] ${title} panel disposed`) closePanelDisposable.dispose() + readyDisposable.dispose() provider.dispose() this.panels.delete(view) this.providers.delete(view) From 95221c995df7fd959f6161552bd36c96ba08ddfc Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Mon, 16 Mar 2026 12:54:49 +0100 Subject: [PATCH 2/2] feat(vscode): persist active settings tab across webview reloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Track the active settings tab so it survives 'Reload Webviews' and can be targeted from external navigation. - Extend NavigateMessage with optional `tab` field - Add settingsTabChanged webview→extension message - Switch Settings Tabs to controlled mode with tab prop - SettingsEditorProvider remembers the last active tab and re-sends it on every webviewReady - openPanel() accepts an optional tab param for deep-linking from extension commands --- .../kilo-vscode/src/SettingsEditorProvider.ts | 20 ++++++++++-- packages/kilo-vscode/webview-ui/src/App.tsx | 6 +++- .../src/components/settings/Settings.tsx | 31 +++++++++++++++++-- .../webview-ui/src/types/messages.ts | 7 +++++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/packages/kilo-vscode/src/SettingsEditorProvider.ts b/packages/kilo-vscode/src/SettingsEditorProvider.ts index f54d4de1581..975f36b9255 100644 --- a/packages/kilo-vscode/src/SettingsEditorProvider.ts +++ b/packages/kilo-vscode/src/SettingsEditorProvider.ts @@ -18,6 +18,7 @@ type PanelView = "settings" | "profile" export class SettingsEditorProvider implements vscode.Disposable { private panels = new Map() private providers = new Map() + private tabs = new Map() constructor( private readonly extensionUri: vscode.Uri, @@ -25,9 +26,15 @@ export class SettingsEditorProvider implements vscode.Disposable { private readonly context: vscode.ExtensionContext, ) {} - openPanel(view: PanelView): void { + openPanel(view: PanelView, tab?: string): void { + if (tab) this.tabs.set(view, tab) + const existing = this.panels.get(view) if (existing) { + if (tab) { + const provider = this.providers.get(view) + provider?.postMessage({ type: "navigate", view, tab }) + } existing.reveal(vscode.ViewColumn.One) return } @@ -63,11 +70,18 @@ export class SettingsEditorProvider implements vscode.Disposable { if (msg.type === "webviewReady") { // Small delay to let KiloProvider's own webviewReady handler finish first setTimeout(() => { - provider.postMessage({ type: "navigate", view }) + provider.postMessage({ type: "navigate", view, tab: this.tabs.get(view) }) }, 50) } }) + // Remember the active settings tab so it survives webview reloads. + const tabDisposable = panel.webview.onDidReceiveMessage((msg) => { + if (msg.type === "settingsTabChanged" && typeof msg.tab === "string") { + this.tabs.set(view, msg.tab) + } + }) + this.panels.set(view, panel) this.providers.set(view, provider) @@ -75,9 +89,11 @@ export class SettingsEditorProvider implements vscode.Disposable { console.log(`[Kilo New] ${title} panel disposed`) closePanelDisposable.dispose() readyDisposable.dispose() + tabDisposable.dispose() provider.dispose() this.panels.delete(view) this.providers.delete(view) + this.tabs.delete(view) }) } diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index 4f4bf317967..8a253a2b40f 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -176,6 +176,7 @@ export const LanguageBridge: Component<{ children: any }> = (props) => { // Inner app component that uses the contexts const AppContent: Component = () => { const [currentView, setCurrentView] = createSignal("newTask") + const [settingsTab, setSettingsTab] = createSignal() const [migrationReturnView, setMigrationReturnView] = createSignal("newTask") // legacy-migration const session = useSession() const server = useServer() @@ -212,7 +213,8 @@ const AppContent: Component = () => { handleViewAction(message.action) } if (message?.type === "navigate" && message.view && VALID_VIEWS.has(message.view)) { - console.log("[Kilo New] App: 🧭 navigate:", message.view) + console.log("[Kilo New] App: 🧭 navigate:", message.view, message.tab ? `tab=${message.tab}` : "") + if (message.tab) setSettingsTab(message.tab) setCurrentView(message.view as ViewType) } if (message?.type === "openCloudSession" && message.sessionId) { @@ -267,6 +269,8 @@ const AppContent: Component = () => { { setMigrationReturnView("settings") setCurrentView("migration") diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx index 816c95cb917..54a626cf1bf 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx @@ -1,6 +1,7 @@ -import { Component } from "solid-js" +import { Component, createSignal, createEffect, on } from "solid-js" import { Icon } from "@kilocode/kilo-ui/icon" import { Tabs } from "@kilocode/kilo-ui/tabs" +import { useVSCode } from "../../context/vscode" import { useLanguage } from "../../context/language" import ProvidersTab from "./ProvidersTab" import AgentBehaviourTab from "./AgentBehaviourTab" @@ -19,12 +20,32 @@ import AboutKiloCodeTab from "./AboutKiloCodeTab" import { useServer } from "../../context/server" export interface SettingsProps { + tab?: string + onTabChange?: (tab: string) => void onMigrateClick?: () => void // legacy-migration } const Settings: Component = (props) => { const server = useServer() const language = useLanguage() + const vscode = useVSCode() + const [active, setActive] = createSignal(props.tab ?? "providers") + + // Sync when the parent changes the tab prop (e.g. via navigate message) + createEffect( + on( + () => props.tab, + (tab) => { + if (tab) setActive(tab) + }, + ), + ) + + const onTabChange = (tab: string) => { + setActive(tab) + props.onTabChange?.(tab) + vscode.postMessage({ type: "settingsTabChanged", tab }) + } return (
@@ -42,7 +63,13 @@ const Settings: Component = (props) => {
{/* Settings tabs */} - + diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 8b4543fc5d9..e9fa87dedad 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -557,6 +557,7 @@ export interface DeviceAuthCancelledMessage { export interface NavigateMessage { type: "navigate" view: "newTask" | "marketplace" | "history" | "cloudHistory" | "profile" | "settings" | "migration" | "subAgentViewer" // legacy-migration: "migration" + tab?: string } export interface ProvidersLoadedMessage { @@ -1327,6 +1328,11 @@ export interface ResetAllSettingsRequest { type: "resetAllSettings" } +export interface SettingsTabChangedMessage { + type: "settingsTabChanged" + tab: string +} + export interface RequestNotificationsMessage { type: "requestNotifications" } @@ -1638,6 +1644,7 @@ export type WebviewMessage = | UpdateConfigMessage | RequestNotificationSettingsMessage | ResetAllSettingsRequest + | SettingsTabChangedMessage | SyncSessionRequest | CreateWorktreeSessionRequest | RequestNotificationsMessage