Skip to content

feat: add folder organization support to collections - #3143

Merged
amirbiron merged 18 commits into
mainfrom
claude/collections-folder-support-iBk06
Apr 8, 2026
Merged

feat: add folder organization support to collections#3143
amirbiron merged 18 commits into
mainfrom
claude/collections-folder-support-iBk06

Conversation

@amirbiron

Copy link
Copy Markdown
Owner

✨ תיאור קצר

הוספת תמיכה בארגון פריטים בתיקיות (folders) בתוך אוספים. משתמשים יכולים ליצור, לשנות שם, למחוק ותיקיות, ולהעביר פריטים בין תיקיות באמצעות drag & drop.

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

  • קוד (Backend)
  • Frontend (JavaScript/UI)
  • מסד נתונים/מיגרציות

פירוט:

Backend (database/collections_manager.py):

  • הוספת CRUD operations לתיקיות: create_folder, rename_folder, delete_folder, reorder_folders
  • הוספת שדה folder לפריטים (items) עם ערך ברירת מחדל '' (root)
  • עדכון אינדקסים ב-MongoDB: שדה folder נוסף לאינדקס ייחודי של פריטים
  • מיגרציה: הסרת אינדקס ישן unique_item (ללא folder) והחלפתו ב-unique_item_folder
  • הוספת validation לשמות תיקיות (max 60 תווים, max 20 תיקיות לאוסף)
  • תמיכה בסמלים (icons) לתיקיות מרשימה מוגדרת מראש
  • בעת מחיקת תיקיה: העברת פריטים ל-root, טיפול בכפילויות

API (webapp/collections_api.py):

  • 4 endpoints חדשים:
    • POST /api/collections/<id>/folders - יצירת תיקיה
    • PUT /api/collections/<id>/folders/<name> - עדכון תיקיה
    • DELETE /api/collections/<id>/folders/<name> - מחיקת תיקיה
    • PUT /api/collections/<id>/folders/reorder - סידור תיקיות
  • עדכון GET /api/collections/<id>/items עם תמיכה ב-folder_filter query parameter
  • ניקוי cache בעת שינויים

Frontend (webapp/static/js/collections.js):

  • הוספת 4 methods ל-API client: createFolder, updateFolder, deleteFolder, reorderFolders
  • רינדור פריטים מקובצים לפי תיקיות (accordion-style עם <details>)
  • UI לניהול תיקיות: כפתור "תיקיה חדשה", עריכה inline, מחיקה
  • Drag & drop תמיכה: העברת פריטים בין תיקיות ול-root
  • Modal dialog ליצירת תיקיה עם בחירת icon
  • רשימה של 20 סמלים מותרים לתיקיות

Shared Collections (webapp/static/js/collection_shared.js):

  • עדכון רינדור פריטים משותפים לתמיכה בתיקיות (grouping)

CSS (webapp/static/css/collections.css):

  • סגנונות חדשים ל-folder sections: .collection-folder, .collection-folder__header, וכו'
  • תמיכה ב-drop target visual feedback
  • responsive design

🧪 בדיקות

  • בדיקה ידנית של יציר

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

הוספת אפשרות לארגן קבצים בתוך אוסף לתיקיות (רמה אחת).
קובץ יכול להופיע גם ב-root וגם בתיקיה (מקומות מרובים).
תיקיות מוצגות כקבוצות מתקפלות (accordion) עם drag & drop.

- Database: שדה folder ב-collection_items, מטאדאטה folders ב-user_collections
- API: endpoints חדשים ל-CRUD תיקיות (create/rename/delete/reorder)
- Frontend: רינדור accordion, מודאל יצירת תיקיה, inline rename, drag & drop
- CSS: סגנונות לתיקיות מתקפלות עם RTL ו-responsive
- Shared view: תצוגת תיקיות בצפייה ציבורית (read-only)

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

github-actions Bot commented Apr 8, 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 8, 2026

Copy link
Copy Markdown

⏱️ Performance report

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

@github-actions

github-actions Bot commented Apr 8, 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 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 14.22414% with 199 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
database/collections_manager.py 14.22% 195 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

