// Modal infrastructure + 3 specific modals: Service detail, Contract proposal, Add rule.
const { Icons, DATA } = window;
const { Pill, OwnerChip, Tabs, currency } = window;
const { useState: useStateM, useEffect: useEffectM } = React;

// ---------- Generic modal shell ----------
function Modal({ open, onClose, children, width = 880, title, eyebrow, headerRight }) {
  useEffectM(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => {
      window.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
    };
  }, [open]);

  if (!open) return null;
  return (
    <div className="md-scrim" onClick={onClose}>
      <div className="md" style={{ width: `min(${width}px, 96vw)` }} onClick={(e) => e.stopPropagation()}>
        <div className="md-head">
          <div style={{ minWidth: 0, flex: 1 }}>
            {eyebrow && <div className="eyebrow" style={{ marginBottom: 6 }}>{eyebrow}</div>}
            <h2 style={{ fontFamily: "var(--serif)", fontSize: 26, letterSpacing: "-0.02em", color: "var(--ink)", fontWeight: 400, lineHeight: 1.1 }}>{title}</h2>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            {headerRight}
            <button className="btn icon ghost" onClick={onClose} title="Close (esc)">
              <Icons.X s={14}/>
            </button>
          </div>
        </div>
        <div className="md-body">{children}</div>
      </div>
    </div>
  );
}

