diff --git a/.changeset/giant-items-tan.md b/.changeset/giant-items-tan.md new file mode 100644 index 000000000..592780bbb --- /dev/null +++ b/.changeset/giant-items-tan.md @@ -0,0 +1,5 @@ +--- +"claude-dev": minor +--- + +Migrate historyButtonClicked to protobus diff --git a/proto/ui.proto b/proto/ui.proto index 6ead4702a..8f67ec0c5 100644 --- a/proto/ui.proto +++ b/proto/ui.proto @@ -31,4 +31,7 @@ service UiService { // Subscribe to MCP button clicked events rpc subscribeToMcpButtonClicked(WebviewProviderTypeRequest) returns (stream Empty); + + // Subscribe to history button click events + rpc subscribeToHistoryButtonClicked(WebviewProviderTypeRequest) returns (stream Empty); } diff --git a/src/core/controller/ui/subscribeToHistoryButtonClicked.ts b/src/core/controller/ui/subscribeToHistoryButtonClicked.ts new file mode 100644 index 000000000..c128a294d --- /dev/null +++ b/src/core/controller/ui/subscribeToHistoryButtonClicked.ts @@ -0,0 +1,66 @@ +import { Controller } from "../index" +import { Empty } from "@shared/proto/common" +import { WebviewProviderType, WebviewProviderTypeRequest } from "@shared/proto/ui" +import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler" + +// Keep track of active subscriptions with their provider type +const activeHistoryButtonClickedSubscriptions = new Map() + +/** + * Subscribe to history button clicked events + * @param controller The controller instance + * @param request The webview provider type request + * @param responseStream The streaming response handler + * @param requestId The ID of the request (passed by the gRPC handler) + */ +export async function subscribeToHistoryButtonClicked( + controller: Controller, + request: WebviewProviderTypeRequest, + responseStream: StreamingResponseHandler, + requestId?: string, +): Promise { + // Extract the provider type from the request + const providerType = request.providerType + console.log(`[DEBUG] set up history button subscription for ${WebviewProviderType[providerType]} webview`) + + // Add this subscription to the active subscriptions with its provider type + activeHistoryButtonClickedSubscriptions.set(responseStream, providerType) + + // Register cleanup when the connection is closed + const cleanup = () => { + activeHistoryButtonClickedSubscriptions.delete(responseStream) + } + + // Register the cleanup function with the request registry if we have a requestId + if (requestId) { + getRequestRegistry().registerRequest(requestId, cleanup, { type: "history_button_clicked_subscription" }, responseStream) + } +} + +/** + * Send a history button clicked event to all active subscribers + * @param webviewType Optional filter to send only to a specific webview type + */ +export async function sendHistoryButtonClickedEvent(webviewType?: WebviewProviderType): Promise { + // Send the event to all active subscribers matching the webview type (if specified) + const promises = Array.from(activeHistoryButtonClickedSubscriptions.entries()).map(async ([responseStream, providerType]) => { + // Skip subscribers of different types if webview type is specified + if (webviewType !== undefined && webviewType !== providerType) { + return + } + + try { + const event: Empty = {} + await responseStream( + event, + false, // Not the last message + ) + } catch (error) { + console.error(`Error sending history button clicked event to ${WebviewProviderType[providerType]}:`, error) + // Remove the subscription if there was an error + activeHistoryButtonClickedSubscriptions.delete(responseStream) + } + }) + + await Promise.all(promises) +} diff --git a/src/extension.ts b/src/extension.ts index 274dadc8c..344f40949 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,6 +18,8 @@ import { telemetryService } from "./services/posthog/telemetry/TelemetryService" import { v4 as uuidv4 } from "uuid" import { WebviewProviderType as WebviewProviderTypeEnum } from "@shared/proto/ui" import { WebviewProviderType } from "./shared/webview/types" +import { sendHistoryButtonClickedEvent } from "./core/controller/ui/subscribeToHistoryButtonClicked" + /* Built using https://github.com/microsoft/vscode-webview-ui-toolkit @@ -175,21 +177,14 @@ export async function activate(context: vscode.ExtensionContext) { ) context.subscriptions.push( - vscode.commands.registerCommand("cline.historyButtonClicked", (webview: any) => { - WebviewProvider.getAllInstances().forEach((instance) => { - const openHistory = async (instance?: WebviewProvider) => { - instance?.controller.postMessageToWebview({ - type: "action", - action: "historyButtonClicked", - }) - } - const isSidebar = !webview - if (isSidebar) { - openHistory(WebviewProvider.getSidebarInstance()) - } else { - WebviewProvider.getTabInstances().forEach(openHistory) - } - }) + vscode.commands.registerCommand("cline.historyButtonClicked", async (webview: any) => { + console.log("[DEBUG] historyButtonClicked", webview) + // Pass the webview type to the event sender + const isSidebar = !webview + const webviewType = isSidebar ? WebviewProviderTypeEnum.SIDEBAR : WebviewProviderTypeEnum.TAB + + // Send event to all subscribers using the gRPC streaming method + await sendHistoryButtonClickedEvent(webviewType) }), ) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index dbd1023fd..591b7bdf6 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -41,7 +41,6 @@ export interface ExtensionMessage { action?: | "chatButtonClicked" | "settingsButtonClicked" - | "historyButtonClicked" | "didBecomeVisible" | "accountLogoutClicked" | "accountButtonClicked" diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index a075b3ef5..9014d7ebf 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -196,9 +196,6 @@ export const ExtensionStateContextProvider: React.FC<{ case "settingsButtonClicked": navigateToSettings() break - case "historyButtonClicked": - navigateToHistory() - break case "accountButtonClicked": navigateToAccount() break @@ -272,9 +269,14 @@ export const ExtensionStateContextProvider: React.FC<{ // References to store subscription cancellation functions const stateSubscriptionRef = useRef<(() => void) | null>(null) const mcpButtonUnsubscribeRef = useRef<(() => void) | null>(null) + const historyButtonClickedSubscriptionRef = useRef<(() => void) | null>(null) // Subscribe to state updates and UI events using the gRPC streaming API useEffect(() => { + // Determine the webview provider type + const webviewType = + window.WEBVIEW_PROVIDER_TYPE === "sidebar" ? WebviewProviderTypeEnum.SIDEBAR : WebviewProviderTypeEnum.TAB + // Set up state subscription stateSubscriptionRef.current = StateServiceClient.subscribeToState(EmptyRequest.create({}), { onResponse: (response) => { @@ -348,8 +350,7 @@ export const ExtensionStateContextProvider: React.FC<{ // Subscribe to MCP button clicked events with webview type mcpButtonUnsubscribeRef.current = UiServiceClient.subscribeToMcpButtonClicked( WebviewProviderTypeRequest.create({ - providerType: - window.WEBVIEW_PROVIDER_TYPE === "sidebar" ? WebviewProviderTypeEnum.SIDEBAR : WebviewProviderTypeEnum.TAB, + providerType: webviewType, }), { onResponse: () => { @@ -365,6 +366,26 @@ export const ExtensionStateContextProvider: React.FC<{ }, ) + // Set up history button clicked subscription with webview type + historyButtonClickedSubscriptionRef.current = UiServiceClient.subscribeToHistoryButtonClicked( + WebviewProviderTypeRequest.create({ + providerType: webviewType, + }), + { + onResponse: () => { + // When history button is clicked, navigate to history view + console.log("[DEBUG] Received history button clicked event from gRPC stream") + navigateToHistory() + }, + onError: (error) => { + console.error("Error in history button clicked subscription:", error) + }, + onComplete: () => { + console.log("History button clicked subscription completed") + }, + }, + ) + // Still send the webviewDidLaunch message for other initialization vscode.postMessage({ type: "webviewDidLaunch" }) @@ -378,6 +399,10 @@ export const ExtensionStateContextProvider: React.FC<{ mcpButtonUnsubscribeRef.current() mcpButtonUnsubscribeRef.current = null } + if (historyButtonClickedSubscriptionRef.current) { + historyButtonClickedSubscriptionRef.current() + historyButtonClickedSubscriptionRef.current = null + } } }, [])