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
45 changes: 39 additions & 6 deletions web-ui/src/app/proof/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client';

import { useState, useEffect } from 'react';
import { useState, useEffect, Suspense } from 'react';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import useSWR from 'swr';
import { InformationCircleIcon } from '@hugeicons/react';
import {
Expand Down Expand Up @@ -117,10 +118,12 @@ function WaiveDialog({
);
}

export default function ProofPage() {
function ProofPageContent() {
const [workspacePath, setWorkspacePath] = useState<string | null>(null);
const [workspaceReady, setWorkspaceReady] = useState(false);
const [waivedReq, setWaivedReq] = useState<ProofRequirement | null>(null);
const searchParams = useSearchParams();
const gateFilter = searchParams.get('gate')?.toLowerCase() ?? null;

useEffect(() => {
setWorkspacePath(getSelectedWorkspacePath());
Expand Down Expand Up @@ -184,13 +187,26 @@ export default function ProofPage() {
</div>
)}

{data && data.total > 0 && (
{data && data.total > 0 && (() => {
const visibleReqs = gateFilter
? data.requirements.filter((r) =>
r.obligations.some((o) => o.gate.toLowerCase() === gateFilter)
)
: data.requirements;

return (
<>
<div className="mb-4 flex gap-4 text-sm text-muted-foreground">
<div className="mb-4 flex flex-wrap items-center gap-4 text-sm text-muted-foreground">
<span>{data.by_status?.open ?? 0} open</span>
<span>{data.by_status?.satisfied ?? 0} satisfied</span>
<span>{data.by_status?.waived ?? 0} waived</span>
<span className="font-medium text-foreground">{data.total} total</span>
{gateFilter && (
<span className="flex items-center gap-1.5 rounded-full border bg-muted px-2.5 py-0.5 text-xs font-medium text-foreground">
Filtered by gate: {gateFilter}
<Link href="/proof" aria-label={`Clear gate filter ${gateFilter}`} className="text-muted-foreground hover:text-foreground">✕</Link>
</span>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)}
</div>

{/* Status legend */}
Expand Down Expand Up @@ -260,7 +276,15 @@ export default function ProofPage() {
</tr>
</thead>
<tbody>
{data.requirements.map((req) => (
{visibleReqs.length === 0 && (
<tr>
<td colSpan={8} className="px-4 py-8 text-center text-sm text-muted-foreground">
No requirements match gate &quot;{gateFilter}&quot;.{' '}
<Link href="/proof" className="text-primary hover:underline">Clear filter</Link>
</td>
</tr>
)}
{visibleReqs.map((req) => (
<tr key={req.id} className="border-b last:border-0 hover:bg-muted/30">
<td className="px-4 py-3 font-mono text-xs">
<Link href={`/proof/${encodeURIComponent(req.id)}`} className="text-primary hover:underline">
Expand Down Expand Up @@ -300,7 +324,8 @@ export default function ProofPage() {
</table>
</div>
</>
)}
);
})()}

{waivedReq && (
<WaiveDialog
Expand Down Expand Up @@ -370,3 +395,11 @@ export default function ProofPage() {
</TooltipProvider>
);
}

export default function ProofPage() {
return (
<Suspense>
<ProofPageContent />
</Suspense>
);
}
56 changes: 56 additions & 0 deletions web-ui/src/components/tasks/TaskDetailModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
Time01Icon,
ViewIcon,
BookOpen01Icon,
Alert02Icon,
CheckListIcon,
} from '@hugeicons/react';
import {
Dialog,
Expand Down Expand Up @@ -221,6 +223,24 @@ export function TaskDetailModal({
</div>
)}

{/* FAILED-state guidance panel */}
{task.status === 'FAILED' && (
<div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2.5">
<div className="flex items-start gap-2">
<Alert02Icon className="mt-0.5 h-4 w-4 shrink-0 text-destructive" />
<div className="space-y-0.5">
<p className="text-xs font-medium text-destructive">Task failed during execution</p>
<p className="text-xs text-muted-foreground">
Check PROOF9 gates to identify which quality requirements need attention.
{(task.requirement_ids ?? []).length === 0 && (
<> Use the button below to view all gates.</>
)}
</p>
</div>
</div>
</div>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)}

<DialogFooter>
{task.status === 'BACKLOG' && (
<Button
Expand Down Expand Up @@ -258,6 +278,42 @@ export function TaskDetailModal({
Execute
</Button>
)}
{task.status === 'FAILED' && (() => {
// Derive the best proof link from requirement obligations if available
let proofLink = '/proof';
for (const reqId of (task.requirement_ids ?? [])) {
const req = requirementsMap.get(reqId);
const gate = req?.obligations?.[0]?.gate?.toLowerCase();
if (gate) { proofLink = `/proof?gate=${encodeURIComponent(gate)}`; break; }
}
return (
<>
<Button
variant="outline"
size="sm"
disabled={isUpdating}
onClick={handleMarkReady}
>
{isUpdating ? (
<Loading03Icon className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<CheckmarkCircle01Icon className="mr-1.5 h-3.5 w-3.5" />
)}
Reset to Ready
</Button>
<Button
size="sm"
onClick={() => {
onClose();
router.push(proofLink);
}}
>
<CheckListIcon className="mr-1.5 h-3.5 w-3.5" />
View PROOF9 Gates
</Button>
</>
);
})()}
</DialogFooter>
</>
)}
Expand Down
Loading