Compare commits

...
Author SHA1 Message Date
celestial-vault 43eb5f448a add back event listener 2025-06-18 14:30:29 -07:00
celestial-vault feaab7718f Merge branch 'main' into migrate-didBecomeVisible-protobus 2025-06-18 13:53:26 -07:00
celestial-vault 20b91678ae fix proto linter issue 2025-06-12 11:43:08 -07:00
celestial-vault eac83ae396 merge conflicts 2025-06-12 11:42:47 -07:00
Elephant Lumps f134a28418 merge conflicts 2025-06-03 23:55:34 -07:00
Elephant Lumps b447c2b9c9 changeset 2025-06-02 19:06:19 -07:00
Elephant Lumps 99d3c79b6a migrate didBecomeVisible 2025-06-02 19:05:56 -07:00
Elephant Lumps 9a2672a0dc prettier 2025-06-02 18:33:32 -07:00
Elephant Lumps f1256ef53a merge conflicts 2025-06-02 18:33:15 -07:00
Elephant Lumps 462bca65f7 send targeted event to the controller 2025-06-02 18:27:21 -07:00
Elephant Lumps 6cdede57b7 merge conflicts 2025-06-02 14:42:00 -07:00
Elephant Lumps 1e767bcecf changeset 2025-05-27 11:34:33 -07:00
Elephant Lumps f26f6dc37c migrate chatButtonClicked 2025-05-27 11:34:09 -07:00
7 changed files with 94 additions and 32 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Migrate didBecomeVisible to protobus
+3
View File
@@ -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);
}
@@ -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<string, StreamingResponseHandler>()
/**
* 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<void> {
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<void> {
// 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)
}
}
+5 -10
View File
@@ -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,
+1 -1
View File
@@ -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[]
@@ -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 = () => {
@@ -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
}
}
}, [])