feat(web): add AwoooP automation truth rail
All checks were successful
Code Review / ai-code-review (push) Successful in 29s
CD Pipeline / tests (push) Successful in 1m44s
CD Pipeline / build-and-deploy (push) Successful in 4m43s
CD Pipeline / post-deploy-checks (push) Successful in 1m33s

This commit is contained in:
Your Name
2026-06-25 15:33:25 +08:00
parent 11be182ccb
commit 092bd37628
3 changed files with 216 additions and 0 deletions

View File

@@ -723,6 +723,72 @@ function OperationsDecisionMap({
const verifiedPercent = percentValue(verified, evaluated);
const qualityHealthyPercent = percentValue(green, green + yellow + red);
const isDegraded = sourceError || qualityError;
const productionClaimReady = Boolean(quality?.production_claim.can_claim_full_auto_repair);
const blockerKey = isDegraded
? "dataIncomplete"
: verified < evaluated
? "repairVerifierGap"
: openWork + manualGates + numberValue(snapshot.approvals) > 0
? "manualGate"
: productionClaimReady
? "ready"
: "watch";
const nextFocusKey = openWork > 0
? "workItems"
: manualGates + numberValue(snapshot.approvals) > 0
? "approvals"
: verified < evaluated
? "runs"
: "observe";
const blockerText = (() => {
switch (blockerKey) {
case "dataIncomplete":
return t("truth.blockers.dataIncomplete");
case "repairVerifierGap":
return t("truth.blockers.repairVerifierGap", { verified, evaluated });
case "manualGate":
return t("truth.blockers.manualGate", {
open: openWork,
gates: manualGates + numberValue(snapshot.approvals),
});
case "ready":
return t("truth.blockers.ready");
default:
return t("truth.blockers.watch");
}
})();
const nextFocusText = (() => {
switch (nextFocusKey) {
case "workItems":
return t("truth.nextFocus.workItems");
case "approvals":
return t("truth.nextFocus.approvals");
case "runs":
return t("truth.nextFocus.runs");
default:
return t("truth.nextFocus.observe");
}
})();
const truthLinks = [
{
key: "runs",
href: "/awooop/runs?project_id=awoooi",
value: sourceError ? "--" : linkedRuns,
tone: linkedPercent >= 80 ? "good" : linkedPercent > 0 ? "warn" : "neutral",
},
{
key: "workItems",
href: "/awooop/work-items?project_id=awoooi",
value: openWork,
tone: openWork === 0 ? "good" : "warn",
},
{
key: "approvals",
href: "/awooop/approvals?project_id=awoooi",
value: manualGates + numberValue(snapshot.approvals),
tone: manualGates + numberValue(snapshot.approvals) === 0 ? "good" : "warn",
},
] as const;
return (
<section className="border border-[#e0ddd4] bg-[#e0ddd4]">
@@ -745,6 +811,80 @@ function OperationsDecisionMap({
</span>
</div>
<div
data-testid="awooop-automation-truth-rail"
className="grid gap-px bg-[#e0ddd4] lg:grid-cols-[1.05fr_0.95fr_1.25fr]"
>
<div className="bg-[#2f2a24] px-4 py-4 text-white">
<p className="text-[11px] font-semibold uppercase tracking-[0.14em] text-[#d9b36f]">
{t("truth.claimEyebrow")}
</p>
<p className="mt-2 text-xl font-semibold">
{productionClaimReady ? t("truth.claimReady") : t("truth.claimBlocked")}
</p>
<p className="mt-2 text-xs leading-5 text-[#e5ddd0]">
{blockerText}
</p>
</div>
<div className="bg-white px-4 py-4">
<p className="text-[11px] font-semibold uppercase tracking-[0.14em] text-[#a15f3b]">
{t("truth.nextEyebrow")}
</p>
<p className="mt-2 text-lg font-semibold text-[#141413]">
{nextFocusText}
</p>
<div className="mt-3 grid grid-cols-3 gap-2 text-[11px]">
<div className="border border-[#eee9dd] bg-[#faf9f3] px-2 py-1.5">
<p className="text-[#77736a]">{t("truth.stats.verified")}</p>
<p className="font-mono font-semibold text-[#141413]">{verified}/{evaluated}</p>
</div>
<div className="border border-[#eee9dd] bg-[#faf9f3] px-2 py-1.5">
<p className="text-[#77736a]">{t("truth.stats.openWork")}</p>
<p className="font-mono font-semibold text-[#141413]">{openWork}</p>
</div>
<div className="border border-[#eee9dd] bg-[#faf9f3] px-2 py-1.5">
<p className="text-[#77736a]">{t("truth.stats.gates")}</p>
<p className="font-mono font-semibold text-[#141413]">
{manualGates + numberValue(snapshot.approvals)}
</p>
</div>
</div>
</div>
<div className="grid gap-px bg-[#e0ddd4] sm:grid-cols-3">
{truthLinks.map((item) => (
<Link
key={item.key}
href={item.href as never}
className="group flex min-h-[112px] items-start justify-between gap-3 bg-white px-4 py-4 hover:bg-[#faf9f3]"
>
<div className="min-w-0">
<p className="text-xs font-semibold text-[#77736a]">
{t(`truth.links.${item.key}.label` as never)}
</p>
<p className="mt-2 font-mono text-2xl font-semibold leading-none text-[#141413]">
{item.value}
</p>
<p className="mt-2 text-[11px] leading-4 text-[#5f5b52]">
{t(`truth.links.${item.key}.detail` as never)}
</p>
</div>
<span
className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center border",
item.tone === "good" && "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]",
item.tone === "warn" && "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
item.tone === "neutral" && "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]"
)}
>
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" aria-hidden="true" />
</span>
</Link>
))}
</div>
</div>
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-2 xl:grid-cols-5">
<VisualFlowNode
label={t("flow.ingest.label")}