Files
openchamber/packages/ui/src/lib/worktreeSessionCreator.ts
T
00821700de chore: remove dead code (59 unused files + ~125 unused exports) (#1835)
* chore: remove dead/unreferenced files across ui, vscode

Remove 59 unused source files (components, hooks, lib utils, stores,
barrels, and orphaned vscode github modules) that are not imported by
any entry-reachable code. Also drop a stale test mock for the removed
execCommands module.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* refactor: remove unused exported symbols (types, functions, consts, hooks)

Remove exported symbols whose identifier is referenced nowhere in the
repository (verified via repo-wide search), across ui types/contracts,
lib utilities, sync layer, stores, and components. Also drop the few
imports/private helpers orphaned by these removals.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* refactor: remove more unused exports (desktop, shortcuts, worktree, vscode)

Continue removing repo-wide unreferenced exported functions, consts and
types across lib/desktop, shortcuts, worktreeSessionCreator, sync, and
vscode gitService, with cascading orphaned helpers/imports cleaned up.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* chore: add dead-code cleanup tooling

* refactor: checkpoint dead-code cleanup

* refactor: remove dead-code suppressions

---------

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-06-26 19:27:53 +03:00

390 lines
13 KiB
TypeScript

/**
* Utilities for creating worktrees and, when needed, sessions bound to them.
* This is a standalone entrypoint for keyboard shortcuts, menu actions,
* and other non-hook contexts.
*/
import { toast } from '@/components/ui';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useConfigStore } from '@/stores/useConfigStore';
import { useContextStore } from '@/stores/contextStore';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { checkIsGitRepository, previewGitWorktree } from '@/lib/gitApi';
import { generateBranchName } from '@/lib/git/branchNameGenerator';
import { parseModelIdentifier } from '@/lib/modelIdentifier';
import { getRootBranch } from '@/lib/worktrees/worktreeStatus';
import { getWorktreeSetupCommands, getWorktreeSetupWaitEnabled } from '@/lib/openchamberConfig';
import {
removeProjectWorktree,
type ProjectRef,
} from '@/lib/worktrees/worktreeManager';
import { createWorktreeWithDefaults } from '@/lib/worktrees/worktreeCreate';
import {
createPendingDraftWorktreeRequest,
rejectPendingDraftWorktreeRequest,
resolvePendingDraftWorktreeRequest,
} from '@/lib/worktrees/pendingDraftWorktree';
import { waitForWorktreeBootstrap } from '@/lib/worktrees/worktreeBootstrap';
const normalizePath = (value: string): string => value.replace(/\\/g, '/').replace(/\/+$/, '') || value;
const waitForWorktreeBootstrapIfEnabled = async (project: ProjectRef, directory: string): Promise<void> => {
if (await getWorktreeSetupWaitEnabled(project)) {
await waitForWorktreeBootstrap(directory);
}
};
const resolveProjectRef = (directory: string): ProjectRef | null => {
const normalized = normalizePath(directory);
const projects = useProjectsStore.getState().projects;
if (projects.length === 0) {
return null;
}
const activeProject = useProjectsStore.getState().getActiveProject();
if (activeProject?.path) {
const activePath = normalizePath(activeProject.path);
if (normalized === activePath || normalized.startsWith(`${activePath}/`)) {
return { id: activeProject.id, path: activeProject.path };
}
}
const matches = projects.filter((project) => {
const projectPath = normalizePath(project.path);
return normalized === projectPath || normalized.startsWith(`${projectPath}/`);
});
const match = matches.sort((a, b) => normalizePath(b.path).length - normalizePath(a.path).length)[0];
return match ? { id: match.id, path: match.path } : null;
};
// Track if a worktree creation flow is already running
let isCreatingWorktreeSession = false;
const applyDefaultAgentAndModelSelection = (sessionId: string, configState = useConfigStore.getState()) => {
try {
const visibleAgents = configState.getVisibleAgents();
let agentName: string | undefined;
if (configState.settingsDefaultAgent) {
const settingsAgent = visibleAgents.find((a) => a.name === configState.settingsDefaultAgent);
if (settingsAgent) {
agentName = settingsAgent.name;
}
}
if (!agentName) {
agentName =
visibleAgents.find((agent) => agent.name === 'build')?.name ||
visibleAgents[0]?.name;
}
if (!agentName) {
return;
}
configState.setAgent(agentName);
useContextStore.getState().saveSessionAgentSelection(sessionId, agentName);
const settingsDefaultModel = configState.settingsDefaultModel;
if (!settingsDefaultModel) {
return;
}
const parsed = parseModelIdentifier(settingsDefaultModel);
if (!parsed) {
return;
}
const { providerId, modelId } = parsed;
const modelMetadata = configState.getModelMetadata(providerId, modelId);
if (!modelMetadata) {
return;
}
useContextStore.getState().saveSessionModelSelection(sessionId, providerId, modelId);
useContextStore.getState().saveAgentModelForSession(sessionId, agentName, providerId, modelId);
const settingsDefaultVariant = configState.settingsDefaultVariant;
if (!settingsDefaultVariant) {
return;
}
const provider = configState.providers.find((p) => p.id === providerId);
const model = provider?.models.find((m: Record<string, unknown>) => (m as { id?: string }).id === modelId) as
| { variants?: Record<string, unknown> }
| undefined;
const variants = model?.variants;
if (variants && Object.prototype.hasOwnProperty.call(variants, settingsDefaultVariant)) {
configState.setCurrentVariant(settingsDefaultVariant);
useContextStore
.getState()
.saveAgentModelVariantForSession(sessionId, agentName, providerId, modelId, settingsDefaultVariant);
}
} catch {
// Ignore errors setting default agent
}
};
const initializeSessionForWorktree = (sessionId: string, metadata: {
path: string;
projectDirectory: string;
branch: string;
label: string;
name?: string;
createdFromBranch?: string;
kind?: 'pr' | 'standard';
}) => {
const sessionStore = useSessionUIStore.getState();
const configState = useConfigStore.getState();
sessionStore.initializeNewOpenChamberSession(sessionId, configState.agents);
sessionStore.setSessionDirectory(sessionId, metadata.path);
sessionStore.setWorktreeMetadata(sessionId, metadata);
applyDefaultAgentAndModelSelection(sessionId, configState);
useDirectoryStore.getState().setDirectory(metadata.path, { showOverlay: false });
};
const createInstantWorktreeDraft = async (options?: {
initialPrompt?: string;
title?: string;
}): Promise<string | null> => {
if (isCreatingWorktreeSession) {
return null;
}
const activeProject = useProjectsStore.getState().getActiveProject();
if (!activeProject?.path) {
toast.error('No active project', {
description: 'Please select a project first.',
});
return null;
}
const projectDirectory = activeProject.path;
let isGitRepo = false;
try {
isGitRepo = await checkIsGitRepository(projectDirectory);
} catch {
// Ignore errors, treat as not a git repo
}
if (!isGitRepo) {
toast.error('Not a Git repository', {
description: 'Worktrees can only be created in Git repositories.',
});
return null;
}
isCreatingWorktreeSession = true;
try {
const projectRef: ProjectRef = { id: activeProject.id, path: projectDirectory };
const pendingRequestId = createPendingDraftWorktreeRequest();
// Lock the draft immediately so no React effect can reset it to the project
// root while we await the preview / worktree creation below.
const sessionStore = useSessionUIStore.getState();
if (sessionStore.newSessionDraft?.open) {
sessionStore.overrideNewSessionDraftTarget({
projectId: projectRef.id,
directoryOverride: sessionStore.newSessionDraft.directoryOverride ?? projectRef.path,
pendingWorktreeRequestId: pendingRequestId,
preserveDirectoryOverride: true,
title: options?.title,
initialPrompt: options?.initialPrompt,
});
} else {
sessionStore.openNewSessionDraft({
selectedProjectId: projectRef.id,
directoryOverride: projectRef.path,
pendingWorktreeRequestId: pendingRequestId,
preserveDirectoryOverride: true,
title: options?.title,
initialPrompt: options?.initialPrompt,
});
}
const preferredName = generateBranchName();
const preview = await previewGitWorktree(projectRef.path, {
mode: 'new',
branchName: preferredName,
worktreeName: preferredName,
}).catch(() => null);
// Refine draft target once we know the actual worktree path from the preview.
if (preview?.path) {
useSessionUIStore.getState().overrideNewSessionDraftTarget({
projectId: projectRef.id,
directoryOverride: preview.path,
pendingWorktreeRequestId: pendingRequestId,
bootstrapPendingDirectory: preview.path,
preserveDirectoryOverride: true,
title: options?.title,
initialPrompt: options?.initialPrompt,
});
useDirectoryStore.getState().setDirectory(preview.path, { showOverlay: false });
}
const setupCommands = await getWorktreeSetupCommands(projectRef);
const metadata = await createWorktreeWithDefaults(projectRef, {
preferredName,
mode: 'new',
branchName: preferredName,
worktreeName: preferredName,
setupCommands,
returnAfterDirectoryCreated: true,
});
resolvePendingDraftWorktreeRequest(pendingRequestId, metadata.path);
useSessionUIStore.getState().overrideNewSessionDraftTarget({
projectId: projectRef.id,
directoryOverride: metadata.path,
pendingWorktreeRequestId: null,
bootstrapPendingDirectory: metadata.path,
preserveDirectoryOverride: true,
title: options?.title,
initialPrompt: options?.initialPrompt,
});
useDirectoryStore.getState().setDirectory(metadata.path, { showOverlay: false });
return metadata.path;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create worktree';
const requestId = useSessionUIStore.getState().newSessionDraft.pendingWorktreeRequestId;
if (requestId) {
rejectPendingDraftWorktreeRequest(requestId, error instanceof Error ? error : new Error(message));
useSessionUIStore.getState().resolvePendingDraftWorktreeTarget(requestId, null);
}
useSessionUIStore.getState().setDraftBootstrapPendingDirectory(null);
toast.error('Failed to create worktree', {
description: message,
});
return null;
} finally {
isCreatingWorktreeSession = false;
}
};
/**
* Create a new worktree and open a draft scoped to it.
*
* @returns The worktree path, or null if creation failed
*/
export async function createWorktreeSession(): Promise<string | null> {
return createInstantWorktreeDraft();
}
/**
* Check if a worktree session is currently being created.
*/
export async function createWorktreeDraft(options?: { initialPrompt?: string; title?: string }): Promise<string | null> {
return createInstantWorktreeDraft(options);
}
/**
* Create a worktree session for a new branch name.
* Callers can still use startPoint for metadata or follow-up git operations.
*/
export async function createWorktreeSessionForNewBranch(
projectDirectory: string,
preferredBranchName: string,
startPoint?: string,
options?: {
kind?: 'pr' | 'standard';
worktreeName?: string;
setUpstream?: boolean;
upstreamRemote?: string;
upstreamBranch?: string;
ensureRemoteName?: string;
ensureRemoteUrl?: string;
createdFromBranch?: string;
returnAfterDirectoryCreated?: boolean;
}
): Promise<{ id: string; branch: string; path: string } | null> {
if (isCreatingWorktreeSession) {
return null;
}
isCreatingWorktreeSession = true;
try {
const start = startPoint?.trim() || 'HEAD';
const base = preferredBranchName?.trim();
if (!base) {
throw new Error('Branch name is required');
}
const kind = options?.kind ?? 'standard';
const projectRef = resolveProjectRef(projectDirectory);
if (!projectRef) {
throw new Error('Project is not registered in OpenChamber');
}
let isGitRepo = false;
try {
isGitRepo = await checkIsGitRepository(projectRef.path);
} catch {
// ignore
}
if (!isGitRepo) {
toast.error('Not a Git repository', {
description: 'Worktrees can only be created in Git repositories.',
});
return null;
}
const setupCommands = await getWorktreeSetupCommands(projectRef);
const rootBranch = await getRootBranch(projectRef.path);
try {
const metadata = await createWorktreeWithDefaults(projectRef, {
preferredName: base,
mode: 'new',
branchName: base,
worktreeName: options?.worktreeName || base,
startRef: start,
setUpstream: options?.setUpstream,
upstreamRemote: options?.upstreamRemote,
upstreamBranch: options?.upstreamBranch,
ensureRemoteName: options?.ensureRemoteName,
ensureRemoteUrl: options?.ensureRemoteUrl,
setupCommands,
returnAfterDirectoryCreated: options?.returnAfterDirectoryCreated,
});
const createdMetadata = {
...metadata,
createdFromBranch: options?.createdFromBranch || rootBranch || start,
kind,
};
await waitForWorktreeBootstrapIfEnabled(projectRef, metadata.path);
const sessionStore = useSessionUIStore.getState();
const session = await sessionStore.createSession(undefined, metadata.path);
if (!session) {
await removeProjectWorktree(projectRef, metadata, { deleteLocalBranch: true }).catch(() => undefined);
throw new Error('Could not create a session for the worktree.');
}
initializeSessionForWorktree(session.id, createdMetadata);
return { id: session.id, branch: metadata.branch || base, path: metadata.path };
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create worktree session';
toast.error('Failed to create worktree', { description: message });
return null;
}
} finally {
isCreatingWorktreeSession = false;
}
}