Skip to content

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

Merged
amirbiron merged 3 commits into
mainfrom
claude/mcp-analytics-errors-bglgbh
Sep 6, 2026
Merged

amirbiron merged 3 commits into
mainfrom
claude/mcp-analytics-errors-bglgbh

Conversation

@amirbiron

@amirbiron amirbiron commented Sep 6, 2026 •

Copy link
Copy Markdown
Owner

✨ תיאור קצר

המשך ל-#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

Review in cubic

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.

…קור האחרון"

ארבעה תיקוני קריאוּת במסך /admin/mcp, אחרי שהטבלאות התמלאו בנתונים אמיתיים
והתברר מה קשה לקרוא בהן.

ניקוי ההודעה
הודעת ValidationError של Pydantic היא ארבע שורות, ושלוש מהן רעש בהקשר של
טבלה: כותרת שנושאת את שם הכלי — שכבר יש לו עמודה משלו; הזחות; ושורת "For
further information visit ..." שזהה בכל שגיאה מאותו סוג. summarize_validation_message
משאיר שורה אחת לכל שדה שנדחה:

    lines.1 · Input should be a valid integer [input_value='9', input_type=str]

הפירסור **נכשל בטוח**: מה שאינו נראה כמו הודעת Pydantic מוחזר כפי שהוא, בלי
לגעת. הכלל הזה חשוב יותר מהניקוי עצמו — מאז שהשער נפתח לכל סוגי השגיאות
ההודעה בתא יכולה להיות של כל ספרייה, וקיצור שמנחש פורמט היה מוחק דווקא את מה
שאי אפשר לאבחן בלעדיו. המבנה שהפירסור נשען עליו נמדד על pydantic 2.12.3 ולא
נלקח מהתיעוד: הרינדור עצמו הוא ב-Rust ב-pydantic_core, ואין קוד פייתון לקרוא.

כיווניות
ההודעה והכוונה הן טקסט טכני באנגלית בתוך עמוד RTL. בלי כפיית כיווניות
האלגוריתם הדו-כיווני מזיז סוגריים וסימני שוויון לקצה הלא נכון,
ו-[input_value='9'] מוצג שבור. .mcp-ltr יושב על התא ולא על הטבלה — סדר
העמודות נשאר RTL — ומשתמש ב-unicode-bidi: isolate כדי לא לדלוף לשכנים.

מודאל לכוונה המלאה
הטקסט מקוצץ בתא, והתא עצמו הוא <button> אמיתי ולא span עם tabindex: כך Enter
ו-Space עובדים בלי JavaScript משלנו, וקורא מסך מכריז "כפתור" במקום להשמיע
טקסט שלא ברור שאפשר ללחוץ עליו. Escape סוגר ומחזיר את הפוקוס לכפתור שפתח.
הטקסט נכתב ב-textContent — זהו מסלול שני לאותו טקסט שסוכן חיצוני כתב, וה-escape
של Jinja אינו מגן עליו.

באנר "X שגיאות חדשות מאז הביקור האחרון"
הטבלה מציגה חלון נע של 30 יום, ולכן המספר הכולל יורד מעצמו כששגיאות ישנות
נושרות — גם כשנוספו חדשות — ואינו יכול לשמש איתות. מה שכן יציב הוא חותמת הזמן
של החדשה ביותר שכבר נראתה: סופרים כמה שורות חדשות ממנה, ורק אז שומרים את
הנוכחית. ההשוואה היא על data-at הגולמי ולא על המחרוזת המוצגת, שמעוגלת לדקה.
בביקור ראשון הבאנר שותק — אז הכול "חדש". localStorage חסום פשוט לא מציג אותו.

בדיקות
12 מוטציות, כל אחת מאמתת שהעוגן שלה קיים לפני שהיא רצה, וכל אחת מפילה בדיוק
את הטסט שאמור לתפוס אותה: ויתור על הפירסור, הצגת ההודעה הגולמית, הסרת ה-LTR,
חזרה ל-span, מודאל פתוח כברירת מחדל, innerHTML במקום textContent (בשני
מסלולים), הסרת data-at, ניתוק הלחיצה, רישום הביקור לפני הספירה, ספירה שמתעלמת
מחותמת הזמן, וביקור ראשון שנספר כאילו הכול חדש.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018YKyee3UAYEbTKFjxdmBjs
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @amirbiron, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 12 hours and 9 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 15 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 4be06030-0ba0-44fc-8001-e7d6bed650eb