// ---------- 1) Service detail modal, keys + apps ----------
function ServiceDetailModal({ service, onClose, onOpenApp, policy, onSetPolicy, posture }) {
  if (!service) return null;

  // Build the per-app key inventory from service shape.
  const keys = serviceKeys(service);
  const totalCost = service.cost;

  const isLLM = service.kind === "LLM";
  const eyebrow = `External service · ${service.kind} · ${service.apps.length} apps`;

  return (
    <Modal
      open={!!service} onClose={onClose}
      width={920}
      eyebrow={eyebrow}
      title={service.name}
      headerRight={
        <>
          <button className="btn sm"><Icons.Flask s={12}/> Test connection</button>
          <button className="btn sm primary"><Icons.Plus s={12}/> Issue per-app key</button>
        </>
      }>
      {/* top facts strip */}
      <div className="md-stats">
        <div><div className="md-stat-l">Spend / mo</div><div className="md-stat-v">{currency(totalCost)}</div></div>
        <div><div className="md-stat-l">Behavior</div><div className="md-stat-v">{service.det ? "deterministic" : "non-det"}</div></div>
        <div><div className="md-stat-l">Apps</div><div className="md-stat-v">{service.apps.length}</div></div>
        <div><div className="md-stat-l">Keys on file</div><div className="md-stat-v">{keys.length}</div></div>
      </div>

      {/* Outbound policy editor */}
      {policy && onSetPolicy && (
        <div className="md-section">
          <div className="md-section-head">
            <h3>Outbound policy</h3>
            <span className="subtitle">How Stoda treats outbound calls to <span className="sig">{service.name}</span> across the fleet.</span>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
            <PolicyOpt
              cur={policy} val="allowed" set={onSetPolicy}
              label="Allowlisted" sub="Apps may call this service. Explicit allow."/>
            <PolicyOpt
              cur={policy} val="blocked" set={onSetPolicy}
              label="Blocked" sub="All outbound calls fail closed. Deploys using it are held."/>
            <PolicyOpt
              cur={policy} val="inherit" set={onSetPolicy}
              label={posture === "default-deny" ? "Default-deny" : "Default-allow"}
              sub={posture === "default-deny"
                ? "Inherit org posture. Service is implicitly denied unless allowlisted."
                : "Inherit org posture. Service is implicitly allowed unless blocked."}/>
          </div>
        </div>
      )}

      {/* Shared-key warning */}
      {service.sharedKey && (
        <div className="md-banner risk">
          <Icons.Alert s={14}/>
          <div>
            <b>Shared key across {service.apps.length} apps.</b> {currency(totalCost)} this month is unattributed, costs roll up to one team, blast radius covers every app using <span className="sig">{service.sharedKey}</span>. Per-app keys restore team-level P&L and isolate failures.
          </div>
          <button className="btn sm primary" style={{ flexShrink: 0 }}>Split into per-app keys <Icons.Arrow s={12}/></button>
        </div>
      )}
      {!service.sharedKey && service.apps.length > 1 && (
        <div className="md-banner ok">
          <Icons.Check s={14}/>
          <div>
            <b>Per-app keys.</b> Each app holds its own secret, costs attributed by key, blast radius scoped to one app.
          </div>
        </div>
      )}

      {/* Keys table */}
      <div className="md-section">
        <div className="md-section-head">
          <h3>API keys ({keys.length})</h3>
          <span className="subtitle">Last-4 of secret · scope · who can use it · last rotation</span>
        </div>
        <div className="card flush">
          <table className="t">
            <thead><tr>
              <th>Key</th><th>Scope</th><th>Apps</th><th>Created</th><th>Last rotated</th><th>Status</th><th></th>
            </tr></thead>
            <tbody>
              {keys.map((k, i) => (
                <tr key={i} style={{ cursor: "default" }}>
                  <td>
                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <span style={{ width: 22, height: 22, borderRadius: 6, background: k.scope === "shared" ? "var(--amber-bg)" : "var(--cream-3)", border: "1px solid var(--tan-2)", display: "inline-flex", alignItems: "center", justifyContent: "center", color: k.scope === "shared" ? "var(--amber)" : "var(--ink-3)" }}>
                        <Icons.Lock s={11}/>
                      </span>
                      <div>
                        <div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--ink)" }}>
                          {k.id}<span style={{ color: "var(--muted-2)" }}>···</span>{k.last4}
                        </div>
                        <div style={{ fontSize: 11, color: "var(--muted)", marginTop: 1 }}>{k.envName}</div>
                      </div>
                    </div>
                  </td>
                  <td>
                    {k.scope === "shared"
                      ? <Pill kind="warn">shared · {k.apps.length} apps</Pill>
                      : <Pill kind="neutral">per-app</Pill>}
                  </td>
                  <td>
                    <div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
                      {k.apps.slice(0, 3).map((a, j) => (
                        <button key={j} className="md-app-chip" onClick={() => onOpenApp && onOpenApp(a)}>
                          {a} <Icons.Arrow s={10}/>
                        </button>
                      ))}
                      {k.apps.length > 3 && <span style={{ fontSize: 11, color: "var(--muted)", alignSelf: "center" }}>+{k.apps.length - 3}</span>}
                    </div>
                  </td>
                  <td style={{ color: "var(--muted)", fontSize: 12 }}>{k.created}</td>
                  <td style={{ color: "var(--muted)", fontSize: 12 }}>{k.rotated}</td>
                  <td>
                    {k.status === "ok" && <Pill kind="ok">active</Pill>}
                    {k.status === "stale" && <Pill kind="warn">{k.staleReason || "stale"}</Pill>}
                    {k.status === "issue" && <Pill kind="risk">issue</Pill>}
                  </td>
                  <td style={{ color: "var(--muted)", textAlign: "right" }}>
                    <button className="btn ghost sm">Rotate</button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {/* Recent calls / spend split */}
      <div className="md-section">
        <div className="md-section-head">
          <h3>Spend split, last 30 days</h3>
          <span className="subtitle">Per-app attribution from request tags + key mapping.</span>
        </div>
        <div className="card">
          <SpendSplit service={service}/>
        </div>
      </div>

      {/* Notes if non-det */}
    </Modal>
  );
}

// Build deterministic key inventory from a service record.
function serviceKeys(s) {
  const out = [];
  // Shared key, if present (always the problem state, unattributed, broad blast radius)
  if (s.sharedKey) {
    out.push({
      id: s.sharedKey.toLowerCase().replace(/_/g, "-"),
      last4: hashLast4(s.sharedKey + ":shared"),
      envName: s.sharedKey,
      scope: "shared",
      apps: s.apps,
      created: "Aug 14, 2025",
      rotated: "Nov 02, 2025",
      status: "stale",
      staleReason: "shared · unattributed",
    });
  }
  // Per-app keys (when not shared, or for some apps even if shared)
  const perAppApps = s.sharedKey ? [] : s.apps;
  perAppApps.forEach((appName, i) => {
    out.push({
      id: keyId(s.name, appName),
      last4: hashLast4(s.name + ":" + appName),
      envName: keyEnv(s, appName),
      scope: "per-app",
      apps: [appName],
      created: ["Sep 22, 2025", "Oct 04, 2025", "Jan 18, 2026", "Feb 02, 2026"][i % 4],
      rotated: ["Mar 18, 2026", "Apr 02, 2026", "Mar 28, 2026", "Apr 11, 2026"][i % 4],
      status: i === 1 ? "stale" : "ok",
      staleReason: i === 1 ? "180d+ since rotation" : null,
    });
  });
  return out;
}
function hashLast4(seed) {
  let h = 0;
  for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) | 0;
  const v = Math.abs(h) % 10000;
  return v.toString(16).toUpperCase().padStart(4, "0");
}
function keyId(svc, app) {
  const a = app.toLowerCase().split(/\s+/).map(w => w[0]).join("").slice(0, 3);
  const s = svc.toLowerCase().replace(/[^a-z]/g, "").slice(0, 6);
  return `${s}-${a}`;
}
function keyEnv(s, app) {
  const base = s.kind === "LLM" ? "API_KEY" : "TOKEN";
  const tag = app.toUpperCase().replace(/\s+/g, "_").replace(/[^A-Z_]/g, "").slice(0, 14);
  const svc = s.name.toUpperCase().replace(/[^A-Z]/g, "").slice(0, 8);
  return `${svc}_${tag}_${base}`;
}

function SpendSplit({ service }) {
  // Synthesize a stable split by app, summing to service.cost.
  const total = service.cost || 1;
  const apps = service.apps;
  // Weights: derived from app name hash, normalized.
  const weights = apps.map(a => {
    let h = 0; for (let i = 0; i < a.length; i++) h = (h * 17 + a.charCodeAt(i)) | 0;
    return (Math.abs(h) % 7) + 1;
  });
  const wSum = weights.reduce((s, w) => s + w, 0);
  const split = apps.map((a, i) => ({
    app: a,
    cost: Math.round(total * weights[i] / wSum),
    pct: weights[i] / wSum,
  }));

  return (
    <div>
      <div className="costbar" style={{ height: 14 }}>
        {split.map((p, i) => (
          <span key={i} title={`${p.app}, ${currency(p.cost)}`}
            style={{ width: `${p.pct * 100}%`, background: ["var(--orange)", "var(--ink-2)", "var(--green)", "var(--blue)", "var(--amber)"][i % 5] }}/>
        ))}
      </div>
      <div style={{ marginTop: 14, display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: 8 }}>
        {split.map((p, i) => (
          <div key={i} style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 12 }}>
            <span style={{ width: 8, height: 8, borderRadius: 2, background: ["var(--orange)", "var(--ink-2)", "var(--green)", "var(--blue)", "var(--amber)"][i % 5], flexShrink: 0 }}/>
            <span style={{ color: "var(--ink)", flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.app}</span>
            <span style={{ fontVariantNumeric: "tabular-nums", color: "var(--muted)" }}>{currency(p.cost)}</span>
            <span style={{ fontVariantNumeric: "tabular-nums", color: "var(--muted-2)", fontSize: 11 }}>{Math.round(p.pct * 100)}%</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// PolicyOpt — option button used by ServiceDetailModal's policy editor.
function PolicyOpt({ val, cur, set, label, sub }) {
  const on = cur === val;
  return (
    <button onClick={() => set(val)} className={"md-opt block" + (on ? " on" : "")}>
      <div style={{ fontSize: 13, color: "var(--ink)", fontWeight: 500 }}>{label}</div>
      <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 2 }}>{sub}</div>
    </button>
  );
}

// ---------- 2) Contract proposal modal ----------
function ContractProposalModal({ open, onClose }) {
  const [tab, setTab] = useStateM("schema");

  // The proposed contract for deals.postgres, used by Quote Copilot, Onboarding Wizard, Churn Early Warning.
  const fields = [
    { name: "id",            type: "uuid",                read: ["QC", "OW", "CEW"], write: ["QC", "OW"], conflict: false, note: "Primary key. Stable across all 3 apps." },
    { name: "account_id",    type: "uuid",                read: ["QC", "OW", "CEW"], write: ["QC", "OW"], conflict: false, note: "FK to accounts.postgres." },
    { name: "stage",         type: "enum<8>",             read: ["QC", "OW", "CEW"], write: ["QC", "OW"], conflict: true,  note: "QC writes free-string stage names; OW writes the canonical enum. Contract aligns to enum." },
    { name: "amount_cents",  type: "int64",               read: ["QC", "OW", "CEW"], write: ["QC", "OW"], conflict: false, note: "Always cents. QC v122 had a $-floats branch, already migrated." },
    { name: "currency",      type: "iso4217",             read: ["QC", "CEW"],       write: ["QC"],       conflict: false, note: "Defaults to USD if absent." },
    { name: "owner_email",   type: "email",               read: ["QC", "OW", "CEW"], write: ["QC", "OW"], conflict: false, note: "Stoda PII tag, sent only to det endpoints." },
    { name: "closed_at",     type: "timestamp?",          read: ["CEW"],             write: ["QC"],       conflict: true,  note: "QC sets, OW touches via trigger, CEW reads. Contract makes the trigger explicit." },
    { name: "legacy_stage",  type: "string?",             read: ["CEW"],             write: [],            conflict: true,  note: "DEPRECATED. CEW still reads; migration plan removes it Apr 30." },
  ];

  const apps = [
    { code: "QC",  name: "Quote Copilot",       owner: "ml", read: 7, write: 4, status: "ready",   note: "Already typed against draft schema. 0 changes needed." },
    { code: "OW",  name: "Onboarding Wizard",   owner: "ml", read: 6, write: 3, status: "patch",   note: "Stoda will rewrite 3 call sites, `stage` string → enum mapping." },
    { code: "CEW", name: "Churn Early Warning", owner: "ml", read: 5, write: 0, status: "patch",   note: "Drops `legacy_stage` read after Apr 30 migration. Staging test added." },
  ];

  const tests = [
    { id: "T1", name: "Roundtrip insert/select preserves all 22 fields",      kind: "deterministic", runs: "every PR", status: "passing" },
    { id: "T2", name: "Stage enum rejects free-string writes",                 kind: "deterministic", runs: "every PR", status: "passing" },
    { id: "T3", name: "PII fields never exit to non-det endpoints",            kind: "deterministic", runs: "every PR", status: "passing" },
    { id: "T4", name: "Closed-at trigger fires within 50ms of stage=closed",   kind: "deterministic", runs: "every PR", status: "passing" },
    { id: "T5", name: "Legacy_stage migration: CEW reads degrade gracefully",  kind: "deterministic", runs: "pre-migration", status: "pending" },
    { id: "T6", name: "Contract version bump → all 3 apps regenerate clean",   kind: "synthesis",     runs: "on any change", status: "passing" },
  ];

  return (
    <Modal
      open={open} onClose={onClose}
      width={1040}
      eyebrow="Recommendation · Knowledge"
      title="Codify a shared contract for deals.postgres"
      headerRight={
        <>
          <button className="btn sm">Request changes</button>
          <button className="btn sm">Open in editor</button>
          <button className="btn sm primary"><Icons.Check s={12}/> Approve & codify</button>
        </>
      }>
      <div className="md-banner info">
        <Icons.Book s={14}/>
        <div>
          <b>Why now.</b> Three apps query <span className="sig">deals.postgres</span> directly with no typed contract. Last week's `legacy_stage` rename almost broke Churn Early Warning silently, Stoda caught it on staging. Codifying the contract turns this into a single reviewable diff every time the schema moves.
        </div>
      </div>

      <div style={{ display: "flex", gap: 28, padding: "16px 0", borderBottom: "1px solid var(--tan-2)", marginBottom: 16 }}>
        <ProposalStat label="Apps protected" value="3" foot="QC · OW · CEW"/>
        <ProposalStat label="Fields" value="22" foot="3 with conflicts → reconciled"/>
        <ProposalStat label="Auto-patch sites" value="11" foot="across 2 apps"/>
        <ProposalStat label="Built-in tests" value="6" foot="run on every PR"/>
        <ProposalStat label="Time to merge" value="~22 min" foot="ci + 1 reviewer"/>
      </div>

      <Tabs value={tab} onChange={setTab} items={[
        { value: "schema",    label: `Schema (${fields.length})` },
        { value: "consumers", label: `Consuming apps (${apps.length})` },
        { value: "migration", label: "Migration" },
        { value: "tests",     label: `Tests (${tests.length})` },
      ]}/>

      {tab === "schema" && (
        <div className="card flush" style={{ marginTop: 14 }}>
          <table className="t">
            <thead><tr><th>Field</th><th>Type</th><th>Reads</th><th>Writes</th><th>Notes</th></tr></thead>
            <tbody>
              {fields.map((f, i) => (
                <tr key={i} style={{ cursor: "default", background: f.conflict ? "color-mix(in srgb, var(--amber-bg) 60%, transparent)" : undefined }}>
                  <td>
                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <span className="sig" style={{ fontSize: 12, color: "var(--ink)" }}>{f.name}</span>
                      {f.conflict && <Pill kind="warn">conflict</Pill>}
                      {f.name === "owner_email" && <Pill kind="info">PII</Pill>}
                      {f.name === "legacy_stage" && <Pill kind="neutral">deprecated</Pill>}
                    </div>
                  </td>
                  <td><span className="sig" style={{ color: "var(--orange-dark)" }}>{f.type}</span></td>
                  <td>
                    <div style={{ display: "flex", gap: 3 }}>
                      {f.read.map((c, j) => <span key={j} className="md-tag-mono">{c}</span>)}
                    </div>
                  </td>
                  <td>
                    <div style={{ display: "flex", gap: 3 }}>
                      {f.write.length ? f.write.map((c, j) => <span key={j} className="md-tag-mono solid">{c}</span>) : <span style={{ fontSize: 11, color: "var(--muted-2)" }}>-</span>}
                    </div>
                  </td>
                  <td style={{ fontSize: 12, color: "var(--ink-3)", maxWidth: 360 }}>{f.note}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {tab === "consumers" && (
        <div style={{ marginTop: 14, display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
          {apps.map((a, i) => (
            <div key={i} className="card" style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                <OwnerChip id={a.owner} size={28} showName={false}/>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 13.5, color: "var(--ink)", fontWeight: 500 }}>{a.name}</div>
                  <div style={{ fontSize: 11.5, color: "var(--muted)" }}>{a.read} reads · {a.write} writes</div>
                </div>
              </div>
              {a.status === "ready" && <Pill kind="ok">no changes</Pill>}
              {a.status === "patch" && <Pill kind="warn">auto-patch</Pill>}
              <p style={{ fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5, margin: 0 }}>{a.note}</p>
              <button className="btn sm ghost" style={{ alignSelf: "flex-start" }}>View diff <Icons.Arrow s={11}/></button>
            </div>
          ))}
        </div>
      )}

      {tab === "migration" && (
        <div style={{ marginTop: 14 }}>
          <div className="card" style={{ marginBottom: 12 }}>
            <h3>Auto-generated migration plan</h3>
            <div className="subtitle" style={{ marginBottom: 14 }}>Two reversible steps. Stoda runs each on staging first, replays operator-authored tests, then promotes.</div>

            <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              <MigStep n="1" title="Define contract module" detail="Generate typed client at packages/contracts/deals.ts. No runtime change." status="ready"/>
              <MigStep n="2" title="Replace direct SQL with typed client (QC, OW)" detail="11 call sites across 2 apps. Stoda emits a PR per app." status="ready"/>
              <MigStep n="3" title="Drop legacy_stage column" detail="After Churn Early Warning's reads degrade gracefully. Scheduled Apr 30, 4 days from now." status="scheduled"/>
            </div>
          </div>

          <div className="card" style={{ background: "var(--cream-2)" }}>
            <div style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
              <span style={{ width: 28, height: 28, borderRadius: 7, background: "var(--paper)", border: "1px solid var(--tan-2)", display: "inline-flex", alignItems: "center", justifyContent: "center", color: "var(--green)" }}>
                <Icons.Shield s={14}/>
              </span>
              <div style={{ flex: 1 }}>
                <h4 style={{ fontSize: 14, fontWeight: 500, color: "var(--ink)", marginBottom: 4 }}>Rollback plan</h4>
                <p style={{ fontSize: 12.5, color: "var(--ink-3)", margin: 0 }}>Each step ships behind a flag. If any operator-authored test fails on staging, the step is auto-reverted and the gate holds, no human action required.</p>
              </div>
            </div>
          </div>
        </div>
      )}

      {tab === "tests" && (
        <div className="card flush" style={{ marginTop: 14 }}>
          <table className="t">
            <thead><tr><th>Test</th><th>Kind</th><th>Runs</th><th>Status</th></tr></thead>
            <tbody>
              {tests.map((t, i) => (
                <tr key={i} style={{ cursor: "default" }}>
                  <td>
                    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                      <span className="sig" style={{ fontSize: 11, color: "var(--muted)" }}>{t.id}</span>
                      <span style={{ fontSize: 13, color: "var(--ink)" }}>{t.name}</span>
                    </div>
                  </td>
                  <td><Pill kind={t.kind === "deterministic" ? "neutral" : "info"}>{t.kind}</Pill></td>
                  <td style={{ color: "var(--muted)", fontSize: 12 }}>{t.runs}</td>
                  <td>
                    {t.status === "passing" && <Pill kind="ok">passing</Pill>}
                    {t.status === "pending" && <Pill kind="warn">pending</Pill>}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </Modal>
  );
}

function ProposalStat({ label, value, foot }) {
  return (
    <div>
      <div style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--muted)", fontWeight: 500 }}>{label}</div>
      <div style={{ fontFamily: "var(--serif)", fontSize: 24, color: "var(--ink)", letterSpacing: "-0.02em", lineHeight: 1.1, marginTop: 2 }}>{value}</div>
      <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 2 }}>{foot}</div>
    </div>
  );
}

function MigStep({ n, title, detail, status }) {
  return (
    <div style={{ display: "flex", gap: 12, alignItems: "flex-start", padding: 12, border: "1px solid var(--tan-2)", borderRadius: 9, background: "var(--cream-2)" }}>
      <span style={{ width: 26, height: 26, borderRadius: "50%", background: "var(--ink-2)", color: "#fff", fontFamily: "var(--mono)", fontSize: 11, display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>{n}</span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{ fontSize: 13, color: "var(--ink)", fontWeight: 500 }}>{title}</span>
          {status === "ready" && <Pill kind="ok">ready</Pill>}
          {status === "scheduled" && <Pill kind="info">scheduled</Pill>}
        </div>
        <div style={{ fontSize: 12.5, color: "var(--muted)", marginTop: 3, lineHeight: 1.5 }}>{detail}</div>
      </div>
    </div>
  );
}

// ---------- 3) Add rule modal, Library + Custom tabs ----------
function AddRuleModal({ open, onClose, presetRuleId }) {
  const [mode, setMode] = useStateM("library"); // "library" | "custom"

  return (
    <Modal
      open={open} onClose={onClose}
      width={1080}
      eyebrow="Governance · Add a rule"
      title="What should this rule enforce?"
    >
      <Tabs value={mode} onChange={setMode} items={[
        { value: "library", label: "From library" },
        { value: "custom",  label: "Author custom rule" },
      ]}/>

      {mode === "library" && <RuleLibrary onClose={onClose} presetRuleId={presetRuleId}/>}
      {mode === "custom"  && <RuleAuthor  onClose={onClose}/>}
    </Modal>
  );
}

function RuleLibrary({ onClose, presetRuleId }) {
  // Build library of rules NOT yet active in the org. Pull a few real ones, mark as off, and add ~6 fresh ones.
  const orgRules = DATA.RULES;
  const offRules = orgRules.filter(r => r.status === "off" || r.status === "partial");
  const fresh = [
    { id: "lib-1", category: "security", name: "Block API responses containing SSN-shaped strings",
      summary: "Pattern-matches 9-digit SSN-like strings in any outbound response body. Built-in: 12 tests across 6 vendor schemas.",
      builtInTests: 12, fleetEligible: 14 },
    { id: "lib-2", category: "quality",  name: "LLM responses must validate against pinned JSON schema",
      summary: "Wraps every non-det endpoint call. Drift in the response shape blocks the deploy and opens an alert.",
      builtInTests: 18, fleetEligible: 6 },
    { id: "lib-3", category: "cost",     name: "Per-app daily LLM budget with automatic cool-down",
      summary: "Soft cap (warn) and hard cap (cool-down). Both are operator-tunable per app and team.",
      builtInTests: 9, fleetEligible: 11 },
    { id: "lib-4", category: "data",     name: "Multi-app schemas must declare a backup window",
      summary: "Catches the 'no recent backup' failure mode before it ships. Verified against the BYOC backup ledger.",
      builtInTests: 7, fleetEligible: 4 },
    { id: "lib-5", category: "pipeline", name: "Block deploys on Friday after 14:00 local",
      summary: "Soft block, operator can still deploy with VP approval. Optional per team.",
      builtInTests: 4, fleetEligible: 24 },
    { id: "lib-6", category: "network",  name: "All outbound calls must include an `X-App-Id` header",
      summary: "Restores per-app attribution for shared egress proxies. Stoda auto-injects on app startup if missing.",
      builtInTests: 6, fleetEligible: 22 },
    { id: "lib-7", category: "security",  name: "Mandate per-app API keys, no shared credentials",
      summary: "Detects when a single API key (OpenAI, Stripe, etc.) is referenced by 2+ apps. Per-app keys restore cost attribution and reduce blast radius. Stoda generates new keys, opens PRs to swap them, and validates with the provider before merging.",
      builtInTests: 8, fleetEligible: 4, defaultGrace: "14d" },
  ];

  const library = [
    ...offRules.map(r => ({
      id: r.id, category: r.category, name: r.name, summary: r.summary,
      builtInTests: r.builtInTests, fleetEligible: 24, current: true,
    })),
    ...fresh,
  ];

  const cats = DATA.RULE_CATEGORIES;
  const [cat, setCat] = useStateM("all");
  const [pick, setPick] = useStateM(presetRuleId || library[0]?.id || null);
  const [scope, setScope] = useStateM("all");
  const [severity, setSeverity] = useStateM("hard");
  const [grace, setGrace] = useStateM(presetRuleId === "lib-7" ? "7d" : "0");
  const [confirming, setConfirming] = useStateM(false);
  const [q, setQ] = useStateM("");

  const filtered = library.filter(r => {
    if (cat !== "all" && r.category !== cat) return false;
    if (q && !(r.name + " " + r.summary).toLowerCase().includes(q.toLowerCase())) return false;
    return true;
  });
  const selected = library.find(r => r.id === pick);

  return (
    <div style={{ marginTop: 14, display: "grid", gridTemplateColumns: "260px 1fr", gap: 0, border: "1px solid var(--tan-2)", borderRadius: 12, overflow: "hidden", minHeight: 480 }}>
      {/* sidebar, categories */}
      <div style={{ borderRight: "1px solid var(--tan-2)", background: "var(--cream-2)", padding: 14 }}>
        <div className="eyebrow" style={{ marginBottom: 10 }}>Categories</div>
        <button className={"md-cat" + (cat === "all" ? " on" : "")} onClick={() => setCat("all")}>
          <span>All</span><span className="md-cat-n">{library.length}</span>
        </button>
        {cats.map(c => {
          const Ic = Icons[c.icon] || Icons.Shield;
          const n = library.filter(r => r.category === c.id).length;
          if (n === 0) return null;
          return (
            <button key={c.id} className={"md-cat" + (cat === c.id ? " on" : "")} onClick={() => setCat(c.id)}>
              <Ic s={12}/> <span style={{ flex: 1, textAlign: "left" }}>{c.name}</span>
              <span className="md-cat-n">{n}</span>
            </button>
          );
        })}
      </div>

      {/* middle, list */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 0 }}>
        <div style={{ borderRight: "1px solid var(--tan-2)" }}>
          <div style={{ padding: "12px 14px", borderBottom: "1px solid var(--tan-2)", background: "var(--paper)" }}>
            <div style={{ position: "relative" }}>
              <input className="gov-search" style={{ width: "100%" }} placeholder="Filter rules…" value={q} onChange={(e) => setQ(e.target.value)}/>
              <span style={{ position: "absolute", left: 10, top: 8, color: "var(--muted)" }}><Icons.Search s={12}/></span>
            </div>
          </div>
          <div style={{ maxHeight: 420, overflowY: "auto" }}>
            {filtered.map(r => (
              <button key={r.id} onClick={() => setPick(r.id)}
                className={"md-lib-item" + (pick === r.id ? " on" : "")}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
                  <span style={{ fontSize: 13, color: "var(--ink)", fontWeight: 500, flex: 1 }}>{r.name}</span>
                  {r.current && <Pill kind="info">in fleet</Pill>}
                </div>
                <div style={{ fontSize: 11.5, color: "var(--muted)", lineHeight: 1.4 }}>
                  {(cats.find(c => c.id === r.category) || {}).name} · {r.builtInTests} tests
                </div>
              </button>
            ))}
            {filtered.length === 0 && <div className="empty">No rules match.</div>}
          </div>
        </div>

        {/* right, detail + scope */}
        <div style={{ padding: 18, display: "flex", flexDirection: "column", gap: 14, background: "var(--paper)", overflow: "auto", maxHeight: 480 }}>
          {selected ? (
            <>
              <div>
                <div className="eyebrow" style={{ marginBottom: 4 }}>{(cats.find(c => c.id === selected.category) || {}).name}</div>
                <h3 style={{ fontFamily: "var(--serif)", fontSize: 20, fontWeight: 400, color: "var(--ink)", letterSpacing: "-0.02em" }}>{selected.name}</h3>
                <p style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 6, lineHeight: 1.55 }}>{selected.summary}</p>
              </div>

              <div className="md-banner ok" style={{ padding: "8px 12px", fontSize: 12.5 }}>
                <Icons.Check s={13}/>
                <div>
                  <b>{selected.builtInTests} built-in tests</b> ship with this rule. Maintained by Stoda, applied on every deploy across the apps you scope here.
                </div>
              </div>

              {/* Fleet preview */}
              <div>
                <div className="eyebrow" style={{ marginBottom: 6 }}>Fleet preview</div>
                <div className="card" style={{ padding: "12px 14px", background: "var(--cream-2)" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 13, color: "var(--ink)" }}><b>{selected.fleetEligible}</b> apps eligible · <span style={{ color: "var(--amber)" }}>3</span> would violate at first run</div>
                      <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 2 }}>Stoda will dry-run on staging before turning the gate hard.</div>
                    </div>
                    <button className="btn sm ghost">See list <Icons.Arrow s={11}/></button>
                  </div>
                </div>
              </div>

              {/* Scope */}
              <div>
                <div className="eyebrow" style={{ marginBottom: 6 }}>Scope</div>
                <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                  <ScopeOpt val="all" cur={scope} set={setScope} label="All apps in fleet" sub="24 apps · org-wide gate"/>
                  <ScopeOpt val="team" cur={scope} set={setScope} label="By team" sub="ML · Revenue Ops · Finance · People Ops"/>
                  <ScopeOpt val="apps" cur={scope} set={setScope} label="Specific apps" sub="Hand-pick from fleet"/>
                </div>
              </div>

              {/* Severity */}
              <div>
                <div className="eyebrow" style={{ marginBottom: 6 }}>Enforcement</div>
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
                  <SevOpt val="warn" cur={severity} set={setSeverity} label="Warn"
                    sub="Surface in Signals. Don't block deploys."/>
                  <SevOpt val="hard" cur={severity} set={setSeverity} label="Hard gate"
                    sub="Block deploys until violations clear."/>
                </div>
              </div>

              {/* Grace period */}
              <div>
                <div className="eyebrow" style={{ marginBottom: 6 }}>Grace period for existing operators</div>
                <div style={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 6 }}>
                  {[["0","None"],["3d","3 days"],["7d","7 days"],["14d","14 days"],["30d","30 days"]].map(([v,l]) => (
                    <button key={v} className={"btn sm" + (grace === v ? " primary" : "")} onClick={() => setGrace(v)}>{l}</button>
                  ))}
                </div>
                <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 6, lineHeight: 1.5 }}>
                  Operators with existing violations get this long to migrate before the gate goes hard. New deploys are gated immediately.
                </div>
              </div>
            </>
          ) : (
            <div className="empty">Pick a rule to configure.</div>
          )}
        </div>
      </div>

      <div style={{ gridColumn: "1 / -1", padding: "12px 18px", borderTop: "1px solid var(--tan-2)", background: "var(--cream-3)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <div style={{ fontSize: 12, color: "var(--muted)" }}>
          {selected
            ? <>Adding <b style={{ color: "var(--ink)" }}>{selected.name}</b> · {scope === "all" ? "all 24 apps" : scope === "team" ? "selected teams" : "selected apps"} · {severity === "hard" ? "hard gate" : "warn only"}{grace !== "0" && <> · {grace} grace</>}</>
            : "Pick a rule to continue."}
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn sm" onClick={onClose}>Cancel</button>
          <button className="btn sm" disabled={!selected}>Save as draft</button>
          <button className="btn sm primary" disabled={!selected} onClick={() => setConfirming(true)}>
            <Icons.Check s={12}/> Turn on{severity === "hard" ? "" : " (warn)"}
          </button>
        </div>
      </div>

      {confirming && selected && (
        <div className="md-scrim" style={{ zIndex: 60 }} onClick={() => setConfirming(false)}>
          <div className="md" style={{ width: "min(560px, 94vw)" }} onClick={(e) => e.stopPropagation()}>
            <div className="md-head">
              <div style={{ flex: 1 }}>
                <div className="eyebrow" style={{ marginBottom: 6 }}>Confirm rollout</div>
                <h2 style={{ fontFamily: "var(--serif)", fontSize: 22, letterSpacing: "-0.02em", color: "var(--ink)", fontWeight: 400, lineHeight: 1.2 }}>Turning on <em>{selected.name}</em></h2>
              </div>
            </div>
            <div className="md-body">
              <div style={{ display: "grid", gap: 10, fontSize: 13, color: "var(--ink-3)", lineHeight: 1.55 }}>
                <div style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
                  <Icons.Alert s={14}/>
                  <div><b style={{ color: "var(--ink)" }}>{selected.fleetEligible} operators</b> currently in violation will have <b style={{ color: "var(--ink)" }}>{grace === "0" ? "no grace period" : grace.replace("d", " days")}</b> to migrate before the {severity === "hard" ? "hard gate" : "warning"} takes effect.</div>
                </div>
                <div style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
                  <Icons.Bell s={14}/>
                  <div>They'll be notified by <b style={{ color: "var(--ink)" }}>email and Slack</b> immediately, with a follow-up 24h before the gate hardens.</div>
                </div>
                <div style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
                  <Icons.Check s={14}/>
                  <div>New deploys are gated <b style={{ color: "var(--ink)" }}>starting now</b>. Existing apps keep running through the grace period.</div>
                </div>
              </div>
            </div>
            <div className="md-foot">
              <button className="btn ghost" onClick={() => setConfirming(false)}>Back</button>
              <button className="btn primary" onClick={() => { setConfirming(false); onClose(); }}>Confirm · notify {selected.fleetEligible} operators <Icons.Arrow s={12}/></button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function ScopeOpt({ val, cur, set, label, sub }) {
  const on = cur === val;
  return (
    <button onClick={() => set(val)} className={"md-opt" + (on ? " on" : "")}>
      <span className={"md-radio" + (on ? " on" : "")}/>
      <div style={{ flex: 1, textAlign: "left" }}>
        <div style={{ fontSize: 13, color: "var(--ink)", fontWeight: 500 }}>{label}</div>
        <div style={{ fontSize: 11.5, color: "var(--muted)" }}>{sub}</div>
      </div>
    </button>
  );
}

function SevOpt({ val, cur, set, label, sub }) {
  const on = cur === val;
  return (
    <button onClick={() => set(val)} className={"md-opt block" + (on ? " on" : "")}>
      <div style={{ fontSize: 13, color: "var(--ink)", fontWeight: 500 }}>{label}</div>
      <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 2 }}>{sub}</div>
    </button>
  );
}

function RuleAuthor({ onClose }) {
  const [name, setName] = useStateM("Block deploys touching pricing.* without finance approval");
  const [desc, setDesc] = useStateM(
    "When a deploy diff includes any change under apps/*/pricing/** or schemas/pricing.*, the pull request must carry an approval from a member of the Finance team. Auto-approve if the diff only touches comments or whitespace."
  );
  const [generated, setGenerated] = useStateM(true); // default: show as already generated

  const tests = [
    { name: "PR with pricing.ts change + finance approval → pass",         status: "passing" },
    { name: "PR with pricing.ts change + no finance approval → blocked",   status: "passing" },
    { name: "PR with pricing/comment-only diff → auto-approved",           status: "passing" },
    { name: "PR with non-pricing change → rule does not trigger",          status: "passing" },
    { name: "Approver outside finance team → blocked, with reason logged", status: "passing" },
  ];

  return (
    <div style={{ marginTop: 14, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
      {/* Left, describe */}
      <div className="card" style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: 6 }}>1. Describe in plain language</div>
          <input className="md-input" value={name} onChange={(e) => setName(e.target.value)} placeholder="Rule name"/>
          <textarea className="md-input" rows={6} value={desc} onChange={(e) => setDesc(e.target.value)}
            placeholder="Describe what this rule should enforce, when it should fire, and any auto-approve conditions."/>
        </div>
        <button className="btn sm" onClick={() => setGenerated(true)} style={{ alignSelf: "flex-start" }}>
          <Icons.Spark s={12}/> Generate tests with Stoda
        </button>
        <div className="md-banner info" style={{ padding: "10px 12px", fontSize: 12 }}>
          <Icons.Info s={13}/>
          <div>Stoda compiles your description into deterministic tests that run on every deploy. Custom rules require a 2-week dry-run before they hard-block.</div>
        </div>
      </div>

      {/* Right, generated */}
      <div className="card" style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        <div className="eyebrow">2. Stoda's generated tests</div>
        {generated ? (
          <>
            <div className="md-codeblock">
              <div className="md-code-head">
                <span className="sig">rules/pricing-finance-approval.ts</span>
                <span style={{ color: "var(--muted)", fontSize: 11 }}>auto-generated · review before save</span>
              </div>
              <pre className="md-code">{`when deploy.diff.touches(["apps/*/pricing/**", "schemas/pricing.*"])
  require approval from team:"finance"

allow when diff.kind == "comment-only"
allow when diff.kind == "whitespace-only"

on_violation:
  block deploy with reason "pricing change requires finance approval"
  notify channel:#deploys
  log to audit_trail`}</pre>
            </div>

            <div>
              <div className="eyebrow" style={{ marginBottom: 6 }}>Tests Stoda will run</div>
              <ul style={{ listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 6 }}>
                {tests.map((t, i) => (
                  <li key={i} style={{ display: "flex", alignItems: "center", gap: 10, fontSize: 12.5, color: "var(--ink-3)" }}>
                    <span style={{ width: 16, height: 16, borderRadius: "50%", background: "var(--green-bg)", color: "var(--green)", display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
                      <Icons.Check s={9}/>
                    </span>
                    <span style={{ flex: 1 }}>{t.name}</span>
                  </li>
                ))}
              </ul>
            </div>
          </>
        ) : (
          <div className="empty">Click "Generate tests with Stoda", usually takes 4–8 seconds.</div>
        )}
      </div>

      {/* Footer */}
      <div style={{ gridColumn: "1 / -1", padding: "12px 0", borderTop: "1px solid var(--tan-2)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <div style={{ fontSize: 12, color: "var(--muted)" }}>
          {generated ? "5 tests · will dry-run for 14 days before hard-blocking." : "Describe the rule, then generate."}
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn sm" onClick={onClose}>Cancel</button>
          <button className="btn sm">Save as draft</button>
          <button className="btn sm primary" disabled={!generated}>
            <Icons.Check s={12}/> Turn on (dry-run)
          </button>
        </div>
      </div>
    </div>
  );
}

// ---------- 4) Templating kit modal, for s3 PTO Bot signal ----------
function TemplatingKitModal({ open, onClose }) {
  const [tab, setTab] = useStateM("blueprint");
  const ptoBot = DATA.APPS.find(a => a.name === "PTO Bot");

  return (
    <Modal
      open={open} onClose={onClose}
      width={1000}
      eyebrow="Templating kit · From PTO Bot"
      title={<>Template the <em>approval flow.</em></>}
      headerRight={
        <>
          <button className="btn sm"><Icons.ExternalLink s={12}/> Open PTO Bot</button>
          <button className="btn sm primary"><Icons.Plus s={12}/> Start new app from this</button>
        </>
      }>

      <div className="md-banner ok" style={{ marginBottom: 16 }}>
        <Icons.Spark s={14}/>
        <div>
          <b>PTO Bot is your most-used internal app.</b> 182 WAU, 454 monthly uniques, 0 incidents in 90 days. The approval flow inside it is the right shape for the next two HR apps People Ops wants to ship.
        </div>
      </div>

      <Tabs value={tab} onChange={setTab} items={[
        { value: "blueprint", label: "What gets templated" },
        { value: "next",      label: "Next apps it unlocks" },
        { value: "tests",     label: "Tests come along" },
      ]}/>

      {tab === "blueprint" && (
        <div className="md-section" style={{ marginTop: 14 }}>
          <div className="md-section-head">
            <h3>Blueprint</h3>
            <span className="subtitle">Stoda extracts the parts that generalize and parameterizes the rest.</span>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
            {[
              { name: "Slack-first request",       generalizes: true,  detail: "User → bot DM → form modal. Approver picks from manager chain.",
                params: ["form fields", "approver chain"] },
              { name: "Approver routing rules",    generalizes: true,  detail: "Auto-route by team, fallback to skip-level after 24h. Out-of-office aware.",
                params: ["chain depth", "OOO fallback"] },
              { name: "Calendar write-through",    generalizes: false, detail: "Specific to PTO. Skip for next two apps.", params: [] },
              { name: "BambooHR sync",             generalizes: false, detail: "Specific to PTO. Replaceable per app.", params: ["target system"] },
              { name: "Audit trail + receipts",    generalizes: true,  detail: "Every approval gets a signed event in the audit log. Inherited automatically.",
                params: [] },
              { name: "Operator-test harness",     generalizes: true,  detail: "9 deterministic tests that codify the happy path. Forked for the new flow.",
                params: [] },
            ].map((b, i) => (
              <div key={i} className="card" style={{ padding: 14, background: b.generalizes ? "var(--paper)" : "var(--cream-2)" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
                  {b.generalizes
                    ? <Pill kind="ok">portable</Pill>
                    : <Pill kind="neutral">app-specific</Pill>}
                  <h4 style={{ fontSize: 13, color: "var(--ink)" }}>{b.name}</h4>
                </div>
                <p style={{ fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5, margin: "0 0 8px" }}>{b.detail}</p>
                {b.params.length > 0 && (
                  <div style={{ display: "flex", gap: 5, flexWrap: "wrap" }}>
                    {b.params.map((p, j) => (
                      <span key={j} style={{ fontFamily: "var(--mono)", fontSize: 11, padding: "2px 7px", background: "var(--cream-3)", borderRadius: 4, color: "var(--ink-3)", border: "1px solid var(--tan-2)" }}>{p}</span>
                    ))}
                  </div>
                )}
              </div>
            ))}
          </div>
        </div>
      )}

      {tab === "next" && (
        <div className="md-section" style={{ marginTop: 14 }}>
          <div className="md-section-head">
            <h3>Two HR apps People Ops wants next</h3>
            <span className="subtitle">Both reuse the approval flow with different forms and target systems.</span>
          </div>
          <div style={{ display: "grid", gap: 12 }}>
            {[
              { name: "Expense reimbursement bot", who: "180 staff", est: "~3 days build · vs ~3 weeks from scratch",
                detail: "Same Slack form pattern, swap target system to NetSuite. Auto-route to manager + finance for $500+." },
              { name: "Equipment request bot",     who: "180 staff", est: "~2 days build",
                detail: "Slack form → manager approval → IT ticket. Reuses approval routing wholesale; only the form fields differ." },
            ].map((a, i) => (
              <div key={i} className="card" style={{ padding: 14, display: "flex", gap: 14, alignItems: "flex-start" }}>
                <span style={{ width: 36, height: 36, borderRadius: 9, background: "color-mix(in srgb, var(--orange) 12%, var(--paper))", border: "1px solid var(--tan-2)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--orange-dark)", flexShrink: 0 }}>
                  <Icons.Spark s={16}/>
                </span>
                <div style={{ flex: 1 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
                    <h4 style={{ fontSize: 14, color: "var(--ink)" }}>{a.name}</h4>
                    <Pill kind="info">{a.who}</Pill>
                  </div>
                  <p style={{ fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5, margin: "0 0 8px" }}>{a.detail}</p>
                  <div style={{ fontSize: 11.5, color: "var(--muted)" }}>{a.est}</div>
                </div>
                <button className="btn sm primary">Scaffold from PTO Bot <Icons.Arrow s={11}/></button>
              </div>
            ))}
          </div>
        </div>
      )}

      {tab === "tests" && (
        <div className="md-section" style={{ marginTop: 14 }}>
          <div className="md-section-head">
            <h3>Tests inherited from PTO Bot</h3>
            <span className="subtitle">7 of {ptoBot ? ptoBot.tests.total : 9} tests transfer as-is. The other 2 are PTO-specific.</span>
          </div>
          <div className="card flush">
            <table className="t">
              <thead><tr><th>Test</th><th>Kind</th><th>Transfers</th></tr></thead>
              <tbody>
                {[
                  { name: "Slack form schema validates",                          kind: "deterministic", transfers: true },
                  { name: "Manager-chain lookup falls back to skip-level after 24h", kind: "deterministic", transfers: true },
                  { name: "Out-of-office approver auto-skipped",                     kind: "deterministic", transfers: true },
                  { name: "Audit event written with signed receipt",                 kind: "deterministic", transfers: true },
                  { name: "Approval idempotent across retries",                      kind: "deterministic", transfers: true },
                  { name: "Approver outside chain → request blocked, reason logged", kind: "deterministic", transfers: true },
                  { name: "Approval webhook re-fires within 5s of confirmation",     kind: "deterministic", transfers: true },
                  { name: "BambooHR balance updates within 60s",                     kind: "deterministic", transfers: false },
                  { name: "Calendar event created on calendar of approver + requester", kind: "deterministic", transfers: false },
                ].map((t, i) => (
                  <tr key={i}>
                    <td>{t.name}</td>
                    <td><span style={{ fontSize: 12, color: "var(--muted)" }}>{t.kind}</span></td>
                    <td>{t.transfers ? <Pill kind="ok">inherits</Pill> : <Pill kind="neutral">PTO-only</Pill>}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}

      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "14px 0 0", borderTop: "1px solid var(--tan-2)", marginTop: 14 }}>
        <div style={{ fontSize: 12, color: "var(--muted)" }}>
          Templating kit available to operators in <b>People Ops</b> and <b>Eng Productivity</b>.
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn sm" onClick={onClose}>Close</button>
          <button className="btn sm primary"><Icons.Plus s={12}/> Start scaffolding</button>
        </div>
      </div>
    </Modal>
  );
}

Object.assign(window, { Modal, ServiceDetailModal, ContractProposalModal, AddRuleModal, TemplatingKitModal, GrantExceptionModal, OutboundDecisionModal, BudgetIncreaseModal, RuleInspectModal });

// ---------- 5) Grant exception modal, for s1 dependency-test failure ----------
function GrantExceptionModal({ open, onClose }) {
  const [duration, setDuration] = useStateM("7d");
  const [reason, setReason] = useStateM("Operator (Maya) is mid-fix; tests failing on a flaky upstream. Grant a week to land the patch without auto-disable.");
  return (
    <Modal open={open} onClose={onClose} width={760}
      eyebrow="Decision · Dependency exception"
      title={<>Grant Quote Copilot <em>3 more days</em>?</>}>
      <div className="md-body">
        <div className="card" style={{ padding: 14, marginBottom: 14, background: "var(--cream-3)" }}>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.55 }}>
            Stoda will pause the auto-disable countdown for the duration you grant. Tests must still pass before deploys can ship, the exception only stops the disable timer.
          </div>
        </div>
        <div style={{ display: "grid", gap: 12 }}>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Duration</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 6 }}>
              {[["3d","3 days"],["7d","7 days"],["14d","14 days"],["custom","Custom"]].map(([v,l]) => (
                <button key={v} className={"btn sm" + (duration === v ? " primary" : "")} onClick={() => setDuration(v)}>{l}</button>
              ))}
            </div>
          </div>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Reason (logged to audit trail)</div>
            <textarea value={reason} onChange={(e) => setReason(e.target.value)} rows={3}
              style={{ width: "100%", border: "1px solid var(--tan-2)", borderRadius: 8, padding: 10, fontFamily: "inherit", fontSize: 13, color: "var(--ink)", background: "var(--paper)" }}/>
          </div>
        </div>
      </div>
      <div className="md-foot">
        <button className="btn ghost" onClick={onClose}>Cancel</button>
        <button className="btn primary" onClick={onClose}>Grant exception <Icons.Arrow s={12}/></button>
      </div>
    </Modal>
  );
}

// ---------- 6) Outbound decision modal, for s2 Salesforce allowlist ----------
function OutboundDecisionModal({ open, onClose, mode = "allow" }) {
  const [scope, setScope] = useStateM("app");
  const [ttl, setTtl] = useStateM("permanent");
  if (mode === "block") {
    return (
      <Modal open={open} onClose={onClose} width={680}
        eyebrow="Decision · Block outbound"
        title={<>Block <em>api.salesforce.com</em>?</>}>
        <div className="md-body">
          <div className="card" style={{ padding: 14, marginBottom: 14, background: "var(--amber-bg)", borderColor: "var(--amber-border)" }}>
            <div style={{ fontSize: 12.5, color: "var(--ink)", lineHeight: 1.55 }}>
              <b>Quote Copilot</b> made <span className="sig">~412 calls</span> to this domain in the last 24h. Blocking will cause those code paths to fail until the operator removes the call or you reverse this.
            </div>
          </div>
          <div style={{ display: "grid", gap: 12 }}>
            <div>
              <div className="eyebrow" style={{ marginBottom: 6 }}>Scope</div>
              <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6 }}>
                {[["app","This app only"],["fleet","Fleet-wide"],["team","Team only"]].map(([v,l]) => (
                  <button key={v} className={"btn sm" + (scope === v ? " primary" : "")} onClick={() => setScope(v)}>{l}</button>
                ))}
              </div>
            </div>
            <div>
              <div className="eyebrow" style={{ marginBottom: 6 }}>Notify</div>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)" }}>
                Stoda will page the operator and post the block reason to the app's deploy log.
              </div>
            </div>
          </div>
        </div>
        <div className="md-foot">
          <button className="btn ghost" onClick={onClose}>Cancel</button>
          <button className="btn primary" onClick={onClose} style={{ background: "var(--ink)" }}>Block now <Icons.Arrow s={12}/></button>
        </div>
      </Modal>
    );
  }
  return (
    <Modal open={open} onClose={onClose} width={760}
      eyebrow="Decision · Outbound allowlist"
      title={<>Allow <em>api.salesforce.com</em>?</>}>
      <div className="md-body">
        <div className="card" style={{ padding: 14, marginBottom: 14, background: "var(--cream-3)" }}>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.55 }}>
            First-seen outbound from <b>Quote Copilot</b> to <span className="sig">api.salesforce.com</span>. Currently passing through in dry-run. Choose how to record this decision.
          </div>
        </div>
        <div style={{ display: "grid", gap: 12 }}>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Scope</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6 }}>
              {[["app","This app only"],["fleet","Fleet-wide"],["team","Team only"]].map(([v,l]) => (
                <button key={v} className={"btn sm" + (scope === v ? " primary" : "")} onClick={() => setScope(v)}>{l}</button>
              ))}
            </div>
          </div>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Duration</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 6 }}>
              {[["permanent","Permanent"],["30d","30 days"],["7d","7 days"]].map(([v,l]) => (
                <button key={v} className={"btn sm" + (ttl === v ? " primary" : "")} onClick={() => setTtl(v)}>{l}</button>
              ))}
            </div>
          </div>
        </div>
      </div>
      <div className="md-foot">
        <button className="btn ghost" onClick={onClose}>Block</button>
        <button className="btn primary" onClick={onClose}>Allow <Icons.Arrow s={12}/></button>
      </div>
    </Modal>
  );
}

// ---------- 7) Budget increase modal, for s3 Campaign Studio ----------
function BudgetIncreaseModal({ open, onClose }) {
  const [kind, setKind] = useStateM("permanent");
  const [amount, setAmount] = useStateM(4000);
  return (
    <Modal open={open} onClose={onClose} width={760}
      eyebrow="Decision · Budget increase"
      title={<>Raise <em>Marketing</em> budget?</>}>
      <div className="md-body">
        <div className="card" style={{ padding: 14, marginBottom: 14, background: "var(--cream-3)" }}>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.55 }}>
            Current: <b>$3,200/mo</b> · Spend trending to <b>$3,400</b> by May 17. Without an increase, Stoda will pause new deploys for Campaign Studio on May 17.
          </div>
        </div>
        <div style={{ display: "grid", gap: 12 }}>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Type</div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
              <button className={"btn sm" + (kind === "permanent" ? " primary" : "")} onClick={() => setKind("permanent")}>Permanent</button>
              <button className={"btn sm" + (kind === "temporary" ? " primary" : "")} onClick={() => setKind("temporary")}>Temporary (this month)</button>
            </div>
          </div>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>New monthly budget</div>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <input type="range" min={3200} max={6000} step={100} value={amount} onChange={(e) => setAmount(+e.target.value)} style={{ flex: 1 }}/>
              <div style={{ fontFamily: "var(--serif)", fontSize: 22, color: "var(--ink)", letterSpacing: "-0.02em", minWidth: 90, textAlign: "right" }}>{currency(amount)}</div>
            </div>
            <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 6 }}>Safe through {amount >= 4000 ? "Jun 14" : "May 28"} at current run rate.</div>
          </div>
        </div>
      </div>
      <div className="md-foot">
        <button className="btn ghost" onClick={onClose}>Accept the pause</button>
        <button className="btn primary" onClick={onClose}>Raise to {currency(amount)} <Icons.Arrow s={12}/></button>
      </div>
    </Modal>
  );
}

