Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6f32502
fix(web): compact pull request metadata and completed checks
maria-rcks Sep 7, 2026
ee9d3e2
fix(web): mute pull request panel chrome
maria-rcks Sep 8, 2026
ef1a62d
fix(web): reuse muted ghost check disclosure
maria-rcks Sep 8, 2026
7bc7ab1
fix(web): separate pull request metadata rows
maria-rcks Sep 10, 2026
f3ec493
chore: merge upstream main into pull request layout
maria-rcks Sep 10, 2026
345ff95
fix(web): simplify checks and restore comment borders
maria-rcks Sep 10, 2026
ef63250
fix(web): keep neutral pull request checks visible
maria-rcks Sep 10, 2026
9dcd8b7
fix(web): collapse all pull request checks
maria-rcks Sep 10, 2026
95eac41
fix(web): simplify pull request section headings
maria-rcks Sep 10, 2026
623d2c1
fix(web): align pull request headings left
maria-rcks Sep 10, 2026
7828ad4
fix(web): keep comment sorting visible without dividers
maria-rcks Sep 10, 2026
619d19b
fix(web): restore pull request action button styles
maria-rcks Sep 10, 2026
832c9b1
fix(web): share human review icons across pull request lists
maria-rcks Sep 10, 2026
c985a96
fix(web): reconcile review icons with the current pr list
maria-rcks Sep 10, 2026
2a4c098
fix(web): place linked pr timestamps before branches
maria-rcks Sep 10, 2026
1ba112d
fix(web): align linked pr timestamps below diff counts
maria-rcks Sep 11, 2026
5c277b3
fix(web): reconcile pr controls with current main
maria-rcks Sep 11, 2026
c54f5c0
fix(web): group pr review and ci status beside diff counts
maria-rcks Sep 11, 2026
408b3ab
fix(web): match approval icons to passing ci color
maria-rcks Sep 11, 2026
f24366c
fix(web): put linked pr checks after review status
maria-rcks Sep 11, 2026
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
7 changes: 6 additions & 1 deletion apps/web/src/components/pullRequest/PullRequestMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ export function PullRequestMarkdown({
[repositoryUrl],
);
return (
<div className={cn("space-y-3", className)}>
<div
className={cn(
"space-y-3 [&_[data-markdown-details]]:border-0 [&_[data-markdown-details-summary]]:text-foreground/80 [&_[data-markdown-details-summary]>svg]:text-muted-foreground/60",
className,
)}
>
{segments.map((segment) => {
if (segment.kind === "markdown") {
return (
Expand Down
157 changes: 92 additions & 65 deletions apps/web/src/components/pullRequest/PullRequestSummaryTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import {
ChevronRightIcon,
GitPullRequestClosedIcon,
HammerIcon,
MessageSquareIcon,
PencilIcon,
RotateCcwIcon,
SendIcon,
TagIcon,
UsersIcon,
} from "lucide-react";
import { useRef, useState, type ReactNode } from "react";
import { useId, useRef, useState, type ReactNode } from "react";

import { useAtomCommand } from "~/state/use-atom-command";
import { pullRequestEnvironment } from "~/state/pullRequests";
Expand All @@ -41,6 +40,7 @@ import {
pullRequestReviewOutcomeLabel,
pullRequestReviewOutcomeRingClassName,
pullRequestReviewOutcomeStaleLabel,
summarizePullRequestChecks,
} from "./pullRequestPresentation";
import { PullRequestLabelPicker } from "./PullRequestLabelPicker";
import { PullRequestReviewerPicker } from "./PullRequestReviewerPicker";
Expand Down Expand Up @@ -172,7 +172,7 @@ function CollapsedComment({
const [open, setOpen] = useState(false);
return (
<Collapsible open={open} onOpenChange={setOpen}>
<article className="group rounded-lg border border-border/60 [contain-intrinsic-block-size:44px] [content-visibility:auto]">
<article className="group rounded-lg [contain-intrinsic-block-size:44px] [content-visibility:auto]">
<CollapsibleTrigger
className={cn(
"flex w-full items-center gap-2 p-3 text-left transition-opacity hover:opacity-100",
Expand All @@ -187,7 +187,7 @@ function CollapsedComment({
<ChevronDownIcon
aria-hidden
className={cn(
"size-3.5 shrink-0 text-muted-foreground transition-transform",
"size-3.5 shrink-0 text-muted-foreground/60 transition-transform",
open && "rotate-180",
)}
/>
Expand Down Expand Up @@ -229,8 +229,8 @@ function MetaRow({
children: ReactNode;
}) {
return (
<div className="grid min-h-8 grid-cols-[6rem_minmax(0,1fr)] items-center gap-2 py-1.5 text-xs">
<span className="flex min-w-0 items-center gap-1.5 text-muted-foreground">
<div className="grid min-w-0 grid-cols-[6rem_minmax(0,1fr)] items-center gap-2 text-xs">
<span className="flex items-center gap-1.5 text-muted-foreground">
{icon}
{label}
</span>
Expand Down Expand Up @@ -286,16 +286,16 @@ function Section({
where it started. Opaque, because the rows it covers scroll beneath it. */}
<div
ref={headingRef}
className="sticky top-0 z-10 flex w-full items-center border-t border-border/60 bg-background pr-4"
className="sticky top-0 z-10 flex w-full items-center bg-background pr-4"
>
{/* Title first, chevron riding to its right, count last: the row reads as a heading
with an affordance rather than a tree node. */}
<CollapsibleTrigger className="flex min-w-0 flex-1 items-center gap-1.5 px-4 py-3 text-left text-sm font-medium">
<CollapsibleTrigger className="flex min-w-0 flex-1 items-center gap-1.5 px-4 py-3 text-left text-xs font-medium text-muted-foreground hover:text-foreground">
<span>{title}</span>
<ChevronRightIcon
aria-hidden
className={cn(
"size-3.5 text-muted-foreground transition-transform",
"size-3.5 text-muted-foreground/60 transition-transform",
open && "rotate-90",
)}
/>
Expand Down Expand Up @@ -461,6 +461,12 @@ export function PullRequestSummaryTab({
// Keyed by the pull request, so opening another one starts at the end of its conversation
// rather than wherever the last one had been read back to.
const [shown, setShown] = useState({ url: detail.url, count: COMMENT_PAGE });
const checksId = useId();
const [expandedChecksUrl, setExpandedChecksUrl] = useState<string | null>(null);
const showCompletedChecks = expandedChecksUrl === detail.url;
const completedCheckCount = detail.checks.filter((check) =>
["success", "skipped", "neutral"].includes(check.status),
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Outdated
).length;
const shownComments = shown.url === detail.url ? shown.count : COMMENT_PAGE;
// Windowed by recency regardless of display order: expanding always reaches further back in
// time, whether the newest comment currently reads first or last.
Expand Down Expand Up @@ -596,8 +602,8 @@ export function PullRequestSummaryTab({

return (
<div className="h-full overflow-y-auto" data-pull-request-summary-scroll>
<section className="px-4 py-3">
<div>
<section className="px-4 py-2.5">
<div className="space-y-2">
<MetaRow icon={<UsersIcon className="size-3.5" />} label="Reviewers">
<span className="flex min-w-0 flex-wrap items-center gap-1.5">
{reviewerEntries.length === 0 ? (
Expand Down Expand Up @@ -701,7 +707,7 @@ export function PullRequestSummaryTab({
return (
<span
key={label.name}
className="inline-flex max-w-48 items-center gap-1.5 rounded-full border border-border/70 bg-muted/40 py-0.5 pl-1.5 pr-2 text-xs"
className="inline-flex max-w-48 items-center gap-1.5 rounded-full bg-muted/40 py-0.5 pl-1.5 pr-2 text-xs"
>
<span
aria-hidden
Expand All @@ -724,19 +730,10 @@ export function PullRequestSummaryTab({
</span>
</MetaRow>
) : null}
<MetaRow icon={<MessageSquareIcon className="size-3.5" />} label="Comments">
{activityPending
? "Loading conversation…"
: activityError
? "Conversation unavailable"
: detail.commentCount === 1
? "1 comment"
: `${detail.commentCount} comments`}
</MetaRow>
</div>
</section>

<Section title="Description">
<section aria-label="Description" className="px-4 pt-2 pb-4">
Comment thread
maria-rcks marked this conversation as resolved.
<div className="group">
{bodyScope === detail.url ? (
<PullRequestMarkdownEditor
Expand Down Expand Up @@ -783,57 +780,87 @@ export function PullRequestSummaryTab({
onRefresh={onRefresh}
/>
</div>
</Section>
</section>

<Section title="Checks" count={detail.checks.length}>
<Section title="Checks">
{detail.checks.length === 0 ? (
<p className="text-xs text-muted-foreground">No checks reported.</p>
) : (
<div className="space-y-0.5">
{detail.checks.map((check, index) => {
const finding = { kind: "check", check } as const;
const failing = check.status === "failure" || check.status === "cancelled";
return (
<div
// Position too: the host decides how many runs share a name, and a repeated
// key would be a rendering fault on top of whatever the list already says.
key={`${index}:${check.name}:${check.url ?? ""}`}
className="group flex items-center gap-1 rounded-md pr-1 hover:bg-accent/60"
<div>
<div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-2 text-xs">
<span className="text-muted-foreground">
{summarizePullRequestChecks(detail.checks)}
</span>
{completedCheckCount > 0 ? (
<Button
size="xs"
variant="ghost-muted"
aria-expanded={showCompletedChecks}
aria-controls={checksId}
onClick={() => setExpandedChecksUrl(showCompletedChecks ? null : detail.url)}
>
<button
type="button"
disabled={!check.url}
onClick={() => check.url && openCheck(check.url)}
<ChevronRightIcon
className={cn(
"flex min-w-0 flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs",
check.url ? undefined : "cursor-default",
"size-3.5 text-muted-foreground/60",
showCompletedChecks && "rotate-90",
)}
/>
{showCompletedChecks ? "Hide" : "Show"} {completedCheckCount} completed
</Button>
) : null}
</div>
<div id={checksId} className="mt-2">
{detail.checks.map((check, index) => {
if (
!showCompletedChecks &&
["success", "skipped", "neutral"].includes(check.status)
) {
return null;
}
const finding = { kind: "check", check } as const;
const failing = check.status === "failure" || check.status === "cancelled";
return (
<div
// Position too: the host decides how many runs share a name, and a repeated
// key would be a rendering fault on top of whatever the list already says.
key={`${index}:${check.name}:${check.url ?? ""}`}
className="group flex items-center gap-2 rounded-md pr-1 hover:bg-accent/60"
>
<PullRequestCheckStatusIcon status={check.status} />
<span className="min-w-0 flex-1 truncate">{check.name}</span>
<span className="shrink-0 text-muted-foreground">
{pullRequestCheckStatusLabel(check)}
</span>
</button>
{/* Only where there is something to fix. A passing check has no failure to
reproduce, and the button would be an invitation to waste a thread. */}
{onFixFinding && failing ? (
<Button
size="xs"
variant="ghost"
className="shrink-0"
disabled={pendingFinding !== null && pendingFinding !== undefined}
onClick={() => onFixFinding(finding)}
<button
type="button"
disabled={!check.url}
onClick={() => check.url && openCheck(check.url)}
className={cn(
"flex min-w-0 flex-1 items-start gap-2 rounded-md px-2 py-2 text-left text-xs leading-5 [&>svg]:mt-0.5",
check.url ? undefined : "cursor-default",
)}
>
<HammerIcon className="size-3" />
{pendingFinding === pullRequestFindingKey(finding)
? "Preparing..."
: fixCheckLabel}
</Button>
) : null}
</div>
);
})}
<PullRequestCheckStatusIcon status={check.status} />
<span className="min-w-0 flex-1 wrap-anywhere">{check.name}</span>
<span className="shrink-0 text-muted-foreground">
{pullRequestCheckStatusLabel(check)}
</span>
</button>
{/* Only where there is something to fix. A passing check has no failure to
reproduce, and the button would be an invitation to waste a thread. */}
{onFixFinding && failing ? (
<Button
size="xs"
variant="ghost"
className="shrink-0"
disabled={pendingFinding !== null && pendingFinding !== undefined}
onClick={() => onFixFinding(finding)}
>
<HammerIcon className="size-3" />
{pendingFinding === pullRequestFindingKey(finding)
? "Preparing..."
: fixCheckLabel}
</Button>
) : null}
</div>
);
})}
</div>
</div>
)}
</Section>
Expand Down Expand Up @@ -935,7 +962,7 @@ export function PullRequestSummaryTab({
key={comment.id}
// Offscreen comments skip style, layout and paint. Bot comments carry pages of
// highlighted code, and the conversation is below the description either way.
className="group rounded-lg border border-border/60 p-3 [contain-intrinsic-block-size:120px] [content-visibility:auto]"
className="group rounded-lg py-3 [contain-intrinsic-block-size:120px] [content-visibility:auto]"
>
<div className="flex items-start gap-2">
<span className="flex min-w-0 flex-1 items-center gap-2 text-xs text-muted-foreground">
Expand Down
Loading