Comment thread database/collections_manager.py Outdated
Comment thread webapp/collections_api.py
Comment thread webapp/static/js/collections.js
1. Mixed mode dedup: מפתח dedup חזר ל-2-tuple (source, file_name) בלבד
   כדי למנוע כפילויות כשקובץ ידני בתיקיה ומחושב ב-root
2. Route conflict: הוספת ולידציה לשמות תיקיות שמורים ("reorder")
   כדי למנוע התנגשות עם endpoint סידור תיקיות
3. Drag reorder: איסוף פריטים מכל ה-sections (root + תיקיות)
   כדי לא לאבד פריטים מתיקיות אחרות בעת שליחת סדר לשרת

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py Outdated
Comment thread webapp/static/js/collections.js Outdated
Comment thread webapp/static/js/collections.js Outdated
1. Missing migration backfill: פריטים קיימים ללא folder מקבלים "" (root)
   כדי למנוע אי-התאמה בין null ל-"" באינדקס MongoDB
2. Cross-folder drag metadata: endpoint חדש move_item_folder שמעביר
   פריט בין תיקיות עם שמירת כל המטאדאטה (notes, tags, pinned)
   במקום add+remove שמאבד מידע
3. Root drop zone: container ה-root מרונדר תמיד (גם כשריק)
   כדי שתמיד יהיה אזור drop לגרירת פריטים חזרה ל-root

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread webapp/collections_api.py Fixed
move_item_folder החזיר str(e) ישירות למשתמש — חשיפת מידע רגיש.
מוחלף בהודעת שגיאה גנרית + לוג פנימי.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread webapp/static/js/collections.js Outdated
Comment thread webapp/static/js/collections.js
Comment thread webapp/static/js/collections.js
1. Empty state: כשאין פריטים ואין תיקיות, buildFolderGroupedHtml מחזיר
   ריק כדי שהודעת "אין פריטים" תוצג כמו לפני השינוי
2. Folder drop race: סימון dropInProgress בעת drop על תיקיה/root
   כדי שדragend לא ישלח reorder מיותר עם סדר ישן
3. Add-folder button: מוסתר ב-workspace collections כי תיקיות
   לא רלוונטיות שם

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py
Comment thread database/collections_manager.py
When moving an item to a folder where the same file already exists,
the unique index throws DuplicateKeyError. Now catches it gracefully
by removing the source item (target already has the file).

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread webapp/static/js/collections.js
Comment thread database/collections_manager.py
The workspace board's remove handler was missing the folder field
in the removeItems payload, while the regular view handler included it.
This could cause silent removal failures for items in non-root folders.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py Outdated
Comment thread database/collections_manager.py
…olders

1. move_item_folder: on DuplicateKeyError, merge source metadata
   (note, tags, pinned, workspace_state) into target before deleting
   the source item, preventing silent metadata loss.

2. Mixed-mode dedup: manual items no longer deduplicated against each
   other across folders. The seen set now only filters computed items
   that duplicate manual ones.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py Outdated
The truthiness check 'if val:' skipped merging pinned=False and
empty tags/notes. Changed to 'is not None' to handle boolean and
empty-string fields correctly.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py
Comment thread webapp/static/js/collections.js
…ection drag

1. move_item_folder: call _refresh_collection_counts after deleting
   source item on DuplicateKeyError so items_count stays accurate.

2. Cross-collection sidebar drag: strip folder from payload when
   adding item to a different collection, preventing orphaned folders
   in the target collection.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread webapp/static/js/collections.js Outdated
Comment thread database/collections_manager.py
1. Folder/root drop handlers: set dropInProgress only after
   validating drag data and checking same-folder no-op, preventing
   leaked drag state on cancelled drops.

2. rename_folder fallback loop: wrap each update_one in its own
   try/except so a single DuplicateKeyError doesn't abort remaining
   items.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py
Comment thread database/collections_manager.py Outdated
…data

1. Add DuplicateKeyError stub class in the pymongo-unavailable fallback
   block to prevent NameError in move_item_folder.

2. Metadata merge on duplicate move now only copies meaningful values
   (non-empty note, non-empty tags, pinned=True, non-empty workspace_state)
   to avoid overwriting destination metadata with source defaults.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py Outdated