// ---------- 8) Rule inspect modal, opened from Governance "Inspect" buttons ----------
function RuleInspectModal({ open, onClose, ruleId }) {
  const rule = DATA.RULES.find(r => r.id === ruleId);
  const [enabled, setEnabled] = useStateM(true);
  const [severity, setSeverity] = useStateM("hard");
  const [scope, setScope] = useStateM("fleet");
  useEffectM(() => {
    if (rule) {
      setEnabled(rule.status !== "off");
      setSeverity(rule.status === "partial" ? "warn" : "hard");
    }
  }, [ruleId]);
  if (!rule) return null;

  const allApps = DATA.APPS;
  const apps = allApps.slice(0, Math.min(rule.coverage || 8, 12)).map((a, i) => {
    const seed = (a.id.length + i + rule.id.length) % 11;
    let state;
    if (i < rule.violations) {
      state = seed === 0 ? "blocked" : seed === 1 ? "exception" : "warned";
    } else {
      state = "passing";
    }
    return { ...a, state };
  });
  const stateCfg = {
    passing:   { label: "passing",                  kind: "ok",   sub: "" },
    warned:    { label: "live · warned",            kind: "warn", sub: "operator notified" },
    exception: { label: "live · exception",         kind: "warn", sub: "expires in 7d" },
    blocked:   { label: "blocked in staging",       kind: "risk", sub: "deploy held" },
  };
  const violations = apps.filter(a => a.state !== "passing");

  return (
    <Modal open={open} onClose={onClose} width={920}
      eyebrow={`Governance · ${DATA.RULE_CATEGORIES.find(c => c.id === rule.category)?.name || ""}`}
      title={rule.name}>
      <div className="md-body">
        <div style={{ fontSize: 13, color: "var(--ink-3)", lineHeight: 1.55, marginBottom: 16 }}>
          {rule.summary}
        </div>

        <div className="md-section-head" style={{ marginBottom: 8 }}>
          <h3 style={{ margin: 0, fontFamily: "var(--serif)", fontSize: 15, fontWeight: 500 }}>Configure</h3>
        </div>
        <div className="card" style={{ padding: 14, marginBottom: 16, display: "grid", gap: 14 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 12.5, color: "var(--ink)" }}>Rule active</div>
              <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 2 }}>Runs on every deploy across the fleet.</div>
            </div>
            <button className={"btn sm" + (enabled ? " primary" : "")} onClick={() => setEnabled(!enabled)} style={{ minWidth: 64 }}>
              {enabled ? "On" : "Off"}
            </button>
          </div>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Severity</div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
              <button className={"btn sm" + (severity === "hard" ? " primary" : "")} onClick={() => setSeverity("hard")}>Hard block</button>
              <button className={"btn sm" + (severity === "warn" ? " primary" : "")} onClick={() => setSeverity("warn")}>Warn only</button>
            </div>
            <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 6 }}>
              {severity === "hard" ? "Deploys held in staging until resolved or overridden." : "Deploys proceed; operator and CTO are notified."}
            </div>
          </div>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Applies to</div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
              <button className={"btn sm" + (scope === "fleet" ? " primary" : "")} onClick={() => setScope("fleet")}>Whole fleet</button>
              <button className={"btn sm" + (scope === "tagged" ? " primary" : "")} onClick={() => setScope("tagged")}>Tagged apps only</button>
            </div>
          </div>
        </div>

        {violations.length > 0 && (
          <>
            <div className="md-section-head" style={{ marginBottom: 8 }}>
              <h3 style={{ margin: 0, fontFamily: "var(--serif)", fontSize: 15, fontWeight: 500 }}>
                Violations <span style={{ color: "var(--muted)", fontWeight: 400 }}>· {violations.length}</span>
              </h3>
            </div>
            <div className="card flush" style={{ marginBottom: 16 }}>
              <table className="t">
                <thead><tr><th>App</th><th>Owner</th><th>Status</th><th></th></tr></thead>
                <tbody>
                  {violations.map(a => {
                    const cfg = stateCfg[a.state];
                    return (
                      <tr key={a.id}>
                        <td style={{ fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--ink)" }}>{a.name}</td>
                        <td><OwnerChip code={a.owner}/></td>
                        <td>
                          <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
                            <Pill kind={cfg.kind}>{cfg.label}</Pill>
                            {cfg.sub && <span style={{ fontSize: 11, color: "var(--muted)" }}>{cfg.sub}</span>}
                          </div>
                        </td>
                        <td style={{ textAlign: "right" }}>
                          <button className="btn sm ghost">Open <Icons.Arrow s={11}/></button>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          </>
        )}

        <div className="md-section-head" style={{ marginBottom: 8 }}>
          <h3 style={{ margin: 0, fontFamily: "var(--serif)", fontSize: 15, fontWeight: 500 }}>
            Apps under this rule <span style={{ color: "var(--muted)", fontWeight: 400 }}>· {apps.length}</span>
          </h3>
        </div>
        <div className="card flush">
          <table className="t">
            <thead><tr><th>App</th><th>Owner</th><th>Status</th></tr></thead>
            <tbody>
              {apps.map(a => {
                const cfg = stateCfg[a.state];
                return (
                  <tr key={a.id}>
                    <td style={{ fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--ink)" }}>{a.name}</td>
                    <td><OwnerChip code={a.owner}/></td>
                    <td><Pill kind={cfg.kind}>{cfg.label}</Pill></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
      <div className="md-foot">
        <button className="btn ghost" onClick={onClose}>Cancel</button>
        <button className="btn primary" onClick={onClose}>Save changes <Icons.Arrow s={12}/></button>
      </div>
    </Modal>
  );
}
