···11+# Open Collection Card Removal Analysis
22+33+**Date:** 2026-02-04
44+**Issue:** Cards being removed from open collections when users remove them from their library
55+66+---
77+88+## Original Issue
99+1010+### Observed Behavior
1111+1212+When a user removed a card from their library, the card was also being removed from open collections, even when those collections were created and owned by other users.
1313+1414+**Example Scenario:**
1515+1616+- User A creates Open Collection X
1717+- User B creates Card Y and adds it to Collection X
1818+- User B removes Card Y from their library
1919+- **Result:** Card Y disappears from Collection X (unexpected)
2020+2121+### Expected Behavior
2222+2323+When a user removes a card from their library:
2424+2525+- The card should only be removed from collections owned by the card's author
2626+- Cards should remain in open collections created by other users
2727+2828+---
2929+3030+## Changes Made
3131+3232+### 1. CardLibraryService.ts
3333+3434+**File:** `src/modules/cards/domain/services/CardLibraryService.ts`
3535+3636+**Change:** Added conditional check to only remove from collections when the user removing the card is also the card author.
3737+3838+```typescript
3939+// Only remove from collections if the user removing is the card author
4040+// This ensures only card authors can clean up their own collections
4141+if (card.curatorId.equals(curatorId)) {
4242+ // Get all collections owned by the card's author that contain this card
4343+ const collectionsResult =
4444+ await this.collectionRepository.findByCuratorIdContainingCard(
4545+ card.curatorId, // Changed from curatorId to card.curatorId
4646+ card.cardId,
4747+ );
4848+4949+ // ... remove from collections logic
5050+}
5151+```
5252+5353+**Before:** `findByCuratorIdContainingCard(curatorId, card.cardId)`
5454+5555+- Found collections owned by the person removing the card
5656+5757+**After:** Wrapped in conditional + `findByCuratorIdContainingCard(card.curatorId, card.cardId)`
5858+5959+- Only executes if person removing = card author
6060+- Finds collections owned by the card author
6161+6262+### 2. Test Coverage Added
6363+6464+**File:** `src/modules/cards/tests/application/RemoveCardFromLibraryUseCase.test.ts`
6565+6666+Added three new test cases:
6767+6868+1. **"should not remove from collections when non-author removes card from library"**
6969+ - Alice creates Card A and Collection X
7070+ - Bob adds Card A to his library
7171+ - Bob removes Card A from his library
7272+ - ✅ Card A remains in Alice's Collection X
7373+7474+2. **"should not remove card from open collections owned by others when card author removes from library"**
7575+ - Alice creates Card A
7676+ - Bob creates Open Collection Y
7777+ - Alice adds Card A to Bob's Collection Y
7878+ - Alice removes Card A from her library
7979+ - ⚠️ Card A gets deleted (CASCADE removes from Collection Y)
8080+8181+3. **"should only remove from author-owned collections, not from open collections by others"**
8282+ - Alice creates Card A
8383+ - Alice creates Collection X, Bob creates Collection Y
8484+ - Alice adds Card A to both collections
8585+ - Bob adds Card A to his library (prevents deletion)
8686+ - Alice removes Card A from her library
8787+ - ✅ Card removed from Collection X, remains in Collection Y
8888+8989+---
9090+9191+## Analysis of `findByCuratorIdContainingCard`
9292+9393+### Method Purpose
9494+9595+**File:** `src/modules/cards/infrastructure/repositories/DrizzleCollectionRepository.ts:298-391`
9696+9797+```typescript
9898+async findByCuratorIdContainingCard(
9999+ authorId: CuratorId,
100100+ cardId: CardId,
101101+): Promise<Result<Collection[]>>
102102+```
103103+104104+### SQL Query Logic
105105+106106+```typescript
107107+.where(
108108+ and(
109109+ eq(collections.authorId, authorIdString), // Collection author = provided ID
110110+ eq(collectionCards.cardId, cardIdString), // Collection contains the card
111111+ ),
112112+);
113113+```
114114+115115+Finds collections where:
116116+117117+- **Collection author** equals the provided `authorId` parameter
118118+- **Collection contains** the specified `cardId`
119119+120120+### Conclusion
121121+122122+✅ **Method is working correctly.** It properly filters collections by author ID and did NOT contribute to removing cards from other users' collections through the collection cleanup logic.
123123+124124+---
125125+126126+## Root Cause Discovery: Card Deletion CASCADE
127127+128128+### The Real Culprit
129129+130130+The observed behavior of cards disappearing from other users' collections was NOT caused by the collection cleanup logic, but by **card deletion with database CASCADE**.
131131+132132+**File:** `src/modules/cards/application/useCases/commands/RemoveCardFromLibraryUseCase.ts:112-150`
133133+134134+```typescript
135135+// Handle deletion with proper ordering for URL cards
136136+if (updatedCard.libraryCount === 0 && updatedCard.curatorId.equals(curatorId)) {
137137+ // Delete the card from the database
138138+ const deleteResult = await this.cardRepository.delete(updatedCard.cardId);
139139+}
140140+```
141141+142142+### Deletion Flow
143143+144144+When a user removes a card from their library:
145145+146146+1. Card's `libraryCount` is decremented
147147+2. If `libraryCount === 0` AND user is the card author:
148148+ - Card is **deleted entirely** from the database
149149+3. Database CASCADE (ON DELETE CASCADE) automatically removes the card from ALL collections
150150+4. This includes collections owned by other users
151151+152152+### Example with CASCADE
153153+154154+**Scenario:**
155155+156156+- User B creates Card Y (User B is card author)
157157+- User B adds Card Y to their library (libraryCount = 1)
158158+- User A creates Open Collection X
159159+- User B adds Card Y to Collection X
160160+- User B removes Card Y from library
161161+162162+**What happens:**
163163+164164+1. Card Y's libraryCount becomes 0
165165+2. User B is card author ✓
166166+3. Card Y gets **DELETED**
167167+4. Database CASCADE removes Card Y from Collection X
168168+5. Card Y disappears from User A's collection
169169+170170+### Database Schema
171171+172172+**File:** `src/modules/cards/tests/test-utils/createTestSchema.ts`
173173+174174+```sql
175175+CREATE TABLE IF NOT EXISTS collection_cards (
176176+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
177177+ collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
178178+ card_id UUID NOT NULL REFERENCES cards(id) ON DELETE CASCADE, -- CASCADE here!
179179+ ...
180180+)
181181+```
182182+183183+When a card is deleted, `ON DELETE CASCADE` automatically removes all `collection_cards` entries.
184184+185185+---
186186+187187+## Current State
188188+189189+### What Our Changes Fixed ✅
190190+191191+1. **Collection cleanup logic now correct:**
192192+ - Only card authors can trigger collection cleanup
193193+ - Only removes from collections owned by the card author
194194+ - Non-authors removing cards from their library doesn't affect any collections
195195+196196+2. **Test coverage:**
197197+ - Comprehensive tests verify the new behavior
198198+ - Edge cases covered (non-author removal, mixed collections, etc.)
199199+200200+### What Our Changes DID NOT Fix ⚠️
201201+202202+**Card deletion CASCADE still affects other users' collections:**
203203+204204+Even with our changes, cards are still removed from other users' collections when:
205205+206206+1. Card author removes the card from their library
207207+2. Card's `libraryCount` drops to 0
208208+3. Card gets deleted (because author is removing and libraryCount = 0)
209209+4. CASCADE deletion removes from ALL collections, including those owned by others
210210+211211+**Example:**
212212+213213+- User B creates Card Y
214214+- User B adds to their library (libraryCount = 1)
215215+- User A adds Card Y to their Open Collection X
216216+- User B removes Card Y from library
217217+- Card Y deleted (libraryCount = 0, curator = owner)
218218+- CASCADE removes from Collection X ❌
219219+220220+---
221221+222222+## Recommendations
223223+224224+### Option 1: Prevent Deletion if Card Exists in ANY Collections
225225+226226+**Change:** Don't delete cards if they exist in any collection, regardless of ownership.
227227+228228+```typescript
229229+// Before deletion check:
230230+const collections = await this.collectionRepository.findByCardId(card.cardId);
231231+if (collections.length > 0) {
232232+ // Don't delete - card is in use in collections
233233+ return ok(card);
234234+}
235235+236236+// Only delete if not in any collections
237237+if (updatedCard.libraryCount === 0 && updatedCard.curatorId.equals(curatorId)) {
238238+ // Safe to delete
239239+}
240240+```
241241+242242+**Pros:**
243243+244244+- Cards remain accessible in collections even if removed from all libraries
245245+- Preserves curated collections
246246+247247+**Cons:**
248248+249249+- Cards may accumulate without being in any library
250250+- Need cleanup mechanism for orphaned cards
251251+252252+### Option 2: Only Prevent if in Card Author's Collections
253253+254254+**Change:** Don't delete cards if they exist in collections owned by the card author.
255255+256256+```typescript
257257+const authorCollections =
258258+ await this.collectionRepository.findByCuratorIdContainingCard(
259259+ card.curatorId,
260260+ card.cardId,
261261+ );
262262+263263+if (authorCollections.length > 0) {
264264+ // Don't delete - card is in author's collections
265265+ return ok(card);
266266+}
267267+```
268268+269269+**Pros:**
270270+271271+- Card authors maintain control over their own collections
272272+- Still allows deletion if only in others' collections
273273+274274+**Cons:**
275275+276276+- Cards can still disappear from other users' collections
277277+- Doesn't solve the original issue
278278+279279+### Option 3: Soft Delete with Visibility Flag
280280+281281+**Change:** Mark cards as "deleted" but keep them in database.
282282+283283+```typescript
284284+// Instead of deleting:
285285+card.markAsDeleted();
286286+await this.cardRepository.save(card);
287287+288288+// Collections can still reference the card
289289+// UI can choose to hide/show deleted cards
290290+```
291291+292292+**Pros:**
293293+294294+- No CASCADE issues
295295+- Collections maintain integrity
296296+- Can implement "undelete" functionality
297297+298298+**Cons:**
299299+300300+- More complex implementation
301301+- Need to handle deleted cards in queries
302302+303303+### Option 4: Collection-Based Ownership
304304+305305+**Change:** Cards exist as long as they're in at least one collection OR library.
306306+307307+```typescript
308308+const totalReferences =
309309+ updatedCard.libraryCount +
310310+ (await this.collectionRepository.findByCardId(card.cardId)).length;
311311+312312+if (totalReferences === 0 && updatedCard.curatorId.equals(curatorId)) {
313313+ // Only delete if not in ANY library or collection
314314+ await this.cardRepository.delete(updatedCard.cardId);
315315+}
316316+```
317317+318318+**Pros:**
319319+320320+- Natural model - collections are "collections of cards"
321321+- Solves the CASCADE issue
322322+- Cards exist as long as they're referenced anywhere
323323+324324+**Cons:**
325325+326326+- Changes the ownership model
327327+- Need to handle card updates when author removes from library
328328+329329+---
330330+331331+## Summary
332332+333333+### Key Findings
334334+335335+1. ✅ `findByCuratorIdContainingCard` is working correctly
336336+2. ✅ Collection cleanup logic now only targets card author's collections
337337+3. ⚠️ Card deletion CASCADE is the root cause of cards disappearing from other users' collections
338338+4. ⚠️ Our changes fixed collection cleanup but NOT CASCADE deletion
339339+340340+### Next Decision Point
341341+342342+**Should cards be deleted when they exist in collections owned by other users?**
343343+344344+This is a product/design decision that affects:
345345+346346+- Card ownership model
347347+- Collection integrity
348348+- User expectations
349349+- Database cleanup strategy
350350+351351+Current implementation: Cards are deleted when `libraryCount = 0` and user is card author, regardless of collection membership.
352352+353353+### Files Modified
354354+355355+- `src/modules/cards/domain/services/CardLibraryService.ts`
356356+- `src/modules/cards/tests/application/RemoveCardFromLibraryUseCase.test.ts`
357357+358358+### Type Check
359359+360360+✅ All type checks pass: `npm run build:check`