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:
Bohdan Triapitsyn
2026-02-27 20:45:03 +02:00
committed by GitHub
parent d948aa5557
commit 95c71789c4
26 changed files with 2572 additions and 148 deletions
@@ -9,6 +9,7 @@ import { useUIStore } from '@/stores/useUIStore';
import { PROJECT_COLORS, PROJECT_ICONS, PROJECT_COLOR_MAP as COLOR_MAP, getProjectIconImageUrl } from '@/lib/projectMeta';
import { RiCloseLine } from '@remixicon/react';
import { WorktreeSectionContent } from '@/components/sections/openchamber/WorktreeSectionContent';
import { ProjectActionsSection } from '@/components/sections/projects/ProjectActionsSection';
export const ProjectsPage: React.FC = () => {
const projects = useProjectsStore((state) => state.projects);
@@ -45,6 +46,13 @@ export const ProjectsPage: React.FC = () => {
const [previewImageFailed, setPreviewImageFailed] = React.useState(false);
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
const selectedProjectRef = React.useMemo(() => {
if (!selectedProject) {
return null;
}
return { id: selectedProject.id, path: selectedProject.path };
}, [selectedProject]);
React.useEffect(() => {
if (!selectedProject) {
setName('');
@@ -149,7 +157,6 @@ export const ProjectsPage: React.FC = () => {
</ScrollableOverlay>
);
}
return (
<ScrollableOverlay keyboardAvoid outerClassName="h-full" className="w-full bg-background">
<div className="mx-auto w-full max-w-4xl p-3 sm:p-6 sm:pt-8">
@@ -370,6 +377,13 @@ export const ProjectsPage: React.FC = () => {
</div>
</div>
{/* Worktree Group */}
<div className="mb-8">
<section className="px-2 pb-2 pt-0">
{selectedProjectRef && <ProjectActionsSection projectRef={selectedProjectRef} />}
</section>
</div>
{/* Worktree Group */}
<div className="mb-8">
<div className="mb-1 px-1">
@@ -378,7 +392,7 @@ export const ProjectsPage: React.FC = () => {
</h3>
</div>
<section className="px-2 pb-2 pt-0">
<WorktreeSectionContent projectRef={{ id: selectedProject.id, path: selectedProject.path }} />
{selectedProjectRef && <WorktreeSectionContent projectRef={selectedProjectRef} />}
</section>
</div>