feat: חיפוש בפתקים בוובאפ — שם, תוכן, וסינון לפי צבע
✨ תיאור קצר
עמוד /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