Files
legal-ai/docs/spec/X6-ui-api-contract.md
Chaim 6e69c1dc38
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 9s
feat(ia): IA גל-2 — איחוד-משטחים: ערוץ-למידה אחד · /operations⊇/diagnostics · MET-2/3 (#131, X17)
גל-2 מבקלוג #127 — איחוד-משטחים לפי משטח-היעד של X17. מקיים INV-IA1/IA3/IA4 +
דלתות-הספ (X6 INV-UI7/8, 07-learning §0.4, 00-constitution G2). שומר G10/INV-LRN1
(לא הוסר שום שער-אנושי — רק שער/דגל כפול).

א) תיבת-אישור אחת (INV-IA1): כרטיסי "אישור הלכות"+"פסיקה חסרה" ב-/operations
   מצביעים ל-/approvals (לתיבת-האישורים ←) — /operations מנטר, /approvals מחליט.

ב) ערוץ-למידה אחד (INV-IA3): הוסר applied_to_skill end-to-end —
   - UI: כפתור "סמן כ'אומץ'" + badge "אומץ" ב-lessons-tab; badge ב-curator-portrait.
   - API: LessonPatch, _lesson_to_json, patch call, curator recent_findings (→review_status).
   - db.py: list/add/update_decision_lesson לא בוחרים/כותבים applied_to_skill;
     הפרמטר הוסר. העמודה+אינדקס נשמרים (back-compat, ללא migration), מסומנים DEPRECATED.
   - types: DecisionLesson/LessonPatch/CuratorFinding.
   review_status='approved' = הסטטוס היחיד "זורם-לכותב" (INV-LRN1, #126).

ג) MET-2/3 lost-update (INV-IA3): _append_methodology_override רץ עכשיו בטרנזקציה
   אחת עם SELECT ... FOR UPDATE — אין read-modify-write מתפצל מול עורך-המתודולוגיה
   או promote מקביל. /methodology = העורך-הקנוני; promote מבטל את ה-cache (גל-1 MET-1).

ד) /operations⊇/diagnostics (INV-IA4): גוף /diagnostics חולץ ל-<SystemHealthSection/>
   ומורנדר ב-/operations תחת "בריאות-מערכת". /diagnostics → redirect ל-/operations.
   /diagnostics הוסר מהניווט. משטח-ניטור יחיד.

