Elnor Repo Reader

Q_TASK_EVALUATOR_EXTRACTOR_V3.jsx

Design Mockups/DOC23 Mockups/Q_TASK_EVALUATOR_EXTRACTOR_V3.jsx

Short text page 7f50054c69bc. Generated 2026-06-09T01:23:58.539Z from commit dbaa25962edc11ab30e8d4ca1715f9ae5bf77331. Worktree: clean.

Open readable HTML page · Open raw txt · Open path URL

ELNOR REPO READER TEXT MIRROR
Original path: Design Mockups/DOC23 Mockups/Q_TASK_EVALUATOR_EXTRACTOR_V3.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";

// ═══════════════════════════════════════════════════════════════
// EVALUATOR + EXTRACTOR EXPANDED VIEWS — V3
// Changes from V2:
//   • Per-outcome routing as default (each outcome picks its destination)
//   • Aggregate "any_failed_out" port preserved for convenience
//   • Meta-outcome shown as a distinct addable outcome type
//   • Pipeline indicator showing position in a chain of Evaluators
//   • "+ Add Outcome" with three entry paths (type · ask TA · load preset)
//   • Outcome cards default to inferred-plan SUMMARY; Adjust opens form
//   • Findings viewable on demand via drawer (R0.6.5 §26 action buttons)
//   • Findings deliverable as inline document comments option
// ═══════════════════════════════════════════════════════════════

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",
  metaAccent:"#e879f9",
};
const sans = "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
const mono = "'SF Mono','Cascadia Code','Fira Code', monospace";

// ─── Helpers ───
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, 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, sm, 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: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, onClick}) => (
  <span onClick={onClick} 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,cursor:onClick?"pointer":"default"}}>
    {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:"◐"},
    pending: {label:"PENDING", color:c.textTer, icon:"○"},
    in_progress: {label:"IN PROGRESS", color:c.cyan, 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},
    meta: {label:"META", color:c.metaAccent},
  };
  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>
      )}
    </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 V3 — PER-OUTCOME ROUTING · META-OUTCOME · THREE-PATH CREATION
        <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 VIEW
