fix(desktop): polish Projects navigation and detail presentation

Make sidebar and repository navigation behave predictably while clarifying review metadata, activity hierarchy, and file-detail presentation.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
Thomas Petersen
2026-08-17 15:13:59 -04:00
parent 8728af5040
commit f24a8909c8
17 changed files with 244 additions and 108 deletions
@@ -354,7 +354,7 @@ function DiscussionMessagePreview({
}) {
return (
<Markdown
className="inbox-preview-markdown mt-0.5 text-inherit leading-4"
className="inbox-preview-markdown mt-0.5 text-inherit leading-6"
content={discussionSnippet(content)}
interactive={false}
mentionNames={mentionNames}
@@ -15,12 +15,16 @@ export function CopyTextButton({
text: string;
}) {
const [copied, setCopied] = React.useState(false);
const handleCopy = React.useCallback(() => {
void writeTextToClipboard(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2_000);
});
}, [text]);
const handleCopy = React.useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
void writeTextToClipboard(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2_000);
});
},
[text],
);
return (
<button
@@ -30,6 +34,7 @@ export function CopyTextButton({
className,
)}
onClick={handleCopy}
onKeyDown={(event) => event.stopPropagation()}
type="button"
>
{copied ? (
@@ -310,13 +310,13 @@ export function ProjectEntityListRow({
</span>
{count != null ? (
<span
className="grid w-12 shrink-0 grid-cols-[0.875rem_1fr] items-center gap-1 text-xs text-muted-foreground/65"
className="flex w-12 shrink-0 items-center gap-1 text-xs text-muted-foreground/65"
data-projects-text-priority="secondary"
data-testid={countTestId}
title={countTitle}
>
<MessageSquare className="h-3.5 w-3.5" />
<span className="text-right tabular-nums">
<span className="tabular-nums">
{count}
{countSuffix}
</span>
@@ -531,6 +531,7 @@ export function ProjectRepositoryActionsPanel({
<ProjectWorkItemContextDetails
issue={selectedIssue}
pullRequest={selectedPullRequest}
repository={repository}
/>
{!selectedIssue &&
!selectedPullRequest &&
@@ -620,6 +620,7 @@ export function RepositoryFilesPanel({
profiles,
fallbackAuthorPubkey,
onContextChange,
onOpenCommit,
sourceControls,
unavailableMessage,
}: {
@@ -633,6 +634,7 @@ export function RepositoryFilesPanel({
kind: "file" | "folder";
path: string;
}) => void;
onOpenCommit?: (commitHash: string) => void;
/** Branch picker + remote/local toggle rendered in the panel header. */
sourceControls?: RepoSourceHeaderControls;
unavailableMessage?: string;
@@ -838,9 +840,36 @@ export function RepositoryFilesPanel({
) : null}
<div className="overflow-x-auto px-2 pb-2">
<table className="w-full border-separate border-spacing-y-0.5 caption-bottom text-sm">
<table className="w-full border-collapse caption-bottom text-sm">
<thead>
<tr className="border-border/50 border-b bg-muted/20">
<tr
aria-label={
latestCommit
? `Open commit ${latestCommit.shortHash}`
: undefined
}
className={cn(
"bg-muted/20",
latestCommit &&
onOpenCommit &&
"cursor-pointer transition-colors hover:bg-muted/35 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
)}
data-testid="project-repository-latest-commit"
onClick={
latestCommit && onOpenCommit
? () => onOpenCommit(latestCommit.hash)
: undefined
}
onKeyDown={
latestCommit && onOpenCommit
? (event) =>
handleRepositoryEntryKeyDown(event, () =>
onOpenCommit(latestCommit.hash),
)
: undefined
}
tabIndex={latestCommit && onOpenCommit ? 0 : undefined}
>
<th className="px-4 py-3 text-left font-normal" colSpan={3}>
{latestCommit ? (
<div className="flex min-w-0 items-center justify-between gap-3 text-sm">
@@ -892,9 +921,8 @@ export function RepositoryFilesPanel({
</tr>
</thead>
<tbody>
{visibleEntries.map((entry, index) => {
{visibleEntries.map((entry) => {
const latestCommit = entry.latestCommit;
const rowIsLast = index === visibleEntries.length - 1;
const openEntry = () =>
openRepositoryEntry(entry, setCurrentPath, setSelectedFile);
@@ -902,6 +930,7 @@ export function RepositoryFilesPanel({
<tr
aria-label={`Open ${entry.type} ${entry.name}`}
className="group/repository-entry cursor-pointer text-xs focus-visible:outline-hidden"
data-testid="project-repository-entry-row"
key={`${entry.type}:${entry.path}`}
onClick={openEntry}
onKeyDown={(event) =>
@@ -909,12 +938,7 @@ export function RepositoryFilesPanel({
}
tabIndex={0}
>
<td
className={cn(
"min-w-52 rounded-l-md px-3 py-2 align-middle transition-colors group-hover/repository-entry:bg-muted/35 group-focus-visible/repository-entry:bg-muted/35",
!rowIsLast && "border-border/50 border-b",
)}
>
<td className="min-w-52 px-3 py-2 align-middle transition-colors group-hover/repository-entry:bg-muted/35 group-focus-visible/repository-entry:bg-muted/35">
<div className="flex min-w-0 items-center gap-2">
<RepositoryEntryIcon entry={entry} />
<span className="truncate font-medium text-foreground">
@@ -922,23 +946,13 @@ export function RepositoryFilesPanel({
</span>
</div>
</td>
<td
className={cn(
"max-w-96 p-2 align-middle transition-colors group-hover/repository-entry:bg-muted/35 group-focus-visible/repository-entry:bg-muted/35",
!rowIsLast && "border-border/50 border-b",
)}
>
<td className="max-w-96 p-2 align-middle transition-colors group-hover/repository-entry:bg-muted/35 group-focus-visible/repository-entry:bg-muted/35">
<RepositoryCommitCell
commit={latestCommit}
profiles={profiles}
/>
</td>
<td
className={cn(
"w-36 whitespace-nowrap rounded-r-md p-2 text-right align-middle text-muted-foreground transition-colors group-hover/repository-entry:bg-muted/35 group-focus-visible/repository-entry:bg-muted/35",
!rowIsLast && "border-border/50 border-b",
)}
>
<td className="w-36 whitespace-nowrap p-2 text-right align-middle text-muted-foreground transition-colors group-hover/repository-entry:bg-muted/35 group-focus-visible/repository-entry:bg-muted/35">
{latestCommit ? (
<time
dateTime={new Date(
@@ -1,6 +1,7 @@
import {
CheckCircle2,
CircleDot,
GitBranch,
GitPullRequest,
type LucideIcon,
MessageCircle,
@@ -11,6 +12,7 @@ import type * as React from "react";
import type {
ProjectIssue,
ProjectPullRequest,
Repository,
} from "@/features/projects/hooks";
function ContextDetailRow({
@@ -36,9 +38,11 @@ function ContextDetailRow({
export function ProjectWorkItemContextDetails({
issue,
pullRequest,
repository,
}: {
issue?: ProjectIssue | null;
pullRequest?: ProjectPullRequest | null;
repository: Repository;
}) {
if (issue) {
return (
@@ -63,8 +67,25 @@ export function ProjectWorkItemContextDetails({
}
if (pullRequest) {
const sourceBranch = pullRequest.branchName || "Unknown branch";
const targetBranch =
pullRequest.targetBranch || repository.defaultBranch || "Default branch";
const branchLabel = `${sourceBranch}${targetBranch}`;
return (
<>
<ContextDetailRow
icon={GitBranch}
label="Branch"
value={
<span
className="block max-w-40 truncate"
data-testid="project-context-branch"
title={branchLabel}
>
{branchLabel}
</span>
}
/>
<ContextDetailRow
icon={GitPullRequest}
label="Status"
@@ -561,6 +561,7 @@ export function WorkspaceTabs({
files={files}
isLoading={displayedSnapshotLoading}
onContextChange={onFilesContextChange}
onOpenCommit={onSelectedCommitHashChange}
profiles={profiles}
snapshot={displayedSnapshot}
unavailableMessage={
@@ -26,6 +26,8 @@ import type { UserProfileLookup } from "@/features/profile/lib/identity";
import { Button } from "@/shared/ui/button";
import { ProjectsCreateMenu } from "./ProjectsCreateMenu";
import { ProjectsSelectionCountMenu } from "./ProjectsSelectionCountMenu";
import { useCommunities } from "@/features/communities/useCommunities";
import { useActiveCommunityIcon } from "@/features/communities/useCommunityIcons";
import {
type OverviewContextStatIcon,
type ProjectsOverviewSection,
@@ -130,11 +132,34 @@ export function ProjectsOverviewPanel({
}
export function ProjectsActivityIntro() {
const { activeCommunity } = useCommunities();
const communityIconQuery = useActiveCommunityIcon(activeCommunity?.relayUrl);
const communityIcon = communityIconQuery.data ?? null;
return (
<section
className="pb-8 pt-5 text-center"
className="pb-8 pt-16 text-center"
data-testid="projects-activity-intro"
>
<div
aria-label={`${activeCommunity?.name ?? "Current"} relay`}
className="mx-auto mb-3 flex h-10 w-10 items-center justify-center overflow-hidden rounded-xl text-4xl"
data-testid="projects-activity-relay-icon"
role="img"
>
{communityIcon ? (
<img
alt=""
className="h-full w-full object-cover"
draggable={false}
src={communityIcon}
/>
) : (
<span aria-hidden="true" className="-translate-y-px leading-none">
🐝
</span>
)}
</div>
<h2
className="text-xl font-semibold tracking-tight text-foreground"
data-testid="projects-page-header"
@@ -291,7 +291,7 @@ export function PullRequestReviewersRow({
<ProjectDetailMetaRow icon={Users} label="Reviewers">
<div className="grid min-w-0 gap-1">
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
{showSummary ? (
{showSummary && displayedDecisionActors.length === 0 ? (
<span className="font-medium" data-testid={summaryTestId}>
{reviewSummary}
</span>
@@ -317,32 +317,27 @@ export function PullRequestReviewersRow({
? History
: null;
const decisionLabel = hasApproved
? "Approved"
? `Approved by ${label}`
: hasRequestedChanges
? "Changes requested"
? `Changes requested by ${label}`
: needsRereview
? "Re-review needed"
: "Pending";
? `Re-review needed from ${label}`
: `Awaiting review from ${label}`;
return (
<span
className="flex min-w-0 items-center gap-2"
className={cn(
"flex min-w-0 items-center gap-1 text-sm",
hasApproved && "text-green-600 dark:text-green-400",
hasRequestedChanges && "text-amber-600 dark:text-amber-400",
!hasApproved && !hasRequestedChanges && "text-muted-foreground",
)}
data-testid="project-reviewer-decision"
key={pubkey}
>
<span className="truncate text-sm text-foreground">{label}</span>
<span
className={cn(
"inline-flex shrink-0 items-center gap-0.5 text-sm",
hasApproved && "text-green-600 dark:text-green-400",
hasRequestedChanges && "text-amber-600 dark:text-amber-400",
!hasApproved &&
!hasRequestedChanges &&
"text-muted-foreground",
)}
>
{DecisionIcon ? <DecisionIcon className="h-3.5 w-3.5" /> : null}
{decisionLabel}
</span>
{DecisionIcon ? (
<DecisionIcon className="h-3.5 w-3.5 shrink-0" />
) : null}
<span className="truncate">{decisionLabel}</span>
</span>
);
})}
@@ -32,7 +32,10 @@ import {
useLeaveChannelDialog,
type SectionDialogValue,
} from "@/features/sidebar/ui/ChannelSectionDialogs";
import { AppSidebarPinnedHeader } from "@/features/sidebar/ui/AppSidebarPinnedHeader";
import {
AppSidebarPinnedHeader,
AppSidebarPrimaryMenu,
} from "@/features/sidebar/ui/AppSidebarPinnedHeader";
import { SidebarProjectsSection } from "@/features/sidebar/ui/SidebarProjectsSection";
import { MoreUnreadButton } from "@/features/sidebar/ui/MoreUnreadButton";
import { SidebarSection } from "@/features/sidebar/ui/SidebarSection";
@@ -569,23 +572,15 @@ export function AppSidebar({
currentChannelId={
selectedView === "channel" ? selectedChannelId : null
}
homeBadgeCount={homeBadgeCount}
onBrowseChannels={onBrowseChannels}
onCreateAgent={onCreateAgent}
onCreateChannel={handleOpenCreateChannel}
onOpenDm={onOpenDm}
onOpenSearchResult={onOpenSearchResult}
onSelectAgents={onSelectAgents}
onSelectChannel={onSelectChannel}
onSelectHome={onSelectHome}
onSelectProjects={onSelectProjects}
onSelectPulse={onSelectPulse}
onSelectWorkflows={onSelectWorkflows}
projectsOverviewActive={projectsOverviewActive}
searchChannels={searchChannels}
searchFocusRequest={searchFocusRequests[0]}
scopeSearchFocusRequest={searchFocusRequests[1]}
selectedView={selectedView}
suggestionChannels={channels}
/>
@@ -614,6 +609,9 @@ export function AppSidebar({
data-sidebar-background
data-testid="sidebar-scroll-content"
>
{/* biome-ignore format: keep compact to stay within file size limit */}
<AppSidebarPrimaryMenu homeBadgeCount={homeBadgeCount} onSelectAgents={onSelectAgents} onSelectHome={onSelectHome} onSelectProjects={onSelectProjects} onSelectPulse={onSelectPulse} onSelectWorkflows={onSelectWorkflows} projectsOverviewActive={projectsOverviewActive} selectedView={selectedView} />
<SidebarProjectsSection />
{isLoading ? (
@@ -32,7 +32,7 @@ type AppSidebarPrimaryMenuProps = {
selectedView: SidebarSelectedView;
};
type AppSidebarPinnedHeaderProps = AppSidebarPrimaryMenuProps & {
type AppSidebarPinnedHeaderProps = {
channelLabels: Record<string, string>;
currentChannelId?: string | null;
currentPubkey?: string;
@@ -52,23 +52,15 @@ export function AppSidebarPinnedHeader({
channelLabels,
currentChannelId,
currentPubkey,
homeBadgeCount,
onBrowseChannels,
onCreateAgent,
onCreateChannel,
onOpenDm,
onOpenSearchResult,
onSelectAgents,
onSelectChannel,
onSelectHome,
onSelectProjects,
onSelectPulse,
onSelectWorkflows,
projectsOverviewActive,
searchChannels,
searchFocusRequest,
scopeSearchFocusRequest,
selectedView,
suggestionChannels,
}: AppSidebarPinnedHeaderProps) {
return (
@@ -79,7 +71,6 @@ export function AppSidebarPinnedHeader({
<TopbarSearch
channelLabels={channelLabels}
channels={searchChannels}
className="mb-2"
currentChannelId={currentChannelId}
currentPubkey={currentPubkey}
focusRequest={searchFocusRequest}
@@ -92,16 +83,6 @@ export function AppSidebarPinnedHeader({
scopeFocusRequest={scopeSearchFocusRequest}
suggestionChannels={suggestionChannels}
/>
<AppSidebarPrimaryMenu
homeBadgeCount={homeBadgeCount}
onSelectAgents={onSelectAgents}
onSelectHome={onSelectHome}
onSelectProjects={onSelectProjects}
onSelectPulse={onSelectPulse}
onSelectWorkflows={onSelectWorkflows}
projectsOverviewActive={projectsOverviewActive}
selectedView={selectedView}
/>
</div>
);
}
@@ -118,7 +99,7 @@ export function AppSidebarPrimaryMenu({
}: AppSidebarPrimaryMenuProps) {
return (
<SidebarHeader
className="relative z-40 cursor-default select-none px-0 pb-2 pt-0"
className="relative z-40 cursor-default select-none px-2 pb-2 pt-0"
data-tauri-drag-region
data-testid="sidebar-primary-menu"
>
@@ -322,14 +322,7 @@ function SidebarProjectsSectionContent() {
isActive={isActive}
isExpanded={isExpanded}
onDelete={() => setProjectToDelete(project)}
onOpen={() => {
if (isActive) {
setProjectExpanded(project, !isExpanded);
return;
}
setProjectExpanded(project, true);
void goProject(project.id);
}}
onOpen={() => setProjectExpanded(project, !isExpanded)}
onRemove={() => handleRemove(project)}
project={project}
/>
@@ -93,7 +93,7 @@ async function expectBuzzSidebarPalette(page: Page, mode: "light" | "dark") {
(element) => getComputedStyle(element, "::before").backgroundColor,
);
expect(pinnedSpacerColor).toBe("rgba(0, 0, 0, 0)");
await expect(pinnedHeader.getByTestId("open-agents-view")).toBeVisible();
await expect(scrollContent.getByTestId("open-agents-view")).toBeVisible();
const searchBox = await search.boundingBox();
const pinnedHeaderBox = await pinnedHeader.boundingBox();
const primaryMenuBox = await primaryMenu.boundingBox();
@@ -129,7 +129,7 @@ async function expectBuzzSidebarPalette(page: Page, mode: "light" | "dark") {
expect(searchBox.y + searchBox.height).toBeLessThanOrEqual(
pinnedHeaderBox.y + pinnedHeaderBox.height,
);
expect(primaryMenuBox.y).toBeLessThan(
expect(primaryMenuBox.y).toBeGreaterThanOrEqual(
pinnedHeaderBox.y + pinnedHeaderBox.height,
);
for (const rowBox of [primaryRowBox, activeRowBox, hoverRowBox]) {
@@ -471,7 +471,13 @@ test("multi-repository projects switch the active repository", async ({
},
);
await projectRow.click();
await expect(page).toHaveURL(/\/projects\//);
await expect(page).not.toHaveURL(/\/projects\//);
await expect(projectRow).toHaveAttribute("aria-expanded", "false");
await expect(relayToolsRepository).toBeHidden();
await projectRow.click();
await expect(page).not.toHaveURL(/\/projects\//);
await expect(projectRow).toHaveAttribute("aria-expanded", "true");
await expect(relayToolsRepository).toBeVisible();
const projectSidebarMetrics = await sidebarScrollContent.evaluate(
(element) => {
@@ -497,7 +503,7 @@ test("multi-repository projects switch the active repository", async ({
return element.scrollTop;
});
await projectRow.click();
await expect(page).toHaveURL(/\/projects\//);
await expect(page).not.toHaveURL(/\/projects\//);
await expect(projectRow).toHaveAttribute("aria-expanded", "true");
await expect
.poll(() =>
@@ -570,6 +576,30 @@ test("multi-repository projects switch the active repository", async ({
.toBe(true);
});
test("latest files commit opens its detail without a divider", async ({
page,
}) => {
await enableProjectsFeature(page);
await installMockBridge(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-projects-view").click();
await page.getByTestId("projects-section-projects").click();
const projectEntry = page
.locator(
'[data-testid="project-card-buzz"], [data-testid="project-row-buzz"]',
)
.first();
await expect(projectEntry).toBeVisible({ timeout: 10_000 });
await projectEntry.click();
await page.getByRole("tab", { name: "Files" }).click();
const latestCommit = page.getByTestId("project-repository-latest-commit");
await expect(latestCommit).toBeVisible();
await expect(latestCommit).toHaveCSS("border-bottom-width", "0px");
await latestCommit.click();
await expect(page.getByTestId("project-commit-detail")).toBeVisible();
});
test("commit detail opens from the commits feed with a diff", async ({
page,
}) => {
+19 -7
View File
@@ -204,6 +204,7 @@ test("PR creator/owner can toggle draft, request reviews, and approve", async ({
).toBeVisible();
await expect(page.getByTestId("project-context-chat-agent")).toBeVisible();
await expect(page.getByTestId("project-context-discuss")).toBeVisible();
await expect(page.getByTestId("project-context-branch")).toContainText("→");
await expect(page.getByTestId("project-context-review-summary")).toHaveCount(
0,
);
@@ -264,6 +265,12 @@ test("PR creator/owner can toggle draft, request reviews, and approve", async ({
await expect(page.getByText("Requested a review from bob")).toBeVisible({
timeout: 10_000,
});
await expect(page.getByTestId("project-review-summary")).toHaveCount(0);
await expect(
page
.getByTestId("project-reviewer-decision")
.filter({ hasText: "Awaiting review from bob" }),
).toHaveCount(1);
await waitForAnimations(page);
await page.screenshot({
@@ -287,9 +294,12 @@ test("PR creator/owner can toggle draft, request reviews, and approve", async ({
.getByTestId("project-pull-request-timeline-row")
.filter({ hasText: "requested changes" }),
).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("project-review-summary")).toHaveText(
"Changes requested",
);
await expect(page.getByTestId("project-review-summary")).toHaveCount(0);
await expect(
page
.getByTestId("project-reviewer-decision")
.filter({ hasText: "Changes requested" }),
).toHaveCount(1);
const changeRequestEvent = await page.evaluate(() =>
window.__BUZZ_E2E_SIGNED_EVENTS__
?.filter(
@@ -368,10 +378,12 @@ test("PR creator/owner can toggle draft, request reviews, and approve", async ({
await expect(
page.getByRole("button", { name: "Approve", exact: true }),
).toHaveCount(0);
await expect(page.getByTestId("project-review-summary")).toHaveText(
"Awaiting review",
);
await expect(page.getByText("Approved", { exact: true })).toBeVisible();
await expect(page.getByTestId("project-review-summary")).toHaveCount(0);
await expect(
page
.getByTestId("project-reviewer-decision")
.filter({ hasText: "Approved by" }),
).toHaveCount(1);
const approvalEvent = await page.evaluate(() =>
window.__BUZZ_E2E_SIGNED_EVENTS__
?.filter(
@@ -40,7 +40,19 @@ test("projects activity overview screenshot", async ({ page }) => {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByTestId("open-projects-view").click();
await expect(page.getByTestId("projects-page-tabs")).toBeVisible();
await expect(page.getByTestId("projects-page-header")).toBeVisible();
const activityHeader = page.getByTestId("projects-page-header");
const relayIcon = page.getByTestId("projects-activity-relay-icon");
await expect(activityHeader).toBeVisible();
await expect(relayIcon).toBeVisible();
const [activityHeaderBox, relayIconBox] = await Promise.all([
activityHeader.boundingBox(),
relayIcon.boundingBox(),
]);
expect(activityHeaderBox).not.toBeNull();
expect(relayIconBox).not.toBeNull();
expect((relayIconBox?.y ?? 0) + (relayIconBox?.height ?? 0)).toBeLessThan(
activityHeaderBox?.y ?? 0,
);
await expect(page.getByTestId("projects-activity-search")).toBeVisible();
await expect(page.getByTestId("projects-activity-intro")).toContainText(
"Projects Activity",
@@ -279,10 +291,10 @@ test("projects v3 workspace screenshot states", async ({ page }) => {
name: "Fetch",
exact: true,
});
await expect(peopleSection).toHaveCSS("border-top-width", "1px");
await expect(peopleSection).toHaveCSS("border-top-width", "0px");
await expect(detailsHeading.locator("..")).toHaveCSS(
"border-top-width",
"1px",
"0px",
);
expect(
await repositoryHeading.evaluate(
@@ -305,9 +317,9 @@ test("projects v3 workspace screenshot states", async ({ page }) => {
(peopleSectionBounds?.y ?? 0) -
((actionsSectionBounds?.y ?? 0) + (actionsSectionBounds?.height ?? 0)),
).toBe(8);
expect(
(detailsHeadingBounds?.y ?? 0) - (peopleSectionBounds?.y ?? 0) - 1,
).toBe(8);
expect((detailsHeadingBounds?.y ?? 0) - (peopleSectionBounds?.y ?? 0)).toBe(
8,
);
const people = repositoryActionsPanel.getByTestId(
"project-repository-person",
);
@@ -768,6 +780,13 @@ test("projects v3 workspace screenshot states", async ({ page }) => {
await expect(
workspacePanel.getByTestId("project-repository-entry-icon").first(),
).toHaveCSS("border-radius", "8px");
const repositoryEntryCell = workspacePanel
.getByTestId("project-repository-entry-row")
.first()
.locator("td")
.first();
await expect(repositoryEntryCell).toHaveCSS("border-radius", "0px");
await expect(repositoryEntryCell).toHaveCSS("border-bottom-width", "0px");
await chatPanelTab.click();
await expect(agentContext).toContainText("Files");
await expect(
@@ -861,7 +880,7 @@ test("projects v3 workspace screenshot states", async ({ page }) => {
repositoryActionsPanel
.getByTestId("project-repository-people")
.locator(".."),
).toHaveCSS("border-top-width", "1px");
).toHaveCSS("border-top-width", "0px");
await expect(
repositoryActionsPanel.getByRole("heading", {
name: "Task activity",
+41
View File
@@ -528,6 +528,47 @@ test("places sidebar search above the primary navigation", async ({ page }) => {
expect(searchBox.y + searchBox.height).toBeLessThanOrEqual(menuBox.y);
});
test("keeps only search pinned while primary navigation scrolls", async ({
page,
}) => {
await loadTheme(page, "github-light");
const search = page.getByTestId("open-search");
const primaryMenu = page.getByTestId("sidebar-primary-menu");
const sidebarScroller = page.locator(".buzz-sidebar-scrollbar");
const [initialSearchBox, initialMenuBox] = await Promise.all([
search.boundingBox(),
primaryMenu.boundingBox(),
]);
expect(initialSearchBox).not.toBeNull();
expect(initialMenuBox).not.toBeNull();
const scrollTop = await sidebarScroller.evaluate((element) => {
element.scrollTop = Math.min(
120,
Math.max(0, element.scrollHeight - element.clientHeight),
);
return element.scrollTop;
});
expect(scrollTop).toBeGreaterThan(0);
await expect
.poll(() =>
sidebarScroller.evaluate((element) => Math.round(element.scrollTop)),
)
.toBe(Math.round(scrollTop));
const [scrolledSearchBox, scrolledMenuBox] = await Promise.all([
search.boundingBox(),
primaryMenu.boundingBox(),
]);
expect(scrolledSearchBox).not.toBeNull();
expect(scrolledMenuBox).not.toBeNull();
expect(
Math.abs((scrolledSearchBox?.y ?? 0) - (initialSearchBox?.y ?? 0)),
).toBeLessThanOrEqual(1);
expect(scrolledMenuBox?.y ?? 0).toBeLessThan(initialMenuBox?.y ?? 0);
});
test("sidebar rail resizes without toggling the sidebar", async ({ page }) => {
await page.goto("/");
const rail = page.getByRole("button", { name: "Resize sidebar" });