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
3 changes: 3 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,9 @@ Do not add motion for decoration. New animation must honor
File/folder rows, filter, summary toggle, and review progress retain desktop
control heights and spacing; do not apply the general mobile enlargement here.
- Hide redundant summary/shortcut detail before removing essential actions.
Keep the Files changed total and inline added/deleted/modified/renamed counts
visible in the drawer. Use file icons, semantic status colors, hover labels,
and accessible names; wrap the group when the sidebar is too narrow.
- At mobile widths or coarse pointers, app-owned controls target 44px hit heights;
icon controls also reach 44px width. Text-input sizing uses `--text-input-touch`.
The compact View options and collapse/expand icons match the theme control's
Expand Down
62 changes: 59 additions & 3 deletions apps/web/src/ReviewTools.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { ChevronDownIcon, CopyIcon } from "lucide-react";
import {
ChevronDownIcon,
CopyIcon,
FileMinus2Icon,
FilePenLineIcon,
FilePlus2Icon,
FileSymlinkIcon,
} from "lucide-react";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Expand Down Expand Up @@ -145,6 +152,34 @@ export function ChangeSummary() {
save("summary-collapsed", String(summaryCollapsed));
}, [summaryCollapsed]);
const allFiles = repository?.files ?? [];
const fileChanges = [
{
kind: "added",
Icon: FilePlus2Icon,
count: allFiles.filter((file) => ["A", "?", "C"].includes(file.status))
.length,
description: "Files added (including untracked and copied files)",
},
{
kind: "deleted",
Icon: FileMinus2Icon,
count: allFiles.filter((file) => file.status === "D").length,
description: "Files deleted",
},
{
kind: "modified",
Icon: FilePenLineIcon,
count: allFiles.filter((file) => ["M", "T", "U"].includes(file.status))
.length,
description: "Files modified (including type changes and conflicts)",
},
{
kind: "renamed",
Icon: FileSymlinkIcon,
count: allFiles.filter((file) => file.status === "R").length,
description: "Files renamed",
},
];
const additions = allFiles.reduce((sum, file) => sum + file.additions, 0);
const deletions = allFiles.reduce((sum, file) => sum + file.deletions, 0);
const reviewedCount = allFiles.filter(isReviewed).length;
Expand All @@ -166,9 +201,30 @@ export function ChangeSummary() {
</Button>
<div id="summary-content" hidden={summaryCollapsed}>
<div className="sidebar-bottom">
<div className="summary-row">
<div className="summary-row summary-files-row">
<span>Files changed</span>
<strong id="summary-files">{allFiles.length}</strong>
<div className="summary-file-counts">
<strong id="summary-files">{allFiles.length}</strong>
<div
className="summary-file-breakdown"
role="group"
aria-label="File changes by type"
>
{fileChanges.map(({ kind, Icon, count, description }) => (
<span
key={kind}
className="summary-file-kind"
data-kind={kind}
role="img"
aria-label={`${count} ${count === 1 ? "file" : "files"} ${kind}`}
title={`${description}: ${count}`}
>
<Icon aria-hidden="true" />
<span aria-hidden="true">{count.toLocaleString()}</span>
</span>
))}
</div>
</div>
</div>
<div className="summary-row">
<span>Additions</span>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/review.css
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@

@media (max-width: 767px) {
.sidebar-bottom .summary-label,
.sidebar-bottom .summary-row,
.sidebar-bottom .summary-row:not(.summary-files-row),
.sidebar-bottom .change-bar {
display: none;
}
Expand Down
41 changes: 41 additions & 0 deletions apps/web/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,47 @@ kbd {
.summary-row strong {
font: var(--text-sm) var(--font-mono);
}
.summary-files-row {
align-items: center;
flex-wrap: wrap;
gap: var(--space-1) var(--space-2);
}
.summary-file-counts,
.summary-file-breakdown,
.summary-file-kind {
display: flex;
align-items: center;
}
.summary-file-counts {
margin-left: auto;
gap: var(--space-2);
}
.summary-file-breakdown {
flex-wrap: wrap;
gap: var(--space-2);
border-left: 1px solid var(--border-muted);
padding-left: var(--space-2);
}
.summary-file-kind {
gap: var(--space-1);
font: var(--text-xs) var(--font-mono);
}
.summary-file-kind svg {
width: var(--icon-sm);
height: var(--icon-sm);
}
.summary-file-kind[data-kind="added"] {
color: var(--success);
}
.summary-file-kind[data-kind="deleted"] {
color: var(--error);
}
.summary-file-kind[data-kind="modified"] {
color: var(--warning);
}
.summary-file-kind[data-kind="renamed"] {
color: var(--accent);
}
.change-bar {
height: var(--space-1);
background: var(--error);
Expand Down
36 changes: 36 additions & 0 deletions apps/web/test/browser/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,42 @@ async function resolvedRadius(

test.beforeEach(async ({ request }) => resetFixtureState(request));

test("file summary counts change types across filtering and mobile", async ({
page,
}) => {
await page.goto("/");
const summary = page.getByRole("region", { name: "Summary", exact: true });
await expect(summary.locator("#summary-files")).toHaveText("12");
for (const label of [
"4 files added",
"2 files deleted",
"5 files modified",
"1 file renamed",
]) {
await expect(summary.getByRole("img", { name: label })).toBeVisible();
}
await page.getByRole("searchbox", { name: "Filter files" }).fill("value.ts");
await expect(page.locator("#file-tree [data-path]")).toHaveCount(1);
await expect(summary.locator("#summary-files")).toHaveText("12");
await expect(
summary.getByRole("img", { name: "1 file renamed" }),
).toBeVisible();

await page.setViewportSize({ width: 390, height: 844 });
await page.getByRole("button", { name: "Show file sidebar" }).click();
await expect(summary.locator("#summary-files")).toBeVisible();
await expect(
summary.getByRole("img", { name: "4 files added" }),
).toBeVisible();
await expect(
summary.getByRole("img", { name: "1 file renamed" }),
).toBeVisible();
const row = summary.locator(".summary-files-row");
expect(
await row.evaluate((element) => element.scrollWidth <= element.clientWidth),
).toBe(true);
});

test("disabled comments issue no requests and expose no comment UI", async ({
page,
}) => {
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading
Loading