Keep new worktrees clean when setup changes are uncommitted (#3311)

* fix(worktrees): preserve selected base config

New worktrees were overwriting the selected ref's paseo.json with source-checkout bytes, which could create an accidental revert in an otherwise fresh workspace. Keep committed base config authoritative and warn when locally saved setup changes still need to be committed.

* fix(worktrees): seed config exclusively

* fix(projects): refresh config status on focus
This commit is contained in:
Mohamed Boudra
2026-08-13 12:02:15 +02:00
committed by GitHub
parent 9a7301d9a0
commit c190fed27c
23 changed files with 460 additions and 55 deletions
@@ -3,6 +3,7 @@ import type { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import { Pressable, Text, TextInput, View } from "react-native";
import { router } from "expo-router";
import { useFocusEffect } from "@react-navigation/native";
import { StyleSheet } from "react-native-unistyles";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ArrowLeft, MoreVertical, Pencil, Plus } from "lucide-react-native";
@@ -200,6 +201,12 @@ function ProjectSettingsBody({
queryFn: () => client.readProjectConfig(selectedHost.repoRoot),
retry: false,
});
const refetchProjectConfig = readQuery.refetch;
useFocusEffect(
useCallback(() => {
void refetchProjectConfig();
}, [refetchProjectConfig]),
);
const data = readQuery.data;
const supportsCustomIcon = useHostFeature(selectedHost.serverId, "projectCustomIcon");
@@ -240,6 +247,8 @@ function ProjectSettingsBody({
);
const loadedConfig: PaseoConfigRaw | null = data?.ok ? (data.config ?? {}) : null;
const loadedRevision: PaseoConfigRevision | null = data?.ok ? data.revision : null;
const hasUncommittedWorktreeSetupChanges =
data?.ok === true && data.hasUncommittedWorktreeSetupChanges === true;
const readError: ProjectConfigRpcError | null = data && !data.ok ? data.error : null;
const handleReload = useCallback(() => {
@@ -290,6 +299,7 @@ function ProjectSettingsBody({
readQuery,
loadedConfig,
loadedRevision,
hasUncommittedWorktreeSetupChanges,
readError,
selectedHost,
queryKey,
@@ -305,6 +315,7 @@ interface RenderContentInput {
readQuery: ReturnType<typeof useQuery<ReadProjectConfigData>>;
loadedConfig: PaseoConfigRaw | null;
loadedRevision: PaseoConfigRevision | null;
hasUncommittedWorktreeSetupChanges: boolean;
readError: ProjectConfigRpcError | null;
selectedHost: ProjectHostEntry;
queryKey: readonly [string, string, string];
@@ -317,6 +328,7 @@ function renderContent({
readQuery,
loadedConfig,
loadedRevision,
hasUncommittedWorktreeSetupChanges,
readError,
selectedHost,
queryKey,
@@ -358,6 +370,7 @@ function renderContent({
key={formKey}
baseConfig={loadedConfig}
revision={loadedRevision}
hasUncommittedWorktreeSetupChanges={hasUncommittedWorktreeSetupChanges}
repoRoot={selectedHost.repoRoot}
queryKey={queryKey}
client={client}
@@ -438,6 +451,7 @@ function errorToDetail(error: unknown): string | null {
interface ProjectConfigFormProps {
baseConfig: PaseoConfigRaw;
revision: PaseoConfigRevision | null;
hasUncommittedWorktreeSetupChanges: boolean;
repoRoot: string;
queryKey: readonly [string, string, string];
client: DaemonClient;
@@ -447,6 +461,7 @@ interface ProjectConfigFormProps {
function ProjectConfigForm({
baseConfig,
revision,
hasUncommittedWorktreeSetupChanges,
repoRoot,
queryKey,
client,
@@ -479,6 +494,11 @@ function ProjectConfigForm({
revision: result.revision,
requestId: "local-cache",
repoRoot,
...(result.hasUncommittedWorktreeSetupChanges === undefined
? {}
: {
hasUncommittedWorktreeSetupChanges: result.hasUncommittedWorktreeSetupChanges,
}),
});
setWriteError(null);
queryClient.invalidateQueries({ queryKey: ["projects"] });
@@ -660,6 +680,13 @@ function ProjectConfigForm({
testID="worktree-setup-section"
trailing={setupDocsLink}
>
{hasUncommittedWorktreeSetupChanges ? (
<Alert
variant="warning"
title={t("settings.project.worktree.uncommittedTitle")}
description={t("settings.project.worktree.uncommittedDescription")}
/>
) : null}
<SettingsTextAreaCard
testID="worktree-setup-input"
accessibilityLabel={t("settings.project.worktree.setupAccessibility")}