diff --git a/web-ui/src/components/precedents/halacha-review-panel.tsx b/web-ui/src/components/precedents/halacha-review-panel.tsx index 02fa9a9..800df6d 100644 --- a/web-ui/src/components/precedents/halacha-review-panel.tsx +++ b/web-ui/src/components/precedents/halacha-review-panel.tsx @@ -10,7 +10,8 @@ import { Textarea } from "@/components/ui/textarea"; import { CorroborationBadge } from "./corroboration-badge"; import { practiceAreaLabel } from "./practice-area"; import { - useHalachotPending, useHalachotByStatus, useUpdateHalacha, useBatchReviewHalachot, type Halacha, + useHalachotPending, useHalachotByStatus, useUpdateHalacha, useBatchReviewHalachot, + isExtractionFixItem, type Halacha, } from "@/lib/api/precedent-library"; import { AuthorityBadge, ruleTypeLabel } from "./halacha-meta"; @@ -628,23 +629,29 @@ function RestorePanel({ // ─── Pending queue panel (main review flow) ─────────────────────────────────── function PendingPanel() { - // #84.1 — "clean" = quality-gated + prioritized + clustered review queue; - // "needsfix" = the flagged 'needs extraction fix' bucket. - const [view, setView] = useState<"clean" | "needsfix">("clean"); - const { data, isPending, error } = useHalachotPending({ - limit: 500, needsFix: view === "needsfix", - }); + // #133 unified queue — ONE fetch of all pending, split client-side by ACTION: + // "judgment" = items the panel deliberated (or clean) → chair approves/rejects; + // "fix" = flagged-but-never-adjudicated → extraction repair. (No empty "תור נקי".) + const [view, setView] = useState<"judgment" | "fix">("judgment"); + const { data, isPending, error } = useHalachotPending({ limit: 500 }); const update = useUpdateHalacha(); const batch = useBatchReviewHalachot(); const [expandedIds, setExpandedIds] = useState>(new Set()); const [focusedId, setFocusedId] = useState(null); - const groups = useMemo( - () => buildGroups(data?.items ?? []), - [data], - ); + const allItems = useMemo(() => data?.items ?? [], [data]); + const judgmentCount = useMemo( + () => allItems.filter((h) => !isExtractionFixItem(h)).length, [allItems]); + const fixCount = useMemo( + () => allItems.filter(isExtractionFixItem).length, [allItems]); - const totalCount = data?.items.length ?? 0; + const segmentItems = useMemo( + () => allItems.filter((h) => + view === "fix" ? isExtractionFixItem(h) : !isExtractionFixItem(h)), + [allItems, view], + ); + const groups = useMemo(() => buildGroups(segmentItems), [segmentItems]); + const totalCount = segmentItems.length; const visibleItems = useMemo(() => { const out: ReviewItem[] = []; @@ -766,24 +773,26 @@ function PendingPanel() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [focused, visibleItems]); + const segBtn = (key: "judgment" | "fix", label: string, count: number) => ( + + ); + + // #133 — two segments by ACTION (deliberated→judge vs flagged→fix); no empty "תור נקי". const viewToggle = (
- - + {segBtn("judgment", "להכרעתך", judgmentCount)} + {segBtn("fix", "דורש תיקון-חילוץ", fixCount)}
); @@ -804,14 +813,14 @@ function PendingPanel() { body = (

- {view === "needsfix" - ? "בקט התיקון ריק — אין הלכות מסומנות-איכות." - : "אין הלכות נקיות הממתינות לאישור."} + {view === "fix" + ? "אין הלכות הממתינות לתיקון-חילוץ." + : "אין הלכות הממתינות להכרעתך."}

- {view === "needsfix" - ? "פריטים שסומנו (ציטוט לא-מאומת, יישום, כפילות-קרובה וכו') יופיעו כאן." - : "העלה פסיקה חדשה — ההלכות שיחולצו ממנה יופיעו כאן."} + {view === "fix" + ? "פריטים שסומנו בפגם-חילוץ (ציטוט לא-מאומת / קטוע / כפילות) ושלא עברו התלבטות-פאנל יופיעו כאן." + : "הלכות שהפאנל הכריע או דן בהן יופיעו כאן לאישורך."}

); @@ -821,8 +830,11 @@ function PendingPanel() {
{totalCount} - {view === "needsfix" ? " מסומנות" : " ממתינות"} + {view === "fix" ? " לתיקון-חילוץ" : " להכרעה"} {" "}ב-{groups.length} פסיקות + {view === "judgment" && ( + · ממוין: פיצול-פאנל תחילה + )} ניווט: J/K diff --git a/web-ui/src/lib/api/precedent-library.ts b/web-ui/src/lib/api/precedent-library.ts index acb9748..b76ab05 100644 --- a/web-ui/src/lib/api/precedent-library.ts +++ b/web-ui/src/lib/api/precedent-library.ts @@ -603,37 +603,33 @@ export function useRequestHalachotExtraction() { /** #84.1/#84.2/#84.3 — the chair review queue. * - * Default ("clean") view: quality-gated (flagged items hidden), priority-ordered - * (most-uncertain/negatively-treated first), and near-duplicate-clustered into - * one card. Pass `needsFix: true` for the 'needs extraction fix' bucket — every - * pending item carrying a quality flag (filtered client-side). */ -export function useHalachotPending( - opts: { limit?: number; needsFix?: boolean } = {}, -) { - const { limit = 200, needsFix = false } = opts; - const qs = needsFix - ? `review_status=pending_review&exclude_low_quality=false` - + `&include_panel_round=true&limit=${limit}` - : `review_status=pending_review&exclude_low_quality=true` - + `&order_by_priority=true&cluster=true&include_equivalents=true` - + `&include_panel_round=true&limit=${limit}`; + * ONE fetch of the whole pending set (#133 unified queue): all pending halachot, + * priority-ordered (panel-split first → most-uncertain → oldest, #84.3/FU-3), + * near-duplicate-clustered, with the latest panel deliberation attached. The + * review panel splits this client-side by ACTION — "להכרעה" (has a panel round) + * vs "תיקון-חילוץ" (flagged, never adjudicated) — instead of the old empty + * clean/needsfix toggle. */ +export function useHalachotPending(opts: { limit?: number } = {}) { + const { limit = 200 } = opts; + const qs = `review_status=pending_review&exclude_low_quality=false` + + `&order_by_priority=true&cluster=true&include_equivalents=true` + + `&include_panel_round=true&limit=${limit}`; return useQuery({ - queryKey: [...libraryKeys.halachotPending(), needsFix ? "needsfix" : "clean"], - queryFn: async ({ signal }) => { - const res = await apiRequest<{ items: Halacha[]; count: number }>( - `/api/halachot?${qs}`, - { signal }, - ); - if (!needsFix) return res; - // needs-fix bucket = pending items that carry a quality flag - const items = res.items.filter((h) => (h.quality_flags?.length ?? 0) > 0); - return { items, count: items.length }; - }, + queryKey: libraryKeys.halachotPending(), + queryFn: ({ signal }) => + apiRequest<{ items: Halacha[]; count: number }>(`/api/halachot?${qs}`, { signal }), staleTime: 5_000, refetchOnMount: "always", }); } +/** A pending item belongs in the "needs extraction fix" segment when it carries a + * quality flag AND the panel never deliberated it (no round). Everything else — + * deliberated items and clean items — is a chair-judgment item. (#133 unified queue) */ +export function isExtractionFixItem(h: Halacha): boolean { + return !h.panel_round && (h.quality_flags?.length ?? 0) > 0; +} + export function useHalachotByStatus(status: string, limit = 300) { return useQuery({ queryKey: libraryKeys.halachot({ review_status: status, limit: String(limit) }),