);
})}
>
);
}
// ─── Readiness ──────────────────────────────────────────────────────────────
// The composite is shown WITH its four dimensions. A single number invites
// trust-and-skim; the weakest dimension is what a reviewer should look at.
function Readiness({ readiness }) {
if (!readiness || typeof readiness.composite !== "number") return null;
const dims = readiness.dimensions || {};
return (
{readiness.composite}/100
{readiness.capped ? "Capped: a dimension scored zero" : "Publish readiness"}
{Object.entries(dims).map(([k, d]) => (
{d.label}{d.value}
))}
);
}
// ─── Record ─────────────────────────────────────────────────────────────────
// The evidentiary chain for this draft, oldest first. Every row names the hash
// it covered, so a voided approval is visibly a signature against text that no
// longer exists — not an erased one.
function Record({ history }) {
if (!history || !history.length) return null;
const label = { approved: "Signed", voided: "Signature voided", changes_requested: "Sent back", declined: "Declined" };
return (
);
}
// ─── Main ───────────────────────────────────────────────────────────────────
function ReviewQueue() {
const [session, setSession] = useState(null);
const [me, setMe] = useState(null);
const [capacity, setCapacity] = useState(null);
const [items, setItems] = useState([]);
const [resolved, setResolved] = useState([]);
const [activeId, setActiveId] = useState(null);
const [packet, setPacket] = useState(null);
const [rulings, setRulings] = useState({});
const [elevating, setElevating] = useState(false);
const [elevError, setElevError] = useState(null);
const [busy, setBusy] = useState(false);
const [banner, setBanner] = useState(null);
// Editing. `draftBody` is the working copy of the body while an edit is open.
const [editing, setEditing] = useState(false);
const [draftBody, setDraftBody] = useState(null);
const signOut = useCallback((reason) => {
setSession(null); setMe(null); setPacket(null); setItems([]); setResolved([]);
if (reason) setBanner({ kind: "info", text: reason });
}, []);
// Stable callbacks, deliberately: useApi memoizes on these, and an inline
// arrow here would give every render a new client, re-arm every effect that
// depends on it, and cancel the previous load before it could set state.
const needElevation = useCallback(() => setElevating(true), []);
const api = useApi(session?.token, signOut, needElevation);
const loadQueue = useCallback(async (facilityId) => {
const [queue, ent] = await Promise.all([
api(`/api/facilities/${facilityId}/queue`),
api("/api/entitlements").catch(() => null),
]);
setItems(queue.items);
setResolved(queue.resolved || []);
setCapacity(ent);
return queue;
}, [api]);
const loadPacket = useCallback(async (id) => {
const p = await api(`/api/review/${id}`);
setPacket(p);
return p;
}, [api]);
// Load identity and worklist
useEffect(() => {
if (!session) return;
let cancelled = false;
(async () => {
try {
const meRes = await api("/api/me");
if (cancelled) return;
setMe(meRes);
const facilityId = meRes.facilities[0];
if (!facilityId) { setBanner({ kind: "warn", text: "No facility is assigned to your account. Ask your administrator to add one." }); return; }
const queue = await loadQueue(facilityId);
if (cancelled) return;
setActiveId((cur) => cur || queue.items[0]?.id || null);
} catch (e) { if (!cancelled) setBanner({ kind: "stop", text: e.message }); }
})();
return () => { cancelled = true; };
}, [session, api, loadQueue]);
// Load the active packet
useEffect(() => {
if (!activeId || !session) { setPacket(null); return; }
let cancelled = false;
(async () => {
try {
const p = await api(`/api/review/${activeId}`);
if (cancelled) return;
setPacket(p); setRulings({}); setEditing(false); setDraftBody(null);
} catch (e) { if (!cancelled) setBanner({ kind: "stop", text: e.message }); }
})();
return () => { cancelled = true; };
}, [activeId, session, api]);
const attest = async (action, elevationPassword) => {
if (!packet) return;
setBusy(true); setElevError(null);
try {
if (elevationPassword) {
const res = await fetch(`${API_BASE}/api/auth/elevate`, {
method: "POST",
headers: { Authorization: `Bearer ${session.token}`, "Content-Type": "application/json" },
body: JSON.stringify({ password: elevationPassword }),
});
if (!res.ok) {
const d = await res.json().catch(() => ({}));
setElevError(d.error || "That password was not accepted."); setBusy(false); return;
}
}
const r = await api(`/api/review/${packet.id}/attest`, {
method: "POST",
body: { action, contentHash: packet.contentHash },
});
setElevating(false);
// Keep the page on screen after signing. The reviewer should see the
// signature land on the record — and see it voided if the text moves.
await Promise.all([loadQueue(me.facilities[0]), loadPacket(packet.id)]);
setBanner({
kind: "info",
text: action === "approved"
? `Page signed. Your licence is recorded against hash ${r.contentHash}.`
: "Sent back for changes. The page returns to the queue once it is revised.",
});
} catch (e) {
if (e.needsElevation) { setBusy(false); return; } // dialog is already open
setBanner({ kind: "stop", text: e.message });
setElevating(false);
}
setBusy(false);
};
const beginEdit = () => {
setDraftBody((packet.content?.body || []).map((b) => ({ ...b })));
setEditing(true);
};
const saveEdit = async () => {
if (!packet || !draftBody) return;
setBusy(true);
try {
const content = { ...packet.content, body: draftBody };
const r = await api(`/api/review/${packet.id}/edit`, { method: "POST", body: { content } });
setEditing(false); setDraftBody(null); setRulings({});
await Promise.all([loadQueue(me.facilities[0]), loadPacket(packet.id)]);
setBanner({ kind: r.voided ? "warn" : "info", text: r.message });
} catch (e) {
setBanner({ kind: "stop", text: e.message });
}
setBusy(false);
};
if (!session) {
return (<>
>);
}
const total = packet?.claims?.length || 0;
const ruled = Object.values(rulings).filter(Boolean).length;
const rejected = Object.values(rulings).filter((r) => r === "reject").length;
const allRuled = total > 0 && ruled === total;
const alreadySigned = packet?.state === "approved";
const lastRecord = packet?.history?.[packet.history.length - 1];
const recordLeads = alreadySigned || lastRecord?.action === "voided";
const canSign = packet?.preflight?.canSign && allRuled && rejected === 0 && !alreadySigned && !editing;
let signState;
if (!packet) signState = "Select a page from the queue.";
else if (alreadySigned) signState = "Signed. Any edit to the text voids this signature and returns the page to the queue.";
else if (editing) signState = "Finish or cancel the edit before signing.";
else if (!packet.preflight.canSign) signState = packet.preflight.reason;
else if (!allRuled) signState = `${total - ruled} of ${total} claims still need a ruling.`;
else if (rejected > 0) signState = `${rejected} claim${rejected > 1 ? "s" : ""} marked for change. Send it back instead of signing.`;
else if (packet.editedBySelf) signState = "Every claim ruled. You authored this revision; the record will say so.";
else signState = "Every claim ruled. Ready to sign.";
const ListItem = ({ i }) => (
);
return (
<>
{capacity.usage.newPages} of {capacity.usage.newPagesLimit} pages generated this periodPlan {capacity.entitlements.tier}
{capacity.usage.newPagesRemaining === 0 && (
Page limit reached — review continues as normal.
)}
)}
{!packet ? (
{items.length ? "Select a page" : "Queue is clear"}
{items.length
? "Choose a page from the queue to review its claims."
: "Nothing is waiting for your signature. New drafts appear here as they are generated."}
Target query {packet.targetQuery}Routed to {(packet.route || []).join(" or ").replace(/_/g, " ")}
{packet.dualAttestation && Two signatures required}
{packet.editedBy && Current revision edited by {packet.editedBy}}
{banner && (
{banner.text}
)}
{packet.preflight?.warning && (
{packet.preflight.warning}
)}
{!packet.preflight?.canSign && (
You cannot sign this page. {packet.preflight.reason}
)}
{/* Claims lead — unless the page's signature is the news.
A page that is signed, or whose signature was just
voided, should show the record before the exhibits. */}
{recordLeads && }
setRulings((r) => ({ ...r, [i]: v }))} />
{!recordLeads && }
{editing ? "Editing the page text" : "The page as it will publish"}
{alreadySigned
? "This page is signed. Saving any change to the text voids that signature and returns the page to the queue."
: "Saving changes the content hash. A reviewer holding the current packet will be refused on sign and must reload."}