feat: add /process page with tested delivery report model and sample

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:20:28 +02:00
co-authored by Claude Sonnet 5.5
parent c71916cf08
commit a9702dc3ff
7 changed files with 546 additions and 5 deletions
@@ -0,0 +1,30 @@
import * as React from "react";
import { TerminalCard } from "@/src/components/site";
import type { ProcessStep } from "./processSteps";
type ProcessStepCardProps = {
readonly step: ProcessStep;
readonly index: number;
};
export const ProcessStepCard: React.FC<ProcessStepCardProps> = ({
step,
index,
}) => (
<li className="grid gap-6 border-t border-border py-8 md:grid-cols-2 md:gap-10">
<div>
<p className="!mb-2 font-mono text-xs uppercase tracking-widest text-accent">
Step {index + 1}
</p>
<h3 className="!mb-3 !mt-0 text-xl font-semibold">{step.title}</h3>
<p className="!mb-0 text-base leading-relaxed text-muted">{step.rule}</p>
</div>
<TerminalCard title={step.exampleLabel}>
{step.example.map((line) => (
<p key={line} className="!mb-0 whitespace-pre">
{line}
</p>
))}
</TerminalCard>
</li>
);
@@ -0,0 +1,68 @@
export type ProcessStep = {
readonly id: string;
readonly title: string;
readonly rule: string;
readonly exampleLabel: string;
readonly example: readonly string[];
};
export const PROCESS_STEPS: readonly ProcessStep[] = [
{
id: "spec",
title: "Spec first",
rule: "Behavior is written as concrete cases before any code: inputs, outputs, edge cases, error paths. Unclear business rules get one precise question to you, not a guess.",
exampleLabel: "Availability parsing: the cases",
example: [
'"2026-02-30" -> rejected, not a real date',
'"2028-02-29" -> accepted, leap day',
"168 h per week -> accepted, the boundary",
"169 h per week -> rejected",
"-1 h per week -> rejected",
],
},
{
id: "red",
title: "Red",
rule: "The cases run and fail for the right reason: the behavior is missing, not an import error or a typo. A bug fix starts with a failing test that reproduces the bug.",
exampleLabel: "Availability parsing: first run",
example: [
"FAIL parses a leap day",
" expected { ok: true }, received undefined",
"9 failing, all because parseAvailability does not exist yet",
],
},
{
id: "green",
title: "Green",
rule: "The simplest clean solution that makes the cases pass. Pure functions for the logic, side effects pushed to thin edges. Then refactor while the tests stay green.",
exampleLabel: "Availability parsing: second run",
example: [
"9/9 pass",
"date check: month length plus leap-year rule",
"hours check: whole number from 0 to 168",
],
},
{
id: "mutation",
title: "Mutation check",
rule: "A tool changes the code on purpose and reruns the tests. Every change must make a test fail. A surviving mutant means a weak assertion or dead code, and I fix one or the other.",
exampleLabel: "Availability parsing: mutants",
example: [
"hours <= 168 -> hours < 168",
" caught by the 168 h case",
"day <= daysInMonth -> day < daysInMonth",
" caught by the 2028-02-29 case",
],
},
{
id: "report",
title: "Delivery report",
rule: "Every pull request carries the exact commands and their results: case counts, mutation result, coverage on changed lines, typecheck and lint. You can rerun every line.",
exampleLabel: "Availability parsing: the report",
example: [
"spec 9 -> red 9 -> green 9/9",
"mutation 14 caught, 0 survived",
"see the full sample below",
],
},
];
@@ -0,0 +1,16 @@
import type { DeliveryReport } from "@/src/domain/deliveryReport";
// Illustrative numbers for the availability parsing example. Not a real project.
export const SAMPLE_DELIVERY_REPORT: DeliveryReport = {
specCases: 9,
redFailing: 9,
greenPassing: 9,
greenTotal: 9,
mutantsTotal: 14,
mutantsCaught: 14,
mutantsSurvived: 0,
changedLinesCovered: 23,
changedLinesTotal: 23,
typecheckErrors: 0,
lintErrors: 0,
};
+120
View File
@@ -0,0 +1,120 @@
import { describe, expect, it } from "vitest";
import {
formatDeliveryReport,
verdictOf,
type DeliveryReport,
} from "./deliveryReport";
const passing: DeliveryReport = {
specCases: 9,
redFailing: 9,
greenPassing: 9,
greenTotal: 9,
mutantsTotal: 14,
mutantsCaught: 14,
mutantsSurvived: 0,
changedLinesCovered: 23,
changedLinesTotal: 23,
typecheckErrors: 0,
lintErrors: 0,
};
describe("verdictOf", () => {
it("is pass when every gate passes", () => {
expect(verdictOf(passing)).toBe("pass");
});
it.each<[string, Partial<DeliveryReport>]>([
["no spec cases", { specCases: 0, redFailing: 0 }],
["red does not match the written cases", { redFailing: 8 }],
["a green test fails", { greenPassing: 8 }],
["no green tests ran", { greenPassing: 0, greenTotal: 0 }],
["a mutant survived", { mutantsCaught: 13, mutantsSurvived: 1 }],
["no mutants were generated", { mutantsTotal: 0, mutantsCaught: 0 }],
["caught plus survived does not add up", { mutantsCaught: 13 }],
[
"a survivor is reported although all mutants count as caught",
{ mutantsSurvived: 1 },
],
["a changed line is uncovered", { changedLinesCovered: 22 }],
[
"no changed lines measured",
{ changedLinesCovered: 0, changedLinesTotal: 0 },
],
["typecheck has errors", { typecheckErrors: 1 }],
["lint has errors", { lintErrors: 2 }],
])("is fail when %s", (_name, patch) => {
expect(verdictOf({ ...passing, ...patch })).toBe("fail");
});
});
describe("formatDeliveryReport", () => {
it("renders one line per gate and a verdict for a passing report", () => {
expect(formatDeliveryReport(passing)).toEqual([
"[pass] spec 9 cases written before code",
"[pass] red 9 failing for the right reason",
"[pass] green 9/9 pass",
"[pass] mutation 14 mutants, 14 caught, 0 survived",
"[pass] coverage changed lines 100% (23/23)",
"[pass] typecheck clean",
"[pass] lint clean",
"verdict: pass",
]);
});
it("marks failing gates with their numbers", () => {
const lines = formatDeliveryReport({
...passing,
redFailing: 8,
greenPassing: 8,
mutantsCaught: 13,
mutantsSurvived: 1,
changedLinesCovered: 22,
typecheckErrors: 3,
lintErrors: 1,
});
expect(lines).toEqual([
"[pass] spec 9 cases written before code",
"[fail] red 8 failing, expected 9",
"[fail] green 8/9 pass",
"[fail] mutation 14 mutants, 13 caught, 1 survived",
"[fail] coverage changed lines 95.6% (22/23)",
"[fail] typecheck 3 errors",
"[fail] lint 1 error",
"verdict: fail",
]);
});
it("never rounds a partial coverage up to 100", () => {
const lines = formatDeliveryReport({
...passing,
changedLinesCovered: 9999,
changedLinesTotal: 10000,
});
expect(lines).toContain(
"[fail] coverage changed lines 99.9% (9999/10000)",
);
});
it("reports empty measurements as failures", () => {
const lines = formatDeliveryReport({
...passing,
specCases: 0,
redFailing: 0,
greenPassing: 0,
greenTotal: 0,
mutantsTotal: 0,
mutantsCaught: 0,
changedLinesCovered: 0,
changedLinesTotal: 0,
});
expect(lines.slice(0, 5)).toEqual([
"[fail] spec no cases written",
"[fail] red no cases written",
"[fail] green 0/0 pass",
"[fail] mutation no mutants generated",
"[fail] coverage no changed lines measured",
]);
expect(lines.at(-1)).toBe("verdict: fail");
});
});
+120
View File
@@ -0,0 +1,120 @@
export type DeliveryReport = {
readonly specCases: number;
readonly redFailing: number;
readonly greenPassing: number;
readonly greenTotal: number;
readonly mutantsTotal: number;
readonly mutantsCaught: number;
readonly mutantsSurvived: number;
readonly changedLinesCovered: number;
readonly changedLinesTotal: number;
readonly typecheckErrors: number;
readonly lintErrors: number;
};
export type Verdict = "pass" | "fail";
type Gate = {
readonly name: string;
readonly pass: boolean;
readonly detail: string;
};
const NAME_COLUMN_WIDTH = 11;
const PERCENT_SCALE = 1000;
const PERCENT_DIVISOR = 10;
const plural = (count: number, singular: string): string =>
`${count} ${singular}${count === 1 ? "" : "s"}`;
// Floor, never round: 99.96% must not be shown as 100%.
const floorPercent = (part: number, whole: number): number =>
Math.floor((part / whole) * PERCENT_SCALE) / PERCENT_DIVISOR;
const specGate = (r: DeliveryReport): Gate =>
r.specCases > 0
? {
name: "spec",
pass: true,
detail: `${r.specCases} cases written before code`,
}
: { name: "spec", pass: false, detail: "no cases written" };
const redGate = (r: DeliveryReport): Gate => {
if (r.specCases === 0) {
return { name: "red", pass: false, detail: "no cases written" };
}
return r.redFailing === r.specCases
? {
name: "red",
pass: true,
detail: `${r.redFailing} failing for the right reason`,
}
: {
name: "red",
pass: false,
detail: `${r.redFailing} failing, expected ${r.specCases}`,
};
};
const greenGate = (r: DeliveryReport): Gate => ({
name: "green",
pass: r.greenTotal > 0 && r.greenPassing === r.greenTotal,
detail: `${r.greenPassing}/${r.greenTotal} pass`,
});
const mutationGate = (r: DeliveryReport): Gate => {
if (r.mutantsTotal === 0) {
return { name: "mutation", pass: false, detail: "no mutants generated" };
}
return {
name: "mutation",
pass: r.mutantsSurvived === 0 && r.mutantsCaught === r.mutantsTotal,
detail: `${r.mutantsTotal} mutants, ${r.mutantsCaught} caught, ${r.mutantsSurvived} survived`,
};
};
const coverageGate = (r: DeliveryReport): Gate => {
if (r.changedLinesTotal === 0) {
return {
name: "coverage",
pass: false,
detail: "no changed lines measured",
};
}
const percent = floorPercent(r.changedLinesCovered, r.changedLinesTotal);
return {
name: "coverage",
pass: r.changedLinesCovered === r.changedLinesTotal,
detail: `changed lines ${percent}% (${r.changedLinesCovered}/${r.changedLinesTotal})`,
};
};
const errorCountGate = (name: string, errors: number): Gate => ({
name,
pass: errors === 0,
detail: errors === 0 ? "clean" : plural(errors, "error"),
});
const gatesOf = (r: DeliveryReport): readonly Gate[] => [
specGate(r),
redGate(r),
greenGate(r),
mutationGate(r),
coverageGate(r),
errorCountGate("typecheck", r.typecheckErrors),
errorCountGate("lint", r.lintErrors),
];
export const verdictOf = (report: DeliveryReport): Verdict =>
gatesOf(report).every((gate) => gate.pass) ? "pass" : "fail";
export const formatDeliveryReport = (
report: DeliveryReport,
): readonly string[] => [
...gatesOf(report).map(
(gate) =>
`[${gate.pass ? "pass" : "fail"}] ${gate.name.padEnd(NAME_COLUMN_WIDTH)}${gate.detail}`,
),
`verdict: ${verdictOf(report)}`,
];