webapp.sticky_notes_api module

Sticky Notes API for Markdown preview - Stores user-specific notes per file in MongoDB - Endpoints: list, create, update, delete

webapp.sticky_notes_api.emit_event(event, severity='info', **fields)[מקור]
פרמטרים:
webapp.sticky_notes_api.get_db()[מקור]
webapp.sticky_notes_api.kickoff_index_warmup(*, background=True, delay_seconds=0.0)[מקור]

Run index warmup once during startup so requests won’t block on it.

Return type:

None

פרמטרים:
webapp.sticky_notes_api.require_auth(f)[מקור]
webapp.sticky_notes_api.notes_rate_limit(key, max_per_minute)[מקור]
פרמטרים:
  • key (str)

  • max_per_minute (int)

webapp.sticky_notes_api.list_notes(file_id)

List all sticky notes for current user and file.

פרמטרים:

file_id (str)

webapp.sticky_notes_api.get_note_reminder(note_id)
פרמטרים:

note_id (str)

webapp.sticky_notes_api.set_note_reminder(note_id)
פרמטרים:

note_id (str)

webapp.sticky_notes_api.delete_note_reminder(note_id)
פרמטרים:

note_id (str)

webapp.sticky_notes_api.snooze_note_reminder(note_id)
פרמטרים:

note_id (str)

webapp.sticky_notes_api.BADGE_REFRESH_SECONDS = 300

כשיש בועה על המסך השרת מבקש מהלקוח לחזור לכל היותר בעוד חמש דקות — המרווח הקבוע שהיה כאן לפני הדגימה לפי השרת, וזה שריענן גם ניקוי ממכשיר אחר וגם מונה שהשתנה. הערך יושב בשרת ולא בלקוח כדי שללקוח יישאר כלל אחד: ”ישן כמה שהשרת אמר, בין הרצפה לתקרה“. תזכורת נוספת שמבשילה מוקדם יותר גוברת עליו.

webapp.sticky_notes_api.reminders_summary()

Return minimal summary for persistent UI badge.

Response:

{ ok, has_due: bool, count_due: int, next_in_seconds: int | null }

``next_in_seconds`` הוא מה שמחליף את הדגימה הקבועה. עד כאן הלקוח דגם כל חמש דקות בלי קשר למצב, כי ”כן/לא“ היה כל מה שקיבל — 954 קריאות ביממה שכולן החזירו ”אין“. השרת הוא היחיד שיודע גם מתי התזכורת הבאה, ולכן הוא זה שאומר ללקוח מתי לחזור — גם כשיש בועה: אז התשובה היא לכל היותר BADGE_REFRESH_SECONDS, כדי שבועה שנוקתה ממכשיר אחר ומונה שהשתנה ייראו תוך דקות ולא אחרי חצי שעה. אין תזכורת עתידית ואין בועה ← None, והלקוח נרדם עד התקרה שלו (חצי שעה) — לא לנצח, כדי שתזכורת שתיקבע ממכשיר אחר עדיין תתגלה.

והערך יחסי, לא חותמת. remind_at שנקרא מהמסד הוא מודע-אזור רק כל עוד הלקוח נבנה עם tz_aware — ומחרוזת ISO בלי offset נקראת ב-JavaScript כזמן מקומי, כלומר שלוש שעות סטייה בלי שגיאה. מספר שניות אין לו אזור זמן, והוא גם חסין לשעון לקוח שסוטה.

webapp.sticky_notes_api.reminders_list()

Return a list of due sticky‑note reminders for the current user.

Response:

{
  "ok": true,
  "items": [
    { "note_id": "...", "file_id": "...", "preview": "...", "anchor_id": "h2-intro", "anchor_text": "Intro",
      "remind_at": "2026-09-20T09:00:00+00:00" }
  ],
  "count": 1
}

remind_at הוא המועד שהפריט הזה נורה עליו; החלונית מחזירה אותו ב-ack, כדי שהאישור ייקשר למועד הזה ולא ל“איזו שהיא“ תזכורת של הפתק.

webapp.sticky_notes_api.reminders_ack()

Mark current due reminder as acknowledged (user opened it).

סוגר את שני שדות המצב יחד. עד כאן נכתב ack_at בלבד, ו-status נשאר pending לנצח — כך שכרטיס הדשבורד, שסופר לפי status, דיווח תזכורות ”בהמתנה“ שאיש לא המתין להן. השדות מגיעים מ- note_reminder_state.acknowledge_fields(), שמחזירה את שניהם או אף אחד, ומונגו מחילה אותם ב-$set יחיד.

