diff --git a/apps/code/src/renderer/components/MainLayout.tsx b/apps/code/src/renderer/components/MainLayout.tsx index d25f27967..4f712a0f4 100644 --- a/apps/code/src/renderer/components/MainLayout.tsx +++ b/apps/code/src/renderer/components/MainLayout.tsx @@ -25,17 +25,26 @@ import { useTasks } from "@features/tasks/hooks/useTasks"; import { TourOverlay } from "@features/tour/components/TourOverlay"; import { useTourStore } from "@features/tour/stores/tourStore"; import { createFirstTaskTour } from "@features/tour/tours/createFirstTaskTour"; +import { + useWorkspaces, + workspaceApi, +} from "@features/workspace/hooks/useWorkspace"; import { useFeatureFlag } from "@hooks/useFeatureFlag"; import { useIntegrations } from "@hooks/useIntegrations"; import { Box, Flex } from "@radix-ui/themes"; -import { BILLING_FLAG } from "@shared/constants"; +import { useTRPC } from "@renderer/trpc/client"; +import { BILLING_FLAG, SYNC_CLOUD_TASKS_FLAG } from "@shared/constants"; import { useCommandMenuStore } from "@stores/commandMenuStore"; import { useNavigationStore } from "@stores/navigationStore"; import { useShortcutsSheetStore } from "@stores/shortcutsSheetStore"; -import { useCallback, useEffect } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { logger } from "@utils/logger"; +import { useCallback, useEffect, useRef } from "react"; import { useTaskDeepLink } from "../hooks/useTaskDeepLink"; import { GlobalEventHandlers } from "./GlobalEventHandlers"; +const log = logger.scope("main-layout"); + export function MainLayout() { const { view, @@ -56,7 +65,12 @@ export function MainLayout() { close: closeShortcutsSheet, } = useShortcutsSheetStore(); const { data: tasks } = useTasks(); + const { data: workspaces, isFetched: workspacesFetched } = useWorkspaces(); + const trpcReact = useTRPC(); + const queryClient = useQueryClient(); + const reconcilingTaskIds = useRef>(new Set()); const billingEnabled = useFeatureFlag(BILLING_FLAG); + const syncCloudTasksEnabled = useFeatureFlag(SYNC_CLOUD_TASKS_FLAG); // Space switcher data const sidebarData = useSidebarData({ activeView: view }); @@ -80,6 +94,50 @@ export function MainLayout() { } }, [tasks, hydrateTask]); + useEffect(() => { + if (!syncCloudTasksEnabled) return; + if (!tasks || !workspaces || !workspacesFetched) return; + const missing = tasks.filter( + (t) => !workspaces[t.id] && !reconcilingTaskIds.current.has(t.id), + ); + if (missing.length === 0) return; + for (const t of missing) reconcilingTaskIds.current.add(t.id); + void Promise.allSettled( + missing.map((t) => + workspaceApi.create({ + taskId: t.id, + mainRepoPath: "", + folderId: "", + folderPath: "", + mode: "cloud", + }), + ), + ).then((results) => { + let anySucceeded = false; + for (const [i, r] of results.entries()) { + const id = missing[i].id; + reconcilingTaskIds.current.delete(id); + if (r.status === "rejected") { + log.warn(`Failed to reconcile workspace for task ${id}`, r.reason); + } else { + anySucceeded = true; + } + } + if (anySucceeded) { + void queryClient.invalidateQueries( + trpcReact.workspace.getAll.pathFilter(), + ); + } + }); + }, [ + syncCloudTasksEnabled, + tasks, + workspaces, + workspacesFetched, + queryClient, + trpcReact, + ]); + useEffect(() => { if (view.type === "task-detail" && !view.data && !view.taskId) { navigateToTaskInput(); diff --git a/apps/code/src/shared/constants.ts b/apps/code/src/shared/constants.ts index 2c2a27339..eaf28c98c 100644 --- a/apps/code/src/shared/constants.ts +++ b/apps/code/src/shared/constants.ts @@ -1,5 +1,6 @@ export const BILLING_FLAG = "posthog-code-billing"; export const INBOX_GATED_DUE_TO_SCALE_FLAG = "inbox-gated-due-to-scale"; +export const SYNC_CLOUD_TASKS_FLAG = "posthog-code-sync-cloud-tasks"; export const BRANCH_PREFIX = "posthog-code/"; export const DATA_DIR = ".posthog-code"; export const WORKTREES_DIR = ".posthog-code/worktrees";