Q_TASK_EVALUATOR_EXTRACTOR_V2.jsx
Design Mockups/Archived Mockups/DOC23 Task Mockups/Q_TASK_EVALUATOR_EXTRACTOR_V2.jsx
ELNOR REPO READER TEXT MIRROR
Original path: Design Mockups/Archived Mockups/DOC23 Task Mockups/Q_TASK_EVALUATOR_EXTRACTOR_V2.jsx
Source repo: /Users/OpenClaw1/Elnor/Elnor Specs
Git branch: main
Git commit: dbaa25962edc11ab30e8d4ca1715f9ae5bf77331
Generated: 2026-06-09T01:23:58.539Z
---
import React, { useState } from "react";
// ═══════════════════════════════════════════════════════════════
// Q TASK SYSTEM — EVALUATOR + EXTRACTOR EXPANDED VIEWS — V2
// Reorganized: outcomes are the unit. No tabs. Plain-language flow.
// Each outcome card reads as a story: what · how · materials ·
// if fails · pass when · last result.
// ═══════════════════════════════════════════════════════════════
const c = {
bg:"#0f1117", bgCard:"#161920", bgHover:"#1c1f2a", bgSel:"#1e2233",
bgPanel:"#131620", bgPopup:"#1a1e2b", border:"#262a36",
text:"#e2e4ea", textSec:"#8b8fa3", textTer:"#5d6178",
accent:"#5b8af5", green:"#34d399", red:"#f87171",
amber:"#fbbf24", purple:"#a78bfa", cyan:"#22d3ee", orange:"#fb923c",
};
const sans = "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
const mono = "'SF Mono','Cascadia Code','Fira Code', monospace";
// ── Helper components ──
const SectionLabel = ({children, mt}) => (
<div style={{fontSize:9,fontWeight:700,color:c.textTer,marginTop:mt?20:0,marginBottom:8,letterSpacing:1,textTransform:"uppercase",fontFamily:sans}}>
{children}
</div>
);
const FieldLabel = ({children, mt}) => (
<div style={{fontSize:10,fontWeight:600,color:c.textSec,marginTop:mt?12:0,marginBottom:5,fontFamily:sans}}>
{children}
</div>
);
const HelperText = ({children}) => (
<div style={{fontSize:10,color:c.textTer,marginTop:5,lineHeight:1.5,fontStyle:"italic",fontFamily:sans}}>
{children}
</div>
);
const Select = ({opts, v, onChange, compact}) => (
<select defaultValue={v} onChange={onChange||(()=>{})} style={{width:"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:4,padding:compact?"5px 7px":"7px 9px",fontSize:11,color:c.text,fontFamily:sans,outline:"none"}}>
{opts.map((o,i)=><option key={i} value={o}>{o}</option>)}
</select>
);
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:"8px 10px",fontSize:11,color:c.text,fontFamily:sans,resize:"vertical",minHeight:h||60,outline:"none",lineHeight:1.5}} />
);
const Input = ({value, placeholder, onChange, w}) => (
<input defaultValue={value} placeholder={placeholder} onChange={onChange||(()=>{})}
style={{width:w||"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:4,padding:"6px 9px",fontSize:11,color:c.text,fontFamily:sans,outline:"none"}} />
);
const Check = ({label, checked, onChange, hint}) => (
<div style={{padding:"3px 0"}}>
<label style={{display:"flex",alignItems:"center",gap:8,fontSize:11,color:c.text,cursor:"pointer",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,fontWeight:700}}>✓</span>}
</span>
<span>{label}</span>
</label>
{hint && <div style={{fontSize:9,color:c.textTer,marginLeft:22,marginTop:2,fontStyle:"italic"}}>{hint}</div>}
</div>
);
const Radio = ({label, checked, onChange, hint}) => (
<div style={{padding:"3px 0"}}>
<label style={{display:"flex",alignItems:"flex-start",gap:8,fontSize:11,color:c.text,cursor:"pointer",fontFamily:sans}}>
<span style={{width:14,height:14,borderRadius:"50%",border:`1.5px solid ${checked?c.accent:c.border}`,background:c.bg,display:"flex",alignItems:"center",justifyContent:"center",flexShrink:0,marginTop:1}}>
{checked && <span style={{width:7,height:7,borderRadius:"50%",background:c.accent}} />}
</span>
<div style={{flex:1}}>
<div>{label}</div>
{hint && <div style={{fontSize:9,color:c.textTer,marginTop:2,fontStyle:"italic",lineHeight:1.5}}>{hint}</div>}
</div>
</label>
</div>
);
const Btn = ({children, primary, accent, danger, ghost, onClick, sm}) => {
const styles = primary
? {background:c.accent,color:"#fff",border:"none"}
: accent
? {background:`${c.accent}15`,color:c.accent,border:`1px solid ${c.accent}40`}
: danger
? {background:`${c.red}15`,color:c.red,border:`1px solid ${c.red}40`}
: ghost
? {background:"transparent",color:c.textSec,border:`1px solid ${c.border}`}
: {background:c.bgHover,color:c.text,border:`1px solid ${c.border}`};
return (
<button onClick={onClick} style={{...styles,padding:sm?"4px 8px":"6px 11px",borderRadius:4,fontSize:sm?9:10,fontWeight:600,cursor:"pointer",fontFamily:sans,whiteSpace:"nowrap"}}>
{children}
</button>
);
};
const Pill = ({children, color}) => (
<span style={{display:"inline-block",padding:"2px 7px",borderRadius:10,fontSize:9,fontWeight:600,background:`${color}18`,color:color,border:`1px solid ${color}40`,letterSpacing:0.3,textTransform:"uppercase",fontFamily:sans}}>
{children}
</span>
);
const Tag = ({children, color, removable}) => (
<span style={{display:"inline-flex",alignItems:"center",gap:5,padding:"3px 8px",borderRadius:3,fontSize:10,background:`${color||c.accent}12`,color:color||c.accent,border:`1px solid ${color||c.accent}30`,fontFamily:sans}}>
{children}
{removable && <span style={{cursor:"pointer",opacity:0.6,fontSize:11}}>×</span>}
</span>
);
const StatusBadge = ({status}) => {
const map = {
verified: {label:"PASSED", color:c.green, icon:"✓"},
failed: {label:"FAILED", color:c.red, icon:"✕"},
incomplete: {label:"INCOMPLETE", color:c.amber, icon:"◐"},
gathering: {label:"GATHERING…", color:c.cyan, icon:"⟳"},
not_applicable: {label:"N/A", color:c.textTer, icon:"—"},
pending: {label:"PENDING", color:c.textTer, icon:"○"},
awaiting_human: {label:"AWAITING HUMAN", color:c.amber, icon:"👤"},
};
const m = map[status] || map.pending;
return (
<span style={{display:"inline-flex",alignItems:"center",gap:5,padding:"3px 9px",borderRadius:3,fontSize:9,fontWeight:700,background:`${m.color}18`,color:m.color,border:`1px solid ${m.color}40`,letterSpacing:0.3,textTransform:"uppercase",fontFamily:sans}}>
<span>{m.icon}</span>
<span>{m.label}</span>
</span>
);
};
const ResultTypePill = ({type}) => {
const map = {
pass_fail: {label:"PASS/FAIL", color:c.accent},
checklist: {label:"CHECKLIST", color:c.cyan},
score: {label:"SCORE", color:c.purple},
};
const m = map[type];
return <Pill color={m.color}>{m.label}</Pill>;
};
const SectionHeader = ({children}) => (
<div style={{fontSize:9,fontWeight:700,color:c.textTer,letterSpacing:1.5,textTransform:"uppercase",fontFamily:sans,marginTop:18,marginBottom:10,paddingBottom:6,borderBottom:`1px solid ${c.border}`}}>
{children}
</div>
);
const UploadStrip = ({label, files, hint}) => (
<div>
<FieldLabel>{label}</FieldLabel>
<div style={{border:`1px dashed ${c.border}`,borderRadius:5,padding:files&&files.length?"6px":"12px",background:c.bg,cursor:"pointer"}}>
{(!files || !files.length) ? (
<div style={{textAlign:"center",fontSize:10,color:c.textSec,fontFamily:sans}}>
<span style={{marginRight:6}}>📁</span>Click or drag to add files
</div>
) : (
<div style={{display:"flex",flexDirection:"column",gap:3}}>
{files.map((f,i)=>(
<div key={i} style={{display:"flex",alignItems:"center",justifyContent:"space-between",padding:"3px 5px",fontSize:10,color:c.text}}>
<span style={{display:"flex",alignItems:"center",gap:5}}>
<span style={{color:c.textTer}}>{f.icon}</span>
<span>{f.name}</span>
{f.size && <span style={{color:c.textTer,fontSize:9}}>· {f.size}</span>}
</span>
<span style={{cursor:"pointer",color:c.textTer,opacity:0.5,fontSize:11}}>×</span>
</div>
))}
<div style={{textAlign:"center",padding:"3px",fontSize:9,color:c.accent,cursor:"pointer"}}>+ Add more</div>
</div>
)}
</div>
{hint && <HelperText>{hint}</HelperText>}
</div>
);
// ═══════════════════════════════════════════════════════════════
// 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:32,minHeight:32,borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",padding:"0 12px",gap:8,background:c.bgPanel,fontSize:9,color:c.textTer,letterSpacing:0.5}}>
REFERENCE MOCKUP V2 — OUTCOME-CENTRIC LAYOUT
<div style={{flex:1}} />
<div style={{display:"flex",gap:4,padding:2,background:c.bg,borderRadius:5}}>
{[
{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:600,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 [portsOpen, setPortsOpen] = useState(false);
const [expandedOutcome, setExpandedOutcome] = useState("o2");
return (
<div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden"}}>
{/* Header */}
<div style={{height:52,minHeight:52,borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",justifyContent:"space-between",padding:"0 22px",background:c.bgPanel}}>
<div style={{display:"flex",alignItems:"center",gap:12}}>
<span style={{fontSize:20}}>⊜</span>
<div>
<div style={{fontSize:13,fontWeight:700,color:c.text}}>Outcome Evaluator</div>
<div style={{fontSize:10,color:c.textTer,fontFamily:mono,marginTop:2}}>step.evaluator · Brief Review Gate</div>
</div>
<span style={{marginLeft:14}}><StatusBadge status="failed" /></span>
<span style={{fontSize:11,color:c.textSec}}>7 of 8 outcomes passed · iteration 2 of 3</span>
</div>
<div style={{display:"flex",gap:8,alignItems:"center"}}>
<Btn ghost>Preset…</Btn>
<Btn primary>▶ Run</Btn>
<span style={{fontSize:18,color:c.textSec,cursor:"pointer",padding:"0 4px",marginLeft:4}}>✕</span>
</div>
</div>
{/* Body */}
<div style={{flex:1,display:"flex",overflow:"hidden"}}>
{/* LEFT SIDEBAR — global defaults, sources, custom guidance */}
<div style={{width:300,minWidth:300,borderRight:`1px solid ${c.border}`,padding:"18px 16px 22px",overflowY:"auto",background:c.bgPanel}}>
{/* Connected inputs */}
<SectionLabel>Connected Inputs</SectionLabel>
<div style={{display:"flex",flexDirection:"column",gap:4,fontSize:10}}>
<div style={{padding:"5px 7px",background:`${c.accent}10`,borderRadius:3,border:`1px solid ${c.accent}25`}}>
<span style={{color:c.accent,fontFamily:mono}}>subject_in</span> <span style={{color:c.textSec}}>← ◆ Draft Brief</span>
</div>
<div style={{padding:"5px 7px",background:`${c.accent}10`,borderRadius:3,border:`1px solid ${c.accent}25`}}>
<span style={{color:c.accent,fontFamily:mono}}>claims_in</span> <span style={{color:c.textSec}}>← ◈ Brief Claims</span>
</div>
<div style={{padding:"5px 7px",background:`${c.accent}10`,borderRadius:3,border:`1px solid ${c.accent}25`}}>
<span style={{color:c.accent,fontFamily:mono}}>evidence_in.1</span> <span style={{color:c.textSec}}>← ◇ Case Record</span>
</div>
</div>
{/* Overall pass criteria */}
<SectionLabel mt>Overall Pass Criteria</SectionLabel>
<Select opts={[
"All required outcomes must pass",
"Average score ≥ threshold",
"Weighted score ≥ threshold",
]} v="All required outcomes must pass" />
<HelperText>This evaluator's overall verdict passes when this rule holds across the outcomes below.</HelperText>
{/* Default check agent */}
<SectionLabel mt>Defaults</SectionLabel>
<FieldLabel>Agent for checks</FieldLabel>
<Select opts={["(task default)","Elnor","Nova","Atlas"]} v="(task default)" compact />
<FieldLabel mt>Cost cap per run</FieldLabel>
<Input value="$1.00" />
<HelperText>Outcomes can override with their own cost cap.</HelperText>
{/* If verdicts fail — global iteration */}
<SectionLabel mt>If Verdicts Fail</SectionLabel>
<div style={{padding:11,background:c.bg,borderRadius:6,border:`1px solid ${c.border}`,fontSize:11,lineHeight:1.65,color:c.text}}>
Try up to <span style={{display:"inline-block",margin:"0 2px"}}><Input value="3" w={36} /></span> revisions before giving up.
<br />
If the <em>same outcome</em> fails <span style={{display:"inline-block",margin:"0 2px"}}><Input value="3" w={28} /></span> times in a row, send to human.
<br />
If no improvement after <span style={{display:"inline-block",margin:"0 2px"}}><Input value="2" w={28} /></span> iterations, send to human.
<br />
On regression: <Select opts={["revert to prior best","warn and continue","escalate"]} v="revert to prior best" compact />
</div>
<HelperText>Per-outcome overrides live in each outcome's "If this fails" section.</HelperText>
{/* When to involve a human */}
<SectionLabel mt>When to Involve a Human</SectionLabel>
<div style={{padding:11,background:c.bg,borderRadius:6,border:`1px solid ${c.border}`}}>
<Radio label="Only when required outcomes fail (after retries)" checked={true} />
<Radio label="Before any output (review every verdict)" checked={false} hint="Always pause for human review, even when everything passes." />
<Radio label="On regression or stuck failures" checked={false} />
<Radio label="Per outcome (each outcome decides)" checked={false} />
<Radio label="Never (full auto)" checked={false} />
</div>
<FieldLabel mt>Notify via</FieldLabel>
<div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
<Tag color={c.green}>Dashboard</Tag>
<Tag color={c.green}>Discord</Tag>
<Tag color={c.green} removable>Email</Tag>
</div>
{/* Sources */}
<SectionLabel mt>Sources & Materials</SectionLabel>
<UploadStrip label="Reference materials" files={[
{icon:"📄",name:"Bluebook 21st Ed.pdf",size:"4.2 MB"},
{icon:"📄",name:"Schall Brief Template.docx"},
]} hint="Style guides, templates, expected output patterns." />
<div style={{marginTop:10}}>
<UploadStrip label="Evidence sources" files={[
{icon:"📁",name:"Case Record",size:"487 docs"},
{icon:"📄",name:"Motion to Dismiss.pdf"},
]} hint="Documents that ground factual claims." />
</div>
<FieldLabel mt>Connected libraries</FieldLabel>
<div style={{display:"flex",flexDirection:"column",gap:5}}>
{[
{label:"DOC73 PBE: Securities Litigation", icon:"📚"},
{label:"DOC25: Matter file (auto)", icon:"🗂"},
{label:"CourtListener", icon:"⚖"},
].map((l,i)=>(
<div key={i} style={{padding:"5px 8px",background:c.bgCard,borderRadius:3,border:`1px solid ${c.border}`,fontSize:10,color:c.text,display:"flex",alignItems:"center",gap:7}}>
<span style={{fontSize:11}}>{l.icon}</span>
<span>{l.label}</span>
</div>
))}
<div style={{padding:"5px 8px",fontSize:10,color:c.accent,cursor:"pointer"}}>+ Connect library…</div>
</div>
{/* Custom guidance */}
<SectionLabel mt>Custom Guidance</SectionLabel>
<Textarea value="Apply strict Bluebook 21st edition standards for citations. Prefer formal legal register over conversational. When a citation can't be verified from CourtListener, try Westlaw before marking it unresolvable." h={70} placeholder="(Optional) Instructions that apply to all outcome checks." />
<HelperText>Cross-cutting instructions applied to every verifier.</HelperText>
</div>
{/* MAIN — outcomes list */}
<div style={{flex:1,overflowY:"auto",background:c.bg}}>
{/* Verdict banner */}
<div style={{padding:"18px 28px 0"}}>
<div style={{padding:14,background:`${c.amber}08`,borderRadius:8,border:`1px solid ${c.amber}30`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:6}}>
<span style={{fontSize:12,fontWeight:700,color:c.amber}}>↺ NEEDS REVISION — iteration 2 of 3</span>
<span style={{fontSize:10,color:c.textSec,fontFamily:mono}}>last run: 9:24 AM · cost $0.34 · 12.4s</span>
</div>
<div style={{fontSize:11,color:c.text,lineHeight:1.5}}>
7 of 8 outcomes passed. <strong style={{color:c.red}}>Citations resolve to real cases</strong> failed — 3 citations could not be verified after 3 attempts. Feedback sent to next iteration.
</div>
<div style={{display:"flex",gap:8,marginTop:10}}>
<Btn ghost sm>View Full Verdict</Btn>
<Btn ghost sm>Compare to Iteration 1</Btn>
<Btn accent sm>Re-Run</Btn>
</div>
</div>
</div>
{/* Outcomes header */}
<div style={{padding:"22px 28px 0",display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div>
<div style={{fontSize:15,fontWeight:700,color:c.text,fontFamily:sans}}>Outcomes</div>
<div style={{fontSize:11,color:c.textSec,marginTop:3}}>Each outcome defines what success looks like + how to check it. Click an outcome to expand and configure.</div>
</div>
<div style={{display:"flex",gap:8}}>
<Btn ghost sm>↑ Reorder</Btn>
<Btn accent sm>+ Add Outcome</Btn>
</div>
</div>
{/* Outcome cards */}
<div style={{padding:"14px 28px 28px",display:"flex",flexDirection:"column",gap:8}}>
{OUTCOMES.map(o => (
<OutcomeCard key={o.id} outcome={o} expanded={expandedOutcome===o.id} onToggle={()=>setExpandedOutcome(expandedOutcome===o.id?null:o.id)} />
))}
<button style={{padding:"12px",background:"transparent",border:`1px dashed ${c.border}`,borderRadius:6,color:c.textSec,fontSize:11,cursor:"pointer",fontFamily:sans,marginTop:4}}>
+ Add another outcome
</button>
</div>
</div>
</div>
{/* Collapsible port drawer */}
<div style={{borderTop:`1px solid ${c.border}`,background:c.bgPanel}}>
<div onClick={()=>setPortsOpen(!portsOpen)} style={{padding:"8px 22px",display:"flex",alignItems:"center",gap:8,cursor:"pointer",fontSize:10,fontFamily:sans,color:c.textSec,fontWeight:600,letterSpacing:0.5,textTransform:"uppercase"}}>
<span style={{fontSize:11}}>{portsOpen?"▾":"▸"}</span>
Ports
<span style={{color:c.textTer,textTransform:"none",letterSpacing:0,fontWeight:400,fontStyle:"italic",marginLeft:4}}>
5 inputs · 7 outputs active · port routing defined per outcome below
</span>
</div>
{portsOpen && <PortsDrawer />}
</div>
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// OUTCOME DATA
// ═══════════════════════════════════════════════════════════════
const OUTCOMES = [
{
id:"o1", name:"Citations are present",
desc:"Brief contains at least the citations required to support the argument.",
resultType:"pass_fail", required:true,
method:"auto_check", methodSubtype:"regex_count_min",
status:"verified",
lastResultText:"19 citations found.",
},
{
id:"o2", name:"Citations resolve to real cases",
desc:"All cases cited resolve to real cases in CourtListener. Each citation is verified by looking it up; cases not found within 3 lookup attempts return as failures with the case name and search attempts logged.",
resultType:"pass_fail", required:true,
method:"specialist", specialistAgent:"citation-checker",
status:"failed",
dependsOn:["Citations are present"],
lastResult:"failed_3_of_19",
referenceRequired:"required",
},
{
id:"o3", name:"Cited holdings characterized accurately",
desc:"For each cited case, the brief's characterization of the holding matches the actual holding in the case database.",
resultType:"checklist", required:true,
method:"check_claims", claimType:"CaseCitation",
status:"failed", count:{passed:6,total:8},
dependsOn:["Citations resolve to real cases"],
referenceRequired:"required",
},
{
id:"o4", name:"No factual claims unsupported by record",
desc:"Every factual assertion in the brief is supported by an explicit reference to the record.",
resultType:"pass_fail", required:true,
method:"check_claims", claimType:"FactualAssertion",
status:"verified", referenceRequired:"required",
lastResultText:"34 of 34 factual claims supported.",
},
{
id:"o5", name:"Brief addresses all motion issues",
desc:"The motion to dismiss raised 7 issues; the brief addresses each.",
resultType:"checklist", required:true,
method:"agent_assess",
status:"verified", count:{passed:7,total:7},
referenceRequired:"optional",
},
{
id:"o6", name:"Argument is persuasive",
desc:"The brief constructs a persuasive case, addressing counterarguments and supporting reasoning with cited authority.",
resultType:"score", required:false,
method:"agent_assess", rubricPreset:"My legal-arg rubric (modified)",
status:"verified", score:0.78, threshold:0.7,
decomposed:true,
},
{
id:"o7", name:"Tone is professional",
desc:"Consistent with court filing standards.",
resultType:"score", required:false,
method:"agent_assess",
status:"verified", score:0.85, threshold:0.7,
},
{
id:"o8", name:"Brief between 4000-8000 words",
desc:"Word count within the court's required range.",
resultType:"pass_fail", required:true,
method:"auto_check", methodSubtype:"length",
status:"verified",
lastResultText:"6,247 words.",
},
];
// ═══════════════════════════════════════════════════════════════
// OUTCOME CARD
// ═══════════════════════════════════════════════════════════════
function OutcomeCard({outcome, expanded, onToggle}) {
return (
<div style={{background:c.bgCard,border:`1px solid ${expanded?c.accent+"40":c.border}`,borderRadius:7,overflow:"hidden",transition:"border 0.15s"}}>
{/* Header row */}
<div onClick={onToggle} style={{padding:"13px 16px",cursor:"pointer",display:"flex",alignItems:"center",gap:12}}>
<span style={{color:c.textTer,fontSize:11,minWidth:10}}>{expanded?"▾":"▸"}</span>
<div style={{flex:1,minWidth:0}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:4}}>
<span style={{fontSize:13,fontWeight:600,color:c.text}}>{outcome.name}</span>
<ResultTypePill type={outcome.resultType} />
{outcome.required ? <Pill color={c.amber}>REQUIRED</Pill> : <Pill color={c.textTer}>OPTIONAL</Pill>}
{outcome.dependsOn && <Pill color={c.textSec}>↳ depends on {outcome.dependsOn.length}</Pill>}
</div>
{!expanded && (
<div style={{fontSize:10,color:c.textSec,lineHeight:1.4,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>
{outcome.desc}
</div>
)}
</div>
{/* Last result mini-viz on right */}
<div style={{minWidth:140,display:"flex",justifyContent:"flex-end"}}>
<ResultIndicator outcome={outcome} compact />
</div>
</div>
{/* Expanded content */}
{expanded && <OutcomeCardBody outcome={outcome} />}
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// OUTCOME CARD — EXPANDED BODY (the story)
// ═══════════════════════════════════════════════════════════════
function OutcomeCardBody({outcome}) {
return (
<div style={{borderTop:`1px solid ${c.border}`,padding:"6px 22px 22px",background:c.bg}}>
{/* ── WHAT THIS CHECKS ── */}
<SectionHeader>What this checks</SectionHeader>
<FieldLabel>Name</FieldLabel>
<Input value={outcome.name} />
<FieldLabel mt>Success description</FieldLabel>
<Textarea value={outcome.desc} h={70} placeholder="What does success look like? Plain language — this drives the check below." />
<HelperText>This description is what the verifier reads. It's the operative spec for this outcome.</HelperText>
<div style={{display:"flex",alignItems:"center",gap:18,marginTop:14}}>
<div>
<FieldLabel>Result type</FieldLabel>
<div style={{display:"flex",gap:5}}>
{[
{id:"pass_fail",label:"Pass / fail",color:c.accent},
{id:"checklist",label:"Checklist",color:c.cyan},
{id:"score",label:"Score",color:c.purple},
].map(rt=>(
<button key={rt.id} style={{
padding:"6px 14px",fontSize:11,fontWeight:600,
background:outcome.resultType===rt.id?`${rt.color}18`:c.bgCard,
color:outcome.resultType===rt.id?rt.color:c.textSec,
border:`1px solid ${outcome.resultType===rt.id?rt.color+"50":c.border}`,
borderRadius:4,cursor:"pointer",fontFamily:sans
}}>{rt.label}</button>
))}
</div>
</div>
<div style={{flex:1}}>
<FieldLabel>Status</FieldLabel>
<div style={{display:"flex",gap:14,alignItems:"center"}}>
<Check label="Required (must pass for overall verdict)" checked={outcome.required} />
<Check label="Inverse (must NOT be true)" checked={false} />
</div>
</div>
</div>
{outcome.dependsOn && (
<div style={{marginTop:14}}>
<FieldLabel>Depends on</FieldLabel>
<div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
{outcome.dependsOn.map((d,i)=><Tag key={i} color={c.textSec} removable>↳ {d}</Tag>)}
<Tag color={c.textTer}>+ add</Tag>
</div>
<HelperText>This outcome runs only if its dependencies passed. Otherwise marked N/A.</HelperText>
</div>
)}
{/* ── HOW TO CHECK IT ── */}
<SectionHeader>How to check it</SectionHeader>
<FieldLabel>Check by</FieldLabel>
<Select opts={[
"Auto-check (length, format, regex, etc.)",
"Check claims (from upstream extractor)",
"Specialist agent (citation-checker, fact-checker, etc.)",
"Agent assessment (LLM judge with rubric)",
"Multiple checks (combine methods)",
]} v={
outcome.method==="auto_check" ? "Auto-check (length, format, regex, etc.)" :
outcome.method==="check_claims" ? "Check claims (from upstream extractor)" :
outcome.method==="specialist" ? "Specialist agent (citation-checker, fact-checker, etc.)" :
outcome.method==="agent_assess" ? "Agent assessment (LLM judge with rubric)" : "Multiple checks (combine methods)"
} />
<div style={{marginTop:12,padding:14,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<MethodConfig outcome={outcome} />
</div>
{/* ── REFERENCE MATERIALS (per outcome) ── */}
<SectionHeader>Reference materials needed</SectionHeader>
<Radio label="Required — must have evidence to verify" checked={outcome.referenceRequired==="required"} hint="If wired evidence isn't sufficient, this outcome returns INCOMPLETE." />
<Radio label="Optional — use if available, proceed without if not" checked={outcome.referenceRequired==="optional"} />
<Radio label="Not needed — checked against subject itself" checked={!outcome.referenceRequired} />
<Radio label="Audit only — log reference docs but don't use them" checked={false} />
{outcome.referenceRequired && (
<div style={{marginTop:10,padding:10,background:c.bgPanel,borderRadius:5,border:`1px solid ${c.border}`}}>
<FieldLabel>Use these sources for this outcome</FieldLabel>
<div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
<Tag color={c.green}>✓ Case Record</Tag>
{outcome.method==="specialist" && <Tag color={c.green}>✓ CourtListener</Tag>}
<Tag color={c.textTer}>+ select source</Tag>
</div>
</div>
)}
{/* ── IF THIS FAILS ── */}
<SectionHeader>If this fails</SectionHeader>
<div style={{padding:14,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<div style={{fontSize:11,lineHeight:1.8,color:c.text}}>
<Check label="Try again — up to" checked={true} />
<div style={{marginLeft:22,marginBottom:4,display:"flex",alignItems:"center",gap:8}}>
<Input value="3" w={40} />
<span style={{color:c.textSec,fontSize:10}}>retries · on retry,</span>
<Select opts={["re-check only this outcome","re-check everything"]} v="re-check only this outcome" compact />
</div>
<Check label="Then send to human for review" checked={true} hint="Human can answer questions, provide instructions, or request more research." />
<Check label="If still failing after all retries:" checked={true} />
<div style={{marginLeft:22,marginBottom:8}}>
<Select opts={["Mark as failed (fire failed_out)","Abort the run","Keep retrying anyway","Route to custom port…"]} v="Mark as failed (fire failed_out)" compact />
</div>
<div style={{borderTop:`1px solid ${c.border}`,marginTop:10,paddingTop:10}}>
<Check label="Can the agent gather more info to verify?" checked={true} hint="Allow web search, memory lookup, asking the user — when the verifier needs more data to decide." />
<div style={{marginLeft:22,marginTop:6,padding:10,background:c.bg,borderRadius:5}}>
<div style={{display:"flex",gap:14,alignItems:"center",marginBottom:6}}>
<span style={{fontSize:10,color:c.textSec}}>Allow up to</span>
<Input value="$0.50" w={70} />
<span style={{fontSize:10,color:c.textSec}}>and</span>
<Input value="3" w={35} />
<span style={{fontSize:10,color:c.textSec}}>gather attempts</span>
</div>
<div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
<Tag color={c.green}>✓ Web</Tag>
<Tag color={c.green}>✓ Memory</Tag>
<Tag color={c.green}>✓ Ask user</Tag>
<Tag color={c.textTer}>+ source</Tag>
</div>
</div>
</div>
</div>
</div>
{/* ── PASS WHEN ── */}
<SectionHeader>Pass when</SectionHeader>
<PassThreshold outcome={outcome} />
{/* ── LAST RESULT ── */}
{outcome.status && outcome.status !== "pending" && (
<>
<SectionHeader>Last result (iteration 2)</SectionHeader>
<ResultDetail outcome={outcome} />
</>
)}
</div>
);
}
// ─── Method config (changes based on method choice) ───
function MethodConfig({outcome}) {
if (outcome.method === "auto_check") {
return (
<>
<FieldLabel>Check type</FieldLabel>
<Select opts={["Word count (length)","Regex pattern","Format match","Schema validation","File exists","URL reachable"]} v="Word count (length)" compact />
<div style={{display:"flex",gap:10,marginTop:8}}>
<div style={{flex:1}}>
<FieldLabel>Min</FieldLabel>
<Input value="4000" />
</div>
<div style={{flex:1}}>
<FieldLabel>Max</FieldLabel>
<Input value="8000" />
</div>
</div>
<div style={{fontSize:9,color:c.green,marginTop:8,fontStyle:"italic"}}>
✓ Parameters auto-detected from "between 4000-8000 words"
</div>
</>
);
}
if (outcome.method === "specialist") {
return (
<>
<FieldLabel>Specialist agent</FieldLabel>
<Select opts={["citation-checker","fact-checker","legal-researcher","code-test-runner","custom…"]} v={outcome.specialistAgent} compact />
<FieldLabel mt>Tools available to this agent</FieldLabel>
<div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
<Tag color={c.green} removable>casedb_lookup</Tag>
<Tag color={c.green} removable>retrieve_evidence</Tag>
<Tag color={c.amber} removable>web_search</Tag>
<Tag color={c.textTer}>+ tool</Tag>
</div>
<FieldLabel mt>Extra instructions (optional)</FieldLabel>
<Textarea value="" h={50} placeholder="(Optional) Refine the success description for this specialist. Otherwise the description above is used directly." />
</>
);
}
if (outcome.method === "check_claims") {
return (
<>
<FieldLabel>Claim type to check</FieldLabel>
<Select opts={["CaseCitation","FactualAssertion","LegalProposition","OpposingArgument","ProceduralFact"]} v={outcome.claimType} compact />
<HelperText>Claim types come from the upstream <span style={{color:c.cyan,fontFamily:mono}}>◈ Brief Claims</span> extractor.</HelperText>
<FieldLabel mt>Verify against</FieldLabel>
<Select opts={["Wired evidence (evidence_in)","Reference materials","Agent retrieves on its own","Combination"]} v="Wired evidence (evidence_in)" compact />
<FieldLabel mt>Pass rate required</FieldLabel>
<Input value="100%" />
</>
);
}
if (outcome.method === "agent_assess") {
return (
<>
<FieldLabel>Judging agent</FieldLabel>
<Select opts={["(task default)","Elnor","Nova","Atlas","Sage"]} v="(task default)" compact />
<FieldLabel mt>Rubric</FieldLabel>
<Select opts={[
"(custom — based on description above)",
"Quality of reasoning (system preset)",
"Professional writing (system preset)",
"My legal-arg rubric (modified)",
"Save current as preset…",
"Manage presets…",
]} v={outcome.rubricPreset || "(custom — based on description above)"} compact />
{outcome.rubricPreset && (
<div style={{display:"flex",gap:6,marginTop:6}}>
<Btn ghost sm>Edit Rubric</Btn>
<Btn ghost sm>Save As New…</Btn>
<Btn ghost sm>Revert</Btn>
</div>
)}
<div style={{display:"flex",gap:10,marginTop:10}}>
<div style={{flex:1}}>
<FieldLabel>Scale</FieldLabel>
<Select opts={["1-5 with anchors","0-1 continuous","Pass / fail (binary judgment)"]} v="1-5 with anchors" compact />
</div>
<div style={{flex:1}}>
<FieldLabel>Ensemble</FieldLabel>
<Select opts={["1 judge","3 judges (majority)","5 judges (majority)"]} v="1 judge" compact />
</div>
</div>
<div style={{marginTop:10}}>
<Check label="Require evidence citation in rationale" checked={true} hint="Judge must cite specific spans from the subject to support its score." />
<Check label="Use rubric architect agent to help build sub-criteria" checked={false} hint="An LLM analyzes the success description and proposes a decomposed rubric for review." />
</div>
{outcome.decomposed && (
<div style={{marginTop:12,padding:12,background:c.bg,borderRadius:5,border:`1px solid ${c.purple}40`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8}}>
<span style={{fontSize:10,fontWeight:700,color:c.purple,letterSpacing:0.5}}>RUBRIC SUB-CRITERIA</span>
<Btn ghost sm>⚡ Help me build</Btn>
</div>
<div style={{display:"flex",flexDirection:"column",gap:6}}>
{[
{label:"Identifies strongest authority per argument",weight:1.5,score:0.82},
{label:"Anticipates and rebuts counterarguments",weight:1.0,score:0.65},
{label:"Logical chain from facts to conclusion",weight:1.0,score:0.80},
{label:"Clear, accessible language",weight:0.5,score:0.85},
].map((sc,i)=>(
<div key={i} style={{display:"flex",alignItems:"center",gap:8,fontSize:10}}>
<span style={{flex:1,color:c.text}}>{sc.label}</span>
<span style={{color:c.textTer,fontFamily:mono,fontSize:9,minWidth:50}}>weight {sc.weight}</span>
<div style={{flex:1,height:6,background:c.bgPanel,borderRadius:3,overflow:"hidden"}}>
<div style={{width:`${sc.score*100}%`,height:"100%",background:sc.score>=0.7?c.green:c.amber}} />
</div>
<span style={{color:sc.score>=0.7?c.green:c.amber,fontFamily:mono,fontSize:10,minWidth:32}}>{sc.score.toFixed(2)}</span>
</div>
))}
<div style={{display:"flex",alignItems:"center",gap:8,marginTop:6}}>
<Btn ghost sm>+ Add sub-criterion</Btn>
<span style={{flex:1}} />
<span style={{fontSize:9,color:c.textTer}}>Roll up by</span>
<Select opts={["weighted_average","all_must_pass","min_score","checkpoint_count"]} v="weighted_average" compact />
</div>
</div>
</div>
)}
</>
);
}
return null;
}
// ─── Pass threshold (changes based on result type) ───
function PassThreshold({outcome}) {
if (outcome.resultType === "pass_fail") {
return (
<div style={{padding:11,background:c.bgPanel,borderRadius:5,border:`1px solid ${c.border}`,fontSize:11,color:c.text,lineHeight:1.6}}>
Passes when the check returns <strong style={{color:c.green}}>true</strong>.
</div>
);
}
if (outcome.resultType === "checklist") {
return (
<div style={{padding:11,background:c.bgPanel,borderRadius:5,border:`1px solid ${c.border}`,fontSize:11,color:c.text,lineHeight:1.6,display:"flex",alignItems:"center",gap:10,flexWrap:"wrap"}}>
Passes when
<Select opts={["all items","N of M items","at least X items","percent of items"]} v="all items" compact />
pass.
</div>
);
}
if (outcome.resultType === "score") {
return (
<div style={{padding:11,background:c.bgPanel,borderRadius:5,border:`1px solid ${c.border}`,fontSize:11,color:c.text,lineHeight:1.6,display:"flex",alignItems:"center",gap:10}}>
Passes when score ≥
<Input value={outcome.threshold?.toString() || "0.7"} w={60} />
<span style={{color:c.textSec,fontSize:10}}>on the configured scale.</span>
</div>
);
}
return null;
}
// ─── Result indicator (compact, for card header) ───
function ResultIndicator({outcome, compact}) {
if (!outcome.status || outcome.status === "pending") {
return <StatusBadge status="pending" />;
}
if (outcome.resultType === "pass_fail") {
return <StatusBadge status={outcome.status} />;
}
if (outcome.resultType === "checklist") {
return (
<div style={{display:"flex",alignItems:"center",gap:8}}>
<span style={{fontSize:10,color:outcome.count.passed===outcome.count.total?c.green:c.amber,fontFamily:mono,fontWeight:700}}>
{outcome.count.passed} / {outcome.count.total}
</span>
<StatusBadge status={outcome.status} />
</div>
);
}
if (outcome.resultType === "score") {
const passed = outcome.score >= outcome.threshold;
return (
<div style={{display:"flex",alignItems:"center",gap:8}}>
<div style={{position:"relative",width:60,height:6,background:c.bg,borderRadius:3,overflow:"hidden"}}>
<div style={{width:`${outcome.score*100}%`,height:"100%",background:passed?c.green:c.amber,borderRadius:3}} />
<div style={{position:"absolute",left:`${outcome.threshold*100}%`,top:-2,width:1,height:10,background:c.textTer}} />
</div>
<span style={{fontSize:10,color:passed?c.green:c.amber,fontFamily:mono,fontWeight:700}}>{outcome.score.toFixed(2)}</span>
<StatusBadge status={outcome.status} />
</div>
);
}
}
// ─── Result detail (expanded view in card body) ───
function ResultDetail({outcome}) {
if (outcome.status === "verified") {
return (
<div style={{padding:12,background:`${c.green}08`,borderRadius:6,border:`1px solid ${c.green}25`}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:6}}>
<StatusBadge status="verified" />
{outcome.resultType==="score" && (
<span style={{fontSize:10,color:c.green,fontFamily:mono}}>score {outcome.score.toFixed(2)} / threshold {outcome.threshold.toFixed(2)}</span>
)}
{outcome.resultType==="checklist" && (
<span style={{fontSize:10,color:c.green,fontFamily:mono}}>{outcome.count.passed} of {outcome.count.total}</span>
)}
</div>
<div style={{fontSize:11,color:c.text,lineHeight:1.5}}>{outcome.lastResultText || "Passed."}</div>
</div>
);
}
// Failed — checklist case
if (outcome.status === "failed" && outcome.resultType === "checklist") {
return (
<div style={{padding:12,background:`${c.red}08`,borderRadius:6,border:`1px solid ${c.red}25`}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:10}}>
<StatusBadge status="failed" />
<span style={{fontSize:10,color:c.red,fontFamily:mono}}>{outcome.count.passed} of {outcome.count.total}</span>
</div>
<div style={{display:"flex",flexDirection:"column",gap:5}}>
{[
{label:"Citation #1: Tellabs, Inc. v. Makor", status:true},
{label:"Citation #2: Janus Capital Group v. First Derivative", status:true},
{label:"Citation #3: Halliburton Co. v. Erica P. John Fund", status:true},
{label:"Citation #4: Matrixx Initiatives v. Siracusano", status:true},
{label:"Citation #5: Smith v. Jones, 432 F.3d 12", status:false, note:"holding mischaracterized"},
{label:"Citation #6: Basic Inc. v. Levinson", status:true},
{label:"Citation #7: Stoneridge Investment Partners v. Scientific-Atlanta", status:true},
{label:"Citation #8: In re Acme Corp.", status:false, note:"case not found in casedb"},
].map((cp,i)=>(
<div key={i} style={{display:"flex",alignItems:"center",gap:8,padding:"5px 8px",background:c.bg,borderRadius:3}}>
<span style={{color:cp.status?c.green:c.red,fontSize:11,fontWeight:700,minWidth:14}}>{cp.status?"✓":"✕"}</span>
<span style={{fontSize:10,color:c.text,flex:1}}>{cp.label}</span>
{cp.note && <span style={{fontSize:9,color:c.red,fontStyle:"italic"}}>{cp.note}</span>}
</div>
))}
</div>
</div>
);
}
// Failed — pass/fail case
if (outcome.status === "failed" && outcome.resultType === "pass_fail") {
return (
<div style={{padding:12,background:`${c.red}08`,borderRadius:6,border:`1px solid ${c.red}25`}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:8}}>
<StatusBadge status="failed" />
</div>
<div style={{fontSize:11,color:c.text,marginBottom:8,lineHeight:1.5}}>
3 of 19 citations did not resolve via citation-checker after 3 lookup attempts each:
</div>
<div style={{display:"flex",flexDirection:"column",gap:4,marginLeft:6}}>
<div style={{fontSize:10,color:c.text}}>• <code style={{color:c.amber}}>Smith v. Jones, 432 F.3d 12 (5th Cir. 2007)</code> — case not found</div>
<div style={{fontSize:10,color:c.text}}>• <code style={{color:c.amber}}>In re Acme Corp., 215 B.R. 89 (Bankr. S.D.N.Y. 1997)</code> — no matching case</div>
<div style={{fontSize:10,color:c.text}}>• <code style={{color:c.amber}}>Doe v. Smith</code> — citation incomplete</div>
</div>
<div style={{fontSize:10,color:c.textSec,marginTop:10,paddingTop:10,borderTop:`1px solid ${c.border}`,fontStyle:"italic"}}>
Feedback for next iteration: "Re-verify citation sources for §III paragraph 4; consider removing citations that cannot be verified."
</div>
</div>
);
}
// Scored
if (outcome.status === "verified" && outcome.resultType === "score") {
return (
<div style={{padding:12,background:`${c.green}08`,borderRadius:6,border:`1px solid ${c.green}25`}}>
<div style={{display:"flex",alignItems:"center",gap:10,marginBottom:8}}>
<StatusBadge status="verified" />
<div style={{position:"relative",width:140,height:8,background:c.bg,borderRadius:4,overflow:"hidden"}}>
<div style={{width:`${outcome.score*100}%`,height:"100%",background:c.green,borderRadius:4}} />
<div style={{position:"absolute",left:`${outcome.threshold*100}%`,top:-2,width:1,height:12,background:c.textTer}} />
</div>
<span style={{fontSize:11,color:c.green,fontFamily:mono,fontWeight:700}}>{outcome.score.toFixed(2)}</span>
<span style={{fontSize:10,color:c.textTer,fontFamily:mono}}>threshold {outcome.threshold.toFixed(2)}</span>
</div>
</div>
);
}
return null;
}
// ─── Collapsible port drawer ───
function PortsDrawer() {
const inputs = [
{label:"subject_in", role:"required · data", color:c.accent, wired:"◆ Draft Brief"},
{label:"reference_in", role:"optional · data", color:c.accent, wired:null},
{label:"evidence_in.N", role:"optional · expandable", color:c.accent, wired:"◇ Case Record"},
{label:"claims_in", role:"optional · data", color:c.accent, wired:"◈ Brief Claims"},
{label:"human_response_in", role:"optional · data", color:c.amber, wired:null, note:"active when human review used"},
];
const outputs = [
{label:"verdict_out", role:"always · data", color:c.cyan, wired:null},
{label:"passed_out", role:"signal+data", color:c.green, wired:"↺ Loop.stop_in"},
{label:"failed_out", role:"signal+data", color:c.red, wired:"↺ Loop.return_in"},
{label:"feedback_out", role:"data", color:c.purple, wired:"↺ Loop.feedback_in"},
{label:"human_review_out", role:"signal+data", color:c.amber, wired:"👤 Human Review", note:"per outcome routing"},
{label:"needs_more_info_out", role:"signal+data", color:c.cyan, wired:null, note:"when gathering exhausted"},
{label:"gathered_out", role:"data", color:c.cyan, wired:null},
{label:"signal_out", role:"always · signal", color:c.amber, wired:null},
{label:"error_out", role:"signal+data", color:c.red, wired:null},
];
return (
<div style={{padding:"6px 22px 16px",borderTop:`1px solid ${c.border}`,background:c.bg,display:"flex",gap:24}}>
<div style={{flex:1}}>
<div style={{fontSize:9,fontWeight:700,color:c.textTer,letterSpacing:0.5,marginBottom:8}}>INPUTS</div>
{inputs.map((p,i)=><PortRow key={i} port={p} />)}
</div>
<div style={{flex:1.4}}>
<div style={{fontSize:9,fontWeight:700,color:c.textTer,letterSpacing:0.5,marginBottom:8}}>OUTPUTS</div>
{outputs.map((p,i)=><PortRow key={i} port={p} />)}
</div>
</div>
);
}
function PortRow({port}) {
return (
<div style={{display:"flex",alignItems:"center",gap:8,padding:"4px 7px",borderRadius:3,marginBottom:2,fontSize:10}}>
<span style={{width:6,height:6,borderRadius:"50%",background:port.color}} />
<span style={{color:port.color,fontFamily:mono,minWidth:140,fontWeight:600}}>{port.label}</span>
<span style={{color:c.textTer,fontFamily:mono,fontSize:9,minWidth:140}}>{port.role}</span>
{port.wired ? (
<span style={{color:c.text,fontSize:10}}>→ {port.wired}</span>
) : (
<span style={{color:c.textTer,fontSize:9,fontStyle:"italic"}}>{port.note || "unwired"}</span>
)}
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// EXTRACTOR (same naming/layout principles applied)
// ═══════════════════════════════════════════════════════════════
function ExtractorView() {
const [portsOpen, setPortsOpen] = useState(false);
const [expandedType, setExpandedType] = useState("ct1");
return (
<div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden"}}>
<div style={{height:52,minHeight:52,borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",justifyContent:"space-between",padding:"0 22px",background:c.bgPanel}}>
<div style={{display:"flex",alignItems:"center",gap:12}}>
<span style={{fontSize:20}}>◈</span>
<div>
<div style={{fontSize:13,fontWeight:700,color:c.text}}>Claim Extractor</div>
<div style={{fontSize:10,color:c.textTer,fontFamily:mono,marginTop:2}}>step.claim_extractor · Brief Claims</div>
</div>
<span style={{marginLeft:14}}><Pill color={c.green}>50 claims extracted</Pill></span>
<span><Pill color={c.cyan}>cache hit</Pill></span>
</div>
<div style={{display:"flex",gap:8,alignItems:"center"}}>
<Btn ghost>Preset…</Btn>
<Btn primary>▶ Run</Btn>
<span style={{fontSize:18,color:c.textSec,cursor:"pointer",padding:"0 4px",marginLeft:4}}>✕</span>
</div>
</div>
<div style={{flex:1,display:"flex",overflow:"hidden"}}>
{/* LEFT SIDEBAR */}
<div style={{width:300,minWidth:300,borderRight:`1px solid ${c.border}`,padding:"18px 16px 22px",overflowY:"auto",background:c.bgPanel}}>
<SectionLabel>Connected Inputs</SectionLabel>
<div style={{padding:"5px 7px",background:`${c.accent}10`,borderRadius:3,border:`1px solid ${c.accent}25`,fontSize:10}}>
<span style={{color:c.accent,fontFamily:mono}}>data_in</span> <span style={{color:c.textSec}}>← ◆ Draft Brief</span>
</div>
<SectionLabel mt>Downstream Consumers</SectionLabel>
<div style={{padding:"5px 7px",background:`${c.green}10`,borderRadius:3,border:`1px solid ${c.green}25`,fontSize:10}}>
<span style={{color:c.green,fontFamily:mono}}>claims_out</span> <span style={{color:c.textSec}}>→ ⊜ Brief Review</span>
</div>
<SectionLabel mt>Extraction Agent</SectionLabel>
<Select opts={["(task default)","Elnor","Nova","claude-haiku-4-5 (cheap)"]} v="claude-haiku-4-5 (cheap)" compact />
<HelperText>Cheap models are fine for parsing. Authority-required types auto-upgrade.</HelperText>
<FieldLabel mt>Auto-upgrade for authority-required types</FieldLabel>
<Select opts={["claude-sonnet-4-6","claude-opus-4-7"]} v="claude-sonnet-4-6" compact />
<SectionLabel mt>Defaults</SectionLabel>
<FieldLabel>Min confidence</FieldLabel>
<Input value="0.7" />
<FieldLabel mt>Max claims (cap)</FieldLabel>
<Input value="50" />
<FieldLabel mt>Cost cap per run</FieldLabel>
<Input value="$1.00" />
<FieldLabel mt>If cap hit, drop by</FieldLabel>
<Select opts={["lowest confidence first","balance across types","include all anyway (no cap)"]} v="balance across types" compact />
<SectionLabel mt>Caching</SectionLabel>
<Check label="Cache results (skip extraction when subject unchanged)" checked={true} />
<Check label="Force fresh extraction next run" checked={false} />
<SectionLabel mt>Sources & Additional Docs</SectionLabel>
<Check label="Extract from primary subject only" checked={true} />
<Check label="Also extract from uploaded documents" checked={false} />
<Check label="Also extract from connected libraries" checked={false} hint="Expensive — use only for calibration runs." />
<div style={{marginTop:10}}>
<UploadStrip label="Uploaded documents (optional)" files={[]} hint="Extract claims from these in addition to the primary subject." />
</div>
<FieldLabel mt>Connected libraries</FieldLabel>
<div style={{display:"flex",flexDirection:"column",gap:4}}>
<div style={{padding:"5px 8px",background:c.bgCard,borderRadius:3,border:`1px solid ${c.border}`,fontSize:10,color:c.text}}>📚 DOC73 PBE</div>
<div style={{padding:"5px 8px",background:c.bgCard,borderRadius:3,border:`1px solid ${c.border}`,fontSize:10,color:c.text}}>🗂 Matter file</div>
</div>
<SectionLabel mt>Custom Guidance</SectionLabel>
<Textarea value="Prefer Bluebook format. Skip footnotes that only cross-reference (e.g., 'See supra note 3'). For factual assertions, exclude background; extract only claims the brief asserts as true." h={70} />
</div>
{/* MAIN — claim types */}
<div style={{flex:1,overflowY:"auto",background:c.bg}}>
{/* Summary banner */}
<div style={{padding:"18px 28px 0"}}>
<div style={{padding:14,background:c.bgCard,borderRadius:8,border:`1px solid ${c.border}`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:6}}>
<span style={{fontSize:12,fontWeight:700,color:c.text}}>Last Run — 50 claims extracted (cached)</span>
<span style={{fontSize:10,color:c.textSec,fontFamily:mono}}>$0.00 (cached) · was $0.12 fresh · 8:34 AM</span>
</div>
<div style={{display:"grid",gridTemplateColumns:"repeat(5, 1fr)",gap:8,marginTop:10}}>
{[
{name:"CaseCitation",count:19,color:c.accent},
{name:"FactualAssertion",count:18,color:c.green},
{name:"LegalProposition",count:7,color:c.purple},
{name:"OpposingArgument",count:4,color:c.amber},
{name:"ProceduralFact",count:2,color:c.cyan},
].map((t,i)=>(
<div key={i} style={{padding:10,background:c.bg,borderRadius:5,border:`1px solid ${t.color}25`}}>
<div style={{fontSize:8,color:t.color,fontWeight:600,letterSpacing:0.5,marginBottom:2}}>{t.name.toUpperCase()}</div>
<div style={{fontSize:18,fontWeight:700,color:t.color,fontFamily:mono}}>{t.count}</div>
</div>
))}
</div>
</div>
</div>
{/* Claim types header */}
<div style={{padding:"22px 28px 0",display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div>
<div style={{fontSize:15,fontWeight:700,color:c.text,fontFamily:sans}}>Claim Types</div>
<div style={{fontSize:11,color:c.textSec,marginTop:3}}>What to extract. Each type defines its instruction, fields, and how it's used downstream.</div>
</div>
<Btn accent sm>+ Add Claim Type</Btn>
</div>
<div style={{padding:"14px 28px 28px",display:"flex",flexDirection:"column",gap:8}}>
{CLAIM_TYPES.map(t => (
<ClaimTypeCard key={t.id} type={t} expanded={expandedType===t.id} onToggle={()=>setExpandedType(expandedType===t.id?null:t.id)} />
))}
<button style={{padding:"12px",background:"transparent",border:`1px dashed ${c.border}`,borderRadius:6,color:c.textSec,fontSize:11,cursor:"pointer",fontFamily:sans,marginTop:4}}>
+ Add another claim type
</button>
</div>
</div>
</div>
{/* Port drawer */}
<div style={{borderTop:`1px solid ${c.border}`,background:c.bgPanel}}>
<div onClick={()=>setPortsOpen(!portsOpen)} style={{padding:"8px 22px",display:"flex",alignItems:"center",gap:8,cursor:"pointer",fontSize:10,fontFamily:sans,color:c.textSec,fontWeight:600,letterSpacing:0.5,textTransform:"uppercase"}}>
<span style={{fontSize:11}}>{portsOpen?"▾":"▸"}</span>
Ports
<span style={{color:c.textTer,textTransform:"none",letterSpacing:0,fontWeight:400,fontStyle:"italic",marginLeft:4}}>
1 input · 4 outputs
</span>
</div>
{portsOpen && (
<div style={{padding:"6px 22px 16px",borderTop:`1px solid ${c.border}`,background:c.bg,display:"flex",gap:24}}>
<div style={{flex:1}}>
<div style={{fontSize:9,fontWeight:700,color:c.textTer,letterSpacing:0.5,marginBottom:8}}>INPUTS</div>
<PortRow port={{label:"data_in",role:"required · subject",color:c.accent,wired:"◆ Draft Brief"}} />
</div>
<div style={{flex:1.4}}>
<div style={{fontSize:9,fontWeight:700,color:c.textTer,letterSpacing:0.5,marginBottom:8}}>OUTPUTS</div>
<PortRow port={{label:"claims_out",role:"always · data",color:c.green,wired:"⊜ Brief Review"}} />
<PortRow port={{label:"metrics_out",role:"optional",color:c.cyan,wired:null,note:"unwired"}} />
<PortRow port={{label:"signal_out",role:"always",color:c.amber,wired:null,note:"unwired"}} />
<PortRow port={{label:"error_out",role:"signal+data",color:c.red,wired:null,note:"unwired"}} />
</div>
</div>
)}
</div>
</div>
);
}
const CLAIM_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","Cited holdings characterized accurately"]},
{id:"ct2", name:"FactualAssertion", desc:"Factual claims about the matter — dates, amounts, events, parties.", count:18, evaluable:true, authority:false, consumedBy:["No factual claims unsupported"]},
{id:"ct3", name:"LegalProposition", desc:"Statements of legal rules or principles.", count:7, evaluable:true, authority:false, consumedBy:[]},
{id:"ct4", name:"OpposingArgument", desc:"References to opposing counsel's arguments.", count:4, evaluable:true, authority:false, consumedBy:[]},
{id:"ct5", name:"ProceduralFact", desc:"Statements about case procedural posture.", count:2, evaluable:false, authority:false, consumedBy:[]},
];
function ClaimTypeCard({type, expanded, onToggle}) {
return (
<div style={{background:c.bgCard,border:`1px solid ${expanded?c.accent+"40":c.border}`,borderRadius:7,overflow:"hidden"}}>
<div onClick={onToggle} style={{padding:"13px 16px",cursor:"pointer",display:"flex",alignItems:"center",gap:12}}>
<span style={{color:c.textTer,fontSize:11,minWidth:10}}>{expanded?"▾":"▸"}</span>
<div style={{flex:1,minWidth:0}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:4}}>
<span style={{fontSize:13,fontWeight:600,color:c.text,fontFamily:mono}}>{type.name}</span>
{type.evaluable ? <Pill color={c.green}>EVALUABLE</Pill> : <Pill color={c.textTer}>NOT EVALUABLE</Pill>}
{type.authority && <Pill color={c.amber}>NEEDS SPECIALIST</Pill>}
{type.consumedBy.length>0 && <Pill color={c.cyan}>USED BY {type.consumedBy.length}</Pill>}
</div>
{!expanded && (
<div style={{fontSize:10,color:c.textSec,lineHeight:1.4,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>
{type.desc}
</div>
)}
</div>
<span style={{fontSize:14,fontWeight:700,color:type.count>0?c.text:c.textTer,fontFamily:mono}}>{type.count}</span>
</div>
{expanded && (
<div style={{borderTop:`1px solid ${c.border}`,padding:"6px 22px 22px",background:c.bg}}>
<SectionHeader>What this extracts</SectionHeader>
<FieldLabel>Name</FieldLabel>
<Input value={type.name} />
<FieldLabel mt>Description</FieldLabel>
<Textarea value={type.desc} h={50} />
<FieldLabel mt>Extraction instruction (what the extractor reads)</FieldLabel>
<Textarea value="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)." h={70} placeholder="Plain language instruction for the extraction agent." />
<HelperText>This is operative. The extractor reads it directly to find claims of this type.</HelperText>
<div style={{display:"flex",gap:16,marginTop:12}}>
<Check label="Can be checked by an Evaluator" checked={type.evaluable} hint="When false, claims of this type are documented but not verifiable downstream." />
<Check label="Needs specialist agent for verification" checked={type.authority} hint="When true, downstream Evaluators upgrade to a stronger model + specialist." />
</div>
<SectionHeader>Fields to extract</SectionHeader>
<div style={{padding:10,background:c.bgPanel,borderRadius:5,border:`1px solid ${c.border}`,display:"flex",flexDirection:"column",gap:5}}>
{[
{name:"case_name", type:"text", req:true},
{name:"citation", type:"text", req:true},
{name:"court", type:"enum", req:false},
{name:"year", type:"number", req:false},
{name:"holding_quoted", type:"text", req:false},
].map((f,i)=>(
<div key={i} style={{display:"grid",gridTemplateColumns:"1.5fr 1fr auto",gap:8,alignItems:"center",padding:"5px 8px",background:c.bg,borderRadius:3,fontSize:10}}>
<span style={{color:c.text,fontFamily:mono}}>{f.name}</span>
<span style={{color:c.accent,fontFamily:mono}}>{f.type}</span>
<span style={{color:f.req?c.amber:c.textTer}}>{f.req?"required":"optional"}</span>
</div>
))}
<Btn ghost sm>+ Add field</Btn>
</div>
{type.authority && (
<>
<SectionHeader>Preferred specialist for verification</SectionHeader>
<Select opts={["citation-checker","fact-checker","legal-researcher","custom…"]} v="citation-checker" compact />
<HelperText>Downstream Evaluators that check claims of this type will use this specialist by default.</HelperText>
</>
)}
<SectionHeader>Source spans</SectionHeader>
<Check label="Capture source spans (where in the subject the claim was found)" checked={true} />
<Check label="Classify span role (primary / citation / parenthetical / footnote)" checked={true} />
{type.consumedBy.length>0 && (
<>
<SectionHeader>Used by these outcomes</SectionHeader>
<div style={{padding:11,background:`${c.cyan}08`,borderRadius:5,border:`1px solid ${c.cyan}25`}}>
{type.consumedBy.map((o,i)=>(
<div key={i} style={{fontSize:11,color:c.text,padding:"3px 0",cursor:"pointer"}}>
↗ <span style={{color:c.cyan}}>{o}</span>
</div>
))}
</div>
</>
)}
</div>
)}
</div>
);
}