feat: חיפוש בפתקים בוובאפ — שם, תוכן, וסינון לפי צבע

Merged
AM
1mo ago
+1987-45

✨ תיאור קצר

עמוד /notes/search שמחפש בכל הפתקים של המשתמש יחד — לוח, קובץ, וקובץ בריפו. מגיעים אליו מעמוד הקבצים ומהגדרות הלוח. ההתאמה משותפת עם ה-MCP: שני הצרכנים עוברים דרך note_search_filter ונבדלים רק במה שהם מציגים, כך שאותה מילה מחזירה את אותם פתקים בשניהם.

📦 שינויים עיקריים

  • קוד (Backend)
  • בוט טלגרם
  • מסד נתונים/מיגרציות (אינדקס חדש)
  • תיעוד (docs/)
  • DevOps/CI/CD

פירוט:

  • sticky_notes_target — ל-note_search_filter נוסף color_id אופציונלי (מצטרף כפרדיקט עצמאי, לא כענף ב-$or); נוספו note_color_query ו-NOTE_SEARCH_PREVIEW_CHARS.
  • webapp/sticky_notes_api — GET /api/sticky-notes/search עם @require_auth ו-@notes_rate_limit, תקרה ל-limit, ו-build_note_search_pipeline.
  • webapp/routes/notes_search.py — בלופרינט חדש לעמוד עצמו (HTML בלבד, בתבנית boards_ui).
  • תבנית, JS ו-CSS חדשים — כולל פונקציית הדגשה משלנו.
  • אינדקס (user_id, updated_at) — בוובאפ וב-MCP, עם קידום _INDEX_READY_CACHE_KEY.
  • שתי נקודות כניסה — אייקון בעמוד הקבצים (משמאל ל-➕) ושורה בהגדרות הלוח.

שלוש החלטות שנגזרו ממדידה, לא מהיגיון

$text נבחן ונפסל. אינדקס טקסט הוא הפתרון המתבקש, אבל מדידה על פתקים אמיתיים בעברית הראתה שהוא מפספס כ-18% מההתאמות שהרג'קס מוצא: חיפוש "שימוש" החזיר 2 פתקים מתוך 27. הסיבה מתועדת ולא באג — $text מתאים טוקנים שלמים ומתעלם מגזירת סיומות, ולכן "פתק" אינו מוצא "בפתק". רג'קס תת-מחרוזת מוצא את שניהם. מונגו מתירה אינדקס טקסט אחד לכל אוסף, כלומר זו החלטה שקשה לחזור ממנה — ולכן היא מתועדת ב-docs/dev/sticky_notes_extending.

האינדקס (user_id, updated_at) מתקן בעיה שכבר קיימת. הדוקסטרינג של note_search_filter טען ש-user_id כפרדיקט ראשון חוסם את הסריקה למכסת המשתמש "ואינו COLLSCAN". explain על השאילתה האמיתית הראה שהתוכנית הזוכה בוחרת ב-updated_desc — כלומר החיפוש שרץ היום ב-MCP סורק את הפתקים של כל המשתמשים. הדוקסטרינג תוקן לתאר את מה שנמדד.

סינון הצבע צריך את שתי הצורות. במסד 176 פתקים שמורים כ-#FFFFCC באותיות גדולות בעוד שהפלטה מחזיקה #ffffcc — כלומר סינון שמשווה למזהה בלבד היה מחזיר אפס, בלי שגיאה. note_color_query מחזירה $in של המזהה ורג'קס מעוגן חסר-רישיות. הרג'קס יורד בעריכה אחת אחרי ש-scripts/migrate_note_colors.py --apply רץ בכל הסביבות.

עברית, ולמה ההדגשה בצד הלקוח

חיתוך התצוגה המקדימה הוא $substrCP ולעולם לא $substrBytes — אות עברית היא שני בייטים, וחיתוך בבייטים על מספר שנספר בתווים מפיל את השאילתה (Location28657). מאותה סיבה השרת אינו שולח היסטים: הוא סופר תווים, מונגו נקודות-קוד ו-JS יחידות UTF-16. ההתאמה נעשית על 200 התווים שכבר הגיעו.

ההדגשה בנויה מצמתי DOM (createElement + textContent) ולא ממחרוזות HTML, ולכן אין ב-notes_search.js שום innerHTML.

🧪 בדיקות

  • Unit — 35 בדיקות פייתון + 8 בדיקות JS
  • Integration
  • Manual — מדידה בדפדפן

כל בדיקה מומתה. 18 מוטציות בפייתון ו-5 ב-JS, וכל אחת הפילה את הבדיקה שלה: הסרת הדירוג מהמיון, הסרת ה-$project, הזזתו אחרי ה-$sort, החלפת $substrCP ב-$substrBytes, הסרת user_id, שוויון במקום $in, רג'קס בלי i, הסרת re.escape, הסרת $ifNull, הסרת התקרה, הסרת שורת הסנטינל, ו-textContent שהופך ל-innerHTML.

שתי מוטציות חשפו חורים בבדיקות שלי עצמי ותוקנו: בדיקת המדידה בבייטים עברה על קוד שבור כי הפתק שנבחר היה קצר מ-200 גם בבייטים — הוחלף בפתק של 150 תווים (270 בייטים), שיושב בין שתי היחידות. ומוטציה שנייה לא הוחלה בכלל כי הדפוס הופיע פעמיים בקובץ.

