Elnor Repo Reader

Q_TASK_EVALUATOR_EXTRACTOR_V2.jsx

Design Mockups/Archived Mockups/DOC23 Task Mockups/Q_TASK_EVALUATOR_EXTRACTOR_V2.jsx

Generated 2026-06-09T01:23:58.539Z from commit dbaa25962edc11ab30e8d4ca1715f9ae5bf77331. Worktree: clean.

Open text page · Open raw txt · Open path URL

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>
  );
}