diff --git a/apps/code/src/renderer/features/folder-picker/components/FolderPicker.tsx b/apps/code/src/renderer/features/folder-picker/components/FolderPicker.tsx index 174f01017..095d7c32f 100644 --- a/apps/code/src/renderer/features/folder-picker/components/FolderPicker.tsx +++ b/apps/code/src/renderer/features/folder-picker/components/FolderPicker.tsx @@ -14,14 +14,19 @@ import { DropdownMenuTrigger, MenuLabel, } from "@posthog/quill"; +import { Flex, Text } from "@radix-ui/themes"; +import { FIELD_TRIGGER_CLASS } from "@renderer/styles/fieldTrigger"; import { trpcClient } from "@renderer/trpc"; +import { logger } from "@utils/logger"; import type { RefObject } from "react"; +const log = logger.scope("folder-picker"); + interface FolderPickerProps { value: string; onChange: (path: string) => void; placeholder?: string; - size?: "1" | "2"; + variant?: "compact" | "field"; anchor?: RefObject; } @@ -29,6 +34,7 @@ export function FolderPicker({ value, onChange, placeholder = "Select folder...", + variant = "compact", anchor, }: FolderPickerProps) { const { @@ -41,31 +47,67 @@ export function FolderPicker({ const recentFolders = getRecentFolders(); const displayValue = getFolderDisplayName(value); + const isField = variant === "field"; - const handleSelect = async (path: string) => { + const handleSelect = (path: string) => { onChange(path); const folder = getFolderByPath(path); - if (folder) { - updateLastAccessed(folder.id); - } + if (folder) updateLastAccessed(folder.id); }; const handleOpenFilePicker = async () => { - const selectedPath = await trpcClient.os.selectDirectory.query(); - if (selectedPath) { + try { + const selectedPath = await trpcClient.os.selectDirectory.query(); + if (!selectedPath) return; await addFolder(selectedPath); onChange(selectedPath); + } catch (error) { + log.error("Failed to open folder picker", { error }); } }; - if (recentFolders.length === 0) { - return ( - + ) : ( + ); } @@ -74,42 +116,46 @@ export function FolderPicker({ - - - {displayValue || placeholder} - - - + isField ? ( + + ) : ( + + ) } /> Recent - {recentFolders.map((folder) => ( handleSelect(folder.path)} > - - {folder.name} + + + {folder.name} + ))} - - - + Open folder... diff --git a/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx b/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx index e1d5fa7f7..762f68b71 100644 --- a/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx +++ b/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx @@ -24,6 +24,7 @@ import { GitBranch, Plus, } from "@phosphor-icons/react"; +import { cn } from "@posthog/quill"; import { AlertDialog, Box, @@ -45,6 +46,23 @@ import { useProjectsWithIntegrations } from "../hooks/useProjectsWithIntegration import { OnboardingHogTip } from "./OnboardingHogTip"; import { StepActions } from "./StepActions"; +const PANEL_SHADOW = "0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.02)"; + +function getPanelMessage(opts: { + hasConnectError: boolean; + connectError: Parameters[0]; + timedOut: boolean; + isConnecting: boolean; +}): string { + if (opts.hasConnectError) + return describeGithubConnectError(opts.connectError); + if (opts.timedOut) { + return "We didn't hear back from GitHub. If the browser tab was closed, click Connect again."; + } + if (opts.isConnecting) return "Waiting for GitHub..."; + return "Optional. Lets cloud agents work on this repo and open pull requests for you."; +} + interface GitIntegrationStepProps { onNext: () => void; onBack: () => void; @@ -100,13 +118,12 @@ export function GitIntegrationStep({ projectHasTeamIntegration: selectedProject?.hasGithubIntegration ?? null, }); const canTakeAction = !isConnecting && !timedOut && !hasConnectError; - const defaultPanelMessage = hasConnectError - ? describeGithubConnectError(connectError) - : timedOut - ? "We didn't hear back from GitHub. If the browser tab was closed, click Connect again." - : isConnecting - ? "Waiting for GitHub..." - : "Optional. Unlocks cloud agents and pull request workflows."; + const defaultPanelMessage = getPanelMessage({ + hasConnectError, + connectError, + timedOut, + isConnecting, + }); const { data: githubUserIntegrations = [], @@ -191,8 +208,7 @@ export function GitIntegrationStep({ {/* Header + content */} @@ -206,7 +222,8 @@ export function GitIntegrationStep({ Give your agents access to code - Point to a local codebase and optionally connect GitHub. + Pick a repository to run local tasks on this machine. + Connect GitHub to send tasks to cloud agents. @@ -219,10 +236,7 @@ export function GitIntegrationStep({ > @@ -230,19 +244,19 @@ export function GitIntegrationStep({ - Choose your codebase + Choose your repository - Select the local folder for your project so we can - analyze it. + Select a single repository folder, not a parent folder + that contains multiple repos. {isDetectingRepo && ( @@ -278,19 +292,19 @@ export function GitIntegrationStep({ {repoMatchesGitHub ? `Linked to ${detectedRepo.fullName} on GitHub` @@ -310,7 +324,7 @@ export function GitIntegrationStep({ transition={{ duration: 0.2 }} > - No git remote detected -- you can still continue. + No git remote detected. You can still continue. )} @@ -320,213 +334,281 @@ export function GitIntegrationStep({ {/* GitHub integration */} - - - - - - - - Connect GitHub - - - {isLoading || githubUserIntegrationsLoading ? ( - - ) : hasGitIntegration ? ( - anyIntegrationStale ? ( - - Reconnect needed - - ) : ( - - - - {githubUserIntegrations.length > 1 - ? `Connected (${githubUserIntegrations.length})` - : "Connected"} + + + + + + + + Connect GitHub - ) - ) : null} - - {hasGitIntegration ? ( - - {githubUserIntegrations.map((integration) => { - const installationId = integration.installation_id; - const accountName = - integration.account?.name ?? "GitHub"; - const installRepos = - reposByInstallationId[installationId]; - const isLoadingInstallRepos = - installRepos === undefined; - const isStale = - failedInstallationIds.includes(installationId); - const isReconnecting = - reconnectingInstallationId === installationId; - return ( - + ) : hasGitIntegration ? ( + anyIntegrationStale ? ( + + Reconnect needed + + ) : ( + + + + {githubUserIntegrations.length > 1 + ? `Connected (${githubUserIntegrations.length})` + : "Connected"} + + + ) + ) : ( + + Optional + + )} + + {!hasGitIntegration && + !isLoading && + !githubUserIntegrationsLoading && + (selectedProject?.hasGithubIntegration && + canTakeAction ? ( + + GitHub is already set up on{" "} + + {selectedProject.name} + + . Sign in with one click to link your account, no + admin approval needed. + + ) : selectedAlternative && + selectedProject && + canTakeAction ? ( + + GitHub is already connected on{" "} + {alternativeConnectedProjects.length > 1 ? ( + + + + + + {alternativeConnectedProjects.map((p) => ( + + setSelectedAlternativeId(p.id) + } + > + + {p.name} + + + {p.organization.name} + + + ))} + + + ) : ( + <> + + {selectedAlternative.name} + {" "} + ({selectedAlternative.organization.name}) + + )} + . + + ) : ( + + {defaultPanelMessage} + + ))} + + {hasGitIntegration ? ( + + {githubUserIntegrations.map((integration) => { + const installationId = integration.installation_id; + const accountName = + integration.account?.name ?? "GitHub"; + const installRepos = + reposByInstallationId[installationId]; + const isLoadingInstallRepos = + installRepos === undefined; + const isStale = + failedInstallationIds.includes(installationId); + const isReconnecting = + reconnectingInstallationId === installationId; + return ( - - - {accountName} - - - {integration.account?.type === - "Organization" - ? "org" - : "personal"} - + + + + {accountName} + + + {integration.account?.type === + "Organization" + ? "org" + : "personal"} + + + {isStale ? ( + + Reconnect needed + + ) : ( + + {isLoadingInstallRepos + ? "Loading…" + : installRepos.length === 1 + ? "1 repo" + : `${installRepos.length} repos`} + + )} - {isStale ? ( - - Reconnect needed - - ) : ( - - {isLoadingInstallRepos - ? "Loading…" - : installRepos.length === 1 - ? "1 repo" - : `${installRepos.length} repos`} - - )} - - - {isStale && ( + + {isStale && ( + + )} - )} - - + + - - ); - })} - - - + ); + })} + + + + - - ) : !isLoading && !githubUserIntegrationsLoading ? ( - selectedProject?.hasGithubIntegration && canTakeAction ? ( - - - GitHub is already set up on{" "} - - {selectedProject.name} - - . Sign in with one click to link your account — no - admin approval needed. - + ) : !isLoading && !githubUserIntegrationsLoading ? ( + selectedProject?.hasGithubIntegration && + canTakeAction ? ( - - ) : selectedAlternative && - selectedProject && - canTakeAction ? ( - - - GitHub is already connected on{" "} - {alternativeConnectedProjects.length > 1 ? ( - - - - - - {alternativeConnectedProjects.map((p) => ( - - setSelectedAlternativeId(p.id) - } - > - - {p.name} - - - {p.organization.name} - - - ))} - - - ) : ( - <> - - {selectedAlternative.name} - {" "} - ({selectedAlternative.organization.name}) - - )} - . - + ) : selectedAlternative && + selectedProject && + canTakeAction ? ( - - ) : ( - - - {defaultPanelMessage} - - + ) : ( + {hasConnectError && (