Elnor Repo Reader

Q_TASK_EVALUATOR_EXTRACTOR_V4.jsx

Design Mockups/DOC23 Mockups/Q_TASK_EVALUATOR_EXTRACTOR_V4.jsx

Short text page c7bf77c32382. 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_V4.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 — V4
// Major changes from V3:
//   • Sidebar gone (moved to slide-out settings drawer)
//   • No pipeline indicator, no verdict banner — replaced with one-line status
//   • No destination picker in outcomes — routing IS the canvas wiring;
//     the outcome configures only RECOVERY ACTIONS the agent tries before
//     firing failed_out
//   • New "Feedback to next module" section — explicit output shape
//   • One accent color; status colors only (green/red/amber)
//   • Outcomes visible immediately, three short paragraphs per outcome
//   • Meta-outcome has no special visual treatment (just a tag)
// ═══════════════════════════════════════════════════════════════

const c = {
  bg:"#0f1117",
  bgCard:"#161920",
  bgHover:"#1c1f2a",
  bgPanel:"#131620",
  border:"#262a36",
  borderSoft:"#1f232e",
  text:"#e2e4ea",
  textSec:"#8b8fa3",
  textTer:"#5d6178",
  accent:"#5b8af5",
  green:"#34d399",
  red:"#f87171",
  amber:"#fbbf24",
};
const sans = "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
const mono = "'SF Mono','Cascadia Code','Fira Code', monospace";

