feat(ui): chair approval center — one page for every pending human-gate (#63 follow-up)
Dafna asked for a single page under the prod site listing everything she needs
to approve, so nothing is forgotten — the visible embodiment of INV-G10 (human
gates) and INV-QA1 (halacha backlog must be visible).
Backend — GET /api/chair/pending aggregates every pending chair gate, each as a
direct source query (count + sample + action link):
- halachot review backlog (review_status='pending_review') + oldest
- open missing precedents
- unresolved chair_feedback
- qa_failed cases
- gold-set review (FU-5, file-based, best-effort: total vs source='chair')
Frontend — /approvals page ("מרכז אישורים"):
- src/lib/api/chair.ts — usePendingApprovals() (hand-typed until next api:types)
- src/app/approvals/page.tsx — card per category, severity-coloured count, sample
rows, oldest-pending date, link to where each is handled; live (60s refetch)
- app-shell nav: "מרכז אישורים" in the work group + total-pending badge (quiet at 0)
Live counts at build time surfaced the value immediately: 226 open missing
precedents, 178 pending halachot, 20 unapplied feedback notes, 1 qa_failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
web-ui/src/lib/api/chair.ts
Normal file
41
web-ui/src/lib/api/chair.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { apiRequest } from "./client";
|
||||
|
||||
/**
|
||||
* Chair approval center (INV-G10) — aggregates every pending human-gate item
|
||||
* (halacha approvals, missing precedents, unapplied feedback, QA-failed cases,
|
||||
* gold-set review) so nothing Dafna must approve is forgotten.
|
||||
*
|
||||
* Hand-typed (not from the generated types.ts) because /api/chair/pending is a
|
||||
* new endpoint; switch to the generated type after the next `npm run api:types`.
|
||||
*/
|
||||
export type ApprovalSeverity = "high" | "medium" | "low" | "ok";
|
||||
|
||||
export type ApprovalSample = { text: string; source: string };
|
||||
|
||||
export type ApprovalCategory = {
|
||||
key: string;
|
||||
label: string;
|
||||
description: string;
|
||||
count: number;
|
||||
severity: ApprovalSeverity;
|
||||
href: string | null;
|
||||
oldest_at?: string | null;
|
||||
sample?: ApprovalSample[];
|
||||
extra?: { total: number; reviewed: number };
|
||||
};
|
||||
|
||||
export type PendingApprovals = {
|
||||
total_pending: number;
|
||||
generated_at: string;
|
||||
categories: ApprovalCategory[];
|
||||
};
|
||||
|
||||
export function usePendingApprovals() {
|
||||
return useQuery({
|
||||
queryKey: ["chair", "pending"],
|
||||
queryFn: () => apiRequest<PendingApprovals>("/api/chair/pending"),
|
||||
refetchInterval: 60_000,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user