ונקשר למועד שההתראה נשאה. בלי remind_at בגוף, האישור סגר ”איזו שהיא“ תזכורת של הפתק; וכשהפתק נדרך מחדש אחרי שההתראה נורתה — בדיוק מה שעושים כשתזכורת מגיעה ברגע לא נוח — התראה ישנה במגש סגרה את התזכורת של מחר, כי set_note_reminder עושה upsert על אותו מסמך ויש תמיד שורה אחת לפגוע בה. עם המועד, הפילטר תופס רק את המסמך שעדיין נושא אותו: מסמך שנדרך מחדש או נדחה מאז עונה 404, ונשאר פעיל. לקוח שאינו שולח מועד (SW ישן במטמון, התראה שהוצגה לפני השדה) מאשר בלי קשירה — ההתנהגות של קודם.

webapp.sticky_notes_api.create_note(file_id)

Create a new sticky note for a file.

פרמטרים:

file_id (str)

webapp.sticky_notes_api.update_note(note_id)

Update existing note; only owner can update.

פרמטרים:

note_id (str)

webapp.sticky_notes_api.delete_note(note_id)

Delete a note; only owner can delete.

פרמטרים:

note_id (str)

webapp.sticky_notes_api.batch_update_notes()

Batch update multiple notes in one request.

Body format (JSON):

{
  "updates": [
    {
      "id": "...",
      "content": "...",
      "position": {"x": 120, "y": 240},
      "size": {"width": 260, "height": 200},
      "color": "yellow_light",
      "is_minimized": false,
      "line_start": 10,
      "line_end": null,
      "anchor_id": "h2-intro",
      "anchor_text": "Intro",
      "mode": "surface",
      "prev_updated_at": "2024-01-01T00:00:00+00:00"
    }
  ]
}

Response JSON contains results with per-item status, e.g. 200/409.

webapp.sticky_notes_api.list_board_notes(board_id)

פתקי לוח. שאילתה ישירה, בלי $or ובלי מעבר ב-code_snippets.

פרמטרים:

board_id (str)

webapp.sticky_notes_api.create_board_note(board_id)

פתק חדש על לוח.

פרמטרים:

board_id (str)

webapp.sticky_notes_api.list_repo_notes(repo_name, repo_path)

פתקים על קובץ בריפו ממורר.

סימון היתומים נעשה בקריאה, ולא בכתיבה. אין סריקה תקופתית ואין update_many על מסלול קריאה — זה הלקח מסריקת היתומים של הלוחות. הפתק ממשיך להיות מוחזר עם הנתיב האחרון הידוע, ורק נושא דגל.

פרמטרים:
  • repo_name (str)

  • repo_path (str)

webapp.sticky_notes_api.create_repo_note(repo_name, repo_path)

פתק חדש על קובץ בריפו ממורר.

פרמטרים:
  • repo_name (str)

  • repo_path (str)

webapp.sticky_notes_api.list_repo_orphans(repo_name)

קומה ראשונה: פתקים בריפו הזה שהקובץ שלהם כבר אינו בעץ.

”מיותם“ נגזר מהמראה, לא מ-GitHub. דפדפן הריפו מציג עותק מסונכרן, ולכן קובץ שנמחק ב-GitHub לפני שעתיים עדיין קיים כאן והפתק עליו ייראה תקין עד הסנכרון הבא. לכן מוחזר גם last_sync_time — זה לא באג אם כתוב, וכן באג אם לא.

פרמטרים:

repo_name (str)

webapp.sticky_notes_api.list_orphan_repos()

קומה שנייה: ריפואים שיש להם פתקים ואינם ממוררים עוד.

בלי הקומה הזו פתקים של ריפו שהוסר לא מופיעים בשום תצוגה — לא בעץ שלו, כי אין עץ, ולא ברשימת היתומים, כי היא פר-ריפו. זה בדיוק ”המצב שנעלם בשקט“.

webapp.sticky_notes_api.toggle_note_task(note_id)

מסמן או מבטל צ’קבוקס בתוך פתק.

הראוט הזה קיים כדי שאפשר יהיה לאמת את הכתיבה. האלטרנטיבה — לשלוח את התוכן המלא ב-PUT /note/<id> — הופכת כל קליק לדריסת last-writer-wins של עריכות מקבילות, ובעיקר הופכת אימות לבלתי אפשרי: אפשר לאמת רק שכתבנו את מה ששלחנו. הבקשה כאן נושאת כוונה (מספר סידורי + מצב רצוי), וזה הדבר היחיד שניתן לאמת מול המסד.

