All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 13s
גל-1 מבקלוג #127 (docs/ia-audit-redesign.md §4) — תיקון מקומי, ללא הגירת-IA. מקיים G2 בשכבת-ה-UI דרך INV-IA1/IA2/IA5/IA6 (docs/spec/X17). א) פערי-סנכרון (INV-IA2 — mutation מבטל כל קורא): - CAS-1/2: העלאת-DOCX/export מבטלים ['decision-blocks'] (מחוון source_of_truth) - APR-1/4: פתרון/יצירת-הערה מבטלים ['chair','pending'] (תיבה+תג-סרגל) - APR-5/ADM-2: אישור/batch הלכות מבטלים ['chair','pending']+['operations'] - APR-6/ADM-3: create/update/delete/upload פסיקה-חסרה מבטלים שניהם - LRN-6: ComparePanel גוזר בחירה מהקורפוס המרוענן (אין POST ל-id מחוק → 404) - LRN-8: מחיקת-קורפוס מבטלת רשימת-צ'אטים (chat שהתייתם לא נשאר עם קישור-קורפוס תקוע) - LRN-10/MET-1/MET-8: promote מבטל גם lessons וגם methodology (LessonsTab+/methodology) ב) נתונים-שגויים (INV-IA5 — סטטוס מגובה-צרכן): - LRN-4: KPI "דפוסי סגנון" — הוסר היחס-השקרי "מתוך total_patterns" (שאילתות עצמאיות) - LRN-5: findings_applied (דגל אינפורמטיבי-בלבד) → findings_approved (שער INV-LRN1 האמיתי) - ADM-1: halacha_backlog שהוחזר ונזרק → מרונדר ב-/diagnostics, מצביע ל-/approvals (INV-IA1) - ADM-6: מוני-סוכנים מסמנים "חלקי+" כשחברת-Paperclip לא נטענה - APR-3: מכוסה ע"י APR-1 (count+sample מאותה שאילתה; הבעיה היתה staleness-cache) - MET-6: עורך-צ'קליסטים מציג איזה case בוחר כל צ'קליסט (explainer-תחולה) - ADM-5: ערך-Container מסומן "ממתין ל-redeploy" כש-Coolify≠Container ג) מתים/jargon: - PRE-2: הוסר GET /api/precedent-library/queue/pending (אפס צרכני-frontend) - PRE-3/5: AuthorityBadge (binding/persuasive) מרונדר גם בחיפוש, לא רק בתור-הביקורת - MET-5: הוסר ז'רגון T7/T15 מטקסט-העזר ב-/methodology (INV-IA6) Invariants: מקיים INV-IA1/IA2/IA5/IA6 (X17), G2 (מקור-אמת יחיד בשכבת-UI), G10 (לא הוסר שום שער-אנושי — רק סנכרון/נתון/קוד-מת). שומר INV-LRN1. בדיקות: py_compile web/app.py ✓ · tsc --noEmit ✓ · eslint ✓ (לבד מ-learning-panel:109 unescaped-quote — קיים-מראש ב-main, מחוץ לסט-הממצאים). next build נכשל רק בגלל symlink node_modules ב-worktree (Turbopack) — ה-build ב-Docker/CI תקין. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
151 lines
4.7 KiB
TypeScript
151 lines
4.7 KiB
TypeScript
/**
|
||
* Chair feedback hooks — recording and managing Dafna's feedback on drafts.
|
||
*/
|
||
|
||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||
import { apiRequest } from "./client";
|
||
|
||
export type FeedbackCategory =
|
||
| "missing_content"
|
||
| "wrong_tone"
|
||
| "wrong_structure"
|
||
| "factual_error"
|
||
| "style"
|
||
| "other";
|
||
|
||
export type ChairFeedback = {
|
||
id: string;
|
||
case_id: string | null;
|
||
case_number: string;
|
||
block_id: string;
|
||
category: FeedbackCategory;
|
||
feedback_text: string;
|
||
lesson_extracted: string;
|
||
resolved: boolean;
|
||
applied_to: string[];
|
||
created_at: string | null;
|
||
};
|
||
|
||
export type CreateFeedbackInput = {
|
||
case_number?: string;
|
||
block_id?: string;
|
||
feedback_text: string;
|
||
category?: FeedbackCategory;
|
||
lesson_extracted?: string;
|
||
};
|
||
|
||
const feedbackKeys = {
|
||
all: ["feedback"] as const,
|
||
list: (filters: { category?: string; unresolved_only?: boolean }) =>
|
||
[...feedbackKeys.all, "list", filters] as const,
|
||
};
|
||
|
||
export function useFeedbackList(filters: {
|
||
category?: string;
|
||
unresolved_only?: boolean;
|
||
} = {}) {
|
||
const params = new URLSearchParams();
|
||
if (filters.category) params.set("category", filters.category);
|
||
if (filters.unresolved_only) params.set("unresolved_only", "true");
|
||
const qs = params.toString();
|
||
|
||
return useQuery({
|
||
queryKey: feedbackKeys.list(filters),
|
||
queryFn: ({ signal }) =>
|
||
apiRequest<ChairFeedback[]>(`/api/feedback${qs ? `?${qs}` : ""}`, { signal }),
|
||
});
|
||
}
|
||
|
||
/** Feedback filtered by case number */
|
||
export function useCaseFeedback(caseNumber: string | undefined) {
|
||
const params = caseNumber ? `?case_number=${caseNumber}` : "";
|
||
return useQuery({
|
||
queryKey: [...feedbackKeys.all, "case", caseNumber ?? ""] as const,
|
||
queryFn: ({ signal }) =>
|
||
apiRequest<ChairFeedback[]>(`/api/feedback${params}`, { signal }),
|
||
enabled: Boolean(caseNumber),
|
||
staleTime: 5_000,
|
||
refetchInterval: 5_000,
|
||
});
|
||
}
|
||
|
||
export function useCreateFeedback() {
|
||
const qc = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: (data: CreateFeedbackInput) =>
|
||
apiRequest<{ id: string; status: string }>("/api/feedback/json", {
|
||
method: "POST",
|
||
body: data,
|
||
}),
|
||
onSuccess: () => {
|
||
qc.invalidateQueries({ queryKey: feedbackKeys.all });
|
||
// APR-4 (INV-IA2): a new unresolved comment raises the chair-pending
|
||
// aggregate — keep /approvals + the nav badge in sync.
|
||
qc.invalidateQueries({ queryKey: ["chair", "pending"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
export function useResolveFeedback() {
|
||
const qc = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: ({
|
||
feedbackId,
|
||
applied_to,
|
||
fold,
|
||
}: {
|
||
feedbackId: string;
|
||
applied_to: string[];
|
||
/** When true (default server-side), wakes the CEO to fold the lesson
|
||
* into the right knowledge file. Pass false for pure bookkeeping. */
|
||
fold?: boolean;
|
||
}) =>
|
||
apiRequest<{ status: string; fold_queued: boolean }>(
|
||
`/api/feedback/${feedbackId}/resolve`,
|
||
{ method: "PATCH", body: { applied_to, ...(fold === undefined ? {} : { fold }) } },
|
||
),
|
||
onSuccess: () => {
|
||
qc.invalidateQueries({ queryKey: feedbackKeys.all });
|
||
// APR-1/APR-4 (INV-IA2): resolving a comment lowers the chair-pending
|
||
// aggregate; refresh /approvals' counter + the nav badge (one source).
|
||
qc.invalidateQueries({ queryKey: ["chair", "pending"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
/** Hebrew labels for feedback categories */
|
||
export const CATEGORY_LABELS: Record<FeedbackCategory, string> = {
|
||
missing_content: "תוכן חסר",
|
||
wrong_tone: "טון שגוי",
|
||
wrong_structure: "מבנה שגוי",
|
||
factual_error: "שגיאה עובדתית",
|
||
style: "סגנון",
|
||
other: "אחר",
|
||
};
|
||
|
||
/** Tailwind color classes per category */
|
||
export const CATEGORY_COLORS: Record<FeedbackCategory, string> = {
|
||
missing_content: "bg-amber-100 text-amber-800 border-amber-200",
|
||
wrong_tone: "bg-purple-100 text-purple-800 border-purple-200",
|
||
wrong_structure: "bg-blue-100 text-blue-800 border-blue-200",
|
||
factual_error: "bg-red-100 text-red-800 border-red-200",
|
||
style: "bg-emerald-100 text-emerald-800 border-emerald-200",
|
||
other: "bg-gray-100 text-gray-800 border-gray-200",
|
||
};
|
||
|
||
/** Block ID labels */
|
||
export const BLOCK_LABELS: Record<string, string> = {
|
||
"block-alef": "א — כותרת מוסדית",
|
||
"block-bet": "ב — הרכב הוועדה",
|
||
"block-gimel": "ג — צדדים",
|
||
"block-dalet": "ד — החלטה",
|
||
"block-he": "ה — פתיחה",
|
||
"block-vav": "ו — רקע עובדתי",
|
||
"block-zayin": "ז — טענות הצדדים",
|
||
"block-chet": "ח — הליכים",
|
||
"block-tet": "ט — תכניות חלות",
|
||
"block-yod": "י — דיון והכרעה",
|
||
"block-yod-alef": "יא — סיכום",
|
||
"block-yod-bet": "יב — חתימות",
|
||
};
|