Project actions: run commands from header on web + mobile, with SSH-forward URL opening (#542)
* feat: add project actions in header with reliable terminal run flow - Add per-project Actions settings with icons, platform filters, and default action - Run/stop actions from header using terminal tabs, including Ctrl+C then force-kill fallback - Improve terminal UX with stable selection, resize handling, and smarter URL auto-open for localhost ports * feat: add project actions UI with refined header dropdown behavior * feat: add parent-session back button in chat * fix: make web and desktop dev modes reliable and conflict-free - Separate desktop and web dev ports to avoid collisions - Add robust process-tree shutdown so Ctrl+C cleans sidecars - Add true web HMR mode and keep service worker out of dev * fix: linux safe scripts for dev * feat: run project actions on web and add desktop SSH forward URL opening * feat: add mobile project actions button with terminal tabs and tighter tab UI * revert: remove experimental mobile terminal selection UI * feat: show Add action button in header when empty * fix: make retry countdown human-readable in status row * fix: prevent nav rail actions from firing through overlays
This commit is contained in:
committed by
GitHub
parent
d948aa5557
commit
95c71789c4
@@ -55,6 +55,7 @@ import type { GitHubAuthStatus } from '@/lib/api/types';
|
||||
import type { SessionContextUsage } from '@/stores/types/sessionTypes';
|
||||
import { DesktopHostSwitcherDialog } from '@/components/desktop/DesktopHostSwitcher';
|
||||
import { OpenInAppButton } from '@/components/desktop/OpenInAppButton';
|
||||
import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton';
|
||||
import { isDesktopShell, isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { desktopHostsGet, locationMatchesHost, redactSensitiveUrl } from '@/lib/desktopHosts';
|
||||
|
||||
@@ -165,12 +166,13 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
return state.messages.get(currentSessionId);
|
||||
});
|
||||
const sessions = useSessionStore((state) => state.sessions);
|
||||
const activeProjectLabel = useProjectsStore((state) => {
|
||||
const activeProject = useProjectsStore((state) => {
|
||||
if (!state.activeProjectId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeProject = state.projects.find((project) => project.id === state.activeProjectId);
|
||||
return state.projects.find((project) => project.id === state.activeProjectId) ?? null;
|
||||
});
|
||||
const activeProjectLabel = React.useMemo(() => {
|
||||
if (!activeProject) {
|
||||
return null;
|
||||
}
|
||||
@@ -182,7 +184,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
|
||||
const pathSegments = activeProject.path.split(/[\\/]/).filter(Boolean);
|
||||
return pathSegments[pathSegments.length - 1] ?? null;
|
||||
});
|
||||
}, [activeProject]);
|
||||
const quotaResults = useQuotaStore((state) => state.results);
|
||||
const fetchAllQuotas = useQuotaStore((state) => state.fetchAllQuotas);
|
||||
const isQuotaLoading = useQuotaStore((state) => state.isLoading);
|
||||
@@ -491,6 +493,17 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
return worktreeDirectory || sessionDirectory || draftDirectory;
|
||||
}, [draftDirectory, sessionDirectory, worktreeDirectory]);
|
||||
|
||||
const actionDirectory = React.useMemo(() => {
|
||||
return normalize(openDirectory || activeProject?.path || '');
|
||||
}, [activeProject?.path, openDirectory]);
|
||||
|
||||
const activeProjectRef = React.useMemo(() => {
|
||||
if (!activeProject) {
|
||||
return null;
|
||||
}
|
||||
return { id: activeProject.id, path: activeProject.path };
|
||||
}, [activeProject]);
|
||||
|
||||
|
||||
const [planTabAvailable, setPlanTabAvailable] = React.useState(false);
|
||||
const showPlanTab = planTabAvailable;
|
||||
@@ -967,6 +980,14 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeProjectRef && actionDirectory && (
|
||||
<ProjectActionsButton
|
||||
projectRef={activeProjectRef}
|
||||
directory={actionDirectory}
|
||||
className="mr-1"
|
||||
/>
|
||||
)}
|
||||
|
||||
{tabs.length > 0 && (
|
||||
<div className="flex items-center gap-1 rounded-lg bg-[var(--surface-muted)]/50 p-1">
|
||||
{tabs.map((tab) => renderTab(tab))}
|
||||
@@ -1505,6 +1526,15 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
{activeProjectRef && actionDirectory && (
|
||||
<ProjectActionsButton
|
||||
projectRef={activeProjectRef}
|
||||
directory={actionDirectory}
|
||||
compact
|
||||
allowMobile
|
||||
className="h-9"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Mobile Services Menu (Usage + MCP) */}
|
||||
<DropdownMenu
|
||||
|
||||
Reference in New Issue
Block a user