📥 Commits

Reviewing files that changed from the base of the PR and between c16bbd2 and c163637.

📒 Files selected for processing (8)
  • docs/webapp/mcp-analytics.rst
  • docs/whats-new.rst
  • services/mcp_analytics_service.py
  • tests/test_admin_mcp_page.py
  • tests/test_admin_mcp_tabs_browser.py
  • tests/test_mcp_analytics_service.py
  • webapp/app.py
  • webapp/templates/admin_mcp.html

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

🧯 Dangerous deletes guard report

Policy: see .cursorrules — dangerous deletions are blocked unless wrapped safely.

Summary:

  • Flagged findings (blocking): 0
    0
  • Excluded matches (not blocking): 15
  • Total matches (all files): 129

Flagged findings (file:line:snippet):
(none)

Excluded matches (by path pattern)
./webapp/static/js/md_preview.bundle.js.map:4:  "sourcesContent": ["// Markdown-it plugin to render GitHub-style task lists; see\n//\n// https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments\n// https://github.com/blog/1825-t … [truncated]
./docs/DOCUMENTATION_GUIDE.md:453:rm -rf _build
./docs/Makefile:24:	rm -rf $(BUILDDIR)
./Dockerfile:42:    rm -rf /var/lib/apt/lists/*
./Dockerfile:121:    rm -rf /var/lib/apt/lists/*
./node_modules/katex/package.json:153:    "build": "rimraf dist/ && mkdirp dist && cp README.md dist && rollup -c --failAfterWarnings && webpack && node update-sri.js package dist/README.md",
./node_modules/katex/src/fonts/Makefile:139:	rm -rf pfa ff otf ttf woff woff2
./node_modules/mermaid/dist/mermaid.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values for tr … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm/chunk-2M32CCKP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence d … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequen … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs:1:var r={name:"mermaid",version:"11.12.0",description:"Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graph … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.core/chunk-KS23V3DP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence  … [truncated]
./node_modules/mermaid/dist/mermaid.min.js:1524:`,"getStyles"),c1e=RQe});var h1e={};dr(h1e,{diagram:()=>NQe});var NQe,f1e=N(()=>{"use strict";$ge();a1e();l1e();u1e();NQe={parser:Fge,db:n1e,renderer:o1e,styles:c1e}});var m1e,g1e=N(()=>{"use  … [truncated]
./node_modules/mermaid/dist/mermaid.min.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values fo … [truncated]
./README.md:842:find . -name "__pycache__" -exec rm -rf {} +

@sourcery-ai

sourcery-ai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR refines /admin/mcp entirely at the presentation layer: it safely summarizes recognizable Pydantic errors, isolates technical text in the RTL UI, adds an accessible full-intent modal, and signals failures newer than the last browser visit via localStorage, with documentation and unit/browser coverage.

Sequence diagram for the admin MCP failure presentation

sequenceDiagram
    participant Admin
    participant Browser
    participant AdminMCP as Admin MCP page
    participant Jinja
    participant Service as MCP analytics service
    participant Storage as localStorage

    Admin->>Browser: Open /admin/mcp
    Browser->>AdminMCP: Request page
    AdminMCP->>Service: Load failure rows
    Service-->>AdminMCP: Rows with raw error messages and timestamps
    AdminMCP->>Jinja: Apply validation_summary
    Jinja->>Service: summarize_validation_message(message)
    Service-->>Jinja: Cleaned Pydantic text or original message
    Jinja-->>Browser: Render RTL table with LTR cells and data-at
    Browser->>Storage: readLastSeen()
    Storage-->>Browser: Previous newest timestamp
    Browser->>Browser: Count rows newer than previous timestamp
    Browser-->>Admin: Show fresh-failures banner when count > 0
    Browser->>Storage: writeLastSeen(newest timestamp)
Loading

Sequence diagram for opening the full MCP intent modal

sequenceDiagram
    actor Admin
    participant Browser
    participant IntentButton as Intent button
    participant Modal as Intent modal

    Admin->>IntentButton: Click, Enter, or Space
    IntentButton->>Browser: click event
    Browser->>Modal: openModal(button)
    Modal->>Modal: textContent = button title or text
    Modal-->>Admin: Show full intent safely as text
    Admin->>Browser: Press Escape or click close
    Browser->>Modal: closeModal()
    Modal-->>IntentButton: Restore focus
Loading

File-Level Changes

Change Details Files
Add fail-safe Pydantic validation-message summarization before rendering MCP failure rows.
  • Recognize the measured Pydantic error format and emit one readable line per rejected field.
  • Remove only known noise while preserving diagnostic input details.
  • Return non-strings, non-Pydantic messages, and malformed structures unchanged.
  • Expose the summarizer through a Jinja template filter.
services/mcp_analytics_service.py
webapp/app.py
webapp/templates/admin_mcp.html
tests/test_mcp_analytics_service.py
tests/test_admin_mcp_page.py
Improve readability and accessibility of technical MCP content in the RTL admin page.
  • Apply isolated LTR direction to error-message and intent cells without reversing table column order.
  • Replace the clipped intent span with a native button supporting keyboard activation and accessible labeling.
  • Add a modal for viewing full intent text with Escape, backdrop, close-button handling, and focus restoration.
  • Write agent-controlled intent text with textContent to prevent HTML execution.
webapp/templates/admin_mcp.html
tests/test_admin_mcp_page.py
tests/test_admin_mcp_tabs_browser.py
Add a client-side indicator for failures added since the administrator’s previous visit.
  • Attach raw failure timestamps to table rows and compare them against the latest stored timestamp.
  • Keep the first visit quiet, count only newer rows, and update localStorage after counting.
  • Gracefully tolerate localStorage failures and avoid changing backend, database, or PostHog behavior.
webapp/templates/admin_mcp.html
tests/test_admin_mcp_tabs_browser.py
Document the updated MCP analytics presentation and styling behavior.
  • Describe the validation summary, RTL/LTR handling, intent modal, and freshness banner.
  • Record the feature in the project’s whats-new documentation.
docs/webapp/mcp-analytics.rst
docs/whats-new.rst

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

github-actions Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

⏱️ Performance report

(No performance test durations collected. Mark tests with @pytest.mark.performance.)

@github-actions

github-actions Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

📖 Documentation Preview

The documentation has been built successfully!

To view locally:

  1. Download the artifacts
  2. Extract the zip file
  3. Open index.html in your browser

@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread webapp/templates/admin_mcp.html Outdated
Comment thread webapp/templates/admin_mcp.html
Comment thread services/mcp_analytics_service.py Outdated
Comment thread services/mcp_analytics_service.py Outdated
Comment thread tests/test_admin_mcp_tabs_browser.py Outdated
Comment thread tests/test_admin_mcp_tabs_browser.py Outdated
…י ההודעה

שישה ממצאי ריוויו, כולם אמיתיים. שלושה מהם אותו שורש: פעולה גורפת על טקסט
שמכיל גם רעש וגם ערך, או הצהרה שרחבה יותר מהנתונים שמאחוריה.

ניקוי ההודעה — שני באגים
``\r\n`` השאיר ``\r`` בסוף כל שורה, ואז התבנית של שורת התיעוד — שנגמרת
ב-``$`` — כבר לא תפסה, והקישור לתיעוד של Pydantic שרד בתא. הפיצול עובר עכשיו
דרך ``\r\n|\r|\n``, ולא דרך ``splitlines()`` שמפצל גם על ``\x0b`` ו-``
``
שיכולים לשבת בתוך ``input_value``.

והשני: ניקוי הסוגריים הריקות שנשארו מהסרת ``type=`` מחק גם ``[]`` שהיה
**בתוך** הערך, ו-``input_value=[]`` הפך ל-``input_value=`` — כלומר נמחק בדיוק
מה שמפריד בין "הסוכן שלח רשימה ריקה" ל"הסוכן לא שלח כלום". במקום שתי פעולות
יש עכשיו ביטוי אחד עם קבוצה אופציונלית, ומה שמגן הוא שהתאמה נעשית
שמאלית-ראשונה: קבוצת הסוגריים האמיתית היא הראשונה בשורה, ולכן ``[type=...]``
שיושב בתוך הערך לעולם אינו נתפס במקומה.

בדרך תוקן גם ניסיון ביניים שלי: עיגנתי את התבנית ל-``$`` והסברתי שזה מה שמגן.
מוטציה הראתה שאף טסט לא מבחין — ובצדק, כי ההגנה מגיעה מסדר ההתאמה ולא
מהעיגון, ובמקרה קצה העיגון היה בוחר דווקא קבוצה מאוחרת יותר. הוסר.

המודאל — ``<dialog>`` נייטיב
הגרסה הידנית הייתה ``div`` שמוצג ומוסתר, ולכן ``Shift+Tab`` מכפתור הסגירה
יצא אל התוכן שמאחור בזמן שהוא חסום ויזואלית. הפתרון אינו להוסיף לכידת ``Tab``
משלנו אלא להשתמש במה שהדפדפן כבר נותן: ``showModal()`` נועל את הפוקוס, סוגר
ב-``Escape``, מחזיר את הפוקוס לכפתור שפתח, ומצייר את הרקע ב-``::backdrop``.
המעבר **מחק** קוד — מטפל ``Escape`` ידני, ניהול ``lastFocused``, ושכבת רקע —
במקום להוסיף. נמדד בכרומיום אמיתי לפני שנבחר: ``Shift+Tab`` מתחלף בין הכפתור
לדיאלוג ואינו מגיע החוצה, והפוקוס חוזר לבד. תמיכה: Baseline "widely available"
מאז מרץ 2022, ‏96% גלובלית, iOS Safari 15.4+, Samsung Internet 4+.

הבאנר — "לפחות" כשהטבלה חתוכה
הבאנר סופר שורות ב-DOM, והשאילתה מוגבלת ל-25 שורות. כשכל השורות שהוצגו חדשות
ויש עוד מעבר לתקרה, המספר הוא **רצפה** ולא ספירה — והצהרה מדויקת עליו היא
המצאת נתון. ``has_more`` עובר לדפדפן ב-``data-has-more``, והניסוח משתנה
ל"לפחות X". הספירה לא השתנתה, כי אי אפשר לספור מה שהשאילתה לא החזירה: ספירה
אמיתית הייתה דורשת לשלוח לשרת את חותמת הזמן ששמורה בדפדפן, בקשה נוספת עבור
שורת טקסט.

הטסטים — מרים דפדפן במקום אחד
``_fresh_page`` היה עותק שלישי של אותן שלושים שורות הרמה שכבר חיו בפיקסצ'ר,
ובנוסף הניקוי שלו ישב אצל הקורא — ולכן נפילה של ``goto`` דילגה עליו לגמרי
והשאירה דפדפן וסשן Playwright תלויים. שניהם נפתרו באותו צעד: מנהל הקשר יחיד
``_browser_page`` שהפיקסצ'ר עצמו מאציל אליו, עם ה-``finally`` בפנים. יש טסט
שמפנה את ``goto`` לפורט סגור ומוודא שהסגירה בכל זאת רצה.

מוטציות: תשע, כל אחת מפילה בדיוק את הטסט שאמור לתפוס אותה.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018YKyee3UAYEbTKFjxdmBjs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="webapp/templates/admin_mcp.html">

<violation number="1" location="webapp/templates/admin_mcp.html:799">
P3: The backdrop-click close uses an axis-aligned bounding box with inclusive bounds, but the dialog has `border-radius: 16px`. A click in the small rounded-corner band is inside the rect yet visually on the dimmed backdrop, so it won't close the dialog. Minor; optionally use the rect plus a check against the dialogs padding/content before treating it as an inside click.</violation>
</file>

<file name="services/mcp_analytics_service.py">

<violation number="1" location="services/mcp_analytics_service.py:177">
P2: חומרה: 5/10. כשמסר Pydantic מכיל `[type=...]` בתוך טקסט השגיאה לפני המטא־דאטה, התבנית מסירה את המופע המוקדם ומשאירה את קבוצת `[type=...]` האמיתית. עגן את התבנית לסוף שורת הפירוט כדי להסיר רק את קבוצת המטא־דאטה.</violation>
</file>

<file name="tests/test_admin_mcp_tabs_browser.py">

<violation number="1" location="tests/test_admin_mcp_tabs_browser.py:228">
P3: The new `_browser_page` docstring claims it is "המקום היחיד שמרים דפדפן בקובץ הזה" (the only place in this file that launches a browser), but `test_an_intent_that_looks_like_markup_is_never_a_live_element` still launches its own browser directly at line 413 with `with sync_playwright() as pw: pw.chromium.launch(...)`. That test also keeps its cleanup in a caller-side `finally` that begins only *after* `goto`/`evaluate`, so a navigation failure there still leaks the browser/context — the exact leak this refactor was built to eliminate. Correct the docstring or convert that test to `_browser_page` so the claim and the safety guarantee are true. חומרת הממצא: 3 (ניקיון/תחזוקה).</violation>

<violation number="2" location="tests/test_admin_mcp_tabs_browser.py:704">
P3: `test_a_failed_page_load_still_shuts_the_browser_down` forces the navigation failure by pointing at hardcoded port 9 and expects `goto` to raise, truncating the check with a broad `with pytest.raises(Exception):`. If a discard/other service answers on port 9 or the OS blackholes it, `goto` may succeed (test fails spuriously) or hang up to the 30s default timeout instead of failing fast with ERR_CONNECTION_REFUSED. The broad exception also passes if an unrelated setup step fails, so the test does not prove a page-load failure actually happened. Prefer binding a socket to an ephemeral port, closing it, and pointing `goto` there (least likely to be grabbed), and asserting the specific navigation failure. חומרת הממצא: 3 (יציבות הטסט).</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

#: לסוגריים שנשארו ריקות — ושתיהן החטיאו: הראשונה דילגה על הקבוצה האמיתית
#: (שאחרי ``type`` יש בה פסיק) ונחתה על זו שבתוך הערך, והשנייה מחקה גם
#: ``input_value=[]`` והפכה אותו ל-``input_value=``.
_PYDANTIC_BRACKETS = re.compile(r"\s*\[type=[^,\]]+(?:,\s*(?P<rest>.*))?\]")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: חומרה: 5/10. כשמסר Pydantic מכיל [type=...] בתוך טקסט השגיאה לפני המטא־דאטה, התבנית מסירה את המופע המוקדם ומשאירה את קבוצת [type=...] האמיתית. עגן את התבנית לסוף שורת הפירוט כדי להסיר רק את קבוצת המטא־דאטה.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/mcp_analytics_service.py, line 177:

<comment>חומרה: 5/10. כשמסר Pydantic מכיל `[type=...]` בתוך טקסט השגיאה לפני המטא־דאטה, התבנית מסירה את המופע המוקדם ומשאירה את קבוצת `[type=...]` האמיתית. עגן את התבנית לסוף שורת הפירוט כדי להסיר רק את קבוצת המטא־דאטה.</comment>

<file context>
@@ -157,10 +157,34 @@
+#: לסוגריים שנשארו ריקות — ושתיהן החטיאו: הראשונה דילגה על הקבוצה האמיתית
+#: (שאחרי ``type`` יש בה פסיק) ונחתה על זו שבתוך הערך, והשנייה מחקה גם
+#: ``input_value=[]`` והפכה אותו ל-``input_value=``.
+_PYDANTIC_BRACKETS = re.compile(r"\s*\[type=[^,\]]+(?:,\s*(?P<rest>.*))?\]")
+
+
</file context>
Suggested change
_PYDANTIC_BRACKETS = re.compile(r"\s*\[type=[^,\]]+(?:,\s*(?P<rest>.*))?\]")
_PYDANTIC_BRACKETS = re.compile(r"\s*\[type=[^,\]]+(?:,\s*(?P<rest>.*))?\]\s*$")

modal.addEventListener('click', function (event) {
if (event.target !== modal) { return; }
var box = modal.getBoundingClientRect();
var inside = event.clientX >= box.left && event.clientX <= box.right

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The backdrop-click close uses an axis-aligned bounding box with inclusive bounds, but the dialog has border-radius: 16px. A click in the small rounded-corner band is inside the rect yet visually on the dimmed backdrop, so it won't close the dialog. Minor; optionally use the rect plus a check against the dialogs padding/content before treating it as an inside click.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webapp/templates/admin_mcp.html, line 799:

<comment>The backdrop-click close uses an axis-aligned bounding box with inclusive bounds, but the dialog has `border-radius: 16px`. A click in the small rounded-corner band is inside the rect yet visually on the dimmed backdrop, so it won't close the dialog. Minor; optionally use the rect plus a check against the dialogs padding/content before treating it as an inside click.</comment>

<file context>
@@ -767,34 +770,35 @@ <h2 id="mcpIntentModalTitle">מה הסוכן ניסה לעשות</h2>
+    modal.addEventListener('click', function (event) {
+      if (event.target !== modal) { return; }
+      var box = modal.getBoundingClientRect();
+      var inside = event.clientX >= box.left && event.clientX <= box.right
+        && event.clientY >= box.top && event.clientY <= box.bottom;
+      if (!inside) { modal.close(); }
</file context>

try:
with pytest.raises(Exception):
# פורט 9 סגור, ולכן ``goto`` נכשל אחרי ``launch``.
with _browser_page(("http://127.0.0.1:9", session_cookie)):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: test_a_failed_page_load_still_shuts_the_browser_down forces the navigation failure by pointing at hardcoded port 9 and expects goto to raise, truncating the check with a broad with pytest.raises(Exception):. If a discard/other service answers on port 9 or the OS blackholes it, goto may succeed (test fails spuriously) or hang up to the 30s default timeout instead of failing fast with ERR_CONNECTION_REFUSED. The broad exception also passes if an unrelated setup step fails, so the test does not prove a page-load failure actually happened. Prefer binding a socket to an ephemeral port, closing it, and pointing goto there (least likely to be grabbed), and asserting the specific navigation failure. חומרת הממצא: 3 (יציבות הטסט).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_admin_mcp_tabs_browser.py, line 704:

<comment>`test_a_failed_page_load_still_shuts_the_browser_down` forces the navigation failure by pointing at hardcoded port 9 and expects `goto` to raise, truncating the check with a broad `with pytest.raises(Exception):`. If a discard/other service answers on port 9 or the OS blackholes it, `goto` may succeed (test fails spuriously) or hang up to the 30s default timeout instead of failing fast with ERR_CONNECTION_REFUSED. The broad exception also passes if an unrelated setup step fails, so the test does not prove a page-load failure actually happened. Prefer binding a socket to an ephemeral port, closing it, and pointing `goto` there (least likely to be grabbed), and asserting the specific navigation failure. חומרת הממצא: 3 (יציבות הטסט).</comment>

<file context>
@@ -631,13 +621,89 @@ def test_the_banner_counts_only_failures_newer_than_the_last_visit(live_server):
+    try:
+        with pytest.raises(Exception):
+            # פורט 9 סגור, ולכן ``goto`` נכשל אחרי ``launch``.
+            with _browser_page(("http://127.0.0.1:9", session_cookie)):
+                pass  # pragma: no cover
+    finally:
</file context>


@contextlib.contextmanager
def _browser_page(live_server, local_storage=None, viewport=None):
"""פותח את העמוד בהקשר דפדפן נקי — **המקום היחיד** שמרים דפדפן בקובץ הזה.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new _browser_page docstring claims it is "המקום היחיד שמרים דפדפן בקובץ הזה" (the only place in this file that launches a browser), but test_an_intent_that_looks_like_markup_is_never_a_live_element still launches its own browser directly at line 413 with with sync_playwright() as pw: pw.chromium.launch(...). That test also keeps its cleanup in a caller-side finally that begins only after goto/evaluate, so a navigation failure there still leaks the browser/context — the exact leak this refactor was built to eliminate. Correct the docstring or convert that test to _browser_page so the claim and the safety guarantee are true. חומרת הממצא: 3 (ניקיון/תחזוקה).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_admin_mcp_tabs_browser.py, line 228:

<comment>The new `_browser_page` docstring claims it is "המקום היחיד שמרים דפדפן בקובץ הזה" (the only place in this file that launches a browser), but `test_an_intent_that_looks_like_markup_is_never_a_live_element` still launches its own browser directly at line 413 with `with sync_playwright() as pw: pw.chromium.launch(...)`. That test also keeps its cleanup in a caller-side `finally` that begins only *after* `goto`/`evaluate`, so a navigation failure there still leaks the browser/context — the exact leak this refactor was built to eliminate. Correct the docstring or convert that test to `_browser_page` so the claim and the safety guarantee are true. חומרת הממצא: 3 (ניקיון/תחזוקה).</comment>

<file context>
@@ -195,11 +217,28 @@ def live_server():
+
+@contextlib.contextmanager
+def _browser_page(live_server, local_storage=None, viewport=None):
+    """פותח את העמוד בהקשר דפדפן נקי — **המקום היחיד** שמרים דפדפן בקובץ הזה.
+
+    ``local_storage`` הוא הערך של ``mcpFailuresLastSeenAt``; ``None`` מוחק
</file context>

@amirbiron
amirbiron merged commit a559685 into main Sep 6, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants