mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
fix(ui): open in terminal navigates to Automaker terminal view
Instead of opening the system terminal, the "Open in Terminal" action now opens Automaker's built-in terminal with the worktree directory: - Add pendingTerminalCwd state to app store - Update use-worktree-actions to set pending cwd and navigate to /terminal - Add effect in terminal-view to create session with pending cwd This matches the original PR #558 behavior.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { toast } from 'sonner';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import type { WorktreeInfo } from '../types';
|
||||
|
||||
const logger = createLogger('WorktreeActions');
|
||||
@@ -39,6 +41,8 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
|
||||
const [isPushing, setIsPushing] = useState(false);
|
||||
const [isSwitching, setIsSwitching] = useState(false);
|
||||
const [isActivating, setIsActivating] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const setPendingTerminalCwd = useAppStore((state) => state.setPendingTerminalCwd);
|
||||
|
||||
const handleSwitchBranch = useCallback(
|
||||
async (worktree: WorktreeInfo, branchName: string) => {
|
||||
@@ -143,23 +147,16 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleOpenInTerminal = useCallback(async (worktree: WorktreeInfo) => {
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api?.worktree?.openInTerminal) {
|
||||
logger.warn('Open in terminal API not available');
|
||||
return;
|
||||
}
|
||||
const result = await api.worktree.openInTerminal(worktree.path);
|
||||
if (result.success && result.result) {
|
||||
toast.success(result.result.message);
|
||||
} else if (result.error) {
|
||||
toast.error(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Open in terminal failed:', error);
|
||||
}
|
||||
}, []);
|
||||
const handleOpenInTerminal = useCallback(
|
||||
(worktree: WorktreeInfo) => {
|
||||
// Set the pending terminal cwd to the worktree path
|
||||
setPendingTerminalCwd(worktree.path);
|
||||
// Navigate to the terminal page
|
||||
navigate({ to: '/terminal' });
|
||||
logger.info('Opening terminal for worktree:', worktree.path);
|
||||
},
|
||||
[navigate, setPendingTerminalCwd]
|
||||
);
|
||||
|
||||
return {
|
||||
isPulling,
|
||||
|
||||
@@ -244,6 +244,7 @@ export function TerminalView() {
|
||||
setTerminalScrollbackLines,
|
||||
setTerminalScreenReaderMode,
|
||||
updateTerminalPanelSizes,
|
||||
setPendingTerminalCwd,
|
||||
} = useAppStore();
|
||||
|
||||
const [status, setStatus] = useState<TerminalStatus | null>(null);
|
||||
@@ -537,6 +538,100 @@ export function TerminalView() {
|
||||
}
|
||||
}, [terminalState.isUnlocked, fetchServerSettings]);
|
||||
|
||||
// Handle pending terminal cwd (from "open in terminal" action on worktree menu)
|
||||
// When pendingTerminalCwd is set and we're ready, create a terminal with that cwd
|
||||
const pendingTerminalCwdRef = useRef<string | null>(null);
|
||||
const pendingTerminalCreatedRef = useRef<boolean>(false);
|
||||
useEffect(() => {
|
||||
const pendingCwd = terminalState.pendingTerminalCwd;
|
||||
|
||||
// Skip if no pending cwd
|
||||
if (!pendingCwd) {
|
||||
// Reset the created ref when there's no pending cwd
|
||||
pendingTerminalCreatedRef.current = false;
|
||||
pendingTerminalCwdRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if we already created a terminal for this exact cwd
|
||||
if (pendingCwd === pendingTerminalCwdRef.current && pendingTerminalCreatedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if still loading or terminal not enabled
|
||||
if (loading || !status?.enabled) {
|
||||
logger.debug('Waiting for terminal to be ready before creating terminal for pending cwd');
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if password is required but not unlocked yet
|
||||
if (status.passwordRequired && !terminalState.isUnlocked) {
|
||||
logger.debug('Waiting for terminal unlock before creating terminal for pending cwd');
|
||||
return;
|
||||
}
|
||||
|
||||
// Track that we're processing this cwd
|
||||
pendingTerminalCwdRef.current = pendingCwd;
|
||||
|
||||
// Create a terminal with the pending cwd
|
||||
logger.info('Creating terminal from pending cwd:', pendingCwd);
|
||||
|
||||
// Create terminal with the specified cwd
|
||||
const createTerminalWithCwd = async () => {
|
||||
try {
|
||||
const headers: Record<string, string> = {};
|
||||
const authToken = useAppStore.getState().terminalState.authToken;
|
||||
if (authToken) {
|
||||
headers['X-Terminal-Token'] = authToken;
|
||||
}
|
||||
|
||||
const response = await apiFetch('/api/terminal/sessions', 'POST', {
|
||||
headers,
|
||||
body: { cwd: pendingCwd, cols: 80, rows: 24 },
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Mark as successfully created
|
||||
pendingTerminalCreatedRef.current = true;
|
||||
addTerminalToLayout(data.data.id);
|
||||
// Mark this session as new for running initial command
|
||||
if (defaultRunScript) {
|
||||
setNewSessionIds((prev) => new Set(prev).add(data.data.id));
|
||||
}
|
||||
fetchServerSettings();
|
||||
toast.success(`Opened terminal in ${pendingCwd.split('/').pop() || pendingCwd}`);
|
||||
// Clear the pending cwd after successful creation
|
||||
setPendingTerminalCwd(null);
|
||||
} else {
|
||||
logger.error('Failed to create session from pending cwd:', data.error);
|
||||
toast.error('Failed to open terminal', {
|
||||
description: data.error || 'Unknown error',
|
||||
});
|
||||
// Clear pending cwd on failure to prevent infinite retries
|
||||
setPendingTerminalCwd(null);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Create session error from pending cwd:', err);
|
||||
toast.error('Failed to open terminal');
|
||||
// Clear pending cwd on error to prevent infinite retries
|
||||
setPendingTerminalCwd(null);
|
||||
}
|
||||
};
|
||||
|
||||
createTerminalWithCwd();
|
||||
}, [
|
||||
terminalState.pendingTerminalCwd,
|
||||
terminalState.isUnlocked,
|
||||
loading,
|
||||
status?.enabled,
|
||||
status?.passwordRequired,
|
||||
setPendingTerminalCwd,
|
||||
addTerminalToLayout,
|
||||
defaultRunScript,
|
||||
fetchServerSettings,
|
||||
]);
|
||||
|
||||
// Handle project switching - save and restore terminal layouts
|
||||
// Uses terminalState.lastActiveProjectPath (persisted in store) instead of a local ref
|
||||
// This ensures terminals persist when navigating away from terminal route and back
|
||||
|
||||
@@ -531,6 +531,7 @@ export interface TerminalState {
|
||||
lineHeight: number; // Line height multiplier for terminal text
|
||||
maxSessions: number; // Maximum concurrent terminal sessions (server setting)
|
||||
lastActiveProjectPath: string | null; // Last project path to detect route changes vs project switches
|
||||
pendingTerminalCwd: string | null; // Pending cwd to use when creating next terminal (from "open in terminal" action)
|
||||
}
|
||||
|
||||
// Persisted terminal layout - now includes sessionIds for reconnection
|
||||
@@ -1229,6 +1230,7 @@ export interface AppActions {
|
||||
setTerminalLineHeight: (lineHeight: number) => void;
|
||||
setTerminalMaxSessions: (maxSessions: number) => void;
|
||||
setTerminalLastActiveProjectPath: (projectPath: string | null) => void;
|
||||
setPendingTerminalCwd: (cwd: string | null) => void;
|
||||
addTerminalTab: (name?: string) => string;
|
||||
removeTerminalTab: (tabId: string) => void;
|
||||
setActiveTerminalTab: (tabId: string) => void;
|
||||
@@ -1445,6 +1447,7 @@ const initialState: AppState = {
|
||||
lineHeight: 1.0,
|
||||
maxSessions: 100,
|
||||
lastActiveProjectPath: null,
|
||||
pendingTerminalCwd: null,
|
||||
},
|
||||
terminalLayoutByProject: {},
|
||||
specCreatingForProject: null,
|
||||
@@ -2893,6 +2896,9 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
maxSessions: current.maxSessions,
|
||||
// Preserve lastActiveProjectPath - it will be updated separately when needed
|
||||
lastActiveProjectPath: current.lastActiveProjectPath,
|
||||
// Preserve pendingTerminalCwd - this is set by "open in terminal" action and should
|
||||
// survive the clearTerminalState() call that happens during project switching
|
||||
pendingTerminalCwd: current.pendingTerminalCwd,
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -2984,6 +2990,13 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
||||
});
|
||||
},
|
||||
|
||||
setPendingTerminalCwd: (cwd) => {
|
||||
const current = get().terminalState;
|
||||
set({
|
||||
terminalState: { ...current, pendingTerminalCwd: cwd },
|
||||
});
|
||||
},
|
||||
|
||||
addTerminalTab: (name) => {
|
||||
const current = get().terminalState;
|
||||
const newTabId = `tab-${Date.now()}`;
|
||||
|
||||
Reference in New Issue
Block a user