// Administrator console II — settings, health, watchers (WP-U09),
// and the Auditor's access history.
//
// THREE RULES, and the first is the one this file exists to get right.
//
//   1. THERE IS NO EDIT AFFORDANCE ON AN OWNER DECISION. Not a disabled input,
//      not a greyed-out button, not a field that fails on save. The boundary is
//      taught by the screen: an administrator looking at an owner decision must
//      see something that was never theirs to change, not something they are
//      currently forbidden from changing. A disabled editor says "you could,
//      but not now"; read-only text says "this belongs to somebody else".
//
//   2. NEVER-RAN IS ITS OWN STATE. A tile that renders green because a check has
//      never run is absence of evidence rendered as evidence, and it is how a
//      system reassures its operator into an incident.
//
//   3. THE NUDGE NOTIFIES AND DESTROYS NOTHING. It writes an audit row saying
//      Legal was told. It touches no retention state at all, and it cannot —
//      the administrator holds no write on that table.

const { useState, useMemo, useRef } = React;

// ── Settings ─────────────────────────────────────────────────────────────
function OperationalRow({ s, onSaved, onError }) {
  const [value, setValue] = useState(s.value);
  const [busy, setBusy] = useState(false);
  const dirty = value !== s.value;
  const isDuration = ['session_idle_length', 'session_length'].includes(s.key);
  const valid = !isDuration || /^\s*[1-9][0-9]{0,5}\s*[smhd]\s*$/.test(value);
  // A value too long to sit beside its description gets the row to itself
  // rather than a box it does not fit in. `notification_immediate_list` is a
  // comma list needing 569px; it was being edited through 108px.
  const long = value.length > 44;

  return (
    <div className={`waiting-row setting-row${long ? ' waiting-row--stacked' : ''}`}>
      <div className="min-w-0">
        <div className="text-[13px] font-mono setting-key" style={{ color: 'var(--ink)' }}>{s.key}</div>
        <div className="caption mt-0.5">{s.purpose}</div>
      </div>
      <div className={`flex items-center gap-2${long ? ' w-full mt-2' : ' shrink-0'}`}>
        {/* THE BOX FOLLOWS ITS VALUE. This was a hard 110px for all eleven
            settings, and two of them do not fit: `daily_start_of_business`
            needs 195px, and the socialisation list needs 569px. Neither
            clipped with an ellipsis or carried a title, so an administrator
            was editing — and saving — a value they could not read.

            Clamped at both ends: a short number keeps a short box, and a long
            list stops before it takes the row. `title` carries the whole
            value however narrow the window, and `aria-label` gives the
            control the name the key beside it was only ever showing. */}
        {/* A LONG VALUE GETS A BOX THAT WRAPS, not a longer line.
            Giving the stacked row the full width fixed this at 1440 and not at
            768, where `notification_immediate_list` needs 569px and the row
            offers 563 — six pixels short, and a different six at every other
            width. A single line can never promise to show an arbitrarily long
            value; a textarea can, at any width, which is the only version of
            this that is actually true.

            Newlines are stripped on the way in: a setting is one value, and a
            textarea is being used for its wrapping, not to make it multi-line. */}
        {long ? (
          <textarea
            className="font-mono" rows={Math.min(4, Math.ceil(value.length / 60) + 1)}
            style={{ width: '100%', padding: '4px 8px', resize: 'vertical' }}
            value={value}
            onChange={(e) => setValue(e.target.value.replace(/[\r\n]+/g, ''))}
            aria-label={s.key} title={value}
            aria-invalid={!valid}
            data-testid={`setting-${s.key}`}
          />
        ) : (
          <input
            className="font-mono"
            style={{ width: `clamp(12ch, ${value.length + 2}ch, 46ch)`, padding: '4px 8px' }}
            value={value} onChange={(e) => setValue(e.target.value)}
            aria-label={s.key} title={value}
            aria-invalid={!valid}
            data-testid={`setting-${s.key}`}
          />
        )}
        <ActButton
          className="btn btn-sm" disabled={!dirty || busy || !valid}
          onClick={async () => {
            setBusy(true); onError(null);
            const r = await API.setSetting({ key: s.key, value });
            setBusy(false);
            if (!r.ok) { onError(r.reason); setValue(s.value); return; }
            const saved = r.body?.rows?.[0];
            onSaved({ key: saved?.key || s.key, value: saved?.value ?? value });
          }}
        >{busy ? 'saving…' : 'save'}</ActButton>
      </div>
      {!valid && (
        <div className="caption mt-1" role="alert" style={{ color: 'var(--danger)' }}>
          Use a positive duration ending in s, m, h, or d — for example, 30m.
        </div>
      )}
    </div>
  );
}

// An owner decision, rendered as what it is: somebody else's settled choice,
// with the reasoning attached. NO INPUT ELEMENT APPEARS HERE AT ALL.
function OwnerDecisionRow({ s }) {
  return (
    <div className="waiting-row setting-row" data-testid={`owner-decision-${s.key}`}>
      <div className="min-w-0">
        <div className="text-[13px] font-mono setting-key" style={{ color: 'var(--ink)' }}>
          {s.key}
          <span className="ml-3" style={{ color: 'var(--accent)' }}>
            {s.value === '' ? '(deliberately unset)' : s.value}
          </span>
        </div>
        {/* The reasoning, not just the value. A decision recorded without its
            why is a value somebody will "correct" later. */}
        <div className="caption mt-1" style={{ lineHeight: 1.6 }}>{s.rationale}</div>
      </div>
      <div className="flex items-center gap-2 shrink-0 self-start">
        {s.decided
          ? <span className="chip chip-ok" title={`decided by ${s.decided_by}`}>
              decided · {s.decided_by}
            </span>
          // Undecided rows are FLAGGED, not hidden. An open question the system
          // has answered provisionally is exactly the thing somebody needs to
          // see; hiding it makes the provisional answer look settled.
          : <span className="chip chip-pending">undecided</span>}
      </div>
    </div>
  );
}

