Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/database/detailed-schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@
* - ``content``
- str
- כן
- עד 5,000 תווים.
- עד 20,000 תווים. התקרה מוגדרת פעם אחת ב-``sticky_notes_target.MAX_NOTE_CHARS`` ומיובאת משם ל-webapp, ל-MCP ולתבניות.
* - ``position_x`` / ``position_y``
- int
- כן
Expand Down
23 changes: 23 additions & 0 deletions docs/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,29 @@ Mocking HTTP ב‑github_menu_handler
enabled = True # הפיצ'ר נכפה ל-True בזמן טסטים


בדיקות מול מונגו אמיתי
------------------------

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

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

- **אילוץ ייחודיות** — אינדקס ייחודי-חלקי כמו ``one_default_per_user`` הוא מה שסוגר מרוץ בין שתי בקשות מקבילות. רק המסד יכול לדחות, ו-``create_index`` שהחזיר בלי לזרוק אינו ראיה שהאינדקס נוצר.
- **האם אינדקס בשימוש** — רק ``explain`` עונה. סטאב מחזיר תוצאה נכונה גם כששאילתה סורקת את כל האוסף.
- **סמנטיקה של BSON** — ``datetime`` נקטם למילישניות, ובלי ``tz_aware=True`` הוא חוזר נאיבי. השוואה בין נאיבי ל-aware זורקת ``TypeError``, ואם היא עטופה ב-``except`` — הבדיקה שנשענת עליה מפסיקה לרוץ בשקט.
- **צינורות aggregation** — סטאב שנכתב ביד מבין רק את הצורה שנכתבה בו, ולכן שגיאת תחביר אמיתית עוברת אצלו.

הבדיקות האלה חיות ב-``tests/test_note_boards_mongo.py``. הן **מדלגות** כשאין ``MONGODB_URL`` או כשהשרת אינו נגיש, כך שהרצה מקומית רגילה נשארת מהירה, ורצות באמת ב-CI — הג'וב ``Unit Tests`` מרים ``mongo:6.0`` כשירות ומגדיר ``MONGODB_URL``.

להרצה מקומית מול שרת אמיתי:

.. code-block:: bash

MONGODB_URL='mongodb://127.0.0.1:27017' pytest tests/test_note_boards_mongo.py -v

.. warning::
כל הרצה יוצרת מסד עם שם ייחודי משלה (תחילית ``codebot_notes_it_``), וה-teardown מוודא שהשם תואם לתחילית **לפני** ``drop_database``. אל תכוונו את ``MONGODB_URL`` למסד שיש בו נתונים אמיתיים.

כיסוי בדיקות (pytest-cov)
--------------------------

Expand Down
13 changes: 10 additions & 3 deletions docs/user/sticky_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
- מומלץ להצמיד לעוגנים יציבים (כותרת/``id``) כשאפשר, לשמירה לאחר עריכות
* - עריכת תוכן
- קליק על פתק קיים ועריכת השדה
- התוכן מוגבל ל‑5,000 תווים; אין בחירת צבע בממשק והפתק צהוב אחיד
- התוכן מוגבל ל‑20,000 תווים; אין בחירת צבע בממשק והפתק צהוב אחיד
* - הזזה/מיקום מחדש
- גרירה קצרה של הפתק למיקום חדש בתצוגה
- אם הפתק מעוגן לעוגן יציב – ההצמדה תישמר, והמיקום החזותי יעדכן
Expand All @@ -44,7 +44,7 @@
מגבלות, פרטיות ואבטחה
-----------------------
- מגבלות שימוש: עד 200 פתקים ללוח, ועד 1000 פתקים לכל משתמש — התקרה השנייה חלה על כל הפתקים, גם אלה שעל קבצים. עד לגרסה זו שתיהן היו מתועדות אך נאכפו רק במסלול ה-MCP, וגם שם באופן חלקי; כעת הן נאכפות בשכבת ה-API. משתמשי אדמין פטורים.
- אורך פתק: עד 5,000 תווים.
- אורך פתק: עד 20,000 תווים. מעבר לתקרה השמירה **נדחית** ומוחזר ``content_too_long`` — עד לגרסה זו התוכן נחתך בשקט והתשובה הייתה "נשמר". הממשק מציג חיווי אדום עם מספר התווים בפועל כבר בזמן ההקלדה, לפני שמנסים לשמור.
- פרטיות: פתקים נשמרים לחשבון המשתמש בלבד, ואינם מוצגים לאחרים כברירת מחדל.

לוחות פתקים
Expand All @@ -64,7 +64,9 @@
* - מחיקת לוח
- הפתקים שעליו עוברים ללוח ברירת המחדל, והממשק מדווח כמה עברו. אם ההעברה לא הושלמה — הלוח **אינו** נמחק, כדי שאף פתק לא יתייתם.
* - מצב הפתק
- "מוצמד ללוח" (ברירת המחדל) — הפתק חלק מהמשטח וזז איתו. "צמוד למסך" — נשאר מול העיניים גם כשגוללים בלוח.
- "מוצמד ללוח" (ברירת המחדל) — הפתק חלק מהמשטח וזז איתו. "צמוד למסך" — נשאר מול העיניים גם כשגוללים בלוח. הכפתור ``📌`` בראש הפתק מחליף ביניהם, והוא דלוק רק כשהפתק צמוד למסך.
* - גבולות המשטח
- פתק אינו יוצא מגבולות הלוח. גרירה כלפי מעלה נעצרת בקצה העליון, כי הכותרת של הפתק היא גם ידית הגרירה — פתק שהכותרת שלו מעל הקצה היה נשאר תקוע.

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