// ─── Helpers ───
const Btn = ({children, primary, ghost, sm, onClick}) => {
  const styles = primary
    ? {background:c.accent,color:"#fff",border:"none"}
    : ghost
    ? {background:"transparent",color:c.textSec,border:"none"}
    : {background:c.bgHover,color:c.text,border:`1px solid ${c.border}`};
  return <button onClick={onClick} style={{...styles,padding:sm?"4px 9px":"7px 13px",borderRadius:4,fontSize:sm?10:11,fontWeight:500,cursor:"pointer",fontFamily:sans,whiteSpace:"nowrap"}}>{children}</button>;
};
const Input = ({value, w, onChange, placeholder}) => (
  <input defaultValue={value} placeholder={placeholder} onChange={onChange||(()=>{})} style={{width:w||"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:3,padding:"4px 8px",fontSize:11,color:c.text,fontFamily:sans,outline:"none"}} />
);
const Textarea = ({value, h, placeholder, onChange}) => (
  <textarea defaultValue={value} placeholder={placeholder} onChange={onChange||(()=>{})}
    style={{width:"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:4,padding:"10px 12px",fontSize:12,color:c.text,fontFamily:sans,resize:"vertical",minHeight:h||72,outline:"none",lineHeight:1.55}} />
);
const Select = ({opts, v, onChange, w}) => (
  <select defaultValue={v} onChange={onChange||(()=>{})} style={{width:w||"100%",background:c.bg,border:`1px solid ${c.border}`,borderRadius:3,padding:"4px 8px",fontSize:11,color:c.text,fontFamily:sans,outline:"none"}}>
    {opts.map((o,i)=><option key={i}>{o}</option>)}
  </select>
);
const Check = ({label, checked, onChange}) => (
  <label style={{display:"flex",alignItems:"center",gap:9,fontSize:12,color:c.text,cursor:"pointer",padding:"4px 0",fontFamily:sans}}>
    <span style={{width:14,height:14,borderRadius:3,border:`1px solid ${checked?c.accent:c.border}`,background:checked?c.accent:"transparent",display:"flex",alignItems:"center",justifyContent:"center",flexShrink:0}}>
      {checked && <span style={{color:"#fff",fontSize:9}}>✓</span>}
    </span>
    <span>{label}</span>
  </label>
);

// ─── Status dot + label ───
const StatusDot = ({status}) => {
  const map = {
    verified: c.green,
    failed: c.red,
    incomplete: c.amber,
    pending: c.textTer,
    in_progress: c.amber,
  };
  return <span style={{width:8,height:8,borderRadius:"50%",background:map[status]||c.textTer,flexShrink:0,display:"inline-block"}} />;
};

// ─── Result indicator (compact, right side of card header) ───
const ResultIndicator = ({outcome}) => {
  if (!outcome.status || outcome.status === "pending") {
    return <span style={{fontSize:10,color:c.textTer,fontFamily:mono}}>—</span>;
  }
  if (outcome.resultType === "pass_fail") {
    return <span style={{fontSize:11,color:outcome.status==="verified"?c.green:c.red,fontFamily:mono,fontWeight:600}}>
      {outcome.status==="verified" ? "✓ pass" : "✕ fail"}
    </span>;
  }
  if (outcome.resultType === "checklist") {
    const passed = outcome.count.passed === outcome.count.total;
    return <span style={{fontSize:11,color:passed?c.green:c.red,fontFamily:mono,fontWeight:600}}>
      {outcome.count.passed}/{outcome.count.total}
    </span>;
  }
  if (outcome.resultType === "score") {
    const passed = outcome.score >= outcome.threshold;
    return (
      <div style={{display:"flex",alignItems:"center",gap:7}}>
        <div style={{position:"relative",width:60,height:5,background:c.bg,borderRadius:3,overflow:"hidden"}}>
          <div style={{width:`${outcome.score*100}%`,height:"100%",background:passed?c.green:c.red}} />
          <div style={{position:"absolute",left:`${outcome.threshold*100}%`,top:-2,width:1,height:9,background:c.textTer}} />
        </div>
        <span style={{fontSize:11,color:passed?c.green:c.red,fontFamily:mono,fontWeight:600,minWidth:32}}>{outcome.score.toFixed(2)}</span>
      </div>
    );
  }
};

// ═══════════════════════════════════════════════════════════════
// DATA
// ═══════════════════════════════════════════════════════════════
const OUTCOMES = [
  {
    id:"o0", meta:true,
    name:"Output achieves the goal",
    desc:"The final brief opposes the motion to dismiss by showing standing exists, the complaint states a claim under Rule 12(b)(6), the heightened pleading standard is met, and loss causation is adequately alleged.",
    resultType:"score", required:true,
    score:0.82, threshold:0.85,
    status:"failed",
    plan:"Holistic LLM assessment against the goal statement. A separate grading agent reads the final draft, rates how well it accomplishes what was asked, decomposes the assessment into 4 sub-criteria (standing, claim sufficiency, pleading standard, loss causation), and produces a weighted score.",
    recovery:"Re-grade once if score is between 0.80 and 0.85 to reduce noise. If still below threshold, fire failed_out with the rubric breakdown showing which sub-criteria fell short.",
    feedback:"Structured rubric breakdown — each sub-criterion's score, weak points cited from the brief, and suggested revisions per sub-criterion. Routed to whatever the canvas wires failed_out to.",
  },
  {
    id:"o1", name:"Citations are present",
    desc:"Brief contains at least the citations required to support the argument.",
    resultType:"pass_fail", required:true,
    status:"verified",
    plan:"Pattern-match the brief for citation format. Pass if 5 or more citations found.",
    recovery:"No recovery needed — this is a quick mechanical check. On failure, fire failed_out immediately with the count.",
    feedback:"Just the count and a note: 'Brief contains N citations, expected at least 5.'",
  },
  {
    id:"o2", name:"Brief addresses all motion issues",
    desc:"The motion to dismiss raised 7 issues; the brief addresses each.",
    resultType:"checklist", required:true,
    count:{passed:7, total:7},
    status:"verified",
    plan:"Extract the 7 issues from the Motion to Dismiss (already in claims_in). For each, check whether the brief responds — using agent assessment per issue. Pass when all 7 are addressed.",
    recovery:"For any issue not detected as addressed: re-check with a slightly different agent prompt before failing. If still not found, fire failed_out.",
    feedback:"Per-issue list — issue name, status (addressed/missing), brief location if found, suggested response if missing. Routed via failed_out.",
  },
  {
    id:"o3", name:"Citations resolve to real cases",
    desc:"All cases cited resolve to real cases. The citation-checker should verify each citation against CourtListener; flag any that can't be found.",
    resultType:"checklist", required:true,
    count:{passed:16, total:19},
    status:"failed",
    plan:"Citation-checker specialist agent verifies each of the 19 citations against CourtListener. Each citation is a checkpoint. Pass when all 19 verify.",
    recovery:"If CourtListener can't find a citation, try Westlaw before giving up. If a citation has incomplete format (missing reporter/year), ask the upstream drafter for the full citation. Try up to 3 times per citation. After all attempts, fire failed_out with the list of unresolvable citations.",
    feedback:"Per-citation breakdown — case name, full citation, location in brief (section + paragraph), what the verifier tried, why it failed, suggested fix (correct citation if found, or 'remove and re-research'). Routed via failed_out.",
  },
  {
    id:"o4", name:"No factual claims unsupported by record",
    desc:"Every factual assertion in the brief is supported by an explicit reference to the case record.",
    resultType:"pass_fail", required:true,
    status:"verified",
    plan:"Pull FactualAssertion claims from upstream extractor. For each, look for a record citation in proximity. Pass when 100% of factual claims have record support.",
    recovery:"For claims without nearby record cites: search the case record for supporting evidence; if found, suggest the citation back to the drafter. If not found, flag the claim. Try this once per unsupported claim before failing.",
    feedback:"List of unsupported assertions — claim text, brief location, search attempted, what was found (if anything), suggested action.",
  },
  {
    id:"o5", name:"Argument is persuasive",
    desc:"The brief constructs a persuasive case, addressing counterarguments and supporting reasoning with cited authority.",
    resultType:"score", required:false,
    score:0.78, threshold:0.7,
    status:"verified",
    plan:"Agent assessment using a 4-criterion rubric (authority strength, counterargument anticipation, logical chain, accessibility). Weighted average. Threshold 0.7.",
    recovery:"On borderline scores (within 0.05 of threshold), re-assess once with a different judge model before deciding. No further recovery — this is subjective; route to drafter if below.",
    feedback:"Rubric breakdown with score per sub-criterion, weakest area named, suggested revision focus.",
  },
  {
    id:"o6", name:"Tone is professional",
    desc:"Consistent with court filing standards. No casual register, no contractions outside quotes.",
    resultType:"score", required:false,
    score:0.85, threshold:0.7,
    status:"verified",
    plan:"Agent assessment on a 1-5 anchored scale (1 = casual, 3 = balanced, 5 = strict formal). Pass at 3 (normalized 0.7).",
    recovery:"Single re-check on borderline. No further recovery — tone judgment is fast and cheap; just route to drafter.",
    feedback:"Score with rationale citing specific brief passages that lowered the score (e.g., 'paragraph 4 uses contractions').",
  },
  {
    id:"o7", name:"Brief between 4000-8000 words",
    desc:"Word count within the court's required range.",
    resultType:"pass_fail", required:true,
    status:"verified",
    plan:"Word count check. Pass when within 4000-8000.",
    recovery:"No recovery for length — it's mechanical. Fire failed_out with the count and target.",
    feedback:"Just the count, target range, and whether under or over.",
  },
];

// ═══════════════════════════════════════════════════════════════
// MAIN
// ═══════════════════════════════════════════════════════════════
export default function Mockup() {
  const [view, setView] = useState("evaluator");
  return (
    <div style={{width:"100%",height:"100vh",background:c.bg,color:c.text,fontFamily:sans,display:"flex",flexDirection:"column",overflow:"hidden"}}>
      <div style={{height:28,minHeight:28,borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",padding:"0 14px",gap:8,background:c.bgPanel,fontSize:9,color:c.textTer,letterSpacing:0.4}}>
        REFERENCE MOCKUP V4 — SIMPLIFIED, OUTCOME-FIRST
        <div style={{flex:1}} />
        <div style={{display:"flex",gap:4,padding:2,background:c.bg,borderRadius:4}}>
          {[
            {id:"evaluator",label:"⊜ Evaluator"},
            {id:"extractor",label:"◈ Extractor"},
          ].map(t=>(
            <button key={t.id} onClick={()=>setView(t.id)}
              style={{padding:"3px 10px",fontSize:10,fontWeight:500,color:view===t.id?c.text:c.textSec,background:view===t.id?c.bgHover:"transparent",border:"none",borderRadius:3,cursor:"pointer",fontFamily:sans}}>
              {t.label}
            </button>
          ))}
        </div>
      </div>
      {view === "evaluator" ? <EvaluatorView /> : <ExtractorView />}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// EVALUATOR
// ═══════════════════════════════════════════════════════════════
function EvaluatorView() {
  const [expanded, setExpanded] = useState("o3");
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [findingsOpen, setFindingsOpen] = useState(null);
  const [adding, setAdding] = useState(false);
  const [portsOpen, setPortsOpen] = useState(false);

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

      {/* Compact top bar */}
      <div style={{height:44,minHeight:44,borderBottom:`1px solid ${c.borderSoft}`,display:"flex",alignItems:"center",justifyContent:"space-between",padding:"0 22px"}}>
        <div style={{display:"flex",alignItems:"center",gap:10}}>
          <span style={{fontSize:16}}>⊜</span>
          <span style={{fontSize:13,fontWeight:600,color:c.text}}>Final Quality Gate</span>
          <span style={{fontSize:10,color:c.textTer,fontFamily:mono,marginLeft:4}}>step.evaluator</span>
        </div>
        <div style={{display:"flex",gap:6,alignItems:"center"}}>
          <span onClick={()=>setSettingsOpen(true)} style={{fontSize:14,color:c.textSec,cursor:"pointer",padding:"4px 8px"}}>⚙</span>
          <Btn primary sm>▶ Run</Btn>
          <span style={{fontSize:16,color:c.textSec,cursor:"pointer",padding:"0 6px"}}>✕</span>
        </div>
      </div>

      {/* Inline verdict status (one line) */}
      <div style={{padding:"14px 28px 6px",display:"flex",alignItems:"center",gap:14}}>
        <StatusDot status="failed" />
        <span style={{fontSize:12,color:c.text}}>
          <span style={{color:c.red,fontWeight:600}}>Needs revision</span>
          <span style={{color:c.textSec,marginLeft:8}}>· iteration 2 of 3 · 7 of 8 outcomes passed · $0.41 · 14.2s</span>
        </span>
        <div style={{flex:1}} />
        <Btn ghost sm onClick={()=>setFindingsOpen("all")}>View all findings</Btn>
        <Btn ghost sm>Re-run</Btn>
      </div>

      {/* MAIN AREA */}
      <div style={{flex:1,overflowY:"auto"}}>
        <div style={{maxWidth:880,margin:"0 auto",padding:"18px 28px 40px"}}>

          {/* Outcomes header */}
          <div style={{display:"flex",alignItems:"baseline",justifyContent:"space-between",marginBottom:14}}>
            <div style={{fontSize:14,fontWeight:600,color:c.text}}>Outcomes</div>
            <Btn ghost sm onClick={()=>setAdding(!adding)}>{adding?"Cancel":"+ Add"}</Btn>
          </div>

          {adding && <AddOutcome onClose={()=>setAdding(false)} />}

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

        </div>
      </div>

      {/* Collapsible port drawer */}
      <div style={{borderTop:`1px solid ${c.borderSoft}`}}>
        <div onClick={()=>setPortsOpen(!portsOpen)} style={{padding:"7px 22px",display:"flex",alignItems:"center",gap:8,cursor:"pointer",fontSize:10,color:c.textTer,fontWeight:500}}>
          <span>{portsOpen?"▾":"▸"}</span>
          <span>Ports</span>
          <span style={{color:c.textTer,fontStyle:"italic",marginLeft:6,fontWeight:400}}>per-outcome failed_out routes + aggregate any_failed_out</span>
        </div>
        {portsOpen && <PortsDrawer />}
      </div>

      {/* Settings drawer */}
      {settingsOpen && <SettingsDrawer onClose={()=>setSettingsOpen(false)} />}

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

// ═══════════════════════════════════════════════════════════════
// OUTCOME CARD
// ═══════════════════════════════════════════════════════════════
function OutcomeCard({outcome, expanded, onToggle, onViewFindings}) {
  return (
    <div style={{
      background:c.bgCard,
      border:`1px solid ${expanded?c.border:c.borderSoft}`,
      borderRadius:6,overflow:"hidden",
    }}>

      {/* Collapsed row — single line */}
      <div onClick={onToggle} style={{padding:"11px 16px",cursor:"pointer",display:"flex",alignItems:"center",gap:12}}>
        <StatusDot status={outcome.status} />
        <span style={{flex:1,fontSize:12,color:c.text,minWidth:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>
          {outcome.name}
          {outcome.meta && <span style={{fontSize:9,color:c.textTer,marginLeft:8,fontStyle:"italic"}}>· overall goal</span>}
          {!outcome.required && <span style={{fontSize:9,color:c.textTer,marginLeft:8,fontStyle:"italic"}}>· optional</span>}
        </span>
        <ResultIndicator outcome={outcome} />
        {outcome.status === "failed" && (
          <span onClick={(e)=>{e.stopPropagation();onViewFindings();}} style={{fontSize:10,color:c.accent,cursor:"pointer",marginLeft:2}}>findings →</span>
        )}
      </div>

      {/* Expanded body */}
      {expanded && <OutcomeBody outcome={outcome} onViewFindings={onViewFindings} />}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// OUTCOME BODY (expanded)
// ═══════════════════════════════════════════════════════════════
function OutcomeBody({outcome, onViewFindings}) {
  const [adjustPlan, setAdjustPlan] = useState(false);
  const [adjustRecovery, setAdjustRecovery] = useState(false);
  const [adjustFeedback, setAdjustFeedback] = useState(false);

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

      {/* WHAT THIS CHECKS — the only user input */}
      <Para label="What this checks">
        <Textarea value={outcome.desc} h={outcome.meta?100:72} placeholder="Describe what you want to check, in plain language." />
        <Hint>This is the only thing you have to write. Everything below is auto-generated from it.</Hint>
      </Para>

      {/* THE PLAN — auto-generated */}
      <Para label="The plan" sublabel="auto-generated · adjustable" onAdjustToggle={()=>setAdjustPlan(!adjustPlan)} adjustOpen={adjustPlan}>
        <Prose>{outcome.plan}</Prose>
        {adjustPlan && <PlanAdjust outcome={outcome} />}
      </Para>

      {/* ON FAILURE — what the agent tries before firing failed_out */}
      <Para label="If checks fail" sublabel="what the agent tries before failed_out fires" onAdjustToggle={()=>setAdjustRecovery(!adjustRecovery)} adjustOpen={adjustRecovery}>
        <Prose>{outcome.recovery}</Prose>
        {adjustRecovery && <RecoveryAdjust outcome={outcome} />}
      </Para>

      {/* FEEDBACK SHAPE — what the next module receives */}
      <Para label="What downstream gets" sublabel="payload shape on failed_out · adjustable" onAdjustToggle={()=>setAdjustFeedback(!adjustFeedback)} adjustOpen={adjustFeedback}>
        <Prose>{outcome.feedback}</Prose>
        {adjustFeedback && <FeedbackAdjust outcome={outcome} />}
      </Para>

      {/* LAST RESULT */}
      {outcome.status && outcome.status !== "pending" && (
        <Para label="Last run">
          <LastRun outcome={outcome} onViewFindings={onViewFindings} />
        </Para>
      )}
    </div>
  );
}

// ─── Layout primitives for the outcome body ───
const Para = ({label, sublabel, children, onAdjustToggle, adjustOpen}) => (
  <div style={{marginTop:18}}>
    <div style={{display:"flex",alignItems:"baseline",gap:10,marginBottom:7}}>
      <div style={{fontSize:10,fontWeight:600,color:c.textSec,letterSpacing:0.4,textTransform:"uppercase",fontFamily:sans}}>{label}</div>
      {sublabel && <div style={{fontSize:9,color:c.textTer,fontStyle:"italic"}}>{sublabel}</div>}
      <div style={{flex:1}} />
      {onAdjustToggle && (
        <span onClick={onAdjustToggle} style={{fontSize:9,color:c.accent,cursor:"pointer"}}>
          {adjustOpen ? "hide" : "adjust"}
        </span>
      )}
    </div>
    {children}
  </div>
);
const Prose = ({children}) => (
  <div style={{fontSize:12,color:c.text,lineHeight:1.65,padding:"2px 0",fontFamily:sans}}>{children}</div>
);
const Hint = ({children}) => (
  <div style={{fontSize:10,color:c.textTer,marginTop:5,fontStyle:"italic",lineHeight:1.5}}>{children}</div>
);

// ─── Adjust panels (collapsed by default) ───
function PlanAdjust({outcome}) {
  return (
    <div style={{marginTop:10,padding:12,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
      <FieldRow label="Result type">
        <Select opts={["pass/fail","checklist","score"]} v={outcome.resultType==="pass_fail"?"pass/fail":outcome.resultType} w={120} />
      </FieldRow>
      <FieldRow label="Check by">
        <Select opts={["auto-detect from description","auto-check (mechanical)","check claims (from extractor)","specialist agent","agent assessment (LLM rubric)"]} v="auto-detect from description" />
      </FieldRow>
      <FieldRow label="Pass when">
        {outcome.resultType==="checklist" && <Select opts={["all items","≥ 95% of items","at least N items"]} v="all items" w={160} />}
        {outcome.resultType==="score" && <Input value={outcome.threshold?.toString()} w={70} />}
        {outcome.resultType==="pass_fail" && <span style={{fontSize:11,color:c.textSec}}>check returns true</span>}
      </FieldRow>
      <FieldRow label="Reference materials">
        <Select opts={["auto-detect from description","required","used if available","not needed","audit only"]} v="auto-detect from description" />
      </FieldRow>
      <FieldRow label="Cost cap">
        <Input value="$0.50" w={70} />
      </FieldRow>
    </div>
  );
}

function RecoveryAdjust({outcome}) {
  return (
    <div style={{marginTop:10,padding:12,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
      <div style={{fontSize:10,color:c.textSec,marginBottom:8,lineHeight:1.5,fontStyle:"italic"}}>
        The agent has a built-in playbook for this outcome type. You can override the playbook here — or just describe what you want the agent to try, and the system figures it out.
      </div>
      <FieldRow label="Try alternates first">
        <Check label="Try alternate sources (e.g., Westlaw)" checked={true} />
      </FieldRow>
      <FieldRow label="Ask upstream">
        <Check label="Ask the upstream agent for clarification" checked={true} />
      </FieldRow>
      <FieldRow label="Gather more info">
        <Check label="Allow research / web search" checked={true} />
      </FieldRow>
      <FieldRow label="Max attempts">
        <Input value="3" w={50} />
      </FieldRow>
      <FieldRow label="Cost cap on recovery">
        <Input value="$0.50" w={70} />
      </FieldRow>
      <FieldRow label="After all attempts">
        <Select opts={["fire failed_out with findings","escalate to human","mark indeterminate"]} v="fire failed_out with findings" />
      </FieldRow>
      <FieldRow label="Custom recovery">
        <Textarea value="" h={50} placeholder="(Optional) Describe additional recovery steps in plain language." />
      </FieldRow>
    </div>
  );
}

function FeedbackAdjust({outcome}) {
  return (
    <div style={{marginTop:10,padding:12,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
      <div style={{fontSize:10,color:c.textSec,marginBottom:8,lineHeight:1.5,fontStyle:"italic"}}>
        This is the structured payload on failed_out. The next module reads it as instruction. The default shape is auto-derived from the outcome type. Customize if you need a different structure for downstream consumers.
      </div>
      <FieldRow label="Format">
        <Select opts={["structured (auto from outcome type)","plain text summary","JSON schema (custom)","markdown report"]} v="structured (auto from outcome type)" />
      </FieldRow>
      <FieldRow label="Include">
        <div style={{display:"flex",flexDirection:"column",gap:0}}>
          <Check label="Each failure item with location" checked={true} />
          <Check label="Why it failed (verifier's reasoning)" checked={true} />
          <Check label="Suggested fix per item" checked={true} />
          <Check label="Evidence references (sources used)" checked={true} />
          <Check label="Severity per item" checked={false} />
          <Check label="Confidence per item" checked={false} />
        </div>
      </FieldRow>
      <FieldRow label="Style">
        <Select opts={["actionable instructions for next agent","observations + suggestions","critique only","raw findings"]} v="actionable instructions for next agent" />
      </FieldRow>
      <FieldRow label="Also deliver as">
        <div style={{display:"flex",flexDirection:"column",gap:0}}>
          <Check label="Inline document comments (anchored to brief sections)" checked={false} />
          <Check label="Task forum / board post" checked={true} />
          <Check label="Audit trail (always on)" checked={true} />
        </div>
      </FieldRow>
    </div>
  );
}

const FieldRow = ({label, children}) => (
  <div style={{display:"flex",alignItems:"flex-start",gap:14,marginBottom:7}}>
    <div style={{fontSize:10,color:c.textSec,fontWeight:500,minWidth:130,paddingTop:5}}>{label}</div>
    <div style={{flex:1}}>{children}</div>
  </div>
);

// ─── Last run summary (in expanded card) ───
function LastRun({outcome, onViewFindings}) {
  const isFail = outcome.status === "failed";

  if (!isFail) {
    return (
      <div style={{fontSize:12,color:c.green,padding:"6px 0"}}>
        ✓ Passed.
        {outcome.resultType === "checklist" && ` ${outcome.count.passed} of ${outcome.count.total}.`}
        {outcome.resultType === "score" && ` Score ${outcome.score.toFixed(2)} (threshold ${outcome.threshold.toFixed(2)}).`}
      </div>
    );
  }

  return (
    <div style={{padding:"10px 12px",background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
      <div style={{fontSize:12,color:c.text,lineHeight:1.6,marginBottom:8}}>
        ↺ Failed{" "}
        {outcome.resultType === "checklist" && `(${outcome.count.passed} of ${outcome.count.total}). `}
        {outcome.resultType === "score" && `(score ${outcome.score.toFixed(2)} / threshold ${outcome.threshold.toFixed(2)}). `}
        Failed_out fired with structured feedback. Whatever's wired downstream is now revising.
      </div>
      <div style={{display:"flex",gap:8}}>
        <Btn ghost sm onClick={onViewFindings}>View findings</Btn>
        <Btn ghost sm>Override → human</Btn>
        <Btn ghost sm>Re-evaluate</Btn>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// ADD OUTCOME
// ═══════════════════════════════════════════════════════════════
function AddOutcome({onClose}) {
  return (
    <div style={{marginBottom:14,padding:16,background:c.bgCard,border:`1px solid ${c.border}`,borderRadius:6}}>
      <Textarea
        value=""
        placeholder="Describe what you want to check. The system will figure out how."
        h={72}
      />
      <div style={{display:"flex",alignItems:"center",gap:10,marginTop:10}}>
        <span style={{fontSize:10,color:c.textTer}}>or start from:</span>
        <Btn ghost sm>💬 Ask Task Agent</Btn>
        <Btn ghost sm>📋 Load preset</Btn>
        <Btn ghost sm>⊕ Overall goal check</Btn>
        <div style={{flex:1}} />
        <Btn ghost sm onClick={onClose}>Cancel</Btn>
        <Btn primary sm onClick={onClose}>Add</Btn>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// SETTINGS DRAWER
// ═══════════════════════════════════════════════════════════════
function SettingsDrawer({onClose}) {
  return (
    <div onClick={onClose} style={{position:"absolute",top:0,right:0,bottom:0,left:0,background:"rgba(0,0,0,0.4)",zIndex:40,display:"flex",justifyContent:"flex-end"}}>
      <div onClick={(e)=>e.stopPropagation()} style={{width:440,height:"100%",background:c.bgPanel,borderLeft:`1px solid ${c.border}`,display:"flex",flexDirection:"column",boxShadow:"-10px 0 30px #0008"}}>
        <div style={{padding:"14px 22px",borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",justifyContent:"space-between"}}>
          <span style={{fontSize:13,fontWeight:600}}>Settings</span>
          <span onClick={onClose} style={{fontSize:16,color:c.textSec,cursor:"pointer"}}>✕</span>
        </div>
        <div style={{flex:1,overflowY:"auto",padding:"18px 22px"}}>

          <SettingsSection title="Overall verdict">
            <FieldRow label="Pass when">
              <Select opts={["All required outcomes pass","Average score ≥ threshold","Weighted score ≥ threshold"]} v="All required outcomes pass" />
            </FieldRow>
          </SettingsSection>

          <SettingsSection title="Iteration">
            <div style={{fontSize:12,color:c.text,lineHeight:1.7,padding:"4px 0"}}>
              Try up to <Input value="3" w={40} /> revisions before giving up.<br />
              If the same outcome fails <Input value="3" w={32} /> times, send to human.<br />
              If no improvement after <Input value="2" w={32} /> iterations, send to human.
            </div>
          </SettingsSection>

          <SettingsSection title="Human review">
            <Check label="Only when required outcomes fail (after retries)" checked={true} />
            <Check label="Before any output (review every verdict)" checked={false} />
            <Check label="On regression or stuck failures" checked={false} />
          </SettingsSection>

          <SettingsSection title="Defaults">
            <FieldRow label="Agent">
              <Select opts={["(task default)","Elnor","Nova"]} v="(task default)" />
            </FieldRow>
            <FieldRow label="Cost cap per run">
              <Input value="$1.50" w={80} />
            </FieldRow>
          </SettingsSection>

          <SettingsSection title="Sources & libraries">
            <div style={{fontSize:11,color:c.textSec,marginBottom:8}}>Wired evidence comes from the canvas. Uploads here are extras.</div>
            <div style={{padding:10,border:`1px dashed ${c.border}`,borderRadius:4,textAlign:"center",fontSize:11,color:c.textSec}}>
              📁 Drop files here or click to browse
            </div>
            <div style={{marginTop:8,display:"flex",flexDirection:"column",gap:4}}>
              {["📚 DOC73 PBE: Securities Litigation","🗂 Matter file (auto)","⚖ CourtListener"].map((l,i)=>(
                <div key={i} style={{padding:"5px 8px",background:c.bgCard,borderRadius:3,fontSize:11,color:c.text}}>{l}</div>
              ))}
            </div>
          </SettingsSection>

          <SettingsSection title="Custom guidance">
            <Textarea value="Apply strict Bluebook 21st edition standards. Prefer formal legal register." h={80} placeholder="Instructions that apply to all outcome verifications." />
          </SettingsSection>
        </div>
      </div>
    </div>
  );
}
const SettingsSection = ({title,children}) => (
  <div style={{marginBottom:20}}>
    <div style={{fontSize:10,fontWeight:600,color:c.textSec,letterSpacing:0.4,textTransform:"uppercase",marginBottom:8}}>{title}</div>
    {children}
  </div>
);

// ═══════════════════════════════════════════════════════════════
// FINDINGS DRAWER
// ═══════════════════════════════════════════════════════════════
function FindingsDrawer({outcomeId, onClose}) {
  const isAll = outcomeId === "all";
  const title = isAll ? "All findings — last run" : "Citations resolve to real cases — findings";

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

        <div style={{padding:"14px 22px",borderBottom:`1px solid ${c.border}`,display:"flex",alignItems:"center",justifyContent:"space-between"}}>
          <div>
            <div style={{fontSize:13,fontWeight:600}}>{title}</div>
            <div style={{fontSize:10,color:c.textTer,marginTop:2}}>3 findings · iteration 2</div>
          </div>
          <span onClick={onClose} style={{fontSize:16,color:c.textSec,cursor:"pointer"}}>✕</span>
        </div>

        <div style={{flex:1,overflowY:"auto",padding:"16px 22px"}}>

          <div style={{padding:11,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`,marginBottom:16,fontSize:11,color:c.text,lineHeight:1.6}}>
            The check tried 19 citations. 16 verified. 3 could not be verified after attempts via CourtListener, Westlaw, and asking the upstream drafter for source notes. failed_out fired with structured feedback; whatever's wired is revising now.
          </div>

          {[
            {title:"Smith v. Jones, 432 F.3d 12 (5th Cir. 2007)", loc:"§III ¶4", reason:"Case not found in CourtListener after 3 lookup attempts. Tried alternate reporters (F.2d, F. Supp.). Westlaw returned no match.", fix:"Verify case name; possibly Smith v. Jones, 432 F.3d 11 (alternate citation)."},
            {title:"In re Acme Corp., 215 B.R. 89 (Bankr. S.D.N.Y. 1997)", loc:"§IV ¶2", reason:"Citation format valid; no matching case in CourtListener. Westlaw confirmed no S.D.N.Y. case with that citation.", fix:"Possible alternative: In re Acme Corp., 215 B.R. 89 (Bankr. D.N.J. 1997) — different jurisdiction."},
            {title:"Doe v. Smith (incomplete)", loc:"§V ¶1", reason:"Citation incomplete — missing reporter, court, and year.", fix:"Drafter needs to provide full citation or remove."},
          ].map((f,i)=>(
            <div key={i} style={{padding:12,background:c.bgCard,borderRadius:5,border:`1px solid ${c.borderSoft}`,marginBottom:8}}>
              <div style={{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:6}}>
                <span style={{fontSize:12,fontWeight:600,color:c.text}}>{f.title}</span>
                <span style={{fontSize:10,color:c.textTer,fontFamily:mono}}>{f.loc}</span>
              </div>
              <div style={{fontSize:11,color:c.textSec,lineHeight:1.5,marginBottom:4}}>
                <span style={{color:c.red,fontWeight:500}}>Why: </span>{f.reason}
              </div>
              <div style={{fontSize:11,color:c.textSec,lineHeight:1.5}}>
                <span style={{color:c.accent,fontWeight:500}}>Suggested fix: </span>{f.fix}
              </div>
            </div>
          ))}

          <div style={{marginTop:18,padding:11,background:c.bgCard,borderRadius:5,border:`1px solid ${c.borderSoft}`}}>
            <div style={{fontSize:10,fontWeight:600,color:c.textSec,marginBottom:8}}>DELIVERY</div>
            <Check label="Send as inline document comments (for human editing)" checked={false} />
            <Check label="Post to task forum / board" checked={true} />
            <Check label="Audit trail" checked={true} />
          </div>
        </div>

        <div style={{padding:"12px 22px",borderTop:`1px solid ${c.border}`,display:"flex",gap:6,flexWrap:"wrap"}}>
          <Btn ghost sm>Accept guidance</Btn>
          <Btn ghost sm>Mark wrong</Btn>
          <Btn ghost sm>Send to human</Btn>
          <Btn ghost sm>Ask Task Agent</Btn>
        </div>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// PORTS
// ═══════════════════════════════════════════════════════════════
function PortsDrawer() {
  const inputs = [
    {label:"subject_in", note:"← ◆ Final Brief Draft"},
    {label:"claims_in", note:"← ◈ Brief Claims"},
    {label:"evidence_in.1", note:"← ◇ Case Record"},
    {label:"evidence_in.2", note:"← ◇ Motion to Dismiss"},
  ];
  const outputs = [
    {label:"passed_out", note:"→ next stage"},
    {label:"any_failed_out", note:"aggregate fallback — fires when any required outcome fails"},
    {label:"meta_failed_out", note:"per-outcome route, auto-named from \"Output achieves the goal\""},
    {label:"citations_resolve_failed_out", note:"per-outcome route"},
    {label:"motion_issues_failed_out", note:"per-outcome route"},
    {label:"feedback_out", note:"structured feedback for loop iteration"},
  ];
  return (
    <div style={{padding:"4px 22px 14px",borderTop:`1px solid ${c.borderSoft}`,display:"flex",gap:30,fontSize:10,color:c.textSec,maxHeight:240,overflowY:"auto"}}>
      <div style={{flex:1}}>
        <div style={{fontSize:9,color:c.textTer,marginBottom:6,letterSpacing:0.5,fontWeight:600}}>INPUTS</div>
        {inputs.map((p,i)=>(
          <div key={i} style={{padding:"3px 0",fontFamily:mono}}>
            <span style={{color:c.accent}}>{p.label}</span>
            <span style={{color:c.textTer,fontFamily:sans,marginLeft:8}}>{p.note}</span>
          </div>
        ))}
      </div>
      <div style={{flex:1.6}}>
        <div style={{fontSize:9,color:c.textTer,marginBottom:6,letterSpacing:0.5,fontWeight:600}}>OUTPUTS</div>
        {outputs.map((p,i)=>(
          <div key={i} style={{padding:"3px 0",fontFamily:mono}}>
            <span style={{color:p.label.includes("failed")?c.red:c.green}}>{p.label}</span>
            <span style={{color:c.textTer,fontFamily:sans,marginLeft:8,fontStyle:"italic"}}>{p.note}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════
// EXTRACTOR (minimal — same simplification principles)
// ═══════════════════════════════════════════════════════════════
function ExtractorView() {
  const [expanded, setExpanded] = useState("ct1");
  const [settingsOpen, setSettingsOpen] = useState(false);

  const types = [
    {id:"ct1", name:"CaseCitation", desc:"Citations to legal cases — full citation, case name, holding as characterized.", count:19, evaluable:true, authority:true, consumedBy:["Citations resolve to real cases"], instruction:"Extract every citation to a legal case. Include the full citation, the case name, the holding as characterized by the brief, and the source span (paragraph + line)."},
    {id:"ct2", name:"FactualAssertion", desc:"Factual claims — dates, amounts, events, parties.", count:18, evaluable:true, authority:false, consumedBy:["No factual claims unsupported"], instruction:"Extract every factual claim made by the brief, with its location."},
    {id:"ct3", name:"LegalProposition", desc:"Statements of legal rules or principles.", count:7, evaluable:true, authority:false, consumedBy:[], instruction:"Extract statements of legal rules with supporting citation if present."},
    {id:"ct4", name:"OpposingArgument", desc:"References to opposing counsel's arguments.", count:4, evaluable:true, authority:false, consumedBy:[], instruction:"Extract references to opposing counsel's arguments."},
    {id:"ct5", name:"ProceduralFact", desc:"Statements about case procedural posture.", count:2, evaluable:false, authority:false, consumedBy:[], instruction:"Extract procedural facts about the case."},
  ];

  return (
    <div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden",position:"relative"}}>
      <div style={{height:44,minHeight:44,borderBottom:`1px solid ${c.borderSoft}`,display:"flex",alignItems:"center",justifyContent:"space-between",padding:"0 22px"}}>
        <div style={{display:"flex",alignItems:"center",gap:10}}>
          <span style={{fontSize:16}}>◈</span>
          <span style={{fontSize:13,fontWeight:600}}>Claim Extractor</span>
          <span style={{fontSize:10,color:c.textTer,fontFamily:mono,marginLeft:4}}>step.claim_extractor</span>
        </div>
        <div style={{display:"flex",gap:6,alignItems:"center"}}>
          <span onClick={()=>setSettingsOpen(true)} style={{fontSize:14,color:c.textSec,cursor:"pointer",padding:"4px 8px"}}>⚙</span>
          <Btn primary sm>▶ Run</Btn>
          <span style={{fontSize:16,color:c.textSec,cursor:"pointer",padding:"0 6px"}}>✕</span>
        </div>
      </div>

      <div style={{padding:"14px 28px 6px",display:"flex",alignItems:"center",gap:14}}>
        <StatusDot status="verified" />
        <span style={{fontSize:12,color:c.text}}>
          <span style={{color:c.green,fontWeight:600}}>50 claims extracted</span>
          <span style={{color:c.textSec,marginLeft:8}}>· cached · was $0.12 fresh · downstream: ⊜ Final Quality Gate</span>
        </span>
      </div>

      <div style={{flex:1,overflowY:"auto"}}>
        <div style={{maxWidth:880,margin:"0 auto",padding:"18px 28px 40px"}}>
          <div style={{display:"flex",alignItems:"baseline",justifyContent:"space-between",marginBottom:14}}>
            <div style={{fontSize:14,fontWeight:600}}>Claim types</div>
            <Btn ghost sm>+ Add</Btn>
          </div>

          <div style={{display:"flex",flexDirection:"column",gap:6}}>
            {types.map(t => (
              <div key={t.id} style={{background:c.bgCard,border:`1px solid ${expanded===t.id?c.border:c.borderSoft}`,borderRadius:6,overflow:"hidden"}}>
                <div onClick={()=>setExpanded(expanded===t.id?null:t.id)} style={{padding:"11px 16px",cursor:"pointer",display:"flex",alignItems:"center",gap:12}}>
                  <StatusDot status={t.count>0?"verified":"pending"} />
                  <span style={{flex:1,fontSize:12,color:c.text,fontFamily:mono}}>{t.name}
                    {!t.evaluable && <span style={{fontSize:9,color:c.textTer,marginLeft:8,fontFamily:sans,fontStyle:"italic"}}>· not evaluable</span>}
                    {t.authority && <span style={{fontSize:9,color:c.amber,marginLeft:8,fontFamily:sans,fontStyle:"italic"}}>· needs specialist</span>}
                    {t.consumedBy.length>0 && <span style={{fontSize:9,color:c.textTer,marginLeft:8,fontFamily:sans,fontStyle:"italic"}}>· used by {t.consumedBy.length}</span>}
                  </span>
                  <span style={{fontSize:13,color:t.count>0?c.text:c.textTer,fontFamily:mono,fontWeight:600,minWidth:30,textAlign:"right"}}>{t.count}</span>
                </div>

                {expanded===t.id && (
                  <div style={{padding:"4px 22px 22px",borderTop:`1px solid ${c.borderSoft}`}}>
                    <Para label="What this extracts">
                      <Textarea value={t.desc} h={50} />
                    </Para>
                    <Para label="Instruction" sublabel="what the extractor agent reads">
                      <Textarea value={t.instruction} h={60} />
                    </Para>
                    <Para label="Fields per claim">
                      <div style={{padding:10,background:c.bg,borderRadius:5,border:`1px solid ${c.borderSoft}`,fontFamily:mono,fontSize:10,color:c.text,lineHeight:1.7}}>
                        case_name <span style={{color:c.textTer}}>· text · required</span><br/>
                        citation <span style={{color:c.textTer}}>· text · required</span><br/>
                        court <span style={{color:c.textTer}}>· enum · optional</span><br/>
                        year <span style={{color:c.textTer}}>· number · optional</span><br/>
                        holding_quoted <span style={{color:c.textTer}}>· text · optional</span>
                      </div>
                    </Para>
                    {t.consumedBy.length>0 && (
                      <Para label="Used by these outcomes">
                        <div style={{fontSize:11,color:c.text,lineHeight:1.7}}>
                          {t.consumedBy.map((o,i)=>(<div key={i} style={{cursor:"pointer",color:c.accent}}>↗ {o}</div>))}
                        </div>
                      </Para>
                    )}
                  </div>
                )}
              </div>
            ))}
          </div>
        </div>
      </div>

      {settingsOpen && <SettingsDrawer onClose={()=>setSettingsOpen(false)} />}
    </div>
  );
}