All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 9s
גל-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>
128 lines
13 KiB
Markdown
128 lines
13 KiB
Markdown
# X6 — חוזה UI↔API וכללי-עיצוב הממשק (UI↔API Contract & Design Rules)
|
||
|
||
קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **הממשק (web-ui) וחוזה
|
||
ה-API בינו לבקאנד** — שלא היה מכוסה בספ עד כה. הוא מגדיר: (א) חוזה-הקשר פרונט↔בק (OpenAPI כ-SSoT,
|
||
מודלי-תשובה, envelope, SSE, טיפול-שגיאות); (ב) **כללי-עיצוב הממשק** — מקור-אמת יחיד ל-enums/תוויות,
|
||
helpers משותפים, וחוזה-טופס לכל סוג-מסמך. הממצאים בפועל מתועדים ב-[ui-audit.md](ui-audit.md).
|
||
|
||
> **שני סוגי invariant כאן.** UI1–UI5 הם **הנדסיים** (חוזה-API/קליינט כללי — ≥3 מקורות + סטטוס).
|
||
> UI6 (חוזה-טופס) הוא **פרויקטלי-תפעולי**, נגזר מ-[X8](X8-field-provenance.md), ומשרת
|
||
> [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai).
|
||
|
||
---
|
||
|
||
## 1. ארכיטקטורה קיימת
|
||
|
||
- **web-ui** — Next.js 16 + TS + Tailwind v4 + shadcn + TanStack Query. 13 דפים (ראה [ui-audit.md](ui-audit.md)).
|
||
- **Proxy** — [next.config.ts](../../web-ui/next.config.ts): `/api/*` → `NEXT_PUBLIC_API_ORIGIN` (ברירת-מחדל `http://127.0.0.1:8000`); `/openapi.json` → schema של ה-FastAPI.
|
||
- **לקוח** — [client.ts](../../web-ui/src/lib/api/client.ts): `apiRequest<T>` + `ApiError` + `makeQueryClient`. 18 מודולי-API.
|
||
- **טיפוסים** — [types.ts](../../web-ui/src/lib/api/types.ts) (auto-gen `openapi-typescript`, 124 operations). `npm run api:types`.
|
||
- **SSE** — [sse.ts](../../web-ui/src/lib/sse.ts): `openSSE` (progress של העלאות/עיבוד).
|
||
- **בקאנד** — [web/app.py](../../web/app.py): 143 endpoints, מונוליטי, **~60% ללא Pydantic response model**.
|
||
|
||
---
|
||
|
||
## 2. Invariants של התחום
|
||
|
||
### INV-UI1: ה-OpenAPI schema הוא ה-SSoT לחוזה — טיפוסי-לקוח נגזרים, לא ידניים-סוטים
|
||
**כלל:** חוזה ה-API מוגדר **פעם אחת** ב-OpenAPI (שמופק מהבקאנד); טיפוסי-ה-frontend **נגזרים** ממנו
|
||
(`openapi-typescript`), ואינם מתוחזקים ידנית במקביל. אין "טיפוס-מראה" מקומי שמשכפל endpoint וסוטה ממנו.
|
||
מופע של [G2](00-constitution.md#inv-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](../../web-ui/src/lib/api/cases.ts) מתעד מפורשות שה-`/api/cases` מחזיר `unknown`
|
||
ולכן מוחזק טיפוס `CaseDetail` ידני; `PracticeArea` מוגדר ב-3 מקומות עם ערכים שונים ([ui-audit.md](ui-audit.md), [gap-audit GAP-30/31](gap-audit.md)).
|
||
|
||
### INV-UI2: לכל endpoint נצרך — response model מפורש (חוזה-שלמות API)
|
||
**כלל:** כל endpoint שה-UI צורך נושא **response model מפורש** (Pydantic), כך ש-OpenAPI מפיק טיפוס אמיתי
|
||
(לא `unknown`/`object`). זהו פאֶט של [G4](00-constitution.md#inv-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](../../web/app.py) מחזירים dict חופשי → `unknown` ב-types.ts ([gap-audit GAP-30](gap-audit.md)).
|
||
|
||
### INV-UI3: envelope-תשובה ושגיאה עקבי על-פני ה-API
|
||
**כלל:** כל ה-endpoints חולקים **מבנה-תשובה ומבנה-שגיאה אחיד** (לא string-לפעמים-JSON-לפעמים). שגיאות
|
||
לפי תבנית סטנדרטית (Problem Details). מופע של [G2](00-constitution.md#inv-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](../../web/app.py) מחזיר `"לא נמצאו תוצאות."` או JSON; חלק מהכלים `{error:...}`, חלק raise ([gap-audit GAP-32](gap-audit.md), [X9 INV-TOOL1](X9-mcp-tool-contract.md)).
|
||
|
||
### INV-UI4: אין בליעת-שגיאה ב-UI
|
||
**כלל:** כל מצב-שגיאה (fetch/mutation) **מוצג או מטופל מפורשות** — error boundary ו/או טיפול ב-`error`
|
||
של `useQuery`/`useMutation`. אין כשל שקט שמשאיר את המשתמש בלי משוב. תואם כלל "אין בליעה שקטה"
|
||
([חוקה §6](00-constitution.md#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](ui-audit.md) — כרטיס-שגיאה משוכפל ×3, fallback של SSE שמסתיר כישלון כ-"completed" ([gap-audit GAP-32/33](gap-audit.md)).
|
||
|
||
### INV-UI5: חוזה-SSE/progress עם terminal states מוגדרים
|
||
**כלל:** ערוץ ה-progress (SSE) נושא **terminal states מפורשים** (completed/failed/timeout). אין הנחת-השלמה
|
||
שקטה על timeout; אי-התאמות-TTL (frontend↔backend) נמנעות. נקשר ל-freshness ([G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן)).
|
||
**מקורות:** 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](../../web-ui/src/lib/api/documents.ts) — timeout→`{status:"completed"}`; TTL 5ש' front מול 300ש' redis ([gap-audit GAP-33](gap-audit.md)).
|
||
|
||
### INV-UI6: חוזה-טופס מוצהר לכל סוג-מסמך + שיקוף מקור-המילוי
|
||
**כלל:** לכל סוג-מסמך (מסמך-תיק / פסיקה חיצונית / החלטה פנימית) יש **חוזה-טופס מוצהר** — אילו שדות,
|
||
חובה/רשות/אוטו/pending/editable — **נגזר מ-[X8](X8-field-provenance.md)**; וה-UI **משקף את מקור-המילוי**
|
||
(מסמן מה חולץ אוטומטית/ע"י-Opus מול מה שהיו"ר הזין), כדי שהיו"ר ידע מה לאמת. מופע של
|
||
[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) (שקיפות-מקור). **invariant פרויקטלי-תפעולי.**
|
||
**מקור-סמכות:** [X8-field-provenance.md](X8-field-provenance.md) (טבלת-ה-provenance); feedback היו"ר.
|
||
**אכיפה:** רכיב-טופס נגזר-X8 + אינדיקציית "מולא-ע"י-Opus"/"ממתין"/`searchable`. **כיום אין** — שדות-Opus
|
||
מוצגים כשדות-עריכה רגילים ללא סימון.
|
||
**הפרה ידועה:** [precedents/[id]/page.tsx](../../web-ui/src/app/precedents/%5Bid%5D/page.tsx) — `summary`/`headnote`/`key_quote` ללא חיווי-מקור; אין חיווי `searchable` ([gap-audit GAP-36](gap-audit.md)).
|
||
|
||
### INV-UI7: aggregate-נגזר = מקור-אמת יחיד · כל mutation מבטל כל קורא (G2 בשכבת-ה-cache)
|
||
**כלל:** ל-aggregate/מונה נגזר-שרת (למשל `/api/chair/pending`) יש **משטח-בעלים יחיד** שמריץ את השאילתה;
|
||
משטחים אחרים **מצביעים** אליו ולא מריצים מונה-מתחרה client-side. **כל mutation** שמשנה ערך הנקרא במשטח
|
||
אחר **חייב לבטל כל `queryKey` שקורא אותו** — כולל aggregators חוצי-namespace — כך שאף משטח לא יציג ערך
|
||
תקוע אחרי שינוי במשטח אחר. מקדד את **[X17 INV-IA1/IA2](X17-information-architecture.md)**; מופע של
|
||
[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) בשכבת-ה-TanStack-Query.
|
||
**מקור-סמכות:** [X17](X17-information-architecture.md) (≥3 מקורות: TanStack Query invalidation · TkDodo "deriving client state" · NN/g consistency); [ia-audit-redesign.md](../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-information-architecture.md)**.
|
||
**מקור-סמכות:** [X17](X17-information-architecture.md) (NN/g visibility-of-system-status · GOV.UK design-with-data); [ia-audit-redesign.md](../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](X8-field-provenance.md)).
|
||
|
||
הממצאים הקונקרטיים (כפילויות, הגדרות-שגויות, redundancy) ב-[ui-audit.md](ui-audit.md); התיקון — **FU-10**.
|
||
|
||
---
|
||
|
||
## 4. הפניות-אחיות
|
||
- [ui-audit.md](ui-audit.md) — audit דף-אחר-דף (13 דפים) בתבנית-ה-gap.
|
||
- [X8-field-provenance.md](X8-field-provenance.md) — מקור-מילוי-שדות (בסיס ל-INV-UI6).
|
||
- [X7-paperclip-client-params.md](X7-paperclip-client-params.md) — חוזה-ה-API שהפלאגין צורך.
|
||
- [X9-mcp-tool-contract.md](X9-mcp-tool-contract.md) — חוזה-envelope מקביל בכלי-ה-MCP.
|
||
- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים), [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש), [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai), כלל "אין בליעה שקטה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
|
||
- [web-ui/next.config.ts](../../web-ui/next.config.ts), [client.ts](../../web-ui/src/lib/api/client.ts), [types.ts](../../web-ui/src/lib/api/types.ts), [sse.ts](../../web-ui/src/lib/sse.ts).
|