Skip to content

Commit 2b23849

Browse files
authored
fix: spawn native terminals for local workspaces (#616)
1 parent 2046fa1 commit 2b23849

File tree

2 files changed

+62
-10
lines changed

2 files changed

+62
-10
lines changed

src/browser/api.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,22 @@ class WebSocketManager {
184184

185185
const wsManager = new WebSocketManager();
186186

187+
// Cache workspace metadata to avoid async lookup during user gestures (popup blocker issue)
188+
let workspaceMetadataCache: Array<{
189+
id: string;
190+
runtimeConfig?: { type: "local" | "ssh" };
191+
}> = [];
192+
193+
// Update cache when workspace metadata changes
194+
function updateWorkspaceCache() {
195+
void invokeIPC(IPC_CHANNELS.WORKSPACE_LIST).then((workspaces) => {
196+
workspaceMetadataCache = workspaces as typeof workspaceMetadataCache;
197+
});
198+
}
199+
200+
// Initialize cache
201+
updateWorkspaceCache();
202+
187203
// Create the Web API implementation
188204
const webApi: IPCApi = {
189205
tokenizer: {
@@ -240,7 +256,12 @@ const webApi: IPCApi = {
240256
},
241257

242258
onMetadata: (callback) => {
243-
return wsManager.on(IPC_CHANNELS.WORKSPACE_METADATA, callback as (data: unknown) => void);
259+
// Update cache whenever workspace metadata changes
260+
const unsubscribe = wsManager.on(IPC_CHANNELS.WORKSPACE_METADATA, (data: unknown) => {
261+
updateWorkspaceCache();
262+
callback(data as Parameters<typeof callback>[0]);
263+
});
264+
return unsubscribe;
244265
},
245266
},
246267
window: {
@@ -268,10 +289,18 @@ const webApi: IPCApi = {
268289
return wsManager.on(channel, callback as (data: unknown) => void);
269290
},
270291
openWindow: (workspaceId) => {
271-
// In browser mode, open a new window/tab with the terminal page
272-
// Use a unique name with timestamp to create a new window each time
273-
const url = `/terminal.html?workspaceId=${encodeURIComponent(workspaceId)}`;
274-
window.open(url, `terminal-${workspaceId}-${Date.now()}`, "width=1000,height=600");
292+
// Check workspace runtime type using cached metadata (synchronous)
293+
// This must be synchronous to avoid popup blocker during user gesture
294+
const workspace = workspaceMetadataCache.find((ws) => ws.id === workspaceId);
295+
const isSSH = workspace?.runtimeConfig?.type === "ssh";
296+
297+
if (isSSH) {
298+
// SSH workspace - open browser tab with terminal UI (must be synchronous)
299+
const url = `/terminal.html?workspaceId=${encodeURIComponent(workspaceId)}`;
300+
window.open(url, `terminal-${workspaceId}-${Date.now()}`, "width=1000,height=600");
301+
}
302+
// For local workspaces, the IPC handler will open a native terminal
303+
275304
return invokeIPC(IPC_CHANNELS.TERMINAL_WINDOW_OPEN, workspaceId);
276305
},
277306
closeWindow: (workspaceId) => invokeIPC(IPC_CHANNELS.TERMINAL_WINDOW_CLOSE, workspaceId),

src/services/ipcMain.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,12 +1565,35 @@ export class IpcMain {
15651565
ipcMain.handle(IPC_CHANNELS.TERMINAL_WINDOW_OPEN, async (_event, workspaceId: string) => {
15661566
console.log(`[BACKEND] TERMINAL_WINDOW_OPEN handler called with: ${workspaceId}`);
15671567
try {
1568-
if (!this.terminalWindowManager) {
1569-
throw new Error("Terminal window manager not available (desktop mode only)");
1568+
// Look up workspace to determine runtime type
1569+
const allMetadata = await this.config.getAllWorkspaceMetadata();
1570+
const workspace = allMetadata.find((w) => w.id === workspaceId);
1571+
1572+
if (!workspace) {
1573+
log.error(`Workspace not found: ${workspaceId}`);
1574+
throw new Error(`Workspace not found: ${workspaceId}`);
15701575
}
1571-
log.info(`Opening terminal window for workspace: ${workspaceId}`);
1572-
await this.terminalWindowManager.openTerminalWindow(workspaceId);
1573-
log.info(`Terminal window opened successfully for workspace: ${workspaceId}`);
1576+
1577+
const runtimeConfig = workspace.runtimeConfig;
1578+
1579+
// For local workspaces, use native terminal (both desktop and browser mode)
1580+
// For SSH workspaces, use ghostty-web (desktop) or browser terminal (browser mode)
1581+
if (!isSSHRuntime(runtimeConfig)) {
1582+
// Local workspace - use native terminal
1583+
log.info(`Opening native terminal for local workspace: ${workspaceId}`);
1584+
await this.openTerminal({ type: "local", workspacePath: workspace.namedWorkspacePath });
1585+
} else if (this.terminalWindowManager) {
1586+
// SSH workspace in desktop mode - use ghostty-web Electron window
1587+
log.info(`Opening ghostty-web terminal for SSH workspace: ${workspaceId}`);
1588+
await this.terminalWindowManager.openTerminalWindow(workspaceId);
1589+
} else {
1590+
// SSH workspace in browser mode - let browser handle it
1591+
log.info(
1592+
`Browser mode: terminal UI handled by browser for SSH workspace: ${workspaceId}`
1593+
);
1594+
}
1595+
1596+
log.info(`Terminal opened successfully for workspace: ${workspaceId}`);
15741597
} catch (err) {
15751598
log.error("Error opening terminal window:", err);
15761599
throw err;

0 commit comments

Comments
 (0)