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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
QueueHealthCard,
type MaintainerQueueHealth,
} from "@/components/site/app-panels/queue-health-card";
import { SlopDuplicateTrendCard } from "@/components/site/app-panels/slop-duplicate-trend-card";
import type { MaintainerSlopDuplicateTrend } from "@/components/site/app-panels/slop-duplicate-trend-card-model";
import { MaintainerSettings } from "@/components/site/app-panels/maintainer-settings";
import { OnboardingPreviewCard } from "@/components/site/app-panels/onboarding-preview-card";
import { CheckRunReadinessTable } from "@/components/site/check-run-readiness-table";
Expand Down Expand Up @@ -92,6 +94,7 @@ type MaintainerDashboard = {
topContributors: MaintainerTopContributor[];
gateOutcomeBreakdown: GateOutcomeCardData;
queueHealth?: MaintainerQueueHealth;
slopDuplicateTrend?: MaintainerSlopDuplicateTrend;
};
};

Expand Down Expand Up @@ -388,6 +391,10 @@ function MaintainerDashboardView({

<QueueHealthCard queueHealth={data.qualityDashboard.queueHealth} />

{data.qualityDashboard.slopDuplicateTrend ? (
<SlopDuplicateTrendCard trend={data.qualityDashboard.slopDuplicateTrend} />
) : null}

<ContributorQualityTable topContributors={data.qualityDashboard.topContributors} />

<ActivationPreview reviewability={data.reviewability} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Slop + duplicate trend card model (#2202). UI-side mirror of MaintainerSlopDuplicateTrend from
// src/services/maintainer-slop-duplicate-trend.ts — plus pure helpers for chart series mapping.

export type SlopBandLabel = "clean" | "low" | "elevated" | "high";

export type SlopDuplicateTrendWeek = {
weekStart: string;
slopFlagRatePct: number | null;
slopBandLabel: SlopBandLabel | null;
duplicateFlagRatePct: number | null;
};

export type MaintainerSlopDuplicateTrend = {
generatedAt: string;
stale: boolean;
weeks: SlopDuplicateTrendWeek[];
summary: string;
};

export function formatTrendRatePct(value: number | null | undefined): string {
if (value == null) return "—";
return `${value}%`;
}

export function chartValuesForSeries(
weeks: SlopDuplicateTrendWeek[],
series: "slop" | "duplicate",
): number[] {
return weeks.map((week) => {
const value = series === "slop" ? week.slopFlagRatePct : week.duplicateFlagRatePct;
return value ?? 0;
});
}

export function seriesHasSignal(
weeks: SlopDuplicateTrendWeek[],
series: "slop" | "duplicate",
): boolean {
return weeks.some((week) =>
series === "slop" ? week.slopFlagRatePct !== null : week.duplicateFlagRatePct !== null,
);
}

export function trendHasAnySignal(weeks: SlopDuplicateTrendWeek[]): boolean {
return seriesHasSignal(weeks, "slop") || seriesHasSignal(weeks, "duplicate");
}

export function latestWeekWithSignal(
weeks: SlopDuplicateTrendWeek[],
): SlopDuplicateTrendWeek | null {
for (let index = weeks.length - 1; index >= 0; index -= 1) {
const week = weeks[index];
if (!week) continue;
if (week.slopFlagRatePct !== null || week.duplicateFlagRatePct !== null) return week;
}
return null;
}

export function formatGeneratedAt(iso: string): string {
const parsed = Date.parse(iso);
if (!Number.isFinite(parsed)) return iso;
return new Date(parsed).toUTCString().slice(5, 22);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";

import { SlopDuplicateTrendCard } from "@/components/site/app-panels/slop-duplicate-trend-card";
import type { MaintainerSlopDuplicateTrend } from "@/components/site/app-panels/slop-duplicate-trend-card-model";

function trend(
overrides: Partial<MaintainerSlopDuplicateTrend> = {},
): MaintainerSlopDuplicateTrend {
return {
generatedAt: "2026-06-14T12:00:00.000Z",
stale: false,
summary: "8-week slop + duplicate flag rates across 1 shaped repo(s).",
weeks: Array.from({ length: 8 }, (_, index) => ({
weekStart: `2026-04-${String(21 + index).padStart(2, "0")}`,
slopFlagRatePct: 12.5,
slopBandLabel: "low" as const,
duplicateFlagRatePct: 25,
})),
...overrides,
};
}

describe("SlopDuplicateTrendCard", () => {
it("renders both trend series, shared legend, and freshness metadata", () => {
render(<SlopDuplicateTrendCard trend={trend()} />);
expect(screen.getByText("Slop + duplicate trend")).toBeTruthy();
expect(screen.getAllByText("Slop flag rate").length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText("Duplicate flag rate").length).toBeGreaterThanOrEqual(1);
expect(screen.getByText(/latest band: low/i)).toBeTruthy();
expect(screen.getByText(/latest: 25%/i)).toBeTruthy();
expect(screen.getByText(/fresh snapshot/i)).toBeTruthy();
expect(screen.getByText(/generated/i)).toBeTruthy();
expect(screen.getAllByLabelText("Trend chart")).toHaveLength(2);
});

it("shows a one-series-empty branch when only duplicate samples exist", () => {
render(
<SlopDuplicateTrendCard
trend={trend({
weeks: [
{
weekStart: "2026-06-09",
slopFlagRatePct: null,
slopBandLabel: null,
duplicateFlagRatePct: 50,
},
],
})}
/>,
);
expect(screen.getByText("No slop-flag samples in the snapshot window yet.")).toBeTruthy();
expect(screen.getByLabelText("Trend chart")).toBeTruthy();
expect(screen.getByText(/latest: 50%/i)).toBeTruthy();
});

it("shows the no-data branch when every weekly bucket is empty", () => {
render(
<SlopDuplicateTrendCard
trend={trend({
summary:
"No queue-health snapshot history yet for slop + duplicate trends across 1 shaped repo(s).",
weeks: [
{
weekStart: "2026-06-09",
slopFlagRatePct: null,
slopBandLabel: null,
duplicateFlagRatePct: null,
},
],
})}
/>,
);
expect(
screen.getByText(
/Queue-health snapshot history will appear here after signal snapshot jobs run/i,
),
).toBeTruthy();
expect(screen.queryByLabelText("Trend chart")).toBeNull();
});

it("surfaces the stale snapshot pill when data is old", () => {
render(<SlopDuplicateTrendCard trend={trend({ stale: true })} />);
expect(screen.getByText(/stale snapshot/i)).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { StatusPill } from "@/components/site/control-primitives";
import { TrendChart } from "@/components/site/trend-chart";
import {
chartValuesForSeries,
formatGeneratedAt,
formatTrendRatePct,
latestWeekWithSignal,
seriesHasSignal,
trendHasAnySignal,
type MaintainerSlopDuplicateTrend,
type SlopBandLabel,
} from "@/components/site/app-panels/slop-duplicate-trend-card-model";
import { cn } from "@/lib/utils";

const SLOP_BAND_TONE: Record<SlopBandLabel, string> = {
clean: "text-success",
low: "text-mint",
elevated: "text-warning",
high: "text-danger",
};

/** Maintainer quality dashboard card (#2202): weekly slop-flag and duplicate-flag rates from queue-health
* snapshots. Band labels only — never raw slop-risk or credibility numbers. */
export function SlopDuplicateTrendCard({ trend }: { trend: MaintainerSlopDuplicateTrend }) {
const hasSignal = trendHasAnySignal(trend.weeks);
const hasSlop = seriesHasSignal(trend.weeks, "slop");
const hasDuplicate = seriesHasSignal(trend.weeks, "duplicate");
const latest = latestWeekWithSignal(trend.weeks);

return (
<section className="rounded-token border border-border bg-transparent p-5">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<h2 className="font-display text-token-lg font-semibold">Slop + duplicate trend</h2>
<p className="mt-1 text-token-xs text-muted-foreground">
Weekly slop-flag and duplicate-flag rates from queue-health snapshots. Band labels only.
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<StatusPill status={trend.stale ? "warn" : "ready"}>
{trend.stale ? "stale snapshot" : "fresh snapshot"}
</StatusPill>
<span className="font-mono text-token-2xs text-muted-foreground">
generated {formatGeneratedAt(trend.generatedAt)}
</span>
</div>
</div>

{hasSignal ? (
<>
<div className="mt-4 flex flex-wrap items-center gap-4 text-token-xs">
<LegendItem
color="var(--mint)"
label="Slop flag rate"
detail={
latest?.slopBandLabel
? `latest band: ${latest.slopBandLabel}`
: hasSlop
? `latest: ${formatTrendRatePct(latest?.slopFlagRatePct)}`
: "no slop samples"
}
bandLabel={latest?.slopBandLabel}
/>
<LegendItem
color="var(--warning)"
label="Duplicate flag rate"
detail={
hasDuplicate
? `latest: ${formatTrendRatePct(latest?.duplicateFlagRatePct)}`
: "no duplicate samples"
}
/>
</div>

<div className="mt-4 grid gap-4 lg:grid-cols-2">
<TrendPanel
title="Slop flag rate"
emptyMessage="No slop-flag samples in the snapshot window yet."
hasSignal={hasSlop}
values={chartValuesForSeries(trend.weeks, "slop")}
stroke="var(--mint)"
fill="color-mix(in oklab, var(--mint) 18%, transparent)"
/>
<TrendPanel
title="Duplicate flag rate"
emptyMessage="No duplicate-flag samples in the snapshot window yet."
hasSignal={hasDuplicate}
values={chartValuesForSeries(trend.weeks, "duplicate")}
stroke="var(--warning)"
fill="color-mix(in oklab, var(--warning) 18%, transparent)"
/>
</div>

<p className="mt-3 text-token-xs text-muted-foreground">{trend.summary}</p>
</>
) : (
<p className="mt-4 text-token-sm text-muted-foreground">
Queue-health snapshot history will appear here after signal snapshot jobs run for your
scoped repositories.
</p>
)}
</section>
);
}

function TrendPanel({
title,
emptyMessage,
hasSignal,
values,
stroke,
fill,
}: {
title: string;
emptyMessage: string;
hasSignal: boolean;
values: number[];
stroke: string;
fill: string;
}) {
return (
<div className="rounded-token border border-border bg-background/40 p-3">
<div className="text-token-xs font-medium text-foreground">{title}</div>
{hasSignal ? (
<div className="mt-2 h-20">
<TrendChart values={values} stroke={stroke} fill={fill} height={80} showAxis />
</div>
) : (
<p className="mt-2 text-token-xs text-muted-foreground">{emptyMessage}</p>
)}
</div>
);
}

function LegendItem({
color,
label,
detail,
bandLabel,
}: {
color: string;
label: string;
detail: string;
bandLabel?: SlopBandLabel | null;
}) {
return (
<div className="flex items-center gap-2">
<span className="inline-block h-2 w-6 rounded-full" style={{ backgroundColor: color }} />
<span className="text-foreground">{label}</span>
<span
className={cn("text-muted-foreground", bandLabel ? SLOP_BAND_TONE[bandLabel] : undefined)}
>
{detail}
</span>
</div>
);
}
19 changes: 19 additions & 0 deletions packages/gittensory-engine/src/signals/predicted-gate-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,23 @@ export function buildQueueHealth(
const stalePullRequests = openPullRequests.filter((pr) => daysSince(pr.updatedAt ?? pr.createdAt) >= 14);
const draftPullRequests = openPullRequests.filter((pr) => pr.isDraft);
const maintainerAuthoredPullRequests = openPullRequests.filter((pr) => isMaintainerAssociation(pr.authorAssociation));
const slopFlaggedPullRequests = openPullRequests.filter(
(pr) => pr.slopBand === "elevated" || pr.slopBand === "high",
).length;
const highRiskDuplicatePrNumbers = new Set(
collisions.clusters
.filter(
(cluster) =>
cluster.risk === "high" &&
cluster.items.filter((item) => item.type === "pull_request").length >= 2,
)
.flatMap((cluster) =>
cluster.items.filter((item) => item.type === "pull_request").map((item) => item.number),
),
);
const duplicateFlaggedPullRequests = openPullRequests.filter((pr) =>
highRiskDuplicatePrNumbers.has(pr.number),
).length;
const cachedLikelyReviewablePullRequests = openPullRequests.filter((pr) => pr.linkedIssues.length > 0 && daysSince(pr.updatedAt ?? pr.createdAt) < 30).length;
const likelyReviewablePullRequests = Math.min(openPullRequestCount, Math.max(cachedLikelyReviewablePullRequests, countOverrides.likelyReviewablePullRequests ?? 0));
const ageBuckets = {
Expand Down Expand Up @@ -323,6 +340,8 @@ export function buildQueueHealth(
draftPullRequests: draftPullRequests.length,
maintainerAuthoredPullRequests: maintainerAuthoredPullRequests.length,
collisionClusters: collisions.summary.clusterCount,
slopFlaggedPullRequests,
duplicateFlaggedPullRequests,
ageBuckets,
likelyReviewablePullRequests,
cachedOpenPullRequests: openPullRequests.length,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ export type QueueHealth = {
draftPullRequests: number;
maintainerAuthoredPullRequests: number;
collisionClusters: number;
/** Open PRs with slop band elevated or high (public-safe flag count for trend snapshots). */
slopFlaggedPullRequests: number;
/** Open PRs in a high-risk duplicate cluster with 2+ pull requests (public-safe flag count). */
duplicateFlaggedPullRequests: number;
ageBuckets: {
under7Days: number;
days7To30: number;
Expand Down
Loading
Loading