18 KiB
Wekan Persistence Audit Report
Overview
This document audits the persistence mechanisms for Wekan board data, including swimlanes, lists, cards, checklists, and their properties (order, color, background, titles, etc.), as well as per-user settings.
1. BOARD-LEVEL PERSISTENCE (Persisted Across All Users)
1.1 Swimlanes
Collection: swimlanes (models/swimlanes.js)
Persisted Fields:
- ✅
title- Swimlane title (viarename()mutation) - ✅
sort- Swimlane ordering/position (decimal number) - ✅
color- Swimlane color (viasetColor()mutation) - ✅
collapsed- Swimlane collapsed state (viacollapse()mutation) ⚠️ See note below - ✅
archived- Swimlane archived status
Persistence Mechanism:
- Direct MongoDB updates via
Swimlanes.update()andSwimlanes.direct.update() - Automatic timestamps:
updatedAt,modifiedAtfields - Activity tracking for title changes and archive/restore operations
Issues Found:
- ⚠️ ISSUE:
collapsedfield in swimlanes.js line 127 is set todefaultValue: falsebut theisCollapsed()helper (line 251-263) checks for per-user stored values. This creates a mismatch between board-level and per-user storage.
1.2 Lists
Collection: lists (models/lists.js)
Persisted Fields:
- ✅
title- List title - ✅
sort- List ordering/position (decimal number) - ✅
color- List color - ✅
collapsed- List collapsed state (board-wide via REST API line 768-775) ⚠️ See note below - ✅
starred- List starred status - ✅
wipLimit- WIP limit configuration - ✅
archived- List archived status
Persistence Mechanism:
- Direct MongoDB updates via
Lists.update()andLists.direct.update() - Automatic timestamps:
updatedAt,modifiedAt - Activity tracking for title changes, archive/restore
Issues Found:
- ⚠️ ISSUE: Similar to swimlanes,
collapsedfield (line 147) defaults tofalsebut theisCollapsed()helper (line 303-311) also checks for per-user stored values. The REST API allows board-level collapsed state updates (line 768-775), but client also stores per-user viagetCollapsedListFromStorage(). - ⚠️ ISSUE: The
swimlaneIdfield is part of the list (line 48), butdraggableLists()method (line 275) filters by board only, suggesting lists are shared across swimlanes rather than per-swimlane.
1.3 Cards
Collection: cards (models/cards.js)
Persisted Fields:
- ✅
title- Card title - ✅
sort- Card ordering/position within list - ✅
color- Card color (viasetColor()mutation, line 2268) - ✅
boardId,swimlaneId,listId- Card location - ✅
archived- Card archived status - ✅
description- Card description - ✅ Custom fields, labels, members, assignees, etc.
Persistence Mechanism:
move()method (line 2063+) handles reordering and moving cards across swimlanes/lists/boards- Automatic timestamp updates via
modifiedAt,dateLastActivity - Activity tracking for moves, title changes, etc.
- Attachment metadata updated alongside card moves (line 2101-2115)
Issues Found:
- ✅ OK: Order/sort persistence working correctly via card.move() and card.moveOptionalArgs()
- ✅ OK: Color persistence working correctly
- ✅ OK: Title changes persisted automatically
1.4 Checklists
Collection: checklists (models/checklists.js)
Persisted Fields:
- ✅
title- Checklist title (viasetTitle()mutation) - ✅
sort- Checklist ordering (decimal number) - ✅
hideCheckedChecklistItems- Toggle for hiding checked items - ✅
hideAllChecklistItems- Toggle for hiding all items
Persistence Mechanism:
- Direct MongoDB updates via
Checklists.update() - Automatic timestamps:
createdAt,modifiedAt - Activity tracking for creation and removal
1.5 Checklist Items
Collection: checklistItems (models/checklistItems.js)
Persisted Fields:
- ✅
title- Item text (viasetTitle()mutation) - ✅
sort- Item ordering within checklist (decimal number) - ✅
isFinished- Item completion status (viacheck(),uncheck(),toggleItem()mutations)
Persistence Mechanism:
move()mutation (line 159-168) handles reordering within checklists- Direct MongoDB updates via
ChecklistItems.update() - Automatic timestamps:
createdAt,modifiedAt - Activity tracking for item creation/removal and completion state changes
Issue Found:
- ✅ OK: Item order and completion status persist correctly
1.6 Position History Tracking
Collection: positionHistory (models/positionHistory.js)
Purpose: Tracks original positions of swimlanes, lists, and cards before changes
Features:
- ✅ Stores original
sortposition - ✅ Stores original titles
- ✅ Supports swimlanes, lists, and cards
- ✅ Provides helpers to check if entity moved from original position
Implementation Notes:
- Swimlanes track position automatically on insert (swimlanes.js line 387-393)
- Lists track position automatically on insert (lists.js line 487+)
- Can detect moves via
hasMoved()andhasMovedFromOriginalPosition()helpers
2. PER-USER SETTINGS (NOT Persisted Across Boards)
2.1 Per-Board, Per-User Settings
Storage: User profile subdocuments (models/users.js)
A. List Widths
- Field:
profile.listWidths(line 527) - Structure:
listWidths[boardId][listId] = width - Persistence: Via
setListWidth()mutation (line 1834) - Retrieval:
getListWidth(),getListWidthFromStorage()(line 1288-1313) - Constraints: Also stored in
profile.listConstraints - ✅ Status: Working correctly
B. Swimlane Heights
- Field:
profile.swimlaneHeights(searchable in line 1047+) - Structure:
swimlaneHeights[boardId][swimlaneId] = height - Persistence: Via
setSwimlaneHeight()mutation (line 1878) - Retrieval:
getSwimlaneHeight(),getSwimlaneHeightFromStorage()(line 1050-1080) - ✅ Status: Working correctly
C. Collapsed Swimlanes (Per-User)
- Field:
profile.collapsedSwimlanes(line 1900) - Structure:
collapsedSwimlanes[boardId][swimlaneId] = boolean - Persistence: Via
setCollapsedSwimlane()mutation (line 1900-1906) - Retrieval:
getCollapsedSwimlaneFromStorage()(swimlanes.js line 251-263) - Client-Side Fallback:
Users.getPublicCollapsedSwimlane()for public/non-logged-in users (users.js line 60-73) - ✅ Status: Working correctly for logged-in users
D. Collapsed Lists (Per-User)
- Field:
profile.collapsedLists(line 1893) - Structure:
collapsedLists[boardId][listId] = boolean - Persistence: Via
setCollapsedList()mutation (line 1893-1899) - Retrieval:
getCollapsedListFromStorage()(lists.js line 303-311) - Client-Side Fallback:
Users.getPublicCollapsedList()for public users (users.js line 44-52) - ✅ Status: Working correctly for logged-in users
E. Card Collapsed State (Global Per-User)
- Field:
profile.cardCollapsed(line 267) - Persistence: Via
setCardCollapsed()method (line 2088-2091) - Retrieval:
cardCollapsed()helper in cardDetails.js (line 100-107) - Client-Side Fallback:
Users.getPublicCardCollapsed()for public users (users.js line 80-85) - ✅ Status: Working correctly (applies to all boards for a user)
F. Card Maximized State (Global Per-User)
- Field:
profile.cardMaximized(line 260) - Persistence: Via
toggleCardMaximized()mutation (line 1720-1726) - Retrieval:
hasCardMaximized()helper (line 1194-1196) - ✅ Status: Working correctly
G. Board Workspace Trees (Global Per-User)
- Field:
profile.boardWorkspacesTree(line 1981-2026) - Purpose: Stores nested workspace structure for organizing boards
- Persistence: Via
setWorkspacesTree()method (line 1995-2000) - ✅ Status: Working correctly
H. Board Workspace Assignments (Global Per-User)
- Field:
profile.boardWorkspaceAssignments(line 2002-2011) - Purpose: Maps each board to a workspace ID
- Persistence: Via
assignBoardToWorkspace()andunassignBoardFromWorkspace()methods - ✅ Status: Working correctly
I. All Boards Workspaces Setting
- Field:
profile.boardView(line 1807) - Persistence: Via
setBoardView()method (line 1805-1809) - Description: Per-user preference for "All Boards" view style
- ✅ Status: Working correctly
2.2 Client-Side Storage (Non-Logged-In Users)
Storage Methods:
-
Cookies (via
readCookieMap()/writeCookieMap()):wekan-collapsed-lists- Collapsed list states (users.js line 44-58)wekan-collapsed-swimlanes- Collapsed swimlane states
-
localStorage:
wekan-list-widths- List widths (getListWidthFromStorage, line 1316-1327)wekan-swimlane-heights- Swimlane heights (setSwimlaneHeightToStorage, line 1100-1123)
Coverage:
- ✅ Collapse status for lists and swimlanes
- ✅ Width constraints for lists
- ✅ Height constraints for swimlanes
- ❌ Card collapsed state (only via cookies, fallback available)
3. CRITICAL FINDINGS & ISSUES
3.1 HIGH PRIORITY ISSUES
Issue #1: Collapsed State Inconsistency (Swimlanes)
Severity: HIGH
Location: models/swimlanes.js lines 127, 251-263
Problem:
- The swimlane schema defines
collapsedas a board-level field (defaults to false) - But the
isCollapsed()helper prioritizes per-user stored values from the user profile - This creates confusion: is collapsed state board-wide or per-user?
Expected Behavior: Per-user settings should be stored in profile.collapsedSwimlanes, not in the swimlane document itself.
Recommendation:
// CURRENT (WRONG):
collapsed: {
type: Boolean,
defaultValue: false, // Board-wide field
},
// SUGGESTED (CORRECT):
// Remove 'collapsed' from swimlane schema
// Only store per-user state in profile.collapsedSwimlanes
Issue #2: Collapsed State Inconsistency (Lists)
Severity: HIGH
Location: models/lists.js lines 147, 303-311
Problem:
- Similar to swimlanes, lists have a board-level
collapsedfield - REST API allows updating this field (line 768-775)
- But
isCollapsed()helper checks per-user values first - Migrations copy
collapsedstatus between lists (fixMissingListsMigration.js line 165)
Recommendation: Clarify whether collapsed state should be:
- Option A: Board-level only (remove per-user override)
- Option B: Per-user only (remove board-level field)
- Option C: Hybrid with clear precedence rules
Issue #3: Swimlane/List Organization Model Unclear
Severity: MEDIUM
Location: models/lists.js lines 48, 201-230, 275
Problem:
- Lists have a
swimlaneIdfield butdraggableLists()filters byboardIdonly - Some methods reference
myLists()which filters by bothboardIdandswimlaneId - Migrations suggest lists were transitioning from per-swimlane to shared-across-swimlane model
Questions:
- Are lists shared across all swimlanes or isolated to each swimlane?
- What happens when dragging a list to a different swimlane?
Recommendation: Document the intended architecture clearly.
3.2 MEDIUM PRIORITY ISSUES
Issue #4: Position History Only Tracks Original Position
Severity: MEDIUM
Location: models/positionHistory.js
Problem:
- Position history tracks the original position when an entity is created
- It does NOT track subsequent moves/reorders
- Historical audit trail of all position changes is lost
Impact: Cannot determine full history of where a card/list was located over time
Recommendation: Consider extending to track all position changes with timestamps.
Issue #5: Card Collapsed State is Global Per-User, Not Per-Card
Severity: LOW
Location: models/users.js line 267, users.js line 2088-2091
Problem:
profile.cardCollapsedis a single boolean affecting all cards for a user- It's not per-card or per-board, just a global toggle
- Name is misleading
Recommendation: Consider renaming to cardDetailsCollapsedByDefault or similar.
Issue #6: Public User Settings Storage Incomplete
Severity: MEDIUM
Location: models/users.js lines 44-85
Problem:
- Cookie-based storage for public users only covers:
- Collapsed lists
- Collapsed swimlanes
- Missing storage for:
- List widths
- Swimlane heights
- Card collapsed state
Impact: Public/non-logged-in users lose UI preferences on page reload
Recommendation: Implement localStorage storage for all per-user preferences.
3.3 VERIFICATION CHECKLIST
| Item | Status | Notes |
|---|---|---|
| Swimlane order persistence | ✅ | Via sort field, board-level |
| List order persistence | ✅ | Via sort field, board-level |
| Card order persistence | ✅ | Via sort field, card.move() |
| Checklist order persistence | ✅ | Via sort field |
| Checklist item order persistence | ✅ | Via sort field, ChecklistItems.move() |
| Swimlane color changes | ✅ | Via setColor() mutation |
| List color changes | ✅ | Via REST API or direct update |
| Card color changes | ✅ | Via setColor() mutation |
| Swimlane title changes | ✅ | Via rename() mutation, activity tracked |
| List title changes | ✅ | Via REST API or rename() mutation, activity tracked |
| Card title changes | ✅ | Via direct update, activity tracked |
| Checklist title changes | ✅ | Via setTitle() mutation |
| Checklist item title changes | ✅ | Via setTitle() mutation |
| Per-user list widths | ✅ | Via profile.listWidths |
| Per-user swimlane heights | ✅ | Via profile.swimlaneHeights |
| Per-user swimlane collapse state | ✅ | Via profile.collapsedSwimlanes |
| Per-user list collapse state | ✅ | Via profile.collapsedLists |
| Per-user card collapse state | ✅ | Via profile.cardCollapsed |
| Per-user board workspace organization | ✅ | Via profile.boardWorkspacesTree |
| Activity logging for changes | ✅ | Via Activities collection |
4. RECOMMENDATIONS
4.1 Immediate Actions
-
Clarify Collapsed State Architecture
- Decide if collapsed state should be per-user or board-wide
- Update swimlanes.js and lists.js schema accordingly
- Update documentation
-
Complete Public User Storage
- Implement localStorage for list widths/swimlane heights for non-logged-in users
- Test persistence across page reloads
-
Review Position History Usage
- Confirm if current position history implementation meets requirements
- Consider extending to track all changes (not just original position)
4.2 Long-Term Improvements
-
Audit Trail Feature
- Extend position history to track all moves with timestamps
- Enable board managers to see complete history of card/list movements
-
Data Integrity Tests
- Add integration tests to verify:
- Order is persisted correctly after drag-drop
- Color changes persist across sessions
- Per-user settings apply only to correct user
- Per-user settings don't leak across boards
- Add integration tests to verify:
-
Database Indexes
- Verify indexes exist for common queries:
sortfields for swimlanes, lists, cards, checklistsboardIdfields for filtering
- Verify indexes exist for common queries:
4.3 Code Quality Improvements
-
Document Persistence Model
- Add clear comments explaining which fields are board-level vs. per-user
- Document swimlane/list relationship model
-
Consistent Naming
- Rename misleading field names (e.g.,
cardCollapsed) - Align method names with actual functionality
- Rename misleading field names (e.g.,
5. SUMMARY TABLE
Board-Level Persistence (Shared Across Users)
| Entity | Field | Type | Persisted | Notes |
|---|---|---|---|---|
| Swimlane | title | Text | ✅ | Via rename() |
| Swimlane | sort | Number | ✅ | For ordering |
| Swimlane | color | String | ✅ | Via setColor() |
| Swimlane | collapsed | Boolean | ⚠️ | Issue #1: Conflicts with per-user storage |
| Swimlane | archived | Boolean | ✅ | Via archive()/restore() |
| List | title | Text | ✅ | Via rename() or REST |
| List | sort | Number | ✅ | For ordering |
| List | color | String | ✅ | Via REST or update |
| List | collapsed | Boolean | ⚠️ | Issue #2: Conflicts with per-user storage |
| List | starred | Boolean | ✅ | Via REST or update |
| List | wipLimit | Object | ✅ | Via REST or setWipLimit() |
| List | archived | Boolean | ✅ | Via archive() |
| Card | title | Text | ✅ | Direct update |
| Card | sort | Number | ✅ | Via move() |
| Card | color | String | ✅ | Via setColor() |
| Card | boardId/swimlaneId/listId | String | ✅ | Via move() |
| Card | archived | Boolean | ✅ | Via archive() |
| Card | description | Text | ✅ | Direct update |
| Card | customFields | Array | ✅ | Direct update |
| Checklist | title | Text | ✅ | Via setTitle() |
| Checklist | sort | Number | ✅ | Direct update |
| Checklist | hideCheckedChecklistItems | Boolean | ✅ | Via toggle mutation |
| Checklist | hideAllChecklistItems | Boolean | ✅ | Via toggle mutation |
| ChecklistItem | title | Text | ✅ | Via setTitle() |
| ChecklistItem | sort | Number | ✅ | Via move() |
| ChecklistItem | isFinished | Boolean | ✅ | Via check/uncheck/toggle |
Per-User Settings (NOT Persisted Across Boards)
| Setting | Storage | Scope | Notes |
|---|---|---|---|
| List Widths | profile.listWidths | Per-board, per-user | ✅ Working |
| Swimlane Heights | profile.swimlaneHeights | Per-board, per-user | ✅ Working |
| Collapsed Swimlanes | profile.collapsedSwimlanes | Per-board, per-user | ✅ Working |
| Collapsed Lists | profile.collapsedLists | Per-board, per-user | ✅ Working |
| Card Collapsed State | profile.cardCollapsed | Global per-user | ⚠️ Name misleading |
| Card Maximized State | profile.cardMaximized | Global per-user | ✅ Working |
| Board Workspaces | profile.boardWorkspacesTree | Global per-user | ✅ Working |
| Board Workspace Assignments | profile.boardWorkspaceAssignments | Global per-user | ✅ Working |
| Board View Style | profile.boardView | Global per-user | ✅ Working |
Document History
- Created: 2025-12-23
- Status: Initial Audit Complete
- Reviewed: Swimlanes, Lists, Cards, Checklists, ChecklistItems, PositionHistory, Users
- Next Review: After addressing high-priority issues