desktop: keep PR metadata readable in Inbox

Wrap pull request metadata between complete phrases and truncate long channel names so the Inbox detail rail cannot compress copy into word-wide columns.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
Thomas Petersen
2026-08-12 11:02:45 -04:00
parent fc6216cb17
commit 82b63b9a7a
3 changed files with 73 additions and 11 deletions

View File

@@ -17,7 +17,7 @@ export function ProjectOriginReference({
if (channelId) {
return (
<span
className="inline-flex min-w-0 items-center gap-1"
className="inline-flex max-w-full min-w-0 items-center gap-1"
title={
channel
? "Origin is claimed by the event author and is not relay-verified."
@@ -27,20 +27,30 @@ export function ProjectOriginReference({
`Origin channel ${channelId} is not visible to you. Origin is claimed by the event author and is not relay-verified.`
}
>
<span>started from</span>
<span
className="shrink-0 whitespace-nowrap"
data-project-metadata-phrase
>
started from
</span>
{channel ? (
<button
aria-label={`Open author-claimed origin channel #${channel.name}`}
className="truncate font-medium text-foreground underline-offset-2 hover:underline"
className="min-w-0 truncate font-medium text-foreground underline-offset-2 hover:underline"
onClick={() => void goChannel(channel.id)}
type="button"
>
#{channel.name}
</button>
) : (
<span>a private channel</span>
<span className="shrink-0 whitespace-nowrap">a private channel</span>
)}
<span>(author-claimed)</span>
<span
className="shrink-0 whitespace-nowrap"
data-project-metadata-phrase
>
(author-claimed)
</span>
</span>
);
}
@@ -48,10 +58,15 @@ export function ProjectOriginReference({
if (agentName) {
return (
<span
className="inline-flex min-w-0 items-center gap-1"
className="inline-flex max-w-full min-w-0 items-center gap-1"
title="The private conversation identifier is intentionally omitted."
>
<span>started privately with</span>
<span
className="shrink-0 whitespace-nowrap"
data-project-metadata-phrase
>
started privately with
</span>
<span className="truncate font-medium text-foreground">
{agentName}
</span>

View File

@@ -359,9 +359,15 @@ export function PullRequestDetailHeader({
testId="project-pull-request-copy-link"
/>
</h3>
<p className="flex items-center gap-1 text-xs font-medium text-muted-foreground">
<GitPullRequest className="h-3.5 w-3.5" />
<span className="flex min-w-0 items-center gap-1">
<p
className="flex flex-wrap items-center gap-x-1 gap-y-1 text-xs font-medium text-muted-foreground"
data-testid="project-pull-request-detail-metadata"
>
<GitPullRequest className="h-3.5 w-3.5 shrink-0" />
<span
className="flex min-w-0 items-center gap-1"
data-project-metadata-phrase
>
<AuthorIdentity
avatarSize="xs"
profiles={profiles}
@@ -372,7 +378,11 @@ export function PullRequestDetailHeader({
{authorLabel}
</ProfileAuthorName>
</span>
<span title={formatExactTimestamp(pullRequest.createdAt)}>
<span
className="shrink-0 whitespace-nowrap"
data-project-metadata-phrase
title={formatExactTimestamp(pullRequest.createdAt)}
>
created {relativeTime(pullRequest.createdAt)}
</span>
<ProjectOriginReference

View File

@@ -143,4 +143,41 @@ test("Buzz Git pull request renders and stays actionable in Inbox", async ({
await detail.screenshot({
path: "test-results/project-inbox/01-pull-request-detail.png",
});
// At the wide two-column breakpoint, metadata may wrap between phrases but
// must never compress individual phrases into word-wide columns.
await page.setViewportSize({ width: 1440, height: 900 });
await expect
.poll(() =>
layout.evaluate(
(element) =>
getComputedStyle(element)
.gridTemplateColumns.split(" ")
.filter(Boolean).length,
),
)
.toBe(2);
await expect(
detail.getByRole("button", {
name: "Open author-claimed origin channel #general",
}),
).toBeVisible();
const metadataPhrases = detail.locator("[data-project-metadata-phrase]");
await expect(metadataPhrases).not.toHaveCount(0);
const phraseLayouts = await metadataPhrases.evaluateAll((phrases) =>
phrases.map((phrase) => {
const style = getComputedStyle(phrase);
return {
height: phrase.getBoundingClientRect().height,
lineHeight: Number.parseFloat(style.lineHeight),
};
}),
);
for (const phrase of phraseLayouts) {
expect(phrase.height).toBeLessThanOrEqual(phrase.lineHeight * 1.5);
}
await waitForAnimations(page);
await detail.screenshot({
path: "test-results/project-inbox/02-pull-request-detail-wide.png",
});
});