From f0a8af30dc41b2b09b686f293ddcf9dee1e1b5a3 Mon Sep 17 00:00:00 2001 From: Chaim Date: Fri, 12 Jun 2026 07:29:39 +0000 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=D7=AA=D7=95=D7=A8-=D7=90=D7=99?= =?UTF-8?q?=D7=A9=D7=95=D7=A8=20=D7=94=D7=9C=D7=9B=D7=95=D7=AA=20=D7=9E?= =?UTF-8?q?=D7=90=D7=95=D7=97=D7=93=20=E2=80=94=202=20=D7=AA=D7=A6=D7=95?= =?UTF-8?q?=D7=92=D7=95=D7=AA=20=D7=9C=D7=A4=D7=99=20=D7=A4=D7=A2=D7=95?= =?UTF-8?q?=D7=9C=D7=94=20(#133)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit מבטל את ה-toggle "תור נקי / דורש תיקון-חילוץ" שבו "תור נקי" ריק לגמרי (כל ההלכות-הנקיות נפתרו), והעבודה האמיתית חבויה מאחורי הכפתור השני שגם מערבב התלבטות-פאנל עם פגמי-חילוץ. אושר ב-Claude Design (כרטיס 19-halacha-queue-unified). במקום זה — תור אחד, fetch אחד, פיצול client-side לפי **סוג-הפעולה**: - "להכרעתך" = הלכות שהפאנל דן בהן (יש panel_round) או נקיות → אשר/דחה, עם טבלת-ההתלבטות; ממוין פיצול-פאנל-תחילה (FU-3). - "דורש תיקון-חילוץ" = מסומנות-דגל שלא עברו התלבטות → תיקון-חילוץ. `useHalachotPending` אוחד לקריאה אחת (exclude_low_quality=false + order_by_priority + cluster + include_equivalents + include_panel_round); נוסף `isExtractionFixItem(h)` (= !panel_round && יש דגל). PendingPanel מפצל ב-useMemo, segmented-control עם מוני שני הדליים. אפס שינוי-backend (הפרמטרים כבר קיימים מ-#220/#222). display-only, שער-אישור יחיד (INV-IA/G10). ולידציה: tsc + eslint נקי. חלק מ-#133. Co-Authored-By: Claude Opus 4.8 --- .../precedents/halacha-review-panel.tsx | 82 +++++++++++-------- web-ui/src/lib/api/precedent-library.ts | 46 +++++------ 2 files changed, 68 insertions(+), 60 deletions(-) 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) }),