Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions desktop/src/features/projects/ui/ProjectDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,9 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
description:
error instanceof Error ? error.message : "The Git fetch failed.",
});
return;
}
toast.success("Remote state refreshed.");
}, [repoSnapshotQuery, repoStateQuery, repoSyncStatusQuery]);
// Compact branch + remote/local controls shared by the readme and Files
// tab headers.
Expand All @@ -301,6 +303,12 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
? "Local"
: "Local missing",
remoteLabel: repoSnapshotQuery.isLoading ? "Remote checking" : "Remote",
onCloneLocal: project?.cloneUrls[0]
? () => {
void handleCloneRepo();
}
: undefined,
clonePending: cloneRepoMutation.isPending,
canPush: repoSyncStatusQuery.data?.canPush ?? false,
onPush: () => {
void handlePushLocalRepo();
Expand Down Expand Up @@ -813,16 +821,6 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {

<WorkspaceTabs
key={`${project.id}:${tabsResetKey}`}
cloneAction={
!hasLocalCheckout && project.cloneUrls[0]
? {
onClone: () => {
void handleCloneRepo();
},
pending: cloneRepoMutation.isPending,
}
: undefined
}
commitDiff={commitDiffQuery.data}
commitDiffError={commitDiffQuery.error}
commitDiffLoading={commitDiffQuery.isLoading}
Expand Down
48 changes: 36 additions & 12 deletions desktop/src/features/projects/ui/ProjectRepositorySource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Button } from "@/shared/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
Expand Down Expand Up @@ -47,12 +48,12 @@ export function RepositoryBranchDropdown({
<Button
className={
compact
? "h-7 max-w-full gap-1.5 rounded-md bg-muted px-3 font-mono text-sm font-medium hover:bg-muted/80"
: "h-6 max-w-full gap-1.5 px-2 font-mono text-sm font-semibold"
? "h-7 max-w-full gap-1.5 rounded-md px-3 font-mono text-sm font-medium hover:border-input"
: "h-6 max-w-full gap-1.5 px-2 font-mono text-sm font-semibold hover:border-input"
}
size="sm"
type="button"
variant="ghost"
variant="outline"
>
<GitBranch className="h-4 w-4 shrink-0 text-muted-foreground" />
<span className="truncate">{branch}</span>
Expand Down Expand Up @@ -82,6 +83,9 @@ export type RepoSourceHeaderControls = {
localDisabled: boolean;
localLabel: string;
remoteLabel: string;
/** Clones the repository when no local checkout is available. */
onCloneLocal?: () => void;
clonePending?: boolean;
/** Push of local commits, available when the local checkout is ahead. */
canPush?: boolean;
onPush?: () => void;
Expand Down Expand Up @@ -110,15 +114,16 @@ export function RepoSourceDropdown({
controls: RepoSourceHeaderControls;
}) {
const isLocal = controls.source === "local";
const cloneLocal = controls.localDisabled && controls.onCloneLocal;
const SourceIcon = isLocal ? HardDrive : Cloud;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className="h-7 max-w-full shrink-0 gap-1.5 rounded-md bg-muted px-3 text-sm font-medium hover:bg-muted/80"
className="h-7 max-w-full shrink-0 gap-1.5 rounded-md px-3 text-sm font-medium hover:border-input"
size="sm"
type="button"
variant="ghost"
variant="outline"
>
<SourceIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
<span className="truncate">
Expand All @@ -138,14 +143,33 @@ export function RepoSourceDropdown({
<Cloud className="mr-1.5 h-3.5 w-3.5 text-muted-foreground" />
{controls.remoteLabel}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem
disabled={controls.localDisabled}
value="local"
>
<HardDrive className="mr-1.5 h-3.5 w-3.5 text-muted-foreground" />
{controls.localLabel}
</DropdownMenuRadioItem>
{!cloneLocal ? (
<DropdownMenuRadioItem
disabled={controls.localDisabled}
value="local"
>
<HardDrive className="mr-1.5 h-3.5 w-3.5 text-muted-foreground" />
{controls.localLabel}
</DropdownMenuRadioItem>
) : null}
</DropdownMenuRadioGroup>
{cloneLocal ? (
<DropdownMenuItem
className="group"
disabled={controls.clonePending}
onSelect={controls.onCloneLocal}
>
{controls.clonePending ? (
<Loader2 className="animate-spin text-muted-foreground" />
) : (
<DownloadCloud className="text-muted-foreground" />
)}
<span className="text-muted-foreground">{controls.localLabel}</span>
<span className="ml-auto rounded-md border border-input/60 bg-background px-2 py-0.5 text-xs font-medium text-foreground shadow-xs group-focus:border-input">
{controls.clonePending ? "Cloning…" : "Clone"}
</span>
</DropdownMenuItem>
) : null}
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
21 changes: 0 additions & 21 deletions desktop/src/features/projects/ui/ProjectWorkspaceTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
CircleDot,
Download,
GitPullRequest,
Plus,
RefreshCw,
Expand Down Expand Up @@ -48,11 +47,6 @@ import {
} from "./CreateIssueDialog";
import { PROJECT_PANEL_ACTION_BUTTON_CLASS } from "./projectPanelStyles";

type CloneRepositoryAction = {
onClone: () => void;
pending: boolean;
};

type CreatePullRequestAction = {
projects: Project[];
reposDir?: string | null;
Expand Down Expand Up @@ -106,7 +100,6 @@ function WorkItemListHeader({
}

export function WorkspaceTabs({
cloneAction,
commitDiff,
commitDiffError,
commitDiffLoading,
Expand Down Expand Up @@ -142,7 +135,6 @@ export function WorkspaceTabs({
terminalTitle,
viewerGitIdentity,
}: {
cloneAction?: CloneRepositoryAction;
commitDiff: ProjectRepoDiff | null | undefined;
commitDiffError: unknown;
commitDiffLoading: boolean;
Expand Down Expand Up @@ -276,19 +268,6 @@ export function WorkspaceTabs({
<SquareTerminal className="h-[1.125rem] w-[1.125rem]" />
</Button>
) : null}
{cloneAction ? (
<Button
className="h-8 shrink-0 gap-1.5"
disabled={cloneAction.pending}
onClick={cloneAction.onClone}
size="sm"
title="Clone repository"
variant="outline"
>
<Download className="h-4 w-4" />
{cloneAction.pending ? "Cloning…" : "Clone"}
</Button>
) : null}
{updatePullRequestAction ? (
<Button
className="h-8 shrink-0 gap-1.5"
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/features/projects/ui/ProjectsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ export function ProjectsView() {
<PageHeader
className="pointer-events-auto mb-8"
description="Set up and manage your projects."
title={activeCommunity?.name || "Relay"}
title="Projects"
/>
);

Expand Down
25 changes: 23 additions & 2 deletions desktop/src/testing/e2eBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9281,12 +9281,33 @@ export function maybeInstallE2eTauriMocks() {
pulled: true,
message: "Pulled main from remote.",
};
case "clone_project_repository":
case "clone_project_repository": {
const path = "/tmp/buzz/REPOS/mock-project";
const commit = "0123456789abcdef0123456789abcdef01234567";
window.__BUZZ_E2E_PROJECT_REPO_SYNC_STATUS__ = {
local_path: path,
local_branch: "main",
local_head: commit,
local_short_head: commit.slice(0, 7),
remote_branch: "main",
remote_head: commit,
remote_short_head: commit.slice(0, 7),
merge_base: commit,
ahead_count: 0,
behind_count: 0,
has_uncommitted_changes: false,
has_untracked_files: false,
can_push: false,
push_block_reason: "Local branch is already pushed.",
can_pull: false,
pull_block_reason: "Local branch is up to date.",
};
return {
path: "/tmp/buzz/REPOS/mock-project",
path,
cloned: true,
message: "Cloned repository.",
};
}
case "sign_project_pull_request_review_request": {
const { input } = payload as {
input: {
Expand Down
3 changes: 3 additions & 0 deletions desktop/tests/e2e/project-commit-detail.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ test("top-level project lists align dates and overflow actions", async ({
await installMockBridge(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-projects-view").click();
await expect(
page.getByRole("heading", { level: 1, name: "Projects" }),
).toBeVisible();

async function trailingPositions(row: import("@playwright/test").Locator) {
await waitForAnimations(page);
Expand Down
34 changes: 32 additions & 2 deletions desktop/tests/e2e/project-pr-review.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,13 +327,43 @@ test("viewer without repository ownership cannot merge", async ({ page }) => {
);
});

test("project without a checkout can be cloned", async ({ page }) => {
test("project without a checkout offers fetch feedback and dropdown cloning", async ({
page,
}) => {
await enableProjectsFeature(page);
await installMockBridge(page);
await openBuzzProject(page);

await page.getByRole("button", { name: "Clone", exact: true }).click();
await expect(
page.getByRole("button", { name: "Remote", exact: true }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Remote", exact: true }),
).toHaveClass(/\bborder-input\/40\b/);
await expect(page.getByRole("button", { name: /main/ })).toHaveClass(
/\bborder-input\/40\b/,
);
await expect(
page.getByRole("button", { name: "Clone", exact: true }),
).toHaveCount(0);
await page.getByRole("button", { name: "Fetch", exact: true }).click();
await expect(page.getByText("Remote state refreshed.")).toBeVisible();

await page.getByRole("button", { name: "Remote", exact: true }).click();
const cloneItem = page.getByRole("menuitem", {
name: "Local missing Clone",
});
await expect(cloneItem.getByText("Local missing")).toHaveClass(
/text-muted-foreground/,
);
await expect(cloneItem.getByText("Clone", { exact: true })).toHaveClass(
/\bborder-input\/60\b/,
);
await cloneItem.click();
await expect(page.getByText("Cloned repository.")).toBeVisible();
await expect(
page.getByRole("button", { name: "Local", exact: true }),
).toBeVisible();
const commands = await page.evaluate(
() => window.__BUZZ_E2E_COMMANDS__ ?? [],
);
Expand Down
Loading