docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md
Last Updated: 2025-12-23
Status: ✅ Architecture Finalized
Scope: All data persistence related to swimlanes, lists, cards, checklists, checklistItems
The system now enforces clear separation:
Stored in swimlane/list/card/checklist/checklistItem documents. All users see the same value.
| Entity | Properties | Where Stored |
|---|---|---|
| Swimlane | title, color, height, sort, archived | swimlanes.js document |
| List | title, color, width, sort, archived, wipLimit, starred | lists.js document |
| Card | title, color, description, swimlaneId, listId, sort, archived | cards.js document |
| Checklist | title, sort, hideCheckedItems, hideAllItems | checklists.js document |
| ChecklistItem | title, sort, isFinished | checklistItems.js document |
Stored in user.profile or cookies. Each user has their own value, not visible to others.
| Entity | Properties | Where Stored |
|---|---|---|
| User | collapsedSwimlanes | user.profile.collapsedSwimlanes[boardId][swimlaneId] |
| User | collapsedLists | user.profile.collapsedLists[boardId][listId] |
| User | hideMiniCardLabelText | user.profile.hideMiniCardLabelText[boardId] |
| Public User | collapsedSwimlanes | Cookie: wekan-collapsed-swimlanes |
| Public User | collapsedLists | Cookie: wekan-collapsed-lists |
Swimlanes: Added height field
height: {
type: Number,
optional: true,
defaultValue: -1, // -1 = auto-height, 50-2000 = fixed
custom() {
const h = this.value;
if (h !== -1 && (h < 50 || h > 2000)) {
return 'heightOutOfRange';
}
},
}
Lists: Added width field
width: {
type: Number,
optional: true,
defaultValue: 272, // 100-1000 pixels
custom() {
const w = this.value;
if (w < 100 || w > 1000) {
return 'widthOutOfRange';
}
},
}
Status: ✅ Implemented in swimlanes.js and lists.js
Cards already store position per-board:
sort field: decimal number determining order (shared)swimlaneId: which swimlane (shared)listId: which list (shared)Status: ✅ No changes needed
Checklists already store position per-board:
sort field: decimal number determining order (shared)hideCheckedChecklistItems: per-board settinghideAllChecklistItems: per-board settingStatus: ✅ No changes needed
ChecklistItems already store position per-board:
sort field: decimal number determining order (shared)Status: ✅ No changes needed
Current State: Users.js still has per-user width/height methods that read from user.profile:
getListWidth(boardId, listId) - reads user.profile.listWidthsgetSwimlaneHeight(boardId, swimlaneId) - reads user.profile.swimlaneHeightssetListWidth(boardId, listId, width) - writes to user.profile.listWidthssetSwimlaneHeight(boardId, swimlaneId, height) - writes to user.profile.swimlaneHeightsRequired Change:
Status: ⏳ Pending - See IMPLEMENTATION_GUIDE.md for details
Current State: No migration exists to move existing per-user data to per-board
Required:
server/migrations/migrateToPerBoardStorage.jsStatus: ⏳ Pending - Template available in IMPLEMENTATION_GUIDE.md
// Swimlane document (per-board)
{
_id: 'swim123',
title: 'Development',
boardId: 'board123',
// height stored in user profile (per-user) - WRONG!
}
// User A profile (per-user)
{
_id: 'userA',
profile: {
swimlaneHeights: {
'board123': {
'swim123': 300 // Only User A sees 300px height
}
}
}
}
// User B profile (per-user)
{
_id: 'userB',
profile: {
swimlaneHeights: {
'board123': {
'swim123': 400 // Only User B sees 400px height
}
}
}
}
// Swimlane document (per-board - ALL USERS SEE THIS)
{
_id: 'swim123',
title: 'Development',
boardId: 'board123',
height: 300 // All users see 300px height
}
// User A profile (per-user - only User A's preferences)
{
_id: 'userA',
profile: {
collapsedSwimlanes: {
'board123': {
'swim123': false // User A: swimlane not collapsed
}
},
collapsedLists: { ... },
hideMiniCardLabelText: { ... }
// height and width REMOVED - now in documents
}
}
// User B profile (per-user - only User B's preferences)
{
_id: 'userB',
profile: {
collapsedSwimlanes: {
'board123': {
'swim123': true // User B: swimlane is collapsed
}
},
collapsedLists: { ... },
hideMiniCardLabelText: { ... }
// height and width REMOVED - now in documents
}
}
Schema Validation
Data Retrieval
Swimlanes.findOne('swim123').height returns correct valueLists.findOne('list456').width returns correct valueData Updates
Swimlanes.update('swim123', { $set: { height: 500 } }) succeedsLists.update('list456', { $set: { width: 400 } }) succeedsPer-User Isolation
height to Swimlaneswidth to Listsuser.profile.swimlaneHeights data will be preserved until migrationuser.profile.listWidths data will be preserved until migration| Document | Purpose |
|---|---|
| DATA_PERSISTENCE_ARCHITECTURE.md | Complete architecture specification |
| IMPLEMENTATION_GUIDE.md | Step-by-step implementation instructions |
| models/swimlanes.js | Swimlane model with new height field |
| models/lists.js | List model with new width field |
After all phases complete:
Status: ✅ Phase 1 Complete, Awaiting Phase 2