function SettingsPane({ me }) {
  const pane = usePane(() => API.settings());
  const [error, setError] = useState(null);
  const [receipt, setReceipt] = useState(null);

  if (pane.status === 'loading') return <Loading />;
  if (pane.status === 'failed') return <LoadFailed reason={pane.reason} />;

  const operational = pane.rows.filter((s) => s.kind === 'operational');
  const decisions   = pane.rows.filter((s) => s.kind === 'owner_decision');
  const undecided   = decisions.filter((s) => !s.decided);

  return (
    <div>
      {error && (
        <div className="panel p-3 mb-4" style={{ borderColor: 'var(--danger)' }}>
          <div className="tag" style={{ color: 'var(--danger)' }}>refused</div>
          <div className="text-[12.5px] mt-1.5" style={{ color: 'var(--mute)' }}>{error}</div>
        </div>
      )}
      {receipt && (
        <div className="panel p-4 mb-4" role="status" aria-live="polite"
             data-testid="setting-receipt" style={{ borderColor: 'var(--ok)' }}>
          <div className="tag" style={{ color: 'var(--ok)' }}>recorded</div>
          <div className="font-semibold mt-1">Operational setting updated</div>
          <div className="caption mt-1">
            <span className="font-mono">{receipt.key}</span>
            {' is now '}
            <span className="font-mono">{receipt.value}</span>.
          </div>
          <button className="btn btn-sm mt-3"
                  onClick={() => { window.location.hash = '#/health'; }}>
            Review system health
          </button>
        </div>
      )}

      <PaneHead
        title="Operational settings"
        sub="How the machine is tuned. Changing any of these cannot change a contract outcome."
      />
      <div className="panel">
        {operational.map((s) => (
          <OperationalRow key={s.key} s={s}
            onSaved={(saved) => {
              setError(null); setReceipt(saved); pane.reload();
            }}
            onError={(reason) => { setReceipt(null); setError(reason); }} />
        ))}
      </div>

      <div className="mt-8">
        <PanelHead
          title="Owner decisions"
          sub="What the business has chosen. These are Legal admin's, and you are reading them."
          right={undecided.length > 0
            ? <span className="chip chip-pending">{undecided.length} undecided</span>
            : null}
        />
        {/*
          NO EDIT AFFORDANCE. Not a disabled field — nothing that looks like an
          editor at all. A disabled input says "you could, but not now"; text
          says "this was never yours". The boundary is taught by the screen, and
          the server would refuse anyway, which is exactly why the screen must
          not imply otherwise.
        */}
        <div className="panel">
          {decisions.map((s) => <OwnerDecisionRow key={s.key} s={s} />)}
        </div>
        <div className="caption mt-2">
          {me.role === 'administrator'
            ? 'These belong to Legal admin. You see the value, who decided it and why; changing one is their act.'
            : 'Editing these is done in the governance pane of the library workspace.'}
        </div>
      </div>
    </div>
  );
}

// ── System health ────────────────────────────────────────────────────────
// WHICH TILE HAS WORKING BEHIND IT, and which reading answers it.
//
// FIVE OF THE SIX. `retention due` is deliberately absent: it is a count of what
// is due and the list is already further down this page, so opening a panel for
// it would be a second copy of a list six inches below. A tile not in this table
// draws no affordance at all.
//
// `checkName` is how the check HISTORY is narrowed for a tile. It is the value
// `cw.integrity_check.check_name` actually stores, so the panel and the database
// cannot disagree about which rows belong to which verdict — the vocabulary is
// the schema's, copied nowhere.
const EVIDENCE_FOR = {
  'audit chain':      { checkName: 'chain' },
  'anchor':           { checkName: 'anchor' },
  'checkpoints':      { checkName: null },
  'signed documents': { checkName: 'document_hash' },
  'rebuild spot-check': { checkName: 'rebuild_spot_check' },
  'notification digest': { checkName: null },
};

// A figure with no reading behind it yet. `null` and `undefined` both mean "the
// read has not answered", which is not nought — the rule every figure in this
// application keeps.
function Fig({ label, n, tone }) {
  return <StatBox label={label} n={n === null || n === undefined ? null : Number(n)}
                  nStyle={tone ? { color: tone } : undefined} />;
}

