// Screen components: Detail, RunInput, Streaming, Result

const { useState: useState_s, useEffect: useEffect_s, useRef: useRef_s } = React;

// Renders a workapp's raw HTML output inside a sandboxed iframe whose height
// is auto-fit to its content. Used by ResultScreen as a fallback when a run
// returns {output: {html: "<div>…"}} instead of canonical findings/verdict.
function ResultHtmlFrame({ html }) {
  const iframeRef = useRef_s(null);
  const [height, setHeight] = useState_s(600);
  useEffect_s(function () {
    const iframe = iframeRef.current;
    if (!iframe) return;
    const doc = iframe.contentDocument;
    if (!doc) return;
    doc.open();
    doc.write('<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}</style></head><body>' + html + '</body></html>');
    doc.close();
    const measure = function () {
      try {
        const h = doc.documentElement.scrollHeight || doc.body.scrollHeight || 600;
        setHeight(h + 24);
      } catch (e) { /* ignore */ }
    };
    measure();
    setTimeout(measure, 50);
    setTimeout(measure, 200);
  }, [html]);
  return (
    <div style={{background: '#fff', border: '1px solid var(--hair-2)', borderRadius: 12, overflow: 'hidden', margin: '0 0 28px'}}>
      <iframe
        ref={iframeRef}
        sandbox="allow-same-origin"
        style={{ width: '100%', border: 'none', display: 'block', height: height + 'px' }}
        title="Workapp result"
      />
    </div>
  );
}

function priorityPill(priority) {
  const p = (priority || '').toLowerCase();
  const map = {
    high: { label: 'Fix first', bg: '#fef2f2', fg: '#b91c1c', border: '#fecaca' },
    medium: { label: 'Worth a look', bg: '#fffbeb', fg: '#b45309', border: '#fde68a' },
    low: { label: 'Nice-to-have', bg: '#f0fdf4', fg: '#15803d', border: '#bbf7d0' },
  };
  return map[p] || { label: 'Finding', bg: '#f3f4f6', fg: '#374151', border: '#e5e7eb' };
}