הסדר, וכל שלב בו מגן על משהו:

  1. הפתק קיים ושייך למשתמש — אחרת 404.

  2. prev_updated_at — אחרת 409, בדיוק כמו ב-update_note.

  3. סידורי שאינו קיים ⇒ 409, לא 200. התצוגה של הלקוח מיושנת.

  4. כבר במצב המבוקש ⇒ 200 בלי כתיבה. אידמפוטנטי.

  5. Compare-and-swap: הפילטר כולל את התוכן הקודם, כך שכותב מקביל אינו נדרס.

  6. קריאה חוזרת מהמסד ובדיקה שהתו אכן השתנה. לא modified_count, לא ok: true — אלה מדווחים על הקריאה, לא על המצב.

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

פרמטרים:

note_id (str)

webapp.sticky_notes_api.NOTE_SEARCH_DEFAULT_LIMIT = 30

כמה תוצאות מוחזרות כשלא נתבקש אחרת, וכמה הכי הרבה אפשר לבקש.

התקרה אינה קוסמטית. limit מגיע מה-URL, כלומר מחוץ לתהליך, ובלי חסם עליון בקשה אחת יכולה לגרור את כל מכסת המשתמש (MAX_NOTES_PER_USER) לתוך המיון ולתוך התשובה. חמישים תוצאות הן יותר ממה שמישהו סורק בעין; מי שצריך יותר מצמצם את החיפוש.

webapp.sticky_notes_api.NOTE_SEARCH_MAX_NEEDLE = 200

אורך מרבי למחט החיפוש עצמה.

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

webapp.sticky_notes_api.build_note_search_pipeline(user_id, needle, *, color_id, limit)[מקור]

הצינור שמחפש פתקים — בנוי כאן, ונבדק כפי שנשלח.

סדר השלבים אינו קוסמטי, ושלושה מהם קיימים בגלל תקלות שכבר קרו בריפו הזה:

  1. $match — הפילטר המשותף עם ה-MCP (note_search_filter()). אותה פונקציה ולא העתק: שני חיפושי פתקים שמתאימים אחרת הם שני מוצרים שמתחזים לאחד.

  2. $addFields — הדירוג והתצוגה המקדימה, שניהם לפני שהגוף יורד, כי שניהם נגזרים ממנו.

  3. $project בהחרגה — הגוף יורד לפני ה-$sort. בסדר ההפוך מונגו החזירה בפרודקשן שגיאה 292 (QueryExceededMemoryLimitNoDiskUseAllowed) והקוד נפל למסלול איטי; allowDiskUse אינו מציל כי Atlas מתעלם ממנו ב-Flex. מתועד ב-tests/test_files_pipelines_drop_heavy_fields_early.py.

  4. $sort — כותרת לפני גוף, ובתוך כל קבוצה לפי עדכון אחרון. ‏``false < true`` בסדר ה-BSON, ולכן -1 מעלה את פגיעות הכותרת.

  5. $limit עם שורת סנטינל (limit + 1), כמו ב-MCP: שורה נוספת שחזרה היא ראיה שיש עוד, ולא ניחוש מתוך ספירה ששווה לתקרה.

  6. $unset — שדה העזר של הדירוג אינו חלק מהתשובה.

‏``$substrCP`` ו-``$strLenCP``, ולעולם לא הגרסאות ב-Bytes. אות עברית היא שני בייטים, וחיתוך בבייטים על מספר שנספר בתווים נוחת באמצע תו ומפיל את השאילתה (Location28657). זה amir-bug-patterns H6, והאירוע עצמו קרה כאן.

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

Return type:

List[Dict[str, Any]]

פרמטרים:
  • user_id (int)

  • needle (str)

  • color_id (str | None)

  • limit (int)

webapp.sticky_notes_api.search_notes()

חיפוש בפתקים של המשתמש — בשם ובגוף, חוצה את שלושת היעדים.

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

הרשאות: הפתקים של המשתמש, כל שלושת הסוגים — בדיוק כמו codekeeper_search_notes, שאינו מגודר לאדמין (הגידור שם חל על list_repo_notes/create_repo_note בלבד). גם /api/sticky-notes/repo/… בוובאפ נושא @require_auth בלבד, כלומר גייט אדמין כאן היה מסתיר מהמשתמש פתקים שהוא כבר מושך בקריאה אחרת — גייט שאינו מגן על דבר.