From 2576705db0924f185d408c7e52431f99d2d2cf0f Mon Sep 17 00:00:00 2001 From: Thomas Petersen Date: Wed, 12 Aug 2026 07:49:31 -0400 Subject: [PATCH] desktop: make project work-item rows easier to scan Replace opaque event hashes with author, repository, branch, and label context so duplicate PR and issue titles remain distinguishable. Signed-off-by: Thomas Petersen --- .../projects/ui/ProjectsIssuesList.tsx | 84 +++++++++++-------- .../projects/ui/ProjectsPullRequestsList.tsx | 82 +++++++++++------- .../projects/ui/projectListRowStyles.ts | 2 +- .../tests/e2e/projects-v3-screenshots.spec.ts | 25 ++++++ 4 files changed, 126 insertions(+), 67 deletions(-) diff --git a/desktop/src/features/projects/ui/ProjectsIssuesList.tsx b/desktop/src/features/projects/ui/ProjectsIssuesList.tsx index 25ad50c26..17accc32c 100644 --- a/desktop/src/features/projects/ui/ProjectsIssuesList.tsx +++ b/desktop/src/features/projects/ui/ProjectsIssuesList.tsx @@ -59,20 +59,26 @@ function nextStepLabel(status: ProjectIssue["status"]) { return "Open issue"; } +function issueLabelsSummary(issue: ProjectIssue) { + const visibleLabels = issue.labels.slice(0, 2); + if (visibleLabels.length === 0) return null; + const hiddenCount = issue.labels.length - visibleLabels.length; + return `${visibleLabels.join(", ")}${hiddenCount > 0 ? ` +${hiddenCount}` : ""}`; +} + function IssueHeader({ authorTestId, - includeDate = true, issue, profiles, - project, + repository, }: { authorTestId?: string; - includeDate?: boolean; issue: ProjectIssue; profiles?: UserProfileLookup; - project: Project; + repository: Repository; }) { const authorLabel = resolveUserLabel({ profiles, pubkey: issue.author }); + const labelsSummary = issueLabelsSummary(issue); return (
@@ -84,34 +90,24 @@ function IssueHeader({
- {project.name} - {includeDate ? ( + + · + {repository.name} + {labelsSummary ? ( <> - · - created {relativeTime(issue.createdAt)} + · + {labelsSummary} ) : null} - · - - by - + + · - {includeDate ? ( - <> - · - {issue.status} - - ) : ( - <> - · - {issue.status} - - )} + {issue.status}
); @@ -122,12 +118,16 @@ function IssueGridCard({ onOpen, profiles, project, + repository, }: { issue: ProjectIssue; onOpen: (project: Project, issue: ProjectIssue) => void; profiles?: UserProfileLookup; project: Project; + repository: Repository; }) { + const authorLabel = resolveUserLabel({ profiles, pubkey: issue.author }); + return ( onOpen(project, issue)} type="button" > - View {issue.title} + + View issue {issue.title} by {authorLabel} in {repository.name} +
- +
))}
@@ -345,6 +356,7 @@ export function ProjectsIssuesList({ } profiles={profiles} project={project} + repository={repository} /> ))}
diff --git a/desktop/src/features/projects/ui/ProjectsPullRequestsList.tsx b/desktop/src/features/projects/ui/ProjectsPullRequestsList.tsx index 43c3f1322..3e7128146 100644 --- a/desktop/src/features/projects/ui/ProjectsPullRequestsList.tsx +++ b/desktop/src/features/projects/ui/ProjectsPullRequestsList.tsx @@ -58,21 +58,31 @@ function nextStepLabel(status: ProjectPullRequest["status"]) { return "Review PR"; } +function pullRequestBranchLabel(pullRequest: ProjectPullRequest) { + if (pullRequest.branchName && pullRequest.targetBranch) { + return `${pullRequest.branchName} → ${pullRequest.targetBranch}`; + } + return pullRequest.branchName ?? pullRequest.targetBranch; +} + function PullRequestGridCard({ project, profiles, pullRequest, + repository, onOpen, }: { project: Project; profiles?: UserProfileLookup; pullRequest: ProjectPullRequest; + repository: Repository; onOpen: (project: Project, pullRequest: ProjectPullRequest) => void; }) { const authorLabel = resolveUserLabel({ profiles, pubkey: pullRequest.author, }); + const branchLabel = pullRequestBranchLabel(pullRequest); return ( onOpen(project, pullRequest)} type="button" > - View {pullRequest.title} + + View pull request {pullRequest.title} by {authorLabel} in{" "} + {repository.name} +
@@ -95,9 +108,21 @@ function PullRequestGridCard({ {pullRequest.title}

-

- {project.name} -

+
+ + · + {repository.name} + {branchLabel ? ( + <> + · + {branchLabel} + + ) : null} +
@@ -186,20 +206,20 @@ function PullRequestListRow({
- {project.name} - · - - #{pullRequest.id.slice(0, 8)} - - - by - - + + · + {repository.name} + {branchLabel ? ( + <> + · + {branchLabel} + + ) : null} · {pullRequest.status}
@@ -308,6 +328,7 @@ export function ProjectsPullRequestsList({ profiles={profiles} project={project} pullRequest={pullRequest} + repository={repository} /> ))}
@@ -333,6 +354,7 @@ export function ProjectsPullRequestsList({ profiles={profiles} project={project} pullRequest={pullRequest} + repository={repository} /> ))}
diff --git a/desktop/src/features/projects/ui/projectListRowStyles.ts b/desktop/src/features/projects/ui/projectListRowStyles.ts index 3a8dad62e..763420cc0 100644 --- a/desktop/src/features/projects/ui/projectListRowStyles.ts +++ b/desktop/src/features/projects/ui/projectListRowStyles.ts @@ -17,7 +17,7 @@ export const PROJECT_LIST_ROW_META_TEXT_CLASS = export const PROJECT_LIST_ROW_META_CLASS = `flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5 ${PROJECT_LIST_ROW_META_TEXT_CLASS}`; export const PROJECT_LIST_ROW_SUBTEXT_CLASS = - "mt-0.5 text-sm leading-5 text-muted-foreground"; + "mt-0.5 text-xs leading-4 text-muted-foreground"; export const PROJECT_LIST_ROW_PREVIEW_CLASS = `line-clamp-1 ${PROJECT_LIST_ROW_SUBTEXT_CLASS}`; diff --git a/desktop/tests/e2e/projects-v3-screenshots.spec.ts b/desktop/tests/e2e/projects-v3-screenshots.spec.ts index 4aa2bb579..8e312ecf7 100644 --- a/desktop/tests/e2e/projects-v3-screenshots.spec.ts +++ b/desktop/tests/e2e/projects-v3-screenshots.spec.ts @@ -75,3 +75,28 @@ test("projects v3 workspace screenshot states", async ({ page }) => { await waitForAnimations(page); await page.screenshot({ path: `${SHOTS}/04-pr-detail.png` }); }); + +test("projects v3 work-item list metadata", async ({ page }) => { + await page.addInitScript(() => { + window.localStorage.setItem("buzz.projects.viewMode", "list"); + }); + await installMockBridge(page); + await page.goto("/", { waitUntil: "domcontentloaded" }); + await page.getByTestId("open-projects-view").click(); + + await page.getByTestId("projects-section-prs").click(); + const pullRequestRow = page.getByTestId(/^projects-pr-row-/).first(); + await expect(pullRequestRow).toBeVisible(); + await expect(pullRequestRow).toContainText("feature/mock-2-0"); + await expect(pullRequestRow).not.toContainText(/#[0-9a-f]{8}/); + await waitForAnimations(page); + await page.screenshot({ path: `${SHOTS}/05-pr-list-metadata.png` }); + + await page.getByTestId("projects-section-issues").click(); + const issueRow = page.getByTestId(/^projects-issue-row-/).first(); + await expect(issueRow).toBeVisible(); + await expect(issueRow).toContainText("relay-tools"); + await expect(issueRow).not.toContainText(/#[0-9a-f]{8}/); + await waitForAnimations(page); + await page.screenshot({ path: `${SHOTS}/06-issue-list-metadata.png` }); +});