Expand All @@ -77,6 +79,11 @@

שתי התנהגויות שכדאי להכיר: שורות ``- [ ]`` זהות מזוהות לפי **סדר ההופעה** שלהן, כך שאפשר לסמן כל אחת בנפרד; ושורת ``- [ ]`` בתוך בלוק קוד נספרת גם היא, כי הפתק אינו עובר פרסור מארקדאון מלא.

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

.. note::
התצוגה היא **חד-כיוונית**: ``content`` הופך ל-HTML, ולעולם לא להפך. הדברים היחידים שכותבים לתוכן הפתק הם תיבת העריכה עצמה וראוט הצ'קבוקס, שמקבל מספר סידורי ומצב רצוי — לא טקסט. לכן רינדור אינו יכול לאבד שורה.

תזכורות בפתקי לוח
~~~~~~~~~~~~~~~~~~
עובדות בדיוק כמו בפתקי קובץ. לחיצה על ההתראה או על הפעמון פותחת את הקישור הקבוע ``/note/<note_id>``, שמפנה למקום שבו הפתק באמת יושב — קובץ או לוח.
Expand Down
6 changes: 5 additions & 1 deletion mcp_server/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
import re
from typing import Any

# תקרת התוכן מיובאת ולא מוקלדת. עד היום MCP והוובאפ החזיקו כל אחד את המספר
# שלו, כך ששינוי באחד היה משאיר את השני אוכף ערך אחר — ואז אותו פתק נדחה
# בערוץ אחד ומתקבל בשני.
from sticky_notes_target import MAX_NOTE_CHARS as MAX_NOTE_CONTENT

MAX_PER_PAGE = 200
MAX_SEARCH_LIMIT = 100
MAX_COLLECTIONS_LIMIT = 500
Expand Down Expand Up @@ -270,7 +275,6 @@ def append_file(backend: Any, user_id: int, *, file_name: str, content: str) ->

# -- sticky notes ----------------------------------------------------------

MAX_NOTE_CONTENT = 5000
MAX_NOTES_PER_SCOPE = 200
MAX_ANCHOR_TEXT = 256
MAX_NOTE_LINE = 1_000_000
Expand Down
37 changes: 34 additions & 3 deletions sticky_notes_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,24 @@ class NoteTargetError(ValueError):


class NoteQuotaError(ValueError):
"""חריגה מתקרת הפתקים, או ספירה שלא הצליחה."""
"""חריגה מתקרת הפתקים, או ספירה שלא הצליחה.

שני המקרים מיוצגים כתת-מחלקות, ו**זהות השגיאה חיה בסוג ולא בטקסט**.
``except NoteQuotaError`` ממשיך לתפוס את שניהם, אבל מי שצריך להחזיר קוד
ללקוח תופס את התת-מחלקה ומחזיר ליטרל — ולא ``str(exc)``.

הסיבה: ``str`` על חריגה הוא הבטחה שאיש לא אוכף. ``raise`` אחד עתידי
שמשרשר נתיב או שאילתה מוצא אותם ללקוח, ו-CodeQL מסמן בדיוק את הזרימה
הזו. סוג של חריגה לא נושא טקסט שאפשר לדלוף.
"""


class NoteQuotaUnknown(NoteQuotaError):
"""הספירה נכשלה, ולכן אי אפשר לדעת אם יש מקום."""


class NoteQuotaExceeded(NoteQuotaError):
"""התקרה מלאה."""


#: מצבי המיקום של פתק. ``surface`` = מעוגן למשטח שמתחתיו (הלוח), ``screen`` =
Expand All @@ -44,6 +61,20 @@ class NoteQuotaError(ValueError):
BOARD_NOTE_MODES = ("surface", "screen")
DEFAULT_BOARD_MODE = "surface"

#: תקרת אורך התוכן של פתק, בתווים.
#:
#: **מקור האמת היחיד.** המספר הזה היה מוקלד ידנית ב-15 מקומות — 12 פעמים
#: ב-``webapp/sticky_notes_api.py``, פעם ב-``mcp_server/handlers.py``, פעם
#: ב-JS ופעמיים בתיעוד. משמעות הפיזור הזו היא שכל שינוי ערך משאיר מאחור
#: מסלול אחד שאוכף מספר אחר, ומי שנתקל בו רואה חיתוך בלי הסבר.
#:
#: **למה דווקא 20,000:** זו הנקודה שבה תקרת ההעברה (``max_b64_len=120000``
#: ב-``_decode_content_b64``) מפסיקה להיות תקרה שנייה נסתרת. 20,000 תווים
#: של אימוג'י — המקרה הכבד ביותר, ארבעה בתים לתו — נותנים 106,668 תווי
#: base64, כלומר עדיין מתחת ל-120,000. מעל זה צריך לשנות גם אותה, ואז אין
#: נקודת עצירה טבעית.
MAX_NOTE_CHARS = 20_000


def _clean(value: Any) -> str:
"""מזהה כמחרוזת מנורמלת. ``None``, ``''`` ומחרוזת רווחים — כולם ריקים."""
Expand Down Expand Up @@ -163,6 +194,6 @@ def check_note_quota(existing: Optional[int], cap: int, *, is_admin: bool = Fals
if is_admin:
return
if existing is None:
raise NoteQuotaError("note_quota_unknown")
raise NoteQuotaUnknown("note_quota_unknown")
if int(existing) >= int(cap):
raise NoteQuotaError("note_quota_exceeded")
raise NoteQuotaExceeded("note_quota_exceeded")
Loading
Loading