Q_TASK_EVALUATOR_EXTRACTOR_V4.jsx
Design Mockups/DOC23 Mockups/Q_TASK_EVALUATOR_EXTRACTOR_V4.jsx
import React, { useState } from "react";
// ═══════════════════════════════════════════════════════════════
// EVALUATOR — V4
// Major changes from V3:
// • Sidebar gone (moved to slide-out settings drawer)
// • No pipeline indicator, no verdict banner — replaced with one-line status
// • No destination picker in outcomes — routing IS the canvas wiring;
// the outcome configures only RECOVERY ACTIONS the agent tries before
// firing failed_out
// • New "Feedback to next module" section — explicit output shape
// • One accent color; status colors only (green/red/amber)
// • Outcomes visible immediately, three short paragraphs per outcome
// • Meta-outcome has no special visual treatment (just a tag)
// ═══════════════════════════════════════════════════════════════
const c = {
bg:"#0f1117",
bgCard:"#161920",
bgHover:"#1c1f2a",
bgPanel:"#131620",
border:"#262a36",
borderSoft:"#1f232e",
text:"#e2e4ea",
textSec:"#8b8fa3",
textTer:"#5d6178",
accent:"#5b8af5",
green:"#34d399",
red:"#f87171",
amber:"#fbbf24",
};
const sans = "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
const mono = "'SF Mono','Cascadia Code','Fira Code', monospace";
// ─── Helpers ───
const Btn = ({children, primary, ghost, sm, onClick}) => {
const styles = primary
? {background:c.accent,color:"#fff",border:"none"}
: ghost
? {background:"transparent",color:c.textSec,border:"none"}
: {background:c.bgHover,color:c.text,border:`1px solid ${c.border}`};
return <button onClick={onClick} style={{...styles,padding:sm?"4px 9px":"7px 13px",borderRadius:4,fontSize:sm?10:11,fontWeight:500,cursor:"pointer",fontFamily:sans,whiteSpace:"nowrap"}}>{children}</button>;
};
const Input = ({value, w, onChange, placeholder}) => (
<input defaultValue={value} placeholder={placeholder} onChange={onChange||(()=>{})} style={{width:w||"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:3,padding:"4px 8px",fontSize:11,color:c.text,fontFamily:sans,outline:"none"}} />
);
const Textarea = ({value, h, placeholder, onChange}) => (
<textarea defaultValue={value} placeholder={placeholder} onChange={onChange||(()=>{})}
style={{width:"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:4,padding:"10px 12px",fontSize:12,color:c.text,fontFamily:sans,resize:"vertical",minHeight:h||72,outline:"none",lineHeight:1.55}} />
);
const Select = ({opts, v, onChange, w}) => (
<select defaultValue={v} onChange={onChange||(()=>{})} style={{width:w||"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:3,padding:"4px 8px",fontSize:11,color:c.text,fontFamily:sans,outline:"none"}}>
{opts.map((o,i)=><option key={i}>{o}</option>)}
</select>
);
const Check = ({label, checked, onChange}) => (
<label style={{display:"flex",alignItems:"center",gap:9,fontSize:12,color:c.text,cursor:"pointer",padding:"4px 0",fontFamily:sans}}>
<span style={{width:14,height:14,borderRadius:3,border:`1px solid ${checked?c.accent:c.border}`,background:checked?c.accent:"transparent",display:"flex",alignItems:"center",justifyContent:"center",flexShrink:0}}>
{checked && <span style={{color:"#fff",fontSize:9}}>✓</span>}
</span>
<span>{label}</span>
</label>
);
// ─── Status dot + label ───
const StatusDot = ({status}) => {
const map = {
verified: c.green,
failed: c.red,
incomplete: c.amber,
pending: c.textTer,
in_progress: c.amber,
};
return <span style={{width:8,height:8,borderRadius:"50%",background:map[status]||c.textTer,flexShrink:0,display:"inline-block"}} />;
};
// ─── Result indicator (compact, right side of card header) ───
const ResultIndicator = ({outcome}) => {
if (!outcome.status || outcome.status === "pending") {
return <span style={{fontSize:10,color:c.textTer,fontFamily:mono}}>—</span>;
}
if (outcome.resultType === "pass_fail") {
return <span style={{fontSize:11,color:outcome.status==="verified"?c.green:c.red,fontFamily:mono,fontWeight:600}}>
{outcome.status==="verified" ? "✓ pass" : "✕ fail"}
</span>;
}
if (outcome.resultType === "checklist") {
const passed = outcome.count.passed === outcome.count.total;
return <span style={{fontSize:11,color:passed?c.green:c.red,fontFamily:mono,fontWeight:600}}>
{outcome.count.passed}/{outcome.count.total}
</span>;
}
if (outcome.resultType === "score") {
const passed = outcome.score >= outcome.threshold;
return (
<div style={{display:"flex",alignItems:"center",gap:7}}>
<div style={{position:"relative",width:60,height:5,background:c.bg,borderRadius:3,overflow:"hidden"}}>
<div style={{width:`${outcome.score*100}%`,height:"100%",background:passed?c.green:c.red}} />
<div style={{position:"absolute",left:`${outcome.threshold*100}%`,top:-2,width:1,height:9,background:c.textTer}} />
</div>
<span style={{fontSize:11,color:passed?c.green:c.red,fontFamily:mono,fontWeight:600,minWidth:32}}>{outcome.score.toFixed(2)}</span>
</div>
);
}
};
// ═══════════════════════════════════════════════════════════════
// DATA
// ═══════════════════════════════════════════════════════════════
const OUTCOMES = [
{
id:"o0", meta:true,
name:"Output achieves the goal",
desc:"The final brief opposes the motion to dismiss by showing standing exists, the complaint states a claim under Rule 12(b)(6), the heightened pleading standard is met, and loss causation is adequately alleged.",
resultType:"score", required:true,
score:0.82, threshold:0.85,
status:"failed",
plan:"Holistic LLM assessment against the goal statement. A separate grading agent reads the final draft, rates how well it accomplishes what was asked, decomposes the assessment into 4 sub-criteria (standing, claim sufficiency, pleading standard, loss causation), and produces a weighted score.",
recovery:"Re-grade once if score is between 0.80 and 0.85 to reduce noise. If still below threshold, fire failed_out with the rubric breakdown showing which sub-criteria fell short.",
feedback:"Structured rubric breakdown — each sub-criterion's score, weak points cited from the brief, and suggested revisions per sub-criterion. Routed to whatever the canvas wires failed_out to.",
},
{
id:"o1", name:"Citations are present",
desc:"Brief contains at least the citations required to support the argument.",
resultType:"pass_fail", required:true,
status:"verified",
plan:"Pattern-match the brief for citation format. Pass if 5 or more citations found.",
recovery:"No recovery needed — this is a quick mechanical check. On failure, fire failed_out immediately with the count.",
feedback:"Just the count and a note: 'Brief contains N citations, expected at least 5.'",
},
{
id:"o2", name:"Brief addresses all motion issues",
desc:"The motion to dismiss raised 7 issues; the brief addresses each.",
resultType:"checklist", required:true,
count:{passed:7, total:7},
status:"verified",
plan:"Extract the 7 issues from the Motion to Dismiss (already in claims_in). For each, check whether the brief responds — using agent assessment per issue. Pass when all 7 are addressed.",
recovery:"For any issue not detected as addressed: re-check with a slightly different agent prompt before failing. If still not found, fire failed_out.",
feedback:"Per-issue list — issue name, status (addressed/missing), brief location if found, suggested response if missing. Routed via failed_out.",
},
{
id:"o3", name:"Citations resolve to real cases",
desc:"All cases cited resolve to real cases. The citation-checker should verify each citation against CourtListener; flag any that can't be found.",
resultType:"checklist", required:true,
count:{passed:16, total:19},
status:"failed",
plan:"Citation-checker specialist agent verifies each of the 19 citations against CourtListener. Each citation is a checkpoint. Pass when all 19 verify.",
recovery:"If CourtListener can't find a citation, try Westlaw before giving up. If a citation has incomplete format (missing reporter/year), ask the upstream drafter for the full citation. Try up to 3 times per citation. After all attempts, fire failed_out with the list of unresolvable citations.",
feedback:"Per-citation breakdown — case name, full citation, location in brief (section + paragraph), what the verifier tried, why it failed, suggested fix (correct citation if found, or 'remove and re-research'). Routed via failed_out.",
},
{
id:"o4", name:"No factual claims unsupported by record",
desc:"Every factual assertion in the brief is supported by an explicit reference to the case record.",
resultType:"pass_fail", required:true,
status:"verified",
plan:"Pull FactualAssertion claims from upstream extractor. For each, look for a record citation in proximity. Pass when 100% of factual claims have record support.",
recovery:"For claims without nearby record cites: search the case record for supporting evidence; if found, suggest the citation back to the drafter. If not found, flag the claim. Try this once per unsupported claim before failing.",
feedback:"List of unsupported assertions — claim text, brief location, search attempted, what was found (if anything), suggested action.",
},
{
id:"o5", name:"Argument is persuasive",
desc:"The brief constructs a persuasive case, addressing counterarguments and supporting reasoning with cited authority.",
resultType:"score", required:false,
score:0.78, threshold:0.7,
status:"verified",
plan:"Agent assessment using a 4-criterion rubric (authority strength, counterargument anticipation, logical chain, accessibility). Weighted average. Threshold 0.7.",
recovery:"On borderline scores (within 0.05 of threshold), re-assess once with a different judge model before deciding. No further recovery — this is subjective; route to drafter if below.",
feedback:"Rubric breakdown with score per sub-criterion, weakest area named, suggested revision focus.",
},
{
id:"o6", name:"Tone is professional",
desc:"Consistent with court filing standards. No casual register, no contractions outside quotes.",
resultType:"score", required:false,
score:0.85, threshold:0.7,
status:"verified",
plan:"Agent assessment on a 1-5 anchored scale (1 = casual, 3 = balanced, 5 = strict formal). Pass at 3 (normalized 0.7).",
recovery:"Single re-check on borderline. No further recovery — tone judgment is fast and cheap; just route to drafter.",
feedback:"Score with rationale citing specific brief passages that lowered the score (e.g., 'paragraph 4 uses contractions').",
},
{
id:"o7", name:"Brief between 4000-8000 words",
desc:"Word count within the court's required range.",
resultType:"pass_fail", required:true,
status:"verified",
plan:"Word count check. Pass when within 4000-8000.",
recovery:"No recovery for length — it's mechanical. Fire failed_out with the count and target.",
feedback:"Just the count, target range, and whether under or over.",
},
];
// ═══════════════════════════════════════════════════════════════
// MAIN
// ═══════════════════════════════════════════════════════════════
export default function Mockup() {
const [view, setView] = useState("evaluator");
return (
<div style={{width:"100%",height:"100vh",background:c.bg,color:c.text,fontFamily:sans,display:"flex",flexDirection:"column",overflow:"hidden"}}>
<div style={{height:28,minHeight:28,borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",padding:"0 14px",gap:8,background:c.bgPanel,fontSize:9,color:c.textTer,letterSpacing:0.4}}>
REFERENCE MOCKUP V4 — SIMPLIFIED, OUTCOME-FIRST
<div style={{flex:1}} />
<div style={{display:"flex",gap:4,padding:2,background:c.bg,borderRadius:4}}>
{[
{id:"evaluator",label:"⊜ Evaluator"},
{id:"extractor",label:"◈ Extractor"},
].map(t=>(
<button key={t.id} onClick={()=>setView(t.id)}
style={{padding:"3px 10px",fontSize:10,fontWeight:500,color:view===t.id?c.text:c.textSec,background:view===t.id?c.bgHover:"transparent",border:"none",borderRadius:3,cursor:"pointer",fontFamily:sans}}>
{t.label}
</button>
))}
</div>
</div>
{view === "evaluator" ? <EvaluatorView /> : <ExtractorView />}
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// EVALUATOR
// ═══════════════════════════════════════════════════════════════
function EvaluatorView() {
const [expanded, setExpanded] = useState("o3");
const [settingsOpen, setSettingsOpen] = useState(false);
const [findingsOpen, setFindingsOpen] = useState(null);
const [adding, setAdding] = useState(false);
const [portsOpen, setPortsOpen] = useState(false);
return (
<div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden",position:"relative"}}>
{/* Compact top bar */}
<div style={{height:44,minHeight:44,borderBottom:`1px solid ${c.borderSoft}`,display:"flex",alignItems:"center",justifyContent:"space-between",padding:"0 22px"}}>
<div style={{display:"flex",alignItems:"center",gap:10}}>
<span style={{fontSize:16}}>⊜</span>
<span style={{fontSize:13,fontWeight:600,color:c.text}}>Final Quality Gate</span>
<span style={{fontSize:10,color:c.textTer,fontFamily:mono,marginLeft:4}}>step.evaluator</span>
</div>
<div style={{display:"flex",gap:6,alignItems:"center"}}>
<span onClick={()=>setSettingsOpen(true)} style={{fontSize:14,color:c.textSec,cursor:"pointer",padding:"4px 8px"}}>⚙</span>
<Btn primary sm>▶ Run</Btn>
<span style={{fontSize:16,color:c.textSec,cursor:"pointer",padding:"0 6px"}}>✕</span>
</div>
</div>
{/* Inline verdict status (one line) */}
<div style={{padding:"14px 28px 6px",display:"flex",alignItems:"center",gap:14}}>
<StatusDot status="failed" />
<span style={{fontSize:12,color:c.text}}>
<span style={{color:c.red,fontWeight:600}}>Needs revision</span>
<span style={{color:c.textSec,marginLeft:8}}>· iteration 2 of 3 · 7 of 8 outcomes passed · $0.41 · 14.2s</span>
</span>
<div style={{flex:1}} />
<Btn ghost sm onClick={()=>setFindingsOpen("all")}>View all findings</Btn>
<Btn ghost sm>Re-run</Btn>
</div>
{/* MAIN AREA */}
<div style={{flex:1,overflowY:"auto"}}>
<div style={{maxWidth:880,margin:"0 auto",padding:"18px 28px 40px"}}>
{/* Outcomes header */}
<div style={{display:"flex",alignItems:"baseline",justifyContent:"space-between",marginBottom:14}}>
<div style={{fontSize:14,fontWeight:600,color:c.text}}>Outcomes</div>
<Btn ghost sm onClick={()=>setAdding(!adding)}>{adding?"Cancel":"+ Add"}</Btn>
</div>
{adding && <AddOutcome onClose={()=>setAdding(false)} />}
{/* Outcome list */}
<div style={{display:"flex",flexDirection:"column",gap:6}}>
{OUTCOMES.map(o => (
<OutcomeCard
key={o.id}
outcome={o}
expanded={expanded===o.id}
onToggle={()=>setExpanded(expanded===o.id?null:o.id)}
onViewFindings={()=>setFindingsOpen(o.id)}
/>
))}
</div>
</div>
</div>
{/* Collapsible port drawer */}
<div style={{borderTop:`1px solid ${c.borderSoft}`}}>
<div onClick={()=>setPortsOpen(!portsOpen)} style={{padding:"7px 22px",display:"flex",alignItems:"center",gap:8,cursor:"pointer",fontSize:10,color:c.textTer,fontWeight:500}}>
<span>{portsOpen?"▾":"▸"}</span>
<span>Ports</span>
<span style={{color:c.textTer,fontStyle:"italic",marginLeft:6,fontWeight:400}}>per-outcome failed_out routes + aggregate any_failed_out</span>
</div>
{portsOpen && <PortsDrawer />}
</div>
{/* Settings drawer */}
{settingsOpen && <SettingsDrawer onClose={()=>setSettingsOpen(false)} />}
{/* Findings drawer */}
{findingsOpen && <FindingsDrawer outcomeId={findingsOpen} onClose={()=>setFindingsOpen(null)} />}
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// OUTCOME CARD
// ═══════════════════════════════════════════════════════════════
function OutcomeCard({outcome, expanded, onToggle, onViewFindings}) {
return (
<div style={{
background:c.bgCard,
border:`1px solid ${expanded?c.border:c.borderSoft}`,
borderRadius:6,overflow:"hidden",
}}>
{/* Collapsed row — single line */}
<div onClick={onToggle} style={{padding:"11px 16px",cursor:"pointer",display:"flex",alignItems:"center",gap:12}}>
<StatusDot status={outcome.status} />
<span style={{flex:1,fontSize:12,color:c.text,minWidth:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>
{outcome.name}
{outcome.meta && <span style={{fontSize:9,color:c.textTer,marginLeft:8,fontStyle:"italic"}}>· overall goal</span>}
{!outcome.required && <span style={{fontSize:9,color:c.textTer,marginLeft:8,fontStyle:"italic"}}>· optional</span>}
</span>
<ResultIndicator outcome={outcome} />
{outcome.status === "failed" && (
<span onClick={(e)=>{e.stopPropagation();onViewFindings();}} style={{fontSize:10,color:c.accent,cursor:"pointer",marginLeft:2}}>findings →</span>
)}
</div>
{/* Expanded body */}
{expanded && <OutcomeBody outcome={outcome} onViewFindings={onViewFindings} />}
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// OUTCOME BODY (expanded)
// ═══════════════════════════════════════════════════════════════
function OutcomeBody({outcome, onViewFindings}) {
const [adjustPlan, setAdjustPlan] = useState(false);
const [adjustRecovery, setAdjustRecovery] = useState(false);
const [adjustFeedback, setAdjustFeedback] = useState(false);
return (
<div style={{padding:"4px 22px 22px",borderTop:`1px solid ${c.borderSoft}`}}>
{/* WHAT THIS CHECKS — the only user input */}
<Para label="What this checks">
<Textarea value={outcome.desc} h={outcome.meta?100:72} placeholder="Describe what you want to check, in plain language." />
<Hint>This is the only thing you have to write. Everything below is auto-generated from it.</Hint>
</Para>
{/* THE PLAN — auto-generated */}
<Para label="The plan" sublabel="auto-generated · adjustable" onAdjustToggle={()=>setAdjustPlan(!adjustPlan)} adjustOpen={adjustPlan}>
<Prose>{outcome.plan}</Prose>
{adjustPlan && <PlanAdjust outcome={outcome} />}
</Para>
{/* ON FAILURE — what the agent tries before firing failed_out */}
<Para label="If checks fail" sublabel="what the agent tries before failed_out fires" onAdjustToggle={()=>setAdjustRecovery(!adjustRecovery)} adjustOpen={adjustRecovery}>
<Prose>{outcome.recovery}</Prose>
{adjustRecovery && <RecoveryAdjust outcome={outcome} />}
</Para>
{/* FEEDBACK SHAPE — what the next module receives */}
<Para label="What downstream gets" sublabel="payload shape on failed_out · adjustable" onAdjustToggle={()=>setAdjustFeedback(!adjustFeedback)} adjustOpen={adjustFeedback}>
<Prose>{outcome.feedback}</Prose>
{adjustFeedback && <FeedbackAdjust outcome={outcome} />}
</Para>
{/* LAST RESULT */}
{outcome.status && outcome.status !== "pending" && (
<Para label="Last run">
<LastRun outcome={outcome} onViewFindings={onViewFindings} />
</Para>
)}
</div>
);
}
// ─── Layout primitives for the outcome body ───
const Para = ({label, sublabel, children, onAdjustToggle, adjustOpen}) => (
<div style={{marginTop:18}}>
<div style={{display:"flex",alignItems:"baseline",gap:10,marginBottom:7}}>
<div style={{fontSize:10,fontWeight:600,color:c.textSec,letterSpacing:0.4,textTransform:"uppercase",fontFamily:sans}}>{label}</div>
{sublabel && <div style={{fontSize:9,color:c.textTer,fontStyle:"italic"}}>{sublabel}</div>}
<div style={{flex:1}} />
{onAdjustToggle && (
<span onClick={onAdjustToggle} style={{fontSize:9,color:c.accent,cursor:"pointer"}}>
{adjustOpen ? "hide" : "adjust"}
</span>
)}
</div>
{children}
</div>
);
const Prose = ({children}) => (
<div style={{fontSize:12,color:c.text,lineHeight:1.65,padding:"2px 0",fontFamily:sans}}>{children}</div>
);
const Hint = ({children}) => (
<div style={{fontSize:10,color:c.textTer,marginTop:5,fontStyle:"italic",lineHeight:1.5}}>{children}</div>
);
// ─── Adjust panels (collapsed by default) ───
function PlanAdjust({outcome}) {
return (
<div style={{marginTop:10,padding:12,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
<FieldRow label="Result type">
<Select opts={["pass/fail","checklist","score"]} v={outcome.resultType==="pass_fail"?"pass/fail":outcome.resultType} w={120} />
</FieldRow>
<FieldRow label="Check by">
<Select opts={["auto-detect from description","auto-check (mechanical)","check claims (from extractor)","specialist agent","agent assessment (LLM rubric)"]} v="auto-detect from description" />
</FieldRow>
<FieldRow label="Pass when">
{outcome.resultType==="checklist" && <Select opts={["all items","≥ 95% of items","at least N items"]} v="all items" w={160} />}
{outcome.resultType==="score" && <Input value={outcome.threshold?.toString()} w={70} />}
{outcome.resultType==="pass_fail" && <span style={{fontSize:11,color:c.textSec}}>check returns true</span>}
</FieldRow>
<FieldRow label="Reference materials">
<Select opts={["auto-detect from description","required","used if available","not needed","audit only"]} v="auto-detect from description" />
</FieldRow>
<FieldRow label="Cost cap">
<Input value="$0.50" w={70} />
</FieldRow>
</div>
);
}
function RecoveryAdjust({outcome}) {
return (
<div style={{marginTop:10,padding:12,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
<div style={{fontSize:10,color:c.textSec,marginBottom:8,lineHeight:1.5,fontStyle:"italic"}}>
The agent has a built-in playbook for this outcome type. You can override the playbook here — or just describe what you want the agent to try, and the system figures it out.
</div>
<FieldRow label="Try alternates first">
<Check label="Try alternate sources (e.g., Westlaw)" checked={true} />
</FieldRow>
<FieldRow label="Ask upstream">
<Check label="Ask the upstream agent for clarification" checked={true} />
</FieldRow>
<FieldRow label="Gather more info">
<Check label="Allow research / web search" checked={true} />
</FieldRow>
<FieldRow label="Max attempts">
<Input value="3" w={50} />
</FieldRow>
<FieldRow label="Cost cap on recovery">
<Input value="$0.50" w={70} />
</FieldRow>
<FieldRow label="After all attempts">
<Select opts={["fire failed_out with findings","escalate to human","mark indeterminate"]} v="fire failed_out with findings" />
</FieldRow>
<FieldRow label="Custom recovery">
<Textarea value="" h={50} placeholder="(Optional) Describe additional recovery steps in plain language." />
</FieldRow>
</div>
);
}
function FeedbackAdjust({outcome}) {
return (
<div style={{marginTop:10,padding:12,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
<div style={{fontSize:10,color:c.textSec,marginBottom:8,lineHeight:1.5,fontStyle:"italic"}}>
This is the structured payload on failed_out. The next module reads it as instruction. The default shape is auto-derived from the outcome type. Customize if you need a different structure for downstream consumers.
</div>
<FieldRow label="Format">
<Select opts={["structured (auto from outcome type)","plain text summary","JSON schema (custom)","markdown report"]} v="structured (auto from outcome type)" />
</FieldRow>
<FieldRow label="Include">
<div style={{display:"flex",flexDirection:"column",gap:0}}>
<Check label="Each failure item with location" checked={true} />
<Check label="Why it failed (verifier's reasoning)" checked={true} />
<Check label="Suggested fix per item" checked={true} />
<Check label="Evidence references (sources used)" checked={true} />
<Check label="Severity per item" checked={false} />
<Check label="Confidence per item" checked={false} />
</div>
</FieldRow>
<FieldRow label="Style">
<Select opts={["actionable instructions for next agent","observations + suggestions","critique only","raw findings"]} v="actionable instructions for next agent" />
</FieldRow>
<FieldRow label="Also deliver as">
<div style={{display:"flex",flexDirection:"column",gap:0}}>
<Check label="Inline document comments (anchored to brief sections)" checked={false} />
<Check label="Task forum / board post" checked={true} />
<Check label="Audit trail (always on)" checked={true} />
</div>
</FieldRow>
</div>
);
}
const FieldRow = ({label, children}) => (
<div style={{display:"flex",alignItems:"flex-start",gap:14,marginBottom:7}}>
<div style={{fontSize:10,color:c.textSec,fontWeight:500,minWidth:130,paddingTop:5}}>{label}</div>
<div style={{flex:1}}>{children}</div>
</div>
);
// ─── Last run summary (in expanded card) ───
function LastRun({outcome, onViewFindings}) {
const isFail = outcome.status === "failed";
if (!isFail) {
return (
<div style={{fontSize:12,color:c.green,padding:"6px 0"}}>
✓ Passed.
{outcome.resultType === "checklist" && ` ${outcome.count.passed} of ${outcome.count.total}.`}
{outcome.resultType === "score" && ` Score ${outcome.score.toFixed(2)} (threshold ${outcome.threshold.toFixed(2)}).`}
</div>
);
}
return (
<div style={{padding:"10px 12px",background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
<div style={{fontSize:12,color:c.text,lineHeight:1.6,marginBottom:8}}>
↺ Failed{" "}
{outcome.resultType === "checklist" && `(${outcome.count.passed} of ${outcome.count.total}). `}
{outcome.resultType === "score" && `(score ${outcome.score.toFixed(2)} / threshold ${outcome.threshold.toFixed(2)}). `}
Failed_out fired with structured feedback. Whatever's wired downstream is now revising.
</div>
<div style={{display:"flex",gap:8}}>
<Btn ghost sm onClick={onViewFindings}>View findings</Btn>
<Btn ghost sm>Override → human</Btn>
<Btn ghost sm>Re-evaluate</Btn>
</div>
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// ADD OUTCOME
// ═══════════════════════════════════════════════════════════════
function AddOutcome({onClose}) {
return (
<div style={{marginBottom:14,padding:16,background:c.bgCard,border:`1px solid ${c.border}`,borderRadius:6}}>
<Textarea
value=""
placeholder="Describe what you want to check. The system will figure out how."
h={72}
/>
<div style={{display:"flex",alignItems:"center",gap:10,marginTop:10}}>
<span style={{fontSize:10,color:c.textTer}}>or start from:</span>
<Btn ghost sm>💬 Ask Task Agent</Btn>
<Btn ghost sm>📋 Load preset</Btn>
<Btn ghost sm>⊕ Overall goal check</Btn>
<div style={{flex:1}} />
<Btn ghost sm onClick={onClose}>Cancel</Btn>
<Btn primary sm onClick={onClose}>Add</Btn>
</div>
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// SETTINGS DRAWER
// ═══════════════════════════════════════════════════════════════
function SettingsDrawer({onClose}) {
return (
<div onClick={onClose} style={{position:"absolute",top:0,right:0,bottom:0,left:0,background:"rgba(0,0,0,0.4)",zIndex:40,display:"flex",justifyContent:"flex-end"}}>
<div onClick={(e)=>e.stopPropagation()} style={{width:440,height:"100%",background:c.bgPanel,borderLeft:`1px solid ${c.border}`,display:"flex",flexDirection:"column",boxShadow:"-10px 0 30px #0008"}}>
<div style={{padding:"14px 22px",borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<span style={{fontSize:13,fontWeight:600}}>Settings</span>
<span onClick={onClose} style={{fontSize:16,color:c.textSec,cursor:"pointer"}}>✕</span>
</div>
<div style={{flex:1,overflowY:"auto",padding:"18px 22px"}}>
<SettingsSection title="Overall verdict">
<FieldRow label="Pass when">
<Select opts={["All required outcomes pass","Average score ≥ threshold","Weighted score ≥ threshold"]} v="All required outcomes pass" />
</FieldRow>
</SettingsSection>
<SettingsSection title="Iteration">
<div style={{fontSize:12,color:c.text,lineHeight:1.7,padding:"4px 0"}}>
Try up to <Input value="3" w={40} /> revisions before giving up.<br />
If the same outcome fails <Input value="3" w={32} /> times, send to human.<br />
If no improvement after <Input value="2" w={32} /> iterations, send to human.
</div>
</SettingsSection>
<SettingsSection title="Human review">
<Check label="Only when required outcomes fail (after retries)" checked={true} />
<Check label="Before any output (review every verdict)" checked={false} />
<Check label="On regression or stuck failures" checked={false} />
</SettingsSection>
<SettingsSection title="Defaults">
<FieldRow label="Agent">
<Select opts={["(task default)","Elnor","Nova"]} v="(task default)" />
</FieldRow>
<FieldRow label="Cost cap per run">
<Input value="$1.50" w={80} />
</FieldRow>
</SettingsSection>
<SettingsSection title="Sources & libraries">
<div style={{fontSize:11,color:c.textSec,marginBottom:8}}>Wired evidence comes from the canvas. Uploads here are extras.</div>
<div style={{padding:10,border:`1px dashed ${c.border}`,borderRadius:4,textAlign:"center",fontSize:11,color:c.textSec}}>
📁 Drop files here or click to browse
</div>
<div style={{marginTop:8,display:"flex",flexDirection:"column",gap:4}}>
{["📚 DOC73 PBE: Securities Litigation","🗂 Matter file (auto)","⚖ CourtListener"].map((l,i)=>(
<div key={i} style={{padding:"5px 8px",background:c.bgCard,borderRadius:3,fontSize:11,color:c.text}}>{l}</div>
))}
</div>
</SettingsSection>
<SettingsSection title="Custom guidance">
<Textarea value="Apply strict Bluebook 21st edition standards. Prefer formal legal register." h={80} placeholder="Instructions that apply to all outcome verifications." />
</SettingsSection>
</div>
</div>
</div>
);
}
const SettingsSection = ({title,children}) => (
<div style={{marginBottom:20}}>
<div style={{fontSize:10,fontWeight:600,color:c.textSec,letterSpacing:0.4,textTransform:"uppercase",marginBottom:8}}>{title}</div>
{children}
</div>
);
// ═══════════════════════════════════════════════════════════════
// FINDINGS DRAWER
// ═══════════════════════════════════════════════════════════════
function FindingsDrawer({outcomeId, onClose}) {
const isAll = outcomeId === "all";
const title = isAll ? "All findings — last run" : "Citations resolve to real cases — findings";
return (
<div onClick={onClose} style={{position:"absolute",top:0,right:0,bottom:0,left:0,background:"rgba(0,0,0,0.4)",zIndex:40,display:"flex",justifyContent:"flex-end"}}>
<div onClick={(e)=>e.stopPropagation()} style={{width:540,height:"100%",background:c.bgPanel,borderLeft:`1px solid ${c.border}`,display:"flex",flexDirection:"column",boxShadow:"-10px 0 30px #0008"}}>
<div style={{padding:"14px 22px",borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div>
<div style={{fontSize:13,fontWeight:600}}>{title}</div>
<div style={{fontSize:10,color:c.textTer,marginTop:2}}>3 findings · iteration 2</div>
</div>
<span onClick={onClose} style={{fontSize:16,color:c.textSec,cursor:"pointer"}}>✕</span>
</div>
<div style={{flex:1,overflowY:"auto",padding:"16px 22px"}}>
<div style={{padding:11,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`,marginBottom:16,fontSize:11,color:c.text,lineHeight:1.6}}>
The check tried 19 citations. 16 verified. 3 could not be verified after attempts via CourtListener, Westlaw, and asking the upstream drafter for source notes. failed_out fired with structured feedback; whatever's wired is revising now.
</div>
{[
{title:"Smith v. Jones, 432 F.3d 12 (5th Cir. 2007)", loc:"§III ¶4", reason:"Case not found in CourtListener after 3 lookup attempts. Tried alternate reporters (F.2d, F. Supp.). Westlaw returned no match.", fix:"Verify case name; possibly Smith v. Jones, 432 F.3d 11 (alternate citation)."},
{title:"In re Acme Corp., 215 B.R. 89 (Bankr. S.D.N.Y. 1997)", loc:"§IV ¶2", reason:"Citation format valid; no matching case in CourtListener. Westlaw confirmed no S.D.N.Y. case with that citation.", fix:"Possible alternative: In re Acme Corp., 215 B.R. 89 (Bankr. D.N.J. 1997) — different jurisdiction."},
{title:"Doe v. Smith (incomplete)", loc:"§V ¶1", reason:"Citation incomplete — missing reporter, court, and year.", fix:"Drafter needs to provide full citation or remove."},
].map((f,i)=>(
<div key={i} style={{padding:12,background:c.bgCard,borderRadius:5,border:`1px solid ${c.borderSoft}`,marginBottom:8}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:6}}>
<span style={{fontSize:12,fontWeight:600,color:c.text}}>{f.title}</span>
<span style={{fontSize:10,color:c.textTer,fontFamily:mono}}>{f.loc}</span>
</div>
<div style={{fontSize:11,color:c.textSec,lineHeight:1.5,marginBottom:4}}>
<span style={{color:c.red,fontWeight:500}}>Why: </span>{f.reason}
</div>
<div style={{fontSize:11,color:c.textSec,lineHeight:1.5}}>
<span style={{color:c.accent,fontWeight:500}}>Suggested fix: </span>{f.fix}
</div>
</div>
))}
<div style={{marginTop:18,padding:11,background:c.bgCard,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
<div style={{fontSize:10,fontWeight:600,color:c.textSec,marginBottom:8}}>DELIVERY</div>
<Check label="Send as inline document comments (for human editing)" checked={false} />
<Check label="Post to task forum / board" checked={true} />
<Check label="Audit trail" checked={true} />
</div>
</div>
<div style={{padding:"12px 22px",borderTop:`1px solid ${c.border}`,display:"flex",gap:6,flexWrap:"wrap"}}>
<Btn ghost sm>Accept guidance</Btn>
<Btn ghost sm>Mark wrong</Btn>
<Btn ghost sm>Send to human</Btn>
<Btn ghost sm>Ask Task Agent</Btn>
</div>
</div>
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// PORTS
// ═══════════════════════════════════════════════════════════════
function PortsDrawer() {
const inputs = [
{label:"subject_in", note:"← ◆ Final Brief Draft"},
{label:"claims_in", note:"← ◈ Brief Claims"},
{label:"evidence_in.1", note:"← ◇ Case Record"},
{label:"evidence_in.2", note:"← ◇ Motion to Dismiss"},
];
const outputs = [
{label:"passed_out", note:"→ next stage"},
{label:"any_failed_out", note:"aggregate fallback — fires when any required outcome fails"},
{label:"meta_failed_out", note:"per-outcome route, auto-named from \"Output achieves the goal\""},
{label:"citations_resolve_failed_out", note:"per-outcome route"},
{label:"motion_issues_failed_out", note:"per-outcome route"},
{label:"feedback_out", note:"structured feedback for loop iteration"},
];
return (
<div style={{padding:"4px 22px 14px",borderTop:`1px solid ${c.borderSoft}`,display:"flex",gap:30,fontSize:10,color:c.textSec,maxHeight:240,overflowY:"auto"}}>
<div style={{flex:1}}>
<div style={{fontSize:9,color:c.textTer,marginBottom:6,letterSpacing:0.5,fontWeight:600}}>INPUTS</div>
{inputs.map((p,i)=>(
<div key={i} style={{padding:"3px 0",fontFamily:mono}}>
<span style={{color:c.accent}}>{p.label}</span>
<span style={{color:c.textTer,fontFamily:sans,marginLeft:8}}>{p.note}</span>
</div>
))}
</div>
<div style={{flex:1.6}}>
<div style={{fontSize:9,color:c.textTer,marginBottom:6,letterSpacing:0.5,fontWeight:600}}>OUTPUTS</div>
{outputs.map((p,i)=>(
<div key={i} style={{padding:"3px 0",fontFamily:mono}}>
<span style={{color:p.label.includes("failed")?c.red:c.green}}>{p.label}</span>
<span style={{color:c.textTer,fontFamily:sans,marginLeft:8,fontStyle:"italic"}}>{p.note}</span>
</div>
))}
</div>
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// EXTRACTOR (minimal — same simplification principles)
// ═══════════════════════════════════════════════════════════════
function ExtractorView() {
const [expanded, setExpanded] = useState("ct1");
const [settingsOpen, setSettingsOpen] = useState(false);
const types = [
{id:"ct1", name:"CaseCitation", desc:"Citations to legal cases — full citation, case name, holding as characterized.", count:19, evaluable:true, authority:true, consumedBy:["Citations resolve to real cases"], instruction:"Extract every citation to a legal case. Include the full citation, the case name, the holding as characterized by the brief, and the source span (paragraph + line)."},
{id:"ct2", name:"FactualAssertion", desc:"Factual claims — dates, amounts, events, parties.", count:18, evaluable:true, authority:false, consumedBy:["No factual claims unsupported"], instruction:"Extract every factual claim made by the brief, with its location."},
{id:"ct3", name:"LegalProposition", desc:"Statements of legal rules or principles.", count:7, evaluable:true, authority:false, consumedBy:[], instruction:"Extract statements of legal rules with supporting citation if present."},
{id:"ct4", name:"OpposingArgument", desc:"References to opposing counsel's arguments.", count:4, evaluable:true, authority:false, consumedBy:[], instruction:"Extract references to opposing counsel's arguments."},
{id:"ct5", name:"ProceduralFact", desc:"Statements about case procedural posture.", count:2, evaluable:false, authority:false, consumedBy:[], instruction:"Extract procedural facts about the case."},
];
return (
<div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden",position:"relative"}}>
<div style={{height:44,minHeight:44,borderBottom:`1px solid ${c.borderSoft}`,display:"flex",alignItems:"center",justifyContent:"space-between",padding:"0 22px"}}>
<div style={{display:"flex",alignItems:"center",gap:10}}>
<span style={{fontSize:16}}>◈</span>
<span style={{fontSize:13,fontWeight:600}}>Claim Extractor</span>
<span style={{fontSize:10,color:c.textTer,fontFamily:mono,marginLeft:4}}>step.claim_extractor</span>
</div>
<div style={{display:"flex",gap:6,alignItems:"center"}}>
<span onClick={()=>setSettingsOpen(true)} style={{fontSize:14,color:c.textSec,cursor:"pointer",padding:"4px 8px"}}>⚙</span>
<Btn primary sm>▶ Run</Btn>
<span style={{fontSize:16,color:c.textSec,cursor:"pointer",padding:"0 6px"}}>✕</span>
</div>
</div>
<div style={{padding:"14px 28px 6px",display:"flex",alignItems:"center",gap:14}}>
<StatusDot status="verified" />
<span style={{fontSize:12,color:c.text}}>
<span style={{color:c.green,fontWeight:600}}>50 claims extracted</span>
<span style={{color:c.textSec,marginLeft:8}}>· cached · was $0.12 fresh · downstream: ⊜ Final Quality Gate</span>
</span>
</div>
<div style={{flex:1,overflowY:"auto"}}>
<div style={{maxWidth:880,margin:"0 auto",padding:"18px 28px 40px"}}>
<div style={{display:"flex",alignItems:"baseline",justifyContent:"space-between",marginBottom:14}}>
<div style={{fontSize:14,fontWeight:600}}>Claim types</div>
<Btn ghost sm>+ Add</Btn>
</div>
<div style={{display:"flex",flexDirection:"column",gap:6}}>
{types.map(t => (
<div key={t.id} style={{background:c.bgCard,border:`1px solid ${expanded===t.id?c.border:c.borderSoft}`,borderRadius:6,overflow:"hidden"}}>
<div onClick={()=>setExpanded(expanded===t.id?null:t.id)} style={{padding:"11px 16px",cursor:"pointer",display:"flex",alignItems:"center",gap:12}}>
<StatusDot status={t.count>0?"verified":"pending"} />
<span style={{flex:1,fontSize:12,color:c.text,fontFamily:mono}}>{t.name}
{!t.evaluable && <span style={{fontSize:9,color:c.textTer,marginLeft:8,fontFamily:sans,fontStyle:"italic"}}>· not evaluable</span>}
{t.authority && <span style={{fontSize:9,color:c.amber,marginLeft:8,fontFamily:sans,fontStyle:"italic"}}>· needs specialist</span>}
{t.consumedBy.length>0 && <span style={{fontSize:9,color:c.textTer,marginLeft:8,fontFamily:sans,fontStyle:"italic"}}>· used by {t.consumedBy.length}</span>}
</span>
<span style={{fontSize:13,color:t.count>0?c.text:c.textTer,fontFamily:mono,fontWeight:600,minWidth:30,textAlign:"right"}}>{t.count}</span>
</div>
{expanded===t.id && (
<div style={{padding:"4px 22px 22px",borderTop:`1px solid ${c.borderSoft}`}}>
<Para label="What this extracts">
<Textarea value={t.desc} h={50} />
</Para>
<Para label="Instruction" sublabel="what the extractor agent reads">
<Textarea value={t.instruction} h={60} />
</Para>
<Para label="Fields per claim">
<div style={{padding:10,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`,fontFamily:mono,fontSize:10,color:c.text,lineHeight:1.7}}>
case_name <span style={{color:c.textTer}}>· text · required</span><br/>
citation <span style={{color:c.textTer}}>· text · required</span><br/>
court <span style={{color:c.textTer}}>· enum · optional</span><br/>
year <span style={{color:c.textTer}}>· number · optional</span><br/>
holding_quoted <span style={{color:c.textTer}}>· text · optional</span>
</div>
</Para>
{t.consumedBy.length>0 && (
<Para label="Used by these outcomes">
<div style={{fontSize:11,color:c.text,lineHeight:1.7}}>
{t.consumedBy.map((o,i)=>(<div key={i} style={{cursor:"pointer",color:c.accent}}>↗ {o}</div>))}
</div>
</Para>
)}
</div>
)}
</div>
))}
</div>
</div>
</div>
{settingsOpen && <SettingsDrawer onClose={()=>setSettingsOpen(false)} />}
</div>
);
}