Q_TASK_EVALUATOR_EXTRACTOR_EXPANDED_V1.jsx
Design Mockups/Archived Mockups/DOC23 Task Mockups/Q_TASK_EVALUATOR_EXTRACTOR_EXPANDED_V1.jsx
ELNOR REPO READER TEXT MIRROR
Original path: Design Mockups/Archived Mockups/DOC23 Task Mockups/Q_TASK_EVALUATOR_EXTRACTOR_EXPANDED_V1.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 — V1
// Focused reference for two new module types under design.
// Visual language matches Q_TASK_MODULAR_CANVAS_V11_ALT_D.
//
// New patterns shown here:
// • Conditional port system — ports populated based on Mode + toggles
// • Human review integration — full sub-config + Q&A round-trip
// • Document / folder / library upload zones for context
// • Custom guidance prompt boxes (beyond just outcomes)
// • Verdict mode (binary / count / scored) per outcome
// • Verification method routing from free-text success description
// • Decomposition for broad outcomes (rubric sub-criteria)
// • Per-outcome evidence policy, iteration policy, gather policy
// • Iteration / Loop integration — feedback_out → feedback_in pattern
// • Rubric preset library with "edit and save as..." pattern
// ═══════════════════════════════════════════════════════════════
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 (matching V11 ALT D conventions) ──
const DL = ({mt, children}) => (
<div style={{fontSize:9,fontWeight:600,color:c.textTer,marginTop:mt?10:0,marginBottom:4,letterSpacing:0.3,textTransform:"uppercase",fontFamily:sans}}>
{children}
</div>
);
const DS = ({opts, v, onChange}) => (
<select defaultValue={v} onChange={onChange||(()=>{})} style={{width:"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:4,padding:"6px 8px",fontSize:11,color:c.text,fontFamily:sans,outline:"none"}}>
{opts.map((o,i)=><option key={i} value={o}>{o}</option>)}
</select>
);
const DT = ({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:"7px 9px",fontSize:11,color:c.text,fontFamily:sans,resize:"vertical",minHeight:h||60,outline:"none",lineHeight:1.5}} />
);
const DI = ({value, placeholder, onChange}) => (
<input defaultValue={value} placeholder={placeholder} onChange={onChange||(()=>{})}
style={{width:"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:4,padding:"6px 8px",fontSize:11,color:c.text,fontFamily:sans,outline:"none"}} />
);
const DC = ({label, checked, onChange}) => (
<label style={{display:"flex",alignItems:"center",gap:8,padding:"4px 0",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>
);
const Btn = ({children, primary, accent, danger, ghost, onClick}) => {
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:"5px 10px",borderRadius:4,fontSize: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>
);
// ─── Port indicator (used in bottom port bar) ───
const Port = ({label, role, color, enabled, condition}) => (
<div style={{
display:"flex",alignItems:"center",gap:6,padding:"5px 8px",
background:enabled?`${color}10`:"transparent",
border:`1px dashed ${enabled?color+"50":c.border}`,
borderRadius:4,opacity:enabled?1:0.4,minHeight:24
}}>
<span style={{width:6,height:6,borderRadius:"50%",background:enabled?color:c.textTer}} />
<span style={{fontSize:9,fontWeight:600,color:enabled?color:c.textTer,fontFamily:mono}}>{label}</span>
{role && <span style={{fontSize:8,color:c.textTer,fontFamily:mono}}>{role}</span>}
{!enabled && condition && (
<span style={{fontSize:8,color:c.textTer,fontFamily:mono,marginLeft:4}}>· {condition}</span>
)}
</div>
);
// ─── Outcome verdict mode badge ───
const VerdictBadge = ({mode}) => {
const colors = {binary: c.accent, count: c.cyan, scored: c.purple};
const labels = {binary: "BINARY", count: "COUNT", scored: "SCORED"};
return <Pill color={colors[mode]}>{labels[mode]}</Pill>;
};
// ─── Status badge for outcome results ───
const StatusBadge = ({status}) => {
const map = {
verified: {label:"VERIFIED", color:c.green, icon:"✓"},
failed: {label:"FAILED", color:c.red, icon:"✕"},
incomplete: {label:"INCOMPLETE", color:c.amber, icon:"◐"},
subject_incomplete: {label:"SUBJECT INCOMPLETE", color:c.amber, icon:"◐"},
gathering_required: {label:"GATHERING…", color:c.cyan, icon:"⟳"},
indeterminate: {label:"INDETERMINATE", color:c.textSec, icon:"?"},
not_applicable: {label:"N/A", color:c.textTer, icon:"—"},
pending: {label:"PENDING", color:c.textTer, icon:"○"},
};
const m = map[status] || map.pending;
return (
<span style={{display:"inline-flex",alignItems:"center",gap:5,padding:"2px 8px",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>
);
};
// ─── Document upload zone ───
const UploadZone = ({label, hint, files}) => (
<div>
<DL>{label}</DL>
<div style={{border:`1px dashed ${c.border}`,borderRadius:6,padding:files&&files.length?"8px":"16px",background:c.bg,cursor:"pointer"}}>
{(!files || !files.length) ? (
<div style={{textAlign:"center"}}>
<div style={{fontSize:20,color:c.textTer,marginBottom:4}}>📁</div>
<div style={{fontSize:10,color:c.textSec,fontFamily:sans}}>Click to browse or drag files here</div>
{hint && <div style={{fontSize:9,color:c.textTer,marginTop:4,fontFamily:sans,fontStyle:"italic"}}>{hint}</div>}
</div>
) : (
<div style={{display:"flex",flexDirection:"column",gap:4}}>
{files.map((f,i)=>(
<div key={i} style={{display:"flex",alignItems:"center",justifyContent:"space-between",padding:"4px 6px",background:c.bgCard,borderRadius:3,fontSize:10}}>
<span style={{display:"flex",alignItems:"center",gap:6,color:c.text,fontFamily:sans}}>
<span style={{color:c.textTer}}>{f.icon}</span>
<span>{f.name}</span>
<span style={{color:c.textTer,fontSize:9}}>{f.size}</span>
</span>
<span style={{cursor:"pointer",color:c.textTer,opacity:0.6,fontSize:11}}>×</span>
</div>
))}
<div style={{textAlign:"center",padding:"4px",fontSize:9,color:c.textSec,fontFamily:sans,cursor:"pointer"}}>+ Add more</div>
</div>
)}
</div>
</div>
);
// ═══════════════════════════════════════════════════════════════
// MAIN COMPONENT
// ═══════════════════════════════════════════════════════════════
export default function EvaluatorExtractorMockup() {
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"}}>
<ViewSwitcher view={view} setView={setView} />
{view === "evaluator" ? <EvaluatorExpandedView /> : <ExtractorExpandedView />}
</div>
);
}
function ViewSwitcher({view, setView}) {
return (
<div style={{height:36,minHeight:36,borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",padding:"0 12px",gap:8,background:c.bgPanel}}>
<div style={{fontSize:9,fontWeight:600,color:c.textTer,letterSpacing:0.4,marginRight:8}}>REFERENCE MOCKUP — EVALUATOR + EXTRACTOR EXPANDED VIEWS</div>
<div style={{flex:1}} />
<div style={{display:"flex",gap:4,padding:2,background:c.bg,borderRadius:5}}>
<button onClick={()=>setView("evaluator")}
style={{padding:"4px 10px",fontSize:10,fontWeight:600,color:view==="evaluator"?c.text:c.textSec,background:view==="evaluator"?c.bgHover:"transparent",border:"none",borderRadius:3,cursor:"pointer",fontFamily:sans}}>
⊜ step.evaluator
</button>
<button onClick={()=>setView("extractor")}
style={{padding:"4px 10px",fontSize:10,fontWeight:600,color:view==="extractor"?c.text:c.textSec,background:view==="extractor"?c.bgHover:"transparent",border:"none",borderRadius:3,cursor:"pointer",fontFamily:sans}}>
◈ step.claim_extractor
</button>
</div>
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// EVALUATOR EXPANDED VIEW
// ═══════════════════════════════════════════════════════════════
function EvaluatorExpandedView() {
const [tab, setTab] = useState("outcomes");
const [mode, setMode] = useState("Gate + iteration");
const [humanReviewEnabled, setHumanReviewEnabled] = useState(true);
const [gatheringEnabled, setGatheringEnabled] = useState(true);
const [expandedOutcome, setExpandedOutcome] = useState("o2");
// ─── Conditional port computation (driven by Mode + toggles) ───
const isGate = mode.includes("Gate") || mode.includes("Pass/fail");
const hasIteration = mode.includes("iteration") || mode.includes("loop");
const isScored = mode.includes("Scored") || mode.includes("Score");
const ports = {
inputs: [
{label:"subject_in", role:"data·required", color:c.accent, enabled:true},
{label:"reference_in", role:"data·optional", color:c.accent, enabled:true},
{label:"evidence_in.N", role:"data·optional·expandable", color:c.accent, enabled:true},
{label:"claims_in", role:"data·optional", color:c.accent, enabled:true, condition:"from claim_extractor"},
{label:"human_response_in", role:"data·optional", color:c.amber, enabled:humanReviewEnabled, condition:"Human Review on"},
],
outputs: [
{label:"verdict_out", role:"data·always", color:c.cyan, enabled:true},
{label:"passed_out", role:"signal+data", color:c.green, enabled:isGate},
{label:"failed_out", role:"signal+data", color:c.red, enabled:isGate},
{label:"needs_revision_out", role:"signal+data", color:c.amber, enabled:hasIteration, condition:"Iteration mode"},
{label:"feedback_out", role:"data", color:c.purple, enabled:true},
{label:"human_review_out", role:"signal+data", color:c.amber, enabled:humanReviewEnabled, condition:"Human Review on"},
{label:"needs_more_info_out", role:"signal+data", color:c.cyan, enabled:gatheringEnabled, condition:"Gathering on"},
{label:"gathered_out", role:"data", color:c.cyan, enabled:gatheringEnabled, condition:"Gathering on"},
{label:"scores_out", role:"data", color:c.purple, enabled:isScored, condition:"Scored mode"},
{label:"signal_out", role:"signal·always", color:c.amber, enabled:true},
{label:"error_out", role:"signal+data", color:c.red, enabled:true},
],
};
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 20px",background:c.bgPanel}}>
<div style={{display:"flex",alignItems:"center",gap:12}}>
<span style={{fontSize:18}}>⊜</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:10}}><Pill color={c.green}>VERIFIED · 7/8 outcomes</Pill></span>
<span><Pill color={c.amber}>iteration 2 of 3</Pill></span>
</div>
<div style={{display:"flex",gap:8,alignItems:"center"}}>
<Btn ghost>Load Outcome Set</Btn>
<Btn ghost>Save as Preset…</Btn>
<Btn primary>▶ Run Evaluation</Btn>
<span style={{fontSize:18,color:c.textSec,cursor:"pointer",padding:"0 4px",marginLeft:4}}>✕</span>
</div>
</div>
{/* Tab bar */}
<div style={{height:36,minHeight:36,borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",padding:"0 16px",gap:4,background:c.bg}}>
{[
{id:"outcomes", label:"Outcomes", count:8},
{id:"context", label:"Context & Sources"},
{id:"human", label:"Human Review"},
{id:"iteration", label:"Iteration"},
{id:"routing", label:"Routing & Ports"},
{id:"results", label:"Last Results"},
].map(t=>(
<button key={t.id} onClick={()=>setTab(t.id)}
style={{padding:"8px 14px",fontSize:11,fontWeight:600,color:tab===t.id?c.text:c.textSec,background:"transparent",border:"none",borderBottom:tab===t.id?`2px solid ${c.accent}`:"2px solid transparent",cursor:"pointer",fontFamily:sans,display:"flex",alignItems:"center",gap:6}}>
{t.label}
{t.count && <span style={{fontSize:9,color:c.textTer,background:c.bgHover,padding:"1px 5px",borderRadius:8}}>{t.count}</span>}
</button>
))}
</div>
{/* Body */}
<div style={{flex:1,display:"flex",overflow:"hidden"}}>
{/* Left sidebar — common config */}
<div style={{width:260,minWidth:260,borderRight:`1px solid ${c.border}`,padding:16,overflowY:"auto",background:c.bgPanel}}>
<DL>Evaluator Mode</DL>
<DS opts={[
"Pure gate (pass/fail)",
"Scored evaluation",
"Gate + iteration",
"Multi-output (everything)",
"Compare candidates",
]} v={mode} onChange={e=>setMode(e.target.value)} />
<div style={{fontSize:9,color:c.textTer,marginTop:6,fontFamily:sans,lineHeight:1.5,fontStyle:"italic"}}>
Mode controls which output ports are exposed below.
</div>
<DL mt>Toggles</DL>
<DC label="Enable Human Review escalation" checked={humanReviewEnabled} onChange={()=>setHumanReviewEnabled(!humanReviewEnabled)} />
<DC label="Enable active gathering (research)" checked={gatheringEnabled} onChange={()=>setGatheringEnabled(!gatheringEnabled)} />
<DC label="Inside a Loop body (auto-detected)" checked={true} />
<DL mt>Decision Policy</DL>
<DS opts={["All required outcomes pass","Aggregate score ≥ threshold","Custom expression"]} v="All required outcomes pass" />
<DL mt>On Failure</DL>
<DS opts={["Iterate (route to revision loop)","Escalate to human","Mark failed, no retry"]} v="Iterate (route to revision loop)" />
<DL mt>Max Iterations</DL>
<div style={{display:"flex",gap:6,alignItems:"center"}}>
<DI value="3" />
<span style={{fontSize:9,color:c.textTer,whiteSpace:"nowrap"}}>then escalate</span>
</div>
<div style={{borderTop:`1px solid ${c.border}`,marginTop:14,paddingTop:12}}>
<DL>Connected Inputs</DL>
<div style={{display:"flex",flexDirection:"column",gap:5,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,fontSize:9}}>subject_in</span>
<span style={{color:c.textSec,marginLeft:6}}>← ◆ Draft Brief.data_out</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,fontSize:9}}>evidence_in.1</span>
<span style={{color:c.textSec,marginLeft:6}}>← ◇ Case Record</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,fontSize:9}}>claims_in</span>
<span style={{color:c.textSec,marginLeft:6}}>← ◈ Brief Claims.claims_out</span>
</div>
</div>
</div>
<div style={{borderTop:`1px solid ${c.border}`,marginTop:14,paddingTop:12}}>
<DL>Outcome Set Preset</DL>
<DS opts={[
"(custom — unsaved)",
"Legal Brief Review (system)",
"My Legal Brief v2 (modified)",
"Research Summary (system)",
"Save as new…",
"Manage presets…",
]} v="My Legal Brief v2 (modified)" />
<div style={{fontSize:9,color:c.amber,marginTop:4,fontFamily:sans}}>✎ Modified — Save as new or Update</div>
</div>
<div style={{borderTop:`1px solid ${c.border}`,marginTop:14,paddingTop:12}}>
<DL>Last Run</DL>
<div style={{fontSize:10,color:c.textSec,lineHeight:1.6,fontFamily:sans}}>
<div>Iteration 2 of 3</div>
<div>7 of 8 outcomes verified</div>
<div>Cost: $0.34 · 12.4s</div>
<div style={{color:c.amber,marginTop:4}}>↺ Currently in revision loop</div>
</div>
</div>
</div>
{/* Main area — tab content */}
<div style={{flex:1,overflow:"auto",background:c.bg}}>
{tab === "outcomes" && <OutcomesTab expandedOutcome={expandedOutcome} setExpandedOutcome={setExpandedOutcome} />}
{tab === "context" && <ContextTab />}
{tab === "human" && <HumanReviewTab />}
{tab === "iteration" && <IterationTab />}
{tab === "routing" && <RoutingTab ports={ports} />}
{tab === "results" && <ResultsTab />}
</div>
</div>
{/* Bottom port bar — shows currently enabled ports */}
<div style={{borderTop:`1px solid ${c.border}`,padding:"8px 16px",background:c.bgPanel,minHeight:80}}>
<div style={{display:"flex",gap:24,alignItems:"flex-start"}}>
<div style={{flex:1}}>
<div style={{fontSize:9,fontWeight:600,color:c.textTer,letterSpacing:0.3,marginBottom:6,fontFamily:sans}}>INPUT PORTS</div>
<div style={{display:"flex",gap:6,flexWrap:"wrap"}}>
{ports.inputs.map((p,i)=><Port key={i} {...p} />)}
</div>
</div>
<div style={{flex:2}}>
<div style={{fontSize:9,fontWeight:600,color:c.textTer,letterSpacing:0.3,marginBottom:6,fontFamily:sans}}>OUTPUT PORTS</div>
<div style={{display:"flex",gap:6,flexWrap:"wrap"}}>
{ports.outputs.map((p,i)=><Port key={i} {...p} />)}
</div>
</div>
</div>
<div style={{fontSize:9,color:c.textTer,marginTop:8,fontStyle:"italic",fontFamily:sans}}>
Ports auto-populate based on Mode + toggles. Faded ports are inactive in current config; hover for the gating condition.
</div>
</div>
</div>
);
}
// ─── Outcomes tab ───
function OutcomesTab({expandedOutcome, setExpandedOutcome}) {
const outcomes = [
{id:"o1", name:"Citations are present", desc:"Brief contains at least the citations required to support the argument.", mode:"binary", required:true, method:"automated_check", status:"verified"},
{id:"o2", name:"Citations resolve to real cases", desc:"All cases cited resolve to real cases in the casedb. The citation-checker sub-agent verifies each citation by looking it up; cases not found within 3 lookup attempts return as failures with the case name and search attempts logged.", mode:"binary", required:true, method:"subagent_verification", status:"failed", evidenceRole:"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.", mode:"count", required:true, method:"claim_verification", status:"failed", countTotal:8, countPassed:6, evidenceRole:"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.", mode:"binary", required:true, method:"claim_verification", status:"verified", evidenceRole:"required"},
{id:"o5", name:"Brief addresses all motion issues", desc:"The motion to dismiss raised 7 issues; the brief addresses each.", mode:"count", required:true, method:"judge_reasoning", status:"verified", countTotal:7, countPassed:7, evidenceRole:"informs"},
{id:"o6", name:"Argument is persuasive", desc:"The brief constructs a persuasive case for the client's position, addressing counterarguments and supporting reasoning with cited authority.", mode:"scored", required:false, method:"judge_reasoning", status:"verified", score:0.78, threshold:0.7, decomposed:true, evidenceRole:"not_used"},
{id:"o7", name:"Tone is professional", desc:"Tone is consistent with court filing standards.", mode:"scored", required:false, method:"judge_reasoning", status:"verified", score:0.85, threshold:0.7, evidenceRole:"not_used"},
{id:"o8", name:"Brief between 4000-8000 words", desc:"Word count falls within the court's required range.", mode:"binary", required:true, method:"automated_check", status:"verified", evidenceRole:"not_used"},
];
return (
<div style={{padding:20}}>
{/* Action bar */}
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:16}}>
<div>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Outcomes</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>
Each outcome defines what success looks like + how to verify it. Free-text description IS the operative spec.
</div>
</div>
<div style={{display:"flex",gap:8}}>
<Btn ghost>↑ Reorder</Btn>
<Btn accent>+ Add Outcome</Btn>
</div>
</div>
{/* Outcome cards */}
<div style={{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)} />
))}
</div>
<div style={{marginTop:12}}>
<button style={{width:"100%",padding:"10px",background:"transparent",border:`1px dashed ${c.border}`,borderRadius:6,color:c.textSec,fontSize:11,cursor:"pointer",fontFamily:sans}}>
+ Add another outcome
</button>
</div>
</div>
);
}
function OutcomeCard({outcome, expanded, onToggle}) {
const methodLabels = {
automated_check: "Automated Check",
claim_verification: "Claim Verification",
subagent_verification: "Sub-Agent Verification",
judge_reasoning: "Judge Reasoning (LLM)",
hybrid: "Hybrid",
};
return (
<div style={{background:c.bgCard,border:`1px solid ${expanded?c.accent+"40":c.border}`,borderRadius:6,overflow:"hidden",transition:"border 0.15s"}}>
{/* Card header (collapsed view) */}
<div onClick={onToggle} style={{padding:"10px 14px",cursor:"pointer",display:"flex",alignItems:"center",gap:10}}>
<span style={{color:c.textTer,fontSize:10,minWidth:8}}>{expanded?"▾":"▸"}</span>
<div style={{flex:1,minWidth:0}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:3}}>
<span style={{fontSize:12,fontWeight:600,color:c.text}}>{outcome.name}</span>
<VerdictBadge mode={outcome.mode} />
{outcome.required ? <Pill color={c.amber}>REQUIRED</Pill> : <Pill color={c.textTer}>DESIRED</Pill>}
</div>
<div style={{fontSize:10,color:c.textSec,lineHeight:1.4,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>
{outcome.desc}
</div>
</div>
<div style={{display:"flex",flexDirection:"column",alignItems:"flex-end",gap:4}}>
<StatusBadge status={outcome.status} />
{outcome.mode==="count" && (
<span style={{fontSize:10,color:c.textSec,fontFamily:mono}}>{outcome.countPassed}/{outcome.countTotal}</span>
)}
{outcome.mode==="scored" && (
<span style={{fontSize:10,color:outcome.score>=outcome.threshold?c.green:c.amber,fontFamily:mono}}>
{outcome.score?.toFixed(2)} / {outcome.threshold?.toFixed(2)}
</span>
)}
</div>
</div>
{/* Expanded card body */}
{expanded && (
<div style={{borderTop:`1px solid ${c.border}`,padding:"14px 16px",background:c.bg}}>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:20}}>
{/* LEFT: Definition + Decomposition */}
<div>
<DL>Name</DL>
<DI value={outcome.name} />
<DL mt>Success Description (the operative spec)</DL>
<DT value={outcome.desc} h={80} placeholder="What does success look like? Free text — this drives the verification." />
<div style={{fontSize:9,color:c.textTer,marginTop:4,fontFamily:sans,fontStyle:"italic",lineHeight:1.5}}>
This description IS the operative spec. It's routed into the verification method below (as the judge's rubric, the sub-agent's criterion, or the claim verification's success rule).
</div>
<DL mt>Verdict Mode</DL>
<div style={{display:"flex",gap:6}}>
{["binary","count","scored"].map(m => (
<button key={m} style={{
flex:1,padding:"7px 0",fontSize:10,fontWeight:600,
background:outcome.mode===m?(m==="binary"?c.accent:m==="count"?c.cyan:c.purple)+"15":c.bgCard,
color:outcome.mode===m?(m==="binary"?c.accent:m==="count"?c.cyan:c.purple):c.textSec,
border:`1px solid ${outcome.mode===m?(m==="binary"?c.accent:m==="count"?c.cyan:c.purple)+"40":c.border}`,
borderRadius:4,cursor:"pointer",fontFamily:sans,letterSpacing:0.3,textTransform:"uppercase"
}}>{m}</button>
))}
</div>
<div style={{fontSize:9,color:c.textTer,marginTop:4,fontStyle:"italic",lineHeight:1.5}}>
{outcome.mode==="binary" && "Pure yes/no. Either the criterion holds or it doesn't."}
{outcome.mode==="count" && "Decompose into checkpoints; outcome passes when threshold (all / N of M) met."}
{outcome.mode==="scored" && "0-1 or 1-5 scale with anchors. Use for continuum judgments."}
</div>
<div style={{display:"flex",gap:8,marginTop:12}}>
<div style={{flex:1}}>
<DC label="Required (must pass for verdict)" checked={outcome.required} />
</div>
<div style={{flex:1}}>
<DC label="Inverse (must NOT be true)" checked={false} />
</div>
</div>
{/* Decomposition (only when scored + decomposed, or count) */}
{(outcome.decomposed || outcome.mode==="count") && (
<div style={{marginTop:14,padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8}}>
<div style={{fontSize:10,fontWeight:700,color:c.purple,letterSpacing:0.3,fontFamily:sans}}>
{outcome.mode==="count"?"CHECKPOINTS":"DECOMPOSITION"}
</div>
<Btn ghost>⚡ Help me build this</Btn>
</div>
{outcome.mode==="count" ? (
<div style={{display:"flex",flexDirection:"column",gap:5}}>
{[
{label:"Issue 1: Standing",status:"verified"},
{label:"Issue 2: Subject matter jurisdiction",status:"verified"},
{label:"Issue 3: Failure to state a claim",status:"verified"},
{label:"Issue 4: Statute of limitations",status:"failed"},
{label:"Issue 5: Heightened pleading (9(b))",status:"verified"},
{label:"Issue 6: Loss causation",status:"verified"},
{label:"Issue 7: Materiality",status:"verified"},
].map((cp,i)=>(
<div key={i} style={{display:"flex",alignItems:"center",justifyContent:"space-between",padding:"4px 8px",background:c.bg,borderRadius:3}}>
<span style={{fontSize:10,color:c.text}}>{cp.label}</span>
<StatusBadge status={cp.status} />
</div>
))}
<Btn ghost>+ Add Checkpoint</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:"Uses clear, accessible language",weight:0.5,score:0.85},
].map((sc,i)=>(
<div key={i} style={{padding:"6px 8px",background:c.bg,borderRadius:3}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:3}}>
<span style={{fontSize:10,color:c.text}}>{sc.label}</span>
<span style={{fontSize:10,color:sc.score>=0.7?c.green:c.amber,fontFamily:mono}}>{sc.score.toFixed(2)}</span>
</div>
<div style={{display:"flex",alignItems:"center",gap:8,fontSize:9,color:c.textTer}}>
<span>weight {sc.weight}</span>
<span>·</span>
<span style={{cursor:"pointer",color:c.accent}}>configure</span>
</div>
</div>
))}
<DL mt>Roll-Up</DL>
<DS opts={["all_must_pass","weighted_average","min_score","checkpoint_count"]} v="weighted_average" />
</div>
)}
</div>
)}
</div>
{/* RIGHT: Verification method + policies */}
<div>
<DL>Verification Method</DL>
<DS opts={[
"Automated Check (mechanical)",
"Claim Verification (extract + verify)",
"Sub-Agent Verification (specialist)",
"Judge Reasoning (LLM rubric)",
"Hybrid (combine multiple)",
]} v={methodLabels[outcome.method]} />
<div style={{fontSize:9,color:c.textTer,marginTop:4,fontStyle:"italic",lineHeight:1.5}}>
The success description above is auto-routed into this method's operative prompt. Customize below to override.
</div>
{/* Conditional method config — sub-agent verification example */}
{outcome.method==="subagent_verification" && (
<div style={{marginTop:10,padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DL>Specialist Agent</DL>
<DS opts={["citation-checker","fact-checker","legal-research","custom..."]} v="citation-checker" />
<DL mt>Tools Allowed</DL>
<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>
</div>
<DL mt>Instruction Override</DL>
<DT value="" h={50} placeholder="(Optional) Override the auto-generated instruction from the success description above." />
</div>
)}
{outcome.method==="claim_verification" && (
<div style={{marginTop:10,padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DL>Claim Types (from upstream extractor)</DL>
<div style={{display:"flex",gap:5,flexWrap:"wrap",marginBottom:4}}>
<Tag color={c.accent}>CaseCitation</Tag>
<Tag color={c.accent}>LegalProposition</Tag>
</div>
<DL mt>Verification Source</DL>
<DS opts={["evidence_in","reference_in","agent_retrieved","hybrid"]} v="evidence_in" />
<DL mt>Min Pass Rate</DL>
<DI value="0.95" />
<div style={{fontSize:9,color:c.textTer,marginTop:6,fontFamily:sans,lineHeight:1.5}}>
Claim types come from <span style={{color:c.cyan}}>◈ Brief Claims</span> upstream. Add types via the extractor module.
</div>
</div>
)}
{outcome.method==="judge_reasoning" && (
<div style={{marginTop:10,padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DL>Judging Agent</DL>
<DS opts={["(task default)","Elnor","Nova","Atlas","Sage"]} v="(task default)" />
<DL mt>Rubric Preset</DL>
<DS opts={["(custom)","Quality of reasoning (system)","Professional writing (system)","My legal-arg rubric","Save as new…"]} v="My legal-arg rubric" />
<div style={{display:"flex",gap:6,marginTop:6}}>
<Btn ghost>Edit Rubric</Btn>
<Btn ghost>Save As…</Btn>
</div>
<DL mt>Anchored Scale</DL>
<DS opts={["binary","1-5 with anchors","0-1 continuous"]} v="1-5 with anchors" />
<DC label="Require evidence citation in rationale" checked={true} />
<DC label="Use ensemble (3 judges)" checked={false} />
<DL mt>Reference Exemplars</DL>
<div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
<Tag color={c.green}>✓ Strong example (score 5)</Tag>
<Tag color={c.amber}>~ Mid example (score 3)</Tag>
</div>
</div>
)}
{outcome.method==="automated_check" && (
<div style={{marginTop:10,padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DL>Check Type</DL>
<DS opts={["length","regex","format_match","schema_validate","file_exists","url_reachable","code_eval"]} v="length" />
<div style={{display:"flex",gap:8,marginTop:6}}>
<div style={{flex:1}}>
<DL>Min</DL>
<DI value="4000" />
</div>
<div style={{flex:1}}>
<DL>Max</DL>
<DI value="8000" />
</div>
</div>
<div style={{fontSize:9,color:c.green,marginTop:6,fontFamily:sans,lineHeight:1.5}}>
✓ Parameters auto-extracted from your description "between 4000-8000 words"
</div>
</div>
)}
{/* Evidence policy */}
<div style={{marginTop:14}}>
<DL>Evidence Policy</DL>
<DS opts={[
"Required for verification",
"Informs verification (used if available)",
"Audit only (logged, not used)",
"Not used",
]} v={outcome.evidenceRole==="required"?"Required for verification":outcome.evidenceRole==="informs"?"Informs verification (used if available)":outcome.evidenceRole==="audit_only"?"Audit only (logged, not used)":"Not used"} />
</div>
{/* Iteration policy */}
<div style={{marginTop:10}}>
<DL>Iteration Policy (when in Loop)</DL>
<DS opts={[
"Re-verify always",
"Re-verify if subject changed",
"Re-verify only if previously failed",
"Verify once (stable thereafter)",
]} v="Re-verify if subject changed" />
</div>
{/* Gather policy */}
<div style={{marginTop:10}}>
<DL>Gather Policy (when incomplete)</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DC label="Allow external research (web/casedb)" checked={true} />
<DC label="Allow memory query (DOC72/DOC73)" checked={true} />
<DC label="Allow user prompt (needs_more_info_out)" checked={true} />
<div style={{display:"flex",gap:8,marginTop:6}}>
<div style={{flex:1}}>
<DL>Max Gather Steps</DL>
<DI value="3" />
</div>
<div style={{flex:1}}>
<DL>Max Cost</DL>
<DI value="$0.50" />
</div>
</div>
</div>
</div>
{/* Threshold */}
<div style={{marginTop:10}}>
<DL>Threshold / On-Below</DL>
<div style={{display:"flex",gap:8}}>
<div style={{flex:1}}>
<DI value="0.7" placeholder="Pass at" />
</div>
<div style={{flex:2}}>
<DS opts={["fail","revise","human_review","warn_only","incomplete"]} v="revise" />
</div>
</div>
</div>
{/* Per-outcome cost cap */}
<div style={{marginTop:10}}>
<DL>Max Cost (this outcome)</DL>
<DI value="$0.25" />
</div>
{/* Prerequisites */}
<div style={{marginTop:10}}>
<DL>Prerequisite Outcomes</DL>
<div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
<Tag color={c.cyan} removable>Citations are present</Tag>
</div>
<div style={{fontSize:9,color:c.textTer,marginTop:4,fontStyle:"italic"}}>
If prereqs unmet → outcome marked N/A (not run).
</div>
</div>
</div>
</div>
{/* Last result for this outcome */}
{outcome.status==="failed" && (
<div style={{marginTop:14,padding:12,background:`${c.red}08`,borderRadius:6,border:`1px solid ${c.red}25`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8}}>
<div style={{fontSize:11,fontWeight:700,color:c.red,fontFamily:sans}}>✕ FAILED — Last iteration (iteration 2)</div>
<span style={{fontSize:10,color:c.textSec}}>{outcome.mode==="count"?`${outcome.countPassed}/${outcome.countTotal}`:"—"}</span>
</div>
<div style={{fontSize:10,color:c.text,lineHeight:1.5}}>
{outcome.id==="o2" ? (
<>
<div style={{marginBottom:6}}>3 citations did not resolve via the citation-checker sub-agent:</div>
<div style={{display:"flex",flexDirection:"column",gap:4,marginLeft:14}}>
<div>• <code style={{color:c.amber}}>Smith v. Jones, 432 F.3d 12 (5th Cir. 2007)</code> — case not found in casedb after 3 lookup attempts</div>
<div>• <code style={{color:c.amber}}>In re Acme Corp., 215 B.R. 89 (Bankr. S.D.N.Y. 1997)</code> — citation format valid, no matching case</div>
<div>• <code style={{color:c.amber}}>Doe v. Smith</code> — citation incomplete (no reporter / year)</div>
</div>
<div style={{marginTop:8,fontSize:9,color:c.textSec,fontStyle:"italic"}}>
Feedback emitted to next iteration: "Re-verify citation sources for §III paragraph 4; consider removing citations that cannot be verified."
</div>
</>
) : (
<div>Issue 4 (Statute of limitations) not addressed in brief. The motion to dismiss raised this at §IV; the brief currently has no response. Iteration 2's draft addressed it in passing but without sufficient treatment.</div>
)}
</div>
</div>
)}
</div>
)}
</div>
);
}
// ─── Context & Sources tab ───
function ContextTab() {
return (
<div style={{padding:20}}>
<div style={{marginBottom:16}}>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Context & Sources</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>
Provide reference materials, evidence, libraries, and free-text guidance beyond the outcomes themselves.
</div>
</div>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:20}}>
{/* LEFT column */}
<div style={{display:"flex",flexDirection:"column",gap:16}}>
{/* Subject (read-only from wiring) */}
<div>
<DL>Subject (from wiring)</DL>
<div style={{padding:"8px 10px",background:`${c.accent}08`,border:`1px solid ${c.accent}30`,borderRadius:4}}>
<div style={{fontSize:11,color:c.accent,fontFamily:mono}}>subject_in ← ◆ Draft Brief.data_out</div>
<div style={{fontSize:10,color:c.textSec,marginTop:3,fontFamily:sans}}>Type: ContextBundle · Current size: ~6,200 words</div>
</div>
</div>
{/* Reference materials */}
<UploadZone
label="Reference Materials (uploadable)"
hint="Style guides, templates, expected output patterns. Wired into reference_in."
files={[
{icon:"📄",name:"Bluebook 21st Ed. — Citation Format.pdf",size:"4.2 MB"},
{icon:"📄",name:"Schall Firm Brief Template v3.docx",size:"380 KB"},
]}
/>
{/* Evidence sources */}
<UploadZone
label="Evidence Sources"
hint="Documents that ground factual claims. Wired into evidence_in.N."
files={[
{icon:"📁",name:"Paramount v. City of LA — Record",size:"487 docs"},
{icon:"📄",name:"Motion to Dismiss (May 4, 2026).pdf",size:"1.2 MB"},
]}
/>
{/* Document libraries / connectors */}
<div>
<DL>Document Libraries & Connectors</DL>
<div style={{display:"flex",flexDirection:"column",gap:6}}>
{[
{label:"DOC73 PBE: Securities Litigation Library", icon:"📚", status:"connected", color:c.green},
{label:"DOC25: Matter file (auto-discovered)", icon:"🗂", status:"connected", color:c.green},
{label:"CourtListener API", icon:"⚖", status:"connected", color:c.green},
{label:"Westlaw connector", icon:"⚡", status:"available", color:c.textTer},
{label:"+ Add connector...", icon:"+", status:null, color:c.accent},
].map((l,i)=>(
<div key={i} style={{display:"flex",alignItems:"center",justifyContent:"space-between",padding:"7px 9px",background:c.bgCard,borderRadius:4,border:`1px solid ${c.border}`,cursor:"pointer"}}>
<span style={{display:"flex",alignItems:"center",gap:8,fontSize:11,color:l.label.startsWith("+")?c.accent:c.text}}>
<span style={{fontSize:13}}>{l.icon}</span>
<span>{l.label}</span>
</span>
{l.status && <Pill color={l.color}>{l.status}</Pill>}
</div>
))}
</div>
</div>
</div>
{/* RIGHT column */}
<div style={{display:"flex",flexDirection:"column",gap:16}}>
{/* Custom guidance */}
<div>
<DL>Custom Guidance / Hints</DL>
<div style={{fontSize:10,color:c.textTer,marginBottom:6,fontStyle:"italic",lineHeight:1.5}}>
Free-text instructions that apply to ALL outcome verifications. Use for cross-cutting guidance the outcomes don't capture: tone, level of strictness, special considerations, etc.
</div>
<DT value="Apply strict Bluebook 21st edition standards for citation format. When evaluating tone, prefer formal legal register over conversational style. For the citation accuracy outcome, do NOT downgrade to indeterminate just because a case is from a state court reporter — search Westlaw if CourtListener fails." h={120} placeholder="(Optional) Additional guidance that applies to all verifications…" />
</div>
{/* Per-method extra context */}
<div>
<DL>Per-Method Guidance</DL>
<div style={{display:"flex",flexDirection:"column",gap:8}}>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:6}}>
<span style={{fontSize:11,fontWeight:600,color:c.accent}}>Sub-Agent Verifications</span>
<Btn ghost>Expand</Btn>
</div>
<DT value="Citation checker: when a case is not found, try the alternative reporter (e.g., F.2d ↔ F.3d) before giving up." h={50} />
</div>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:6}}>
<span style={{fontSize:11,fontWeight:600,color:c.purple}}>Judge Reasoning</span>
<Btn ghost>Expand</Btn>
</div>
<DT value="" h={50} placeholder="(Optional) Additional guidance for all judge-reasoning outcomes." />
</div>
</div>
</div>
{/* Context budget */}
<div>
<DL>Context Budget</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:8}}>
<span style={{fontSize:10,color:c.textSec}}>Per-verification tokens</span>
<span style={{fontSize:10,color:c.text,fontFamily:mono}}>2,400 / 4,000</span>
</div>
<div style={{height:6,background:c.bg,borderRadius:3,overflow:"hidden"}}>
<div style={{width:"60%",height:"100%",background:c.accent,borderRadius:3}} />
</div>
<div style={{fontSize:9,color:c.textTer,marginTop:6,lineHeight:1.5,fontStyle:"italic"}}>
Subject + reference + evidence + custom guidance must fit within the per-verification budget. Overflow → StorageRef-backed retrieval.
</div>
</div>
</div>
{/* Inherit task context */}
<div>
<DC label="Inherit task-level context (Global Instructions, task entities)" checked={false} />
<div style={{fontSize:9,color:c.textTer,marginTop:4,marginLeft:22,fontStyle:"italic",lineHeight:1.5}}>
By default, evaluator-mode CIL strips task Globals from verifier dispatches (per R4.1 §A3.7). Enable to inherit task-level evaluation guidance specifically.
</div>
</div>
</div>
</div>
</div>
);
}
// ─── Human Review tab ───
function HumanReviewTab() {
return (
<div style={{padding:20}}>
<div style={{marginBottom:16,display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Human Review Integration</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>
When the evaluator needs human judgment, instructions, more research, or answers — route through here.
</div>
</div>
<Pill color={c.amber}>HUMAN REVIEW ON</Pill>
</div>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:20}}>
{/* LEFT — when to escalate */}
<div style={{display:"flex",flexDirection:"column",gap:14}}>
<div>
<DL>When to Escalate to Human</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,display:"flex",flexDirection:"column",gap:4}}>
<DC label="On any required outcome failure (after iteration cap)" checked={true} />
<DC label="On regression (an outcome that passed now fails)" checked={true} />
<DC label="On stuck failure (same outcome fails N times)" checked={true} />
<DC label="When gathering exhausted and outcome still incomplete" checked={true} />
<DC label="When inter-rater disagreement > threshold" checked={false} />
<DC label="When estimated cost exceeds budget" checked={true} />
<DC label="On user-marked outcomes (always escalate, even if pass)" checked={false} />
</div>
</div>
<div>
<DL>Stuck Failure Threshold</DL>
<div style={{display:"flex",gap:8,alignItems:"center"}}>
<DI value="3" />
<span style={{fontSize:10,color:c.textSec,whiteSpace:"nowrap"}}>iterations failing same outcome</span>
</div>
</div>
<div>
<DL>Review Presentation</DL>
<DS opts={[
"Compact (verdict + failure summary)",
"Full (verdict + per-outcome detail + audit trail)",
"Diff (changes since last review)",
]} v="Full (verdict + per-outcome detail + audit trail)" />
</div>
<div>
<DL>Where to Notify</DL>
<div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
<Tag color={c.green}>Dashboard</Tag>
<Tag color={c.green}>Discord (Schall Firm)</Tag>
<Tag color={c.green} removable>Email (will@schall.com)</Tag>
<Tag color={c.textTer} removable>+ Add channel</Tag>
</div>
</div>
<div>
<DL>Default Review Mode</DL>
<DS opts={[
"Standard review (approve / request revisions)",
"Annotated review (inline comments + instructions)",
"Question framework (evaluator asks; user answers)",
"Open dialog (free-form back and forth)",
]} v="Question framework (evaluator asks; user answers)" />
</div>
</div>
{/* RIGHT — Question framework preview */}
<div>
<DL>Question Framework Preview</DL>
<div style={{padding:14,background:c.bgPanel,borderRadius:8,border:`1px solid ${c.border}`}}>
<div style={{fontSize:11,color:c.textSec,marginBottom:10,lineHeight:1.5,fontFamily:sans}}>
When escalating, the evaluator can ask specific questions to the user. The user's answers route back via <span style={{color:c.amber,fontFamily:mono}}>human_response_in</span> and inform the next iteration.
</div>
<div style={{padding:10,background:c.bg,borderRadius:6,border:`1px solid ${c.border}`,marginBottom:8}}>
<div style={{fontSize:10,color:c.amber,fontWeight:600,marginBottom:6,letterSpacing:0.3}}>EVALUATOR ASKS</div>
<div style={{fontSize:11,color:c.text,marginBottom:8,lineHeight:1.5}}>
Three citations could not be verified after 3 lookup attempts. How should I proceed?
</div>
<div style={{display:"flex",flexDirection:"column",gap:5}}>
<button style={{padding:"7px 10px",background:c.bgCard,border:`1px solid ${c.border}`,borderRadius:4,fontSize:10,color:c.text,cursor:"pointer",fontFamily:sans,textAlign:"left"}}>
◯ Remove the unverified citations
</button>
<button style={{padding:"7px 10px",background:c.bgCard,border:`1px solid ${c.border}`,borderRadius:4,fontSize:10,color:c.text,cursor:"pointer",fontFamily:sans,textAlign:"left"}}>
◯ Approve them as-is (override)
</button>
<button style={{padding:"7px 10px",background:c.bgCard,border:`1px solid ${c.border}`,borderRadius:4,fontSize:10,color:c.text,cursor:"pointer",fontFamily:sans,textAlign:"left"}}>
◯ Try alternative source (Westlaw)
</button>
<button style={{padding:"7px 10px",background:c.bgCard,border:`1px solid ${c.border}`,borderRadius:4,fontSize:10,color:c.text,cursor:"pointer",fontFamily:sans,textAlign:"left"}}>
◯ Other (provide instructions below)
</button>
</div>
<DL mt>Your Instructions</DL>
<DT value="" h={50} placeholder="Free-text instructions for the next iteration…" />
</div>
<div style={{display:"flex",gap:6,justifyContent:"flex-end"}}>
<Btn ghost>Skip (mark indeterminate)</Btn>
<Btn accent>Send Answer</Btn>
</div>
</div>
<DL mt>Question Types Available</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,display:"flex",flexDirection:"column",gap:4}}>
<DC label="Multiple choice (evaluator proposes options)" checked={true} />
<DC label="Free-text revision instructions" checked={true} />
<DC label="Approve / reject with annotation" checked={true} />
<DC label="Provide additional documents/references" checked={true} />
<DC label="Answer factual question (evaluator can't determine)" checked={true} />
<DC label="Modify outcomes / criteria for this run" checked={false} />
</div>
<DL mt>Timeout</DL>
<div style={{display:"flex",gap:8,alignItems:"center"}}>
<DI value="4" />
<DS opts={["hours","days","weeks","no timeout"]} v="hours" />
<DS opts={["then continue without","then fail","then notify again"]} v="then notify again" />
</div>
</div>
</div>
{/* Q&A history for current run */}
<div style={{marginTop:20,padding:14,background:c.bgPanel,borderRadius:8,border:`1px solid ${c.border}`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:10}}>
<div style={{fontSize:12,fontWeight:700,color:c.text,fontFamily:sans}}>Q&A History — This Run</div>
<Pill color={c.amber}>1 PENDING</Pill>
</div>
<div style={{display:"flex",flexDirection:"column",gap:8}}>
<div style={{padding:10,background:c.bgCard,borderRadius:6,borderLeft:`3px solid ${c.green}`}}>
<div style={{display:"flex",justifyContent:"space-between",marginBottom:4}}>
<span style={{fontSize:10,color:c.green,fontWeight:600}}>✓ ANSWERED · iteration 1 · 8:42 AM</span>
<span style={{fontSize:9,color:c.textTer}}>Q1</span>
</div>
<div style={{fontSize:10,color:c.text,marginBottom:4}}>Q: Should statute of limitations be addressed as a primary defense or as a backup?</div>
<div style={{fontSize:10,color:c.textSec,fontStyle:"italic"}}>A: Primary defense. The motion specifically pleads SOL as the first ground.</div>
</div>
<div style={{padding:10,background:c.bgCard,borderRadius:6,borderLeft:`3px solid ${c.amber}`}}>
<div style={{display:"flex",justifyContent:"space-between",marginBottom:4}}>
<span style={{fontSize:10,color:c.amber,fontWeight:600}}>○ PENDING · iteration 2 · 9:18 AM</span>
<span style={{fontSize:9,color:c.textTer}}>Q2</span>
</div>
<div style={{fontSize:10,color:c.text,marginBottom:6}}>Q: Three citations could not be verified after 3 lookup attempts. How should I proceed?</div>
<Btn accent>Answer Now</Btn>
</div>
</div>
</div>
</div>
);
}
// ─── Iteration tab ───
function IterationTab() {
return (
<div style={{padding:20}}>
<div style={{marginBottom:16}}>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Iteration & Loop Integration</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>
This evaluator is wired inside a Loop Controller. Configure how iteration carryforward works.
</div>
</div>
{/* Loop status */}
<div style={{padding:14,background:`${c.amber}08`,border:`1px solid ${c.amber}30`,borderRadius:8,marginBottom:16}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div>
<div style={{fontSize:12,fontWeight:700,color:c.amber,fontFamily:sans,marginBottom:4}}>↺ Inside Loop body — Pattern E (Loop + Evaluator)</div>
<div style={{fontSize:11,color:c.text,fontFamily:mono,lineHeight:1.6}}>
Loop "Brief Refinement" · max_iterations: 3 · iteration 2 in progress
</div>
</div>
<Btn ghost>View Wiring</Btn>
</div>
<div style={{fontSize:10,color:c.textSec,marginTop:8,lineHeight:1.5,fontStyle:"italic"}}>
<code style={{color:c.green}}>passed_out</code> → Loop.stop_in · <code style={{color:c.red}}>failed_out</code> → Loop.return_in · <code style={{color:c.purple}}>feedback_out</code> → Loop.feedback_in
</div>
</div>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:20}}>
{/* LEFT — convergence settings */}
<div>
<DL>Convergence Tracking</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,display:"flex",flexDirection:"column",gap:4}}>
<DC label="Detect plateau (no improvement N iterations)" checked={true} />
<DC label="Detect regression (an outcome got worse)" checked={true} />
<DC label="Track stuck failures (same outcome fails N times)" checked={true} />
<DC label="Show 'do not disrupt' list to next iteration" checked={true} />
</div>
<DL mt>On Plateau</DL>
<DS opts={["Continue iterating","Escalate to human after 2","Abort loop","Switch verification method"]} v="Escalate to human after 2" />
<DL mt>On Regression</DL>
<DS opts={["Warn (continue)","Escalate","Revert to prior best","Abort"]} v="Revert to prior best" />
<DL mt>Feedback Composition</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DC label="Include cumulative progress summary" checked={true} />
<DC label="Include newly-passed (what got fixed)" checked={true} />
<DC label="Include still-failing (what to fix)" checked={true} />
<DC label="Include regressions (what broke)" checked={true} />
<DC label="Include prioritized focus" checked={true} />
<DC label="Include do-not-disrupt list" checked={true} />
</div>
</div>
{/* RIGHT — iteration history + feedback preview */}
<div>
<DL>Iteration History (this run)</DL>
<div style={{display:"flex",flexDirection:"column",gap:8}}>
{[
{n:1,passed:3,failed:5,score:0.42,status:"failed",time:"8:34 AM"},
{n:2,passed:7,failed:1,score:0.83,status:"in_progress",time:"9:18 AM"},
{n:3,passed:0,failed:0,score:0,status:"pending",time:""},
].map((it,i)=>(
<div key={i} style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,opacity:it.status==="pending"?0.5:1}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div style={{display:"flex",alignItems:"center",gap:10}}>
<span style={{fontSize:12,fontWeight:700,color:c.text}}>Iteration {it.n}</span>
<Pill color={it.status==="failed"?c.red:it.status==="in_progress"?c.amber:c.textTer}>{it.status.replace("_"," ").toUpperCase()}</Pill>
</div>
<span style={{fontSize:10,color:c.textTer}}>{it.time}</span>
</div>
{it.status!=="pending" && (
<div style={{display:"flex",gap:14,marginTop:6,fontSize:10,color:c.textSec,fontFamily:mono}}>
<span style={{color:c.green}}>✓ {it.passed} passed</span>
<span style={{color:c.red}}>✕ {it.failed} failed</span>
<span>· score {it.score.toFixed(2)}</span>
</div>
)}
</div>
))}
</div>
<DL mt>Next Iteration Feedback (preview)</DL>
<div style={{padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.purple}40`,fontSize:10,color:c.text,lineHeight:1.6,fontFamily:sans}}>
<div style={{color:c.purple,fontWeight:700,marginBottom:6,letterSpacing:0.3}}>FEEDBACK FOR ITERATION 3</div>
<div style={{marginBottom:4}}><strong style={{color:c.green}}>Progress:</strong> 7 of 8 outcomes now pass (up from 3/8 in iteration 1).</div>
<div style={{marginBottom:4}}><strong style={{color:c.green}}>Newly passed:</strong> word count, citations present, statute of limitations, tone.</div>
<div style={{marginBottom:4}}><strong style={{color:c.red}}>Still failing:</strong> Citations resolve to real cases (3 citations unresolvable).</div>
<div style={{marginBottom:4}}><strong style={{color:c.amber}}>Focus this iteration:</strong> Address the 3 unresolvable citations in §III, paragraph 4. Either find correct citations OR remove unsupported assertions.</div>
<div style={{color:c.textSec,fontStyle:"italic"}}><strong>Do not disrupt:</strong> §I (introduction), §II (standing argument), §IV (SOL), §V (conclusion) — all passing.</div>
<div style={{fontSize:9,color:c.textTer,marginTop:8,fontStyle:"italic",borderTop:`1px solid ${c.border}`,paddingTop:6}}>
Composed into the next pass's instruction via Loop.feedback_in → CIL composition.
</div>
</div>
</div>
</div>
</div>
);
}
// ─── Routing & Ports tab ───
function RoutingTab({ports}) {
return (
<div style={{padding:20}}>
<div style={{marginBottom:16}}>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Routing & Ports</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>
Output ports are auto-populated based on Evaluator Mode + toggles. Active ports are emphasized; inactive ports show their gating condition.
</div>
</div>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:20}}>
{/* LEFT — Outputs */}
<div>
<DL>Output Ports</DL>
<div style={{display:"flex",flexDirection:"column",gap:6}}>
{ports.outputs.map((p,i)=>(
<PortRow key={i} port={p} />
))}
</div>
</div>
{/* RIGHT — Inputs + routing pattern preview */}
<div>
<DL>Input Ports</DL>
<div style={{display:"flex",flexDirection:"column",gap:6,marginBottom:20}}>
{ports.inputs.map((p,i)=>(
<PortRow key={i} port={p} input />
))}
</div>
<DL>Canonical Wiring (Pattern E)</DL>
<div style={{padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,fontFamily:mono,fontSize:10,lineHeight:1.8,color:c.text}}>
<div><span style={{color:c.cyan}}>↺ Loop</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>loop_out</span> → <span style={{color:c.accent}}>◆ Agent</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>data_in</span></div>
<div><span style={{color:c.cyan}}>↺ Loop</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>instruction_out</span> → <span style={{color:c.accent}}>◆ Agent</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>instruction_in</span></div>
<div><span style={{color:c.accent}}>◆ Agent</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>data_out</span> → <span style={{color:c.green}}>⊜ Evaluator</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>subject_in</span></div>
<div style={{marginTop:6}}>
<span style={{color:c.green}}>⊜ Evaluator</span><span style={{color:c.textTer}}>.</span><span style={{color:c.green}}>passed_out</span> → <span style={{color:c.cyan}}>↺ Loop</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>stop_in</span>
</div>
<div>
<span style={{color:c.green}}>⊜ Evaluator</span><span style={{color:c.textTer}}>.</span><span style={{color:c.red}}>failed_out</span> → <span style={{color:c.cyan}}>↺ Loop</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>return_in</span>
</div>
<div>
<span style={{color:c.green}}>⊜ Evaluator</span><span style={{color:c.textTer}}>.</span><span style={{color:c.purple}}>feedback_out</span> → <span style={{color:c.cyan}}>↺ Loop</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>feedback_in</span> <span style={{color:c.textTer,fontSize:9}}>(NEW)</span>
</div>
<div>
<span style={{color:c.green}}>⊜ Evaluator</span><span style={{color:c.textTer}}>.</span><span style={{color:c.amber}}>human_review_out</span> → <span style={{color:c.amber}}>👤 Human</span> <span style={{color:c.textTer,fontSize:9}}>(outside loop)</span>
</div>
<div style={{marginTop:6}}>
<span style={{color:c.cyan}}>↺ Loop</span><span style={{color:c.textTer}}>.</span><span style={{color:c.text}}>done_out</span> → next stage
</div>
</div>
</div>
</div>
</div>
);
}
function PortRow({port, input}) {
return (
<div style={{
padding:"8px 10px",
background:port.enabled?(input?`${port.color}08`:`${port.color}10`):c.bgCard,
border:`1px solid ${port.enabled?port.color+"40":c.border}`,
borderRadius:5,
opacity:port.enabled?1:0.5,
}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div style={{display:"flex",alignItems:"center",gap:8}}>
<span style={{width:8,height:8,borderRadius:"50%",background:port.enabled?port.color:c.textTer}} />
<span style={{fontSize:11,fontWeight:600,color:port.enabled?port.color:c.textTer,fontFamily:mono}}>{port.label}</span>
<span style={{fontSize:9,color:c.textTer,fontFamily:mono}}>· {port.role}</span>
</div>
<div>
{port.enabled ? (
<Pill color={c.green}>ACTIVE</Pill>
) : (
<span style={{fontSize:9,color:c.textTer,fontFamily:sans,fontStyle:"italic"}}>
inactive · {port.condition || "mode dependent"}
</span>
)}
</div>
</div>
</div>
);
}
// ─── Results tab ───
function ResultsTab() {
return (
<div style={{padding:20}}>
<div style={{marginBottom:16,display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Last Run Results</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>Iteration 2 · 9:24 AM · Cost: $0.34 · Duration: 12.4s</div>
</div>
<div style={{display:"flex",gap:8}}>
<Btn ghost>View Raw Verdict</Btn>
<Btn ghost>Compare to Iteration 1</Btn>
<Btn accent>Re-Evaluate</Btn>
</div>
</div>
{/* Top-line verdict */}
<div style={{padding:16,background:`${c.amber}08`,borderRadius:8,border:`1px solid ${c.amber}30`,marginBottom:16}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:6}}>
<span style={{fontSize:13,fontWeight:700,color:c.amber}}>↺ VERDICT: NEEDS REVISION (iteration 2 of 3)</span>
<span style={{fontSize:10,color:c.textSec,fontFamily:mono}}>quality_index: 0.83</span>
</div>
<div style={{fontSize:11,color:c.text,lineHeight:1.5}}>
7 of 8 outcomes verified. 1 required outcome failed: <strong>Citations resolve to real cases</strong>. Routing to <code style={{color:c.red}}>failed_out</code> with structured feedback for iteration 3.
</div>
</div>
{/* Trajectory */}
<div style={{padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,marginBottom:16}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8}}>
<span style={{fontSize:11,fontWeight:700,color:c.text,fontFamily:sans}}>Trajectory</span>
<Pill color={c.green}>IMPROVING</Pill>
</div>
<div style={{display:"flex",alignItems:"center",gap:14,fontSize:11}}>
<div>
<div style={{color:c.textSec,fontSize:9}}>ITERATION 1</div>
<div style={{color:c.red,fontFamily:mono}}>3/8 · 0.42</div>
</div>
<div style={{color:c.green,fontSize:14}}>→</div>
<div>
<div style={{color:c.textSec,fontSize:9}}>ITERATION 2</div>
<div style={{color:c.amber,fontFamily:mono}}>7/8 · 0.83</div>
</div>
<div style={{color:c.green,fontSize:14}}>→</div>
<div>
<div style={{color:c.textSec,fontSize:9}}>ITERATION 3 (PROJECTED)</div>
<div style={{color:c.textTer,fontFamily:mono}}>8/8 likely</div>
</div>
</div>
</div>
{/* Per-outcome results summary */}
<DL>Per-Outcome Results</DL>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:8}}>
{[
{name:"Citations present",status:"verified",detail:"19 citations found"},
{name:"Citations resolve to real cases",status:"failed",detail:"16/19 verified · 3 not found"},
{name:"Cited holdings characterized accurately",status:"failed",detail:"6/8 · 2 mischaracterized"},
{name:"No factual claims unsupported",status:"verified",detail:"34/34 claims supported"},
{name:"Brief addresses all motion issues",status:"verified",detail:"7/7 issues addressed"},
{name:"Argument is persuasive",status:"verified",detail:"0.78 (threshold 0.7)"},
{name:"Tone is professional",status:"verified",detail:"0.85 (threshold 0.7)"},
{name:"Brief between 4000-8000 words",status:"verified",detail:"6,247 words"},
].map((o,i)=>(
<div key={i} style={{padding:10,background:c.bgCard,borderRadius:5,border:`1px solid ${c.border}`}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:4}}>
<span style={{fontSize:11,color:c.text}}>{o.name}</span>
<StatusBadge status={o.status} />
</div>
<div style={{fontSize:10,color:c.textSec,fontFamily:mono}}>{o.detail}</div>
</div>
))}
</div>
{/* Cost breakdown */}
<DL mt>Cost Breakdown</DL>
<div style={{padding:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<div style={{display:"grid",gridTemplateColumns:"1fr 80px 60px",gap:"4px 16px",fontSize:10,fontFamily:mono}}>
<div style={{color:c.textTer,fontWeight:600,fontSize:9}}>OUTCOME</div>
<div style={{color:c.textTer,fontWeight:600,fontSize:9,textAlign:"right"}}>METHOD</div>
<div style={{color:c.textTer,fontWeight:600,fontSize:9,textAlign:"right"}}>COST</div>
{[
["Citations are present","automated","$0.00"],
["Citations resolve to real cases","subagent","$0.18"],
["Cited holdings characterized","claim_verify","$0.06"],
["No factual claims unsupported","claim_verify","$0.03"],
["Brief addresses motion issues","judge","$0.04"],
["Argument is persuasive","judge","$0.02"],
["Tone is professional","judge","$0.01"],
["Word count","automated","$0.00"],
].map((r,i)=>(
<React.Fragment key={i}>
<div style={{color:c.text}}>{r[0]}</div>
<div style={{color:c.textSec,textAlign:"right"}}>{r[1]}</div>
<div style={{color:c.text,textAlign:"right"}}>{r[2]}</div>
</React.Fragment>
))}
<div style={{color:c.text,fontWeight:700,borderTop:`1px solid ${c.border}`,paddingTop:4,marginTop:2}}>Total</div>
<div></div>
<div style={{color:c.green,fontWeight:700,textAlign:"right",borderTop:`1px solid ${c.border}`,paddingTop:4,marginTop:2}}>$0.34</div>
</div>
</div>
</div>
);
}
// ═══════════════════════════════════════════════════════════════
// EXTRACTOR EXPANDED VIEW
// ═══════════════════════════════════════════════════════════════
function ExtractorExpandedView() {
const [tab, setTab] = useState("claim_types");
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 20px",background:c.bgPanel}}>
<div style={{display:"flex",alignItems:"center",gap:12}}>
<span style={{fontSize:18}}>◈</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:10}}><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>Load Claim Type Preset</Btn>
<Btn ghost>Save as Preset…</Btn>
<Btn primary>▶ Run Extraction</Btn>
<span style={{fontSize:18,color:c.textSec,cursor:"pointer",padding:"0 4px",marginLeft:4}}>✕</span>
</div>
</div>
{/* Tab bar */}
<div style={{height:36,minHeight:36,borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",padding:"0 16px",gap:4,background:c.bg}}>
{[
{id:"claim_types", label:"Claim Types", count:5},
{id:"sources", label:"Sources & Documents"},
{id:"config", label:"Extraction Config"},
{id:"results", label:"Last Results", count:50},
].map(t=>(
<button key={t.id} onClick={()=>setTab(t.id)}
style={{padding:"8px 14px",fontSize:11,fontWeight:600,color:tab===t.id?c.text:c.textSec,background:"transparent",border:"none",borderBottom:tab===t.id?`2px solid ${c.accent}`:"2px solid transparent",cursor:"pointer",fontFamily:sans,display:"flex",alignItems:"center",gap:6}}>
{t.label}
{t.count && <span style={{fontSize:9,color:c.textTer,background:c.bgHover,padding:"1px 5px",borderRadius:8}}>{t.count}</span>}
</button>
))}
</div>
{/* Body */}
<div style={{flex:1,display:"flex",overflow:"hidden"}}>
{/* Left sidebar */}
<div style={{width:260,minWidth:260,borderRight:`1px solid ${c.border}`,padding:16,overflowY:"auto",background:c.bgPanel}}>
<DL>Extraction Agent</DL>
<DS opts={["(task default)","Elnor","Nova","claude-haiku-4-5 (cheap)"]} v="claude-haiku-4-5 (cheap)" />
<div style={{fontSize:9,color:c.textTer,marginTop:4,fontStyle:"italic"}}>Most extraction is parsing — use cheap fast models. Authority-required types upgrade automatically.</div>
<DL mt>Source Mode</DL>
<DS opts={["Single subject (data_in)","Compare bundle (comparison_bundle_in)","Multi-source"]} v="Single subject (data_in)" />
<DL mt>Min Confidence</DL>
<DI value="0.7" />
<DL mt>Max Claims (cap)</DL>
<DI value="50" />
<DL mt>Truncation Policy</DL>
<DS opts={["top_n_by_confidence","top_n_by_type_balance","include_all_at_cap"]} v="top_n_by_type_balance" />
<DL mt>Source Spans</DL>
<DC label="Include source spans" checked={true} />
<div style={{borderTop:`1px solid ${c.border}`,marginTop:14,paddingTop:12}}>
<DL>Cache</DL>
<div style={{fontSize:10,color:c.textSec,lineHeight:1.6,fontFamily:sans}}>
<div>Cache hit on last run</div>
<div>Cache key: 5a3f...8d12</div>
<div style={{color:c.green,marginTop:4}}>$0.00 (cached) vs $0.12 fresh</div>
</div>
</div>
<div style={{borderTop:`1px solid ${c.border}`,marginTop:14,paddingTop:12}}>
<DL>Downstream Consumers</DL>
<div style={{display:"flex",flexDirection:"column",gap:5,fontSize:10}}>
<div style={{padding:"5px 7px",background:`${c.green}10`,borderRadius:3,border:`1px solid ${c.green}25`}}>
<span style={{color:c.green,fontFamily:mono,fontSize:9}}>claims_out</span>
<span style={{color:c.textSec,marginLeft:6}}>→ ⊜ Brief Review.claims_in</span>
</div>
</div>
</div>
</div>
{/* Main area */}
<div style={{flex:1,overflow:"auto",background:c.bg}}>
{tab === "claim_types" && <ClaimTypesTab />}
{tab === "sources" && <ExtractorSourcesTab />}
{tab === "config" && <ExtractorConfigTab />}
{tab === "results" && <ExtractorResultsTab />}
</div>
</div>
</div>
);
}
function ClaimTypesTab() {
const types = [
{id:"ct1", name:"CaseCitation", desc:"Citations to legal cases. Each citation includes case name, reporter, court, year, and quoted holding (if any).", 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).", fieldCount:6, evaluable:true, authorityRequired:true, expanded:true, expectedCount:"12-19", consumedBy:["Citations resolve to real cases","Cited holdings characterized accurately"]},
{id:"ct2", name:"FactualAssertion", desc:"Factual claims about the matter — dates, amounts, events, parties.", instruction:"Extract every factual claim. Include the assertion text, the source span, and any cited evidence reference.", fieldCount:3, evaluable:true, authorityRequired:false, expanded:false, expectedCount:"30+"},
{id:"ct3", name:"LegalProposition", desc:"Statements of legal rules or principles, with or without supporting citation.", instruction:"Extract statements of legal rules. Include the proposition, any supporting citation, and source span.", fieldCount:4, evaluable:true, authorityRequired:false, expanded:false, expectedCount:"5-10"},
{id:"ct4", name:"OpposingArgument", desc:"References to opposing counsel's arguments, with citation to motion or opposition.", instruction:"Extract every reference to opposing counsel's argument. Include argument summary, source citation, and the brief's response.", fieldCount:4, evaluable:true, authorityRequired:false, expanded:false, expectedCount:"5-15"},
{id:"ct5", name:"ProceduralFact", desc:"Statements about case procedural posture, deadlines, court orders.", instruction:"Extract procedural facts.", fieldCount:3, evaluable:false, authorityRequired:false, expanded:false, expectedCount:"3-8"},
];
return (
<div style={{padding:20}}>
<div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:16}}>
<div>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Claim Types</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>
Define what to extract. Each type has its own extraction instruction and field schema. Used by downstream Evaluators via <code style={{color:c.cyan,fontFamily:mono}}>claims_in</code>.
</div>
</div>
<Btn accent>+ Add Claim Type</Btn>
</div>
<div style={{display:"flex",flexDirection:"column",gap:8}}>
{types.map(t => <ClaimTypeCard key={t.id} type={t} />)}
</div>
</div>
);
}
function ClaimTypeCard({type}) {
const [expanded, setExpanded] = useState(type.expanded);
return (
<div style={{background:c.bgCard,border:`1px solid ${expanded?c.accent+"40":c.border}`,borderRadius:6,overflow:"hidden"}}>
<div onClick={()=>setExpanded(!expanded)} style={{padding:"10px 14px",cursor:"pointer",display:"flex",alignItems:"center",gap:10}}>
<span style={{color:c.textTer,fontSize:10,minWidth:8}}>{expanded?"▾":"▸"}</span>
<div style={{flex:1,minWidth:0}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:3}}>
<span style={{fontSize:12,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.authorityRequired && <Pill color={c.amber}>AUTHORITY REQUIRED</Pill>}
<span style={{fontSize:9,color:c.textSec,fontFamily:mono}}>· {type.fieldCount} fields</span>
</div>
<div style={{fontSize:10,color:c.textSec,lineHeight:1.4,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>
{type.desc}
</div>
</div>
<div style={{textAlign:"right"}}>
<div style={{fontSize:10,color:c.textTer,fontFamily:mono}}>Expected: {type.expectedCount}</div>
{type.consumedBy && (
<div style={{fontSize:9,color:c.cyan,marginTop:3,fontFamily:sans}}>
↗ {type.consumedBy.length} outcome{type.consumedBy.length>1?"s":""}
</div>
)}
</div>
</div>
{expanded && (
<div style={{borderTop:`1px solid ${c.border}`,padding:"14px 16px",background:c.bg}}>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:20}}>
{/* LEFT */}
<div>
<DL>Type Name</DL>
<DI value={type.name} />
<DL mt>Description</DL>
<DT value={type.desc} h={50} />
<DL mt>Extraction Instruction (what the extractor LLM reads)</DL>
<DT value={type.instruction} h={80} placeholder="Plain language instruction for the extraction agent." />
<div style={{fontSize:9,color:c.textTer,marginTop:4,fontStyle:"italic"}}>
This instruction is operative — the extractor reads it directly.
</div>
<DL mt>Evaluation Instruction (consumed by Evaluator)</DL>
<DT value={"Verify the citation by looking it up in CourtListener. Confirm case name, reporter, court, year. Confirm holding characterization is accurate."} h={60} placeholder="(Optional) How downstream Evaluators should verify claims of this type." />
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:8,marginTop:10}}>
<DC label="Evaluable" checked={type.evaluable} />
<DC label="Authority required" checked={type.authorityRequired} />
</div>
</div>
{/* RIGHT */}
<div>
<DL>Field Schema</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,display:"flex",flexDirection:"column",gap:6}}>
{[
{name:"case_name", type:"string", req:true},
{name:"citation", type:"string", req:true},
{name:"court", type:"enum", req:false},
{name:"year", type:"number", req:false},
{name:"holding_quoted", type:"string", req:false},
{name:"cited_proposition_ref", type:"ref", req:false},
].map((f,i)=>(
<div key={i} style={{display:"grid",gridTemplateColumns:"1.5fr 1fr auto",gap:8,alignItems:"center",padding:"5px 7px",background:c.bg,borderRadius:3}}>
<span style={{fontSize:10,color:c.text,fontFamily:mono}}>{f.name}</span>
<span style={{fontSize:9,color:c.accent,fontFamily:mono}}>{f.type}</span>
<span style={{fontSize:9,color:f.req?c.amber:c.textTer,fontFamily:sans}}>{f.req?"req":"opt"}</span>
</div>
))}
<Btn ghost>+ Add Field</Btn>
</div>
<DL mt>Preferred Sub-Agent (for verification)</DL>
<DS opts={["(none)","citation-checker","fact-checker","custom..."]} v="citation-checker" />
<DL mt>Source Spans</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DC label="Include source spans (offsets in subject)" checked={true} />
<DC label="Classify span role (primary / citation / parenthetical / footnote)" checked={true} />
</div>
{type.consumedBy && (
<div style={{marginTop:14,padding:10,background:`${c.cyan}08`,borderRadius:6,border:`1px solid ${c.cyan}25`}}>
<div style={{fontSize:10,fontWeight:700,color:c.cyan,marginBottom:6,letterSpacing:0.3}}>CONSUMED BY OUTCOMES</div>
<div style={{display:"flex",flexDirection:"column",gap:4}}>
{type.consumedBy.map((o,i)=>(
<div key={i} style={{fontSize:10,color:c.text}}>↗ <span style={{color:c.cyan}}>{o}</span></div>
))}
</div>
</div>
)}
</div>
</div>
</div>
)}
</div>
);
}
function ExtractorSourcesTab() {
return (
<div style={{padding:20}}>
<div style={{marginBottom:16}}>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Sources & Documents</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>
What to extract claims from. Multiple sources can be combined into one extraction run.
</div>
</div>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:20}}>
<div style={{display:"flex",flexDirection:"column",gap:16}}>
<div>
<DL>Primary Subject (from wiring)</DL>
<div style={{padding:"8px 10px",background:`${c.accent}08`,border:`1px solid ${c.accent}30`,borderRadius:4}}>
<div style={{fontSize:11,color:c.accent,fontFamily:mono}}>data_in ← ◆ Draft Brief.data_out</div>
<div style={{fontSize:10,color:c.textSec,marginTop:3,fontFamily:sans}}>Type: ContextBundle · ~6,200 words · text content</div>
</div>
</div>
<UploadZone
label="Additional Documents (uploadable)"
hint="Extract claims from these documents in addition to the primary subject."
files={[]}
/>
<div>
<DL>Document Libraries</DL>
<div style={{display:"flex",flexDirection:"column",gap:6}}>
{[
{label:"DOC73 PBE: Securities Litigation",icon:"📚",color:c.green},
{label:"Matter file (auto)",icon:"🗂",color:c.green},
{label:"+ Connect library...",icon:"+",color:c.accent},
].map((l,i)=>(
<div key={i} style={{padding:"7px 9px",background:c.bgCard,borderRadius:4,border:`1px solid ${c.border}`,fontSize:11,color:l.label.startsWith("+")?c.accent:c.text,display:"flex",alignItems:"center",gap:8,cursor:"pointer"}}>
<span style={{fontSize:13}}>{l.icon}</span>
<span>{l.label}</span>
</div>
))}
</div>
</div>
</div>
<div style={{display:"flex",flexDirection:"column",gap:16}}>
<div>
<DL>Custom Extraction Guidance</DL>
<div style={{fontSize:10,color:c.textTer,marginBottom:6,fontStyle:"italic",lineHeight:1.5}}>
Free-text guidance applied to all claim type extractions. Use for cross-cutting hints.
</div>
<DT value="When extracting citations from this brief, prefer the Bluebook format. Skip footnotes that contain only cross-references (e.g., 'See supra note 3'). For factual assertions, exclude background/contextual statements; only extract claims the brief is asserting as true." h={120} />
</div>
<div>
<DL>Per-Source Behavior</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DC label="Extract from primary subject only" checked={true} />
<DC label="Also extract from uploaded documents" checked={false} />
<DC label="Also extract from connected libraries (semantic search)" checked={false} />
<div style={{fontSize:9,color:c.textTer,marginTop:6,lineHeight:1.5,fontStyle:"italic"}}>
Extracting from libraries is expensive — use only for ground-truth/expected-claim extraction in calibration runs.
</div>
</div>
</div>
<div>
<DL>Privileged Content Handling</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DS opts={["Auto-detect from source data_class","Always treat as privileged","Always treat as public"]} v="Auto-detect from source data_class" />
<div style={{fontSize:9,color:c.textTer,marginTop:6,lineHeight:1.5,fontStyle:"italic"}}>
Privileged content forces task-scoped cache (never shared across tasks). Auto-detect uses the source's <code>data_class</code>.
</div>
</div>
</div>
</div>
</div>
</div>
);
}
function ExtractorConfigTab() {
return (
<div style={{padding:20}}>
<div style={{marginBottom:16}}>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Extraction Config</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>
Agent, sampling, caching, validation.
</div>
</div>
<div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:20}}>
<div style={{display:"flex",flexDirection:"column",gap:14}}>
<div>
<DL>Extraction Agent</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DL>Agent</DL>
<DS opts={["(task default)","Elnor","Nova","Atlas","custom..."]} v="Elnor" />
<DL mt>Model</DL>
<DS opts={["(agent default)","claude-haiku-4-5","claude-sonnet-4-6","gemini-2.5-flash"]} v="claude-haiku-4-5" />
<DL mt>Think Level</DL>
<DS opts={["off","minimal","low","medium"]} v="minimal" />
<DL mt>Temperature</DL>
<DI value="0.0" />
<div style={{fontSize:9,color:c.green,marginTop:4,fontStyle:"italic"}}>
✓ Temperature 0 + minimal thinking is right for parsing. Cheap and deterministic.
</div>
</div>
</div>
<div>
<DL>Authority-Required Upgrade</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DC label="Auto-upgrade model for authority-required types" checked={true} />
<DL mt>Upgrade Model</DL>
<DS opts={["claude-sonnet-4-6","claude-opus-4-7"]} v="claude-sonnet-4-6" />
<div style={{fontSize:9,color:c.textTer,marginTop:6,fontStyle:"italic",lineHeight:1.5}}>
Authority-required types (e.g., CaseCitation) auto-use a stronger model. Cheap model still used for parsing-only types.
</div>
</div>
</div>
<div>
<DL>Cost Cap</DL>
<div style={{display:"flex",gap:8,alignItems:"center"}}>
<DI value="$1.00" />
<span style={{fontSize:10,color:c.textSec,whiteSpace:"nowrap"}}>per run · abort if exceeded</span>
</div>
</div>
</div>
<div style={{display:"flex",flexDirection:"column",gap:14}}>
<div>
<DL>Caching</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DC label="Cache extraction results" checked={true} />
<DC label="Honor cache hits across iterations (when subject unchanged)" checked={true} />
<DC label="Force fresh extraction on next run" checked={false} />
<div style={{fontSize:9,color:c.textTer,marginTop:6,fontStyle:"italic",lineHeight:1.5}}>
Cache key includes: subject hash + claim types structural hash + extraction agent fingerprint + min confidence. Display-only renames don't invalidate cache.
</div>
</div>
</div>
<div>
<DL>Validation</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
<DC label="Reject claims missing required fields" checked={true} />
<DC label="Warn on truncation (cap hit before all claims extracted)" checked={true} />
<DC label="Warn on cross-type ref unresolvable" checked={true} />
<DC label="Validate source span offsets against subject" checked={true} />
</div>
</div>
<div>
<DL>Output Ports</DL>
<div style={{padding:10,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,display:"flex",flexDirection:"column",gap:6}}>
<Port label="claims_out" role="data·always" color={c.green} enabled={true} />
<Port label="metrics_out" role="data·optional" color={c.cyan} enabled={true} />
<Port label="signal_out" role="signal·always" color={c.amber} enabled={true} />
<Port label="error_out" role="signal+data" color={c.red} enabled={true} />
</div>
</div>
</div>
</div>
</div>
);
}
function ExtractorResultsTab() {
return (
<div style={{padding:20}}>
<div style={{marginBottom:16,display:"flex",alignItems:"center",justifyContent:"space-between"}}>
<div>
<div style={{fontSize:14,fontWeight:700,color:c.text,fontFamily:sans}}>Last Run Results</div>
<div style={{fontSize:11,color:c.textSec,marginTop:2,fontFamily:sans}}>50 claims extracted · cache hit · $0.00 (cached from previous $0.12 run)</div>
</div>
<Btn accent>Re-Extract (force fresh)</Btn>
</div>
{/* Per-type counts */}
<DL>Extraction Summary</DL>
<div style={{display:"grid",gridTemplateColumns:"repeat(5, 1fr)",gap:8,marginBottom:20}}>
{[
{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:12,background:c.bgPanel,borderRadius:6,border:`1px solid ${t.color}30`}}>
<div style={{fontSize:9,color:t.color,fontWeight:600,letterSpacing:0.3,fontFamily:sans,marginBottom:4}}>{t.name.toUpperCase()}</div>
<div style={{fontSize:22,fontWeight:700,color:t.color,fontFamily:mono}}>{t.count}</div>
<div style={{fontSize:9,color:c.textTer,marginTop:2}}>claims</div>
</div>
))}
</div>
{/* Sample claims */}
<DL>Sample Claims</DL>
<div style={{display:"flex",flexDirection:"column",gap:6}}>
{[
{type:"CaseCitation",confidence:0.98,text:"Tellabs, Inc. v. Makor Issues & Rights, Ltd., 551 U.S. 308 (2007)",span:"§II ¶3"},
{type:"CaseCitation",confidence:0.45,text:"Smith v. Jones, 432 F.3d 12 (5th Cir. 2007) — UNRESOLVABLE",span:"§III ¶4",warning:true},
{type:"FactualAssertion",confidence:0.92,text:"Defendant's CEO sold $4.2M in shares during the class period",span:"§IV ¶7"},
{type:"LegalProposition",confidence:0.88,text:"Loss causation requires showing the misrepresentation proximately caused the loss",span:"§V ¶2"},
].map((cl,i)=>(
<div key={i} style={{padding:10,background:c.bgCard,borderRadius:5,border:`1px solid ${cl.warning?c.red+"40":c.border}`}}>
<div style={{display:"flex",alignItems:"center",gap:8,marginBottom:4}}>
<Pill color={cl.warning?c.red:c.accent}>{cl.type}</Pill>
<span style={{fontSize:9,color:c.textTer,fontFamily:mono}}>· conf {cl.confidence.toFixed(2)}</span>
<span style={{fontSize:9,color:c.textTer,fontFamily:mono}}>· {cl.span}</span>
{cl.warning && <Pill color={c.red}>FLAGGED</Pill>}
</div>
<div style={{fontSize:11,color:c.text,fontFamily:sans}}>{cl.text}</div>
</div>
))}
<Btn ghost>View all 50 claims →</Btn>
</div>
</div>
);
}