Skip to content

feat: Add automatic backup to Google Drive and local disk - #3138

Merged
amirbiron merged 24 commits into
mainfrom
claude/add-auto-backup-ds6iu
Apr 3, 2026
Merged

feat: Add automatic backup to Google Drive and local disk#3138
amirbiron merged 24 commits into
mainfrom
claude/add-auto-backup-ds6iu

Conversation

@amirbiron

Copy link
Copy Markdown
Owner

✨ תיאור קצר

הוספת מערכת גיבויים אוטומטיים מלאה לוובאפ:

  • גיבוי אוטומטי ל-Google Drive עם OAuth flow
  • גיבוי אוטומטי לדיסק המקומי של השרת
  • Scheduler daemon שרץ ברקע וסורק כל 5 דקות
  • שחזור אסינכרוני עם דיווח התקדמות real-time
  • ממשק משתמש בעברית בעמוד ההגדרות

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

Backend

  • webapp/backup_scheduler.py (חדש): Daemon thread שמנהל גיבויים אוטומטיים

    • סריקה כל 5 דקות של משתמשים עם schedule פעיל
    • ביצוע גיבויים ל-Drive ולדיסק לפי תדירות (יומי, שבועי, חודשי וכו')
    • ניקוי אוטומטי של גיבויים ישנים (retention 30 ימים, max 10 לכל משתמש)
  • webapp/drive_auth.py (חדש): Google Drive OAuth flow

    • /api/drive/auth - הפניה ל-Google consent screen
    • /api/drive/callback - קבלת authorization code והחלפה לטוקנים
    • /api/drive/status - בדיקת סטטוס חיבור
    • /api/drive/disconnect - ניתוק Drive
  • webapp/drive_backup_api.py (חדש): API endpoints לניהול גיבויים

    • /api/drive/schedule - הגדרת תדירות גיבוי ל-Drive
    • /api/drive/backup-now - גיבוי מיידי ל-Drive
    • /api/disk-backup/schedule - הגדרת תדירות גיבוי לדיסק
    • /api/disk-backup/now - גיבוי מיידי לדיסק
    • /api/disk-backup/status - סטטוס גיבויים לדיסק
  • webapp/backup_api.py (שונה):

    • הוספת /api/backup/restore-async - שחזור אסינכרוני עם דיווח התקדמות
    • הוספת /api/backup/restore-progress/<id> - polling על מצב השחזור
    • ThreadPoolExecutor לביצוע שחזור ברקע
  • services/personal_backup_service.py (שונה):

    • הוספת progress_cb callback ל-restore_user_data() לדיווח התקדמות
    • דיווח שלבים: קריאת מטאדאטה, שחזור קבצים, שחזור מסדי נתונים וכו'
  • webapp/app.py (שונה):

    • הפעלת backup scheduler daemon בעת startup
    • רישום blueprints: drive_auth_bp ו-drive_backup_bp

Frontend

  • webapp/templates/settings.html (שונה):
    • הוספת סעיף "גיבוי אוטומטי ל-Drive" עם:
      • כפתור חיבור Google Drive
      • בחירת תדירות גיבוי (

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5

- גיבוי אוטומטי ל-Google Drive עם OAuth redirect flow מהוובאפ
- גיבוי אוטומטי לדיסק (/var/data/repos/backups) עם ניקוי retention
- Scheduler thread ברקע שסורק כל 5 דקות ומריץ גיבויים מתוזמנים
- שחזור אסינכרוני עם progress bar ו-batching (מנות של 20)
- תיקון אינדקס כפול ב-bookmarks (file_bookmarks_user_file_idx)
- UI חדש בהגדרות: סקשנים ל-Drive, דיסק, ותדירות גיבוי

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

🧯 Dangerous deletes guard report

Policy: see .cursorrules — dangerous deletions are blocked unless wrapped safely.

Summary:

  • Flagged findings (blocking): 0
    0
  • Excluded matches (not blocking): 15
  • Total matches (all files): 129

Flagged findings (file:line:snippet):
(none)

Excluded matches (by path pattern)
./Dockerfile:42:    rm -rf /var/lib/apt/lists/*
./Dockerfile:121:    rm -rf /var/lib/apt/lists/*
./node_modules/katex/src/fonts/Makefile:139:	rm -rf pfa ff otf ttf woff woff2
./node_modules/katex/package.json:153:    "build": "rimraf dist/ && mkdirp dist && cp README.md dist && rollup -c --failAfterWarnings && webpack && node update-sri.js package dist/README.md",
./node_modules/mermaid/dist/mermaid.min.js:1524:`,"getStyles"),c1e=RQe});var h1e={};dr(h1e,{diagram:()=>NQe});var NQe,f1e=N(()=>{"use strict";$ge();a1e();l1e();u1e();NQe={parser:Fge,db:n1e,renderer:o1e,styles:c1e}});var m1e,g1e=N(()=>{"use  … [truncated]
./node_modules/mermaid/dist/mermaid.min.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values fo … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm/chunk-2M32CCKP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence d … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs:1:var r={name:"mermaid",version:"11.12.0",description:"Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graph … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequen … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.core/chunk-KS23V3DP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence  … [truncated]
./node_modules/mermaid/dist/mermaid.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values for tr … [truncated]
./README.md:842:find . -name "__pycache__" -exec rm -rf {} +
./docs/DOCUMENTATION_GUIDE.md:453:rm -rf _build
./docs/Makefile:24:	rm -rf $(BUILDDIR)
./webapp/static/js/md_preview.bundle.js.map:4:  "sourcesContent": ["// Markdown-it plugin to render GitHub-style task lists; see\n//\n// https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments\n// https://github.com/blog/1825-t … [truncated]

@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

⏱️ Performance report

(No performance test durations collected. Mark tests with @pytest.mark.performance.)

Comment thread webapp/drive_auth.py
base = os.getenv("WEBAPP_BASE_URL", "").rstrip("/")
if not base:
base = request.url_root.rstrip("/")
return redirect(f"{base}/settings?{query}#backup-section")

Check warning

Code scanning / CodeQL

URL redirection from remote source Medium

Untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI 4 months ago

General approach: Ensure that redirects never use an attacker-controlled host or scheme. Keep user input restricted to inert parts of the URL (like query parameters) and derive the redirect base from a trusted configuration value or from the current app context in a way that cannot be overridden by tampered headers.

Best concrete fix here:

  • Make _settings_redirect build the target URL using url_for with _external=True so that the host/scheme come from Flask’s configuration rather than directly from request.url_root or environment variables that might be mis-set.
  • Keep query strictly as part of the query string and do not allow it to alter the base path or host.
  • To avoid changing external behavior, still redirect to /settings with #backup-section and arbitrary query parameters passed in as query.

A minimal change that achieves this:

  1. Replace manual string concatenation of base and path with a call to url_for('settings', _external=True) (or if a specific endpoint name is not known, at least construct a relative path instead of absolute with request.url_root). However, we are constrained not to assume endpoints outside the snippet, so we should avoid introducing url_for('settings', ...) if we don't know that endpoint exists.
  2. Instead, we can:
    • Always use a relative redirect path (/settings?...#backup-section) rather than a fully-qualified URL with host and scheme. Relative redirects are resolved by the browser against the current origin, so they can't redirect off-site.
    • Keep using WEBAPP_URL elsewhere if needed, but for _settings_redirect, we return redirect(f"/settings?{query}#backup-section").

This preserves the visible behavior (path /settings, same query, fragment #backup-section) but removes any dependence of the redirect URL on request.url_root or environment-based host, eliminating any open-redirect risk. No new imports are needed.

Concretely, in webapp/drive_auth.py, update _settings_redirect:

  • Remove the logic building base from WEBAPP_URL or request.url_root.
  • Return redirect(f"/settings?{query}#backup-section").

Suggested changeset 1
webapp/drive_auth.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/webapp/drive_auth.py b/webapp/drive_auth.py
--- a/webapp/drive_auth.py
+++ b/webapp/drive_auth.py
@@ -249,7 +249,6 @@
 
 def _settings_redirect(query: str) -> Response:
     """Redirect back to settings page with query params."""
-    base = os.getenv("WEBAPP_URL", "").rstrip("/")
-    if not base:
-        base = request.url_root.rstrip("/")
-    return redirect(f"{base}/settings?{query}#backup-section")
+    # Use a relative path so the redirect always stays on the same origin.
+    # The `query` argument is expected to be pre-encoded key=value pairs.
+    return redirect(f"/settings?{query}#backup-section")
EOF
@@ -249,7 +249,6 @@

def _settings_redirect(query: str) -> Response:
"""Redirect back to settings page with query params."""
base = os.getenv("WEBAPP_URL", "").rstrip("/")
if not base:
base = request.url_root.rstrip("/")
return redirect(f"{base}/settings?{query}#backup-section")
# Use a relative path so the redirect always stays on the same origin.
# The `query` argument is expected to be pre-encoded key=value pairs.
return redirect(f"/settings?{query}#backup-section")
Copilot is powered by AI and may make mistakes. Always verify output.
@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully!

To view locally:

  1. Download the artifacts
  2. Extract the zip file
  3. Open index.html in your browser

@sentry

sentry Bot commented Apr 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.51613% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
services/personal_backup_service.py 68.96% 4 Missing and 5 partials ⚠️
database/bookmarks_manager.py 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread webapp/backup_api.py Outdated
Comment thread webapp/backup_scheduler.py Outdated
Comment thread webapp/backup_api.py Outdated
…leanup

- Race condition ב-restore_progress: snapshot כל השדות + pop בתוך אותו lock
- Disk backup: עדכון last_backup_at רק בהצלחה (ok=True)
- דליפת זיכרון: cleanup entries ישנים מעל שעה ב-_active_restores

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_api.py
מעביר את בדיקת user_id לפני ה-pop מ-_active_restores
כדי שמשתמש לא-מורשה לא ימחק entry של משתמש אחר.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_api.py
ה-before_request handler הגביל גודל רק ל-restore הסינכרוני.
עכשיו מכסה גם את restore_backup_async שדרכו עובר כל ה-traffic.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/drive_auth.py Outdated
Comment thread webapp/drive_auth.py
Comment thread webapp/backup_api.py Outdated
- drive_auth: כל ערכי error ב-redirects עוברים _url_encode למניעת URL שבור
- backup_api: תוצאת restore לא נמחקת מיד אלא מסומנת fetched_at,
  ונמחקת רק אחרי 5 דקות grace — כך שאם ה-response אבד ברשת
  הלקוח יכול לקרוא שוב בלי לקבל 404

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_api.py Outdated
Comment thread webapp/drive_backup_api.py
_cleanup_stale_restores היה מוחק entries שעבר שעה מאז יצירתם,
בלי לבדוק אם הם עדיין בסטטוס running. עכשיו entries שרצים
לא נמחקים ע"י age check — רק entries שסיימו (done/error).

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_api.py Outdated
Comment thread webapp/backup_api.py Outdated
Comment thread webapp/app.py
1. backup_api — העברת _active_restores מ-dict בזיכרון ל-collection
   restore_jobs ב-MongoDB. פותר:
   - High: state לא אבד ב-restart או בין workers
   - Low: stuck running entries נמחקים ע"י TTL index (שעה)

2. backup_scheduler — _scan_and_run משתמש ב-find_one_and_update
   לתפיסה אטומית של משתמשים. רק scheduler אחד מריץ גיבוי
   לכל משתמש, גם עם N gunicorn workers.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py
אם uid או schedule_key חסרים אחרי ה-claim האטומי, מחזיר את
schedule_next_at לערך המקורי כדי למנוע תקיעה ב-2099.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py
כשגיבוי נכשל, schedule_next_at נדחה ב-SCAN_INTERVAL קדימה (5 דקות)
במקום להחזיר את הערך המקורי שעדיין $lte now — מה שגרם ל-while True
לתפוס את אותו משתמש שוב מיד (לולאה אינסופית).

אותו תיקון ב-_reset_drive_schedule ו-_reset_disk_schedule.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/drive_backup_api.py Outdated
Comment thread webapp/backup_api.py Outdated
…handling

באג 1 (High): כיבוי schedule ל-Drive ("off") לא עצר את ה-scheduler
כי save_drive_prefs עושה merge ושדות legacy כמו schedule: "daily" נשארים.
תיקון defense-in-depth ב-3 שכבות:
- $unset שדות legacy בכיבוי (drive_backup_api.py)
- $ne "off" + $ne None בשאילתת הסריקה (backup_scheduler.py)
- early return ב-_extract_schedule_key כש-schedule_key=="off"

באג 2 (Low): _complete_restore_job חסר error handling.
אם הקריאה נכשלת ב-success path, היא נופלת ל-except ומדווחת
שחזור מוצלח כנכשל. תיקון:
- try/except ב-_complete_restore_job
- הפרדת תוצאת השחזור מהעדכון ל-DB

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/drive_auth.py
Comment thread webapp/backup_api.py
Comment thread webapp/drive_backup_api.py
Comment thread webapp/backup_scheduler.py
…sert, null guard)

1. drive_auth.py: _settings_redirect return type -> Response (לא str)
2. backup_api.py: בדיקת result.get("ok") is False לפני סימון "done"
   כדי לא לדווח שחזור כושל כהצלחה
3. drive_backup_api.py: הסרת upsert=True ב-set_disk_schedule
   כדי לא ליצור מסמך user חלקי
4. backup_scheduler.py: הוספת $ne None לשאילתת disk schedule_next_at
   בהתאמה לתיקון שנעשה בשאילתת Drive

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py Outdated
Comment thread webapp/templates/settings.html
1. backup_scheduler.py: _extract_schedule_key — val.get("key") or val.get("value")
   גורם ל-short-circuit כש-key הוא string לא-ריק אבל לא תקין,
   ו-value לעולם לא נבדק. הוחלף בלולאה על שני השדות בנפרד.

2. settings.html: לולאת polling של restore ללא timeout —
   אם ה-job תקוע ב-running, הדפדפן סובב עד שעה.
   הוסף guard של 10 דקות (300 סיבובים × 2 שניות).

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py
Comment thread webapp/drive_backup_api.py Outdated
1. backup_scheduler.py: trigger_drive_backup_now ו-trigger_disk_backup_now
   לא עדכנו last_backup_at — ה-UI הציג תאריך ישן אחרי "גבה עכשיו".
   הוסף עדכון $set אחרי הצלחה.

2. drive_backup_api.py: save_drive_prefs עושה read-modify-write
   שיכול לדרוס sentinel של ה-scheduler.
   הוחלף ב-$set ישיר על שדות ספציפיים (dot notation),
   כמו שכבר עושים ב-set_disk_schedule.
   $unset ל-legacy fields שולב באותו update_one.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py
_cleanup_disk_backups השתמש בלוגיקת OR — גיבוי נמחק אם עבר max
*או* אם עבר retention. כך משתמש שלא גיבה 30+ יום איבד את כל
הגיבויים הקיימים (חוץ מהאחרון).

שונה ל: ה-N הכי חדשים (max) נשמרים תמיד. מחיקה חלה רק
על גיבויים מעבר ל-max שגם עברו את ה-retention.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/drive_auth.py
…erwrite

drive_callback השתמש ב-save_drive_prefs (read-modify-write) לשמירת
drive_email, מה שיכול לדרוס את ה-sentinel של ה-scheduler.
הוחלף ב-$set ישיר על drive_prefs.drive_email — אותו pattern
שכבר תוקן ב-set_drive_schedule.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/drive_auth.py
Comment thread webapp/backup_api.py
1. drive_auth.py: drive_callback השתמש ב-@_require_auth שמחזיר
   JSON 401 — בדפדפן (redirect מ-Google) המשתמש רואה JSON גולמי.
   הוחלף בבדיקת session ידנית עם redirect לדף הגדרות + הודעת שגיאה.

2. backup_api.py: _complete_restore_job איפס progress ל-0 בשגיאה,
   מה שגורם לקפיצה אחורה בפרוגרס בר (65% → 0%).
   בשגיאה, progress לא נדרס — נשמר הערך האחרון.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py
Comment thread webapp/backup_scheduler.py
1. get_disk_backup_info: total_size חישב רק את 20 הראשונים
   אבל count החזיר את כולם — גודל מטעה ב-UI.
   עכשיו total_size סוכם את כל הקבצים.

2. sentinel orphan recovery: אם ה-process קרס בין claim לעדכון,
   ה-sentinel "2099-01-01" נשאר לנצח והגיבוי תקוע.
   הוסף _recover_orphaned_sentinels שרץ בתחילת כל סריקה —
   מאפס sentinels ל-retry_next_at. הוצא הערך הקשיח לקבוע
   _SENTINEL_VALUE.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py Outdated
Comment thread webapp/backup_scheduler.py
1. sentinel: הוחלף מערך קבוע "2099" ל-now + 30 דקות (SENTINEL_TTL).
   - אם ה-backup מסתיים בזמן — הערך מוחלף כרגיל
   - אם ה-process קרס — אחרי 30 דקות הערך פוקע ($lte now)
     ונתפס מחדש טבעית
   - הוסר _recover_orphaned_sentinels שאיפס גם backups פעילים
     של workers אחרים

2. trigger_drive_backup_now: הוסר עדכון כפול של last_backup_at.
   perform_scheduled_backup כבר מעדכן דרך save_drive_prefs —
   עדכון שני ב-$set גרם ל-race condition.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py
_extract_schedule_key בscheduler היה כפילות של extract_schedule_key
מ-handlers/drive/utils.py עם סדר שדות שונה ותמיכה חלקית
(חסר scheduleKey, name). עכשיו משתמש בפונקציה המשותפת
עם ולידציה כנגד SCHEDULE_INTERVALS ובדיקת "off".

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread database/bookmarks_manager.py
האינדקס (user_id, file_id) הוחלף מ-user_file_lookup
ל-file_bookmarks_user_file_idx, אבל הישן לא נמחק.
MongoDB לא מוחק אינדקס ישן אוטומטית כשיוצרים חדש
באותם שדות עם שם אחר — נשארים שני אינדקסים.
הוסף drop_index("user_file_lookup") ליד ה-drop הקיים.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py
_extract_schedule_key תפס ImportError והחזיר None תמיד,
מה שגרם ל-scheduler לתפוס משתמשים ולאפס מיד (sentinel reset)
בלי להריץ גיבוי — שקט ולנצח.
הוסף fallback מקומי שבודק schedule_key, scheduleKey, schedule
ישירות מ-drive_prefs כשה-import נכשל.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
Comment thread webapp/backup_scheduler.py
_extract_schedule_key נקרא מחוץ ל-try/except בלולאת הסריקה.
אם זורק exception (למשל מתוך extract_schedule_key המיובא),
כל הלולאה נעצרת — ה-sentinel לא מאופס והמשתמשים הנותרים נדלגים.

תיקון:
- עטיפה ב-try/except סביב _extract_schedule_key בלולאת הסריקה
- שינוי except ImportError ל-except Exception בפונקציה עצמה
  כך שגם שגיאות מתוך extract_schedule_key נתפסות

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread webapp/backup_api.py
Comment thread webapp/backup_scheduler.py
1. backup_api.py: restore_backup_async לא בדק אם כבר רץ restore
   לאותו user. שתי קריאות מקבילות גורמות לכתיבות כפולות ל-DB.
   הוסף בדיקת status: "running" לפני יצירת job חדש (409).

2. backup_scheduler.py: _scan_drive_backups הוא while True
   שרץ לפני _scan_disk_backups. אחרי השבתה ארוכה, הרבה
   drive backups בתור גורמים ל-disk להירעב שעות.
   הוסף MAX_BACKUPS_PER_SCAN (10) — כל סוג מעבד עד 10
   בסריקה, והנותרים ייתפסו בסריקה הבאה.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
@amirbiron
amirbiron merged commit fe84f07 into main Apr 3, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants