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:
gsxdsm
2026-02-19 21:55:12 -08:00
committed by GitHub
parent ee52333636
commit 7df2182818
80 changed files with 4729 additions and 1107 deletions

View File

@@ -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>

View File

@@ -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>
);
}