feat(ui): colonne tags de versions à gauche du graph Git — #193

This commit is contained in:
2026-08-14 09:33:05 +02:00
parent 2b2da223d5
commit 2bbe7f63b0
2 changed files with 58 additions and 12 deletions

View File

@ -69,6 +69,8 @@ const LANE_W = 16;
const ROW_H = 36; const ROW_H = 36;
/** Radius of the commit dot. */ /** Radius of the commit dot. */
const DOT_R = 4.5; const DOT_R = 4.5;
/** Stable version tag column, kept outside the DAG lanes. */
const TAGS_COL_W = 112;
/** Horizontal centre of a lane. */ /** Horizontal centre of a lane. */
const laneX = (lane: number) => lane * LANE_W + LANE_W / 2; const laneX = (lane: number) => lane * LANE_W + LANE_W / 2;
@ -283,6 +285,8 @@ export function GitGraphView({ projectId }: GitGraphViewProps) {
)} )}
{vm.rows.map((row) => { {vm.rows.map((row) => {
const isSelected = row.commit.hash === selectedHash; const isSelected = row.commit.hash === selectedHash;
const tagRefs = row.commit.refs.filter(isTag);
const branchRefs = row.commit.refs.filter((ref) => !isTag(ref));
return ( return (
<div <div
key={row.commit.hash} key={row.commit.hash}
@ -297,21 +301,36 @@ export function GitGraphView({ projectId }: GitGraphViewProps) {
)} )}
style={{ height: ROW_H }} style={{ height: ROW_H }}
> >
{/* Version tags live in their own stable column, left of the DAG. */}
<div
data-testid={`git-tags-cell-${row.commit.hash.slice(0, 8)}`}
className="flex shrink-0 items-center gap-1 overflow-hidden"
style={{ width: TAGS_COL_W }}
>
{tagRefs.map((ref) => (
<span
key={ref}
title={ref}
className="min-w-0 max-w-full truncate rounded bg-warning/20 px-1 py-0.5 text-xs font-medium text-warning"
>
{ref}
</span>
))}
</div>
{/* SVG lanes + node */} {/* SVG lanes + node */}
<GraphColumn row={row} laneCount={laneCount} /> <GraphColumn row={row} laneCount={laneCount} />
{/* Ref badges */} {/* Branch refs stay beside the graph in the existing refs zone. */}
{row.commit.refs.length > 0 && ( {branchRefs.length > 0 && (
<div className="flex shrink-0 flex-wrap gap-1"> <div
{row.commit.refs.map((ref) => ( data-testid={`git-branch-refs-cell-${row.commit.hash.slice(0, 8)}`}
className="flex shrink-0 flex-wrap gap-1"
>
{branchRefs.map((ref) => (
<span <span
key={ref} key={ref}
className={cn( className="rounded bg-primary/20 px-1 py-0.5 text-xs font-medium text-primary"
"rounded px-1 py-0.5 text-xs font-medium",
isTag(ref)
? "bg-warning/20 text-warning"
: "bg-primary/20 text-primary",
)}
> >
{ref} {ref}
</span> </span>

View File

@ -10,7 +10,7 @@
* - Clicking close hides the detail panel. * - Clicking close hides the detail panel.
*/ */
import { describe, it, expect } from "vitest"; import { describe, it, expect, vi } from "vitest";
import { render, screen, waitFor, fireEvent } from "@testing-library/react"; import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import { MockGitGateway } from "@/adapters/mock"; import { MockGitGateway } from "@/adapters/mock";
@ -22,7 +22,7 @@ import { GitGraphView } from "./GitGraphView";
// Helpers // Helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function renderView(git: MockGitGateway = new MockGitGateway()) { function renderView(git: unknown = new MockGitGateway()) {
const gateways = { git } as unknown as Gateways; const gateways = { git } as unknown as Gateways;
return render( return render(
<DIProvider gateways={gateways}> <DIProvider gateways={gateways}>
@ -71,6 +71,10 @@ describe("GitGraphView", () => {
expect(tagBadge).toBeTruthy(); expect(tagBadge).toBeTruthy();
// Tag badge should have the warning colour class // Tag badge should have the warning colour class
expect(tagBadge.className).toMatch(/warning/); expect(tagBadge.className).toMatch(/warning/);
const tagCell = screen.getByTestId("git-tags-cell-aaaaaaaa");
expect(tagCell.textContent).toContain("tag: v1.0");
expect(tagCell.style.width).toBe("112px");
expect(tagBadge.className).toMatch(/truncate/);
}); });
it("shows 'feature' branch badge", async () => { it("shows 'feature' branch badge", async () => {
@ -82,6 +86,29 @@ describe("GitGraphView", () => {
expect(featureBadge.className).toMatch(/primary/); expect(featureBadge.className).toMatch(/primary/);
}); });
it("keeps tags in the left tag column and branches in the refs zone", async () => {
renderView({
graph: vi.fn(async () => [
{
hash: "1111111111111111111111111111111111111111",
summary: "Release build",
parents: [],
refs: ["main", "tag: v2026.08.14-long-release-name"],
author: "Alice",
timestamp: 1_717_200_000,
},
]),
});
await waitForCommits();
const tagCell = screen.getByTestId("git-tags-cell-11111111");
const branchCell = screen.getByTestId("git-branch-refs-cell-11111111");
expect(tagCell.textContent).toContain("tag: v2026.08.14-long-release-name");
expect(tagCell.textContent).not.toContain("main");
expect(branchCell.textContent).toContain("main");
expect(branchCell.textContent).not.toContain("tag: v2026.08.14-long-release-name");
});
it("clicking a commit row opens the detail panel", async () => { it("clicking a commit row opens the detail panel", async () => {
renderView(); renderView();
await waitForCommits(); await waitForCommits();