// ═══════════════════════════════════════════════════════════════
function EvaluatorView() {
  const [portsOpen, setPortsOpen] = useState(false);
  const [expanded, setExpanded] = useState("o3");          // outcome adjust form open
  const [adding, setAdding] = useState(false);              // showing add-outcome form
  const [findingsOpen, setFindingsOpen] = useState(null);   // outcome_id whose findings are open
  const [pipelineExpanded, setPipelineExpanded] = useState(false);

  return (
    <div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden",position:"relative"}}>

      {/* 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}}>Final Quality Gate</div>
            <div style={{fontSize:10,color:c.textTer,fontFamily:mono,marginTop:2}}>step.evaluator · stage 5 of 5 in pipeline</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>

      {/* Pipeline indicator */}
      <PipelineIndicator expanded={pipelineExpanded} setExpanded={setPipelineExpanded} />

      {/* Body */}
      <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={{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}}>← ◆ Final Brief Draft</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 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.2</span> <span style={{color:c.textSec}}>← ◇ Motion to Dismiss</span>
            </div>
          </div>

          <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" />

          <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.50" />
          <HelperText>Outcomes can override with their own cost cap.</HelperText>

          <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 <Input value="3" w={36} /> revisions before giving up.
            <br />
            If the <em>same outcome</em> fails <Input value="3" w={28} /> times, send to human.
            <br />
            If no improvement after <Input value="2" w={28} /> iterations, send to human.
            <br />
            On regression: <Select opts={["revert to prior best","warn and continue","escalate"]} v="revert to prior best" compact />
          </div>

          <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} />
            <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>

          <SectionLabel mt>Sources & Materials</SectionLabel>
          <UploadStrip label="Reference materials" files={[
            {icon:"📄",name:"Bluebook 21st Ed.pdf",size:"4.2 MB"},
          ]} />
          <div style={{marginTop:10}}>
            <UploadStrip label="Evidence sources" files={[
              {icon:"📁",name:"Case Record",size:"487 docs"},
              {icon:"📄",name:"Motion to Dismiss.pdf"},
            ]} />
          </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>

          <SectionLabel mt>Custom Guidance</SectionLabel>
          <Textarea value="Apply strict Bluebook 21st edition standards. Prefer formal legal register. When a citation can't be verified via CourtListener, try Westlaw before marking it unresolvable." h={70} />
        </div>

        {/* MAIN */}
        <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.41 · 14.2s</span>
              </div>
              <div style={{fontSize:11,color:c.text,lineHeight:1.5,marginBottom:8}}>
                7 of 8 outcomes passed. <strong style={{color:c.red}}>Citations resolve to real cases</strong> failed — 3 citations didn't verify. Sent back to <span style={{color:c.accent}}>Brief Drafter</span> with structured feedback.
              </div>
              <div style={{display:"flex",gap:8}}>
                <Btn ghost sm onClick={()=>setFindingsOpen("all")}>View all findings</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 describes what to check and where to send the work if it fails.</div>
            </div>
            <Btn accent sm onClick={()=>setAdding(!adding)}>{adding ? "Cancel" : "+ Add Outcome"}</Btn>
          </div>

          {/* Add Outcome inline form */}
          {adding && <AddOutcomeForm onClose={()=>setAdding(false)} />}

          {/* Outcome cards */}
          <div style={{padding:"14px 28px 28px",display:"flex",flexDirection:"column",gap:8}}>
            {OUTCOMES.map(o => (
              <OutcomeCard
                key={o.id}
                outcome={o}
                expanded={expanded===o.id}
                onToggle={()=>setExpanded(expanded===o.id?null:o.id)}
                onViewFindings={()=>setFindingsOpen(o.id)}
              />
            ))}
          </div>
        </div>
      </div>

      {/* 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}}>
            6 outputs (5 per-outcome routes + 1 aggregate)
          </span>
        </div>
        {portsOpen && <PortsDrawer />}
      </div>

      {/* Findings drawer */}
      {findingsOpen && <FindingsDrawer outcomeId={findingsOpen} onClose={()=>setFindingsOpen(null)} />}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// PIPELINE INDICATOR
// ═══════════════════════════════════════════════════════════════
function PipelineIndicator({expanded, setExpanded}) {
  const stages = [
    {n:1, name:"Source Sufficiency", status:"verified"},
    {n:2, name:"Research Complete", status:"verified"},
    {n:3, name:"Outline Coverage", status:"verified"},
    {n:4, name:"No Hallucination", status:"verified"},
    {n:5, name:"Final Quality Gate", status:"in_progress", current:true},
  ];
  return (
    <div style={{borderBottom:`1px solid ${c.border}`,background:c.bg}}>
      <div onClick={()=>setExpanded(!expanded)} style={{padding:"8px 22px",display:"flex",alignItems:"center",gap:14,cursor:"pointer"}}>
        <span style={{fontSize:9,fontWeight:700,color:c.textTer,letterSpacing:1,textTransform:"uppercase"}}>Evaluator Pipeline</span>
        <div style={{flex:1,display:"flex",alignItems:"center",gap:6,overflow:"hidden"}}>
          {stages.map((s,i)=>(
            <React.Fragment key={i}>
              <div style={{
                display:"flex",alignItems:"center",gap:5,padding:"3px 9px",
                background:s.current?`${c.amber}15`:s.status==="verified"?`${c.green}10`:c.bgPanel,
                border:`1px solid ${s.current?c.amber+"50":s.status==="verified"?c.green+"30":c.border}`,
                borderRadius:11,
                fontSize:10,fontWeight:s.current?700:500,
                color:s.current?c.amber:s.status==="verified"?c.green:c.textSec,
              }}>
                <span style={{fontSize:8}}>{s.status==="verified"?"✓":s.current?"⟳":"○"}</span>
                <span>⊜ {s.name}</span>
              </div>
              {i<stages.length-1 && <span style={{color:c.textTer,fontSize:9}}>→</span>}
            </React.Fragment>
          ))}
        </div>
        <span style={{fontSize:9,color:c.textTer,fontStyle:"italic"}}>5 evaluators in chain · {expanded?"hide details ▴":"expand ▾"}</span>
      </div>
      {expanded && (
        <div style={{padding:"4px 22px 12px",fontSize:10,color:c.textSec,lineHeight:1.6,fontStyle:"italic"}}>
          This Evaluator is the final stage. Upstream stages each verified their concern; the work product reaches here only after they all passed (or were overridden by user). The final stage applies holistic checks and the meta-outcome ("output achieves the goal").
        </div>
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// ADD OUTCOME FORM (three entry paths + inferred plan)
// ═══════════════════════════════════════════════════════════════
function AddOutcomeForm({onClose}) {
  const [description, setDescription] = useState("");
  const [showInferred, setShowInferred] = useState(false);

  // Simulate auto-detection
  const handleDescriptionChange = (e) => {
    setDescription(e.target.value);
    if (e.target.value.length > 20) setShowInferred(true);
  };

  return (
    <div style={{margin:"14px 28px 0",padding:18,background:c.bgPanel,border:`1px solid ${c.accent}40`,borderRadius:8}}>
      <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:12}}>
        <div style={{fontSize:12,fontWeight:700,color:c.text}}>+ Add Outcome</div>
        <span onClick={onClose} style={{fontSize:14,color:c.textSec,cursor:"pointer"}}>✕</span>
      </div>

      <FieldLabel>Describe what to check</FieldLabel>
      <textarea
        defaultValue="Make sure all citations resolve to real cases. The citation-checker should verify each citation against CourtListener; flag any that can't be found."
        onChange={handleDescriptionChange}
        placeholder="Plain language — what does success look like for this outcome?"
        style={{width:"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:4,padding:"9px 11px",fontSize:11,color:c.text,fontFamily:sans,resize:"vertical",minHeight:60,outline:"none",lineHeight:1.5}}
      />

      <div style={{display:"flex",gap:8,alignItems:"center",marginTop:10,flexWrap:"wrap"}}>
        <span style={{fontSize:10,color:c.textTer}}>OR start from:</span>
        <Btn ghost sm>💬 Ask Task Agent</Btn>
        <Btn ghost sm>📋 Load preset</Btn>
        <Btn ghost sm><span style={{color:c.metaAccent}}>⊕</span> Meta — overall quality check</Btn>
      </div>

      {/* Inferred plan */}
      {(showInferred || description.length === 0) && (
        <div style={{marginTop:16,padding:14,background:c.bg,borderRadius:6,border:`1px solid ${c.accent}30`}}>
          <div style={{display:"flex",alignItems:"center",gap:6,marginBottom:8}}>
            <span style={{fontSize:11}}>🪄</span>
            <span style={{fontSize:10,fontWeight:700,color:c.accent,letterSpacing:0.5,textTransform:"uppercase"}}>Inferred Plan</span>
            <span style={{fontSize:9,color:c.textTer,fontStyle:"italic"}}>· you can adjust any of this</span>
          </div>

          <div style={{fontSize:11,color:c.text,lineHeight:1.7,marginBottom:12}}>
            This looks like a <strong style={{color:c.accent}}>citation accuracy check</strong>. I'll use the <strong>citation-checker specialist agent</strong> to verify each citation in the brief against CourtListener.
            <br/><br/>
            <span style={{color:c.textSec}}>Result type:</span> <strong>Checklist</strong> (each citation gets verified individually, threshold 100%)
            <br/>
            <span style={{color:c.textSec}}>Reference materials needed:</span> CourtListener (already connected) · Case Record (already wired)
            <br/>
            <span style={{color:c.textSec}}>If it fails:</span> Route to <Select opts={["⊜ Citation Specialist (revise)","↻ Brief Drafter (revise)","👤 Human Review","+ Pick destination..."]} v="⊜ Citation Specialist (revise)" compact /> 
            <br/>
            <span style={{color:c.textSec}}>Retry:</span> up to 3 attempts · <span style={{color:c.textSec}}>Can it gather more info:</span> yes — allowed to search Westlaw if CourtListener fails (capped at $0.50)
          </div>

          <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
            <Btn ghost sm>Adjust details…</Btn>
            <Btn ghost sm>💬 Refine with Task Agent</Btn>
            <Btn primary sm onClick={onClose}>Add outcome →</Btn>
          </div>
        </div>
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// OUTCOME DATA
// ═══════════════════════════════════════════════════════════════
const OUTCOMES = [
  {
    id:"o0", isMeta:true,
    name:"Output achieves the goal",
    desc:"The final brief opposes the motion to dismiss by showing (1) standing exists, (2) the complaint states a claim under Rule 12(b)(6), (3) the heightened pleading standard is met, and (4) loss causation is adequately alleged.",
    resultType:"score",
    required:true,
    score:0.82, threshold:0.85,
    status:"failed",
    routeOnFail:"👤 Human Review",
    summary:"Holistic LLM judgment against the goal statement above. Reads the final draft, checks whether it accomplishes what the user asked for. Threshold 0.85.",
    decomposed:true,
    method:"agent_assess",
  },
  {
    id:"o1", name:"Citations are present",
    desc:"Brief contains at least the citations required to support the argument.",
    resultType:"pass_fail", required:true,
    status:"verified",
    routeOnFail:"↻ Brief Drafter (revise)",
    summary:"Auto-check using regex pattern matching for citation format. Fails if fewer than 5 citations found.",
    method:"auto_check",
    lastResultText:"19 citations found.",
  },
  {
    id:"o2", name:"Brief addresses all motion issues",
    desc:"The motion to dismiss raised 7 issues; the brief addresses each.",
    resultType:"checklist", required:true,
    status:"verified", count:{passed:7, total:7},
    routeOnFail:"↻ Brief Drafter (with missing issues listed)",
    summary:"Extract issues from Motion to Dismiss; verify each is addressed in the brief via agent assessment. Pass when all 7 addressed.",
    method:"check_claims",
  },
  {
    id:"o3", name:"Citations resolve to real cases",
    desc:"All cases cited resolve to real cases in CourtListener. Each citation verified via citation-checker; cases not found within 3 lookup attempts return as failures with the case name and search attempts logged.",
    resultType:"checklist", required:true,
    status:"failed", count:{passed:16, total:19},
    dependsOn:["Citations are present"],
    routeOnFail:"⊜ Citation Specialist (revise + verify)",
    summary:"Citation-checker specialist agent verifies each citation against CourtListener and Westlaw. Each citation is a checkpoint.",
    method:"specialist",
    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,
    status:"verified",
    routeOnFail:"↻ Brief Drafter (revise unsupported facts)",
    summary:"Extract FactualAssertion claims from upstream extractor; verify each against the Case Record. 100% pass rate required.",
    method:"check_claims",
    lastResultText:"34 of 34 factual claims supported.",
  },
  {
    id:"o5", name:"Argument is persuasive",
    desc:"The brief constructs a persuasive case, addressing counterarguments and supporting reasoning with cited authority.",
    resultType:"score", required:false,
    status:"verified", score:0.78, threshold:0.7,
    routeOnFail:"↻ Brief Drafter (with rubric feedback)",
    summary:"Agent assessment using 'My legal-arg rubric' (4 sub-criteria, weighted). Threshold 0.7 for desired pass.",
    method:"agent_assess",
    decomposed:true,
  },
  {
    id:"o6", name:"Tone is professional",
    desc:"Consistent with court filing standards.",
    resultType:"score", required:false,
    status:"verified", score:0.85, threshold:0.7,
    routeOnFail:"↻ Brief Drafter (tone notes)",
    summary:"Agent assessment with 1-5 anchored scale. Threshold 0.7 for desired pass.",
    method:"agent_assess",
  },
  {
    id:"o7", name:"Brief between 4000-8000 words",
    desc:"Word count falls within the court's required range.",
    resultType:"pass_fail", required:true,
    status:"verified",
    routeOnFail:"↻ Brief Drafter (trim or expand)",
    summary:"Auto-check on word count. Min 4000, max 8000.",
    method:"auto_check",
    lastResultText:"6,247 words.",
  },
];

// ═══════════════════════════════════════════════════════════════
// OUTCOME CARD
// ═══════════════════════════════════════════════════════════════
function OutcomeCard({outcome, expanded, onToggle, onViewFindings}) {
  const isMeta = outcome.isMeta;
  return (
    <div style={{
      background:c.bgCard,
      border:`1px solid ${expanded ? (isMeta?c.metaAccent+"50":c.accent+"40") : isMeta?c.metaAccent+"30":c.border}`,
      borderRadius:7,overflow:"hidden",transition:"border 0.15s",
      ...(isMeta && {boxShadow:`inset 3px 0 0 ${c.metaAccent}`})
    }}>
      {/* 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,flexWrap:"wrap"}}>
            {isMeta && <span style={{fontSize:12,color:c.metaAccent}}>⊕</span>}
            <span style={{fontSize:13,fontWeight:600,color:c.text}}>{outcome.name}</span>
            <ResultTypePill type={isMeta?"meta":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>}
            {outcome.routeOnFail && (
              <span style={{fontSize:9,color:c.textTer,fontFamily:sans,marginLeft:"auto"}}>
                if fails → <span style={{color:c.textSec}}>{outcome.routeOnFail}</span>
              </span>
            )}
          </div>
          {!expanded && (
            <div style={{fontSize:10,color:c.textSec,lineHeight:1.4,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>
              {outcome.desc}
            </div>
          )}
        </div>

        <div style={{display:"flex",alignItems:"center",gap:8}}>
          <ResultIndicator outcome={outcome} />
          {outcome.status === "failed" && (
            <Btn ghost sm onClick={(e)=>{e.stopPropagation();onViewFindings();}}>View findings</Btn>
          )}
        </div>
      </div>

      {expanded && (
        <OutcomeCardExpanded outcome={outcome} onViewFindings={onViewFindings} />
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// OUTCOME CARD — EXPANDED (summary view + Adjust panel)
// ═══════════════════════════════════════════════════════════════
function OutcomeCardExpanded({outcome, onViewFindings}) {
  const [adjustOpen, setAdjustOpen] = useState(false);
  const isMeta = outcome.isMeta;

  return (
    <div style={{borderTop:`1px solid ${c.border}`,padding:"4px 22px 22px",background:c.bg}}>

      {/* What this checks */}
      <SectionHeader>What this checks</SectionHeader>
      <Textarea value={outcome.desc} h={isMeta?100:70} />
      <HelperText>
        {isMeta
          ? "This is the meta-outcome: a holistic check against the stated goal. The description above is the goal statement."
          : "This description is the operative spec — what the verifier reads."}
      </HelperText>

      {/* Plain-English plan (summary mode default) */}
      <SectionHeader>The plan</SectionHeader>
      <div style={{padding:13,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`,fontSize:11,color:c.text,lineHeight:1.7}}>
        {outcome.summary}
      </div>
      <div style={{marginTop:10,display:"flex",gap:8,alignItems:"center"}}>
        <Btn ghost sm onClick={()=>setAdjustOpen(!adjustOpen)}>{adjustOpen ? "Hide details ▴" : "Adjust details ▾"}</Btn>
        <Btn ghost sm>💬 Refine with Task Agent</Btn>
        {isMeta && <span style={{fontSize:9,color:c.textTer,fontStyle:"italic",marginLeft:"auto"}}>Goal can be auto-filled from Task Blueprint when available.</span>}
      </div>

      {adjustOpen && <AdjustPanel outcome={outcome} />}

      {/* Routing */}
      <SectionHeader>If this fails</SectionHeader>
      <div style={{padding:13,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
        <div style={{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",fontSize:11,color:c.text,lineHeight:1.8}}>
          Send to
          <Select opts={[
            outcome.routeOnFail,
            "↻ Brief Drafter (revise)",
            "⊜ Citation Specialist (re-verify)",
            "📚 Research Module (gather more)",
            "👤 Human Review (with findings)",
            "↺ Loop back (default failed_out)",
            "+ Custom destination..."
          ]} v={outcome.routeOnFail} compact />
          <span style={{fontSize:10,color:c.textTer}}>· Custom port name:</span>
          <Input value={outcome.id === "o3" ? "to_citation_specialist" : "auto"} w={180} />
        </div>

        <div style={{marginTop:10,padding:10,background:c.bg,borderRadius:5}}>
          <Check label="Try again first (up to 3 retries)" checked={true} />
          <Check label="Send findings as inline document comments" checked={outcome.id === "o5"} hint="Comments appear anchored to the relevant sections for human editing." />
          <Check label="Send to human if still failing after retries" checked={true} />
          <Check label="Can gather more info during verification" checked={outcome.id === "o3"} hint="Specialist may search Westlaw if CourtListener fails. Capped at $0.50 and 3 attempts." />
        </div>
      </div>

      {/* Last result */}
      {outcome.status && outcome.status !== "pending" && (
        <>
          <SectionHeader>Last result (iteration 2)</SectionHeader>
          <ResultSummary outcome={outcome} onViewFindings={onViewFindings} />
        </>
      )}
    </div>
  );
}

// ─── Adjust panel (the form-fill power view) ───
function AdjustPanel({outcome}) {
  return (
    <div style={{marginTop:12,padding:14,background:c.bgPanel,borderRadius:6,border:`1px solid ${c.border}`}}>
      <div style={{fontSize:9,fontWeight:700,color:c.textTer,letterSpacing:1,textTransform:"uppercase",marginBottom:10}}>Adjust Details</div>

      <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:16}}>
        <div>
          <FieldLabel>Check by</FieldLabel>
          <Select opts={[
            "Auto-check (mechanical)",
            "Check claims (from extractor)",
            "Specialist agent (citation-checker, etc.)",
            "Agent assessment (LLM judge with rubric)",
            "Multiple checks",
          ]} v={
            outcome.method==="auto_check" ? "Auto-check (mechanical)" :
            outcome.method==="check_claims" ? "Check claims (from extractor)" :
            outcome.method==="specialist" ? "Specialist agent (citation-checker, etc.)" :
            "Agent assessment (LLM judge with rubric)"
          } compact />
          <HelperText>Auto-detected from description above. Override if needed.</HelperText>

          <FieldLabel mt>Specialist agent</FieldLabel>
          <Select opts={["citation-checker","fact-checker","legal-researcher","custom…"]} v="citation-checker" compact />

          <FieldLabel mt>Reference materials needed</FieldLabel>
          <Select opts={["Required for verification","Used if available","Audit only","Not needed"]} v={outcome.referenceRequired === "required" ? "Required for verification" : "Used if available"} compact />

          <FieldLabel mt>Iteration policy</FieldLabel>
          <Select opts={["Re-verify always","Re-verify if subject changed","Re-verify only if previously failed","Verify once"]} v="Re-verify if subject changed" compact />
        </div>

        <div>
          <FieldLabel>Pass threshold</FieldLabel>
          {outcome.resultType === "pass_fail" && <div style={{fontSize:11,color:c.text,padding:"7px 9px",background:c.bg,borderRadius:4,border:`1px solid ${c.border}`}}>Passes when check returns true</div>}
          {outcome.resultType === "checklist" && <Select opts={["all items","≥ 95% of items","at least N items"]} v="all items" compact />}
          {outcome.resultType === "score" && <Input value={outcome.threshold?.toString() || "0.7"} />}

          <FieldLabel mt>Cost cap (this outcome)</FieldLabel>
          <Input value="$0.50" />

          <FieldLabel mt>Decomposition</FieldLabel>
          <div style={{padding:10,background:c.bg,borderRadius:5,border:`1px solid ${c.border}`}}>
            <Check label="Break into sub-criteria (rubric)" checked={!!outcome.decomposed} />
            {outcome.decomposed && <Btn ghost sm>⚡ Help me build sub-criteria</Btn>}
          </div>

          <FieldLabel mt>Status</FieldLabel>
          <div style={{display:"flex",gap:14}}>
            <Check label="Required" checked={outcome.required} />
            <Check label="Inverse (must NOT be true)" checked={false} />
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── Result indicator (compact, card header) ───
function ResultIndicator({outcome}) {
  const isMeta = outcome.isMeta;
  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.red,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:70,height:6,background:c.bg,borderRadius:3,overflow:"hidden"}}>
          <div style={{width:`${outcome.score*100}%`,height:"100%",background:passed?c.green:c.red,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.red,fontFamily:mono,fontWeight:700}}>{outcome.score.toFixed(2)}</span>
        <StatusBadge status={passed?"verified":"failed"} />
      </div>
    );
  }
}

// ─── Result summary (in expanded card body) ───
function ResultSummary({outcome, onViewFindings}) {
  const isFail = outcome.status === "failed";
  if (!isFail) {
    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}}>{outcome.lastResultText || "Passed."}</div>
      </div>
    );
  }

  // FAIL — show routing status as default, with "View findings" affordance
  return (
    <div style={{padding:12,background:`${c.amber}08`,borderRadius:6,border:`1px solid ${c.amber}30`}}>
      <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8}}>
        <div style={{display:"flex",alignItems:"center",gap:8}}>
          <Pill color={c.amber}>↻ SENT FOR REVISION</Pill>
          {outcome.resultType==="checklist" && <span style={{fontSize:10,color:c.red,fontFamily:mono}}>{outcome.count.passed} of {outcome.count.total}</span>}
          {outcome.resultType==="score" && <span style={{fontSize:10,color:c.red,fontFamily:mono}}>score {outcome.score.toFixed(2)} / threshold {outcome.threshold.toFixed(2)}</span>}
        </div>
        <span style={{fontSize:10,color:c.textSec,fontFamily:mono}}>iteration 2 of 3</span>
      </div>

      <div style={{fontSize:11,color:c.text,lineHeight:1.6,marginBottom:10}}>
        Work routed to <strong style={{color:c.accent}}>{outcome.routeOnFail}</strong> with structured feedback. Revising now.
      </div>

      <div style={{display:"flex",gap:8,flexWrap:"wrap"}}>
        <Btn ghost sm onClick={onViewFindings}>📋 View findings (3)</Btn>
        <Btn ghost sm>💬 Send comments to drafter</Btn>
        <Btn ghost sm>👤 Override — send to human</Btn>
        <Btn ghost sm>↻ Re-evaluate now</Btn>
      </div>

      <div style={{fontSize:9,color:c.textTer,marginTop:10,fontStyle:"italic",lineHeight:1.5}}>
        Findings are written to the audit trail and available here on demand. The system has routed the work to the destination above; you don't need to address the findings unless you want to override or refine.
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// FINDINGS DRAWER
// ═══════════════════════════════════════════════════════════════
function FindingsDrawer({outcomeId, onClose}) {
  const findings = outcomeId === "o3" ? [
    {
      id:"f1", severity:"high", state:"proposed",
      title:"Citation #5 — Smith v. Jones, 432 F.3d 12 (5th Cir. 2007)",
      detail:"Case not found in CourtListener after 3 lookup attempts. Tried alternate reporters (F.2d, F. Supp.). Westlaw also returned no match.",
      authority:["tool_verified", "model_judgment_only"],
      location:"§III ¶4, line 12",
    },
    {
      id:"f2", severity:"high", state:"proposed",
      title:"Citation #8 — In re Acme Corp., 215 B.R. 89 (Bankr. S.D.N.Y. 1997)",
      detail:"Citation format valid but no matching case in CourtListener. Possible alternative: In re Acme Corp., 215 B.R. 89 (Bankr. D.N.J. 1997) — different jurisdiction.",
      authority:["tool_verified"],
      location:"§IV ¶2, line 7",
    },
    {
      id:"f3", severity:"medium", state:"proposed",
      title:"Citation #15 — Doe v. Smith (incomplete)",
      detail:"Citation incomplete — missing reporter, court, and year. Cannot verify.",
      authority:["deterministic_check"],
      location:"§V ¶1, line 3",
    },
  ] : [];

  return (
    <div onClick={onClose} style={{position:"absolute",top:0,right:0,bottom:0,left:0,background:"rgba(0,0,0,0.4)",zIndex:50,display:"flex",justifyContent:"flex-end"}}>
      <div onClick={(e)=>e.stopPropagation()} style={{width:560,maxWidth:"90%",height:"100%",background:c.bgPanel,borderLeft:`1px solid ${c.border}`,display:"flex",flexDirection:"column",boxShadow:"-10px 0 30px #0008"}}>

        {/* Drawer header */}
        <div style={{padding:"14px 18px",borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",justifyContent:"space-between"}}>
          <div>
            <div style={{fontSize:13,fontWeight:700,color:c.text}}>Findings · Citations resolve to real cases</div>
            <div style={{fontSize:10,color:c.textSec,marginTop:2,fontFamily:sans}}>3 findings · all proposed · iteration 2</div>
          </div>
          <span onClick={onClose} style={{fontSize:18,color:c.textSec,cursor:"pointer",padding:"0 6px"}}>✕</span>
        </div>

        {/* Drawer body */}
        <div style={{flex:1,overflowY:"auto",padding:"14px 18px"}}>

          <div style={{padding:12,background:c.bg,borderRadius:6,border:`1px solid ${c.border}`,marginBottom:14,fontSize:11,color:c.text,lineHeight:1.5}}>
            <div style={{fontSize:10,fontWeight:600,color:c.textTer,letterSpacing:0.5,marginBottom:4}}>WHAT HAPPENED</div>
            The system attempted to verify 19 citations. 16 verified successfully. 3 could not be verified after 3 attempts each using CourtListener; alternate reporters and Westlaw lookups also failed.
            <br/><br/>
            <div style={{fontSize:10,fontWeight:600,color:c.textTer,letterSpacing:0.5,marginBottom:4}}>WHAT'S BEING DONE</div>
            <span style={{color:c.amber}}>↻ The work has been routed to Citation Specialist with the structured feedback below.</span> The specialist will attempt re-verification and either correct the citations or remove them. If still failing after retry, this finding escalates to you.
          </div>

          <div style={{fontSize:10,fontWeight:700,color:c.textTer,letterSpacing:1,textTransform:"uppercase",marginBottom:10}}>Findings ({findings.length})</div>

          <div style={{display:"flex",flexDirection:"column",gap:10}}>
            {findings.map((f, i) => (
              <FindingCard key={f.id} finding={f} />
            ))}
          </div>

          <div style={{marginTop:18,padding:12,background:c.bgCard,borderRadius:6,border:`1px solid ${c.border}`}}>
            <div style={{fontSize:10,fontWeight:600,color:c.textTer,letterSpacing:0.5,marginBottom:8,textTransform:"uppercase"}}>Delivery options</div>
            <Check label="Send findings as inline document comments (anchored to brief sections)" checked={false} hint="Useful for human-edit workflows. Comments appear in the Brief draft at the cited locations." />
            <Check label="Post findings to Task Forum / Board" checked={true} />
            <Check label="Save full audit bundle to Run Inspector" checked={true} hint="Always on. Findings are part of the permanent audit trail regardless of UI display." />
          </div>
        </div>

        {/* Drawer footer — actions */}
        <div style={{padding:"12px 18px",borderTop:`1px solid ${c.border}`,display:"flex",gap:8,flexWrap:"wrap",background:c.bg}}>
          <Btn ghost sm>📎 Open evidence</Btn>
          <Btn ghost sm>✓ Accept guidance</Btn>
          <Btn ghost sm>✕ Mark wrong</Btn>
          <Btn ghost sm>⤓ Supersede</Btn>
          <Btn ghost sm>👤 Send to human</Btn>
          <Btn ghost sm>💬 Ask Task Agent</Btn>
          <Btn accent sm>↻ Fork from here</Btn>
        </div>
      </div>
    </div>
  );
}

function FindingCard({finding}) {
  const sevColor = finding.severity === "high" ? c.red : finding.severity === "medium" ? c.amber : c.textSec;
  return (
    <div style={{padding:12,background:c.bgCard,border:`1px solid ${c.border}`,borderRadius:6,borderLeft:`3px solid ${sevColor}`}}>
      <div style={{display:"flex",alignItems:"center",gap:8,marginBottom:6,flexWrap:"wrap"}}>
        <Pill color={sevColor}>{finding.severity}</Pill>
        <Pill color={c.textSec}>{finding.state}</Pill>
        {finding.authority.map((a,i) => <Tag key={i} color={c.textTer}>{a}</Tag>)}
        <span style={{fontSize:9,color:c.textTer,fontFamily:mono,marginLeft:"auto"}}>{finding.location}</span>
      </div>
      <div style={{fontSize:11,color:c.text,fontWeight:600,marginBottom:4}}>{finding.title}</div>
      <div style={{fontSize:10,color:c.textSec,lineHeight:1.5}}>{finding.detail}</div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// PORT DRAWER
// ═══════════════════════════════════════════════════════════════
function PortsDrawer() {
  const inputs = [
    {label:"subject_in", role:"required · data", color:c.accent, wired:"◆ Final Brief Draft"},
    {label:"reference_in", role:"optional · data", color:c.accent, wired:null},
    {label:"evidence_in.N", role:"optional · expandable", color:c.accent, wired:"◇ Case Record + Motion"},
    {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:"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:"any_failed_out", role:"aggregate fallback", color:c.red, wired:null, note:"fires when any outcome fails (convenience)"},
    {label:"meta_failed_out", role:"per-outcome route", color:c.metaAccent, wired:"👤 Human Review"},
    {label:"citations_present_failed_out", role:"per-outcome route", color:c.red, wired:"↻ Brief Drafter"},
    {label:"motion_issues_failed_out", role:"per-outcome route", color:c.red, wired:"↻ Brief Drafter"},
    {label:"to_citation_specialist", role:"per-outcome route · custom name", color:c.red, wired:"⊜ Citation Specialist"},
    {label:"factual_claims_failed_out", role:"per-outcome route", color:c.red, wired:"↻ Brief Drafter"},
    {label:"persuasive_failed_out", role:"per-outcome route", color:c.red, wired:"↻ Brief Drafter"},
    {label:"tone_failed_out", role:"per-outcome route", color:c.red, wired:"↻ Brief Drafter"},
    {label:"word_count_failed_out", role:"per-outcome route", color:c.red, wired:"↻ Brief Drafter"},
    {label:"feedback_out", role:"data", color:c.purple, wired:"↺ Loop.feedback_in"},
    {label:"findings_out", role:"data · structured", color:c.purple, wired:null, note:"to forum / comments"},
    {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,maxHeight:280,overflowY:"auto"}}>
      <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.6}}>
        <div style={{fontSize:9,fontWeight:700,color:c.textTer,letterSpacing:0.5,marginBottom:8}}>OUTPUTS — per-outcome routes are auto-named from outcomes; custom names override</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,flexShrink:0}} />
      <span style={{color:port.color,fontFamily:mono,minWidth:200,fontWeight:600,fontSize:10}}>{port.label}</span>
      <span style={{color:c.textTer,fontFamily:mono,fontSize:9,minWidth:160}}>{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 VIEW (lightly updated for V3 consistency)
// ═══════════════════════════════════════════════════════════════
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"}}>
        <div style={{width:300,minWidth:300,borderRight:`1px solid ${c.border}`,padding:"18px 16px",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}}>← ◆ Final Brief Draft</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}}>→ ⊜ Final Quality Gate</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 work for parsing. Authority-required types auto-upgrade.</HelperText>

          <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" />

          <SectionLabel mt>Caching</SectionLabel>
          <Check label="Cache results" checked={true} />

          <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>

        <div style={{flex:1,overflowY:"auto",background:c.bg}}>
          <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</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>

          <div style={{padding:"22px 28px 0",display:"flex",alignItems:"center",justifyContent:"space-between"}}>
            <div>
              <div style={{fontSize:15,fontWeight:700,color:c.text}}>Claim Types</div>
              <div style={{fontSize:11,color:c.textSec,marginTop:3}}>What to extract. Each type defines its instruction, fields, and downstream use.</div>
            </div>
            <Btn accent sm>+ Add Claim Type</Btn>
          </div>

          <div style={{padding:"14px 28px 28px",display:"flex",flexDirection:"column",gap:8}}>
            {[
              {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"], expanded:expandedType==="ct1"},
              {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"], expanded:expandedType==="ct2"},
              {id:"ct3", name:"LegalProposition", desc:"Statements of legal rules or principles.", count:7, evaluable:true, authority:false, consumedBy:[], expanded:expandedType==="ct3"},
              {id:"ct4", name:"OpposingArgument", desc:"References to opposing counsel's arguments.", count:4, evaluable:true, authority:false, consumedBy:[], expanded:expandedType==="ct4"},
              {id:"ct5", name:"ProceduralFact", desc:"Statements about case procedural posture.", count:2, evaluable:false, authority:false, consumedBy:[], expanded:expandedType==="ct5"},
            ].map(t => (
              <div key={t.id} style={{background:c.bgCard,border:`1px solid ${t.expanded?c.accent+"40":c.border}`,borderRadius:7,overflow:"hidden"}}>
                <div onClick={()=>setExpandedType(t.expanded?null:t.id)} style={{padding:"13px 16px",cursor:"pointer",display:"flex",alignItems:"center",gap:12}}>
                  <span style={{color:c.textTer,fontSize:11,minWidth:10}}>{t.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}}>{t.name}</span>
                      {t.evaluable ? <Pill color={c.green}>EVALUABLE</Pill> : <Pill color={c.textTer}>NOT EVALUABLE</Pill>}
                      {t.authority && <Pill color={c.amber}>NEEDS SPECIALIST</Pill>}
                      {t.consumedBy.length>0 && <Pill color={c.cyan}>USED BY {t.consumedBy.length}</Pill>}
                    </div>
                    {!t.expanded && <div style={{fontSize:10,color:c.textSec,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}}>{t.desc}</div>}
                  </div>
                  <span style={{fontSize:14,fontWeight:700,color:t.count>0?c.text:c.textTer,fontFamily:mono}}>{t.count}</span>
                </div>
                {t.expanded && (
                  <div style={{borderTop:`1px solid ${c.border}`,padding:"6px 22px 22px",background:c.bg}}>
                    <SectionHeader>What this extracts</SectionHeader>
                    <FieldLabel>Description</FieldLabel>
                    <Textarea value={t.desc} h={50} />
                    <FieldLabel mt>Extraction instruction</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={60} />
                    {t.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`}}>
                          {t.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>
            ))}
          </div>
        </div>
      </div>

      <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:"◆ Final Brief Draft"}} />
            </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:"⊜ Final Quality Gate"}} />
              <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>
  );
}