diff --git a/CLAUDE.md b/CLAUDE.md index f596ec66..23342a26 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ SHIP: cf pr create → cf pr merge LOOP: Glitch → cf proof capture → New REQ → Enforced forever ``` -**Status: CLI ✅ | Server ✅ | ReAct agent ✅ | Web UI ✅ | Agent adapters ✅ | Multi-provider LLM ✅ | Next: Phase 3.5C** — See `docs/PRODUCT_ROADMAP.md`. +**Status: CLI ✅ | Server ✅ | ReAct agent ✅ | Web UI ✅ | Agent adapters ✅ | Multi-provider LLM ✅ | Next: Phase 4A** — See `docs/PRODUCT_ROADMAP.md`. If you are an agent working in this repo: **do not improvise architecture**. Follow the documents listed below. @@ -34,12 +34,11 @@ If you are an agent working in this repo: **do not improvise architecture**. Fol **Rule 0:** If a change does not directly support the Think → Build → Prove → Ship pipeline, do not implement it. -### Current Focus: Phase 3.5C +### Current Focus: Phase 4A -**Phase 3.5B is complete** — `[Run Gates]` button, live gate progress, per-gate evidence display (`GateEvidencePanel`), and run history panel (`RunHistoryPanel`) are all shipped. New backend endpoints: `GET /api/v2/proof/runs` and `GET /api/v2/proof/runs/{run_id}/evidence`. +**Phase 3.5C is complete** — `CaptureGlitchModal` form (description/markdown, source, scope, gate obligations, severity, expiry) reachable from the PROOF9 page and the persistent sidebar "Capture Glitch" button. REQ detail view (`/proof/[req_id]`) ships markdown description rendering, `ProofScope` metadata display, obligations table with `Latest Run` column, sortable/filterable evidence history, and empty-state CTA. Backend: `ScopeOut` model on `RequirementResponse`. Issues #568, #569. Next, in order: -- **3.5C**: Glitch capture web UI - **4A**: PR status tracking + PROOF9 merge gate - **4B**: Post-merge glitch capture loop - **5.1–5.5**: Platform completeness (#554–#565) diff --git a/codeframe/ui/routers/proof_v2.py b/codeframe/ui/routers/proof_v2.py index 5bfb4449..623a14ad 100644 --- a/codeframe/ui/routers/proof_v2.py +++ b/codeframe/ui/routers/proof_v2.py @@ -103,6 +103,16 @@ class RunProofRequest(BaseModel): gate: Optional[Gate] = Field(default=None, description="Run only this gate (unit, sec, contract, etc.)") +class ScopeOut(BaseModel): + """Serialized requirement scope.""" + + routes: list[str] = Field(default_factory=list) + components: list[str] = Field(default_factory=list) + apis: list[str] = Field(default_factory=list) + files: list[str] = Field(default_factory=list) + tags: list[str] = Field(default_factory=list) + + class ObligationOut(BaseModel): """Serialized proof obligation.""" @@ -145,6 +155,7 @@ class RequirementResponse(BaseModel): created_by: str source_issue: Optional[str] related_reqs: list[str] + scope: Optional[ScopeOut] = None class CaptureRequirementResponse(RequirementResponse): @@ -265,6 +276,13 @@ def _req_to_response(req) -> RequirementResponse: created_by=req.created_by, source_issue=req.source_issue, related_reqs=req.related_reqs, + scope=ScopeOut( + routes=req.scope.routes, + components=req.scope.components, + apis=req.scope.apis, + files=req.scope.files, + tags=req.scope.tags, + ) if req.scope else None, ) diff --git a/docs/PHASE_3_UI_ARCHITECTURE.md b/docs/PHASE_3_UI_ARCHITECTURE.md index bdae6aa0..225e2db5 100644 --- a/docs/PHASE_3_UI_ARCHITECTURE.md +++ b/docs/PHASE_3_UI_ARCHITECTURE.md @@ -134,6 +134,10 @@ Persistent left sidebar with icon + label navigation: 4. **Execution** (play/monitor icon) - only visible when runs are active 5. **Blockers** (alert icon) - badge count for open blockers 6. **Review** (git branch icon) +7. **PROOF9** (checkmark icon) +8. **Sessions** (command-line icon) - badge count for active sessions + +**Sidebar action button**: A **"Capture Glitch"** button (Add01Icon) is always visible at the bottom of the sidebar. Clicking it opens `CaptureGlitchModal` without navigating away from the current page. This is the primary entry point for the glitch capture closed loop from anywhere in the app. ### Secondary Navigation - **Workspace breadcrumb** at top: shows current repo path, links to workspace root @@ -367,10 +371,21 @@ ProofPage (/proof) └── (click → loads GateEvidencePanel for that run) ProofRequirementPage (/proof/[req_id]) -├── RequirementDetail -│ ├── ObligationsList -│ └── EvidenceHistory -└── WaiveForm +├── RequirementHeader +│ ├── Title, severity badge, ProofStatusBadge +│ ├── MarkdownDescription (ReactMarkdown, images disallowed) +│ ├── MetadataRow (created_at, source, source_issue, created_by, waiver expiry) +│ └── ScopeChips (files, routes, components, APIs, tags from ProofScope) +├── ObligationsTable ← new (Phase 3.5C) +│ └── ObligationRow[] (gate name, Latest Run pass/fail badge, link to evidence) +│ └── Latest Run column: shows most-recent run result per gate +├── EvidenceHistory +│ ├── FilterBar (gate select, result select, search input, Reset Filters) +│ ├── EvidenceTable (sortable: gate, result, run_id, timestamp, artifact) +│ │ └── EvidenceRow[] (click run_id → focusRun filter) +│ └── EmptyState CTA: "Capture a Glitch" link when no evidence exists +├── GateEvidencePanel (loads artifact content for latest run) +└── WaiveDialog (modal, opens via Waive button in header) ``` **API Endpoints Used:** diff --git a/docs/PRODUCT_ROADMAP.md b/docs/PRODUCT_ROADMAP.md index adc21570..2b377235 100644 --- a/docs/PRODUCT_ROADMAP.md +++ b/docs/PRODUCT_ROADMAP.md @@ -36,23 +36,11 @@ Fully shipped: `[Run Gates]` button on the PROOF9 page, live gate progress view --- -### Milestone C: Glitch Capture UI ❌ NOT STARTED +### Milestone C: Glitch Capture UI ✅ COMPLETE (#568, #569) -**Current state**: The CLI has `cf proof capture` for converting a production glitch into a permanent PROOF9 requirement. The proof page has a glitch_type *filter* for reading existing requirements but no capture form (verified 2026-04-06). +Fully shipped: `CaptureGlitchModal` form reachable from the PROOF9 page header and the sidebar "Capture Glitch" button. Form collects description (markdown), source (production/QA/dogfooding/monitoring), scope (files/routes/components, stored as `ScopeOut` on the backend and `ProofScope` in the frontend types), gate obligations (multi-select), severity, and optional expiry. On submit, creates a new REQ in the requirements ledger immediately. -**What to build**: - -- A **"Capture Glitch"** entry point reachable from the PROOF9 page and the sidebar -- A structured form collecting: - - Description of the failure (free text, supports markdown) - - Where it was found (production / QA / dogfooding / monitoring) - - Scope selector: which files, routes, or components are affected - - Which PROOF9 gates should be required as proof obligations (multi-select) - - Severity and optional expiry (for time-bounded obligations) -- On submit: creates a new REQ in the requirements ledger, associates obligations, and shows the new requirement in the PROOF9 table immediately -- A **REQ detail view** that shows the glitch description, its obligations, and the evidence history across all gate runs - -**Why it matters for the vision**: The glitch capture closed loop — *Ship → Discover glitch → Capture → Enforce forever → Ship with higher confidence* — is described as "the defining feature of the system." Without a web UI for capture, this loop requires CLI access and will be skipped by most users. This is the most differentiated feature in CodeFRAME and it is currently invisible to web users. +REQ detail view (`/proof/[req_id]`): markdown-rendered description, scope metadata display, obligations table with a `Latest Run` column showing pass/fail per gate from the most recent run, sortable/filterable evidence history, and a "Capture Glitch" empty-state CTA when no evidence exists yet. --- @@ -201,7 +189,7 @@ These are items that were considered and excluded because they do not serve the |---|---|---|---| | 3.5A | Bidirectional agent chat | ✅ Complete | #500–509 | | 3.5B | Run gates from the web UI | ✅ Complete | #566, #567, #574, #575 | -| 3.5C | Glitch capture UI | ❌ Not started | — | +| 3.5C | Glitch capture UI | ✅ Complete | #568, #569 | | 4A | PR status + PROOF9 merge gate | ❌ Not started | — | | 4B | Post-merge glitch capture loop | ❌ Not started | — | | 5.1 | Settings page | ❌ Not started | #554–556 | @@ -210,6 +198,6 @@ These are items that were considered and excluded because they do not serve the | 5.4 | PRD stress-test web UI | ❌ Not started | #561–562 | | 5.5 | GitHub Issues import | ❌ Not started | #563–565 | -**Current focus**: Phase 3.5C — Glitch capture web UI. +**Current focus**: Phase 4A — PR status tracking + PROOF9 merge gate. The ordering within Phase 5 is by onboarding impact. Settings (5.1) and cost (5.2) block new users earliest. diff --git a/web-ui/__mocks__/@hugeicons/react.js b/web-ui/__mocks__/@hugeicons/react.js index 6505e9ba..6f998146 100644 --- a/web-ui/__mocks__/@hugeicons/react.js +++ b/web-ui/__mocks__/@hugeicons/react.js @@ -37,6 +37,7 @@ module.exports = { SentIcon: createIconMock('SentIcon'), // AppSidebar Home01Icon: createIconMock('Home01Icon'), + Add01Icon: createIconMock('Add01Icon'), // PipelineProgressBar Tick01Icon: createIconMock('Tick01Icon'), // Task Board components diff --git a/web-ui/__tests__/app/proof/req_id/page.test.tsx b/web-ui/__tests__/app/proof/req_id/page.test.tsx index dc4e9c23..3ada6399 100644 --- a/web-ui/__tests__/app/proof/req_id/page.test.tsx +++ b/web-ui/__tests__/app/proof/req_id/page.test.tsx @@ -6,10 +6,16 @@ jest.mock('@/lib/api', () => ({ proofApi: { getRequirement: jest.fn(), getEvidence: jest.fn(), + getRunDetail: jest.fn(), waive: jest.fn(), }, })); +jest.mock('react-markdown', () => ({ + __esModule: true, + default: ({ children }: { children: string }) =>
{children}
, +})); + jest.mock('@/lib/workspace-storage', () => ({ getSelectedWorkspacePath: jest.fn(() => '/test/workspace'), })); @@ -45,6 +51,7 @@ const baseReq = { source_issue: null, related_reqs: [], source: 'manual', + scope: null, }; const waivedReq = { @@ -67,15 +74,116 @@ describe('ProofDetailPage', () => { localStorageMock.clear(); }); - const setupSWR = (req: typeof baseReq) => { + const setupSWR = (req: typeof baseReq, evidence: unknown[] = []) => { mockUseSWR.mockImplementation((key: any) => { + if (typeof key === 'string' && key.includes('/runs/') && key.includes('/evidence')) { + // Run detail endpoint + return { data: { evidence: [] }, error: undefined, isLoading: false, mutate: jest.fn() } as any; + } if (typeof key === 'string' && key.includes('/evidence')) { - return { data: mockEvidenceResponse, error: undefined, isLoading: false, mutate: jest.fn() } as any; + return { data: evidence, error: undefined, isLoading: false, mutate: jest.fn() } as any; } return { data: req, error: undefined, isLoading: false, mutate: jest.fn() } as any; }); }; + describe('description rendering', () => { + it('renders description via ReactMarkdown', async () => { + setupSWR(baseReq); + render({children}
, +})); + jest.mock('swr'); jest.mock('next/navigation', () => ({ useParams: () => ({ req_id: 'REQ-001' }), @@ -65,6 +70,7 @@ const REQ: ProofRequirement = { created_by: 'user', source_issue: null, related_reqs: [], + scope: null, }; function setup(evidence: ProofEvidence[] = EVIDENCE) { diff --git a/web-ui/src/app/proof/[req_id]/page.tsx b/web-ui/src/app/proof/[req_id]/page.tsx index 5e942106..0fd289f3 100644 --- a/web-ui/src/app/proof/[req_id]/page.tsx +++ b/web-ui/src/app/proof/[req_id]/page.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, useMemo } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; +import ReactMarkdown from 'react-markdown'; import useSWR from 'swr'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -114,6 +115,19 @@ export default function ProofDetailPage() { [latestRunDetail, reqId] ); + // Map gate name → most-recent evidence entry for that gate + const latestRunByGate = useMemo{req.description}
+Failed to load evidence.
)} {!evidenceLoading && !evidenceError && (!evidence || !Array.isArray(evidence) || evidence.length === 0) && ( -No evidence recorded yet.
+No gate runs yet for this requirement.
+ +