From d5f8091d660bd3cb80ccde2807df9e3961dbeb3e Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 3 Dec 2025 14:09:56 +0700 Subject: [PATCH] refactor: simplify modal mounting with lazy wrapper and add read-only git env --- packages/app/src/app/index.tsx | 23 ++++---------- .../app/src/components/create-agent-modal.tsx | 30 +++++++++++++++++-- packages/app/src/components/global-footer.tsx | 4 ++- packages/server/src/server/session.ts | 13 ++++++-- packages/server/src/utils/worktree.ts | 5 ++++ 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/packages/app/src/app/index.tsx b/packages/app/src/app/index.tsx index 35bc25d39..fb1f1833e 100644 --- a/packages/app/src/app/index.tsx +++ b/packages/app/src/app/index.tsx @@ -24,8 +24,6 @@ export default function HomeScreen() { } = useAggregatedAgents(); const [showCreateModal, setShowCreateModal] = useState(false); const [showImportModal, setShowImportModal] = useState(false); - const [createModalMounted, setCreateModalMounted] = useState(false); - const [importModalMounted, setImportModalMounted] = useState(false); const [pendingImportServerId, setPendingImportServerId] = useState(null); const { modal, flow, action, serverId: serverIdParam } = useLocalSearchParams<{ modal?: string; @@ -49,13 +47,11 @@ export default function HomeScreen() { const hasAgents = aggregatedAgents.length > 0; const handleCreateAgent = useCallback(() => { - setCreateModalMounted(true); setShowCreateModal(true); }, []); const openImportModal = useCallback((serverIdOverride?: string | null) => { setPendingImportServerId(serverIdOverride ?? null); - setImportModalMounted(true); setShowImportModal(true); }, []); @@ -138,19 +134,12 @@ export default function HomeScreen() { {/* Create Agent Modal */} - {createModalMounted ? ( - - ) : null} - {importModalMounted ? ( - - ) : null} + + ); } diff --git a/packages/app/src/components/create-agent-modal.tsx b/packages/app/src/components/create-agent-modal.tsx index c2164fb7c..8a923ad54 100644 --- a/packages/app/src/components/create-agent-modal.tsx +++ b/packages/app/src/components/create-agent-modal.tsx @@ -82,6 +82,7 @@ interface AgentFlowModalProps { flow: "create" | "import"; initialValues?: CreateAgentInitialValues; serverId?: string | null; + onAfterClose?: () => void; } type DictationToastConfig = { @@ -237,6 +238,7 @@ function AgentFlowModal({ flow, initialValues, serverId, + onAfterClose, }: AgentFlowModalProps) { const insets = useSafeAreaInsets(); const { height: screenHeight, width: screenWidth } = useWindowDimensions(); @@ -1326,7 +1328,8 @@ function AgentFlowModal({ resetFormState(); setIsMounted(false); navigateToAgentIfNeeded(); - }, [navigateToAgentIfNeeded, resetFormState]); + onAfterClose?.(); + }, [navigateToAgentIfNeeded, onAfterClose, resetFormState]); const requestImportCandidates = useCallback( (provider?: AgentProvider) => { @@ -2433,12 +2436,33 @@ function AgentFlowModal({ ); } +function LazyAgentFlowModal(props: Omit) { + const { isVisible } = props; + const [shouldRender, setShouldRender] = useState(isVisible); + + useEffect(() => { + if (isVisible) { + setShouldRender(true); + } + }, [isVisible]); + + const handleAfterClose = useCallback(() => { + setShouldRender(false); + }, []); + + if (!shouldRender) { + return null; + } + + return ; +} + export function CreateAgentModal(props: ModalWrapperProps) { - return ; + return ; } export function ImportAgentModal(props: ModalWrapperProps) { - return ; + return ; } interface ModalHeaderProps { diff --git a/packages/app/src/components/global-footer.tsx b/packages/app/src/components/global-footer.tsx index 0f1b66102..7fe1db851 100644 --- a/packages/app/src/components/global-footer.tsx +++ b/packages/app/src/components/global-footer.tsx @@ -171,7 +171,9 @@ export function GlobalFooter() { setShowImportModal(true)} + onPress={() => { + setShowImportModal(true); + }} style={({ pressed }) => [ styles.footerButton, pressed && styles.buttonPressed, diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index cc9bb7fd4..09aeb24b4 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -72,6 +72,10 @@ type AgentMcpClientConfig = { }; const execAsync = promisify(exec); +const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = { + ...process.env, + GIT_OPTIONAL_LOCKS: "0", +}; const ACTIVE_TITLE_GENERATIONS = new Set(); const pendingAgentInitializations = new Map>(); let restartRequested = false; @@ -1526,11 +1530,11 @@ export class Session { const repoInfo = await detectRepoInfo(resolvedCwd); const { stdout: branchesRaw } = await execAsync( "git branch --format='%(refname:short)'", - { cwd: repoInfo.path } + { cwd: repoInfo.path, env: READ_ONLY_GIT_ENV } ); const { stdout: currentRaw } = await execAsync( "git rev-parse --abbrev-ref HEAD", - { cwd: resolvedCwd } + { cwd: resolvedCwd, env: READ_ONLY_GIT_ENV } ); const currentBranch = currentRaw.trim(); const branches = branchesRaw @@ -1687,7 +1691,10 @@ export class Session { private async isWorkingTreeDirty(cwd: string): Promise { try { - const { stdout } = await execAsync("git status --porcelain", { cwd }); + const { stdout } = await execAsync("git status --porcelain", { + cwd, + env: READ_ONLY_GIT_ENV, + }); return stdout.trim().length > 0; } catch (error) { throw new Error( diff --git a/packages/server/src/utils/worktree.ts b/packages/server/src/utils/worktree.ts index 25d597b1e..7a2699e18 100644 --- a/packages/server/src/utils/worktree.ts +++ b/packages/server/src/utils/worktree.ts @@ -4,6 +4,10 @@ import { existsSync, readFileSync } from "fs"; import { join, basename, dirname } from "path"; const execAsync = promisify(exec); +const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = { + ...process.env, + GIT_OPTIONAL_LOCKS: "0", +}; interface RepoInfo { type: "bare" | "normal"; @@ -88,6 +92,7 @@ export async function detectRepoInfo(cwd: string): Promise { try { const { stdout } = await execAsync("git rev-parse --show-toplevel", { cwd, + env: READ_ONLY_GIT_ENV, }); const repoRoot = stdout.trim(); return {