Skip to content

feat(web-ui): link failed tasks to PROOF9 gate failure for root cause (#474)#496

Merged
frankbria merged 2 commits intomainfrom
feat/issue-474-failed-task-proof9-links
Mar 24, 2026
Merged

feat(web-ui): link failed tasks to PROOF9 gate failure for root cause (#474)#496
frankbria merged 2 commits intomainfrom
feat/issue-474-failed-task-proof9-links

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Mar 24, 2026

Summary

  • TaskDetailModal FAILED state: adds destructive guidance panel + "View PROOF9 Gates" (primary) + "Reset to Ready" (outline) footer buttons
  • Proof page ?gate= filter: useSearchParams reads ?gate= param and filters the requirements table to show only requirements with matching obligation gate names (pytest, ruff, build, custom)
  • Filter indicator badge with clear link shown when active
  • Empty-state row when no requirements match the filter
  • Proof page wrapped in <Suspense> for Next.js useSearchParams compatibility

Closes #474

Test plan

  • FAILED task modal shows red guidance panel + footer buttons
  • "View PROOF9 Gates" navigates to /proof
  • "Reset to Ready" transitions FAILED → READY
  • /proof?gate=pytest filters table to pytest-obligation requirements
  • /proof?gate=ruff filters to ruff-obligation requirements
  • No-match shows "No requirements match gate X" row
  • Build passes clean

Summary by CodeRabbit

  • New Features
    • Added gate filtering to the requirements table with a visible "Filtered by gate" pill and clear-filter action; empty-state reflects active filters.
    • Added a failed-task guidance panel and contextual actions including "Reset to Ready" and "View PROOF9 Gates" to navigate to related gate results.
    • Improved top summary layout to wrap responsively for better display on narrow screens.

…#474)

- TaskDetailModal: add FAILED-state guidance panel with destructive styling
- TaskDetailModal: add 'View PROOF9 Gates' + 'Reset to Ready' footer buttons for FAILED
- proof/page.tsx: add ?gate= filter support using useSearchParams
- Filter requirements by obligation.gate match (pytest/ruff/build/custom)
- Show 'Filtered by gate: X' badge with clear link when filter active
- Empty state for no-match case
- Wrap ProofPageContent in Suspense for useSearchParams compatibility
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 2026

Walkthrough

The PR adds gate-based filtering to the Proof page (reads ?gate=), refactors the page into a Suspense-wrapped content component, and enhances the TaskDetailModal to show FAILED-specific guidance plus a "View PROOF9 Gates" action that navigates to the Proof page with a gate query.

Changes

Cohort / File(s) Summary
Proof Page Filtering
web-ui/src/app/proof/page.tsx
Refactored exported page into ProofPageContent + ProofPage that renders it inside <Suspense>. Added useSearchParams to extract/normalize gate query param, computed gateFilter, derived visibleReqs by filtering requirements whose obligations include the gate, updated header/top-summary layout to wrap, and added a "Filtered by gate" pill and empty-state row with clear link.
Failed Task Modal Enhancement
web-ui/src/components/tasks/TaskDetailModal.tsx
When task.status === 'FAILED', renders a destructive guidance panel with PROOF9 gate instructions (conditioned on task.requirement_ids). Added modal footer actions: "Reset to Ready" (outline, disabled/loading handling) and "View PROOF9 Gates" (closes modal and navigates to /proof?gate=<gate> computed from requirementsMap or falls back to /proof).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as TaskDetailModal
  participant Router
  participant ProofPage
  participant Data as RequirementsStore

  User->>UI: Open failed task modal
  UI->>UI: Render FAILED guidance + "View PROOF9 Gates"
  User->>UI: Click "View PROOF9 Gates"
  UI->>Router: navigate to /proof?gate=<derivedGate>
  Router->>ProofPage: load page with gate query
  ProofPage->>Data: request requirements list
  Data-->>ProofPage: return requirements
  ProofPage->>ProofPage: filter requirements by gate (visibleReqs)
  ProofPage-->>User: render filtered requirements (or empty-state)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 I nibble at failures, soft and light,
I point to gates that hid from sight.
Click my paw — the proof appears,
Filters clear your trailing fears.
Hop on, root cause in sight! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(web-ui): link failed tasks to PROOF9 gate failure for root cause' clearly and concisely summarizes the main change: adding the ability to link failed tasks to PROOF9 gate failures.
Linked Issues check ✅ Passed The PR implements core requirements: TaskDetailModal shows PROOF9 guidance for FAILED tasks with navigation buttons, Proof page accepts and filters by ?gate= parameter, and graceful fallback is provided when gate cannot be determined.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #474 objectives: enhancing TaskDetailModal FAILED state UI, implementing gate filtering in Proof page, and adding Suspense wrapping for Next.js compatibility.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-474-failed-task-proof9-links

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Mar 24, 2026

PR review posted below as structured feedback on the two changed files.

@claude
Copy link

claude bot commented Mar 24, 2026

proof/page.tsx - IIFE in JSX: The IIFE pattern used to scope visibleReqs inside JSX is unusual and harder to read. Extract it above the return statement instead. The conditional rendering block becomes a clean JSX expression and the tricky double-closing pattern disappears.

@claude
Copy link

claude bot commented Mar 24, 2026

proof/page.tsx - Suspense missing fallback: The Suspense wrapper has no fallback prop so React renders nothing during useSearchParams hydration. This can cause a layout flash. Add a minimal loading placeholder as the fallback.

@claude
Copy link

claude bot commented Mar 24, 2026

proof/page.tsx - Stats bar shows unfiltered totals: When gateFilter is active the open/satisfied/waived/total counts still reflect all requirements not just the visible filtered set. A user who filters by pytest and sees 10 total cannot tell if that is 10 matching or 10 overall. Suggest adding a showing-N-of-M label next to the filter badge.

@claude
Copy link

claude bot commented Mar 24, 2026

proof/page.tsx - Clear filter button accessibility: The unicode X character link used to clear the gate filter has no accessible label. Add aria-label='Clear gate filter' for screen reader users.

@claude
Copy link

claude bot commented Mar 24, 2026

TaskDetailModal.tsx - View PROOF9 Gates does not pass gate context: The proof page now supports ?gate= filtering but the button in the FAILED task modal navigates to /proof without it. If the gate that caused failure is derivable from task.requirement_ids this would be a natural integration point. Worth a follow-up issue once the task object exposes that info.

@claude
Copy link

claude bot commented Mar 24, 2026

Overall: Good focused PR that fills a real gap. The IIFE refactor and Suspense fallback are the two items worth addressing before merge. The stats/filter consistency and gate-context navigation are solid follow-up candidates to keep this PR scoped tightly. Using Suspense around useSearchParams is exactly right for Next.js App Router.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/src/app/proof/page.tsx`:
- Around line 204-208: The clear-filter Link next to the gateFilter label is
icon-only and lacks an accessible name; update the Link (the JSX element
rendering the "✕") to include an accessible label by adding an aria-label (e.g.,
aria-label="Clear filter") or by including visible text for screen readers
(e.g., a visually-hidden "Clear filter" span) so assistive tech can announce its
purpose; ensure this change is applied to the Link element that renders the
close icon adjacent to gateFilter.

In `@web-ui/src/components/tasks/TaskDetailModal.tsx`:
- Around line 226-241: The FAILED-state panel in TaskDetailModal currently links
generically to /proof and loses root-cause context; update the component to
detect and carry the failing gate/requirement (use task.requirement_ids and any
resolved gate identifier present on the task object) and build a contextual
link: if a single requirement id exists navigate to /proof/<reqId>, if a gate
identifier is available navigate to /proof?gate=<gateId>, otherwise fall back to
/proof; apply the same change to the similar block referenced around the other
FAILED-state rendering (the other FAILED panel at the 281-305 region) so both
CTA buttons/routes preserve and pass the resolved gate/requirement context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 98f9d415-2874-4579-a9fa-2549cbf70d04

📥 Commits

Reviewing files that changed from the base of the PR and between 7f6ded0 and 4593a47.

📒 Files selected for processing (2)
  • web-ui/src/app/proof/page.tsx
  • web-ui/src/components/tasks/TaskDetailModal.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
web-ui/src/app/proof/page.tsx (2)

399-405: Add a fallback to <Suspense> for smoother hydration.

When useSearchParams suspends during SSR/hydration, React renders the fallback. Without one, users may see a brief flash of empty content. Consider adding a minimal loading state.

♻️ Suggested fix with loading fallback
 export default function ProofPage() {
   return (
-    <Suspense>
+    <Suspense fallback={
+      <main className="min-h-screen bg-background">
+        <div className="mx-auto max-w-7xl px-4 py-8">
+          <div className="space-y-2">
+            {[...Array(4)].map((_, i) => (
+              <div key={i} className="h-12 animate-pulse rounded-md bg-muted" />
+            ))}
+          </div>
+        </div>
+      </main>
+    }>
       <ProofPageContent />
     </Suspense>
   );
 }

This reuses the same skeleton pattern already present in ProofPageContent for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/proof/page.tsx` around lines 399 - 405, The Suspense wrapper
in ProofPage lacks a fallback, causing a blank flash when useSearchParams
suspends; update the ProofPage component to pass a minimal loading fallback prop
to Suspense (e.g., a small skeleton/loading UI that mirrors the skeleton used
inside ProofPageContent) so hydration shows that state until ProofPageContent
resolves; locate ProofPage and Suspense around ProofPageContent and reuse the
skeleton pattern from ProofPageContent for the fallback.

190-196: Consider replacing the IIFE with useMemo for clearer intent.

The inline IIFE pattern works but is unconventional in React. Using useMemo makes the derived computation explicit and avoids re-filtering on every render when dependencies haven't changed.

♻️ Suggested refactor using useMemo

Add useMemo to the import on line 3:

-import { useState, useEffect, Suspense } from 'react';
+import { useState, useEffect, Suspense, useMemo } from 'react';

Then replace the IIFE with a memoized value before the return statement (after line 136):

+  const visibleReqs = useMemo(() => {
+    if (!data || data.total === 0) return [];
+    return gateFilter
+      ? data.requirements.filter((r) =>
+          r.obligations.some((o) => o.gate.toLowerCase() === gateFilter)
+        )
+      : data.requirements;
+  }, [data, gateFilter]);

Then simplify the render block (lines 190-328) to use visibleReqs directly without the IIFE wrapper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/app/proof/page.tsx` around lines 190 - 196, Replace the inline
IIFE that computes visibleReqs with a memoized value using React's useMemo:
import useMemo, create a const visibleReqs = useMemo(() => gateFilter ?
data.requirements.filter(r => r.obligations.some(o => o.gate.toLowerCase() ===
gateFilter)) : data.requirements, [data, gateFilter]) placed before the
component return, and then use visibleReqs directly in the render instead of
invoking the IIFE; reference the existing symbols gateFilter, data.requirements,
visibleReqs and ensure the dependency array includes data and gateFilter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web-ui/src/app/proof/page.tsx`:
- Around line 399-405: The Suspense wrapper in ProofPage lacks a fallback,
causing a blank flash when useSearchParams suspends; update the ProofPage
component to pass a minimal loading fallback prop to Suspense (e.g., a small
skeleton/loading UI that mirrors the skeleton used inside ProofPageContent) so
hydration shows that state until ProofPageContent resolves; locate ProofPage and
Suspense around ProofPageContent and reuse the skeleton pattern from
ProofPageContent for the fallback.
- Around line 190-196: Replace the inline IIFE that computes visibleReqs with a
memoized value using React's useMemo: import useMemo, create a const visibleReqs
= useMemo(() => gateFilter ? data.requirements.filter(r => r.obligations.some(o
=> o.gate.toLowerCase() === gateFilter)) : data.requirements, [data,
gateFilter]) placed before the component return, and then use visibleReqs
directly in the render instead of invoking the IIFE; reference the existing
symbols gateFilter, data.requirements, visibleReqs and ensure the dependency
array includes data and gateFilter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef295985-00e8-42ca-aae2-0b12ebc4f4f5

📥 Commits

Reviewing files that changed from the base of the PR and between 4593a47 and 4d918e2.

📒 Files selected for processing (2)
  • web-ui/src/app/proof/page.tsx
  • web-ui/src/components/tasks/TaskDetailModal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-ui/src/components/tasks/TaskDetailModal.tsx

@claude
Copy link

claude bot commented Mar 24, 2026

Code Review — PR #496: Link failed tasks to PROOF9 gate failure for root cause

The overall direction is correct and the UX flow makes sense. The implementation is clean and the Suspense wrapper is the right fix for the Next.js useSearchParams constraint. A few issues worth addressing before merge:

BUG 1: Gate link picks the wrong obligation in some cases

File: web-ui/src/components/tasks/TaskDetailModal.tsx, gate-link derivation block

The loop that derives proofLink takes the first obligation of the first requirement with any obligation. If the task has multiple requirements or a requirement has multiple obligations, the picked gate may not be the one that caused the failure. Consider checking obligation.status (or any similar failure signal) to pick the most relevant gate, or iterate all obligations across all linked requirements to find the best candidate rather than stopping at the first match.

BUG 2: Summary counts are not adjusted when a gate filter is active

File: web-ui/src/app/proof/page.tsx, summary row

The summary row (N open / N satisfied / N waived / N total) is sourced from data.by_status and data.total, which reflect the full unfiltered dataset. When gateFilter is set, the table may show 3 rows while the summary still says 12 total. Either hide the counts row when filtering, or recompute the counts from visibleReqs so they match what is displayed in the table.

ISSUE 3: Missing Suspense fallback

File: web-ui/src/app/proof/page.tsx, Suspense wrapper

Suspense without a fallback prop renders null during the hydration gap, giving a blank screen. Even fallback={null} (explicit) is better because it documents the intent. A loading skeleton matching the existing isLoading skeleton would be ideal.

ISSUE 4: Code quality: IIFE pattern inside JSX

Both files use immediately-invoked function expressions to introduce local variables inside JSX. This pattern is valid but unconventional in React and makes the render path harder to follow. The standard approach is to compute visibleReqs and proofLink above the return statement, before the JSX block. This also eliminates the nesting that currently hides the empty-state check inside the IIFE.

MINOR 5: gateFilter value is rendered verbatim without sanitization

The value from searchParams.get('gate') is displayed in two places. React's escaping prevents XSS, but a malformed or excessively long query string value will be shown to the user. A simple slice(0, 64) guard or an allowlist check against known gate names (pytest, ruff, build, custom) would make this more defensive.

MINOR 6: Pre-existing but newly exercised: extra SWR fetch on modal open

useRequirementsLookup in TaskDetailModal issues a fetch for all PROOF9 requirements whenever the modal opens on a page that has not already fetched them. This is pre-existing behavior, but the new FAILED-state code path makes it more likely to be hit. Worth noting for a future optimization (e.g., conditionally fetching only when task.status === 'FAILED').

Summary: Bugs 1 and 2 are the most important to fix before merge. Issue 3 (missing Suspense fallback) and Issue 4 (IIFE style) are meaningful improvements. Items 5 and 6 are lower priority.

@frankbria frankbria merged commit 814f550 into main Mar 24, 2026
11 checks passed
@frankbria frankbria deleted the feat/issue-474-failed-task-proof9-links branch March 24, 2026 23:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UX: Link failed tasks to their PROOF9 gate failure for root cause

1 participant