function HealthEvidence({ tile, chain, checks, points, docs, rebuild, notifs, onClose }) {
  const spec = EVIDENCE_FOR[tile];
  if (!spec) return null;

  const c = (chain.status === 'loaded' && chain.rows[0]) || {};
  const d = (docs.status === 'loaded' && docs.rows[0]) || {};
  const r = (rebuild.status === 'loaded' && rebuild.rows[0]) || {};
  const n = (notifs && notifs.status === 'loaded' && notifs.rows[0]) || {};

  // THE HISTORY, NARROWED BY THE SCHEMA'S OWN WORD FOR THIS CHECK. Counted off
  // everything the read returned, never off a narrowed copy of it.
  const history = checks.status === 'loaded' && spec.checkName
    ? checks.rows.filter((x) => x.check_name === spec.checkName)
    : [];

  const head = (
    <PanelHead
      title={`${tile} — the working`}
      sub="The readings this verdict was computed from. Nothing here is new information; it is what the summary above already counted."
      right={<button type="button" className="chip chip-focus"
                     data-testid="health-evidence-close" onClick={onClose}>
               close <span aria-hidden="true">×</span>
             </button>} />
  );

  const failed = [chain, checks, points, docs, rebuild, notifs].find((p) => p && p.status === 'failed');
  if (failed) return <div className="panel p-4">{head}<LoadFailed reason={failed.reason} /></div>;

  return (
    <div className="panel p-4">
      {head}

      {tile === 'checkpoints' ? (
        <>
          <div className="grid gap-3 mt-3"
               style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))' }}>
            <Fig label="checkpoints taken" n={c.checkpoints_taken} />
            <Fig label="acts on the chain" n={c.chain_height} />
          </div>
          <p className="caption mt-2">
            <strong>Both figures are the database&rsquo;s own count</strong>, not a
            length of the list below — which holds the most recent 500.
          </p>
          {points.status === 'loaded' && points.rows.length === 0 ? (
            <Empty kicker="checkpoints" line="No checkpoint has ever been taken."
                   sub="Never-ran is not a failure and not a pass. Until one is taken there is
                        nothing for the anchor check to compare the log against." />
          ) : (
            <div className="mt-3">
              <table className="ledger">
                <thead>
                  <tr><th>Taken</th><th>Height</th><th>Last act</th>
                      <th>Next due</th><th style={{ textAlign: 'right' }}>Standing</th></tr>
                </thead>
                <tbody>
                  {(points.rows ?? []).map((p) => (
                    <tr key={p.checkpoint_id}>
                      <td className="mono">{String(p.taken_at ?? '').slice(0, 16)}</td>
                      <td className="mono">{p.height}</td>
                      <td className="mono">{p.last_seq}</td>
                      <td className="mono">{String(p.next_due_at ?? '').slice(0, 16)}</td>
                      <td style={{ textAlign: 'right' }}>
                        {p.overdue
                          ? <span className="chip chip-pending">overdue</span>
                          : <span className="chip chip-ok">in time</span>}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </>
      ) : tile === 'signed documents' ? (
        <>
          {/* THE RESIDUAL IS THE POINT, and the view says so in its own comment:
              a document nobody has ever checked is not a verified document. */}
          <div className="grid gap-3 mt-3"
               style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))' }}>
            <Fig label="stored" n={d.documents_stored} />
            <Fig label="checked, and matched" n={d.documents_verified} />
            <Fig label="checked, and did NOT match" n={d.documents_mismatched}
                 tone={Number(d.documents_mismatched) > 0 ? 'var(--danger)' : undefined} />
            <Fig label="never checked" n={d.documents_never_checked}
                 tone={Number(d.documents_never_checked) > 0 ? 'var(--accent-2)' : undefined} />
          </div>
          <p className="caption mt-2">
            <strong>Stored is a count of rows and proves nothing about bytes.</strong>
            The difference between it and the checked figures is what has never
            been looked at, and it is never folded into either of them.
          </p>
          <div className="mt-4"><CheckHistory rows={history} noun="document check" /></div>
        </>
      ) : tile === 'rebuild spot-check' ? (
        <>
          <div className="mt-3 caption" style={{ fontStyle: 'normal' }}>
            {r.ran_at
              ? <>Last run <span className="mono">{String(r.ran_at).slice(0, 16)}</span>
                  {r.run_id && <> against run <span className="mono">{r.run_id}</span></>}.</>
              : <>No rebuild has ever been attempted, so there is nothing to compare.</>}
          </div>
          {r.detail && <div className="caption mt-1">{r.detail}</div>}
          <div className="mt-4"><CheckHistory rows={history} noun="rebuild" /></div>
        </>
      ) : tile === 'notification digest' ? (
        <>
          <div className="grid gap-3 mt-3"
               style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))' }}>
            <Fig label="sent today" n={n.sent_today} />
            <Fig label="failed today" n={n.failed_today}
                 tone={Number(n.failed_today) > 0 ? 'var(--danger)' : undefined} />
            <Fig label="total today" n={n.total_today} />
            <Fig label="all outbox records" n={n.total_outbox_rows} />
          </div>
          <p className="caption mt-2">
            {n.last_ran_at
              ? <>Last digest run <span className="mono">{String(n.last_ran_at).slice(0, 16)}</span>.</>
              : <>No notification digest has ever run.</>}
          </p>
        </>
      ) : (
        <>
          {/* audit chain, and anchor. Both are whole-log checks and both read
              their state, time and detail off cw.health_chain. */}
          <div className="grid gap-3 mt-3"
               style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))' }}>
            <Fig label="acts on the chain" n={c.chain_height} />
            <Fig label="checkpoints taken" n={c.checkpoints_taken} />
          </div>
          <div className="caption mt-2">
            {tile === 'anchor'
              ? (c.anchor_ran_at
                  ? <>Last run <span className="mono">{String(c.anchor_ran_at).slice(0, 16)}</span>. {c.anchor_detail}</>
                  : <>The newest checkpoint has never been compared against the log.</>)
              : (c.chain_ran_at
                  ? <>Last run <span className="mono">{String(c.chain_ran_at).slice(0, 16)}</span>. {c.chain_detail}</>
                  : <>Every row&rsquo;s link to its parent has never been walked.</>)}
          </div>
          <div className="mt-4"><CheckHistory rows={history} noun="run" /></div>
        </>
      )}
    </div>
  );
}