ה) דלתות-ספ (≥3 מקורות ב-X17, אושר ע"י חיים /goal):
   - X6: INV-UI7 (aggregate=SSoT, mutation מבטל queryKey) + INV-UI8 (render-or-remove, חלקיות).
   - 07-learning §0.4: שער-אחד + טרנזקציה-אחת + applied_to_skill מוסר.
   - 00-constitution G2: תאום-המתודולוגיה כהפרה-ידועה-ממותנת.
   - X17 דלתות-ספ סומנו  קודדו.

בדיקות: py_compile app.py + db.py ✓ · tsc --noEmit ✓ · eslint ✓ (לבד מ-learning-panel:109
קיים-מראש). next build נכשל ב-worktree רק בגלל symlink (Turbopack) — Docker/CI תקין.
api:types יתרענן בדפלוי (curator/lessons אינם response-modeled; הטיפוסים יד-כתובים עודכנו).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 21:04:57 +00:00

13 KiB
Raw Blame History

X6 — חוזה UI↔API וכללי-עיצוב הממשק (UI↔API Contract & Design Rules)

קובץ-תחום זה כפוף ל-חוקת המערכת והוא ה-deep-dive על הממשק (web-ui) וחוזה ה-API בינו לבקאנד — שלא היה מכוסה בספ עד כה. הוא מגדיר: (א) חוזה-הקשר פרונט↔בק (OpenAPI כ-SSoT, מודלי-תשובה, envelope, SSE, טיפול-שגיאות); (ב) כללי-עיצוב הממשק — מקור-אמת יחיד ל-enums/תוויות, helpers משותפים, וחוזה-טופס לכל סוג-מסמך. הממצאים בפועל מתועדים ב-ui-audit.md.

שני סוגי invariant כאן. UI1UI5 הם הנדסיים (חוזה-API/קליינט כללי — ≥3 מקורות + סטטוס). UI6 (חוזה-טופס) הוא פרויקטלי-תפעולי, נגזר מ-X8, ומשרת G9.


1. ארכיטקטורה קיימת

  • web-ui — Next.js 16 + TS + Tailwind v4 + shadcn + TanStack Query. 13 דפים (ראה ui-audit.md).
  • Proxynext.config.ts: /api/*NEXT_PUBLIC_API_ORIGIN (ברירת-מחדל http://127.0.0.1:8000); /openapi.json → schema של ה-FastAPI.
  • לקוחclient.ts: apiRequest<T> + ApiError + makeQueryClient. 18 מודולי-API.
  • טיפוסיםtypes.ts (auto-gen openapi-typescript, 124 operations). npm run api:types.
  • SSEsse.ts: openSSE (progress של העלאות/עיבוד).
  • בקאנדweb/app.py: 143 endpoints, מונוליטי, ~60% ללא Pydantic response model.

2. Invariants של התחום

INV-UI1: ה-OpenAPI schema הוא ה-SSoT לחוזה — טיפוסי-לקוח נגזרים, לא ידניים-סוטים

כלל: חוזה ה-API מוגדר פעם אחת ב-OpenAPI (שמופק מהבקאנד); טיפוסי-ה-frontend נגזרים ממנו (openapi-typescript), ואינם מתוחזקים ידנית במקביל. אין "טיפוס-מראה" מקומי שמשכפל endpoint וסוטה ממנו. מופע של G2 (מקור-אמת יחיד). מקורות: OpenAPI Specification 3.1 (single contract / source of truth; JSON-Schema 2020-12) (https://spec.openapis.org/oas/latest.html) · Pact — consumer-driven contract testing (https://docs.pact.io/) · Speakeasy — Pact vs OpenAPI (provider-driven SSoT) (https://www.speakeasy.com/blog/pact-vs-openapi) | סטטוס: verified אכיפה: npm run api:types ב-CI; איסור טיפוסי-מראה ידניים. כיום אין — ה-frontend מתחזק טיפוסים ידניים. הפרה ידועה: cases.ts:1-9 מתעד מפורשות שה-/api/cases מחזיר unknown ולכן מוחזק טיפוס CaseDetail ידני; PracticeArea מוגדר ב-3 מקומות עם ערכים שונים (ui-audit.md, gap-audit GAP-30/31).

INV-UI2: לכל endpoint נצרך — response model מפורש (חוזה-שלמות API)

כלל: כל endpoint שה-UI צורך נושא response model מפורש (Pydantic), כך ש-OpenAPI מפיק טיפוס אמיתי (לא unknown/object). זהו פאֶט של G4 (שלמות-חוזה לפני צריכה). מקורות: OpenAPI 3.1 (schema objects) · Zalando RESTful API Guidelines (explicit schemas) (https://opensource.zalando.com/restful-api-guidelines/) · FastAPI Response Model docs (https://fastapi.tiangolo.com/tutorial/response-model/) | סטטוס: verified אכיפה: linter/CI שמסמן endpoint נצרך ללא response_model. כיום אין — ~60% מהendpoints ללא מודל. הפרה ידועה: רוב ה-endpoints ב-app.py מחזירים dict חופשי → unknown ב-types.ts (gap-audit GAP-30).

INV-UI3: envelope-תשובה ושגיאה עקבי על-פני ה-API

כלל: כל ה-endpoints חולקים מבנה-תשובה ומבנה-שגיאה אחיד (לא string-לפעמים-JSON-לפעמים). שגיאות לפי תבנית סטנדרטית (Problem Details). מופע של G2. מקורות: RFC 9457 — Problem Details for HTTP APIs (https://www.rfc-editor.org/rfc/rfc9457) · Zalando RESTful API Guidelines (consistent responses) · Microsoft REST API Guidelines (error structure) (https://github.com/microsoft/api-guidelines) | סטטוס: verified אכיפה: envelope משותף ב-app.py + handler-שגיאות גלובלי. כיום אין — מעורב string/JSON/{error}/{detail}. הפרה ידועה: search.py מחזיר "לא נמצאו תוצאות." או JSON; חלק מהכלים {error:...}, חלק raise (gap-audit GAP-32, X9 INV-TOOL1).

INV-UI4: אין בליעת-שגיאה ב-UI

כלל: כל מצב-שגיאה (fetch/mutation) מוצג או מטופל מפורשות — error boundary ו/או טיפול ב-error של useQuery/useMutation. אין כשל שקט שמשאיר את המשתמש בלי משוב. תואם כלל "אין בליעה שקטה" (חוקה §6). מקורות: React docs — Error Boundaries (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) · TanStack Query — Error handling (https://tanstack.com/query/latest/docs/framework/react/guides/query-functions#handling-and-throwing-errors) · Nielsen Norman Group — Error-Message Guidelines (https://www.nngroup.com/articles/error-message-guidelines/) | סטטוס: verified אכיפה: error boundary ברמת-האפליקציה + רכיב-שגיאה משותף; code-review. כיום חלקי — חלק מהדפים אינם מטפלים ב-error; כרטיסי-שגיאה משוכפלים ולא-עקביים. הפרה ידועה: ui-audit.md — כרטיס-שגיאה משוכפל ×3, fallback של SSE שמסתיר כישלון כ-"completed" (gap-audit GAP-32/33).

INV-UI5: חוזה-SSE/progress עם terminal states מוגדרים

כלל: ערוץ ה-progress (SSE) נושא terminal states מפורשים (completed/failed/timeout). אין הנחת-השלמה שקטה על timeout; אי-התאמות-TTL (frontend↔backend) נמנעות. נקשר ל-freshness (G6). מקורות: WHATWG HTML — Server-Sent Events / EventSource (https://html.spec.whatwg.org/multipage/server-sent-events.html) · MDN — Using server-sent events (https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) · TanStack Query — Important Defaults (staleTime/refetch) (https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults) | סטטוס: verified אכיפה: סכמת-אירוע SSE עם terminal state מפורש; יישור TTL. כיום: fallback של 10ש' מניח completed. הפרה ידועה: documents.ts:226-232 — timeout→{status:"completed"}; TTL 5ש' front מול 300ש' redis (gap-audit GAP-33).

INV-UI6: חוזה-טופס מוצהר לכל סוג-מסמך + שיקוף מקור-המילוי

כלל: לכל סוג-מסמך (מסמך-תיק / פסיקה חיצונית / החלטה פנימית) יש חוזה-טופס מוצהר — אילו שדות, חובה/רשות/אוטו/pending/editable — נגזר מ-X8; וה-UI משקף את מקור-המילוי (מסמן מה חולץ אוטומטית/ע"י-Opus מול מה שהיו"ר הזין), כדי שהיו"ר ידע מה לאמת. מופע של G9 (שקיפות-מקור). invariant פרויקטלי-תפעולי. מקור-סמכות: X8-field-provenance.md (טבלת-ה-provenance); feedback היו"ר. אכיפה: רכיב-טופס נגזר-X8 + אינדיקציית "מולא-ע"י-Opus"/"ממתין"/searchable. כיום אין — שדות-Opus מוצגים כשדות-עריכה רגילים ללא סימון. הפרה ידועה: precedents/[id]/page.tsxsummary/headnote/key_quote ללא חיווי-מקור; אין חיווי searchable (gap-audit GAP-36).

INV-UI7: aggregate-נגזר = מקור-אמת יחיד · כל mutation מבטל כל קורא (G2 בשכבת-ה-cache)

כלל: ל-aggregate/מונה נגזר-שרת (למשל /api/chair/pending) יש משטח-בעלים יחיד שמריץ את השאילתה; משטחים אחרים מצביעים אליו ולא מריצים מונה-מתחרה client-side. כל mutation שמשנה ערך הנקרא במשטח אחר חייב לבטל כל queryKey שקורא אותו — כולל aggregators חוצי-namespace — כך שאף משטח לא יציג ערך תקוע אחרי שינוי במשטח אחר. מקדד את X17 INV-IA1/IA2; מופע של G2 בשכבת-ה-TanStack-Query. מקור-סמכות: X17 (≥3 מקורות: TanStack Query invalidation · TkDodo "deriving client state" · NN/g consistency); ia-audit-redesign.md §D1. אכיפה: מונה-הגייטים חי רק ב-/approvals+['chair','pending']; /operations מצביע. כל mutation להלכות/ פסיקה-חסרה/הערות מבטל ['chair','pending'] (גל-1 #130, PR #207).

INV-UI8: שדה-response מרונדר-או-מוסר · חלקיות מוצגת

כלל: שדה שמופיע ב-response לרנדר או להסיר — אסור לזרוק אותו בשקט ב-frontend (אם אין צרכן — להסירו מה-response). KPI מוצג חייב להיות ממופה לצרכן אמיתי (לא דגל אינפורמטיבי-בלבד). aggregate מדויק כש-partial-failure השמיט תורם — להציג חלקיות ("+"/"חלקי"), לא מספר-מוקטן-כאילו-שלם. מקדד את X17 INV-IA5. מקור-סמכות: X17 (NN/g visibility-of-system-status · GOV.UK design-with-data); ia-audit-redesign.md §D3. אכיפה: halacha_backlog מרונדר ב-/operations (לא נזרק); findings_approved (review_status, צרכן אמיתי) החליף את findings_applied (דגל מת); מוני-סוכנים מסמנים "חלקי" כשחברה לא-נטענה (גל-1 #130).


3. כללי-עיצוב (Design Rules) — נגזרים מה-invariants

  • SSoT ל-enums/תוויות/tones: כל enum (CaseStatus, PracticeArea, AppealSubtype, DocType, outcome) + תוויותיו + צבעיו מוגדרים פעם אחת ונצרכים מיבוא — לא משוכפלים בין דפים/רכיבים (מופע UI1/G2).
  • helpers משותפים: פירמוט-תאריך, builder ל-FormData (העלאות), רכיב-שגיאה, query-config (intervals) — משותפים, לא מועתקים.
  • חוזי-טופס: ראה INV-UI6 (X8).

הממצאים הקונקרטיים (כפילויות, הגדרות-שגויות, redundancy) ב-ui-audit.md; התיקון — FU-10.


4. הפניות-אחיות