workspace_state is a string (e.g. "todo", "in_progress", "done"),
not a dict. The isinstance(dict) check was always False, silently
dropping workspace_state during duplicate-key folder moves.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread webapp/collections_api.py Outdated
Comment thread webapp/static/js/collections.js Outdated
…ailure

1. create_folder/update_folder: replace sanitize_input (which calls
   html.escape) with plain strip+truncate. The DB layer validates
   folder names, and the frontend escapes on render — no need to
   HTML-encode before storage.

2. Folder drop handlers: wrap moveItemFolder calls in try/finally
   with clearActiveDragContext() to prevent leaked drag state when
   the API call fails.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py
After the rename fallback loop, any items still carrying the old
folder name (due to DuplicateKeyError or other failures) are moved
to root folder to prevent orphaned items pointing to a non-existent
folder in the collection metadata.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py Outdated
Added class-level _migration_done flag so the drop_index and
update_many backfill operations only execute on first instantiation,
not on every API request.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread database/collections_manager.py
Fetch the destination item before merging so that existing metadata
(note, tags, pinned, workspace_state) on the target is preserved.
Source values are only copied when the target field is empty/missing.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G
Comment thread webapp/static/js/collections.js
The touch endHandler in wireDnd was not updated with folder support.
Now traverses up to #collectionItems, uses the correct selector for
all sections, and includes folder field in the reorder payload.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

@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.

Reviewed by Cursor Bugbot for commit 74bca69. Configure here.

Comment thread webapp/static/js/collections.js
Comment thread database/collections_manager.py
@amirbiron
amirbiron merged commit ebd1ddf into main Apr 8, 2026
25 of 26 checks passed
amirbiron added a commit that referenced this pull request Apr 8, 2026
* feat: תמיכה בתיקיות בתוך אוספים (Collections Folders)

הוספת אפשרות לארגן קבצים בתוך אוסף לתיקיות (רמה אחת).
קובץ יכול להופיע גם ב-root וגם בתיקיה (מקומות מרובים).
תיקיות מוצגות כקבוצות מתקפלות (accordion) עם drag & drop.

- Database: שדה folder ב-collection_items, מטאדאטה folders ב-user_collections
- API: endpoints חדשים ל-CRUD תיקיות (create/rename/delete/reorder)
- Frontend: רינדור accordion, מודאל יצירת תיקיה, inline rename, drag & drop
- CSS: סגנונות לתיקיות מתקפלות עם RTL ו-responsive
- Shared view: תצוגת תיקיות בצפייה ציבורית (read-only)

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים בתיקיות אוספים (Bugbot PR #3143)

1. Mixed mode dedup: מפתח dedup חזר ל-2-tuple (source, file_name) בלבד
   כדי למנוע כפילויות כשקובץ ידני בתיקיה ומחושב ב-root
2. Route conflict: הוספת ולידציה לשמות תיקיות שמורים ("reorder")
   כדי למנוע התנגשות עם endpoint סידור תיקיות
3. Drag reorder: איסוף פריטים מכל ה-sections (root + תיקיות)
   כדי לא לאבד פריטים מתיקיות אחרות בעת שליחת סדר לשרת

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים נוספים בתיקיות אוספים (Bugbot סבב 2)

1. Missing migration backfill: פריטים קיימים ללא folder מקבלים "" (root)
   כדי למנוע אי-התאמה בין null ל-"" באינדקס MongoDB
2. Cross-folder drag metadata: endpoint חדש move_item_folder שמעביר
   פריט בין תיקיות עם שמירת כל המטאדאטה (notes, tags, pinned)
   במקום add+remove שמאבד מידע
3. Root drop zone: container ה-root מרונדר תמיד (גם כשריק)
   כדי שתמיד יהיה אזור drop לגרירת פריטים חזרה ל-root

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: הסתרת פרטי exception מתגובת API (CodeQL)

move_item_folder החזיר str(e) ישירות למשתמש — חשיפת מידע רגיש.
מוחלף בהודעת שגיאה גנרית + לוג פנימי.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים נוספים בתיקיות אוספים (Bugbot סבב 3)

1. Empty state: כשאין פריטים ואין תיקיות, buildFolderGroupedHtml מחזיר
   ריק כדי שהודעת "אין פריטים" תוצג כמו לפני השינוי
2. Folder drop race: סימון dropInProgress בעת drop על תיקיה/root
   כדי שדragend לא ישלח reorder מיותר עם סדר ישן
3. Add-folder button: מוסתר ב-workspace collections כי תיקיות
   לא רלוונטיות שם

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: handle DuplicateKeyError in move_item_folder

When moving an item to a folder where the same file already exists,
the unique index throws DuplicateKeyError. Now catches it gracefully
by removing the source item (target already has the file).

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add missing folder field to workspace remove payload

The workspace board's remove handler was missing the folder field
in the removeItems payload, while the regular view handler included it.
This could cause silent removal failures for items in non-root folders.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: merge metadata on duplicate move & allow same file in multiple folders

1. move_item_folder: on DuplicateKeyError, merge source metadata
   (note, tags, pinned, workspace_state) into target before deleting
   the source item, preventing silent metadata loss.

2. Mixed-mode dedup: manual items no longer deduplicated against each
   other across folders. The seen set now only filters computed items
   that duplicate manual ones.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: use 'is not None' for metadata merge in move_item_folder

The truthiness check 'if val:' skipped merging pinned=False and
empty tags/notes. Changed to 'is not None' to handle boolean and
empty-string fields correctly.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: refresh counts after duplicate move & strip folder on cross-collection drag

1. move_item_folder: call _refresh_collection_counts after deleting
   source item on DuplicateKeyError so items_count stays accurate.

2. Cross-collection sidebar drag: strip folder from payload when
   adding item to a different collection, preventing orphaned folders
   in the target collection.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: move dropInProgress after validation & resilient rename fallback

1. Folder/root drop handlers: set dropInProgress only after
   validating drag data and checking same-folder no-op, preventing
   leaked drag state on cancelled drops.

2. rename_folder fallback loop: wrap each update_one in its own
   try/except so a single DuplicateKeyError doesn't abort remaining
   items.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add DuplicateKeyError fallback stub & only merge meaningful metadata

1. Add DuplicateKeyError stub class in the pymongo-unavailable fallback
   block to prevent NameError in move_item_folder.

2. Metadata merge on duplicate move now only copies meaningful values
   (non-empty note, non-empty tags, pinned=True, non-empty workspace_state)
   to avoid overwriting destination metadata with source defaults.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: correct workspace_state type check from dict to str in merge

workspace_state is a string (e.g. "todo", "in_progress", "done"),
not a dict. The isinstance(dict) check was always False, silently
dropping workspace_state during duplicate-key folder moves.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: prevent double HTML-escaping of folder names & cleanup drag on failure

1. create_folder/update_folder: replace sanitize_input (which calls
   html.escape) with plain strip+truncate. The DB layer validates
   folder names, and the frontend escapes on render — no need to
   HTML-encode before storage.

2. Folder drop handlers: wrap moveItemFolder calls in try/finally
   with clearActiveDragContext() to prevent leaked drag state when
   the API call fails.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: move orphaned items to root after folder rename failures

After the rename fallback loop, any items still carrying the old
folder name (due to DuplicateKeyError or other failures) are moved
to root folder to prevent orphaned items pointing to a non-existent
folder in the collection metadata.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: run backfill migration only once per process

Added class-level _migration_done flag so the drop_index and
update_many backfill operations only execute on first instantiation,
not on every API request.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: only merge source metadata into target when target field is empty

Fetch the destination item before merging so that existing metadata
(note, tags, pinned, workspace_state) on the target is preserved.
Source values are only copied when the target field is empty/missing.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add folder data to touch handler reorder payload

The touch endHandler in wireDnd was not updated with folder support.
Now traverses up to #collectionItems, uses the correct selector for
all sections, and includes folder field in the reorder payload.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: improve folder drag-and-drop reliability

1. Fix dragleave flicker on folder headers by checking relatedTarget
   (same pattern used by sidebar drop handlers).
2. Set dropEffect='move' on dragover for visual feedback.
3. Make entire card draggable (not just the handle) so users can
   drag from anywhere on the card to move it between folders.
4. Add stopPropagation on drop to prevent details/summary toggle.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: prevent duplicate folder event handlers & add dragend on card

1. Use AbortController in wireFolderEvents to remove all prior
   listeners before re-registering on each render, preventing
   duplicate click/drag handlers from stacking up.

2. Add dragend handler on the card element (not just the handle)
   so drag cleanup runs correctly when dragging from card body.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

---------

Co-authored-by: Claude <noreply@anthropic.com>
amirbiron added a commit that referenced this pull request Apr 8, 2026
* feat: תמיכה בתיקיות בתוך אוספים (Collections Folders)

הוספת אפשרות לארגן קבצים בתוך אוסף לתיקיות (רמה אחת).
קובץ יכול להופיע גם ב-root וגם בתיקיה (מקומות מרובים).
תיקיות מוצגות כקבוצות מתקפלות (accordion) עם drag & drop.

- Database: שדה folder ב-collection_items, מטאדאטה folders ב-user_collections
- API: endpoints חדשים ל-CRUD תיקיות (create/rename/delete/reorder)
- Frontend: רינדור accordion, מודאל יצירת תיקיה, inline rename, drag & drop
- CSS: סגנונות לתיקיות מתקפלות עם RTL ו-responsive
- Shared view: תצוגת תיקיות בצפייה ציבורית (read-only)

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים בתיקיות אוספים (Bugbot PR #3143)

1. Mixed mode dedup: מפתח dedup חזר ל-2-tuple (source, file_name) בלבד
   כדי למנוע כפילויות כשקובץ ידני בתיקיה ומחושב ב-root
2. Route conflict: הוספת ולידציה לשמות תיקיות שמורים ("reorder")
   כדי למנוע התנגשות עם endpoint סידור תיקיות
3. Drag reorder: איסוף פריטים מכל ה-sections (root + תיקיות)
   כדי לא לאבד פריטים מתיקיות אחרות בעת שליחת סדר לשרת

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים נוספים בתיקיות אוספים (Bugbot סבב 2)

1. Missing migration backfill: פריטים קיימים ללא folder מקבלים "" (root)
   כדי למנוע אי-התאמה בין null ל-"" באינדקס MongoDB
2. Cross-folder drag metadata: endpoint חדש move_item_folder שמעביר
   פריט בין תיקיות עם שמירת כל המטאדאטה (notes, tags, pinned)
   במקום add+remove שמאבד מידע
3. Root drop zone: container ה-root מרונדר תמיד (גם כשריק)
   כדי שתמיד יהיה אזור drop לגרירת פריטים חזרה ל-root

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: הסתרת פרטי exception מתגובת API (CodeQL)

move_item_folder החזיר str(e) ישירות למשתמש — חשיפת מידע רגיש.
מוחלף בהודעת שגיאה גנרית + לוג פנימי.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים נוספים בתיקיות אוספים (Bugbot סבב 3)

1. Empty state: כשאין פריטים ואין תיקיות, buildFolderGroupedHtml מחזיר
   ריק כדי שהודעת "אין פריטים" תוצג כמו לפני השינוי
2. Folder drop race: סימון dropInProgress בעת drop על תיקיה/root
   כדי שדragend לא ישלח reorder מיותר עם סדר ישן
3. Add-folder button: מוסתר ב-workspace collections כי תיקיות
   לא רלוונטיות שם

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: handle DuplicateKeyError in move_item_folder

When moving an item to a folder where the same file already exists,
the unique index throws DuplicateKeyError. Now catches it gracefully
by removing the source item (target already has the file).

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add missing folder field to workspace remove payload

The workspace board's remove handler was missing the folder field
in the removeItems payload, while the regular view handler included it.
This could cause silent removal failures for items in non-root folders.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: merge metadata on duplicate move & allow same file in multiple folders

1. move_item_folder: on DuplicateKeyError, merge source metadata
   (note, tags, pinned, workspace_state) into target before deleting
   the source item, preventing silent metadata loss.

2. Mixed-mode dedup: manual items no longer deduplicated against each
   other across folders. The seen set now only filters computed items
   that duplicate manual ones.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: use 'is not None' for metadata merge in move_item_folder

The truthiness check 'if val:' skipped merging pinned=False and
empty tags/notes. Changed to 'is not None' to handle boolean and
empty-string fields correctly.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: refresh counts after duplicate move & strip folder on cross-collection drag

1. move_item_folder: call _refresh_collection_counts after deleting
   source item on DuplicateKeyError so items_count stays accurate.

2. Cross-collection sidebar drag: strip folder from payload when
   adding item to a different collection, preventing orphaned folders
   in the target collection.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: move dropInProgress after validation & resilient rename fallback

1. Folder/root drop handlers: set dropInProgress only after
   validating drag data and checking same-folder no-op, preventing
   leaked drag state on cancelled drops.

2. rename_folder fallback loop: wrap each update_one in its own
   try/except so a single DuplicateKeyError doesn't abort remaining
   items.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add DuplicateKeyError fallback stub & only merge meaningful metadata

1. Add DuplicateKeyError stub class in the pymongo-unavailable fallback
   block to prevent NameError in move_item_folder.

2. Metadata merge on duplicate move now only copies meaningful values
   (non-empty note, non-empty tags, pinned=True, non-empty workspace_state)
   to avoid overwriting destination metadata with source defaults.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: correct workspace_state type check from dict to str in merge

workspace_state is a string (e.g. "todo", "in_progress", "done"),
not a dict. The isinstance(dict) check was always False, silently
dropping workspace_state during duplicate-key folder moves.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: prevent double HTML-escaping of folder names & cleanup drag on failure

1. create_folder/update_folder: replace sanitize_input (which calls
   html.escape) with plain strip+truncate. The DB layer validates
   folder names, and the frontend escapes on render — no need to
   HTML-encode before storage.

2. Folder drop handlers: wrap moveItemFolder calls in try/finally
   with clearActiveDragContext() to prevent leaked drag state when
   the API call fails.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: move orphaned items to root after folder rename failures

After the rename fallback loop, any items still carrying the old
folder name (due to DuplicateKeyError or other failures) are moved
to root folder to prevent orphaned items pointing to a non-existent
folder in the collection metadata.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: run backfill migration only once per process

Added class-level _migration_done flag so the drop_index and
update_many backfill operations only execute on first instantiation,
not on every API request.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: only merge source metadata into target when target field is empty

Fetch the destination item before merging so that existing metadata
(note, tags, pinned, workspace_state) on the target is preserved.
Source values are only copied when the target field is empty/missing.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add folder data to touch handler reorder payload

The touch endHandler in wireDnd was not updated with folder support.
Now traverses up to #collectionItems, uses the correct selector for
all sections, and includes folder field in the reorder payload.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: improve folder drag-and-drop reliability

1. Fix dragleave flicker on folder headers by checking relatedTarget
   (same pattern used by sidebar drop handlers).
2. Set dropEffect='move' on dragover for visual feedback.
3. Make entire card draggable (not just the handle) so users can
   drag from anywhere on the card to move it between folders.
4. Add stopPropagation on drop to prevent details/summary toggle.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: prevent duplicate folder event handlers & add dragend on card

1. Use AbortController in wireFolderEvents to remove all prior
   listeners before re-registering on each render, preventing
   duplicate click/drag handlers from stacking up.

2. Add dragend handler on the card element (not just the handle)
   so drag cleanup runs correctly when dragging from card body.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: prevent double-firing drag events from nested draggable elements

Remove draggable="true" from the handle span and keep only on the
article card. Remove handle-specific dragstart/dragend listeners,
keeping only the card-level ones. This prevents event bubbling from
causing duplicate API reorder calls.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: resolve IndexOptionsConflict in rules_storage index creation

rules_storage.py was creating indexes without explicit names, which
conflicted with the same indexes created by database/manager.py with
name 'visual_rules_enabled_idx'. Now uses matching explicit names
and downgrades code 85/86 conflicts to debug level.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: wrap each create_index in its own try-except to avoid skipping remaining indexes

A single try block meant an early index failure (e.g. name conflict)
would silently prevent all subsequent indexes from being created.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

---------

Co-authored-by: Claude <noreply@anthropic.com>
amirbiron added a commit that referenced this pull request Apr 10, 2026
* feat: תמיכה בתיקיות בתוך אוספים (Collections Folders)

הוספת אפשרות לארגן קבצים בתוך אוסף לתיקיות (רמה אחת).
קובץ יכול להופיע גם ב-root וגם בתיקיה (מקומות מרובים).
תיקיות מוצגות כקבוצות מתקפלות (accordion) עם drag & drop.

- Database: שדה folder ב-collection_items, מטאדאטה folders ב-user_collections
- API: endpoints חדשים ל-CRUD תיקיות (create/rename/delete/reorder)
- Frontend: רינדור accordion, מודאל יצירת תיקיה, inline rename, drag & drop
- CSS: סגנונות לתיקיות מתקפלות עם RTL ו-responsive
- Shared view: תצוגת תיקיות בצפייה ציבורית (read-only)

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים בתיקיות אוספים (Bugbot PR #3143)

1. Mixed mode dedup: מפתח dedup חזר ל-2-tuple (source, file_name) בלבד
   כדי למנוע כפילויות כשקובץ ידני בתיקיה ומחושב ב-root
2. Route conflict: הוספת ולידציה לשמות תיקיות שמורים ("reorder")
   כדי למנוע התנגשות עם endpoint סידור תיקיות
3. Drag reorder: איסוף פריטים מכל ה-sections (root + תיקיות)
   כדי לא לאבד פריטים מתיקיות אחרות בעת שליחת סדר לשרת

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים נוספים בתיקיות אוספים (Bugbot סבב 2)

1. Missing migration backfill: פריטים קיימים ללא folder מקבלים "" (root)
   כדי למנוע אי-התאמה בין null ל-"" באינדקס MongoDB
2. Cross-folder drag metadata: endpoint חדש move_item_folder שמעביר
   פריט בין תיקיות עם שמירת כל המטאדאטה (notes, tags, pinned)
   במקום add+remove שמאבד מידע
3. Root drop zone: container ה-root מרונדר תמיד (גם כשריק)
   כדי שתמיד יהיה אזור drop לגרירת פריטים חזרה ל-root

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: הסתרת פרטי exception מתגובת API (CodeQL)

move_item_folder החזיר str(e) ישירות למשתמש — חשיפת מידע רגיש.
מוחלף בהודעת שגיאה גנרית + לוג פנימי.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: תיקון 3 באגים נוספים בתיקיות אוספים (Bugbot סבב 3)

1. Empty state: כשאין פריטים ואין תיקיות, buildFolderGroupedHtml מחזיר
   ריק כדי שהודעת "אין פריטים" תוצג כמו לפני השינוי
2. Folder drop race: סימון dropInProgress בעת drop על תיקיה/root
   כדי שדragend לא ישלח reorder מיותר עם סדר ישן
3. Add-folder button: מוסתר ב-workspace collections כי תיקיות
   לא רלוונטיות שם

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: handle DuplicateKeyError in move_item_folder

When moving an item to a folder where the same file already exists,
the unique index throws DuplicateKeyError. Now catches it gracefully
by removing the source item (target already has the file).

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add missing folder field to workspace remove payload

The workspace board's remove handler was missing the folder field
in the removeItems payload, while the regular view handler included it.
This could cause silent removal failures for items in non-root folders.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: merge metadata on duplicate move & allow same file in multiple folders

1. move_item_folder: on DuplicateKeyError, merge source metadata
   (note, tags, pinned, workspace_state) into target before deleting
   the source item, preventing silent metadata loss.

2. Mixed-mode dedup: manual items no longer deduplicated against each
   other across folders. The seen set now only filters computed items
   that duplicate manual ones.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: use 'is not None' for metadata merge in move_item_folder

The truthiness check 'if val:' skipped merging pinned=False and
empty tags/notes. Changed to 'is not None' to handle boolean and
empty-string fields correctly.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: refresh counts after duplicate move & strip folder on cross-collection drag

1. move_item_folder: call _refresh_collection_counts after deleting
   source item on DuplicateKeyError so items_count stays accurate.

2. Cross-collection sidebar drag: strip folder from payload when
   adding item to a different collection, preventing orphaned folders
   in the target collection.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: move dropInProgress after validation & resilient rename fallback

1. Folder/root drop handlers: set dropInProgress only after
   validating drag data and checking same-folder no-op, preventing
   leaked drag state on cancelled drops.

2. rename_folder fallback loop: wrap each update_one in its own
   try/except so a single DuplicateKeyError doesn't abort remaining
   items.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add DuplicateKeyError fallback stub & only merge meaningful metadata

1. Add DuplicateKeyError stub class in the pymongo-unavailable fallback
   block to prevent NameError in move_item_folder.

2. Metadata merge on duplicate move now only copies meaningful values
   (non-empty note, non-empty tags, pinned=True, non-empty workspace_state)
   to avoid overwriting destination metadata with source defaults.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: correct workspace_state type check from dict to str in merge

workspace_state is a string (e.g. "todo", "in_progress", "done"),
not a dict. The isinstance(dict) check was always False, silently
dropping workspace_state during duplicate-key folder moves.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: prevent double HTML-escaping of folder names & cleanup drag on failure

1. create_folder/update_folder: replace sanitize_input (which calls
   html.escape) with plain strip+truncate. The DB layer validates
   folder names, and the frontend escapes on render — no need to
   HTML-encode before storage.

2. Folder drop handlers: wrap moveItemFolder calls in try/finally
   with clearActiveDragContext() to prevent leaked drag state when
   the API call fails.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: move orphaned items to root after folder rename failures

After the rename fallback loop, any items still carrying the old
folder name (due to DuplicateKeyError or other failures) are moved
to root folder to prevent orphaned items pointing to a non-existent
folder in the collection metadata.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: run backfill migration only once per process

Added class-level _migration_done flag so the drop_index and
update_many backfill operations only execute on first instantiation,
not on every API request.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: only merge source metadata into target when target field is empty

Fetch the destination item before merging so that existing metadata
(note, tags, pinned, workspace_state) on the target is preserved.
Source values are only copied when the target field is empty/missing.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: add folder data to touch handler reorder payload

The touch endHandler in wireDnd was not updated with folder support.
Now traverses up to #collectionItems, uses the correct selector for
all sections, and includes folder field in the reorder payload.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: improve folder drag-and-drop reliability

1. Fix dragleave flicker on folder headers by checking relatedTarget
   (same pattern used by sidebar drop handlers).
2. Set dropEffect='move' on dragover for visual feedback.
3. Make entire card draggable (not just the handle) so users can
   drag from anywhere on the card to move it between folders.
4. Add stopPropagation on drop to prevent details/summary toggle.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: prevent duplicate folder event handlers & add dragend on card

1. Use AbortController in wireFolderEvents to remove all prior
   listeners before re-registering on each render, preventing
   duplicate click/drag handlers from stacking up.

2. Add dragend handler on the card element (not just the handle)
   so drag cleanup runs correctly when dragging from card body.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: prevent double-firing drag events from nested draggable elements

Remove draggable="true" from the handle span and keep only on the
article card. Remove handle-specific dragstart/dragend listeners,
keeping only the card-level ones. This prevents event bubbling from
causing duplicate API reorder calls.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: resolve IndexOptionsConflict in rules_storage index creation

rules_storage.py was creating indexes without explicit names, which
conflicted with the same indexes created by database/manager.py with
name 'visual_rules_enabled_idx'. Now uses matching explicit names
and downgrades code 85/86 conflicts to debug level.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* fix: wrap each create_index in its own try-except to avoid skipping remaining indexes

A single try block meant an early index failure (e.g. name conflict)
would silently prevent all subsequent indexes from being created.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

* feat: persist folder open/closed state across page navigations

Uses localStorage to remember whether each folder is open or closed.
When the user closes a folder and navigates away, it stays closed
when they return.

https://claude.ai/code/session_01EukqPajpmNN2jnAmB5xz9G

---------

Co-authored-by: Claude <noreply@anthropic.com>
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