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
62 changes: 62 additions & 0 deletions apps/gittensory-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -10820,6 +10820,67 @@
},
"summary": {
"type": "string"
},
"checkRunReadiness": {
"type": "object",
"nullable": true,
"properties": {
"readinessBand": {
"type": "string",
"enum": [
"strong",
"developing",
"early"
]
},
"components": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"enum": [
"traceability",
"related_work",
"change_scope",
"validation",
"pr_state",
"queue_pressure"
]
},
"label": {
"type": "string"
},
"band": {
"type": "string",
"enum": [
"met",
"partial",
"unmet"
]
},
"evidence": {
"type": "string"
},
"action": {
"type": "string"
}
},
"required": [
"key",
"label",
"band",
"evidence",
"action"
]
}
}
},
"required": [
"readinessBand",
"components"
]
}
},
"required": [
Expand All @@ -10833,6 +10894,7 @@
"previewComment",
"appliedLabel",
"checkRun",
"checkRunReadiness",
"installPreview",
"warnings",
"summary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
import { ActivationPreview } from "@/components/site/app-panels/activation-preview";
import { AiReviewSettings } from "@/components/site/app-panels/ai-review-settings";
import { MaintainerSettings } from "@/components/site/app-panels/maintainer-settings";
import { CheckRunReadinessTable } from "@/components/site/check-run-readiness-table";
import type { CheckRunReadinessTableData } from "@/components/site/check-run-readiness-model";
import { StatCard } from "@/components/site/primitives";
import { RefreshMeta } from "@/components/site/refresh-meta";
import { EmptyState, LoadingState, StateBoundary } from "@/components/site/state-views";
Expand Down Expand Up @@ -154,6 +156,7 @@ type SettingsPreviewResponse = {
previewComment: string | null;
appliedLabel: string | null;
checkRun: { willCreate: boolean; title: string; detailLevel: string } | null;
checkRunReadiness: CheckRunReadinessTableData | null;
installPreview: InstallPreview;
warnings: string[];
summary: string;
Expand All @@ -168,7 +171,9 @@ const MAINTAINER_ROLES = ["maintainer", "owner", "operator"] as const;
* dashboard query and the BYOK form from ever mounting for a non-maintainer (defense-in-depth + a clean
* message instead of a raw 403). The backend remains the source of truth.
*/
export function MaintainerPanel() {
export function MaintainerPanel({
initialRepoFullName,
}: { initialRepoFullName?: string | undefined } = {}) {
const { session, hydrated } = useSession();
const isMaintainer = (session?.roles ?? []).some((role) =>
MAINTAINER_ROLES.includes(role as (typeof MAINTAINER_ROLES)[number]),
Expand All @@ -183,10 +188,14 @@ export function MaintainerPanel() {
/>
);
}
return <MaintainerDashboardView />;
return <MaintainerDashboardView initialRepoFullName={initialRepoFullName} />;
}

function MaintainerDashboardView() {
function MaintainerDashboardView({
initialRepoFullName,
}: {
initialRepoFullName?: string | undefined;
}) {
const dashboard = useApiResource<MaintainerDashboard>(
"/v1/app/maintainer-dashboard",
"Maintainer dashboard",
Expand Down Expand Up @@ -360,7 +369,10 @@ function MaintainerDashboardView() {

<ActivationPreview reviewability={data.reviewability} />

<SurfacePreview reviewability={data.reviewability} />
<SurfacePreview
reviewability={data.reviewability}
initialRepoFullName={initialRepoFullName}
/>

<MaintainerSettings reviewability={data.reviewability} />

Expand All @@ -373,12 +385,14 @@ function MaintainerDashboardView() {

function SurfacePreview({
reviewability,
initialRepoFullName,
}: {
reviewability: MaintainerDashboard["reviewability"];
initialRepoFullName?: string | undefined;
}) {
const repoOptions = useMemo(() => extractPreviewRepoOptions(reviewability), [reviewability]);
const [form, setForm] = useState<PreviewFormState>({
repoFullName: repoOptions[0] ?? "",
repoFullName: initialRepoFullName ?? repoOptions[0] ?? "",
scenarioId: "confirmed-miner",
title: "Sample pull request",
labels: "bug",
Expand All @@ -396,6 +410,15 @@ function SurfacePreview({
}
}, [form.repoFullName, repoOptions]);

useEffect(() => {
if (!initialRepoFullName) return;
setForm((current) =>
current.repoFullName === initialRepoFullName
? current
: { ...current, repoFullName: initialRepoFullName },
);
}, [initialRepoFullName]);

async function runPreview(nextForm = form) {
const target = splitRepoFullName(nextForm.repoFullName);
if (!target) {
Expand Down Expand Up @@ -682,6 +705,13 @@ function PreviewResult({
</div>
) : null}

{preview.checkRun?.willCreate ? (
<CheckRunReadinessTable
detailLevel={preview.checkRun.detailLevel as "minimal" | "standard" | "deep"}
readiness={preview.checkRunReadiness}
/>
) : null}

<div>
<div className="mb-1.5 flex items-center justify-between gap-2">
<div className="font-mono text-token-2xs uppercase tracking-wider text-muted-foreground">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Check-run details-page readiness table model (#2216). Pure helpers + types for the Context check
// details slice — mirrors the public-safe band shape from buildExtensionPrStatus (src/signals/
// extension-contributor-context.ts) so the UI never renders raw readiness scores.

export type CheckRunDetailLevel = "minimal" | "standard" | "deep";

export type ReadinessComponentBand = "met" | "partial" | "unmet";

export type ContributorReadinessBand = "strong" | "developing" | "early";

export type CheckRunReadinessRow = {
key: string;
label: string;
band: ReadinessComponentBand;
evidence: string;
action: string;
};

export type CheckRunReadinessTableData = {
readinessBand: ContributorReadinessBand;
components: CheckRunReadinessRow[];
};

/** Context check details publish the readiness table only at standard/deep detail levels. */
export function shouldShowCheckRunReadinessTable(detailLevel: CheckRunDetailLevel): boolean {
return detailLevel !== "minimal";
}

/** Gate the table on detail level AND the presence of readiness rows (empty set → hide). */
export function resolveCheckRunReadinessView(args: {
detailLevel: CheckRunDetailLevel | null | undefined;
readiness: CheckRunReadinessTableData | null | undefined;
}): CheckRunReadinessTableData | null {
if (!args.detailLevel || !shouldShowCheckRunReadinessTable(args.detailLevel)) return null;
if (!args.readiness || args.readiness.components.length === 0) return null;
return args.readiness;
}

export const READINESS_BAND_LABEL: Record<ContributorReadinessBand, string> = {
strong: "Strong",
developing: "Developing",
early: "Early",
};

export const COMPONENT_BAND_LABEL: Record<ReadinessComponentBand, string> = {
met: "Met",
partial: "Partial",
unmet: "Unmet",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";

import { CheckRunReadinessTable } from "@/components/site/check-run-readiness-table";
import {
resolveCheckRunReadinessView,
shouldShowCheckRunReadinessTable,
type CheckRunReadinessTableData,
} from "@/components/site/check-run-readiness-model";

const SAMPLE: CheckRunReadinessTableData = {
readinessBand: "developing",
components: [
{
key: "validation",
label: "Validation posture",
band: "partial",
evidence: "Test plan noted but not verified.",
action: "Run the documented test plan.",
},
],
};

describe("shouldShowCheckRunReadinessTable", () => {
it("shows at standard and deep, hides at minimal", () => {
expect(shouldShowCheckRunReadinessTable("minimal")).toBe(false);
expect(shouldShowCheckRunReadinessTable("standard")).toBe(true);
expect(shouldShowCheckRunReadinessTable("deep")).toBe(true);
});
});

describe("resolveCheckRunReadinessView", () => {
it("returns null below standard detail level even when readiness is present", () => {
expect(resolveCheckRunReadinessView({ detailLevel: "minimal", readiness: SAMPLE })).toBeNull();
});

it("returns null for an empty readiness component set at standard (both gate-off and gate-on shapes)", () => {
const empty: CheckRunReadinessTableData = { readinessBand: "early", components: [] };
expect(resolveCheckRunReadinessView({ detailLevel: "standard", readiness: empty })).toBeNull();
expect(resolveCheckRunReadinessView({ detailLevel: "deep", readiness: empty })).toBeNull();
});

it("returns the readiness payload at standard when components are present", () => {
expect(resolveCheckRunReadinessView({ detailLevel: "standard", readiness: SAMPLE })).toEqual(
SAMPLE,
);
});
});

describe("CheckRunReadinessTable", () => {
it("renders the table at standard detail level", () => {
render(<CheckRunReadinessTable detailLevel="standard" readiness={SAMPLE} />);
expect(screen.getByText("Context check readiness")).toBeTruthy();
expect(screen.getByText("Validation posture")).toBeTruthy();
expect(screen.getByText("Test plan noted but not verified.")).toBeTruthy();
expect(screen.getByText("Developing")).toBeTruthy();
expect(screen.getByText("Partial")).toBeTruthy();
});

it("hides the table at minimal detail level", () => {
render(<CheckRunReadinessTable detailLevel="minimal" readiness={SAMPLE} />);
expect(screen.queryByText("Context check readiness")).toBeNull();
});

it("hides the table when the readiness component set is empty", () => {
render(
<CheckRunReadinessTable
detailLevel="standard"
readiness={{ readinessBand: "early", components: [] }}
/>,
);
expect(screen.queryByText("Context check readiness")).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { StatusPill, type Status } from "@/components/site/control-primitives";
import {
COMPONENT_BAND_LABEL,
READINESS_BAND_LABEL,
resolveCheckRunReadinessView,
type CheckRunDetailLevel,
type CheckRunReadinessTableData,
type ContributorReadinessBand,
type ReadinessComponentBand,
} from "@/components/site/check-run-readiness-model";
import { cn } from "@/lib/utils";

const READINESS_BAND_TONE: Record<ContributorReadinessBand, Status> = {
strong: "ready",
developing: "warn",
early: "info",
};

const COMPONENT_BAND_TONE: Record<ReadinessComponentBand, Status> = {
met: "ready",
partial: "warn",
unmet: "blocked",
};

/**
* Scannable readiness table for the Context check details page (#2216). Consumes the public-safe
* band payload from settings-preview (`checkRunReadiness`); hidden below `standard` detail level.
*/
export function CheckRunReadinessTable({
detailLevel,
readiness,
className,
}: {
detailLevel: CheckRunDetailLevel | null | undefined;
readiness: CheckRunReadinessTableData | null | undefined;
className?: string;
}) {
const view = resolveCheckRunReadinessView({ detailLevel, readiness });
if (!view) return null;

return (
<section className={cn("space-y-3", className)} aria-labelledby="check-run-readiness-title">
<div className="flex flex-wrap items-center justify-between gap-2">
<div>
<h3
id="check-run-readiness-title"
className="font-mono text-token-2xs uppercase tracking-wider text-muted-foreground"
>
Context check readiness
</h3>
<p className="mt-1 text-token-xs text-muted-foreground">
Public-safe bands from the same readiness rubric as the PR panel — no raw scores.
</p>
</div>
<StatusPill status={READINESS_BAND_TONE[view.readinessBand]}>
{READINESS_BAND_LABEL[view.readinessBand]}
</StatusPill>
</div>

<div className="overflow-hidden rounded-token border-hairline">
<table className="w-full text-left text-token-xs">
<thead className="border-b-hairline font-mono uppercase tracking-wider text-muted-foreground">
<tr>
<th className="px-3 py-2 font-normal">Signal</th>
<th className="px-3 py-2 font-normal">Band</th>
<th className="px-3 py-2 font-normal">Evidence</th>
<th className="hidden px-3 py-2 font-normal lg:table-cell">Action</th>
</tr>
</thead>
<tbody>
{view.components.map((row) => (
<tr key={row.key} className="border-b-hairline last:border-b-0 align-top">
<td className="px-3 py-2 font-medium text-foreground">{row.label}</td>
<td className="px-3 py-2">
<StatusPill status={COMPONENT_BAND_TONE[row.band]}>
{COMPONENT_BAND_LABEL[row.band]}
</StatusPill>
</td>
<td className="px-3 py-2 text-muted-foreground">{row.evidence}</td>
<td className="hidden px-3 py-2 text-muted-foreground lg:table-cell">
{row.action}
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
);
}
Loading
Loading