Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/tmux-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class TmuxManager {
["status", "off"],
["set-titles", "off"],
["allow-rename", "off"],
["mouse", "off"],
["mouse", "on"], // Enable mouse mode for scroll handling
["focus-events", "on"],
["history-limit", "200000"],
["remain-on-exit", "off"],
Expand Down
19 changes: 13 additions & 6 deletions apps/desktop/src/main/lib/window-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class WindowManager {
private windowWorkspaces: Map<BrowserWindow, string | null> = new Map();
private restoredWindowIds: Set<number> = new Set();

async createWindow(
restoreState?: { workspaceId: string | null; bounds?: Electron.Rectangle },
): Promise<BrowserWindow> {
async createWindow(restoreState?: {
workspaceId: string | null;
bounds?: Electron.Rectangle;
}): Promise<BrowserWindow> {
const window = await MainWindow();

// Restore window bounds if provided
Expand Down Expand Up @@ -55,7 +56,7 @@ class WindowManager {
// Save final state before closing (window is still valid here)
// Get workspace ID from our map before window might be destroyed
const workspaceId = this.windowWorkspaces.get(window) ?? null;

try {
if (!window.isDestroyed()) {
const bounds = window.getBounds();
Expand All @@ -77,7 +78,10 @@ class WindowManager {
} catch (error) {
// Silently fail if window is destroyed - we'll clean up in closed handler
if (!(error instanceof Error && error.message.includes("destroyed"))) {
console.error("[WindowManager] Failed to save window state on close:", error);
console.error(
"[WindowManager] Failed to save window state on close:",
error,
);
}
}
});
Expand Down Expand Up @@ -109,7 +113,10 @@ class WindowManager {
return this.windowWorkspaces.get(window) ?? null;
}

setWorkspaceForWindow(window: BrowserWindow, workspaceId: string | null): void {
setWorkspaceForWindow(
window: BrowserWindow,
workspaceId: string | null,
): void {
this.windowWorkspaces.set(window, workspaceId);
// Persist the workspace association
windowStateManager.saveWindowState(window, workspaceId);
Expand Down
6 changes: 4 additions & 2 deletions apps/desktop/src/main/lib/window-state-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ class WindowStateManager {

this.write(state);
} catch (error) {
console.error("[WindowStateManager] Failed to save window state by ID:", error);
console.error(
"[WindowStateManager] Failed to save window state by ID:",
error,
);
}
}

Expand All @@ -157,4 +160,3 @@ class WindowStateManager {
}

export default WindowStateManager.getInstance();

6 changes: 4 additions & 2 deletions apps/desktop/src/main/lib/workspace-ipcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import type {
} from "shared/types";

import configManager from "./config-manager";
import windowManager from "./window-manager";
import workspaceManager from "./workspace-manager";
import worktreeManager from "./worktree-manager";
import windowManager from "./window-manager";

export function registerWorkspaceIPCs() {
// Open repository dialog
Expand Down Expand Up @@ -627,7 +627,9 @@ export function registerWorkspaceIPCs() {

// Detect main branch instead of using workspace.branch
// This ensures we compare against main/master, not a feature branch
const mainBranch = await worktreeManager.detectMainBranch(workspace.repoPath);
const mainBranch = await worktreeManager.detectMainBranch(
workspace.repoPath,
);

return await worktreeManager.getGitDiffFile(
worktree.path,
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/main/lib/worktree-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,10 @@ class WorktreeManager {
const oldPath = parts[2]; // For renamed files

// Check if file should be excluded
if (this.shouldExcludeFile(filePath) || (oldPath && this.shouldExcludeFile(oldPath))) {
if (
this.shouldExcludeFile(filePath) ||
(oldPath && this.shouldExcludeFile(oldPath))
) {
continue;
}

Expand Down
11 changes: 6 additions & 5 deletions apps/desktop/src/renderer/contexts/AppProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type React from "react";
import { useState } from "react";
import {
WorkspaceProvider,
TabProvider,
SidebarProvider,
WorktreeOperationsProvider,
TabProvider,
TaskProvider,
WorkspaceProvider,
WorktreeOperationsProvider,
} from "./index";

interface AppProvidersProps {
Expand All @@ -15,7 +15,9 @@ interface AppProvidersProps {
export function AppProviders({ children }: AppProvidersProps) {
// Tab selection state needs to be lifted to AppProviders level
// so WorkspaceProvider can use it
const [selectedWorktreeId, setSelectedWorktreeId] = useState<string | null>(null);
const [selectedWorktreeId, setSelectedWorktreeId] = useState<string | null>(
null,
);
const [selectedTabId, setSelectedTabId] = useState<string | null>(null);

return (
Expand All @@ -38,4 +40,3 @@ export function AppProviders({ children }: AppProvidersProps) {
</WorkspaceProvider>
);
}

1 change: 0 additions & 1 deletion apps/desktop/src/renderer/contexts/SidebarContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,3 @@ export function useSidebarContext() {
}
return context;
}

9 changes: 2 additions & 7 deletions apps/desktop/src/renderer/contexts/TabContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, useContext } from "react";
import type React from "react";
import { createContext, useContext } from "react";
import type { Tab, Worktree } from "shared/types";
import { useTabs } from "../screens/main/hooks";
import { useWorkspaceContext } from "./WorkspaceContext";
Expand Down Expand Up @@ -45,11 +45,7 @@ export function TabProvider({
setSelectedTabId,
});

return (
<TabContext.Provider value={tabData}>
{children}
</TabContext.Provider>
);
return <TabContext.Provider value={tabData}>{children}</TabContext.Provider>;
}

export function useTabContext() {
Expand All @@ -59,4 +55,3 @@ export function useTabContext() {
}
return context;
}

9 changes: 3 additions & 6 deletions apps/desktop/src/renderer/contexts/TaskContext.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type React from "react";
import { createContext, useContext } from "react";
import type { TaskStatus } from "../screens/main/components/Layout/StatusIndicator";
import type { PendingWorktree, UITask } from "../screens/main/types";
import { useTasks } from "../screens/main/hooks";
import { useWorkspaceContext } from "./WorkspaceContext";
import type { PendingWorktree, UITask } from "../screens/main/types";
import { useTabContext } from "./TabContext";
import { useWorkspaceContext } from "./WorkspaceContext";
import { useWorktreeOperationsContext } from "./WorktreeOperationsContext";

interface TaskContextValue {
Expand Down Expand Up @@ -50,9 +50,7 @@ export function TaskProvider({ children }: TaskProviderProps) {
});

return (
<TaskContext.Provider value={taskData}>
{children}
</TaskContext.Provider>
<TaskContext.Provider value={taskData}>{children}</TaskContext.Provider>
);
}

Expand All @@ -63,4 +61,3 @@ export function useTaskContext() {
}
return context;
}

70 changes: 35 additions & 35 deletions apps/desktop/src/renderer/contexts/WorkspaceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,53 @@ import type { Workspace } from "shared/types";
import { useWorkspace } from "../screens/main/hooks";

interface WorkspaceContextValue {
workspaces: Workspace[] | null;
currentWorkspace: Workspace | null;
setCurrentWorkspace: React.Dispatch<React.SetStateAction<Workspace | null>>;
setWorkspaces: React.Dispatch<React.SetStateAction<Workspace[] | null>>;
loading: boolean;
error: string | null;
showWorkspaceSelection: boolean;
setShowWorkspaceSelection: React.Dispatch<React.SetStateAction<boolean>>;
loadAllWorkspaces: () => Promise<void>;
handleWorkspaceSelect: (workspaceId: string) => Promise<void>;
handleWorkspaceSelectFromModal: (workspaceId: string) => Promise<void>;
handleCreateWorkspaceFromModal: () => Promise<void>;
workspaces: Workspace[] | null;
currentWorkspace: Workspace | null;
setCurrentWorkspace: React.Dispatch<React.SetStateAction<Workspace | null>>;
setWorkspaces: React.Dispatch<React.SetStateAction<Workspace[] | null>>;
loading: boolean;
error: string | null;
showWorkspaceSelection: boolean;
setShowWorkspaceSelection: React.Dispatch<React.SetStateAction<boolean>>;
loadAllWorkspaces: () => Promise<void>;
handleWorkspaceSelect: (workspaceId: string) => Promise<void>;
handleWorkspaceSelectFromModal: (workspaceId: string) => Promise<void>;
handleCreateWorkspaceFromModal: () => Promise<void>;
}

const WorkspaceContext = createContext<WorkspaceContextValue | undefined>(
undefined,
undefined,
);

interface WorkspaceProviderProps {
children: React.ReactNode;
setSelectedWorktreeId?: (id: string | null) => void;
setSelectedTabId?: (id: string | null) => void;
children: React.ReactNode;
setSelectedWorktreeId?: (id: string | null) => void;
setSelectedTabId?: (id: string | null) => void;
}

export function WorkspaceProvider({
children,
setSelectedWorktreeId,
setSelectedTabId,
children,
setSelectedWorktreeId,
setSelectedTabId,
}: WorkspaceProviderProps) {
const workspaceData = useWorkspace({
setSelectedWorktreeId,
setSelectedTabId,
});
const workspaceData = useWorkspace({
setSelectedWorktreeId,
setSelectedTabId,
});

return (
<WorkspaceContext.Provider value={workspaceData}>
{children}
</WorkspaceContext.Provider>
);
return (
<WorkspaceContext.Provider value={workspaceData}>
{children}
</WorkspaceContext.Provider>
);
}

export function useWorkspaceContext() {
const context = useContext(WorkspaceContext);
if (context === undefined) {
throw new Error(
"useWorkspaceContext must be used within a WorkspaceProvider",
);
}
return context;
const context = useContext(WorkspaceContext);
if (context === undefined) {
throw new Error(
"useWorkspaceContext must be used within a WorkspaceProvider",
);
}
return context;
}
32 changes: 22 additions & 10 deletions apps/desktop/src/renderer/contexts/WorktreeOperationsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,40 @@ import type React from "react";
import { createContext, useContext } from "react";
import type { Worktree } from "shared/types";
import { useWorktrees } from "../screens/main/hooks";
import { useWorkspaceContext } from "./WorkspaceContext";
import { useTabContext } from "./TabContext";
import { useWorkspaceContext } from "./WorkspaceContext";

interface WorktreeOperationsContextValue {
handleWorktreeCreated: () => Promise<void>;
handleWorktreeCreatedWithResult: () => Promise<{ id: string; worktrees?: Worktree[] } | null>;
handleWorktreeCreatedWithResult: () => Promise<{
id: string;
worktrees?: Worktree[];
} | null>;
handleUpdateWorktree: (worktreeId: string, updatedWorktree: Worktree) => void;
handleCreatePR: (selectedWorktreeId: string | null) => Promise<void>;
handleMergePR: (selectedWorktreeId: string | null) => Promise<void>;
handleDeleteWorktree: (worktreeId: string) => Promise<void>;
}

const WorktreeOperationsContext = createContext<WorktreeOperationsContextValue | undefined>(
undefined,
);
const WorktreeOperationsContext = createContext<
WorktreeOperationsContextValue | undefined
>(undefined);

interface WorktreeOperationsProviderProps {
children: React.ReactNode;
}

export function WorktreeOperationsProvider({ children }: WorktreeOperationsProviderProps) {
const { currentWorkspace, setCurrentWorkspace, setWorkspaces, loadAllWorkspaces } = useWorkspaceContext();
const { selectedWorktreeId, setSelectedWorktreeId, setSelectedTabId } = useTabContext();
export function WorktreeOperationsProvider({
children,
}: WorktreeOperationsProviderProps) {
const {
currentWorkspace,
setCurrentWorkspace,
setWorkspaces,
loadAllWorkspaces,
} = useWorkspaceContext();
const { selectedWorktreeId, setSelectedWorktreeId, setSelectedTabId } =
useTabContext();

const worktreeOperations = useWorktrees({
currentWorkspace,
Expand All @@ -46,8 +57,9 @@ export function WorktreeOperationsProvider({ children }: WorktreeOperationsProvi
export function useWorktreeOperationsContext() {
const context = useContext(WorktreeOperationsContext);
if (context === undefined) {
throw new Error("useWorktreeOperationsContext must be used within a WorktreeOperationsProvider");
throw new Error(
"useWorktreeOperationsContext must be used within a WorktreeOperationsProvider",
);
}
return context;
}

12 changes: 7 additions & 5 deletions apps/desktop/src/renderer/contexts/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export { AppProviders } from "./AppProviders";
export { WorkspaceProvider, useWorkspaceContext } from "./WorkspaceContext";
export { TabProvider, useTabContext } from "./TabContext";
export { SidebarProvider, useSidebarContext } from "./SidebarContext";
export { WorktreeOperationsProvider, useWorktreeOperationsContext } from "./WorktreeOperationsContext";
export { TabProvider, useTabContext } from "./TabContext";
export { TaskProvider, useTaskContext } from "./TaskContext";
export { WorktreeProvider, useWorktree } from "./WorktreeContext";

export { useWorkspaceContext, WorkspaceProvider } from "./WorkspaceContext";
export { useWorktree, WorktreeProvider } from "./WorktreeContext";
export {
useWorktreeOperationsContext,
WorktreeOperationsProvider,
} from "./WorktreeOperationsContext";
21 changes: 15 additions & 6 deletions apps/desktop/src/renderer/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,24 @@
-webkit-font-smoothing: antialiased;
}

/* Ensure xterm terminal fills container height */
/* Fix xterm.js scrollbar positioning */
.xterm {
height: 100% !important;
width: 100% !important;
position: relative;
height: 100%;
width: 100%;
}

.xterm-screen {
height: 100% !important;
width: 100% !important;
.xterm .xterm-viewport {
overflow-y: auto;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}

.xterm .xterm-screen {
position: relative;
}

/* Hide scrollbar for workspace carousel and tabs */
Expand Down
Loading
Loading