Skip to content

feat(web-ui): Glitch Capture entry point and form for PROOF9 (#568)#576

Merged
frankbria merged 1 commit intomainfrom
feat/glitch-capture-web-ui-568
Apr 10, 2026
Merged

feat(web-ui): Glitch Capture entry point and form for PROOF9 (#568)#576
frankbria merged 1 commit intomainfrom
feat/glitch-capture-web-ui-568

Conversation

@frankbria
Copy link
Copy Markdown
Owner

@frankbria frankbria commented Apr 10, 2026

Summary

Closes #568 — Phase 3.5C: Glitch Capture web UI

  • [Capture Glitch] button added to PROOF9 page header, alongside [Run Gates]
  • Full modal form with: description (required), where-found dropdown, scope textarea, 9-gate checkbox list (≥1 required), severity dropdown, optional expiry date
  • Calls POST /api/v2/proof/requirements — backend was already ready
  • On success: modal closes, mutate() re-fetches the requirements table so the new REQ appears immediately
  • On error: inline error shown, modal stays open
  • Form state resets cleanly on reopen

Changes

File Change
web-ui/src/components/proof/CaptureGlitchModal.tsx New component
web-ui/src/components/proof/index.ts Export added
web-ui/src/lib/api.ts proofApi.capture() method added
web-ui/src/types/index.ts CaptureGlitchRequest type added
web-ui/src/app/proof/page.tsx Button + modal wired in
web-ui/src/__tests__/components/proof/CaptureGlitchModal.test.tsx 12 new tests
web-ui/src/__tests__/components/proof/ProofPage.test.tsx Mock updated (CaptureGlitchModal + GateEvidencePanel were missing)

Test plan

  • 12 new unit tests covering: rendering, form field presence, 9-gate checkboxes, validation (empty description, no gates), successful submit with correct payload, title truncation, API error handling, disabled state during submit, cancel, state reset on reopen
  • All 715 tests pass (npm test)
  • npm run build succeeds — all routes clean
  • Python: uv run ruff check . passes

Summary by CodeRabbit

  • New Features

    • Added "Capture Glitch" functionality to convert production failures into PROOF9 requirements.
    • New modal dialog with form fields for description, scope, gates, severity, and expiry.
    • Integrated form validation and error handling for improved user feedback.
  • Tests

    • Added comprehensive test coverage for the new Capture Glitch modal component.

Closes #568

Adds the [Capture Glitch] button to the PROOF9 page header alongside
[Run Gates], opening a full modal form that calls POST /api/v2/proof/requirements
and re-fetches the requirements table on success.

Changes:
- CaptureGlitchModal component: description (required), source dropdown,
  scope textarea, 9-gate checkbox list (≥1 required), severity dropdown,
  optional expiry date; validates before submit, shows inline errors,
  resets state on reopen
- proofApi.capture() method added to api.ts
- CaptureGlitchRequest type added to types/index.ts
- Exported from proof/index.ts
- Wired into proof/page.tsx with captureOpen state + mutate() on success
- 12 new tests covering rendering, validation, submission, error, reset
- ProofPage.test.tsx mock updated to include CaptureGlitchModal +
  GateEvidencePanel (were missing, causing 18 pre-existing test failures)

Tests: 715/715 pass. Build: clean.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

Walkthrough

This pull request introduces a "Capture Glitch" feature to the PROOF9 page, enabling users to convert production failures into permanent proof requirements. It adds a new modal component with a form for collecting failure details, integrates it into the proof page with a button, extends the API client with a capture method, defines supporting types, and includes comprehensive test coverage.

Changes

Cohort / File(s) Summary
Test Suites
web-ui/src/__tests__/components/proof/CaptureGlitchModal.test.tsx
New test suite for CaptureGlitchModal with 211 lines covering form rendering, validation (empty description, no gates selected), successful submission, error handling, loading state, cancellation, and state reset on reopen.
Test Fixtures
web-ui/src/__tests__/components/proof/ProofPage.test.tsx
Updated Jest mock to include GateEvidencePanel and CaptureGlitchModal component stubs.
Modal Component
web-ui/src/components/proof/CaptureGlitchModal.tsx
New client-side React component with form state for description, source, scope, 9 gate checkboxes, severity, expiry, and submission status. Validates required fields, derives title/where from input, constructs CaptureGlitchRequest, calls proofApi.capture, and handles success/error outcomes.
Component Exports
web-ui/src/components/proof/index.ts
Added re-export of CaptureGlitchModal to the proof components barrel.
Page Integration
web-ui/src/app/proof/page.tsx
Introduced captureOpen state and flex button container in header with new outline "Capture Glitch" button (toggles modal) and existing "Run Gates" button. Modal's onClose closes it; onSuccess closes it and triggers SWR mutate() to refresh requirements.
API Extension
web-ui/src/lib/api.ts
Extended proofApi with new capture(workspacePath, body) method that POSTs to /api/v2/proof/requirements with workspace_path query parameter, returning a ProofRequirement.
Type Definitions
web-ui/src/types/index.ts
Added CaptureGlitchRequest interface with fields: title, description, where, severity (uses ProofSeverity), source (constrained to 5 enum values), and created_by.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

Poem

🐰 A glitch appears, from prod it came,
But now there's a form to capture the shame!
With buttons and modals and gates to select,
We hop through the flow to capture, inspect.
The proof page now glimmers—no CLI required,
Web users rejoice, the loop's now wired! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.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): Glitch Capture entry point and form for PROOF9 (#568)' accurately summarizes the main changes: adding a Capture Glitch button entry point and modal form component to the PROOF9 web UI.
Linked Issues check ✅ Passed The PR fully implements all acceptance criteria from issue #568: Capture Glitch button visible, form with all required fields (description, where-found dropdown, scope textarea, 9-gate checkboxes with validation, severity, expiry), API integration via proofApi.capture(), success/error handling, and tests passing.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the Glitch Capture feature: new modal component, API method, types, page integration, and comprehensive tests. No unrelated modifications detected.

✏️ 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/glitch-capture-web-ui-568

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

@claude
Copy link
Copy Markdown

claude bot commented Apr 10, 2026

Review pending - writing to file first

@claude
Copy link
Copy Markdown

claude bot commented Apr 10, 2026

Review: Phase 3.5C Glitch Capture Web UI

Solid implementation overall. Three bugs to fix before merge.


Bug (Medium): Gate checkboxes are never sent to the backend

CaptureGlitchModal.tsx validates that >=1 gate is selected, but selectedGates is never included in the body payload, and CaptureGlitchRequest in types/index.ts has no gates field.

The backend capture_requirement_endpoint passes only title/description/where/severity/source/created_by to capture_requirement(), which derives obligations via LLM internally — it does not accept gates as input.

Two options:

  1. Remove the gate checkboxes if auto-derivation is the intent. Showing them implies user control that does not exist.
  2. Wire them up — add a gates hint field to CaptureRequirementRequest and pass it to capture_requirement() so selections influence derivation.

Validating but silently discarding is the worst outcome.


Bug (Low): Expiry date field is dead code

The expires state is collected and rendered but never included in the payload. The backend CaptureRequirementRequest has no expires field (WaiveRequirementRequest does). Remove the field from the form, or add a note that expiry is set at waiver time, not capture time.


Bug (Low): Axios error detail extraction always returns undefined

In handleSubmit:

const detail = (err as { detail?: string })?.detail;

FastAPI error responses land at err.response.data.detail in axios, not err.detail. This always resolves to undefined, so the custom backend message is never surfaced. Fix:

const axiosErr = err as { response?: { data?: { detail?: string } } };
const detail = axiosErr?.response?.data?.detail;
setError(typeof detail === 'string' ? detail : 'Failed to capture glitch. Please try again.');

Minor: Redundant aria-label alongside label htmlFor

Elements already associated via htmlFor (e.g. <Textarea id="capture-description" aria-label="Description">) do not need an additional aria-label. Harmless but unnecessary.


Test coverage note

Once gate/expiry is resolved, update the submission test to verify exactly what is sent rather than relying on objectContaining without a gates assertion.


What is working well

  • State reset on open via useEffect is clean
  • Disabled state + spinner during submit is correct
  • onSuccess/onClose separation from the parent is a good pattern
  • page.tsx integration (button placement, mutate() on success) is correct
  • 12 tests cover rendering, validation, submission, error handling, cancel, and reopen
  • ProofPage.test.tsx mock update is complete

Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
web-ui/src/__tests__/components/proof/CaptureGlitchModal.test.tsx (1)

125-134: Strengthen payload assertions to cover all mapped form fields.

The submit test currently won’t catch regressions for gates/expires mapping. Add them to the expected payload once serialization is in place.

Suggested assertion update
       expect(mockCapture).toHaveBeenCalledWith(
         WORKSPACE,
         expect.objectContaining({
           title: 'Something broke in production',
           description: 'Something broke in production',
+          where: 'production',
           severity: 'high',
           source: 'production',
           created_by: 'human',
+          gates: ['unit'],
+          expires: null,
         })
       );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/__tests__/components/proof/CaptureGlitchModal.test.tsx` around
lines 125 - 134, The test assertion for CaptureGlitchModal's submit should also
assert the serialized gating and expiry fields: update the
expect(mockCapture).toHaveBeenCalledWith(WORKSPACE,
expect.objectContaining({...})) payload to include gates (e.g., gates: <expected
serialized value>) and expires (e.g., expires: <expected serialized value>)
alongside title, description, severity, source, and created_by; locate the
assertion in CaptureGlitchModal.test.tsx and add the gates and expires keys
matching how the component serializes those form fields so regressions in
gates/expires mapping are caught.
🤖 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/components/proof/CaptureGlitchModal.tsx`:
- Around line 87-115: The submit handler (handleSubmit) currently builds
CaptureGlitchRequest without including selectedGates and expires, so those
validated inputs are dropped; update handleSubmit to add selectedGates (map to
an appropriate field name like gates or affected_gates) and expires (map to
expires_at or ttl) into the body object, update the CaptureGlitchRequest type
definition to include these fields, and ensure the backend request model/handler
accepts and maps those new fields as well so user input is preserved end-to-end.
- Line 130: The modal in CaptureGlitchModal uses DialogContent with class
"max-w-lg", producing a centered dialog instead of a full-screen slide-over;
update the DialogContent usage in CaptureGlitchModal to remove "max-w-lg" and
replace it with full-screen/slide-over appropriate utility classes (e.g., full
width/height and no max-width) so the component renders edge-to-edge on small
and large screens, and adjust padding/margin classes as needed to preserve inner
layout and accessibility (keep the same component name CaptureGlitchModal and
DialogContent so you only change the className and related layout utilities).

---

Nitpick comments:
In `@web-ui/src/__tests__/components/proof/CaptureGlitchModal.test.tsx`:
- Around line 125-134: The test assertion for CaptureGlitchModal's submit should
also assert the serialized gating and expiry fields: update the
expect(mockCapture).toHaveBeenCalledWith(WORKSPACE,
expect.objectContaining({...})) payload to include gates (e.g., gates: <expected
serialized value>) and expires (e.g., expires: <expected serialized value>)
alongside title, description, severity, source, and created_by; locate the
assertion in CaptureGlitchModal.test.tsx and add the gates and expires keys
matching how the component serializes those form fields so regressions in
gates/expires mapping are caught.
🪄 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: 06ff95ff-771d-4aa3-8b60-dea47d722842

📥 Commits

Reviewing files that changed from the base of the PR and between 687935e and 5e13f85.

📒 Files selected for processing (7)
  • web-ui/src/__tests__/components/proof/CaptureGlitchModal.test.tsx
  • web-ui/src/__tests__/components/proof/ProofPage.test.tsx
  • web-ui/src/app/proof/page.tsx
  • web-ui/src/components/proof/CaptureGlitchModal.tsx
  • web-ui/src/components/proof/index.ts
  • web-ui/src/lib/api.ts
  • web-ui/src/types/index.ts

Comment on lines +87 to +115
async function handleSubmit() {
// Validate
if (!description.trim()) {
setError('Description is required');
return;
}
if (selectedGates.size === 0) {
setError('Select at least one gate');
return;
}

setError(null);
setSubmitting(true);

// Derive title from first line of description (max 80 chars)
const title = description.trim().split('\n')[0].slice(0, 80);

// Derive `where` from scope lines, falling back to source
const scopeLines = scopeText.split('\n').map((l) => l.trim()).filter(Boolean);
const where = scopeLines.length > 0 ? scopeLines.join(', ') : source;

const body: CaptureGlitchRequest = {
title,
description: description.trim(),
where,
severity,
source,
created_by: 'human',
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Required/collected form inputs are dropped before API submission.

selectedGates and expires are validated/collected but never included in CaptureGlitchRequest, so user input is discarded on submit.

Proposed fix
   const body: CaptureGlitchRequest = {
     title,
     description: description.trim(),
     where,
     severity,
     source,
     created_by: 'human',
+    gates: Array.from(selectedGates),
+    expires: expires || null,
   };
// Also update web-ui/src/types/index.ts CaptureGlitchRequest
// and backend request model/handler to accept these mapped fields.

Also applies to: 222-233

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

In `@web-ui/src/components/proof/CaptureGlitchModal.tsx` around lines 87 - 115,
The submit handler (handleSubmit) currently builds CaptureGlitchRequest without
including selectedGates and expires, so those validated inputs are dropped;
update handleSubmit to add selectedGates (map to an appropriate field name like
gates or affected_gates) and expires (map to expires_at or ttl) into the body
object, update the CaptureGlitchRequest type definition to include these fields,
and ensure the backend request model/handler accepts and maps those new fields
as well so user input is preserved end-to-end.


return (
<Dialog open={open} onOpenChange={(o) => { if (!o) onClose(); }}>
<DialogContent className="max-w-lg">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Modal size does not meet the full-screen requirement.

DialogContent is constrained to max-w-lg; this is a centered dialog, not a full-screen modal/slide-over.

Proposed fix
-      <DialogContent className="max-w-lg">
+      <DialogContent className="h-[100dvh] w-[100dvw] max-w-none rounded-none overflow-y-auto">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<DialogContent className="max-w-lg">
<DialogContent className="h-[100dvh] w-[100dvw] max-w-none rounded-none overflow-y-auto">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/proof/CaptureGlitchModal.tsx` at line 130, The modal in
CaptureGlitchModal uses DialogContent with class "max-w-lg", producing a
centered dialog instead of a full-screen slide-over; update the DialogContent
usage in CaptureGlitchModal to remove "max-w-lg" and replace it with
full-screen/slide-over appropriate utility classes (e.g., full width/height and
no max-width) so the component renders edge-to-edge on small and large screens,
and adjust padding/margin classes as needed to preserve inner layout and
accessibility (keep the same component name CaptureGlitchModal and DialogContent
so you only change the className and related layout utilities).

@frankbria frankbria merged commit 19b0cd1 into main Apr 10, 2026
16 checks passed
@frankbria frankbria deleted the feat/glitch-capture-web-ui-568 branch April 10, 2026 05:02
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.

[Phase 3.5C] Glitch Capture: entry point and capture form

1 participant