// EVERY TIME THIS CHECK HAS RUN, newest first, with who ran it and what was
// seen. `POST /checks/<name>` has written to cw.integrity_check since 0013 and
// nothing had ever read it back — so a person could run a check and never see
// that it had run, let alone what it found.
function CheckHistory({ rows, noun }) {
  if (!rows || rows.length === 0) {
    return (
      <Empty
        kicker="history"
        line={`No ${noun} has ever been recorded.`}
        sub="Never-ran is its own answer. It is not a pass and it is not a failure —
             nobody has asked the question yet." />
    );
  }
  return (
    <>
      <div className="section-label">Every time it has run</div>
      <table className="ledger mt-2">
        <thead>
          <tr><th>Ran</th><th>By</th><th>Subject</th><th>Outcome</th><th>What was seen</th></tr>
        </thead>
        <tbody>
          {rows.map((x) => (
            <tr key={x.check_id}>
              <td className="mono">{String(x.ran_at ?? '').slice(0, 16)}</td>
              <td className="mono">{x.ran_by}</td>
              <td className="mono">{x.subject ?? <span className="caption">the whole log</span>}</td>
              <td>{x.outcome === 'pass'
                ? <span className="chip chip-ok">passed</span>
                : <span className="chip chip-err">failed</span>}</td>
              <td>{x.detail || <span className="caption">nothing recorded</span>}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </>
  );
}

function EmailDeliveryTest() {
  const acts = useActs();
  const [reply, setReply] = useState(null);
  const feedback = useRef(null);
  React.useEffect(() => {
    if (reply && feedback.current) {
      feedback.current.focus();
      feedback.current.scrollIntoView({ block: 'nearest' });
    }
  }, [reply]);
  const send = () => acts.run('email-test', async () => {
    setReply(null);
    const result = await API.testEmailDelivery();
    setReply(result);
    return result;
  });
  return <section className="panel p-4 mb-4" aria-label="Email delivery test">
    <PanelHead title="Test email delivery"
      sub="Send a test to your own notification address from People and access. No contract information is included." />
    <ActButton className="chip mt-3" disabled={!!acts.busy} onClick={send}>
      {acts.busy ? 'Sending test…' : 'Send me a test email'}
    </ActButton>
    {reply && <div ref={feedback} tabIndex={-1} className="caption mt-3"
      role={reply.ok ? 'status' : 'alert'} style={{ overflowWrap: 'anywhere' }}>
      {reply.ok
        ? <>The mail server accepted the test. Check your inbox to confirm arrival.
            {' '}Reference: {reply.body.reference}</>
        : reply.reason}
    </div>}
  </section>;
}

function HealthPane({ me }) {
  // One act at a time. useActs guards with a ref, so a second click in the
  // same tick never reaches the network — `disabled` alone cannot, because it
  // only takes effect after a render.
  const acts = useActs();

  const tiles = usePane(() => API.health());
  // ── THE EVIDENCE UNDER EACH VERDICT (0013, reached 2026-08-23) ──────────
  // `cw.health_summary` is six verdicts computed from four views and a table
  // that `0013` granted to this role and served to NOBODY. So the one page in
  // this product whose job is to prove the record has not been tampered with
  // could say "the audit chain has never been verified" and show nothing at
  // all behind it — not the height of the chain, not what ran and when, not
  // which documents were checked. `a-grant-nobody-can-reach.test.mjs`, written
  // the same day, named all five.
  //
  // Nothing here is new information. It is the information the verdict above
  // it was already computed FROM, which is why a tile and its evidence cannot
  // disagree: one is an aggregate of the other, in the database.
  const chain  = usePane(() => API.healthChain());
  const checks = usePane(() => API.healthChecks());
  const points = usePane(() => API.healthCheckpoints());
  const docs   = usePane(() => API.healthDocuments());
  const rebuild= usePane(() => API.healthRebuild());
  const notifs = usePane(() => API.healthNotifications());
  const due   = usePane(() => API.retentionDue());
  const pipeline = usePane(() => API.redactionState());
  // The permitted raiser → recipient pairs (0064). Read once here and handed
  // to every raise control on this screen: what a role may raise is not a
  // secret, and asking once is one request rather than one per row.
  const routes = usePane(() => API.noticeRoutes());
  const gaps  = usePane(() => API.notificationGap());
  const [error, setError] = useState(null);
  const [disposing, setDisposing] = useState(null); // { id, verb }
  // WHICH VERDICT IS OPEN. Held here rather than in the address, because it is
  // a disclosure on one pane rather than a record somebody would link to — and
  // above every early return, because a hook after one blanks the pane on the
  // render after the data lands (S318).
  const [open, setOpen] = useState(null);
  const evidenceRef = useRef(null);

  if (tiles.status === 'loading') return <Loading />;
  if (tiles.status === 'failed') return <LoadFailed reason={tiles.reason} />;

  // THROUGH `acts`, WHICH WAS ALREADY AT THE TOP OF THIS COMPONENT. The guard
  // was here, with a comment saying precisely why it exists — and the three
  // acts that seal the record went round it, through a local `run()` holding a
  // `busy` useState. One act on this pane used the guard; three did not.
  //
  // `disabled={busy === 'chain'}` is why the census that found the other eight
  // (S322) walked past this one: it LOOKS guarded. React state does not take
  // effect until the next render, and two clicks land in the same tick — which
  // is what a double-click IS.
  //
  // Driven, not read: one double-click on "verify the chain" put TWO rows in
  // cw.integrity_check, 300ms apart, both `pass`, both mine. A checkpoint is a
  // SEAL on the audit record and that record is append-only, so neither copy
  // can be taken back out.
  const busy = acts.busy;
  const run = (which, fn) => acts.run(which, async () => {
    setError(null);
    const r = await fn();
    // EVERY READING, NOT JUST THE VERDICT. Running a check writes a row to
    // cw.integrity_check, and until 2026-08-23 there was nothing else on
    // this page to refresh. Reloading the tile and leaving the evidence
    // stale would put a verdict and its own working out of step on one
    // screen, which is worse than not showing the working at all.
    if (!r.ok) setError(r.reason);
    else { tiles.reload(); due.reload(); chain.reload(); checks.reload();
           points.reload(); docs.reload(); rebuild.reload(); notifs.reload(); }
    return r;
  });

  // never_ran is visibly its own thing — dashed, muted, and worded as a
  // question rather than an answer.
  const chip = (state) => {
    if (state === 'pass')     return <span className="chip chip-ok">verified</span>;
    if (state === 'fail')     return <span className="chip chip-err">failed</span>;
    if (state === 'due')      return <span className="chip chip-pending">due</span>;
    if (state === 'none due') return <span className="chip chip-std">none due</span>;
    if (state === 'overdue')  return <span className="chip chip-pending">overdue</span>;
    return <span className="chip chip-unknown">never run</span>;
  };

  return (
    <div>
      {error && (
        <div className="panel p-3 mb-4" style={{ borderColor: 'var(--danger)' }}>
          <div className="tag" style={{ color: 'var(--danger)' }}>refused</div>
          <div className="text-[12.5px] mt-1.5" style={{ color: 'var(--mute)' }}>{error}</div>
        </div>
      )}

      <PaneHead
        title="System health"
        sub="Evidence, not decoration. A tile is verified only when a check actually ran."
      />
      {me.role === 'administrator' && <EmailDeliveryTest />}
      <div className="panel">
        {tiles.rows.map((t) => {
          // A VERDICT OPENS ITS WORKING, where there is working to open. Five of
          // the six tiles have a reading behind them; `retention due` does not —
          // it is a count of what is due, and the list is already further down
          // this page. A row with nothing behind it stays completely inert, which
          // is the rule the whole application keeps: an affordance is a claim
          // that there is somewhere to go.
          const has = EVIDENCE_FOR[t.tile];
          const shown = open === t.tile;
          const openable = Boolean(has);
          return (
          <div className={`waiting-row${openable ? ' health-row--drill' : ''}`} key={t.tile}
               data-testid={`tile-${t.tile.replace(/\s/g, '-')}`}
               {...(openable
                 ? { ...openableRow(() => setOpen(shown ? null : t.tile),
                       `${shown ? 'hide' : 'show'} the evidence behind the ${t.tile} check`),
                     'aria-pressed': shown }
                 : {})}>
            <div className="min-w-0">
              <div className="text-[13px]" style={{ color: 'var(--ink)' }}>{t.tile}</div>
              {t.detail && <div className="caption mt-0.5" style={{ lineHeight: 1.6 }}>{t.detail}</div>}
              {openable && (
                <div className="caption mt-0.5" style={{ fontStyle: 'normal' }}>
                  {shown ? '▾ its working, below' : '▸ show its working'}
                </div>
              )}
            </div>
            <div className="flex items-center gap-3 shrink-0">
              {/* A FAILING OR NEVER-RUN CHECK IS RAISEABLE (NT-3). Watching a
                  tile go red and having nothing to do about it is exactly the
                  gap Mike named: the administrator observes, and until now
                  could only mention it in a corridor. */}
              {(t.state === 'fail' || t.state === 'never_ran' || t.state === 'overdue') && (
                <RaiseNotice
                  me={me} routes={routes.rows}
                  subject={{ kind: 'health_tile', ref: t.tile,
                             about: `the ${t.tile} check` }} />
              )}
              {chip(t.state)}
              <span className="waiting-age">{t.as_of ? since(t.as_of) : 'not yet'}</span>
            </div>
          </div>
          );
        })}
      </div>

      {/* ── THE WORKING ───────────────────────────────────────────────────
          Drawn under the strip rather than inside a row, so the six verdicts
          stay together and readable while one of them is open. */}
      {open && (
        <div className="mt-5" ref={evidenceRef} data-testid="health-evidence">
          <HealthEvidence
            tile={open} chain={chain} checks={checks} points={points}
            docs={docs} rebuild={rebuild} notifs={notifs} onClose={() => setOpen(null)} />
        </div>
      )}

      {/* ── Who cannot be reached (OB-09's read, finally on a screen) ──────
          Empty is good news ONLY IF SOMEBODY IS LOOKING — cw.notification_gap's
          own words. This is somebody looking, and the act beside each row is
          how the fact gets to the people waiting on that person. */}
      {gaps.status === 'loaded' && gaps.rows.length > 0 && (
        <div className="mt-6">
          <PanelHead
            title="Being waited on, and unreachable"
            sub="Something is waiting on these people and no channel can deliver it. The address book is yours; who is waiting is not." />
          {gaps.rows.map((g) => (
            <div className="panel-2 p-3 mb-2 flex items-baseline justify-between"
                 key={g.person} data-testid="unreachable-person">
              <div>
                <span className="font-mono text-[12.5px]">{g.person}</span>
                <span className="ml-3 caption">{g.role}</span>
              </div>
              <RaiseNotice
                me={me} routes={routes.rows}
                subject={{ kind: 'notification_gap', ref: g.person,
                           about: `${g.person} being unreachable` }} />
            </div>
          ))}
        </div>
      )}

      {/* REMOVED 2026-08-05 (0067): the intake question-set coverage panel.
          It counted questions whose answers matched no term list — but four of
          the six questions have no term list to match, so they were reported as
          gaps they could never not be, and for the rest the number could not
          tell "we missed something" from "there was nothing to find". Removed
          rather than narrowed: a weak number on a screen is worse than none. */}

      <div className="flex gap-2 mt-4">
        <button className="btn" disabled={busy === 'checkpoint'}
                onClick={() => run('checkpoint', () => API.takeCheckpoint())}>
          ▶ take a checkpoint
        </button>
        <button className="btn" disabled={busy === 'anchor'}
                onClick={() => run('anchor', () => API.runCheck('anchor'))}>
          ▶ check the anchor
        </button>
        <button className="btn" disabled={busy === 'chain'}
                onClick={() => run('chain', () => API.runCheck('chain'))}>
          ▶ verify the chain
        </button>
      </div>
      <div className="caption mt-2">
        Each of these records that it ran, whichever way it comes out. A check
        that failed and a check nobody has run are different facts, and the
        tiles above show them differently.
      </div>

      {/* ── Retention: visible here, and yours to action ───────────────────
          Owner decision U9 (0022) moved destruction to the Administrator and
          REVOKED Legal admin's right rather than sharing it. The act itself
          is offered below (D-5, 2026-08-02), behind the strongest
          confirmation idiom in the product: the record's own id, typed. */}
      <div className="mt-8">
        <PanelHead
          title="Retention coming due"
          sub="What is due, and the destroy act itself — never automatic, always yours, always recorded."
        />
        {due.status === 'failed' ? <LoadFailed reason={due.reason} /> : (
          <WaitingList
            order="given"
            items={(due.rows ?? []).map((r) => ({
              key: r.agreement_id,
              title: r.agreement_id,
              sub: r.under_hold
                ? `due ${r.retention_until} · held`
                : `due ${r.retention_until}`,
              at: null,
              chips: (
                <>
                  {/* Held and due are different states and must never render
                      alike — that distinction is what stops a destruction
                      being attempted on something frozen.
                      THE MATTER IS NOT SHOWN, and that is owner decision U13
                      (0024): the Administrator is told that a record is held,
                      not why. Asking Legal is the intended next step, and the
                      refusal itself names the matter to whoever attempts the
                      act. Do not add it back here as a convenience. */}
                  {r.under_hold
                    ? <span className="chip chip-err">held</span>
                    : <span className="chip chip-pending">due</span>}
                  <button
                    className="btn btn-sm"
                    data-testid={`nudge-${r.agreement_id}`}
                    disabled={acts.busy !== null}
                    onClick={() => acts.run(`nudge-${r.agreement_id}`, async () => {
                      setError(null);
                      const n = await API.nudgeRetention({
                        agreement_id: r.agreement_id,
                        note: 'past its retention date',
                      });
                      if (!n.ok) setError(n.reason);
                    })}
                  >record a reminder</button>
                  {r.under_hold ? (
                    /* Blocked BECAUSE HELD, and rendered that way — not a
                       greyed destroy that says "you could, but not now". The
                       hold is somebody else's decision to release. */
                    <span className="caption" data-testid={`blocked-${r.agreement_id}`}>
                      destroy is blocked while held — Legal knows the matter
                    </span>
                  ) : (
                    <ActButton
                      className="btn btn-sm"
                      data-testid={`destroy-${r.agreement_id}`}
                      onClick={() => setDisposing(
                        disposing?.id === r.agreement_id && disposing?.verb === 'destroy'
                          ? null : { id: r.agreement_id, verb: 'destroy' })}
                    >destroy…</ActButton>
                  )}
                </>
              ),
            }))}
            empty={<Empty kicker="retention" line="Nothing is past its retention date."
                          sub="When something is, it appears here for you to act on." />}
          />
        )}
        {disposing?.verb === 'destroy' && (
          <IrreversibleConfirm
            verb="destroy" agreementId={disposing.id}
            description={`Destroying ${disposing.id} is the retention decision, taken and
              recorded — the first of the two disposal acts. It is refused under any
              hold and before the retention date, and it cannot be undone.`}
            action={() => API.destroyRetention({ agreement_id: disposing.id })}
            onDone={(did) => { setDisposing(null); if (did) { due.reload(); pipeline.reload(); } }} />
        )}
        <div className="caption mt-2">
          <strong>The reminder records that this was seen.</strong> It changes no
          retention state and destroys nothing — nothing here destroys anything
          on a timer, by decision. The destroy act is separate, confirmed with
          the record's own id, and lands on the chain under your name.
        </div>
      </div>

      <DisposalPipeline pipeline={pipeline} due={due} disposing={disposing}
                        setDisposing={setDisposing} />
      <DelegatesDesk />
    </div>
  );
}

// ── The strongest confirmation idiom in the product ───────────────────────
// Typing the record's own id is deliberate friction proportionate to an
// irreversible act (WP-U13's anti-pattern: destruction must never be cheaper
// than a clause retirement). The refusal, if one comes, is the database's
// sentence — including the matter names on a hold, which is where U13 says
// they belong.
function IrreversibleConfirm({ verb, agreementId, description, action, onDone }) {
  const [typed, setTyped] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  return (
    <div className="panel p-3 mt-3" data-testid={`confirm-${verb}`}>
      <div className="section-label">{verb} {agreementId}</div>
      <div className="caption mt-1" style={{ whiteSpace: 'pre-wrap' }}>{description}</div>
      <div className="flex gap-2 mt-3 items-end">
        <div>
          <label className="section-label">Type the id to confirm</label>
          <input aria-label="Type the id to confirm" className="mt-1.5 font-mono" value={typed} placeholder={agreementId}
                 onChange={(e) => setTyped(e.target.value)}
                 data-testid={`type-to-${verb}`} />
        </div>
        <button className="btn" onClick={() => onDone(false)}>cancel</button>
        <button className="btn btn-primary"
                disabled={busy || typed.trim() !== agreementId}
                data-testid={`really-${verb}`}
                onClick={async () => {
                  setBusy(true); setError(null);
                  const r = await action();
                  setBusy(false);
                  if (!r.ok) { setError(r.reason); return; }
                  onDone(true);
                }}>
          {busy ? `${verb}ing…` : `✓ ${verb}, permanently`}
        </button>
      </div>
      {error && (
        <div className="panel p-3 mt-3" data-testid="disposal-error">
          <div className="section-label">refused</div>
          <div className="caption mt-1">{error}</div>
        </div>
      )}
    </div>
  );
}

// ── The disposal pipeline: destroyed → redact → purge ─────────────────────
function DisposalPipeline({ pipeline, due, disposing, setDisposing }) {
  return (
    <div className="mt-8">
      <PanelHead
        title="Disposal pipeline"
        sub="Destroy decides, redact removes content but keeps the fact, purge removes the record. In that order, only." />
      {pipeline.status === 'loading' ? <Loading /> :
       pipeline.status === 'failed' ? <LoadFailed reason={pipeline.reason} /> :
       pipeline.rows.length === 0 ? (
        <Empty kicker="disposal" line="Nothing is in the disposal pipeline."
               sub="Records appear here once destroyed under retention, for the
                    redaction review and — only after it — the purge." />
      ) : (
        <div className="panel">
          {pipeline.rows.map((d) => (
            <div className="waiting-row" key={d.agreement_id} style={{ alignItems: 'flex-start' }}>
              <div className="min-w-0">
                <div className="text-[13px]" style={{ color: 'var(--ink)' }}>
                  {d.agreement_id}
                  <span className="caption">
                    {d.destroyed_on ? ` · destroyed ${d.destroyed_on}` : ''}
                    {d.redacted_on ? ` · redacted ${d.redacted_on} by ${d.redacted_by}` : ''}
                    {d.purged_on ? ` · purged ${d.purged_on}` : ''}
                  </span>
                </div>
                {d.external_bytes_pending && (
                  <div className="caption mt-0.5" data-testid="external-bytes-admin">
                    <strong>Bytes may survive outside.</strong> Redaction cleared this
                    system's pointer; it does not reach into an external store.
                  </div>
                )}
                {disposing?.id === d.agreement_id && disposing?.verb === 'redact' && (
                  <IrreversibleConfirm
                    verb="redact" agreementId={d.agreement_id}
                    description={`Redacting ${d.agreement_id} clears the document bytes and
                      the storage pointer. The filename, size, hash, dates and every audit
                      row stay — the fact survives the content.`}
                    action={() => API.redactAgreement({ agreement_id: d.agreement_id })}
                    onDone={(did) => { setDisposing(null); if (did) pipeline.reload(); }} />
                )}
                {disposing?.id === d.agreement_id && disposing?.verb === 'purge' && (
                  <IrreversibleConfirm
                    verb="purge" agreementId={d.agreement_id}
                    description={`Purging ${d.agreement_id} deletes the executed agreement,
                      its documents, certificate and signatories. The audit chain survives
                      it — evidence of correct disposal outlives the thing disposed of.
                      Yours alone; this one cannot be delegated.`}
                    action={() => API.purgeAgreement({ agreement_id: d.agreement_id })}
                    onDone={(did) => { setDisposing(null); if (did) pipeline.reload(); }} />
                )}
              </div>
              <div className="flex items-center gap-3 shrink-0">
                <span className={`chip ${d.state === 'live' ? 'chip-ok'
                  : d.state === 'purged' ? 'chip-err' : 'chip-pending'}`}>
                  {d.state}
                </span>
                {d.state === 'destroyed' && (
                  <button className="btn btn-sm" data-testid={`redact-${d.agreement_id}`}
                          onClick={() => setDisposing(
                            disposing?.id === d.agreement_id && disposing?.verb === 'redact'
                              ? null : { id: d.agreement_id, verb: 'redact' })}>
                    redact…
                  </button>
                )}
                {d.state === 'redacted' && (
                  <button className="btn btn-sm" data-testid={`purge-${d.agreement_id}`}
                          onClick={() => setDisposing(
                            disposing?.id === d.agreement_id && disposing?.verb === 'purge'
                              ? null : { id: d.agreement_id, verb: 'purge' })}>
                    purge…
                  </button>
                )}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ── Redaction delegates (U12): one person, one authority, revocable ───────
function DelegatesDesk() {
  const pane = usePane(() => API.recordsDelegates());
  const people = usePane(() => API.people());
  const [person, setPerson] = useState('');
  const [reason, setReason] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  if (pane.status === 'loading') return <Loading />;
  if (pane.status === 'failed') return <LoadFailed reason={pane.reason} />;

  const live = pane.rows.filter((d) => !d.revoked_at);

  return (
    <div className="mt-8">
      <PanelHead
        title="Redaction delegates"
        sub="You may delegate the redact act — named, reasoned, revocable, on the record. Never the purge." />
      {live.length === 0 ? (
        <div className="caption">No delegation is live. Redaction is yours alone until one is.</div>
      ) : (
        <div className="panel">
          {live.map((d) => (
            <div className="waiting-row" key={d.delegate_id}>
              <div className="min-w-0">
                <div className="text-[13px]" style={{ color: 'var(--ink)' }}>
                  {d.person}
                  <span className="caption"> · granted {String(d.granted_at).slice(0, 10)} · {d.reason}</span>
                </div>
              </div>
              <ActButton className="btn btn-sm" disabled={busy}
                      data-testid={`revoke-delegate-${d.person}`}
                      onClick={async () => {
                        setBusy(true); setError(null);
                        const r = await API.revokeRecordsDelegate({ person: d.person });
                        setBusy(false);
                        if (!r.ok) { setError(r.reason); return; }
                        pane.reload();
                      }}>
                revoke
              </ActButton>
            </div>
          ))}
        </div>
      )}
      <div className="flex gap-2 mt-3 items-end">
        <div style={{ width: 240 }}>
          <label className="section-label">Person</label>
          <select aria-label="Person" className="mt-1.5 w-full font-mono" value={person}
                  onChange={(e) => setPerson(e.target.value)}>
            <option value="">choose a person</option>
            {(people.rows ?? []).map((p) => (
              <option key={p.person} value={p.person}>{p.person}</option>
            ))}
          </select>
        </div>
        <div className="grow">
          <label className="section-label">Why</label>
          <input aria-label="Why" className="mt-1.5 w-full" value={reason}
                 placeholder="a reason of substance — it is reviewed later"
                 onChange={(e) => setReason(e.target.value)} />
        </div>
        <ActButton className="btn btn-primary" disabled={busy || !person || reason.trim().length < 5}
                data-testid="grant-delegate"
                onClick={async () => {
                  setBusy(true); setError(null);
                  const r = await API.grantRecordsDelegate({
                    person, reason: reason.trim(),
                  });
                  setBusy(false);
                  if (!r.ok) { setError(r.reason); return; }
                  setPerson(''); setReason(''); pane.reload();
                }}>
          ✓ delegate redaction
        </ActButton>
      </div>
      {error && (
        <div className="panel p-3 mt-3">
          <div className="section-label">refused</div>
          <div className="caption mt-1">{error}</div>
        </div>
      )}
    </div>
  );
}

// ── Watchers ─────────────────────────────────────────────────────────────
function WatchersPane() {
  // One act at a time. useActs guards with a ref, so a second click in the
  // same tick never reaches the network — `disabled` alone cannot, because it
  // only takes effect after a render.
  const acts = useActs();

  const watchers = usePane(() => API.watchers());
  const coverage = usePane(() => API.watcherCoverage());
  const people   = usePane(() => API.people());
  const [error, setError] = useState(null);
  const [category, setCategory] = useState('');
  const [person, setPerson] = useState('');

  if (watchers.status === 'loading') return <Loading />;
  if (watchers.status === 'failed') return <LoadFailed reason={watchers.reason} />;
  // The coverage read is checked too, and this one is the dangerous half.
  // Defaulting its rows to an empty list turned a FAILED read into "no gaps",
  // so the uncovered-categories banner never rendered — the screen read as
  // though every category had a watcher. The watchers list loads separately
  // and looked fine, so nothing appeared wrong at all.
  // cw.watcher_coverage exists so that "a zero is a visible gap, not a
  // silence"; swallowing the failure turned it back into a silence.
  if (coverage.status === 'failed') return <LoadFailed reason={coverage.reason} />;
  if (coverage.status === 'loading') return <Loading />;

  const gaps = coverage.rows.filter((c) => c.watcher_count === 0);

  const reload = () => { watchers.reload(); coverage.reload(); };

  return (
    <div>
      {error && (
        <div className="panel p-3 mb-4" style={{ borderColor: 'var(--danger)' }}>
          <div className="tag" style={{ color: 'var(--danger)' }}>refused</div>
          <div className="text-[12.5px] mt-1.5" style={{ color: 'var(--mute)' }}>{error}</div>
        </div>
      )}

      {/* The gap, surfaced. An override request touching a category with nobody
          watching it is not "nobody to tell" — it is a hole in the
          socialisation, and the system's job ends at making it visible. */}
      {gaps.length > 0 && (
        <div className="panel p-4 mb-6" style={{ borderColor: 'var(--accent-2)' }}>
          <div className="tag" style={{ color: 'var(--accent-2)' }}>uncovered categories</div>
          <div className="font-serif italic mt-1.5" style={{ fontSize: 16, color: 'var(--ink)' }}>
            {gaps.length} categor{gaps.length === 1 ? 'y has' : 'ies have'} nobody
            watching {gaps.length === 1 ? 'it' : 'them'}.
          </div>
          <div className="caption mt-2" style={{ lineHeight: 1.6 }}>
            An override request in{' '}
            <span className="font-mono">{gaps.map((g) => g.category_key).join(', ')}</span>{' '}
            would be socialised to nobody. Add a watcher, or an always-watcher who
            sees every category.
          </div>
        </div>
      )}

      <PaneHead
        title="Watchers"
        sub="Who is told when an override is requested. Never who decides it."
      />
      <div className="panel">
        {watchers.rows.map((w) => (
          <div className="waiting-row" key={w.watcher_id}>
            <div>
              <div className="text-[13px]" style={{ color: 'var(--ink)' }}>{w.person}</div>
              <div className="caption mt-0.5">added by {w.added_by}</div>
            </div>
            <div className="flex items-center gap-2">
              <span className="chip chip-std">
                {w.category_key ?? 'every category'}
              </span>
              <button className="btn btn-sm"
                      disabled={acts.busy !== null}
                      onClick={() => acts.run(`remove-${w.watcher_id}`, async () => {
                        setError(null);
                        const r = await API.removeWatcher({ watcher_id: w.watcher_id });
                        if (!r.ok) setError(r.reason); else reload();
                      })}>remove</button>
            </div>
          </div>
        ))}
        {watchers.rows.length === 0 && (
          <div className="p-4">
            <Empty kicker="watchers" line="Nobody is watching anything yet."
                   sub="Every override request would be socialised to nobody at all." />
          </div>
        )}
      </div>

      <div className="panel p-4 mt-6">
        <PanelHead title="Add a watcher" sub="Leave the category blank for somebody who watches everything." />
        <div className="flex gap-2 items-end">
          <div style={{ width: 180 }}>
            <label className="section-label">Category</label>
            <select aria-label="Category" className="mt-1.5 w-full font-mono" value={category}
                    onChange={(e) => setCategory(e.target.value)}>
              <option value="">every category</option>
              {coverage.rows.map((c) => (
                <option key={c.category_key} value={c.category_key}>{c.category_key}</option>
              ))}
            </select>
          </div>
          <div style={{ width: 260 }}>
            <label className="section-label">Person</label>
            <select aria-label="Person" className="mt-1.5 w-full font-mono" value={person}
                    onChange={(e) => setPerson(e.target.value)}>
              <option value="">choose somebody</option>
              {(people.rows ?? []).filter((p) => p.state === 'active').map((p) => (
                <option key={p.person} value={p.person}>{p.person}</option>
              ))}
            </select>
          </div>
          {/* cw.override_watcher keys on a surrogate watcher_id, so a second
              click does not collide — it writes a SECOND watcher. */}
          <button className="btn btn-primary" disabled={!person || acts.busy !== null}
                  onClick={() => acts.run('add-watcher', async () => {
                    setError(null);
                    const r = await API.addWatcher({
                      category_key: category || null, person,
                    });
                    if (!r.ok) setError(r.reason); else { setPerson(''); reload(); }
                  })}>✓ add</button>
        </div>
        <div className="caption mt-3">
          Adding somebody here gives them <em>sight</em> of an override request.
          It gives them no vote in it — deciding is Legal's, always.
        </div>
      </div>
    </div>
  );
}

// ── Access history, for the Auditor ──────────────────────────────────────
function AccessHistoryPane({ me }) {
  const pane = usePane(() => API.accessHistory());
  // The shared filter, above every early return as hooks must be.
  const filter = useListFilter(pane.rows, {
    view: 'access-history:grants',
    fields: ['person', 'acted_by'],
    facet: 'action',
  });

  if (pane.status === 'loading') return <Loading />;
  if (pane.status === 'failed') return <LoadFailed reason={pane.reason} />;

  // Filtering happens over rows the POLICY already returned. That is the
  // difference between a filter and a leak: nothing here narrows a fetch that
  // was broader than this reader is entitled to — the fetch was already scoped.
  // ONE FILTER. This pane held its own copy of the search-and-facet logic —
  // one of five, beside the shared hook. Same behaviour, with one improvement
  // that came free: useListFilter lowercases both sides, so the search is now
  // case-insensitive on every field rather than on some of them.
  const rows = filter.shown;

  // THE EXPORT CARRIES ITS OWN SCOPE — and this pane needed it most of the
  // two. Its subtitle says "Append-only, so this is the whole story" in the
  // same header block as the button, while the button wrote whatever the
  // search box had narrowed the list to, under the name `access-history.csv`.
  const csv = () => downloadCsv({
    stem: 'access-history',
    head: ['grant_id', 'action', 'person', 'role', 'acted_by', 'acted_at', 'reason', 'bootstrap'],
    rows,
    total: filter.total,
    by: me && me.person,
    cell: (g, k) => (k === 'bootstrap' ? g.is_bootstrap : g[k]),
  });

  return (
    <div>
      <PaneHead
        title="Access history"
        sub="Every act on somebody's access, in order. Append-only, so this is the whole story."
        right={<button className="btn btn-sm" onClick={csv} data-testid="export-access">
          {csvLabel(rows.length, filter.total)}
        </button>}
      />

      {/* minRows 0 — this pane always offered its filter, and converting it
          to the shared component must not quietly take a control away. Seven
          rows today; an access history only grows, and filtering by person or
          by act is the auditor's job rather than a convenience. */}
      <ListFilter filter={filter} testid="access-history" minRows={0}
                  placeholder="person, or who acted"
                  facetLabel="every act" />
      <div className="caption mb-3"><FilterCount filter={filter} /></div>

      <WaitingList
        order="newest"
        items={rows.map((g) => ({
          key: g.grant_id,
          title: `${g.action} · ${g.person} · ${g.role}`,
          sub: `by ${g.acted_by}${g.reason ? ` — ${g.reason}` : ''}${g.is_bootstrap ? ' · bootstrap' : ''}`,
          at: g.acted_at,
          chips: <span className={`chip ${g.action === 'revoked' ? 'chip-err'
            : g.action === 'countersigned' ? 'chip-ok' : 'chip-std'}`}>{g.action}</span>,
        }))}
        empty={<Empty kicker="access history" line="Nothing matches that."
                      sub="Clear the filters to see the whole story." />}
      />
    </div>
  );
}
