diff --git a/web/src/components/projects/integration-form.tsx b/web/src/components/projects/integration-form.tsx index 01361876..668daaea 100644 --- a/web/src/components/projects/integration-form.tsx +++ b/web/src/components/projects/integration-form.tsx @@ -391,14 +391,22 @@ function GitHubWebhookSection({ projectId }: { projectId: string }) { // SCM Tab (GitHub) // ============================================================================ +interface SCMTabProject { + repo?: string | null; + baseBranch?: string | null; + branchPrefix?: string | null; +} + function SCMTab({ projectId, initialProvider, initialCredentials, + project, }: { projectId: string; initialProvider: string; initialCredentials: Map; + project?: SCMTabProject; }) { const queryClient = useQueryClient(); @@ -408,12 +416,31 @@ function SCMTab({ const [provider] = useState(initialProvider || 'github'); const [credentialMap, setCredentialMap] = useState>(initialCredentials); + // Project-level SCM fields + const [repo, setRepo] = useState(project?.repo ?? ''); + const [baseBranch, setBaseBranch] = useState(project?.baseBranch ?? 'main'); + const [branchPrefix, setBranchPrefix] = useState(project?.branchPrefix ?? 'feature/'); + useEffect(() => { setCredentialMap(initialCredentials); }, [initialCredentials]); + useEffect(() => { + setRepo(project?.repo ?? ''); + setBaseBranch(project?.baseBranch ?? 'main'); + setBranchPrefix(project?.branchPrefix ?? 'feature/'); + }, [project?.repo, project?.baseBranch, project?.branchPrefix]); + const saveMutation = useMutation({ mutationFn: async () => { + // Save project-level SCM fields + await trpcClient.projects.update.mutate({ + id: projectId, + repo: repo || undefined, + baseBranch, + branchPrefix, + }); + // Note: triggers are intentionally omitted — they are managed via the Agent Configs tab const result = await trpcClient.projects.integrations.upsert.mutate({ projectId, @@ -435,6 +462,12 @@ function SCMTab({ return result; }, onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: trpc.projects.getById.queryOptions({ id: projectId }).queryKey, + }); + queryClient.invalidateQueries({ + queryKey: trpc.projects.listFull.queryOptions().queryKey, + }); queryClient.invalidateQueries({ queryKey: trpc.projects.integrations.list.queryOptions({ projectId }).queryKey, }); @@ -451,6 +484,42 @@ function SCMTab({ return (
+ {/* Repository Settings */} +
+ +
+ + setRepo(e.target.value)} + placeholder="owner/repo" + /> +
+
+
+ + setBaseBranch(e.target.value)} + placeholder="main" + /> +
+
+ + setBranchPrefix(e.target.value)} + placeholder="feature/" + /> +
+
+
+ +
+

CASCADE uses two separate GitHub bot accounts to prevent feedback loops. The{' '} implementer writes code and creates PRs. The reviewer{' '} @@ -564,6 +633,7 @@ export function IntegrationForm({ projectId }: { projectId: string }) { const scmCredsQuery = useQuery( trpc.projects.integrationCredentials.list.queryOptions({ projectId, category: 'scm' }), ); + const projectQuery = useQuery(trpc.projects.getById.queryOptions({ id: projectId })); const [activeTab, setActiveTab] = useState('pm'); if (integrationsQuery.isLoading) { @@ -614,6 +684,7 @@ export function IntegrationForm({ projectId }: { projectId: string }) { projectId={projectId} initialProvider={scmProvider} initialCredentials={scmCredMap} + project={projectQuery.data} /> )}

diff --git a/web/src/components/projects/project-general-form.tsx b/web/src/components/projects/project-general-form.tsx index 45b0de6c..d9878f93 100644 --- a/web/src/components/projects/project-general-form.tsx +++ b/web/src/components/projects/project-general-form.tsx @@ -6,9 +6,6 @@ import { useState } from 'react'; interface Project { id: string; name: string; - repo?: string | null; - baseBranch: string | null; - branchPrefix: string | null; model: string | null; maxIterations: number | null; watchdogTimeoutMs: number | null; @@ -27,9 +24,6 @@ function numericFieldDefault(value: number | null | undefined): string { export function ProjectGeneralForm({ project }: { project: Project }) { const updateMutation = useProjectUpdate(project.id); const [name, setName] = useState(project.name); - const [repo, setRepo] = useState(project.repo ?? ''); - const [baseBranch, setBaseBranch] = useState(project.baseBranch ?? 'main'); - const [branchPrefix, setBranchPrefix] = useState(project.branchPrefix ?? 'feature/'); const [watchdogTimeoutMs, setWatchdogTimeoutMs] = useState( numericFieldDefault(project.watchdogTimeoutMs), ); @@ -44,9 +38,6 @@ export function ProjectGeneralForm({ project }: { project: Project }) { e.preventDefault(); updateMutation.mutate({ name, - repo: repo || undefined, - baseBranch, - branchPrefix, watchdogTimeoutMs: watchdogTimeoutMs ? Number.parseInt(watchdogTimeoutMs, 10) : null, progressModel: progressModel || null, progressIntervalMinutes: progressIntervalMinutes || null, @@ -57,38 +48,9 @@ export function ProjectGeneralForm({ project }: { project: Project }) { return (
-
-
- - setName(e.target.value)} required /> -
-
- - setRepo(e.target.value)} - placeholder="owner/repo" - /> -
-
-
-
- - setBaseBranch(e.target.value)} - /> -
-
- - setBranchPrefix(e.target.value)} - /> -
+
+ + setName(e.target.value)} required />