refactor: simplify modal mounting with lazy wrapper and add read-only git env

This commit is contained in:
Mohamed Boudra
2025-12-03 14:09:56 +07:00
parent f4f7790d07
commit d5f8091d66
5 changed files with 51 additions and 24 deletions
+6 -17
View File
@@ -24,8 +24,6 @@ export default function HomeScreen() {
} = useAggregatedAgents(); } = useAggregatedAgents();
const [showCreateModal, setShowCreateModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false);
const [showImportModal, setShowImportModal] = useState(false); const [showImportModal, setShowImportModal] = useState(false);
const [createModalMounted, setCreateModalMounted] = useState(false);
const [importModalMounted, setImportModalMounted] = useState(false);
const [pendingImportServerId, setPendingImportServerId] = useState<string | null>(null); const [pendingImportServerId, setPendingImportServerId] = useState<string | null>(null);
const { modal, flow, action, serverId: serverIdParam } = useLocalSearchParams<{ const { modal, flow, action, serverId: serverIdParam } = useLocalSearchParams<{
modal?: string; modal?: string;
@@ -49,13 +47,11 @@ export default function HomeScreen() {
const hasAgents = aggregatedAgents.length > 0; const hasAgents = aggregatedAgents.length > 0;
const handleCreateAgent = useCallback(() => { const handleCreateAgent = useCallback(() => {
setCreateModalMounted(true);
setShowCreateModal(true); setShowCreateModal(true);
}, []); }, []);
const openImportModal = useCallback((serverIdOverride?: string | null) => { const openImportModal = useCallback((serverIdOverride?: string | null) => {
setPendingImportServerId(serverIdOverride ?? null); setPendingImportServerId(serverIdOverride ?? null);
setImportModalMounted(true);
setShowImportModal(true); setShowImportModal(true);
}, []); }, []);
@@ -138,19 +134,12 @@ export default function HomeScreen() {
</ReanimatedAnimated.View> </ReanimatedAnimated.View>
{/* Create Agent Modal */} {/* Create Agent Modal */}
{createModalMounted ? ( <CreateAgentModal isVisible={showCreateModal} onClose={handleCloseCreateModal} />
<CreateAgentModal <ImportAgentModal
isVisible={showCreateModal} isVisible={showImportModal}
onClose={handleCloseCreateModal} onClose={handleCloseImportModal}
/> serverId={pendingImportServerId}
) : null} />
{importModalMounted ? (
<ImportAgentModal
isVisible={showImportModal}
onClose={handleCloseImportModal}
serverId={pendingImportServerId}
/>
) : null}
</View> </View>
); );
} }
@@ -82,6 +82,7 @@ interface AgentFlowModalProps {
flow: "create" | "import"; flow: "create" | "import";
initialValues?: CreateAgentInitialValues; initialValues?: CreateAgentInitialValues;
serverId?: string | null; serverId?: string | null;
onAfterClose?: () => void;
} }
type DictationToastConfig = { type DictationToastConfig = {
@@ -237,6 +238,7 @@ function AgentFlowModal({
flow, flow,
initialValues, initialValues,
serverId, serverId,
onAfterClose,
}: AgentFlowModalProps) { }: AgentFlowModalProps) {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { height: screenHeight, width: screenWidth } = useWindowDimensions(); const { height: screenHeight, width: screenWidth } = useWindowDimensions();
@@ -1326,7 +1328,8 @@ function AgentFlowModal({
resetFormState(); resetFormState();
setIsMounted(false); setIsMounted(false);
navigateToAgentIfNeeded(); navigateToAgentIfNeeded();
}, [navigateToAgentIfNeeded, resetFormState]); onAfterClose?.();
}, [navigateToAgentIfNeeded, onAfterClose, resetFormState]);
const requestImportCandidates = useCallback( const requestImportCandidates = useCallback(
(provider?: AgentProvider) => { (provider?: AgentProvider) => {
@@ -2433,12 +2436,33 @@ function AgentFlowModal({
); );
} }
function LazyAgentFlowModal(props: Omit<AgentFlowModalProps, "onAfterClose">) {
const { isVisible } = props;
const [shouldRender, setShouldRender] = useState(isVisible);
useEffect(() => {
if (isVisible) {
setShouldRender(true);
}
}, [isVisible]);
const handleAfterClose = useCallback(() => {
setShouldRender(false);
}, []);
if (!shouldRender) {
return null;
}
return <AgentFlowModal {...props} onAfterClose={handleAfterClose} />;
}
export function CreateAgentModal(props: ModalWrapperProps) { export function CreateAgentModal(props: ModalWrapperProps) {
return <AgentFlowModal {...props} flow="create" />; return <LazyAgentFlowModal {...props} flow="create" />;
} }
export function ImportAgentModal(props: ModalWrapperProps) { export function ImportAgentModal(props: ModalWrapperProps) {
return <AgentFlowModal {...props} flow="import" />; return <LazyAgentFlowModal {...props} flow="import" />;
} }
interface ModalHeaderProps { interface ModalHeaderProps {
@@ -171,7 +171,9 @@ export function GlobalFooter() {
</Pressable> </Pressable>
<Pressable <Pressable
onPress={() => setShowImportModal(true)} onPress={() => {
setShowImportModal(true);
}}
style={({ pressed }) => [ style={({ pressed }) => [
styles.footerButton, styles.footerButton,
pressed && styles.buttonPressed, pressed && styles.buttonPressed,
+10 -3
View File
@@ -72,6 +72,10 @@ type AgentMcpClientConfig = {
}; };
const execAsync = promisify(exec); const execAsync = promisify(exec);
const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = {
...process.env,
GIT_OPTIONAL_LOCKS: "0",
};
const ACTIVE_TITLE_GENERATIONS = new Set<string>(); const ACTIVE_TITLE_GENERATIONS = new Set<string>();
const pendingAgentInitializations = new Map<string, Promise<ManagedAgent>>(); const pendingAgentInitializations = new Map<string, Promise<ManagedAgent>>();
let restartRequested = false; let restartRequested = false;
@@ -1526,11 +1530,11 @@ export class Session {
const repoInfo = await detectRepoInfo(resolvedCwd); const repoInfo = await detectRepoInfo(resolvedCwd);
const { stdout: branchesRaw } = await execAsync( const { stdout: branchesRaw } = await execAsync(
"git branch --format='%(refname:short)'", "git branch --format='%(refname:short)'",
{ cwd: repoInfo.path } { cwd: repoInfo.path, env: READ_ONLY_GIT_ENV }
); );
const { stdout: currentRaw } = await execAsync( const { stdout: currentRaw } = await execAsync(
"git rev-parse --abbrev-ref HEAD", "git rev-parse --abbrev-ref HEAD",
{ cwd: resolvedCwd } { cwd: resolvedCwd, env: READ_ONLY_GIT_ENV }
); );
const currentBranch = currentRaw.trim(); const currentBranch = currentRaw.trim();
const branches = branchesRaw const branches = branchesRaw
@@ -1687,7 +1691,10 @@ export class Session {
private async isWorkingTreeDirty(cwd: string): Promise<boolean> { private async isWorkingTreeDirty(cwd: string): Promise<boolean> {
try { 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; return stdout.trim().length > 0;
} catch (error) { } catch (error) {
throw new Error( throw new Error(
+5
View File
@@ -4,6 +4,10 @@ import { existsSync, readFileSync } from "fs";
import { join, basename, dirname } from "path"; import { join, basename, dirname } from "path";
const execAsync = promisify(exec); const execAsync = promisify(exec);
const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = {
...process.env,
GIT_OPTIONAL_LOCKS: "0",
};
interface RepoInfo { interface RepoInfo {
type: "bare" | "normal"; type: "bare" | "normal";
@@ -88,6 +92,7 @@ export async function detectRepoInfo(cwd: string): Promise<RepoInfo> {
try { try {
const { stdout } = await execAsync("git rev-parse --show-toplevel", { const { stdout } = await execAsync("git rev-parse --show-toplevel", {
cwd, cwd,
env: READ_ONLY_GIT_ENV,
}); });
const repoRoot = stdout.trim(); const repoRoot = stdout.trim();
return { return {