feat(mcp): ניקוי הודעת השגיאה, כיווניות, מודאל לכוונה, ובאנר "מאז הביקור האחרון" ב-/admin/mcp

Merged
AM
1mo ago
+943-35

✨ תיאור קצר

המשך ל-#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
התאים מאבדים את כפיית ה-LTRtest_technical_text_is_forced_ltr_inside_the_rtl_page
הכוונה חוזרת להיות span עם tabindextest_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.