fix(webapp): קישורי תפריט פנימיים במסמכי Markdown לא גללו לסעיף - #3201
Conversation
שלושה כלים חדשים מעל קולקציית sticky_notes הקיימת של הוובאפ: - codekeeper_list_notes — פתקי הקובץ (קריאה טהורה, בלי ה-backfill של ה-GET בוובאפ) - codekeeper_create_note — פתק חדש על קובץ קיים; line מעגן לשורת מקור, בלעדיו הפתק נוצר צף עם sentinel __floating__ מפורש (אחרת ה-JS מעגן אוטומטית לשורה הקרובה ודורס את הכוונה) - codekeeper_update_note — עדכון חלקי לפי note_id, עם annotation נפרד (destructive+idempotent) כי פתק נדרס במקום ואין לו היסטוריית גרסאות עקרונות: - כותבים בדיוק את סכמת הוובאפ (scope_id מ-sticky_notes_scope.make_scope_id הקנוני, ברירות מחדל בפריטת הקליינט) — פתק מה-MCP מופיע מיד ב-UI - זהות תמיד מהטוקן; יצירה/עדכון מאחורי require_write - תוכן >5000 תווים נדחה בשגיאה (לא קיטום שקט — סוכן לא ישים לב לאובדן) - מגן אנטי-לולאה: עד 200 פתקים לקובץ ביצירה - אינדקס (user_id, scope_id) שחסר היום נוצר lazy/best-effort — משרת גם את שאילתת ה-scope הזהה של הוובאפ - בלי מחיקה ובלי תזכורות (non-goal מתועד); אפס שינויי קוד בוובאפ — דיפלוי לשירות ה-MCP בלבד בדיקות: 19 טסטים הרמטיים חדשים (סניטציה, ולידציות, sentinel, צבעים, scope filter מול make_scope_id האמיתי, סריאליזציה) + עדכון טסט הרישום. 188 טסטי MCP עוברים; black/flake8/doc8 נקיים; Sphinx נבנה עם 0 אזהרות. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
הוספת פריט "תיאור" בתפריט 3-הנקודות בעמוד הצפייה בקובץ (view_file), ראשון ברשימה, שפותח מודאל קטן לעריכת שדה התיאור — חוסך את הכניסה לעריכת קובץ מלאה רק כדי לשנות תיאור. "נעץ לדשבורד" עלה לשני, "שתף" לשלישי. - משתמש ב-endpoint הקיים POST /api/file/<id>/quick-update שמעדכן את התיאור in-place (מטא-דאטה, בלי גרסה חדשה) — אין קוד שרת חדש - מודאל בדפוס מודאל השיתוף הקיים (Escape, לחיצה על הרקע, טוסט הצלחה), עם textarea ומונה תווים (עד 500, תואם למגבלת ה-endpoint) - עדכון חי של התצוגה מתחת לשם ושל תווית התפריט בלי רענון עמוד - זמין לכל הקבצים (התיאור הוא מטא-דאטה אוניברסלי) - אין בעיית מודאל-בתוך-מודאל: התפריט (dropdown) נסגר לפני שהמודאל נפתח, בדיוק כמו "שתף קובץ" הקיים - שינוי template בלבד; דורש דיפלוי לוובאפ בדיקות: Jinja parse תקין, תחביר JS תקין; אין קוד Python שהשתנה. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
באג: אחרי deploy של תבנית, משתמשים שצפו בקובץ לאחרונה המשיכו לראות את
הגרסה הישנה (בלי אלמנטים חדשים) הרבה זמן; אחרים כן ראו חדש. השורש:
ה-ETag של /file/<id> ו-/md/<id> חושב מנתוני הקובץ בלבד (updated_at/תוכן/
version/theme) בלי גרסת ה-deploy — אז קובץ שלא נערך החזיר ETag זהה בין
deploys → הדפדפן קיבל 304 והציג HTML ישן מה-cache. תורם שני: מסלול
If-Modified-Since מבוסס updated_at החזיר 304 בנפרד.
תיקון שורשי (webapp/app.py + cache_manager.py):
- _compute_file_etag כולל עכשיו את _STATIC_VERSION (גרסת deploy) — כל
deploy מבטל ETags ישנים. קורא מרכזי אחד ⇒ מכסה view_file וגם md_preview.
- שלושת מסלולי If-Modified-Since מכבדים RFC 7232 §3.3: מדלגים כשקיים
If-None-Match (אחרת 304 מיושן גם אחרי שה-ETag השתנה).
- מפתח ה-cache צד-שרת של md_preview כולל את גרסת ה-deploy (אחרת HTML
מרונדר ישן מוגש עד 30 דק').
- נלווה: invalidate_file_related מבטל עכשיו גם את המפתח האמיתי
web:md_preview:user:*:{file_id}:* (היה prefix שגוי — עריכת קובץ לא
ביטלה את cache ה-md שלו).
ב-Render גרסת ה-deploy מגיעה מ-RENDER_GIT_COMMIT (משתנה לכל commit).
אימות: py_compile + flake8 + 9 טסטי cache/invalidation עוברים. את לוגיקת
ה-ETag אימתתי בבידוד (הרצת הפונקציה האמיתית: גרסת deploy משנה את ה-ETag,
יציב לאותו קלט, ותוכן עדיין משנה). טסט יחידה שמייבא webapp.app לא ישים —
טסטי ה-webapp מדולגים ב-CI ("צינור הבוט") ו-flask לא זמין בסביבה.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
מתחת לכל קובץ באוסף שיש לו תיאור מופיע אייקון ℹ️; לחיצה פותחת מודאל קטן (קריאה בלבד) עם התיאור — בלי להיכנס לקובץ. - Backend: get_collection_items מצרף עכשיו את ה-description של הקובץ לכל פריט, דרך אותו batch שכבר מחשב is_file_active (הרחבת ה-projection ל-file_name+description) — בלי N+1 ובלי שדות כבדים (Smart Projection נשמר; description ≤500 תווים). קובץ בלי תיאור/לא-פעיל ⇒ "". - Frontend: אייקון ℹ️ ב-.collection-card__meta רק אם יש תיאור; openDescriptionModal בדפוס .collection-modal הקיים (Escape/רקע סוגרים, textContent — בטוח מ-XSS). - מצב workspace לא נכלל בשלב זה. בדיקות: 3 טסטי enrichment חדשים (fakes שתומכים ב-projection) + 49 טסטי collections_manager עוברים; node --check ל-JS; py_compile. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
אייקון התיאור בכרטיס קובץ עבר משורת ה-meta אל צד שם הקובץ (משמאל, RTL) באותה שורה, עם רווח. כשהשם ארוך ונשבר לשתי שורות — האייקון "קופץ" לשורת 4 הכפתורים, משמאל להם (פונקציה layoutDescIcons שמנצלת את זיהוי is-wrapped של autoFitText, בסדר reset→autoFit→move כדי למנוע oscillation). ארכיון לאוספים: שדה is_archived חדש (נפרד מ-is_active), toggle 🗄️ "הצג ארכיון" בסיידבר, וכפתור ארכב/שחזר בכותרת האוסף (מגודר ל-non-workspace). list_collections קיבל archived_only ו-include_archived; ברירת המחדל מחריגה מאורכבים (ne:True מכסה גם אוספים ישנים ללא השדה). הקאש כבר מבחין לפי querystring, וה-PUT מנקה את שתי התצוגות. הגיבוי האישי משתמש ב-include_archived=True כדי לא לפספס אוספים בארכיון. - database/collections_manager.py: doc-build, allow-list, list filter, serializer, index+backfill - webapp/collections_api.py: פרמטר archived ב-GET, דילוג על "שולחן עבודה" בתצוגת ארכיון - webapp/static/js/collections.js + collections.css: אייקון, toggle, כפתורי ארכב/שחזר - services/personal_backup_service.py: include_archived=True בגיבוי ובבדיקת כפילות - tests/test_collections_archive.py: ארכוב/שחזור/include/legacy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
main קלט את אייקון-התיאור הבסיסי דרך squash (#3191), ולכן נוצר קונפליקט מול העבודה החדשה בענף (הזזת האייקון + ארכיון). מיזגתי את main לענף ופתרתי: - collections.css: נשמרה הגרסה שלי (superset — בסיס .desc-info + flex + Slot 2 + toggle ארכיון). - collections.js: הוחזר לגרסה שלי כדי למנוע שכפול של כפתור התיאור שהמיזוג האוטומטי יצר. תוצאת המיזוג זהה בדיוק לעבודה שכבר נבדקה (36 טסטים ירוקים). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
לפי theming_and_css.rst אסורים צבעים קשיחים בקבצי רכיבים — רק var(--token). שתי חריגות הומרו: - מודאל "ערוך תיאור" (view_file.html): הרקע הכהה הקבוע (#1f2a44, #fff, rgba לבנים, focus בצבע primary קשיח) הוחלף בטוקנים סמנטיים — bg-secondary/tertiary, text-primary/secondary/muted, glass-border, primary; ה-scrim וה-shadow קיבלו טוקן-רכיב עם fallback (var(--modal-backdrop, ...), var(--solid-surface-shadow, ...)). כך המודאל מקבל את צבעי הערכה גם בערכות בהירות (rose-pine-dawn, classic). - כפתור "הצג ארכיון" במצב לחוץ (collections.css): rgba לבנים קשיחים הוחלפו בטוקני glass קיימים (glass-hover/glass-border/glass). מודאל ה-ℹ️ באוספים נבדק ונמצא תקין (יורש var(--collections-modal-*)) — ללא שינוי. מודאל השיתוף הסמוך הוא legacy קיים ולא נכלל (חוב נפרד). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…קט collections.css main קלט את הארכיון והזזת האייקון דרך squash (#3192), והענף ממשיך עם תיקון הטוקנים (991b1ad) שנגע באותה שורה. נשמרה גרסת הטוקנים של #toggleArchivedBtn (var(--glass-*)) — ההבדל היחיד מול main, שהוחלף בכוונה. תוצאת המיזוג זהה לחלוטין לעץ שכבר נבדק. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
באג שורש: המחרוזת `סה""כ` פיצלה f-strings לשתי מחרוזות סמוכות — השנייה (בלי
קידומת f) הציגה placeholders כטקסט מילולי (למשל "{len(items)}"). תוקן ב-3 מקומות
(handlers/documents.py, conversation_handlers.py, handlers/save_flow.py) ע"י
מעבר ל-f-string רציף אחד עם מרכאות בודדות. כך "✅ נוסף: X (סה"כ N קבצים)" מציג
את המספר בפועל, וכן כותרת רשימת ה-ZIP השמורים ומסך איסוף הקוד הארוך.
פיצ'ר: שלב בחירת שם ל-ZIP. אחרי "✅ סיום" הבוט מבקש שם (או "⏭️ דלג" לשם אוטומטי):
- conversation_handlers.py: helper משותף finalize_zip_create + _cleanup_zip_state;
zip_create_finish מציב awaiting_zip_name ומבקש שם; callback חדש zip_create_skip_name.
- main.py: hook בראש handle_text_message שתופס את השם (נבדק ראשון כדי שלא ייבלע/ייחשב קוד).
- שם מנוקה דרך TextUtils.clean_filename + סיומת .zip; fallback ל-my-files-<timestamp>.zip.
טסט: חיזוק test_handle_document_collects_zip_items לאימות שהמספר מוצג בפועל (הגנת רגרסיה).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…hread, מסלול ביטול מענה ל-review findings ב-PR #3194: - ניקוי שם רשומת ZIP (utils.safe_zip_entry_name): basename בלבד, דחיית נתיב מוחלט/ מקונן ו-"."/".." — הגנת Zip-Slip. משמש בבניית הארכיון במקום השם הגולמי. - בניית ה-ZIP חולצה ל-utils.build_zip_bytes (טהור) ורצה תחת asyncio.to_thread כדי לא לחסום את לולאת האירועים (בהתאם לכלל ה-Performance ב-CLAUDE.md). - אכיפת מגבלות איסוף (ZIP_CREATE_MAX_FILES=50, ZIP_CREATE_MAX_TOTAL_BYTES=45MB) בזמן צבירת הקבצים ב-handlers/documents.py, עם הגנה כפולה גם בשלב הבנייה. - מסלול ביטול במצב "המתנה לשם": zip_create_cancel עובר דרך _cleanup_zip_state (מנקה גם awaiting_zip_name), ונוסף כפתור "❌ ביטול" למקלדת בקשת השם — כך שמשתמש שמתחרט לא ישלח ארכיון בטעות בהודעת טקסט כלשהי. - טסטים: tests/test_zip_bundle_utils.py (ניקוי שמות + מגבלות, נבדק ע"י פענוח ה-ZIP). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…ון docstring מענה ל-review (סבב 2) ב-PR #3194: - handlers/documents.py: בדיקות מגבלת ה-ZIP (מספר קבצים + גודל לפי document.file_size) הוזזו לפני get_file()/download_to_memory() — לא מורידים לזיכרון קובץ שנדחה מראש. בדיקת len(raw) נשמרה כאימות סופי לפער אפשרי מול הגודל המוצהר. - utils.build_zip_bytes: מניעת שמות רשומה כפולים — הראשון נשמר, הבאים מקבלים סיומת ממספרת (x.txt, x_2.txt) עם שמירת הסיומת. - utils: תיקון docstring (שורה ריקה אחרי רשימת ה-bullets) — מבטל אזהרת docutils/RTD. - tests: חיזוק test_...skips (file_1/file_2 מדויק) + regression לכפילות שמות. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…ך-יתר של URL שלושה תיקונים שורשיים ב-Config Inspector (שרץ בתהליך ה-webapp וקורא os.getenv): 1) הפרדה לפי שירות: הצלבנו את כל 236 המשתנים מול עמודת "רכיב" ב- docs/environment-variables.rst וגם מול הקוד עצמו (import-closure סטטי של webapp/bot/mcp + חיפוש הקוראים של כל KEY). 41 משתנים שנקראים רק בבוט (34, כולל ה-webserver הפנימי שרץ בתוך תהליך הבוט), ב-MCP (6) או בסקריפטים (1) הוסטו מהעמוד הראשי ל"עמוד 2" חדש (טאב "שירותים אחרים") שמציג מטא-דאטה בלבד — בלי Status ובלי Active Value, כי ערכיהם חיים בתהליכים אחרים ואינם נגישים מה-webapp. שדה service חדש ב-ConfigDefinition + get_other_services_entries(). OTEL_EXPORTER_* נשארו בעמוד webapp (ה-SDK קורא אותם מה-env בכל תהליך); BOT_USERNAME נשאר webapp ו-BOT_TOKEN נוסף כהגדרה חסרה (נקרא ב-auth_routes). 2) סטטוס "Set" חדש: ערך שהוגדר בסביבה (למשל ברנדר) כשאין ברירת מחדל בקוד אינו "Modified" — אין דיפולט שממנו סטינו. determine_status מחזיר Set במקרה זה (MCP_SERVER_URL, GITHUB_TOKENS, GITHUB_WEBHOOK_SECRET, ALERTMANAGER_WEBHOOK_SECRET, ALERT_TELEGRAM_BOT_TOKEN ודומיהם). נוספו set_count לסקירה, ספירה בכרטיסי הקטגוריות ו-pill כחול (טוקן --info) ב-UI. 3) ביטול מיסוך-יתר: "URL" הוסר מ-SENSITIVE_PATTERNS — כתובת ציבורית (MCP_SERVER_URL, WEBAPP_URL, PROMETHEUS_URL, PUBLIC_BASE_URL...) אינה סוד. URL שמגלם credentials (MONGODB_URL) נשאר ממוסך דרך sensitive=True מפורש, ו-TOKEN/SECRET/URI/KEY ממשיכים להיתפס בתבניות. בנוסף: תוקנו 5 שורות "רכיב" שגויות ב-docs/environment-variables.rst שהתגלו בהצלבה (ENABLE_INTERNAL_SHARE_WEB, SENTRY_WEBHOOK_SECRET, SENTRY_WEBHOOK_DEDUP_WINDOW_SECONDS, DUMMY_BOT_TOKEN → Bot; BOT_TOKEN → Bot/WebApp). טסטים: 32 ב-test_config_inspector_service.py (כולל 3 מחלקות חדשות: סטטוס Set, מיסוך URL, הפרדת שירותים) — ירוקים. תחביר Jinja אומת. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…פים בעמוד 2, ותיקון העתק-הכל שלושה תיקונים בהמשך למשוב: - ה-webserver הוא שירות Render נפרד (ההרצה הפנימית בתוך תהליך הבוט בוטלה): ה-closure שלו חושב בנפרד מהבוט, ומשתנים שנקראים בו (SENTRY_WEBHOOK_SECRET, SENTRY_WEBHOOK_DEDUP_WINDOW_SECONDS) מסומנים service=webserver. עודכן גם "רכיב" ב-environment-variables.rst (Webserver) ותואר ENABLE_INTERNAL_SHARE_WEB. - משתנים משותפים: השדה service הוחלף ב-services (tuple) — משתנה יכול להשתייך לכמה שירותים. עמוד 2 מציג עכשיו את כל 220 המשתנים ששייכים לשירות שאינו webapp, כולל המשותפים (למשל MONGODB_URL: bot + mcp + webserver), עם ציון השירותים בכל שורה ותג "גם Webapp" למשתנים שערכיהם מוצגים בעמוד הראשון. עמוד 1 נשאר 196. - תיקון P2 מה-review: "העתק הכל" הוגבל ל-#inspectorPageWebapp — טבלת השירותים האחרים (מטא-דאטה בלי ערכים) לא מייצרת יותר שורות KEY= ריקות בייצוא ה-.env. טסטים: 33 ב-test_config_inspector_service.py (עודכנו לסמנטיקת services + טסט משתנה-משותף-בשני-העמודים) — ירוקים. תחביר Jinja אומת. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
סקילים (ZIP עם SKILL.md) נכנסו בטעות למסלול הגיבויים, מה שגרם לשלוש בעיות: save_backup_bytes דוחס מחדש ומזריק metadata.json (לא byte-for-byte), cleanup_expired_backups מוחק לפי retention, ו-restore עם purge הרסני. הפתרון — אחסון עצמאי לחלוטין: - SkillManager חדש (file_manager.py): קולקציית GridFS "skills" נפרדת, שמירת bytes as-is (fs.put ישיר), תמיד מונגו בלי תלות ב-BACKUPS_STORAGE ובלי env var. skill_id ייחודי (timestamp+uuid) מונע התנגשות; שם קובץ עם סיומת ייחוד מונע דריסה. - ניתוב בהעלאה: _maybe_store_zip_copy מציג שני כפתורים "סקיל"/"גיבוי" במקום שמירה אוטומטית; ה-bytes נשמרים זמנית עד לבחירה מפורשת (עזרי stash ב-utils.py, מחיקות מוגבלות ל-allowlist ייעודי). - SkillMenuHandler חדש (prefix skill_): רשימה + הורדה/מחיקה/תיוג/הערה, כפתור "📝 סקילים" בתפריט "הצג את כל הקבצים שלי". תיוג/הערה דרך ה-facade הגנרי הקיים. טסטים: שמירה+הורדה byte-for-byte, בידוד מ-cleanup של הגיבויים, ושני סקילים עם אותו שם שאינם דורסים. טסטי הגיבויים הקיימים נשארים ירוקים. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
תיקון כשלי CI/RTD ויישום ממצאי code review על פיצ'ר הסקילים. CI/RTD: - עדכון test_documents להתנהגות "בחירה מפורשת" (כפתורים) במקום שמירה אוטומטית - עדכון סדר תפריט "הצג את כל הקבצים" בשני טסטי patch_coverage (skill_list) - תיקון docstring שהכשיל את RTD (הסרת * חשוף שנפרש כ-emphasis) אבטחה/נכונות: - main: logger.exception בשמירת הערות, בלי חשיפת שגיאת DB למשתמש - file_manager: נרמול user_id ל-int לפני שמירת סקיל (עקביות שאילתת list_skills) - config_inspector: מיסוך URL עם credentials מוטמעים (user:pass@) גם בשם לא-רגיש - utils: אימות token בטוח לשם קובץ + הרשאות 0o700 לתיקיית ה-pending - conversation: ניקוי ה-pending רק אחרי שמירה מוצלחת (מאפשר retry בכשל) ביצועים/עקביות: - skill_menu: איסוף דירוגים ב-thread, הורדה בסריקת GridFS אחת, safe_edit_message_text - conversation: load ל-to_thread + safe_edit על תשובות ה-routing - documents: קבוע PENDING_ZIP_TTL_SECONDS + הגבלת ZIP ממתינים למשתמש Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
הבאג בפרודקשן ("save_skill_bytes: GridFS 'skills' לא זמין"): ב-_get_skills_gridfs
נעשתה בדיקה ``if not mongo_db``, אבל get_mongo_db() מחזיר אובייקט Database אמיתי של
pymongo — ו-bool()/not עליו זורק NotImplementedError. החריגה נבלעה ב-except הכללי,
המתודה החזירה None, והסקיל לא נשמר. חיבור המונגו עצמו תקין (קטעי קוד כן נשמרים).
השורש: pymongo אוסר truth-value testing על Database/Collection/MongoClient. שאר
מתודות ה-facade כבר משוות נכון עם ``is None`` — רק שני עוזרי ה-GridFS השתמשו ב-not.
התיקון (root-cause, שני המקומות):
- SkillManager._get_skills_gridfs: ``if not mongo_db`` → ``if mongo_db is None``.
- BackupManager._get_gridfs: אותו באג רדום (מוסתר בפרודקשן ע"י BACKUPS_STORAGE=fs,
שמחזיר None עוד קודם) — תוקן גם הוא כדי שלא יתפוצץ במצב BACKUPS_STORAGE=mongo.
טסטים: הטסטים הקיימים מוקים את _get_skills_gridfs ולכן פספסו את המסלול האמיתי.
נוספו טסטי רגרסיה שקוראים למתודה האמיתית עם Database אמיתי של pymongo (connect=False,
בלי שרת) ומוודאים שמוחזר GridFS ולא None. אומת red→green. 11 טסטים ירוקים.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…וך שירותים שלושה תיקונים בעקבות פידבק על העמוד: 1. רווחים שנעלמו: עטיפת הטאבים (.inspector-page) היא display:block, ולכן הילדים (כרטיסי סיכום, שורת סינון, קטגוריות, טבלה) איבדו את ה-gap של ה-flex ההורה (.config-page). העמוד הפעיל הוא עכשיו flex column עם אותו gap — הרווחים חזרו. 2. שורת סינון לעמוד 2 (שירותים אחרים): קטגוריה + שירות (אין שם Status כי אין ערכים חיים), עם סנן/איפוס/העתק הכל. הסינון client-side; "העתק הכל" מעתיק KEY=default עבור השורות הגלויות ומציין בטוסט שאלה ערכי ברירת מחדל. 3. דיוק שיוך שירותים (68 משתנים): מיפוי אוטומטי של צריכה בפועל — לכל משתנה נבדק באילו קבצים הוא נקרא (getenv/config.X) והאם הקובץ שייך לשירות או נטען בסגירת ה-imports שלו (entry: mcp_server/app.py, services/webserver.py). משתנים שסווגו ל-MCP/Webserver בלי שימוש אמיתי הוסרו משם (21 מ-MCP, 65 מ-Webserver — למשל UPTIME_*, PUSH_*, VAPID_*, MAINTENANCE_*, ALERTMANAGER_*). חריגים ידניים: PORT נשאר (נצרך בפקודת ההרצה), PROFILER_* נשארו ב-webserver (profiler_handler נטען שם בפועל). בפרט BACKUPS_STORAGE/BACKUPS_DIR עברו ל-Bot בלבד (גם ב-rst): רק הבוט טוען את file_manager; לוובאפ מנגנון גיבוי נפרד (WEBAPP_BACKUPS_DIR). הגיבויים האלה הם ZIP של קטעי הקוד (get_user_files) + גיבויי GitHub/Drive — לא אוספים/סימניות. טסטים: 67 ירוקים (config_inspector, skill_manager, mcp_docs, rst_parser), Jinja template מתקמפל. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…לתות ממוקדות תיקונים בעקבות code review (כל הממצאים אומתו מול הקוד): אבטחה/פרטיות: - file_manager: אזהרת user_id לא תקין רושמת רק את סוג הערך, לא את הערך עצמו (PII). - utils: הערה מבהירה ש-_SAFE_TOKEN_RE ([A-Za-z0-9_-]) מונע path traversal ב-stash (false positive של static analysis — אין תיקון קוד). נכונות: - documents: stash_pending_zip_bytes ו-cleanup_stale_pending_zips רצים ב-to_thread (I/O של דיסק בתוך handler אסינכרוני); אם שליחת הודעת הבחירה נכשלת — הקובץ והרשומה שנוצרו עבור אותו ZIP מנוקים (בלי כפתורים אין דרך להשלים את הבחירה). - skill_menu: query.answer() לפני עריכה גם ב-delete_confirm/delete_execute וב-send_rating_prompt; ב-skill_rate המענה לפני עריכת ההצלחה בלבד — answer מוקדם גורף היה מבטל את ה-show_alert של הודעות השגיאה הקיימות. - file_manager: נרמול user_id ל-int בכל המתודות (list/get/info/delete) — קלט str לא היה מוצא כלום מול שמירה כ-int. מכסות ואינדקסים (לסקילים אין retention — בלי מכסה האחסון גדל ללא גבול): - SKILLS_MAX_PER_USER (ברירת מחדל 100) ו-SKILLS_MAX_TOTAL_BYTES (ברירת מחדל 1GB, 0 = כיבוי) נאכפים לפני fs.put; תועדו ב-rst וב-config inspector (שירות Bot). - אינדקסים על skills.files: (user_id, skill_id) ו-(skill_id) — best-effort פעם אחת. ביצועים/מבנה: - get_skill_info חדש: שליפת סקיל בודד בשאילתה ממוקדת; _find_skill ופרטי/הורדת סקיל כבר לא סורקים את כל הרשימה. delete_skills מסנן ב-DB עם $in. - conversation: ספירת קבצי ה-ZIP אוחדה עם השמירה להלפר אחד שרץ ב-to_thread. - main: זרימות הערה לגיבוי/סקיל אוחדו להלפר משותף; save_backup_note ב-to_thread. - SkillInfo הומר ל-dataclass; send_rating_prompt מלוגג כשל ומודיע במקום pass. טסטים: fixture עם client.close(); fake תומך $in; טסטי מכסות, נרמול str, ו-get_skill_info; ניקוי קבצי pending בשני טסטים שהשאירו קבצים ב-tmp (אומת 0 שאריות); עדכון הערת הסדר ב-test_patch_coverage. 77 טסטים ירוקים. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…ואייקון מותאם
שלושה שינויים ברמת התצוגה (שום callback_data או מפתח לוגי לא השתנה — אומת שהתווית
אינה משמשת כמפתח זיהוי בשום handler/regex):
1. הודעת קליטת ZIP (documents.py): "איפה לשמור אותו?" במקום "איך", שם הקובץ בשורת
הכותרת, תיאורי הקטגוריות (סקילים / גיבויים עם אזכור שחזור ריפו בלחיצה), ואייקון
הסקיל 🧩 בטקסט ובכפתור. html_escape על שם הקובץ נשמר.
2. התווית "📦 קבצי ZIP" → "📦 קבצי גיבוי" — רוכזה לקבוע BTN_BACKUP_ZIPS
(i18n/strings_he.py, מקור אמת יחיד) הנצרך בכל 6 מופעי הכפתור: תפריט 📚 (message
+ callback), עיבוד Batch, GitHub upload (כולל המקלדת המשוכפלת), ותפריט Drive.
בהיקף שאושר עודכנו גם הטקסטים המפנים לכפתור (עזרה, הודעת אישור גיבוי, כותרות
רשימה, הודעות Drive) והתיעוד (BOT_USER_GUIDE, drive_menu.rst). מופעי "ZIP/גדולים"
בניסוח אחר, docstrings, וה-webapp — במפורש מחוץ להיקף.
3. אייקון ZIP מותאם: tg_emoji(emoji_id, fallback) גנרי ב-utils (לגוף הודעה עם
parse_mode=HTML בלבד — כפתורים לא תומכים ב-entities), שדה CUSTOM_EMOJI_ZIP_ID
ב-config (ברירת מחדל None — ה-ID חי רק ב-ENV, משאב צד ג'). בהודעה: ניסיון עם
האייקון המותאם; על BadRequest — לוג חד-פעמי לתהליך ושליחה חוזרת עם 📁 (המשתמש
מקבל את ההודעה בכל מקרה). fallback הוא 📁 בדיוק (שתי יחידות UTF-16). תועד
ב-rst וב-config inspector (display/Bot).
טסטים: נעילת הנוסח החדש ("איפה לשמור", כפתור "🧩 סקיל") + טסט tg_emoji.
85 טסטים ירוקים; אומת ב-diff שכל שינויי הכפתורים הם תווית בלבד.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…906d5 — נפתר לטובת הנוסח החדש)
בהמשך לשינוי אייקון הסקיל בהודעת קליטת ה-ZIP — איחוד לכל שאר המופעים של הסקיל-כישות (📝 → 🧩): כפתור "🧩 סקילים" בתפריט 📚 (שתי הגרסאות), כותרת הרשימה, כפתורי הפריטים, כותרת פרטי הסקיל, caption בהורדה, הודעת "אין סקילים", והודעת ההצלחה שמפנה לתפריט. שינוי תצוגה בלבד — שום callback_data לא השתנה. אייקון ה-📝 בתפקידיו האחרים (הערה / ערוך הערה / שנה שם) נשאר בכוונה — הוא מציין פעולת כתיבה, לא את ישות הסקיל. 51 טסטים ירוקים; אומת שלא נותר "📝 סקיל/סקילים" בקוד. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…דויק לאימוג'י
שלושת הממצאים אומתו מול הקוד ותוקנו:
- github_menu_handler: שתי עריכות ההודעה בנתיבי השגיאה של רשימת הגיבויים
("רכיב גיבוי לא זמין" / "שגיאה בטעינת קבצי גיבוי") עברו ל-
TelegramUtils.safe_edit_message_text — בולע רק "message is not modified"
(ו-parse fallbacks), מפיץ כל שגיאה אחרת. תוקנו שתי הקריאות הצמודות באותו
בלוק (ה-review ציין את השנייה; הראשונה זהה בדפוס).
- documents: סימון _custom_emoji_warned והלוג עברו לאחרי הצלחת שליחת ה-fallback
עם 📁 — רק אז מוכח שהדחייה נבעה מהאימוג'י המותאם. BadRequest ממקור אחר כבר
לא מסמן/מלוגג בטעות; ה-retry נשאר (כשל אחר ייכשל שוב ויתגלגל ל-except החיצוני
שמנקה את ה-pending).
- טסטים: נתיב ה-fallback (טלגרם דוחה tg-emoji → ההודעה נשלחת שוב עם 📁, ה-ZIP
נשאר ממתין לבחירה, הדגל מסומן) + המסלול החיובי (ID מוגדר → ההודעה מכילה את
התג, הדגל לא מסומן). ל-tests/config.py (ה-config החלופי של הטסטים) נוסף השדה
CUSTOM_EMOJI_ZIP_ID כמו בפרודקשן.
41 טסטים ירוקים, אפס קבצי pending שיוריים.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
הידע על ה-Config Inspector חי עד היום רק בהיסטוריית ה-PRים, ולכן מי שמוסיף משתנה חדש חוזר על אותן טעויות: סטטוס Modified שגוי, שיוך שירות מנוחש, ותיאור שלא תואם בין הקוד ל-rst. העמוד החדש מרכז את המודל ואת נוהל העבודה. docs/webapp/config-inspector.rst (חדש) — לצד cache-inspector, אותו דפוס: - למה עמוד 1 מציג רק משתני Webapp: ה-inspector רץ בתוך תהליך ה-webapp ורואה רק את ה-ENV שלו; הצגת Status למשתנה של שירות אחר הייתה מטעה (מידע שגוי גרוע מהיעדר מידע). - מה בעמוד 2 (Bot/MCP/Webserver): מטא-דאטה בלבד, בלי Status/Active Value, עם תגית "גם Webapp" למשתנים משותפים והפניה ל-Render Dashboard לערכים בפועל. - ארבעת הסטטוסים + הכלל השורשי למניעת Modified שגוי: ה-default בהגדרה חייב להיות זהה תו-בתו לדיפולט שבקוד; אין דיפולט בקוד ⇒ להשאיר ריק כדי לקבל Set. - איך בודקים שיוך שירות לפני הוספה: grep על הצריכה בפועל + טבלת נקודות כניסה (main.py/webapp/mcp_server/webserver/scripts), וקובץ משותף לפי שרשרת ה-imports. - משתנה לכמה שירותים: מתועד בכל המקומות — services מלא, עמודת "רכיב" תואמת ב-rst, ותיאור זהה בשני המקומות. - מיסוך רגישים (כולל למה URL הוסר ו-URI נשאר) וצ'קליסט הוספת משתנה. קישורים: רישום ב-docs/index.rst (חובה — בלי זה RTD נכשל על אזהרה), ו-seealso ב-environment-variables.rst שמפנה לכללים מתוך ההנחיה המחייבת שבראש העמוד. CLAUDE.md: השורה על החרגת examples.rst הייתה מיושנת (העמוד כבר ב-toctree ואינו ב-exclude_patterns). הוחלפה בכלל השימושי — כל עמוד חדש חייב להירשם ב-docs/index.rst. ולידציה: בניית Sphinx עם -W (בדיוק כמו RTD) עברה עם 0 אזהרות; אומת שהעמוד נבנה, מופיע ב-toctree, וכל ההפניות ההדדיות נפתרו. 34 טסטים ירוקים. שינויי תיעוד בלבד. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
🧯 Dangerous deletes guard reportPolicy: see .cursorrules — dangerous deletions are blocked unless wrapped safely. Summary:
Flagged findings (file:line:snippet): Excluded matches (by path pattern) |
|
Warning Review limit reached
Next review available in: 32 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughנוספה תמיכה בעוגני HTML מפורשים במסמכי Markdown, כולל חילוץ, סניטציה, רינדור והחלה ב-DOM, בדיקות ייעודיות ושילוב בתבניות התצוגה. נוסף גם תיעוד Config Inspector ועדכוני ניווט וכללי Sphinx. Changesעוגני Markdown
תיעוד Config Inspector
Estimated code review effort: 4 (Complex) | ~45 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
⏱️ Performance report(No performance test durations collected. Mark tests with |
📖 Documentation PreviewThe documentation has been built successfully!
To view locally:
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
השורש (אומת בשחזור מלא מול markdown-it האמיתי): מסמכים רבים מגדירים עוגן לכותרת
בתחביר HTML — `## 1. תקשורת <a id="communication"></a>` — ומפנים אליו מתוכן
העניינים. ה-renderer רץ עם html:false (הגנת XSS על תוכן שמשתמשים מעלים), ולכן
התגית לא הופכת לאלמנט. שלושה נזקים בפועל:
1. אין אלמנט עם id="communication" → הקישור לא מוביל לשומקום (קישורים בתוך
התוכן נשענים על התנהגות דפדפן טבעית — אין להם handler).
2. התגית מוצגת כטקסט זבל גלוי בתוך הכותרת.
3. הטקסט הזה נבלע ל-id האוטומטי ומייצר slug מעוות
(id="1-תקשורת-a-idcommunicationa").
התיקון שומר על html:false — לא נפתחה פרצה:
- webapp/static/js/md-anchors.js (חדש): שליפת העוגנים ממקור ה-Markdown לפני
הרינדור וניקויָם מהטקסט, ואחרי הרינדור הוספת אלמנט יעד בלתי-נראה לפני הכותרת.
תומך גם בעוגן בשורה נפרדת לפני הכותרת (דפוס GitHub), ב-name= לצד id=, ומדלג
על בלוקי קוד. ה-id מוצב דרך element.id (DOM property) ולא innerHTML — אין
וקטור הזרקה, ובנוסף יש סניטציה.
- מזהי הכותרות עצמם לא נדרסים: הם משמשים לסימניות על כותרות, ודריסה הייתה
שוברת סימניות קיימות. לכן יעד נפרד ולא שינוי של heading.id.
- חובר ל-md_preview.html (הצופה הראשי) ול-live-preview.js (תצוגה מקדימה בעורך,
אותו באג), עם fallback שקט אם המודול לא נטען.
באג נוסף שתוקן אגב: md_preview.html השתמש ב-querySelector(hash) לגלילה לפי
עוגן בכתובת. מזהה שמתחיל בספרה (כמו "1-תקשורת" מכותרת ממוספרת) אינו סלקטור CSS
חוקי והקריאה זרקה — הוחלף ב-getElementById עם decodeURIComponent.
טסטים: tests/md-anchors.test.js (חדש, `node tests/md-anchors.test.js`) — 21
בדיקות ירוקות: שליפה, דפוס GitHub, דילוג על בלוקי קוד, כמה עוגנים לכותרת,
סניטציה, והחלה על DOM (כולל אי-דריסת מזהה תפוס). 81 טסטי פייתון ירוקים; לא
נגעתי בשום קובץ .py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
…zsg' into claude/mcp-codekeeper-webapp-ldnzsg
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
webapp/static/js/live-preview.js (1)
440-466: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winמצב משותף (module-level) בין
render()ל-enhance()— תלוי בסדר קריאות חיצוני.
pendingExplicitAnchorsהוא state גלובלי שנקבע ב-render()ונצרך רק בקריאה נפרדת ל-enhance(root). אם render מופעל שוב (למשל debounce שמתחיל מחזור רינדור נוסף) לפני ש-enhance נקרא על התוצאה הקודמת, ה-state יידרס והעוגנים שיוחלו לא יתאימו ל-DOM שכבר מוצג. קשה לוודא נכונות בלי לראות את קוד הקורא (debounce/scheduler) של הקבצים האלה.מומלץ להחזיר את
anchorsמתוךrender()(למשל כחלק מאובייקט תוצאה) ולהעביר אותם במפורש ל-enhance/applyPendingAnchors, במקום להסתמך על state חיצוני משותף.Also applies to: 483-495
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@webapp/static/js/live-preview.js` around lines 440 - 466, Remove the module-level pendingExplicitAnchors coupling between render() and enhance(). Return anchors from render() as part of its result, pass them explicitly into enhance(root), and update applyPendingAnchors to consume that parameter so consecutive renders cannot overwrite anchors awaiting application.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/webapp/config-inspector.rst`:
- Around line 131-133: Update the variable-search commands in the documented
configuration-inspector examples to search for the variable name without
requiring double quotes, so usages such as os.getenv('MY_VAR') are found. Use rg
or equivalent built-in directory exclusions to omit test directories, and apply
the same change to both referenced command blocks.
- Around line 30-32: עדכנו את ההערה המתארת את מיסוך הערכים כך שתציין במפורש שרק
ערכים שסווגו כרגישים ממוסכים תמיד, תוך שמירה על ההפניה הקיימת
ל-config-inspector-sensitive ולפרטי הרשאות האדמין.
In `@webapp/static/js/md-anchors.js`:
- Line 27: Update the heading-counting logic used by renderWithAnchors so
headingIndex includes both ATX and Setext headings, matching the generated h1–h6
elements. Respect the markdown configuration by excluding Setext headings when
md.disable('lheading') is enabled, and apply the same behavior through the
exported renderWithAnchors path.
- Around line 27-128: Update extractExplicitAnchors in
webapp/static/js/md-anchors.js (lines 27-128) to recognize Setext headings
formed by a text line followed by a valid === or --- underline, increment
headingIndex for them, and associate both inline and preceding standalone
anchors with the Setext heading while preserving ATX handling. Add mixed
Setext/ATX anchor coverage in tests/md-anchors.test.js (lines 41-93) to verify
correct heading indices and anchor assignment.
---
Nitpick comments:
In `@webapp/static/js/live-preview.js`:
- Around line 440-466: Remove the module-level pendingExplicitAnchors coupling
between render() and enhance(). Return anchors from render() as part of its
result, pass them explicitly into enhance(root), and update applyPendingAnchors
to consume that parameter so consecutive renders cannot overwrite anchors
awaiting application.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cdf553ac-f322-489b-af93-7be54068ba36
📒 Files selected for processing (10)
CLAUDE.mddocs/environment-variables.rstdocs/index.rstdocs/webapp/config-inspector.rsttests/md-anchors.test.jswebapp/static/js/live-preview.jswebapp/static/js/md-anchors.jswebapp/templates/edit_file.htmlwebapp/templates/md_preview.htmlwebapp/templates/upload.html
… החיה
סבב review — כל הממצאים אומתו מול הקוד ותוקנו:
1. כותרות Setext (באג נכונות אמיתי, אומת בשחזור): extractExplicitAnchors ספר
רק כותרות ATX, אבל markdown-it מייצר h1/h2 גם מכותרות Setext (שורת טקסט
ואחריה === או ---). התוצאה: האינדקסים זזו והעוגן הוחל על הכותרת הלא נכונה —
במסמך "מבוא\n====" ואחריו "## תקשורת <a id=...>" העוגן נחת על "מבוא".
נוסף זיהוי Setext עם שמירה על ההתנהגות ל-ATX, ועם בדיקה שמרנית שמונעת זיהוי
שגוי: קו מפריד (hr) אחרי שורה ריקה, שורת הפרדה של טבלה, פריט רשימה, ציטוט,
וקוד מוזח — כולם אינם כותרת. נוסף פרמטר setext (ברירת מחדל true) לכיבוי
כש-md.disable('lheading') פעיל; אומת שאף צרכן בריפו לא מנטרל אותו.
2. ניתוק המצב המודולרי ב-live-preview: pendingExplicitAnchors המשותף בין
render() ל-enhance() אפשר לשני רינדורים עוקבים לדרוס זה את העוגנים של זה —
וזה החמיר בגלל ש-repo-browser.js הוא צרכן נוסף של אותו זוג. עכשיו
renderWithAnchors() מחזיר { html, anchors } והם מועברים במפורש ל-
enhance(root, anchors). render() נשאר עם החתימה הקיימת (מחזיר HTML) כדי לא
לשבור קוראים.
3. repo-browser: מנוצל התיקון — כבר היה שם handler תקין לגלילת עוגנים
(setupMarkdownAnchorScrolling עם getElementById), רק שהעוגנים מעולם לא נוצרו.
מעבר ל-renderWithAnchors + טעינת md-anchors.js ב-base_repo.html משלימים את
התמונה גם בעיון בקבצי GitHub.
4. תיעוד config-inspector: פקודות החיפוש הוחלפו ב-rg עם -w (מילה שלמה) ו-
-g '!tests/**' — הן תופסות עכשיו גם os.getenv('X') בגרש בודד וגם config.X
בפקודה אחת, בלי להיתפס ל-X_OTHER. הודגם בפועל לפני העדכון. בנוסף הובהר
שהמיסוך חל רק על ערכים שסווגו כרגישים, ולא על כל ערך.
טסטים: 33 ירוקים (נוספו 12 ל-Setext, כולל מקרי אי-זיהוי), ובנוסף הצלבה של כל
תשעת המקרים מול ה-DOM ש-markdown-it באמת מייצר — 9/9 תואמים. Sphinx -W עבר,
81 טסטי פייתון ירוקים.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K
What
לחיצה על קישור בתוכן העניינים של מסמך Markdown (
[תקשורת](#communication)) לא גללה לסעיף. תוקן מהשורש, בלי לוותר על הגנת ה-XSS.ה-PR כולל גם את עמוד התיעוד החדש ל-Config Inspector ואת עדכון CLAUDE.md (הקומיט הקודם בענף).
Why
מסמכים רבים (בעיקר כאלה שנכתבו ל-GitHub) מגדירים עוגן לכותרת בתחביר HTML:
ה-renderer רץ עם
html: false— הגנת XSS על תוכן שמשתמשים מעלים — ולכן התגית לא הופכת לאלמנט. אומת בשחזור מלא מול markdown-it האמיתי של הריפו, ונמצאו שלושה נזקים:id="communication"→ הקישור לא מוביל לשומקום (קישורים בתוך התוכן נשענים על התנהגות דפדפן טבעית — אין להם handler).idהאוטומטי ומייצר slug מעוות:id="1-תקשורת-a-idcommunicationa".באג נוסף שהתגלה אגב: הגלילה לפי עוגן שבכתובת השתמשה ב-
querySelector(hash). מזהה שמתחיל בספרה (כמו1-תקשורתמכותרת ממוספרת) אינו סלקטור CSS חוקי — הקריאה זרקה, כך שגם כניסה לעמוד עם#hashכזה הייתה שבורה.✨ תיאור קצר
html: falseנשמר — לא נפתחה פרצה.querySelector(hash)→getElementByIdעם decode.docs/webapp/config-inspector.rst+ עדכון הנחיית Sphinx ב-CLAUDE.md.📦 שינויים עיקריים
פירוט נקודות:
webapp/static/js/md-anchors.js(חדש):extractExplicitAnchors/applyExplicitAnchors/renderWithAnchors. תומך גם בעוגן בשורה נפרדת לפני הכותרת (דפוס GitHub), ב-name=לצדid=, ובכמה עוגנים לאותה כותרת; מדלג על בלוקי קוד.element.id(DOM property) ולא דרךinnerHTML— אין וקטור הזרקה; בנוסף יש סניטציה של המזהה.heading.id.md_preview.html(הצופה הראשי) ול-live-preview.js(תצוגה מקדימה בעורך — אותו באג), עם fallback שקט אם המודול לא נטען.scroll-margin-topליעד כדי שהכותרת לא תיעצר מתחת לסרגל העליון.docs/webapp/config-inspector.rst: למה עמוד 1 = Webapp בלבד, מה בעמוד 2, איך נמנעים מסטטוסModifiedשגוי, איך בודקים שיוך שירות לפני הוספת משתנה, ומה מתעדים כשמשתנה משרת כמה שירותים.🧪 בדיקות
-W.node tests/md-anchors.test.js: 21 בדיקות ירוקות (שליפה, דפוס GitHub, דילוג על בלוקי קוד, כמה עוגנים, סניטציה, והחלה על DOM כולל אי-דריסת מזהה תפוס). 81 טסטי פייתון קיימים נשארו ירוקים; לא נגעתי בשום קובץ.pyבתיקון ה-Markdown.🧪 בדיקות נדרשות ב‑PR
📝 סוג שינוי
Rollback: revert של הקומיטים; המודול עצמאי ויש fallback — אם הוא לא נטען, ההתנהגות חוזרת לקודמת. אין מיגרציות.
ידוע ולא בהיקף:
webapp/static/js/repo-browser.js(עיון בקבצי GitHub) מרנדר Markdown בלי plugin העוגנים כלל, ולכן שם לכותרות איןidמלכתחילה ושום קישור פנימי לא יעבוד — פער נפרד שממתין להחלטה.🤖 Generated with Claude Code
https://claude.ai/code/session_01WNFuSyshwpRcxozVZEui5K