גל-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>
13 KiB
X6 — חוזה UI↔API וכללי-עיצוב הממשק (UI↔API Contract & Design Rules)
קובץ-תחום זה כפוף ל-חוקת המערכת והוא ה-deep-dive על הממשק (web-ui) וחוזה ה-API בינו לבקאנד — שלא היה מכוסה בספ עד כה. הוא מגדיר: (א) חוזה-הקשר פרונט↔בק (OpenAPI כ-SSoT, מודלי-תשובה, envelope, SSE, טיפול-שגיאות); (ב) כללי-עיצוב הממשק — מקור-אמת יחיד ל-enums/תוויות, helpers משותפים, וחוזה-טופס לכל סוג-מסמך. הממצאים בפועל מתועדים ב-ui-audit.md.
שני סוגי invariant כאן. UI1–UI5 הם הנדסיים (חוזה-API/קליינט כללי — ≥3 מקורות + סטטוס). UI6 (חוזה-טופס) הוא פרויקטלי-תפעולי, נגזר מ-X8, ומשרת G9.
1. ארכיטקטורה קיימת
- web-ui — Next.js 16 + TS + Tailwind v4 + shadcn + TanStack Query. 13 דפים (ראה ui-audit.md).
- Proxy — next.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. - SSE — sse.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.tsx — summary/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. הפניות-אחיות
- ui-audit.md — audit דף-אחר-דף (13 דפים) בתבנית-ה-gap.
- X8-field-provenance.md — מקור-מילוי-שדות (בסיס ל-INV-UI6).
- X7-paperclip-client-params.md — חוזה-ה-API שהפלאגין צורך.
- X9-mcp-tool-contract.md — חוזה-envelope מקביל בכלי-ה-MCP.
- 00-constitution.md — G2, G4, G9, כלל "אין בליעה שקטה" (§6).
- web-ui/next.config.ts, client.ts, types.ts, sse.ts.