mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-19 10:43:08 +00:00
Improve pull request flow, add branch selection for worktree creation, fix auto-mode concurrency count (#787)
* Changes from fix/fetch-before-pull-fetch * feat: Improve pull request flow, add branch selection for worktree creation, fix for automode concurrency count * feat: Add validation for remote names and improve error handling * Address PR comments and mobile layout fixes * ``` refactor: Extract PR target resolution logic into dedicated service ``` * feat: Add app shell UI and improve service imports. Address PR comments * fix: Improve security validation and cache handling in git operations * feat: Add GET /list endpoint and improve parameter handling * chore: Improve validation, accessibility, and error handling across apps * chore: Format vite server port configuration * fix: Add error handling for gh pr list command and improve offline fallbacks * fix: Preserve existing PR creation time and improve remote handling
This commit is contained in:
@@ -59,6 +59,7 @@ import {
|
||||
applyStickyModifier,
|
||||
type StickyModifier,
|
||||
} from './sticky-modifier-keys';
|
||||
import { TerminalScriptsDropdown } from './terminal-scripts-dropdown';
|
||||
|
||||
const logger = createLogger('Terminal');
|
||||
const NO_STORE_CACHE_MODE: RequestCache = 'no-store';
|
||||
@@ -156,6 +157,11 @@ export function TerminalPanel({
|
||||
const [isImageDragOver, setIsImageDragOver] = useState(false);
|
||||
const [isProcessingImage, setIsProcessingImage] = useState(false);
|
||||
const hasRunInitialCommandRef = useRef(false);
|
||||
// Tracks whether the connected shell is a Windows shell (PowerShell, cmd, etc.).
|
||||
// Maintained as a ref (not state) so sendCommand can read the current value without
|
||||
// causing unnecessary re-renders or stale closure issues. Set inside ws.onmessage
|
||||
// when the 'connected' message is received (see isWindowsShell detection below).
|
||||
const isWindowsShellRef = useRef(false);
|
||||
const searchAddonRef = useRef<XSearchAddon | null>(null);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const [showSearch, setShowSearch] = useState(false);
|
||||
@@ -376,6 +382,17 @@ export function TerminalPanel({
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Send a command to the terminal (types the command and presses Enter).
|
||||
// Uses isWindowsShellRef.current to pick the correct line ending:
|
||||
// Windows shells (PowerShell, cmd) expect '\r\n'; Unix/macOS shells expect '\n'.
|
||||
// isWindowsShellRef is set in ws.onmessage when the 'connected' message arrives.
|
||||
const sendCommand = useCallback((command: string) => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
const lineEnding = isWindowsShellRef.current ? '\r\n' : '\n';
|
||||
wsRef.current.send(JSON.stringify({ type: 'input', data: command + lineEnding }));
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Paste from clipboard
|
||||
const pasteFromClipboard = useCallback(async () => {
|
||||
const terminal = xtermRef.current;
|
||||
@@ -1090,6 +1107,9 @@ export function TerminalPanel({
|
||||
shellPath.includes('powershell') ||
|
||||
shellPath.includes('pwsh') ||
|
||||
shellPath.includes('cmd.exe');
|
||||
// Keep the component-level ref in sync so sendCommand and
|
||||
// runCommandOnConnect both use the correct line ending ('\r\n' vs '\n').
|
||||
isWindowsShellRef.current = isWindowsShell;
|
||||
const isPowerShell = shellPath.includes('powershell') || shellPath.includes('pwsh');
|
||||
|
||||
if (msg.shell) {
|
||||
@@ -1903,6 +1923,12 @@ export function TerminalPanel({
|
||||
<ZoomIn className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
{/* Quick scripts dropdown */}
|
||||
<TerminalScriptsDropdown
|
||||
onRunCommand={sendCommand}
|
||||
isConnected={connectionStatus === 'connected'}
|
||||
/>
|
||||
|
||||
{/* Settings popover */}
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { ScrollText, Play, Settings2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useProjectSettings } from '@/hooks/queries';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DEFAULT_TERMINAL_SCRIPTS } from '../project-settings-view/terminal-scripts-constants';
|
||||
|
||||
interface TerminalScriptsDropdownProps {
|
||||
/** Callback to send a command + newline to the terminal */
|
||||
onRunCommand: (command: string) => void;
|
||||
/** Whether the terminal is connected and ready */
|
||||
isConnected: boolean;
|
||||
/** Optional callback to navigate to project settings scripts section */
|
||||
onOpenSettings?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dropdown menu in the terminal header bar that provides quick-access
|
||||
* to user-configured project scripts. Clicking a script inserts the
|
||||
* command into the terminal and presses Enter.
|
||||
*/
|
||||
export function TerminalScriptsDropdown({
|
||||
onRunCommand,
|
||||
isConnected,
|
||||
onOpenSettings,
|
||||
}: TerminalScriptsDropdownProps) {
|
||||
const currentProject = useAppStore((state) => state.currentProject);
|
||||
const { data: projectSettings } = useProjectSettings(currentProject?.path);
|
||||
|
||||
// Use project-configured scripts or fall back to defaults
|
||||
const scripts = useMemo(() => {
|
||||
const configured = projectSettings?.terminalScripts;
|
||||
if (configured && configured.length > 0) {
|
||||
return configured;
|
||||
}
|
||||
return DEFAULT_TERMINAL_SCRIPTS;
|
||||
}, [projectSettings?.terminalScripts]);
|
||||
|
||||
const handleRunScript = useCallback(
|
||||
(command: string) => {
|
||||
if (!isConnected) return;
|
||||
onRunCommand(command);
|
||||
},
|
||||
[isConnected, onRunCommand]
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
title="Quick Scripts"
|
||||
disabled={!isConnected}
|
||||
>
|
||||
<ScrollText className="h-3 w-3" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
side="bottom"
|
||||
className="w-56"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal">
|
||||
Quick Scripts
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{scripts.map((script) => (
|
||||
<DropdownMenuItem
|
||||
key={script.id}
|
||||
onClick={() => handleRunScript(script.command)}
|
||||
disabled={!isConnected}
|
||||
className="gap-2"
|
||||
>
|
||||
<Play className={cn('h-3.5 w-3.5 shrink-0 text-brand-500')} />
|
||||
<div className="flex flex-col min-w-0 flex-1">
|
||||
<span className="text-sm truncate">{script.name}</span>
|
||||
<span className="text-[10px] text-muted-foreground font-mono truncate">
|
||||
{script.command}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
{onOpenSettings && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={onOpenSettings} className="gap-2 text-muted-foreground">
|
||||
<Settings2 className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="text-sm">Configure Scripts...</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user