From bb6d02df83749d67ff8bb602fc1a3de86222bca5 Mon Sep 17 00:00:00 2001 From: celestial-vault <58194240+celestial-vault@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:46:41 -0700 Subject: [PATCH] Migrate didBecomeVisible protobus (#3996) * migrate chatButtonClicked * changeset * send targeted event to the controller * prettier * migrate didBecomeVisible * changeset * fix proto linter issue * add back event listener --------- Co-authored-by: Elephant Lumps --- .changeset/curvy-bulldogs-jog.md | 5 ++ proto/ui.proto | 3 + .../ui/subscribeToDidBecomeVisible.ts | 63 +++++++++++++++++++ src/core/webview/index.ts | 15 ++--- src/shared/ExtensionMessage.ts | 2 +- webview-ui/src/components/chat/ChatView.tsx | 21 ------- .../src/context/ExtensionStateContext.tsx | 17 +++++ 7 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 .changeset/curvy-bulldogs-jog.md create mode 100644 src/core/controller/ui/subscribeToDidBecomeVisible.ts diff --git a/.changeset/curvy-bulldogs-jog.md b/.changeset/curvy-bulldogs-jog.md new file mode 100644 index 0000000000..771db30d11 --- /dev/null +++ b/.changeset/curvy-bulldogs-jog.md @@ -0,0 +1,5 @@ +--- +"claude-dev": minor +--- + +Migrate didBecomeVisible to protobus diff --git a/proto/ui.proto b/proto/ui.proto index 83e5c24737..8bdc1db1f4 100644 --- a/proto/ui.proto +++ b/proto/ui.proto @@ -259,4 +259,7 @@ service UiService { // Subscribe to focus chat input events with client ID rpc subscribeToFocusChatInput(StringRequest) returns (stream Empty); + + // Subscribe to webview visibility change events + rpc subscribeToDidBecomeVisible(EmptyRequest) returns (stream Empty); } diff --git a/src/core/controller/ui/subscribeToDidBecomeVisible.ts b/src/core/controller/ui/subscribeToDidBecomeVisible.ts new file mode 100644 index 0000000000..ff3079066c --- /dev/null +++ b/src/core/controller/ui/subscribeToDidBecomeVisible.ts @@ -0,0 +1,63 @@ +import { Controller } from "../index" +import { Empty } from "@shared/proto/common" +import { EmptyRequest } from "@shared/proto/common" +import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler" + +// Keep track of active didBecomeVisible subscriptions by controller ID +const activeDidBecomeVisibleSubscriptions = new Map() + +/** + * Subscribe to didBecomeVisible events + * @param controller The controller instance + * @param request The empty request + * @param responseStream The streaming response handler + * @param requestId The ID of the request (passed by the gRPC handler) + */ +export async function subscribeToDidBecomeVisible( + controller: Controller, + request: EmptyRequest, + responseStream: StreamingResponseHandler, + requestId?: string, +): Promise { + const controllerId = controller.id + console.log(`[DEBUG] set up didBecomeVisible subscription for controller ${controllerId}`) + + // Add this subscription to the active subscriptions with the controller ID + activeDidBecomeVisibleSubscriptions.set(controllerId, responseStream) + + // Register cleanup when the connection is closed + const cleanup = () => { + activeDidBecomeVisibleSubscriptions.delete(controllerId) + } + + // Register the cleanup function with the request registry if we have a requestId + if (requestId) { + getRequestRegistry().registerRequest(requestId, cleanup, { type: "didBecomeVisible_subscription" }, responseStream) + } +} + +/** + * Send a didBecomeVisible event to a specific controller's subscription + * @param controllerId The ID of the controller to send the event to + */ +export async function sendDidBecomeVisibleEvent(controllerId: string): Promise { + // Get the subscription for this specific controller + const responseStream = activeDidBecomeVisibleSubscriptions.get(controllerId) + + if (!responseStream) { + console.log(`[DEBUG] No active subscription for controller ${controllerId}`) + return + } + + try { + const event: Empty = Empty.create({}) + await responseStream( + event, + false, // Not the last message + ) + } catch (error) { + console.error(`Error sending didBecomeVisible event to controller ${controllerId}:`, error) + // Remove the subscription if there was an error + activeDidBecomeVisibleSubscriptions.delete(controllerId) + } +} diff --git a/src/core/webview/index.ts b/src/core/webview/index.ts index 015bb76c3d..cea1cd84b8 100644 --- a/src/core/webview/index.ts +++ b/src/core/webview/index.ts @@ -10,6 +10,7 @@ import path from "node:path" import { WebviewProviderType } from "@/shared/webview/types" import { sendThemeEvent } from "@core/controller/ui/subscribeToTheme" import { v4 as uuidv4 } from "uuid" +import { sendDidBecomeVisibleEvent } from "../controller/ui/subscribeToDidBecomeVisible" /* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts @@ -113,12 +114,9 @@ export class WebviewProvider implements vscode.WebviewViewProvider { // WebviewView and WebviewPanel have all the same properties except for this visibility listener // panel webviewView.onDidChangeViewState( - () => { + async () => { if (this.view?.visible) { - this.controller.postMessageToWebview({ - type: "action", - action: "didBecomeVisible", - }) + await sendDidBecomeVisibleEvent(this.controller.id) } }, null, @@ -127,12 +125,9 @@ export class WebviewProvider implements vscode.WebviewViewProvider { } else if ("onDidChangeVisibility" in webviewView) { // sidebar webviewView.onDidChangeVisibility( - () => { + async () => { if (this.view?.visible) { - this.controller.postMessageToWebview({ - type: "action", - action: "didBecomeVisible", - }) + await sendDidBecomeVisibleEvent(this.controller.id) } }, null, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index b402bb666e..c75e80e0cf 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -25,7 +25,7 @@ export interface ExtensionMessage { | "userCreditsPayments" | "grpc_response" // New type for gRPC responses text?: string - action?: "didBecomeVisible" | "accountLogoutClicked" + action?: "accountLogoutClicked" state?: ExtensionState images?: string[] files?: string[] diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 3ee5ddd8c6..4aa6b9b323 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -675,27 +675,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie const shouldDisableFilesAndImages = selectedImages.length + selectedFiles.length >= MAX_IMAGES_AND_FILES_PER_MESSAGE - const handleMessage = useCallback( - (e: MessageEvent) => { - const message: ExtensionMessage = e.data - switch (message.type) { - case "action": - switch (message.action!) { - case "didBecomeVisible": - if (!isHidden && !sendingDisabled && !enableButtons) { - textAreaRef.current?.focus() - } - break - } - break - } - // textAreaRef.current is not explicitly required here since react guarantees that ref will be stable across re-renders, and we're not using its value but its reference. - }, - [isHidden, sendingDisabled, enableButtons, handleSendMessage, handlePrimaryButtonClick, handleSecondaryButtonClick], - ) - - useEvent("message", handleMessage) - // Listen for local focusChatInput event useEffect(() => { const handleFocusChatInput = () => { diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 404ebcff0f..be3fef1514 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -265,6 +265,7 @@ export const ExtensionStateContextProvider: React.FC<{ } }, []) const mcpServersSubscriptionRef = useRef<(() => void) | null>(null) + const didBecomeVisibleUnsubscribeRef = useRef<(() => void) | null>(null) // Subscribe to state updates and UI events using the gRPC streaming API useEffect(() => { @@ -395,6 +396,18 @@ export const ExtensionStateContextProvider: React.FC<{ onComplete: () => {}, }) + // Subscribe to didBecomeVisible events + didBecomeVisibleUnsubscribeRef.current = UiServiceClient.subscribeToDidBecomeVisible(EmptyRequest.create({}), { + onResponse: () => { + console.log("[DEBUG] Received didBecomeVisible event from gRPC stream") + window.dispatchEvent(new CustomEvent("focusChatInput")) + }, + onError: (error) => { + console.error("Error in didBecomeVisible subscription:", error) + }, + onComplete: () => {}, + }) + // Subscribe to MCP servers updates mcpServersSubscriptionRef.current = McpServiceClient.subscribeToMcpServers(EmptyRequest.create(), { onResponse: (response) => { @@ -649,6 +662,10 @@ export const ExtensionStateContextProvider: React.FC<{ mcpServersSubscriptionRef.current() mcpServersSubscriptionRef.current = null } + if (didBecomeVisibleUnsubscribeRef.current) { + didBecomeVisibleUnsubscribeRef.current() + didBecomeVisibleUnsubscribeRef.current = null + } } }, [])