מדידה בדפדפן (Chromium אמיתי, 1024 ו-390): אין שגיאות דף; הרמז "המילה מופיעה בהמשך הפתק" מופיע בדיוק כשאין התאמה בכותרת ואין בתקציר; מטען <img onerror> ומרכאות מופיעים כטקסט ולא נוצר מהם אלמנט; עיגולי הפלטה נדגמו בפיקסלים והחזירו בדיוק את גווני NOTE_COLORS; אין גלילה אופקית; הטקסט בתוך ההדגשה 10.57:1 מול רקע ההדגשה.

מה שלא נמדד, ונאמר במפורש: ניגודיות ההדגשה מול רקע הכרטיס אינה קבועה כאן. הגרסה הראשונה של הדוח נקבה ב-3.35:1, והמספר הזה היה שגוי — הפיקסל נדגם בפינת הכרטיס, ושם ה-border-radius חושף את רקע העמוד, כך שנמדדה הדגשה מול העמוד ולא מול הכרטיס. הוא הוסר ולא הוחלף.

בנוסף, התנהגות העמוד בערכות מיובאות נבדקה חלקית בלבד. מה שכן אומת: הטוקנים של ערכה מיובאת חלים על הכרטיס כמצופה (--card-bg נקרא נכון), וניגודיות הכותרת גבוהה בשלוש הערכות שבשימוש. מה שלא הושלם: ניגודיות שורת התקציר, שנשענת על --text-secondary. [data-theme="custom"] a שב-dark-mode.css צובע כל <a> ב---primary ומנצח בספציפיות את המחלקה של הכרטיס — אותו דפוס שכבר קיבל וו דריסה ייעודי באוספים ובהצעות החיפוש. אם יידרש, זה תיקון ממוקד בעקבות שימוש ולא חוסם מיזוג.

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

🧪 בדיקות נדרשות ב‑PR

  • 🔍 Code Quality & Security
  • Unit Tests (3.11)
  • Unit Tests (3.12)

📝 סוג שינוי

  • feat: פיצ'ר חדש

✅ צ'קליסט

  • הקוד עוקב אחרי הסגנון של הקבצים שהוא יושב בהם
  • בדיקות רצות ועוברות
  • תיעוד עודכן
  • לא נוספו ג'ובים חדשים
  • לא נוספו/שונו משתני סביבה
  • לא נוספו/השתנו טוקנים — ה-CSS החדש נשען על טוקנים קיימים, ובדיקת rg "#[0-9a-fA-F]{3,6}" על notes-search.css מחזירה ריק
  • אין סודות/מפתחות בקוד
  • אין מחיקות מסוכנות
  • הודעת הקומיט תואמת Conventional Commits
  • עיינתי במסמכי אתר התיעוד — נתיב: docs/webapp/theming_and_css.rst | המשפט: "Sticky Notes, Reader Modes ו-theme_preview.html נשארים Hardcoded בכוונה לצורכי Preset/Brand"
    • וגם: docs/dev/sticky_notes_extending.rst, docs/performance-sticky-notes.rst, docs/database/indexing.rst, docs/webapp/commands-catalog.rst (ממנו נגזרה הדרישה שהמזהים בעמוד לא יהיו globalSearchInput/searchBtn, כי global_search.js נטען בכל עמוד ומתחבר לכל דף שמכיל את שניהם)

🧩 השפעות/סיכונים

הרשאות: העמוד מחזיר את הפתקים של המשתמש בכל שלושת הסוגים, בדיוק כמו codekeeper_search_notes — שאינו מגודר לאדמין. הפער בין הגייטים של פתקי ריפו ב-MCP ובוובאפ מדווח בנפרד ב-#3407 ולא שונה כאן.

אינדקס: נוסף אינדקס אחד לאוסף שכבר נושא אחד-עשר. הבנייה best-effort ואינה מפילה בקשה. _INDEX_READY_CACHE_KEY קודם ל-v4, ולכן תהליכים שמחזיקים דגל v3 חי ייבנו מחדש בפריסה.

מיגרציה: scripts/migrate_note_colors.py --apply לא הורץ כאן, וסינון הצבע עובד גם בלעדיו. הרצתו מיישרת את המסד ומאפשרת לפשט את note_color_query בהמשך.

ערכות מיובאות: ראו את סעיף הבדיקות — ייתכן שיידרש וו דריסה לצבע הטקסט בכרטיס, בתבנית שכבר קיימת ב-collections.css וב-global_search.css.

🔗 קישורים

  • Issues שנפתחו אגב העבודה ולא תוקנו כאן: #3406 (בריחת HTML ב-global_search.js), #3407 (גייט פתקי ריפו)

🧯 סיכון / החזרה לאחור (Rollback)

השינוי אדיטיבי: אף מסלול קיים לא שינה התנהגות. note_search_filter קיבל פרמטר עם ברירת מחדל שמשמרת את ההתנהגות הקודמת, ושאר השינויים הם קבצים חדשים או תוספות. Revert מסיר את העמוד ואת נקודות הכניסה; האינדקס נשאר במסד ואינו מזיק (הוא עוזר גם לחיפוש של ה-MCP).

🤖 Generated with Claude Code

https://claude.ai/code/session_01PL3goRvy9ze5NrG5z68Awr