feat(mcp): ניקוי הודעת השגיאה, כיווניות, מודאל לכוונה, ובאנר "מאז הביקור האחרון" ב-/admin/mcp
✨ תיאור קצר
המשך ל-#3328, אחרי שהטבלאות התמלאו בנתונים אמיתיים והתברר מה קשה לקרוא בהן: הודעת השגיאה ארוכה ורובה רעש, טקסט טכני באנגלית מוצג שבור בתוך עמוד RTL, הכוונה נחתכת ואין דרך לראות את המשך המשפט, ומספר השגיאות הכולל לא יכול לשמש איתות כי החלון נע.
📦 שינויים עיקריים
- קוד (Backend)
- בוט טלגרם
- מסד נתונים/מיגרציות
- תיעוד (docs/)
- DevOps/CI/CD
פירוט:
- ניקוי ההודעה. הודעת
ValidationErrorשל Pydantic היא ארבע שורות, ושלוש מהן רעש בהקשר של טבלה: כותרת שנושאת את שם הכלי — שכבר יש לו עמודה משלו; הזחות; ושורתFor further information visit https://errors.pydantic.dev/...שזהה בכל שגיאה מאותו סוג.summarize_validation_messageמשאיר שורה אחת לכל שדה שנדחה:lines.1 · Input should be a valid integer [input_value='9', input_type=str]. הפירסור נכשל בטוח — מה שאינו נראה כמו הודעת Pydantic מוחזר כפי שהוא, בלי לגעת. הכלל הזה חשוב יותר מהניקוי עצמו: מאז שהשער נפתח לכל סוגי השגיאות, ההודעה בתא יכולה להיות של כל ספרייה, וקיצור שמנחש פורמט היה מוחק דווקא את מה שאי אפשר לאבחן בלעדיו. - כיווניות. ההודעה והכוונה הן טקסט טכני באנגלית בתוך עמוד RTL. בלי כפיית כיווניות האלגוריתם הדו-כיווני של הדפדפן מזיז סוגריים וסימני שוויון לקצה הלא נכון, ו-
[input_value='9']מוצג שבור..mcp-ltrיושב על התא ולא על הטבלה — סדר העמודות נשאר RTL — ומשתמש ב-unicode-bidi: isolateכדי לא לדלוף לשכנים. - מודאל לכוונה המלאה. הטקסט מקוצץ בתא, והתא עצמו הוא
<button>אמיתי ולאspanעםtabindex: כך Enter ו-Space עובדים בלי JavaScript משלנו, וקורא מסך מכריז "כפתור" במקום להשמיע טקסט שלא ברור שאפשר ללחוץ עליו.Escapeסוגר ומחזיר את הפוקוס לכפתור שפתח. - באנר "X שגיאות חדשות מאז הביקור האחרון". הטבלה מציגה חלון נע של 30 יום, ולכן המספר הכולל יורד מעצמו כששגיאות ישנות נושרות — גם כשנוספו חדשות — ואינו יכול לשמש איתות. מה שכן יציב הוא חותמת הזמן של החדשה ביותר שכבר נראתה: סופרים כמה שורות חדשות ממנה, ורק אז שומרים את הנוכחית (הסדר הזה הוא שמונע מהביקור הנוכחי לאפס את עצמו). ההשוואה על
data-atהגולמי ולא על המחרוזת המוצגת, שמעוגלת לדקה.
ב-PostHog: אין שינוי. לא נוספו ולא שונו אנדפוינטים; כל השינוי כאן הוא בתצוגה.
🧪 בדיקות
-
Unit
-
Integration
-
Manual
-
tests/test_admin_mcp_page.py,tests/test_mcp_analytics_service.py,tests/test_mcp_analytics_privacy.py,tests/test_mcp_server_build.py,tests/test_doc_summary_style.py— 228 עוברים על הבסיס הנוכחי. -
tests/test_admin_mcp_tabs_browser.py— 15 עוברים ב-Chromium אמיתי, כולל חמישה חדשים: לחיצה על כוונה מקוצצת פותחת מודאל עם הטקסט המלא, כוונה עוינת נשארת טקסט גם בתוך המודאל, הבאנר שותק בביקור ראשון, הבאנר סופר רק שורות חדשות מהביקור האחרון, וכלום אינו חדש כשהביקור מאוחר מכל השגיאות.
מוטציות — 12, כל אחת מפילה בדיוק את הטסט שאמור לתפוס אותה. כל מוטציה מאמתת שהעוגן שלה קיים לפני שהיא רצה, כי מוטציה שלא הוחלה מציגה "מעבר" מזויף:
| המוטציה | הטסט שנפל |
|---|---|
| הפירסור מוותר ומחזיר את ההודעה כמו שהיא | test_a_validation_message_becomes_one_readable_line |
| התבנית מציגה את ההודעה הגולמית בלי הפילטר | test_the_error_message_cell_shows_the_cleaned_line |
| התאים מאבדים את כפיית ה-LTR | test_technical_text_is_forced_ltr_inside_the_rtl_page |
הכוונה חוזרת להיות span עם tabindex | test_a_clipped_intent_is_a_real_button_and_not_a_faked_one |
| המודאל נפתח כברירת מחדל | test_the_intent_modal_exists_and_starts_closed |
innerHTML במקום textContent (סריקת מקור) | test_the_mcp_script_never_assigns_to_innerhtml |
innerHTML במקום textContent (דפדפן אמיתי) | test_a_hostile_intent_stays_text_inside_the_modal_too |
הסרת data-at מהשורות | test_failure_rows_carry_a_raw_timestamp_for_the_new_since_last_visit_count |
| הלחיצה על הכוונה אינה מחוברת למודאל | test_clicking_a_clipped_intent_opens_a_modal_with_the_full_text |
| הביקור נרשם לפני הספירה | test_the_banner_counts_only_failures_newer_than_the_last_visit |
| הספירה מתעלמת מחותמת הזמן ומונה הכול | test_nothing_is_new_when_the_last_visit_is_after_every_failure |
| ביקור ראשון נספר כאילו הכול חדש | test_the_banner_stays_quiet_on_a_first_visit |
מדידה, לא הנחה. המבנה שהפירסור נשען עליו נמדד אמפירית על pydantic 2.12.3 בארבעה סוגי שגיאה, ולא נלקח מהתיעוד: הרינדור עצמו הוא ב-Rust ב-pydantic_core, ואין קוד פייתון לקרוא. במקביל נבדק גם מה לא קורה: הערך שיוצא מהשרת נושא ירידות שורה אמיתיות ולא \n ספרותי — הצורה הבורחת שנראתה בפלט הייתה ארטיפקט של תצוגת JSON, ולכן "ניקוי" כאן פירושו הסרת הרעש של Pydantic ולא טיפול בתווים.
📝 סוג שינוי
- feat: פיצ'ר חדש
- fix: תיקון באג (כיווניות שבורה)
✅ צ'קליסט
- הקוד עוקב אחרי הסגנון —
flake8 --select=E9,F63,F7,F82נקי, ו-ruffנקי על השורות שנוספו (הממצאים שנשארו ב-webapp/app.pyקיימים מלפני ה-PR) - בדיקות רצות ועוברות
- תיעוד עודכן
- ג'ובים חדשים — לא רלוונטי
- משתני סביבה — לא נוספו ולא השתנו
- טוקנים — לא נוספו. ה-CSS החדש משתמש בטוקנים קיימים בלבד (
--card-bg,--card-border,--glass,--glass-border,--glass-hover,--primary,--text-primary), עם fallback, לפיtheming_and_css - אין סודות/מפתחות בקוד
- אין מחיקות מסוכנות
- הודעת הקומיט תואמת Conventional Commits
-
docs/whats-new.rstעודכן - עיינתי במסמכי אתר התיעוד — נתיב:
docs/webapp/mcp-analytics.rst,docs/webapp/theming_and_css.rst,docs/doc-authoring.rst,docs/versioning-stable-anchors.rst,AI-MAP.md| המשפט: "התיעוד הוא התמצאות, לא סמכות. כל טענה עובדתית בו — התנהגות, נתיב, פרמטר, "נתמך" — קודם תאמת מול הקוד לפני שתסתמך עליה."
🧩 השפעות/סיכונים
1. הפירסור נוגע בטקסט שמוצג לאדמין. הסיכון האמיתי אינו קיצור גרוע אלא מחיקה של מידע מאבחן. לכן הכלל אינו "קצר מה שאפשר" אלא "אל תיגע במה שלא זוהה בוודאות": כל הודעה שאינה תואמת את כותרת Pydantic (N validation errors for X) מוחזרת בשלמותה, וכך גם מבנה שנראה כמו Pydantic אך אינו ניתן לפירוק. יש טסט שמוודא שהתוצאה אינה מכילה אף מילה שלא הייתה בקלט.
2. מסלול שני לטקסט שסוכן חיצוני כתב. ה-escape של Jinja מגן על התא בטבלה, אבל המודאל נבנה ב-JavaScript ולכן צריך הגנה משלו. הוא נכתב ב-textContent בלבד, ויש שתי בדיקות נפרדות: אחת סורקת את מקור הסקריפט ואוסרת innerHTML, והשנייה מזריקה כוונה עוינת ובודקת בדפדפן אמיתי שהיא נשארה צומת טקסט. "רק אדמינים רואים" אינו הגנה — אדמינים הם היעד.
3. localStorage הוא לכל דפדפן, לא לכל משתמש. מי שנכנס ממכשיר אחר יראה ספירה שמתחילה מחדש. זו החלטה מודעת: הבאנר הוא עזר תצוגה, והוא לא שווה אוסף חדש במסד או מסלול כתיבה נוסף. קריאה וכתיבה עטופות, כי localStorage זורק בגלישה פרטית — ושם הבאנר פשוט לא מופיע, בלי לגעת בשאר העמוד.
4. אין שינוי במה שנשלח ל-PostHog. השער, הכלים והאנדפוינטים לא נגעו. כל מה שכאן קורה אחרי שהנתון כבר הגיע.
🔗 קישורים
- המשך ל-#3328 (מוזג)
docs/webapp/mcp-analytics.rst→mcp-analytics-reading,mcp-analytics-fresh
🧯 סיכון / החזרה לאחור (Rollback)
הכל הפיך ובלי מיגרציה. revert של הקומיט מחזיר את ההודעה הגולמית, את הכיווניות הקודמת, את ה-span במקום הכפתור ומסיר את הבאנר. שום נתון לא נאסף ולא נמחק, ואין שינוי ב-PostHog. הערך היחיד שנשאר הוא מפתח ב-localStorage של הדפדפן, שפשוט מפסיק להיקרא.
🤖 Generated with Claude Code
https://claude.ai/code/session_018YKyee3UAYEbTKFjxdmBjs
Generated by Claude Code
Summary by Sourcery
Improve the MCP analytics admin page so technical failures and intents are easier and safer to read while providing a reliable signal for errors added since the previous visit.
New Features:
- Add a “new errors since last visit” banner to the MCP analytics page, with support for incomplete result sets.
- Provide a modal for viewing clipped agent intents in full.
Bug Fixes:
- Fix the display of technical MCP text inside the RTL admin page by enforcing isolated left-to-right rendering.
- Simplify Pydantic validation errors while preserving unrecognized messages and diagnostic input details.
Enhancements:
- Improve intent controls with native button semantics, keyboard accessibility, safe text rendering, and focus restoration.
Documentation:
- Document the MCP analytics page behavior and its new validation summaries, directionality, intent modal, and freshness indicator.
- Add the changes to the user-facing release notes.
Tests:
- Add unit and browser coverage for validation-message summarization, RTL/LTR rendering, modal behavior and text safety, freshness counting, truncated results, and browser cleanup.