// ─────────────────────────────────────────────────────────────────────
// 1. WorkApp DETAIL PAGE
// ─────────────────────────────────────────────────────────────────────
function DetailScreen({ slug, onRun, onSeeSample, onRelatedClick, onExpert }) {
  const [w, setW] = useState_s(null);
  const [e, setE] = useState_s(null);
  const [essay, setEssay] = useState_s(null);
  const [methodology, setMethodology] = useState_s(null);
  const [workappInputs, setWorkappInputs] = useState_s([]);  // per-workapp input field definitions
  const [related, setRelated] = useState_s([]);
  const [loadError, setLoadError] = useState_s(null);
  const [fieldValues, setFieldValues] = useState_s({});     // dynamic form values keyed by field name
  const [errors, setErrors] = useState_s({});
  const [touched, setTouched] = useState_s({});
  const [evidenceSources, setEvidenceSources] = useState_s([]);

  useEffect_s(() => {
    if (!slug) return;
    let cancelled = false;
    window.AppData.getWorkapp(slug).then((d) => {
      if (cancelled) return;
      setW(d.wapp);
      setE(d.expert);
      setEssay(d.essay || null);
      setMethodology(d.methodology || null);
      setWorkappInputs(d.inputs || []);
      setEvidenceSources(Array.isArray(d.evidenceSources) ? d.evidenceSources : []);
      d.relatedPromise.then((rel) => { if (!cancelled) setRelated(rel || []); }).catch(() => {});
    }).catch((err) => { if (!cancelled) setLoadError(err); });
    return () => { cancelled = true; };
  }, [slug]);

  // Validate dynamic form fields against workapp input definitions.
  const validate = () => {
    const errs = {};
    const inputs = workappInputs.length > 0 ? workappInputs : [{ name: 'url', type: 'url', label: 'URL', required: true }];
    inputs.forEach((field) => {
      if (!field.required) return;
      const val = (fieldValues[field.name] || '').trim();
      if (!val) {
        errs[field.name] = field.label + ' is required.';
      } else if (field.type === 'url' && !/^(https?:\/\/)?([\w-]+\.)+[\w-]+(\/.*)?$/i.test(val)) {
        errs[field.name] = "That doesn't look like a URL.";
      } else if (field.type === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) {
        errs[field.name] = "That doesn't look like an email address.";
      }
    });
    return errs;
  };

  const setField = (name, value) => {
    setFieldValues((prev) => Object.assign({}, prev, { [name]: value }));
    if (errors[name]) setErrors((prev) => { const next = Object.assign({}, prev); delete next[name]; return next; });
  };

  const submit = (ev) => {
    ev.preventDefault();
    const inputs = workappInputs.length > 0 ? workappInputs : [{ name: 'url', type: 'url', label: 'URL', required: true }];
    const allTouched = inputs.reduce((t, f) => { t[f.name] = true; return t; }, {});
    setTouched(allTouched);
    const errs = validate();
    setErrors(errs);
    if (Object.keys(errs).length === 0) {
      // Normalise URL fields to include scheme
      const payload = Object.assign({}, fieldValues);
      inputs.forEach((f) => {
        if ((f.type === 'url') && payload[f.name] && !/^https?:\/\//i.test(payload[f.name])) {
          payload[f.name] = 'https://' + payload[f.name].trim();
        }
      });
      // Log user intent before navigating
      if (window.AppData && window.AppData.logIntent) {
        window.AppData.logIntent('run_intent', { slug: slug, fields: payload, url: window.location.hash });
      }
      onRun(Object.assign({ slug: slug }, payload));
    }
  };

  if (loadError) return <div className="loading" style={{padding: 40}}>Couldn't load WorkApp.</div>;
  if (!w || !e) return <div className="loading" style={{padding: 40}}>Loading…</div>;

  // Resolve title + italic from the workapp data
  const titleParts = window.AppData.splitItalic(w.title || '');

  // Effective input fields — use workapp-defined inputs if available, else a generic URL field
  const formFields = workappInputs.length > 0 ? workappInputs : [
    { name: 'url', type: 'url', label: 'URL to analyze', required: true },
  ];

  return (
    <div data-screen-label="01 WorkApp Detail">
      <div className="wrap">
        <div className="detail-hero" style={{paddingBottom: 32}}>
          <h1 className="title serif">
            {titleParts.italic
              ? <>{titleParts.title.replace(titleParts.italic, '').trim()} <em>{titleParts.italic}</em></>
              : titleParts.title || w.title}
          </h1>
          <p className="lede">{w.subtitle}</p>

          <ExpertChip expert={e} large onClick={onExpert} />

          <div className="hero-meta" style={{marginTop: 8, paddingTop: 0, borderTop: 0}}>
            <span><b>{w.time || '~60 seconds'}</b> to run</span>
            {w.runs ? <span><b>{w.runs}</b> runs</span> : null}
            <span>No signup for first run</span>
          </div>

        </div>

        {/* Single-column: form first, then content */}
        <div className="detail-single" style={{paddingTop: 8}}>

          {/* Run form — dynamic fields from workapp.inputs */}
          <form className="side-card detail-form" onSubmit={submit}>
            <h3 style={{margin: '0 0 6px', fontFamily: 'Inter Tight, Geist, ui-sans-serif, system-ui, sans-serif', fontSize: 24, fontWeight: 700, letterSpacing: '-0.014em'}}>Get your verdict</h3>
            <p style={{margin: '0 0 22px', fontSize: 14, color: 'var(--muted)', lineHeight: 1.5}}>
              {e && e.name ? (e.name + "'s") : 'This'} diagnostic runs in {w && w.time ? w.time : '~60 seconds'}. No signup for the first run.
            </p>

            {formFields.map(function(field) {
              const val = fieldValues[field.name] || '';
              const hasError = errors[field.name] && touched[field.name];
              const fieldId = 'd-' + field.name;
              return (
                <div className="field" key={field.name}>
                  <label className="field-label" htmlFor={fieldId}>
                    {field.label || field.name}
                    {field.required
                      ? <span style={{color: 'var(--accent)'}}> *</span>
                      : <span style={{color: 'var(--muted)', fontWeight: 400}}> (optional)</span>}
                  </label>
                  {field.type === 'textarea' ? (
                    <textarea
                      id={fieldId}
                      className={'textarea' + (hasError ? ' error' : '')}
                      placeholder={field.description || ''}
                      value={val}
                      onChange={(ev) => setField(field.name, ev.target.value)}
                      onBlur={() => setTouched(Object.assign({}, touched, { [field.name]: true }))}
                      rows={2}
                    />
                  ) : (
                    <input
                      id={fieldId}
                      type={field.type === 'url' ? 'text' : (field.type === 'email' ? 'email' : 'text')}
                      className={'input' + (hasError ? ' error' : '')}
                      placeholder={field.type === 'url' ? 'https://...' : (field.description || '')}
                      value={val}
                      onChange={(ev) => setField(field.name, ev.target.value)}
                      onBlur={() => setTouched(Object.assign({}, touched, { [field.name]: true }))}
                    />
                  )}
                  {hasError && (
                    <div className="field-error"><Icon.Alert className="icon-sm" /> {errors[field.name]}</div>
                  )}
                </div>
              );
            })}

            <div className="detail-form-actions">
              <button className="btn btn-primary btn-lg" type="submit">
                Get my verdict <Icon.ArrowRight />
              </button>
              <span className="detail-form-hint">
                <Icon.Clock className="icon-sm" /> {w && w.time ? w.time : '~60 seconds'}
              </span>
            </div>
            {evidenceSources.length > 0 && (
              <div className="workapp-sources" aria-label="Evidence sources used by this WorkApp">
                <span>Uses</span>
                <div>
                  {evidenceSources
                    .filter((source) => source && source.type !== 'Expert method')
                    .map((source) => (
                      <b key={source.name}>{source.name}</b>
                    ))}
                </div>
              </div>
            )}
          </form>

          {/* Expert essay — from WorkAppPublic.essay */}
          {essay && (
            <article className="method-essay" id="method-note">
              <div className="section-label">{e && e.name ? `${e.name} on the method` : 'On the method'}</div>
              <h2 className="h-section">{essay.heading}</h2>
              <div className="method-essay-byline">
                <ExpertAvatar person={e} className="mini-avatar" style={{background: e && e.avatarBg}} />
                <span>By {e && e.name || 'Expert'}{e && e.title ? ` · ${e.title}` : ''}</span>
              </div>
              <div className="body method-essay-body">
                {(essay.bodyParagraphs || []).map(function(para, i) {
                  return <p key={i}>{para}</p>;
                })}
                {essay.pullQuote ? <blockquote>{essay.pullQuote}</blockquote> : null}
              </div>
            </article>
          )}

          {/* Methodology — from WorkAppPublic.methodology */}
          {methodology && methodology.methodologySteps && methodology.methodologySteps.length > 0 && (
            <div style={{marginTop: 48}}>
              <div className="section-label">Methodology</div>
              <ol className="method-list" style={{maxWidth: 760}}>
                {methodology.methodologySteps.map(function(step, i) {
                  return (
                    <li key={i}><div>
                      <div className="method-step-title">{step.title}</div>
                      <div className="method-step-desc">{step.description}</div>
                    </div></li>
                  );
                })}
              </ol>
            </div>
          )}

          {/* Limitations — from WorkAppPublic.methodology.limitationsNote */}
          {methodology && methodology.limitationsNote && (
            <div style={{marginTop: 40, maxWidth: 760, padding: '20px 0', borderTop: '1px solid var(--hair)', fontSize: 13.5, color: 'var(--muted)', lineHeight: 1.6}}>
              <div style={{fontFamily: 'Geist Mono, monospace', fontSize: 11, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--ink-2)', marginBottom: 6}}>Limitations</div>
              {methodology.limitationsNote}
            </div>
          )}
        </div>

        {/* Related */}
        <div style={{padding: '56px 0', borderTop: '1px solid var(--hair)'}}>
          <h2 className="h-section" style={{marginBottom: 24}}>Related WorkApps</h2>
          <div className="related-grid">
            {related.map((w, i) => (
              <WorkAppCard key={i} wapp={w} variant="compact" onClick={() => onRelatedClick && onRelatedClick(w)} />
            ))}
          </div>
        </div>
      </div>
      <Footer />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// 2. RUN INPUT PAGE
// ─────────────────────────────────────────────────────────────────────
function RunInputScreen({ slug, wapp: wappProp, onSubmit, onBack, initial }) {
  const [wapp, setWapp] = useState_s(wappProp || null);
  const [url, setUrl] = useState_s(initial?.url || '');
  const [context, setContext] = useState_s(initial?.context || '');
  const [audience, setAudience] = useState_s(initial?.audience || '');
  const [advanced, setAdvanced] = useState_s(false);
  const [errors, setErrors] = useState_s({});
  const [touched, setTouched] = useState_s({});

  useEffect_s(() => {
    if (wappProp) { setWapp(wappProp); return; }
    if (!slug) return;
    let cancelled = false;
    window.AppData.getWorkapp(slug).then((d) => { if (!cancelled) setWapp(d.wapp); }).catch(() => {});
    return () => { cancelled = true; };
  }, [slug, wappProp]);

  const validate = () => {
    const errs = {};
    const trimmed = url.trim();
    if (!trimmed) {
      errs.url = 'Please enter a URL.';
    } else {
      // Basic URL validation
      const looksValid = /^(https?:\/\/)?([\w-]+\.)+[\w-]+(\/.*)?$/i.test(trimmed);
      if (!looksValid) {
        errs.url = 'That doesn\'t look like a URL. Try something like example.com or https://example.com.';
      }
    }
    return errs;
  };

  const submit = (e) => {
    e.preventDefault();
    setTouched({ url: true, context: true });
    const errs = validate();
    setErrors(errs);
    if (Object.keys(errs).length === 0) {
      let finalUrl = url.trim();
      if (!/^https?:\/\//.test(finalUrl)) finalUrl = 'https://' + finalUrl;
      onSubmit({ url: finalUrl, context, audience });
    }
  };

  if (!wapp) return <div className="loading" style={{padding: 40}}>Loading…</div>;

  return (
    <div className="run-page" data-screen-label="02 Run Input">
      <div className="wrap-narrow">
        <button className="btn btn-bare btn-sm" onClick={onBack} style={{marginBottom: 16, marginLeft: -8}}>
          <Icon.ArrowLeft className="icon-sm" /> Back to WorkApp
        </button>

        <form className="run-card" onSubmit={submit}>
          <div className="eyebrow" style={{marginBottom: 14}}>
            {/* audience text (defaultAudience) is free-form expert copy and
                often too long for an eyebrow; keep this row to just the
                estimated time until a curated category field exists. */}
            <span style={{display: 'inline-flex', alignItems: 'center', gap: 4}}><Icon.Clock className="icon-sm" /> {wapp.time || '~60 seconds'}</span>
          </div>
          <h1 className="run-h serif">Run "{wapp.title}"</h1>
          <p className="run-sub">
            You'll get a verdict, findings, and a prioritized recommendation.
          </p>

          {/* Dynamic fields from workapp input spec — falls back to a URL field if none defined */}
          {(workapp && workapp.inputs && workapp.inputs.length > 0 ? workapp.inputs : [
            { name: 'url', type: 'url', label: 'URL to analyze', required: true, description: 'Public URL — no login walls.' },
            { name: 'context', type: 'textarea', label: 'Short description', required: false, description: 'One sentence helps the expert method target your specific situation.' },
          ]).map(function(field) {
            const fVal = (field.name === 'url' ? url : context) || '';
            const hasErr = errors[field.name] && touched[field.name];
            const fid = 'ri-' + field.name;
            return (
              <div className="field" key={field.name}>
                <label className="field-label" htmlFor={fid}>
                  {field.label || field.name}
                  {field.required
                    ? <span style={{color: 'var(--accent)'}}> *</span>
                    : <span style={{color: 'var(--muted)', fontWeight: 400}}> (optional)</span>}
                </label>
                {field.description ? <div className="field-hint">{field.description}</div> : null}
                {field.type === 'textarea' ? (
                  <textarea
                    id={fid}
                    className="textarea"
                    placeholder={field.description || ''}
                    value={fVal}
                    onChange={(ev) => { if (field.name === 'url') setUrl(ev.target.value); else setContext(ev.target.value); if (errors[field.name]) setErrors({...errors, [field.name]: undefined}); }}
                    onBlur={() => setTouched({...touched, [field.name]: true})}
                    rows={2}
                  />
                ) : (
                  <input
                    id={fid}
                    type={field.type === 'email' ? 'email' : 'text'}
                    className={'input' + (hasErr ? ' error' : '')}
                    placeholder={field.type === 'url' ? 'https://...' : (field.description || '')}
                    value={fVal}
                    onChange={(ev) => { if (field.name === 'url') setUrl(ev.target.value); else setContext(ev.target.value); if (errors[field.name]) setErrors({...errors, [field.name]: undefined}); }}
                    onBlur={() => setTouched({...touched, [field.name]: true})}
                    autoFocus={field.name === (workapp && workapp.inputs && workapp.inputs[0] ? workapp.inputs[0].name : 'url')}
                  />
                )}
                {hasErr && (
                  <div className="field-error"><Icon.Alert className="icon-sm" /> {errors[field.name]}</div>
                )}
              </div>
            );
          })}

          <div className="run-foot">
            <div className="left">
              <Icon.Clock className="icon-sm" /> Estimated <b style={{color: 'var(--ink)'}}>{wapp && wapp.time ? wapp.time : '~60 seconds'}</b>
            </div>
            <button className="btn btn-primary btn-lg" type="submit">
              Get my verdict <Icon.ArrowRight />
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// 3. STREAMING RUN SCREEN
// ─────────────────────────────────────────────────────────────────────
function StreamingScreen({ runId, workappSlug, inputs, onComplete, onCancel, onRetry, simulateError }) {
  // State variables shadow the prototype's window.WORKAPP / EXPERT / etc.
  // globals (now nulled out by data.jsx). The capitalization is intentional:
  // it lets the JSX field accessors below stay byte-identical to the design
  // team's source files, preserving visual fidelity.
  const [WORKAPP, setWORKAPP] = useState_s(null);
  const [EXPERT, setEXPERT] = useState_s(null);
  const [expectedTotal, setExpectedTotal] = useState_s(0);
  // Steps keyed by id; order preserves arrival order.
  const [steps, setSteps] = useState_s({});
  const [order, setOrder] = useState_s([]);
  const [errored, setErrored] = useState_s(false);
  const [errorMessage, setErrorMessage] = useState_s('');
  const [completed, setCompleted] = useState_s(false);
  const [elapsed, setElapsed] = useState_s(0);
  const startRef = useRef_s(Date.now());
  const cancelledRef = useRef_s(false);
  const esRef = useRef_s(null);

  // Load workapp + expert for the header
  // Fallback: if workappSlug prop is empty, fetch it from the run first.
  useEffect_s(() => {
    let cancelled = false;
    async function load() {
      let slug = workappSlug;
      if (!slug && runId) {
        try {
          const run = await window.AppData.getRun(runId);
          slug = run && run.workappSlug;
        } catch (e) { /* ignore; header will just be missing */ }
      }
      if (!slug || cancelled) return;
      window.AppData.getWorkapp(slug).then((d) => {
        if (cancelled) return;
        setWORKAPP(d.wapp);
        setEXPERT(d.expert);
        const methSteps = d.methodology?.methodologySteps?.length || 0;
        const summary = d.methodology?.methodSummary?.length || 0;
        setExpectedTotal(Math.max(methSteps, summary));
      }).catch(() => {});
    }
    load();
    return () => { cancelled = true; };
  }, [runId, workappSlug]);

  // Tick elapsed
  useEffect_s(() => {
    const t = setInterval(() => setElapsed(Math.floor((Date.now() - startRef.current) / 1000)), 250);
    return () => clearInterval(t);
  }, []);

  // SSE subscription
  useEffect_s(() => {
    if (!runId) return;
    if (simulateError) { setErrored(true); return; }
    var streamUrl = window.AppData && window.AppData.streamUrl
      ? window.AppData.streamUrl(runId)
      : '/api/app/runs/' + runId + '/stream';
    const es = new EventSource(streamUrl, { withCredentials: true });
    esRef.current = es;
    const onStart = (ev) => {
      try {
        const p = JSON.parse(ev.data);
        setSteps((s) => ({ ...s, [p.id]: {
          id: p.id,
          name: p.name,
          tool: p.tool,
          reason: p.reason,
          source: typeof p.source === 'string' ? p.source : '',
          sourceType: typeof p.sourceType === 'string' ? p.sourceType : '',
          thoughts: Array.isArray(p.thoughts) ? p.thoughts.filter(t => typeof t === 'string' && t.trim()) : [],
          finding: '',
          duration: 0,
          state: 'in-progress',
        } }));
        setOrder((o) => (o.indexOf(p.id) === -1 ? [...o, p.id] : o));
      } catch (_) {}
    };
    const onDone = (ev) => {
      try {
        const p = JSON.parse(ev.data);
        setSteps((s) => {
          const prev = s[p.id] || { id: p.id, name: '', tool: '', reason: '' };
          return { ...s, [p.id]: { ...prev, finding: p.finding, duration: p.durationMs, state: 'complete' } };
        });
      } catch (_) {}
    };
    const onComplete2 = () => {
      setCompleted(true);
      es.close();
      if (!cancelledRef.current) setTimeout(() => { if (!cancelledRef.current) onComplete && onComplete(); }, 300);
    };
    const onErr = (ev) => {
      // Delegated to window.SseError.decideSseError (see public/app/sse-error.js)
      // so the decision logic can be unit-tested without a browser. Behavior:
      // a payload-bearing `event: error` is always terminal; a payload-less
      // native error is only terminal once readyState === CLOSED.
      const decision = window.SseError.decideSseError(
        ev && ev.data,
        es.readyState,
        EventSource.CLOSED,
      );
      if (decision.kind === 'ignore') return;
      if (decision.message) setErrorMessage(decision.message);
      setErrored(true);
      try { es.close(); } catch (_) {}
    };
    es.addEventListener('step.start', onStart);
    es.addEventListener('step.done', onDone);
    es.addEventListener('done', onComplete2);
    es.addEventListener('error', onErr);
    return () => {
      try { es.close(); } catch (_) {}
    };
  }, [runId, simulateError, onComplete]);

  const cancel = () => {
    cancelledRef.current = true;
    try { esRef.current && esRef.current.close(); } catch (_) {}
    onCancel && onCancel();
  };

  // Build ordered step array — this stands in for RUN_STEPS in render.
  const stepArr = order.map((id) => steps[id]).filter(Boolean);
  const totalSteps = Math.max(stepArr.length, expectedTotal, 1);
  const completedCount = stepArr.filter((s) => s.state === 'complete').length;
  const stepIdx = completedCount; // index of next in-progress step in display order
  const progress = errored
    ? (completedCount / totalSteps) * 100
    : completed
      ? 100
      : ((completedCount + (stepIdx < stepArr.length ? 0.4 : 0)) / totalSteps) * 100;
  const totalEst = stepArr.reduce((a, s) => a + (s.duration || 0), 0) / 1000;
  const remaining = Math.max(0, Math.ceil(totalEst - elapsed));

  const visibleSteps = stepArr.map((s, i) => ({ i, ...s }));

  if (!WORKAPP || !EXPERT) {
    // If the run already errored (e.g. ERR_MODULE_NOT_FOUND from the sandbox
    // worker) but the workapp/expert metadata never resolved, still render an
    // actionable error card instead of an indefinite "Starting run…" spinner.
    if (errored) {
      return (
        <div className="stream-page" data-screen-label="03 Streaming Run">
          <div className="wrap-narrow">
            <div className="stream-error" data-testid="stream-error">
              <Icon.Alert />
              <div style={{flex: 1}}>
                <h4>The run hit an error</h4>
                <p>{errorMessage || 'We couldn’t start the run.'}</p>
                <div style={{display: 'flex', gap: 8}}>
                  <button className="btn btn-primary btn-sm" onClick={onRetry}>Retry run</button>
                  <button className="btn btn-ghost btn-sm" onClick={cancel}>Change inputs</button>
                </div>
              </div>
            </div>
          </div>
        </div>
      );
    }
    return <div className="loading" style={{padding: 40}}>Starting run…</div>;
  }

  return (
    <div className="stream-page" data-screen-label="03 Streaming Run">
      <div className="wrap-narrow">
        <div style={{marginBottom: 24}}>
          <div className="eyebrow" style={{marginBottom: 12}}>
            <span>Running</span>
            <span className="sep"></span>
            <span>Method by {EXPERT.name}</span>
          </div>
          <h1 className="stream-h serif">{WORKAPP.title}</h1>
          <div className="stream-subtitle">Analyzing <span className="mono" style={{color: 'var(--ink)'}}>{(() => {
            const vals = inputs && typeof inputs === 'object' ? Object.values(inputs).filter(v => typeof v === 'string' && v.trim()) : [];
            const urlish = vals.find(v => /^https?:\/\//i.test(v) || /\.[a-z]{2,}/i.test(v));
            return urlish || vals[0] || 'your input';
          })()}</span></div>
        </div>

        <div className="stream-head">
          <div style={{flex: 1, minWidth: 200}}>
            <div className="stream-progress">
              <div className="stream-progress-bar" style={{width: progress + '%'}}></div>
            </div>
            <div className="stream-eta">
              {errored ? 'STOPPED' : (
                stepIdx >= totalSteps
                  ? 'FINALIZING'
                  : `STEP ${Math.min(stepIdx + 1, totalSteps)} OF ${totalSteps}  ·  ~${remaining}s REMAINING`
              )}
            </div>
          </div>
        </div>

        <div className="stream-list">
          {visibleSteps.map((step) => (
            <div className={`stream-step ${step.state}`} key={step.i}>
              <div className={`step-icon ${step.state}`}>
                {step.state === 'complete' && <Icon.CheckSm />}
                {step.state === 'in-progress' && <div className="spinner"></div>}
                {step.state === 'pending' && <Icon.Minus />}
              </div>
              <div>
                <div className="step-name">
                  <span>{step.name}</span>
                  <span className="step-tool">{step.tool}</span>
                </div>
                {step.source && (
                  <div className={'source-pill-row' + (step.sourceType === 'External tool' ? ' external' : '')}>
                    <span>{step.sourceType === 'External tool' ? 'External data' : (step.sourceType || 'Source')}</span>
                    <b>{step.source}</b>
                  </div>
                )}
                {step.state === 'in-progress' && step.thoughts && step.thoughts.length > 0 && (
                  <div className="step-thoughts" aria-label={step.name + ' narration'}>
                    {step.thoughts.map((line, idx) => <p key={idx}>{line}</p>)}
                  </div>
                )}
                {(step.state === 'complete' || (step.state === 'in-progress' && step.i < stepIdx)) && (
                  <>
                    <div className="step-finding">{step.finding}</div>
                    <div className="step-reason">{step.reason}</div>
                  </>
                )}
                {step.state === 'in-progress' && (!step.thoughts || step.thoughts.length === 0) && step.reason && (
                  <div className="step-reason" style={{fontStyle: 'normal', color: 'var(--ink-2)'}}>
                    {step.reason}
                  </div>
                )}
              </div>
            </div>
          ))}
        </div>

        {errored && (
          <div className="stream-error" data-testid="stream-error">
            <Icon.Alert />
            <div style={{flex: 1}}>
              <h4>The run hit an error</h4>
              <p>{errorMessage || 'We couldn’t complete the run. Your inputs are saved; you can retry or change them.'}</p>
              <div style={{display: 'flex', gap: 8}}>
                <button className="btn btn-primary btn-sm" onClick={onRetry}>Retry run</button>
                <button className="btn btn-ghost btn-sm" onClick={cancel}>Change inputs</button>
              </div>
            </div>
          </div>
        )}

        {!errored && stepIdx < totalSteps && (
          <div className="stream-cancel">
            <button className="btn btn-ghost btn-sm" onClick={cancel}>Cancel run</button>
          </div>
        )}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// 4. RESULT PAGE
// ─────────────────────────────────────────────────────────────────────
function ResultScreen({ runId, workappSlug, inputs, signedIn, onShare, onSave, onRerun, onRelatedClick, onRequireAuth, onCompareUrl, onExpert, onMethodNote }) {
  // State variables shadow the prototype's window.WORKAPP / EXPERT / etc.
  // globals (now nulled out by data.jsx). The capitalization is intentional:
  // it lets the JSX field accessors below stay byte-identical to the design
  // team's source files, preserving visual fidelity.
  const [r, setR] = useState_s(null);
  const [w, setW] = useState_s(null);
  const [e, setE] = useState_s(null);
  const [runMeta, setRunMeta] = useState_s(null);
  const [runInput, setRunInput] = useState_s(null);
  const [methodology, setMethodology] = useState_s(null);
  const [RUN_STEPS, setRunSteps] = useState_s([]);
  const [RELATED, setRELATED] = useState_s([]);
  const [runStatus, setRunStatus] = useState_s('loading');
  const [runError, setRunError] = useState_s(null);
  const [openStep, setOpenStep] = useState_s(0);
  const [traceModalOpen, setTraceModalOpen] = useState_s(false);
  const [compareUrl, setCompareUrl] = useState_s('');
  const [usefulness, setUsefulness] = useState_s('');
  const [usefulnessReason, setUsefulnessReason] = useState_s('');
  const [copiedRecommendation, setCopiedRecommendation] = useState_s(false);
  const [methodNotice, setMethodNotice] = useState_s('');
  const [evidenceSources, setEvidenceSources] = useState_s([]);
  const [essay, setEssay] = useState_s(null);

  // Load workapp + expert + related from the workapp slug.
  // Fallback: if workappSlug prop is empty, fetch it from the run first.
  useEffect_s(() => {
    let cancelled = false;
    async function load() {
      let slug = workappSlug;
      if (!slug && runId) {
        try {
          const run = await window.AppData.getRun(runId);
          slug = run && run.workappSlug;
        } catch (e) { /* ignore; header will just be missing */ }
      }
      if (!slug || cancelled) return;
      window.AppData.getWorkapp(slug).then((d) => {
        if (cancelled) return;
        setW(d.wapp);
        setE(d.expert);
        setMethodology(d.methodology || null);
        setEvidenceSources(Array.isArray(d.evidenceSources) ? d.evidenceSources : []);
        setEssay(d.essay || null);
        d.relatedPromise.then((rel) => { if (!cancelled) setRELATED(rel || []); }).catch(() => {});
      }).catch(() => {});
    }
    load();
    return () => { cancelled = true; };
  }, [runId, workappSlug]);

  // Poll the run until done or errored
  useEffect_s(() => {
    if (!runId) return;
    let cancelled = false;
    let timer = null;
    const tick = () => {
      window.AppData.getRun(runId).then((rs) => {
        if (cancelled) return;
        setRunSteps(rs.runSteps || []);
        if (rs.result) setR(rs.result);
        if (rs.runMeta) setRunMeta(rs.runMeta);
        if (rs.input) setRunInput(rs.input);
        setRunStatus(rs.status);
        if (rs.status === 'running' || rs.status === 'queued') {
          timer = setTimeout(tick, 1500);
        } else if (rs.status === 'error') {
          setRunError(rs.error || 'Run failed');
        }
      }).catch((err) => {
        if (cancelled) return;
        if (err && err.status === 401) {
          window.AppData.redirectToLogin();
          return;
        }
        setRunError(err);
        setRunStatus('error');
      });
    };
    tick();
    return () => { cancelled = true; if (timer) clearTimeout(timer); };
  }, [runId]);

  // Derive a target label from the actual run input (URL-ish first, else first string value).
  function _deriveTarget(rawInputs) {
    if (!rawInputs || typeof rawInputs !== 'object') return '';
    const vals = Object.values(rawInputs).filter(v => typeof v === 'string' && v.trim().length > 0);
    const urlish = vals.find(v => /^https?:\/\//i.test(v) || /\.[a-z]{2,}/i.test(v));
    if (urlish) return urlish.replace(/^https?:\/\//, '').replace(/\/$/, '');
    return vals[0] || '';
  }
  const effectiveInputs = runInput || inputs || {};
  const targetLabel = _deriveTarget(effectiveInputs) || 'your input';
  const domain = targetLabel;

  const submitCompare = (ev) => {
    ev.preventDefault();
    let v = compareUrl.trim();
    if (!v) return;
    if (!/^https?:\/\//i.test(v)) v = 'https://' + v;
    if (!signedIn) {
      onRequireAuth('Create a free account to run another comparison');
      return;
    }
    onCompareUrl && onCompareUrl(v);
  };

  if (runStatus === 'error') {
    return <div className="loading" style={{padding: 40}}>Run failed{runError && runError.message ? ': ' + runError.message : runError ? ': ' + String(runError) : ''}.</div>;
  }
  if (!r || !w || !e) {
    return <div className="loading" style={{padding: 40}}>Loading result…</div>;
  }

  const variantVerdict = r.verdictHeadline || '';
  const variantSub = r.verdictSub || '';
  const resultTitle = w.title;
  const resultMeta = `RESULT · ${domain.toUpperCase()}${runMeta && runMeta.runDate ? ' · ' + new Date(runMeta.runDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).toUpperCase() : ''}`;

  // Two output shapes from the runtime:
  //   canonical → {verdictHeadline, findings, recommendation, ...}
  //   html     → {html: "<div>…</div>"}  (workapps that emit a rendered card)
  const hasHtml = typeof r.html === 'string' && r.html.length > 0;
  const hasCanonical = !!variantVerdict || (Array.isArray(r.findings) && r.findings.length > 0);

  // Format verdict headline with em
  const verdictParts = variantVerdict.split(/(\*[^*]+\*)/g).map((p, i) => {
    if (p.startsWith('*') && p.endsWith('*')) return <em key={i}>{p.slice(1, -1)}</em>;
    return <span key={i}>{p}</span>;
  });

  // Derive everything from real data; only fall back to neutral placeholders.
  const firstAction = (r.findings && r.findings[0] && r.findings[0].action) || (r.recommendation || '').split('. ')[0] || 'Start with the highest-priority finding.';
  const audience = effectiveInputs.audience || w?.audience || '';
  // findingMeta is no longer hardcoded — read from each finding's own data.
  const findingMeta = (r.findings || []).map(f => ({
    section: f.evidenceLocation || '',
    impact: f.impact || '',
    effort: f.effort || '',
  }));
  // Method steps come from the workapp's methodology when available.
  const methodSteps = (methodology && (
    (methodology.methodologySteps || []).map(s => s.title || s.name || s.description).filter(Boolean)
  )) || [];

  const copyText = (text) => {
    navigator.clipboard?.writeText(text).catch(() => {});
    setCopiedRecommendation(true);
    setTimeout(() => setCopiedRecommendation(false), 1800);
  };

  return (
    <div className="result-page" data-screen-label="04 Result">
      <div className="wrap">
        <div className="result-head">
          <div>
            <div className="result-meta">{resultMeta}</div>
            <h1 className="result-h serif">{resultTitle}</h1>
            <div className="result-compact-meta">
              <span>Analyzed: {domain}</span>
              <span>Method by {e && e.name || "expert"}: {w && w.title ? w.title.toLowerCase() : ""}</span>
            </div>
          </div>
        </div>

        <div className="result-actions">
            <button className="btn btn-primary btn-sm result-share-action" data-ph-cta="share_result" onClick={onShare}>
              <Icon.Share className="icon-sm" /> Share
            </button>
            <div className="result-secondary-actions">
              <button className="btn btn-ghost btn-sm" data-ph-cta="save_workapp" onClick={() => signedIn ? onSave() : onRequireAuth('Create a free account to save this result')}>
                <Icon.Bookmark className="icon-sm" /> Save
              </button>
              <button className="btn btn-ghost btn-sm" onClick={() => signedIn ? onRerun() : onRequireAuth('Create a free account to rerun this diagnostic')}>
                <Icon.Refresh className="icon-sm" /> Run again
              </button>
              <button className="btn btn-ghost btn-sm" onClick={() => setTraceModalOpen(true)}>
                <Icon.Activity className="icon-sm" /> How this was analyzed
              </button>
            </div>
            <details className="result-mobile-more">
              <summary className="btn btn-ghost btn-sm">More</summary>
              <div className="result-mobile-menu">
                <button className="btn btn-ghost btn-sm" onClick={() => signedIn ? onSave() : onRequireAuth('Create a free account to save this result')}>
                  <Icon.Bookmark className="icon-sm" /> Save
                </button>
                <button className="btn btn-ghost btn-sm" onClick={() => signedIn ? onRerun() : onRequireAuth('Create a free account to rerun this diagnostic')}>
                  <Icon.Refresh className="icon-sm" /> Run again
                </button>
                <button className="btn btn-ghost btn-sm" onClick={() => setTraceModalOpen(true)}>
                  <Icon.Activity className="icon-sm" /> How this was analyzed
                </button>
              </div>
            </details>
        </div>

        {/* Evidence sources strip — shown for both canonical and HTML results */}
        {evidenceSources.length > 0 && (
          <div className="evidence-sources" style={{marginBottom: 16}}>
            <span>Evidence sources</span>
            <div>
              {evidenceSources.map((source) => (
                <b key={source.name}>{source.name}</b>
              ))}
            </div>
          </div>
        )}

        {/* HTML output path — workapp emitted a rendered card */}
        {hasHtml && !hasCanonical && (
          <ResultHtmlFrame html={r.html} />
        )}

        {/* Structured (canonical) output path */}
        {hasCanonical && (
        <>
        <div className="verdict">
          <div className="verdict-label">Verdict</div>
          <h2 className="verdict-line serif">{verdictParts}</h2>
          <p className="verdict-sub">{variantSub}</p>
          <div className="verdict-confidence">
            {(r.findings && r.findings[0]?.priority) && (
              <div><span className="lab">Priority</span><span className="val">{r.findings[0].priority.charAt(0).toUpperCase() + r.findings[0].priority.slice(1)}</span></div>
            )}
            {r.confidence && <div><span className="lab">Confidence</span><span className="val">{r.confidence}</span></div>}
            {r.basis && <div><span className="lab">Scope</span><span className="val">{r.basis}</span></div>}
            {audience && <div><span className="lab">Audience</span><span className="val">{audience}</span></div>}
          </div>
          {firstAction && (
            <div className="verdict-first-action">
              <span>Do this first</span>
              <b>{firstAction}</b>
            </div>
          )}
        </div>

        {r.verdictSub && (
          <div className="plain-summary">
            <h2>In plain English</h2>
            <p>{r.verdictSub}</p>
          </div>
        )}

        {/* Findings */}
        <div className="result-grid">
          <div>
            <div className="recommendation-block recommendation-priority">
              <div className="block-h">Recommendation</div>
              {(() => {
                const recText = (r.recommendation || '').trim();
                const periodIdx = recText.indexOf('. ');
                const heading = periodIdx > 0 ? recText.slice(0, periodIdx + 1) : recText.split('\n')[0] || recText;
                const body = periodIdx > 0 ? recText.slice(periodIdx + 2) : '';
                return (
                  <>
                    {heading && <h3>{heading}</h3>}
                    {body && <p className="block-body">{body}</p>}
                  </>
                );
              })()}
              {r.recommendationNotes && (
                <div className="recommendation-notes">
                  {r.recommendationNotes.whyFirst && <p><b>Why first:</b> {r.recommendationNotes.whyFirst}</p>}
                  {r.recommendationNotes.impactEffort && <p><b>Impact / effort:</b> {r.recommendationNotes.impactEffort}</p>}
                  {r.recommendationNotes.suggestedDirection && <p><b>Suggested direction:</b> {r.recommendationNotes.suggestedDirection}</p>}
                </div>
              )}
              {r.recommendationNotes && r.recommendationNotes.exampleDirection && (
                <div className="rewrite-direction">
                  <div className="example-direction">
                    <span>Example direction</span>
                    <p>{r.recommendationNotes.exampleDirection}</p>
                  </div>
                </div>
              )}
              <button className="btn btn-ghost btn-sm" type="button" onClick={() => copyText(r.recommendation, 'Recommendation copied')}>
                {copiedRecommendation ? <Icon.CheckSm className="icon-sm" /> : <Icon.Copy className="icon-sm" />} {copiedRecommendation ? 'Copied' : 'Copy recommendation'}
              </button>
            </div>

            {Array.isArray(r.findings) && r.findings.length > 0 && (
              <>
                <h3 className="findings-h">Top findings</h3>
                {r.findings.map(function (f, i) {
                  const meta = (window.ResultHelpers && window.ResultHelpers.deriveFindingMeta(f)) || { section: '', impact: '', effort: '' };
                  const p = (f.priority || '').toLowerCase();
                  const priorityLabel = p === 'high' ? 'High priority' : (p === 'medium' ? 'Medium' : (p === 'low' ? 'Low' : 'Finding'));
                  const priorityClass = p === 'high' || p === 'medium' || p === 'low' ? p : 'medium';
                  return (
                    <div className="finding" key={i}>
                      <div className="finding-num">{String(i + 1).padStart(2, '0')}</div>
                      <div className="finding-body">
                        <h4>{f.title}</h4>
                        <div className="finding-topline">
                          <span className={'priority-pill priority-' + priorityClass}>{priorityLabel}</span>
                          {meta.section && <span className="finding-chip">{meta.section}</span>}
                          {meta.impact && <span className="finding-chip">Impact: {meta.impact}</span>}
                          {meta.effort && <span className="finding-chip">Fix effort: {meta.effort}</span>}
                          {f.evidenceSrc && <span className="finding-chip">Source: {f.evidenceSrc}</span>}
                        </div>
                        {f.why && (
                          <div className="finding-report-section">
                            <h5>Why it matters</h5>
                            <p>{f.why}</p>
                          </div>
                        )}
                        {f.evidence && (
                          <div className="finding-report-section">
                            <h5>Evidence from the page</h5>
                            <div className="evidence">
                              <blockquote>{'“' + f.evidence + '”'}</blockquote>
                              {f.evidenceLocation && <div className="evidence-meta">Location: {f.evidenceLocation}</div>}
                              {f.evidenceTrigger && <p className="evidence-trigger">{f.evidenceTrigger}</p>}
                            </div>
                          </div>
                        )}
                        {f.action && (
                          <div className="finding-report-section">
                            <h5>Recommended fix</h5>
                            <p>{f.action}</p>
                          </div>
                        )}
                      </div>
                    </div>
                  );
                })}
              </>
            )}

            <div className="ignore-block">
              <div className="block-h">Don’t fix yet</div>
              <p className="ignore-subtitle">These may look tempting, but they are not the current bottleneck.</p>
              <ul className="ignore-list">
                {r.ignore.map((it, i) => (
                  <li key={i}><Icon.X className="icon-sm" /><span>{it}</span></li>
                ))}
              </ul>
            </div>

            <div className="result-method-strip result-method-panel">
              <ExpertAvatar person={e} className="expert-avatar result-method-avatar" style={{background: 'linear-gradient(135deg, #111111, #555552)'}} />
              <div className="result-method-copy">
                <div className="result-method-label">Method by {e.name}</div>
                <div className="expert-name">{e.name}</div>
                <div className="expert-title">{e.title}</div>
                {e.bio ? (
                  <p className="result-method-bio">{e.bio}</p>
                ) : null}
                {e.proofLine ? (
                  <div className="method-proof">Proof: {e.proofLine}</div>
                ) : null}
                <div className="method-check-title">What the method checked</div>
                <ol className="result-method-steps">
                  {methodSteps.map((step, i) => <li key={i}>{step}</li>)}
                </ol>
              </div>
              <div className="result-method-actions">
                <button className="btn btn-ghost btn-sm result-method-cta" onClick={() => setTraceModalOpen(true)}>
                  How this was analyzed
                </button>
                <button className="btn btn-ghost btn-sm result-method-cta" onClick={() => onMethodNote && onMethodNote()}>
                  Read {e.name}'s method note
                </button>
                <button
                  className="btn btn-ghost btn-sm result-method-cta"
                  onClick={() => onExpert && onExpert()}
                >
                  View expert profile
                </button>
              </div>
            </div>

            <div className="usefulness-block usefulness-inline" aria-live="polite">
              <div className="usefulness-prompt">Useful?</div>
              <div className="usefulness-options" role="group" aria-label="Rate result usefulness">
                {['Yes', 'Somewhat', 'No'].map(label => (
                  <button
                    key={label}
                    type="button"
                    className={'btn btn-ghost btn-sm' + (usefulness === label ? ' active' : '')}
                    onClick={() => {
                      setUsefulness(label);
                      if (runId && window.AppData && window.AppData.giveFeedback) {
                        window.AppData.giveFeedback(runId, label, usefulnessReason).catch(() => {});
                      }
                    }}
                  >
                    {usefulness === label && <Icon.CheckSm />} {label}
                  </button>
                ))}
              </div>
              {usefulness && usefulness !== 'Yes' && (
                <div className="usefulness-reasons">
                  {['Not specific enough', 'Wrong context', 'Already knew this', 'Wanted deeper steps'].map(reason => (
                    <button
                      key={reason}
                      type="button"
                      className={'chip' + (usefulnessReason === reason ? ' chip-active' : '')}
                      onClick={() => {
                        setUsefulnessReason(reason);
                        if (runId && window.AppData && window.AppData.giveFeedback) {
                          window.AppData.giveFeedback(runId, usefulness, reason).catch(() => {});
                        }
                      }}
                    >
                      {reason}
                    </button>
                  ))}
                </div>
              )}
              {usefulness && (
                <div className="usefulness-thanks">
                  <Icon.CheckSm className="icon-sm" /> Thanks. That signal helps improve the method.
                </div>
              )}
            </div>

          </div>
        </div>
        </>
        )}

        {/* RUN ANOTHER — peak-intent surface (moved to bottom) */}
        <div className="run-another" style={{marginTop: 56}}>
          <div className="run-another-l">
            <div className="section-label" style={{color: 'var(--accent)', marginBottom: 8}}>Run again</div>
            <h3 className="serif" style={{margin: '0 0 6px', fontSize: 26, fontWeight: 500, letterSpacing: '-0.014em'}}>Run {e && e.name ? e.name + "'s method" : 'this method'} on a different input</h3>
            <p style={{margin: 0, fontSize: 14, color: 'var(--muted)', lineHeight: 1.5, maxWidth: '50ch'}}>
              Apply the same method to another target — a competitor, a related page, or a revised version of your own.
            </p>
            <form className="run-another-form" onSubmit={submitCompare}>
              <input
                className="input"
                placeholder="another input"
                value={compareUrl}
                onChange={(ev) => setCompareUrl(ev.target.value)}
              />
              <button type="submit" className="btn btn-primary">Run <Icon.ArrowRight className="icon-sm" /></button>
            </form>
          </div>
          <div className="run-another-r">
            <div className="run-another-r-label">Or try a related method</div>
            <div className="run-another-cards">
              {RELATED.slice(0, 2).map((rw, i) => (
                <a key={i} className="run-another-card" href={rw.slug ? '#/w/' + rw.slug : '#/workapps'} onClick={(ev) => { ev.preventDefault(); onRelatedClick && onRelatedClick(rw); }}>
                  <div className="run-another-card-meta">{rw.time}</div>
                  <div className="run-another-card-title">{rw.title}</div>
                  <div className="run-another-card-foot">
                    <span>By {rw.expert}</span>
                    <Icon.ArrowRight className="icon-sm" />
                  </div>
                </a>
              ))}
            </div>
          </div>
        </div>

        {/* Related */}
        <div style={{padding: '56px 0', borderTop: '1px solid var(--hair)'}}>
          <h2 className="h-section" style={{marginBottom: 24}}>Related WorkApps</h2>
          <div className="related-grid">
            {RELATED.map((w, i) => (
              <WorkAppCard key={i} wapp={w} variant="compact" onClick={() => onRelatedClick && onRelatedClick(w)} />
            ))}
          </div>
        </div>
      </div>
      <Modal open={traceModalOpen} onClose={() => setTraceModalOpen(false)}>
        <div className="trace-modal-head">
          <div>
            <div className="result-meta" style={{marginBottom: 6}}>METHOD · {domain.toUpperCase()}</div>
            <h3>How this result was analyzed</h3>
            <p>{e.name}’s method applied to {domain} · {RUN_STEPS.length} diagnostic step{RUN_STEPS.length === 1 ? '' : 's'} · {(RUN_STEPS.reduce((a, s) => a + (s.duration || 0), 0) / 1000).toFixed(1)}s run</p>
          </div>
          <button className="btn btn-bare btn-sm" onClick={() => setTraceModalOpen(false)} aria-label="Close method">
            <Icon.X />
          </button>
        </div>

        <div className="trace-body trace-body-modal">
          <div className="trace-rule">
            <span className="trace-rule-l">Analysis steps</span>
            <span className="trace-rule-r">Tap a step to see the reasoning</span>
          </div>

          <ol className="trace-steps">
            {RUN_STEPS.map((step, i) => {
              const isOpen = openStep === i;
              return (
                <li key={i} className={'trace-step' + (isOpen ? ' open' : '')}>
                  <button
                    className="trace-step-head"
                    onClick={() => setOpenStep(isOpen ? -1 : i)}
                  >
                    <span className="trace-step-num">{String(i + 1).padStart(2, '0')}</span>
                    <div className="trace-step-meta">
                      <div className="trace-step-name">{step.name}</div>
                      <code className="trace-step-tool">{step.tool}</code>
                    </div>
                    <span className="trace-step-dur">{(step.duration / 1000).toFixed(1)}s</span>
                    <span className="trace-step-chev"><Icon.ChevronDown /></span>
                  </button>
                  {isOpen && (
                    <div className="trace-step-body">
                      <div className="trace-row">
                        <div className="trace-row-k">Finding</div>
                        <div className="trace-row-v">{step.finding}</div>
                      </div>
                      <div className="trace-row">
                        <div className="trace-row-k">Why this step</div>
                        <div className="trace-row-v" style={{color: 'var(--ink-2)', fontStyle: 'italic'}}>{step.reason}</div>
                      </div>
                    </div>
                  )}
                </li>
              );
            })}
          </ol>

          <div className="trace-method">
            <div className="trace-rule">
              <span className="trace-rule-l">Methodology</span>
              <span className="trace-rule-r">By {e.name}</span>
            </div>
            {essay && essay.pullQuote ? (
              <p className="trace-method-lede">
                This diagnostic encodes one rule: <em>{essay.pullQuote}</em> Every step above feeds that single judgment.
              </p>
            ) : (
              <p className="trace-method-lede">
                Every step above feeds a single judgment from {e.name}'s method.
              </p>
            )}
            <ol className="trace-method-list">
              {(() => {
                const principles = (window.ResultHelpers && window.ResultHelpers.deriveMethodPrinciples(methodology)) || [];
                if (principles.length === 0) {
                  return (
                    <li>
                      <div>
                        <div className="trace-method-h">Method principles</div>
                        <p>This WorkApp's author has not yet published a method-summary section. The diagnostic still runs the methodology steps shown above.</p>
                      </div>
                    </li>
                  );
                }
                return principles.map((pr, i) => (
                  <li key={i}>
                    <div>
                      <div className="trace-method-h">{pr.heading}</div>
                      <p>{pr.body}</p>
                    </div>
                  </li>
                ));
              })()}
            </ol>
            <div className="trace-method-foot">
              <button
                className="btn btn-ghost btn-sm"
                onClick={() => onMethodNote ? onMethodNote() : setMethodNotice((e.name || 'Expert') + "'s method note lives on the WorkApp detail page.")}
              >
                Read {e.name}'s method note
              </button>
              <button className="btn btn-bare btn-sm" onClick={() => { setTraceModalOpen(false); onExpert && onExpert(); }}>
                About {e.name} <Icon.ArrowRight className="icon-sm" />
              </button>
            </div>
            {methodNotice && <div className="inline-status" style={{marginTop: 10}}><Icon.CheckSm className="icon-sm" /> {methodNotice}</div>}
          </div>
        </div>
      </Modal>
      <Footer />
    </div>
  );
}

Object.assign(window, { DetailScreen, RunInputScreen, StreamingScreen, ResultScreen });
